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