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