1 /*
2  * Copyright (C) 2019 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.server.wm;
18 
19 
20 import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_EMBEDDED_WINDOWS;
21 import static com.android.server.wm.IdentifierProto.HASH_CODE;
22 import static com.android.server.wm.IdentifierProto.TITLE;
23 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
24 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
25 import static com.android.server.wm.WindowStateProto.IDENTIFIER;
26 
27 import android.annotation.NonNull;
28 import android.annotation.Nullable;
29 import android.os.IBinder;
30 import android.os.RemoteException;
31 import android.util.ArrayMap;
32 import android.util.Slog;
33 import android.util.proto.ProtoOutputStream;
34 import android.view.InputApplicationHandle;
35 import android.view.InputChannel;
36 import android.window.InputTransferToken;
37 
38 import com.android.internal.protolog.common.ProtoLog;
39 import com.android.server.input.InputManagerService;
40 
41 /**
42  * Keeps track of embedded windows.
43  *
44  * If the embedded window does not receive input then Window Manager does not keep track of it.
45  * But if they do receive input, we keep track of the calling PID to blame the right app and
46  * the host window to send pointerDownOutsideFocus.
47  */
48 class EmbeddedWindowController {
49     private static final String TAG = TAG_WITH_CLASS_NAME ? "EmbeddedWindowController" : TAG_WM;
50     /* maps input token to an embedded window */
51     private ArrayMap<IBinder /*input token */, EmbeddedWindow> mWindows = new ArrayMap<>();
52     private ArrayMap<InputTransferToken /*input transfer token */, EmbeddedWindow>
53             mWindowsByInputTransferToken = new ArrayMap<>();
54     private ArrayMap<IBinder /*window token*/, EmbeddedWindow> mWindowsByWindowToken =
55         new ArrayMap<>();
56     private final Object mGlobalLock;
57     private final ActivityTaskManagerService mAtmService;
58 
59     private final InputManagerService mInputManagerService;
60 
EmbeddedWindowController(ActivityTaskManagerService atmService, InputManagerService inputManagerService)61     EmbeddedWindowController(ActivityTaskManagerService atmService,
62             InputManagerService inputManagerService) {
63         mAtmService = atmService;
64         mGlobalLock = atmService.getGlobalLock();
65         mInputManagerService = inputManagerService;
66     }
67 
68     /**
69      * Adds a new embedded window.
70      *
71      * @param inputToken input channel token passed in by the embedding process when it requests
72      *                   the server to add an input channel to the embedded surface.
73      * @param window An {@link EmbeddedWindow} object to add to this controller.
74      */
add(IBinder inputToken, EmbeddedWindow window)75     void add(IBinder inputToken, EmbeddedWindow window) {
76         try {
77             mWindows.put(inputToken, window);
78             final InputTransferToken inputTransferToken = window.getInputTransferToken();
79             mWindowsByInputTransferToken.put(inputTransferToken, window);
80             final IBinder windowToken = window.getWindowToken();
81             mWindowsByWindowToken.put(windowToken, window);
82             updateProcessController(window);
83             window.mClient.linkToDeath(()-> {
84                 synchronized (mGlobalLock) {
85                     mWindows.remove(inputToken);
86                     mWindowsByInputTransferToken.remove(inputTransferToken);
87                     mWindowsByWindowToken.remove(windowToken);
88                 }
89             }, 0);
90         } catch (RemoteException e) {
91             // The caller has died, remove from the map
92             mWindows.remove(inputToken);
93         }
94     }
95 
96     /**
97      * Track the host activity in the embedding process so we can determine if the
98      * process is currently showing any UI to the user.
99      */
updateProcessController(EmbeddedWindow window)100     private void updateProcessController(EmbeddedWindow window) {
101         if (window.mHostActivityRecord == null) {
102             return;
103         }
104         final WindowProcessController processController =
105                 mAtmService.getProcessController(window.mOwnerPid, window.mOwnerUid);
106         if (processController == null) {
107             Slog.w(TAG, "Could not find the embedding process.");
108         } else {
109             processController.addHostActivity(window.mHostActivityRecord);
110         }
111     }
112 
remove(IBinder client)113     void remove(IBinder client) {
114         for (int i = mWindows.size() - 1; i >= 0; i--) {
115             EmbeddedWindow ew = mWindows.valueAt(i);
116             if (ew.mClient == client) {
117                 mWindows.removeAt(i).onRemoved();
118                 mWindowsByInputTransferToken.remove(ew.getInputTransferToken());
119                 mWindowsByWindowToken.remove(ew.getWindowToken());
120                 return;
121             }
122         }
123     }
124 
onWindowRemoved(WindowState host)125     void onWindowRemoved(WindowState host) {
126         for (int i = mWindows.size() - 1; i >= 0; i--) {
127             EmbeddedWindow ew = mWindows.valueAt(i);
128             if (ew.mHostWindowState == host) {
129                 mWindows.removeAt(i).onRemoved();
130                 mWindowsByInputTransferToken.remove(ew.getInputTransferToken());
131                 mWindowsByWindowToken.remove(ew.getWindowToken());
132             }
133         }
134     }
135 
get(IBinder inputToken)136     EmbeddedWindow get(IBinder inputToken) {
137         return mWindows.get(inputToken);
138     }
139 
getByInputTransferToken(InputTransferToken inputTransferToken)140     EmbeddedWindow getByInputTransferToken(InputTransferToken inputTransferToken) {
141         return mWindowsByInputTransferToken.get(inputTransferToken);
142     }
143 
getByWindowToken(IBinder windowToken)144     EmbeddedWindow getByWindowToken(IBinder windowToken) {
145         return mWindowsByWindowToken.get(windowToken);
146     }
147 
isValidTouchGestureParams(WindowState hostWindowState, EmbeddedWindow embeddedWindow)148     private boolean isValidTouchGestureParams(WindowState hostWindowState,
149             EmbeddedWindow embeddedWindow) {
150         if (embeddedWindow == null) {
151             ProtoLog.w(WM_DEBUG_EMBEDDED_WINDOWS,
152                     "Attempt to transfer touch gesture with non-existent embedded window");
153             return false;
154         }
155         final WindowState wsAssociatedWithEmbedded = embeddedWindow.getWindowState();
156         if (wsAssociatedWithEmbedded == null) {
157             ProtoLog.w(WM_DEBUG_EMBEDDED_WINDOWS,
158                     "Attempt to transfer touch gesture using embedded window with no associated "
159                             + "host");
160             return false;
161         }
162         if (wsAssociatedWithEmbedded.mClient.asBinder() != hostWindowState.mClient.asBinder()) {
163             ProtoLog.w(WM_DEBUG_EMBEDDED_WINDOWS,
164                     "Attempt to transfer touch gesture with host window not associated with "
165                             + "embedded window");
166             return false;
167         }
168 
169         if (embeddedWindow.getInputChannelToken() == null) {
170             ProtoLog.w(WM_DEBUG_EMBEDDED_WINDOWS,
171                     "Attempt to transfer touch gesture using embedded window that has no input "
172                             + "channel");
173             return false;
174         }
175         if (hostWindowState.mInputChannelToken == null) {
176             ProtoLog.w(WM_DEBUG_EMBEDDED_WINDOWS,
177                     "Attempt to transfer touch gesture using a host window with no input channel");
178             return false;
179         }
180         return true;
181     }
182 
transferToHost(@onNull InputTransferToken embeddedWindowToken, @NonNull WindowState transferToHostWindowState)183     boolean transferToHost(@NonNull InputTransferToken embeddedWindowToken,
184             @NonNull WindowState transferToHostWindowState) {
185         EmbeddedWindow ew = getByInputTransferToken(embeddedWindowToken);
186         if (!isValidTouchGestureParams(transferToHostWindowState, ew)) {
187             return false;
188         }
189         return mInputManagerService.transferTouchGesture(ew.getInputChannelToken(),
190                 transferToHostWindowState.mInputChannelToken);
191     }
192 
transferToEmbedded(WindowState hostWindowState, @NonNull InputTransferToken transferToToken)193     boolean transferToEmbedded(WindowState hostWindowState,
194             @NonNull InputTransferToken transferToToken) {
195         final EmbeddedWindowController.EmbeddedWindow ew = getByInputTransferToken(transferToToken);
196         if (!isValidTouchGestureParams(hostWindowState, ew)) {
197             return false;
198         }
199         return mInputManagerService.transferTouchGesture(hostWindowState.mInputChannelToken,
200                 ew.getInputChannelToken());
201     }
202 
203     static class EmbeddedWindow implements InputTarget {
204         final IBinder mClient;
205         @Nullable final WindowState mHostWindowState;
206         @Nullable final ActivityRecord mHostActivityRecord;
207         final String mName;
208         final int mOwnerUid;
209         final int mOwnerPid;
210         final WindowManagerService mWmService;
211         final int mDisplayId;
212         public Session mSession;
213         InputChannel mInputChannel;
214         final int mWindowType;
215 
216         /**
217          * A unique token associated with the embedded window that can be used by the host window
218          * to request focus transfer and gesture transfer to the embedded. This is not the input
219          * token since we don't want to give clients access to each others input token.
220          */
221         private final InputTransferToken mInputTransferToken;
222 
223         private boolean mIsFocusable;
224 
225         /**
226          * @param session  calling session to check ownership of the window
227          * @param clientToken client token used to clean up the map if the embedding process dies
228          * @param hostWindowState input channel token belonging to the host window. This is needed
229          *                        to handle input callbacks to wm. It's used when raising ANR and
230          *                        when the user taps out side of the focused region on screen. This
231          *                        can be null if there is no host window.
232          * @param ownerUid  calling uid
233          * @param ownerPid  calling pid used for anr blaming
234          * @param windowType to forward to input
235          * @param displayId used for focus requests
236          */
EmbeddedWindow(Session session, WindowManagerService service, IBinder clientToken, WindowState hostWindowState, int ownerUid, int ownerPid, int windowType, int displayId, InputTransferToken inputTransferToken, String inputHandleName, boolean isFocusable)237         EmbeddedWindow(Session session, WindowManagerService service, IBinder clientToken,
238                        WindowState hostWindowState, int ownerUid, int ownerPid, int windowType,
239                        int displayId, InputTransferToken inputTransferToken, String inputHandleName,
240                        boolean isFocusable) {
241             mSession = session;
242             mWmService = service;
243             mClient = clientToken;
244             mHostWindowState = hostWindowState;
245             mHostActivityRecord = (mHostWindowState != null) ? mHostWindowState.mActivityRecord
246                     : null;
247             mOwnerUid = ownerUid;
248             mOwnerPid = ownerPid;
249             mWindowType = windowType;
250             mDisplayId = displayId;
251             mInputTransferToken = inputTransferToken;
252             final String hostWindowName =
253                     (mHostWindowState != null) ? "-" + mHostWindowState.getWindowTag().toString()
254                             : "";
255             mIsFocusable = isFocusable;
256             mName = "Embedded{" + inputHandleName + hostWindowName + "}";
257         }
258 
259         @Override
toString()260         public String toString() {
261             return mName;
262         }
263 
getApplicationHandle()264         InputApplicationHandle getApplicationHandle() {
265             if (mHostWindowState == null
266                     || mHostWindowState.mInputWindowHandle.getInputApplicationHandle() == null) {
267                 return null;
268             }
269             return new InputApplicationHandle(
270                     mHostWindowState.mInputWindowHandle.getInputApplicationHandle());
271         }
272 
openInputChannel(@onNull InputChannel outInputChannel)273         void openInputChannel(@NonNull InputChannel outInputChannel) {
274             final String name = toString();
275             mInputChannel = mWmService.mInputManager.createInputChannel(name);
276             mInputChannel.copyTo(outInputChannel);
277         }
278 
onRemoved()279         void onRemoved() {
280             if (mInputChannel != null) {
281                 mWmService.mInputManager.removeInputChannel(mInputChannel.getToken());
282                 mInputChannel.dispose();
283                 mInputChannel = null;
284             }
285             if (mHostActivityRecord != null) {
286                 final WindowProcessController wpc =
287                         mWmService.mAtmService.getProcessController(mOwnerPid, mOwnerUid);
288                 if (wpc != null) {
289                     wpc.removeHostActivity(mHostActivityRecord);
290                 }
291             }
292         }
293 
294         @Override
getWindowState()295         public WindowState getWindowState() {
296             return mHostWindowState;
297         }
298 
299         @Override
getDisplayId()300         public int getDisplayId() {
301             return mDisplayId;
302         }
303 
304         @Override
getDisplayContent()305         public DisplayContent getDisplayContent() {
306             return mWmService.mRoot.getDisplayContent(getDisplayId());
307         }
308 
getWindowToken()309         public IBinder getWindowToken() {
310             return mClient;
311         }
312 
313         @Override
getPid()314         public int getPid() {
315             return mOwnerPid;
316         }
317 
318         @Override
getUid()319         public int getUid() {
320             return mOwnerUid;
321         }
322 
getInputTransferToken()323         InputTransferToken getInputTransferToken() {
324             return mInputTransferToken;
325         }
326 
getInputChannelToken()327         IBinder getInputChannelToken() {
328             if (mInputChannel != null) {
329                 return mInputChannel.getToken();
330             }
331             return null;
332         }
333 
setIsFocusable(boolean isFocusable)334         void setIsFocusable(boolean isFocusable) {
335             mIsFocusable = isFocusable;
336         }
337 
338         /**
339          * When an embedded window is touched when it's not currently focus, we need to switch
340          * focus to that embedded window unless the embedded window was marked as not focusable.
341          */
342         @Override
receiveFocusFromTapOutside()343         public boolean receiveFocusFromTapOutside() {
344             return mIsFocusable;
345         }
346 
handleTap(boolean grantFocus)347         private void handleTap(boolean grantFocus) {
348             if (mInputChannel != null) {
349                 if (mHostWindowState != null) {
350                     // Use null session since this is being granted by system server and doesn't
351                     // require the host session to be passed in
352                     mWmService.grantEmbeddedWindowFocus(null, mHostWindowState.mClient,
353                             mInputTransferToken, grantFocus);
354                     if (grantFocus) {
355                         // If granting focus to the embedded when tapped, we need to ensure the host
356                         // gains focus as well or the transfer won't take effect since it requires
357                         // the host to transfer the focus to the embedded.
358                         mHostWindowState.handleTapOutsideFocusInsideSelf();
359                     }
360                 } else {
361                     mWmService.grantEmbeddedWindowFocus(mSession, mInputTransferToken, grantFocus);
362                 }
363             }
364         }
365 
366         @Override
handleTapOutsideFocusOutsideSelf()367         public void handleTapOutsideFocusOutsideSelf() {
368             handleTap(false);
369         }
370 
371         @Override
handleTapOutsideFocusInsideSelf()372         public void handleTapOutsideFocusInsideSelf() {
373             handleTap(true);
374         }
375 
376         @Override
shouldControlIme()377         public boolean shouldControlIme() {
378             return mHostWindowState != null;
379         }
380 
381         @Override
canScreenshotIme()382         public boolean canScreenshotIme() {
383             return true;
384         }
385 
386         @Override
getImeControlTarget()387         public InsetsControlTarget getImeControlTarget() {
388             if (mHostWindowState != null) {
389                 return mHostWindowState.getImeControlTarget();
390             }
391             return mWmService.getDefaultDisplayContentLocked().mRemoteInsetsControlTarget;
392         }
393 
394         @Override
isInputMethodClientFocus(int uid, int pid)395         public boolean isInputMethodClientFocus(int uid, int pid) {
396             return uid == mOwnerUid && pid == mOwnerPid;
397         }
398 
399         @Override
getActivityRecord()400         public ActivityRecord getActivityRecord() {
401             return mHostActivityRecord;
402         }
403 
404         @Override
dumpProto(ProtoOutputStream proto, long fieldId, @WindowTraceLogLevel int logLevel)405         public void dumpProto(ProtoOutputStream proto, long fieldId,
406                               @WindowTraceLogLevel int logLevel) {
407             final long token = proto.start(fieldId);
408 
409             final long token2 = proto.start(IDENTIFIER);
410             proto.write(HASH_CODE, System.identityHashCode(this));
411             proto.write(TITLE, "EmbeddedWindow");
412             proto.end(token2);
413             proto.end(token);
414         }
415     }
416 }
417