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