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.server.wm.IdentifierProto.HASH_CODE;
21 import static com.android.server.wm.IdentifierProto.TITLE;
22 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
23 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
24 import static com.android.server.wm.WindowStateProto.IDENTIFIER;
25 
26 import android.annotation.NonNull;
27 import android.annotation.Nullable;
28 import android.os.IBinder;
29 import android.os.RemoteException;
30 import android.util.ArrayMap;
31 import android.util.Slog;
32 import android.util.proto.ProtoOutputStream;
33 import android.view.IWindow;
34 import android.view.InputApplicationHandle;
35 import android.view.InputChannel;
36 
37 /**
38  * Keeps track of embedded windows.
39  *
40  * If the embedded window does not receive input then Window Manager does not keep track of it.
41  * But if they do receive input, we keep track of the calling PID to blame the right app and
42  * the host window to send pointerDownOutsideFocus.
43  */
44 class EmbeddedWindowController {
45     private static final String TAG = TAG_WITH_CLASS_NAME ? "EmbeddedWindowController" : TAG_WM;
46     /* maps input token to an embedded window */
47     private ArrayMap<IBinder /*input token */, EmbeddedWindow> mWindows = new ArrayMap<>();
48     private ArrayMap<IBinder /*focus grant token */, EmbeddedWindow> mWindowsByFocusToken =
49         new ArrayMap<>();
50     private ArrayMap<IBinder /*window token*/, EmbeddedWindow> mWindowsByWindowToken =
51         new ArrayMap<>();
52     private final Object mGlobalLock;
53     private final ActivityTaskManagerService mAtmService;
54 
EmbeddedWindowController(ActivityTaskManagerService atmService)55     EmbeddedWindowController(ActivityTaskManagerService atmService) {
56         mAtmService = atmService;
57         mGlobalLock = atmService.getGlobalLock();
58     }
59 
60     /**
61      * Adds a new embedded window.
62      *
63      * @param inputToken input channel token passed in by the embedding process when it requests
64      *                   the server to add an input channel to the embedded surface.
65      * @param window An {@link EmbeddedWindow} object to add to this controller.
66      */
add(IBinder inputToken, EmbeddedWindow window)67     void add(IBinder inputToken, EmbeddedWindow window) {
68         try {
69             mWindows.put(inputToken, window);
70             final IBinder focusToken = window.getFocusGrantToken();
71             mWindowsByFocusToken.put(focusToken, window);
72             mWindowsByWindowToken.put(window.getWindowToken(), window);
73             updateProcessController(window);
74             window.mClient.asBinder().linkToDeath(()-> {
75                 synchronized (mGlobalLock) {
76                     mWindows.remove(inputToken);
77                     mWindowsByFocusToken.remove(focusToken);
78                 }
79             }, 0);
80         } catch (RemoteException e) {
81             // The caller has died, remove from the map
82             mWindows.remove(inputToken);
83         }
84     }
85 
86     /**
87      * Track the host activity in the embedding process so we can determine if the
88      * process is currently showing any UI to the user.
89      */
updateProcessController(EmbeddedWindow window)90     private void updateProcessController(EmbeddedWindow window) {
91         if (window.mHostActivityRecord == null) {
92             return;
93         }
94         final WindowProcessController processController =
95                 mAtmService.getProcessController(window.mOwnerPid, window.mOwnerUid);
96         if (processController == null) {
97             Slog.w(TAG, "Could not find the embedding process.");
98         } else {
99             processController.addHostActivity(window.mHostActivityRecord);
100         }
101     }
102 
remove(IWindow client)103     void remove(IWindow client) {
104         for (int i = mWindows.size() - 1; i >= 0; i--) {
105             EmbeddedWindow ew = mWindows.valueAt(i);
106             if (ew.mClient.asBinder() == client.asBinder()) {
107                 mWindows.removeAt(i).onRemoved();
108                 mWindowsByFocusToken.remove(ew.getFocusGrantToken());
109                 mWindowsByWindowToken.remove(ew.getWindowToken());
110                 return;
111             }
112         }
113     }
114 
onWindowRemoved(WindowState host)115     void onWindowRemoved(WindowState host) {
116         for (int i = mWindows.size() - 1; i >= 0; i--) {
117             EmbeddedWindow ew = mWindows.valueAt(i);
118             if (ew.mHostWindowState == host) {
119                 mWindows.removeAt(i).onRemoved();
120                 mWindowsByFocusToken.remove(ew.getFocusGrantToken());
121                 mWindowsByWindowToken.remove(ew.getWindowToken());
122             }
123         }
124     }
125 
get(IBinder inputToken)126     EmbeddedWindow get(IBinder inputToken) {
127         return mWindows.get(inputToken);
128     }
129 
getByFocusToken(IBinder focusGrantToken)130     EmbeddedWindow getByFocusToken(IBinder focusGrantToken) {
131         return mWindowsByFocusToken.get(focusGrantToken);
132     }
133 
getByWindowToken(IBinder windowToken)134     EmbeddedWindow getByWindowToken(IBinder windowToken) {
135         return mWindowsByWindowToken.get(windowToken);
136     }
137 
138     static class EmbeddedWindow implements InputTarget {
139         final IWindow mClient;
140         @Nullable final WindowState mHostWindowState;
141         @Nullable final ActivityRecord mHostActivityRecord;
142         final String mName;
143         final int mOwnerUid;
144         final int mOwnerPid;
145         final WindowManagerService mWmService;
146         final int mDisplayId;
147         public Session mSession;
148         InputChannel mInputChannel;
149         final int mWindowType;
150 
151         /**
152          * A unique token associated with the embedded window that can be used by the host window
153          * to request focus transfer to the embedded. This is not the input token since we don't
154          * want to give clients access to each others input token.
155          */
156         private final IBinder mFocusGrantToken;
157 
158         private boolean mIsFocusable;
159 
160         /**
161          * @param session  calling session to check ownership of the window
162          * @param clientToken client token used to clean up the map if the embedding process dies
163          * @param hostWindowState input channel token belonging to the host window. This is needed
164          *                        to handle input callbacks to wm. It's used when raising ANR and
165          *                        when the user taps out side of the focused region on screen. This
166          *                        can be null if there is no host window.
167          * @param ownerUid  calling uid
168          * @param ownerPid  calling pid used for anr blaming
169          * @param windowType to forward to input
170          * @param displayId used for focus requests
171          */
EmbeddedWindow(Session session, WindowManagerService service, IWindow clientToken, WindowState hostWindowState, int ownerUid, int ownerPid, int windowType, int displayId, IBinder focusGrantToken, String inputHandleName, boolean isFocusable)172         EmbeddedWindow(Session session, WindowManagerService service, IWindow clientToken,
173                        WindowState hostWindowState, int ownerUid, int ownerPid, int windowType,
174                        int displayId, IBinder focusGrantToken, String inputHandleName,
175                        boolean isFocusable) {
176             mSession = session;
177             mWmService = service;
178             mClient = clientToken;
179             mHostWindowState = hostWindowState;
180             mHostActivityRecord = (mHostWindowState != null) ? mHostWindowState.mActivityRecord
181                     : null;
182             mOwnerUid = ownerUid;
183             mOwnerPid = ownerPid;
184             mWindowType = windowType;
185             mDisplayId = displayId;
186             mFocusGrantToken = focusGrantToken;
187             final String hostWindowName =
188                     (mHostWindowState != null) ? "-" + mHostWindowState.getWindowTag().toString()
189                             : "";
190             mIsFocusable = isFocusable;
191             mName = "Embedded{" + inputHandleName + hostWindowName + "}";
192         }
193 
194         @Override
toString()195         public String toString() {
196             return mName;
197         }
198 
getApplicationHandle()199         InputApplicationHandle getApplicationHandle() {
200             if (mHostWindowState == null
201                     || mHostWindowState.mInputWindowHandle.getInputApplicationHandle() == null) {
202                 return null;
203             }
204             return new InputApplicationHandle(
205                     mHostWindowState.mInputWindowHandle.getInputApplicationHandle());
206         }
207 
openInputChannel(@onNull InputChannel outInputChannel)208         void openInputChannel(@NonNull InputChannel outInputChannel) {
209             final String name = toString();
210             mInputChannel = mWmService.mInputManager.createInputChannel(name);
211             mInputChannel.copyTo(outInputChannel);
212         }
213 
onRemoved()214         void onRemoved() {
215             if (mInputChannel != null) {
216                 mWmService.mInputManager.removeInputChannel(mInputChannel.getToken());
217                 mInputChannel.dispose();
218                 mInputChannel = null;
219             }
220             if (mHostActivityRecord != null) {
221                 final WindowProcessController wpc =
222                         mWmService.mAtmService.getProcessController(mOwnerPid, mOwnerUid);
223                 if (wpc != null) {
224                     wpc.removeHostActivity(mHostActivityRecord);
225                 }
226             }
227         }
228 
229         @Override
getWindowState()230         public WindowState getWindowState() {
231             return mHostWindowState;
232         }
233 
234         @Override
getDisplayId()235         public int getDisplayId() {
236             return mDisplayId;
237         }
238 
239         @Override
getDisplayContent()240         public DisplayContent getDisplayContent() {
241             return mWmService.mRoot.getDisplayContent(getDisplayId());
242         }
243 
244         @Override
getIWindow()245         public IWindow getIWindow() {
246             return mClient;
247         }
248 
getWindowToken()249         public IBinder getWindowToken() {
250             return mClient.asBinder();
251         }
252 
253         @Override
getPid()254         public int getPid() {
255             return mOwnerPid;
256         }
257 
258         @Override
getUid()259         public int getUid() {
260             return mOwnerUid;
261         }
262 
getFocusGrantToken()263         IBinder getFocusGrantToken() {
264             return mFocusGrantToken;
265         }
266 
getInputChannelToken()267         IBinder getInputChannelToken() {
268             if (mInputChannel != null) {
269                 return mInputChannel.getToken();
270             }
271             return null;
272         }
273 
setIsFocusable(boolean isFocusable)274         void setIsFocusable(boolean isFocusable) {
275             mIsFocusable = isFocusable;
276         }
277 
278         /**
279          * When an embedded window is touched when it's not currently focus, we need to switch
280          * focus to that embedded window unless the embedded window was marked as not focusable.
281          */
282         @Override
receiveFocusFromTapOutside()283         public boolean receiveFocusFromTapOutside() {
284             return mIsFocusable;
285         }
286 
handleTap(boolean grantFocus)287         private void handleTap(boolean grantFocus) {
288             if (mInputChannel != null) {
289                 if (mHostWindowState != null) {
290                     // Use null session since this is being granted by system server and doesn't
291                     // require the host session to be passed in
292                     mWmService.grantEmbeddedWindowFocus(null, mHostWindowState.mClient,
293                             mFocusGrantToken, grantFocus);
294                     if (grantFocus) {
295                         // If granting focus to the embedded when tapped, we need to ensure the host
296                         // gains focus as well or the transfer won't take effect since it requires
297                         // the host to transfer the focus to the embedded.
298                         mHostWindowState.handleTapOutsideFocusInsideSelf();
299                     }
300                 } else {
301                     mWmService.grantEmbeddedWindowFocus(mSession, mFocusGrantToken, grantFocus);
302                 }
303             }
304         }
305 
306         @Override
handleTapOutsideFocusOutsideSelf()307         public void handleTapOutsideFocusOutsideSelf() {
308             handleTap(false);
309         }
310 
311         @Override
handleTapOutsideFocusInsideSelf()312         public void handleTapOutsideFocusInsideSelf() {
313             handleTap(true);
314         }
315 
316         @Override
shouldControlIme()317         public boolean shouldControlIme() {
318             return mHostWindowState != null;
319         }
320 
321         @Override
canScreenshotIme()322         public boolean canScreenshotIme() {
323             return true;
324         }
325 
326         @Override
getImeControlTarget()327         public InsetsControlTarget getImeControlTarget() {
328             if (mHostWindowState != null) {
329                 return mHostWindowState.getImeControlTarget();
330             }
331             return mWmService.getDefaultDisplayContentLocked().mRemoteInsetsControlTarget;
332         }
333 
334         @Override
isInputMethodClientFocus(int uid, int pid)335         public boolean isInputMethodClientFocus(int uid, int pid) {
336             return uid == mOwnerUid && pid == mOwnerPid;
337         }
338 
339         @Override
getActivityRecord()340         public ActivityRecord getActivityRecord() {
341             return mHostActivityRecord;
342         }
343 
344         @Override
dumpProto(ProtoOutputStream proto, long fieldId, @WindowTraceLogLevel int logLevel)345         public void dumpProto(ProtoOutputStream proto, long fieldId,
346                               @WindowTraceLogLevel int logLevel) {
347             final long token = proto.start(fieldId);
348 
349             final long token2 = proto.start(IDENTIFIER);
350             proto.write(HASH_CODE, System.identityHashCode(this));
351             proto.write(TITLE, "EmbeddedWindow");
352             proto.end(token2);
353             proto.end(token);
354         }
355     }
356 }
357