• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.WmProtoLogGroups.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.view.WindowInsets;
37 import android.view.WindowInsets.Type.InsetsType;
38 import android.window.InputTransferToken;
39 
40 import com.android.internal.protolog.ProtoLog;
41 import com.android.server.input.InputManagerService;
42 
43 import java.util.ArrayList;
44 
45 /**
46  * Keeps track of embedded windows.
47  *
48  * If the embedded window does not receive input then Window Manager does not keep track of it.
49  * But if they do receive input, we keep track of the calling PID to blame the right app and
50  * the host window to send pointerDownOutsideFocus.
51  */
52 class EmbeddedWindowController {
53     private static final String TAG = TAG_WITH_CLASS_NAME ? "EmbeddedWindowController" : TAG_WM;
54     /* maps input token to an embedded window */
55     private ArrayMap<IBinder /*input token */, EmbeddedWindow> mWindows = new ArrayMap<>();
56     private ArrayMap<InputTransferToken /*input transfer token */, EmbeddedWindow>
57             mWindowsByInputTransferToken = new ArrayMap<>();
58     private ArrayMap<IBinder /*window token*/, EmbeddedWindow> mWindowsByWindowToken =
59         new ArrayMap<>();
60     private final Object mGlobalLock;
61     private final ActivityTaskManagerService mAtmService;
62 
63     private final InputManagerService mInputManagerService;
64 
EmbeddedWindowController(ActivityTaskManagerService atmService, InputManagerService inputManagerService)65     EmbeddedWindowController(ActivityTaskManagerService atmService,
66             InputManagerService inputManagerService) {
67         mAtmService = atmService;
68         mGlobalLock = atmService.getGlobalLock();
69         mInputManagerService = inputManagerService;
70     }
71 
72     /**
73      * Adds a new embedded window.
74      *
75      * @param inputToken input channel token passed in by the embedding process when it requests
76      *                   the server to add an input channel to the embedded surface.
77      * @param window An {@link EmbeddedWindow} object to add to this controller.
78      */
add(IBinder inputToken, EmbeddedWindow window)79     void add(IBinder inputToken, EmbeddedWindow window) {
80         try {
81             mWindows.put(inputToken, window);
82             final InputTransferToken inputTransferToken = window.getInputTransferToken();
83             mWindowsByInputTransferToken.put(inputTransferToken, window);
84             final IBinder windowToken = window.getWindowToken();
85             mWindowsByWindowToken.put(windowToken, window);
86             updateProcessController(window);
87             window.mClient.linkToDeath(()-> {
88                 synchronized (mGlobalLock) {
89                     mWindows.remove(inputToken);
90                     mWindowsByInputTransferToken.remove(inputTransferToken);
91                     mWindowsByWindowToken.remove(windowToken);
92                 }
93             }, 0);
94         } catch (RemoteException e) {
95             // The caller has died, remove from the map
96             mWindows.remove(inputToken);
97         }
98     }
99 
100     /**
101      * Track the host activity in the embedding process so we can determine if the
102      * process is currently showing any UI to the user.
103      */
updateProcessController(EmbeddedWindow window)104     private void updateProcessController(EmbeddedWindow window) {
105         if (window.mHostActivityRecord == null) {
106             return;
107         }
108         final WindowProcessController processController =
109                 mAtmService.getProcessController(window.mOwnerPid, window.mOwnerUid);
110         if (processController == null) {
111             Slog.w(TAG, "Could not find the embedding process.");
112         } else {
113             processController.addHostActivity(window.mHostActivityRecord);
114         }
115     }
116 
remove(IBinder client)117     void remove(IBinder client) {
118         for (int i = mWindows.size() - 1; i >= 0; i--) {
119             EmbeddedWindow ew = mWindows.valueAt(i);
120             if (ew.mClient == client) {
121                 mWindows.removeAt(i).onRemoved();
122                 mWindowsByInputTransferToken.remove(ew.getInputTransferToken());
123                 mWindowsByWindowToken.remove(ew.getWindowToken());
124                 return;
125             }
126         }
127     }
128 
onWindowRemoved(WindowState host)129     void onWindowRemoved(WindowState host) {
130         for (int i = mWindows.size() - 1; i >= 0; i--) {
131             EmbeddedWindow ew = mWindows.valueAt(i);
132             if (ew.mHostWindowState == host) {
133                 mWindows.removeAt(i).onRemoved();
134                 mWindowsByInputTransferToken.remove(ew.getInputTransferToken());
135                 mWindowsByWindowToken.remove(ew.getWindowToken());
136             }
137         }
138     }
139 
get(IBinder inputToken)140     EmbeddedWindow get(IBinder inputToken) {
141         return mWindows.get(inputToken);
142     }
143 
getByInputTransferToken(InputTransferToken inputTransferToken)144     EmbeddedWindow getByInputTransferToken(InputTransferToken inputTransferToken) {
145         return mWindowsByInputTransferToken.get(inputTransferToken);
146     }
147 
getByWindowToken(IBinder windowToken)148     EmbeddedWindow getByWindowToken(IBinder windowToken) {
149         return mWindowsByWindowToken.get(windowToken);
150     }
151 
getByHostWindow(WindowState host)152     @Nullable ArrayList<EmbeddedWindow> getByHostWindow(WindowState host) {
153         ArrayList<EmbeddedWindow> windows = null;
154         for (int i = mWindows.size() - 1; i >= 0; i--) {
155             final EmbeddedWindow ew = mWindows.valueAt(i);
156             if (ew.mHostWindowState == host) {
157                 if (windows == null) {
158                     windows = new ArrayList<>();
159                 }
160                 windows.add(ew);
161             }
162         }
163         return windows;
164     }
165 
isValidTouchGestureParams(WindowState hostWindowState, EmbeddedWindow embeddedWindow)166     private boolean isValidTouchGestureParams(WindowState hostWindowState,
167             EmbeddedWindow embeddedWindow) {
168         if (embeddedWindow == null) {
169             ProtoLog.w(WM_DEBUG_EMBEDDED_WINDOWS,
170                     "Attempt to transfer touch gesture with non-existent embedded window");
171             return false;
172         }
173         final WindowState wsAssociatedWithEmbedded = embeddedWindow.getWindowState();
174         if (wsAssociatedWithEmbedded == null) {
175             ProtoLog.w(WM_DEBUG_EMBEDDED_WINDOWS,
176                     "Attempt to transfer touch gesture using embedded window with no associated "
177                             + "host");
178             return false;
179         }
180         if (wsAssociatedWithEmbedded.mClient.asBinder() != hostWindowState.mClient.asBinder()) {
181             ProtoLog.w(WM_DEBUG_EMBEDDED_WINDOWS,
182                     "Attempt to transfer touch gesture with host window not associated with "
183                             + "embedded window");
184             return false;
185         }
186 
187         if (embeddedWindow.getInputChannelToken() == null) {
188             ProtoLog.w(WM_DEBUG_EMBEDDED_WINDOWS,
189                     "Attempt to transfer touch gesture using embedded window that has no input "
190                             + "channel");
191             return false;
192         }
193         if (hostWindowState.mInputChannelToken == null) {
194             ProtoLog.w(WM_DEBUG_EMBEDDED_WINDOWS,
195                     "Attempt to transfer touch gesture using a host window with no input channel");
196             return false;
197         }
198         return true;
199     }
200 
transferToHost(int callingUid, @NonNull InputTransferToken embeddedWindowToken, @NonNull WindowState transferToHostWindowState)201     boolean transferToHost(int callingUid, @NonNull InputTransferToken embeddedWindowToken,
202             @NonNull WindowState transferToHostWindowState) {
203         EmbeddedWindow ew = getByInputTransferToken(embeddedWindowToken);
204         if (!isValidTouchGestureParams(transferToHostWindowState, ew)) {
205             return false;
206         }
207         if (callingUid != ew.mOwnerUid) {
208             throw new SecurityException(
209                     "Transfer request must originate from owner of transferFromToken");
210         }
211         final boolean didTransfer = mInputManagerService.transferTouchGesture(
212                 ew.getInputChannelToken(), transferToHostWindowState.mInputChannelToken,
213                 /* transferEntireGesture */ true);
214         if (didTransfer) {
215             ew.mGestureToEmbedded = false;
216         }
217         return didTransfer;
218     }
219 
transferToEmbedded(int callingUid, WindowState hostWindowState, @NonNull InputTransferToken transferToToken)220     boolean transferToEmbedded(int callingUid, WindowState hostWindowState,
221             @NonNull InputTransferToken transferToToken) {
222         final EmbeddedWindowController.EmbeddedWindow ew = getByInputTransferToken(transferToToken);
223         if (!isValidTouchGestureParams(hostWindowState, ew)) {
224             return false;
225         }
226         if (callingUid != hostWindowState.mOwnerUid) {
227             throw new SecurityException(
228                     "Transfer request must originate from owner of transferFromToken");
229         }
230         final boolean didTransfer = mInputManagerService.transferTouchGesture(
231                 hostWindowState.mInputChannelToken,
232                 ew.getInputChannelToken(), /* transferEntireGesture */ true);
233         if (didTransfer) {
234             ew.mGestureToEmbedded = true;
235             mAtmService.mBackNavigationController.onEmbeddedWindowGestureTransferred(
236                     hostWindowState);
237         }
238         return didTransfer;
239     }
240 
241     static class EmbeddedWindow implements InputTarget {
242         final IBinder mClient;
243         @Nullable final WindowState mHostWindowState;
244         @Nullable final ActivityRecord mHostActivityRecord;
245         final String mName;
246         final int mOwnerUid;
247         final int mOwnerPid;
248         final WindowManagerService mWmService;
249         final int mDisplayId;
250         public Session mSession;
251         InputChannel mInputChannel;
252         final int mWindowType;
253 
254         /**
255          * A unique token associated with the embedded window that can be used by the host window
256          * to request focus transfer and gesture transfer to the embedded. This is not the input
257          * token since we don't want to give clients access to each others input token.
258          */
259         private final InputTransferToken mInputTransferToken;
260 
261         private boolean mIsFocusable;
262 
263         // The EmbeddedWindow can only request the IME. All other insets types are requested by
264         // the host window.
265         private @InsetsType int mRequestedVisibleTypes = 0;
266 
267         /** Whether the gesture is transferred to embedded window. */
268         boolean mGestureToEmbedded = false;
269 
270         /**
271          * @param session  calling session to check ownership of the window
272          * @param clientToken client token used to clean up the map if the embedding process dies
273          * @param hostWindowState input channel token belonging to the host window. This is needed
274          *                        to handle input callbacks to wm. It's used when raising ANR and
275          *                        when the user taps out side of the focused region on screen. This
276          *                        can be null if there is no host window.
277          * @param ownerUid  calling uid
278          * @param ownerPid  calling pid used for anr blaming
279          * @param windowType to forward to input
280          * @param displayId used for focus requests
281          */
EmbeddedWindow(Session session, WindowManagerService service, IBinder clientToken, WindowState hostWindowState, int ownerUid, int ownerPid, int windowType, int displayId, InputTransferToken inputTransferToken, String inputHandleName, boolean isFocusable)282         EmbeddedWindow(Session session, WindowManagerService service, IBinder clientToken,
283                        WindowState hostWindowState, int ownerUid, int ownerPid, int windowType,
284                        int displayId, InputTransferToken inputTransferToken, String inputHandleName,
285                        boolean isFocusable) {
286             mSession = session;
287             mWmService = service;
288             mClient = clientToken;
289             mHostWindowState = hostWindowState;
290             mHostActivityRecord = (mHostWindowState != null) ? mHostWindowState.mActivityRecord
291                     : null;
292             mOwnerUid = ownerUid;
293             mOwnerPid = ownerPid;
294             mWindowType = windowType;
295             mDisplayId = displayId;
296             mInputTransferToken = inputTransferToken;
297             final String hostWindowName =
298                     (mHostWindowState != null) ? "-" + mHostWindowState.getWindowTag().toString()
299                             : "";
300             mIsFocusable = isFocusable;
301             mName = "Embedded{" + inputHandleName + hostWindowName + "}";
302         }
303 
304         @Override
toString()305         public String toString() {
306             return mName;
307         }
308 
getApplicationHandle()309         InputApplicationHandle getApplicationHandle() {
310             if (mHostWindowState == null
311                     || mHostWindowState.mInputWindowHandle.getInputApplicationHandle() == null) {
312                 return null;
313             }
314             return new InputApplicationHandle(
315                     mHostWindowState.mInputWindowHandle.getInputApplicationHandle());
316         }
317 
openInputChannel(@onNull InputChannel outInputChannel)318         void openInputChannel(@NonNull InputChannel outInputChannel) {
319             final String name = toString();
320             mInputChannel = mWmService.mInputManager.createInputChannel(name);
321             mInputChannel.copyTo(outInputChannel);
322         }
323 
onRemoved()324         void onRemoved() {
325             if (mInputChannel != null) {
326                 mWmService.mInputManager.removeInputChannel(mInputChannel.getToken());
327                 mInputChannel.dispose();
328                 mInputChannel = null;
329             }
330             if (mHostActivityRecord != null) {
331                 final WindowProcessController wpc =
332                         mWmService.mAtmService.getProcessController(mOwnerPid, mOwnerUid);
333                 if (wpc != null) {
334                     wpc.removeHostActivity(mHostActivityRecord);
335                 }
336             }
337         }
338 
339         @Override
getWindowState()340         public WindowState getWindowState() {
341             return mHostWindowState;
342         }
343 
344         @Override
getDisplayId()345         public int getDisplayId() {
346             return mDisplayId;
347         }
348 
349         @Override
getDisplayContent()350         public DisplayContent getDisplayContent() {
351             return mWmService.mRoot.getDisplayContent(getDisplayId());
352         }
353 
getWindowToken()354         public IBinder getWindowToken() {
355             return mClient;
356         }
357 
358         @Override
isRequestedVisible(@nsetsType int types)359         public boolean isRequestedVisible(@InsetsType int types) {
360             return (mRequestedVisibleTypes & types) != 0;
361         }
362 
363         @Override
getRequestedVisibleTypes()364         public @InsetsType int getRequestedVisibleTypes() {
365             return mRequestedVisibleTypes;
366         }
367 
368         /**
369          * Only the IME can be requested from the EmbeddedWindow.
370          * @param requestedVisibleTypes other types than {@link WindowInsets.Type#ime()} are
371          *                              not sent to system server via WindowlessWindowManager.
372          * @return an integer as the changed requested visible insets types.
373          */
setRequestedVisibleTypes(@nsetsType int requestedVisibleTypes)374         @InsetsType int setRequestedVisibleTypes(@InsetsType int requestedVisibleTypes) {
375             if (mRequestedVisibleTypes != requestedVisibleTypes) {
376                 final int changedTypes = mRequestedVisibleTypes ^ requestedVisibleTypes;
377                 mRequestedVisibleTypes = requestedVisibleTypes;
378                 return changedTypes;
379             }
380             return 0;
381         }
382 
383         @Override
getPid()384         public int getPid() {
385             return mOwnerPid;
386         }
387 
388         @Override
getUid()389         public int getUid() {
390             return mOwnerUid;
391         }
392 
getInputTransferToken()393         InputTransferToken getInputTransferToken() {
394             return mInputTransferToken;
395         }
396 
getInputChannelToken()397         IBinder getInputChannelToken() {
398             if (mInputChannel != null) {
399                 return mInputChannel.getToken();
400             }
401             return null;
402         }
403 
setIsFocusable(boolean isFocusable)404         void setIsFocusable(boolean isFocusable) {
405             mIsFocusable = isFocusable;
406         }
407 
408         /**
409          * When an embedded window is touched when it's not currently focus, we need to switch
410          * focus to that embedded window unless the embedded window was marked as not focusable.
411          */
412         @Override
receiveFocusFromTapOutside()413         public boolean receiveFocusFromTapOutside() {
414             return mIsFocusable;
415         }
416 
handleTap(boolean grantFocus)417         private void handleTap(boolean grantFocus) {
418             if (mInputChannel != null) {
419                 if (mHostWindowState != null) {
420                     // Use null session since this is being granted by system server and doesn't
421                     // require the host session to be passed in
422                     mWmService.grantEmbeddedWindowFocus(null, mHostWindowState.mClient,
423                             mInputTransferToken, grantFocus);
424                     if (grantFocus) {
425                         // If granting focus to the embedded when tapped, we need to ensure the host
426                         // gains focus as well or the transfer won't take effect since it requires
427                         // the host to transfer the focus to the embedded.
428                         mHostWindowState.handleTapOutsideFocusInsideSelf();
429                     }
430                 } else {
431                     mWmService.grantEmbeddedWindowFocus(mSession, mInputTransferToken, grantFocus);
432                 }
433             }
434         }
435 
436         @Override
handleTapOutsideFocusOutsideSelf()437         public void handleTapOutsideFocusOutsideSelf() {
438             handleTap(false);
439         }
440 
441         @Override
handleTapOutsideFocusInsideSelf()442         public void handleTapOutsideFocusInsideSelf() {
443             handleTap(true);
444         }
445 
446         @Override
shouldControlIme()447         public boolean shouldControlIme() {
448             if (android.view.inputmethod.Flags.refactorInsetsController()) {
449                 // EmbeddedWindow should never be able to control the IME directly, but only the
450                 // RemoteInsetsControlTarget.
451                 return false;
452             }
453             return mHostWindowState != null;
454         }
455 
456         @Override
canScreenshotIme()457         public boolean canScreenshotIme() {
458             return true;
459         }
460 
461         @Override
getImeControlTarget()462         public InsetsControlTarget getImeControlTarget() {
463             if (mHostWindowState != null) {
464                 return mHostWindowState.getImeControlTarget();
465             }
466             return mWmService.getDefaultDisplayContentLocked().mRemoteInsetsControlTarget;
467         }
468 
469         @Override
isInputMethodClientFocus(int uid, int pid)470         public boolean isInputMethodClientFocus(int uid, int pid) {
471             return uid == mOwnerUid && pid == mOwnerPid;
472         }
473 
474         @Override
getActivityRecord()475         public ActivityRecord getActivityRecord() {
476             return mHostActivityRecord;
477         }
478 
479         @Override
dumpProto(ProtoOutputStream proto, long fieldId, @WindowTracingLogLevel int logLevel)480         public void dumpProto(ProtoOutputStream proto, long fieldId,
481                               @WindowTracingLogLevel int logLevel) {
482             final long token = proto.start(fieldId);
483 
484             final long token2 = proto.start(IDENTIFIER);
485             proto.write(HASH_CODE, System.identityHashCode(this));
486             proto.write(TITLE, "EmbeddedWindow");
487             proto.end(token2);
488             proto.end(token);
489         }
490     }
491 }
492