Update input policy to handle embedded windows
ANR - If embedded windows are slow in handling inputs the system should blame the embedded app.
PointerDownOutsideFocus - if a user taps outside the currently focused window onto an
embedded window, treat it as if the host window was tapped.
Rename blessInputSurface -> grantInputChannel and add a name to embedded windows.
Bug: 134365580
Test: b WindowlessWmTest
Test: atest CtsWindowManagerDeviceTestCases:WindowlessWmTests
Change-Id: If88970cf6ce17669b41fec995535151a492fab12
diff --git a/services/core/java/com/android/server/wm/EmbeddedWindowController.java b/services/core/java/com/android/server/wm/EmbeddedWindowController.java
new file mode 100644
index 0000000..24948a2
--- /dev/null
+++ b/services/core/java/com/android/server/wm/EmbeddedWindowController.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm;
+
+import static com.android.server.wm.ActivityRecord.INVALID_PID;
+
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.ArrayMap;
+import android.view.IWindow;
+
+/**
+ * Keeps track of embedded windows.
+ *
+ * If the embedded window does not receive input then Window Manager does not keep track of it.
+ * But if they do receive input, we keep track of the calling PID to blame the right app and
+ * the host window to send pointerDownOutsideFocus.
+ */
+class EmbeddedWindowController {
+ /* maps input token to an embedded window */
+ private ArrayMap<IBinder /*input token */, EmbeddedWindow> mWindows = new ArrayMap<>();
+ private final Object mWmLock;
+
+ EmbeddedWindowController(Object wmLock) {
+ mWmLock = wmLock;
+ }
+
+ void add(IBinder inputToken, IWindow window, WindowState hostWindowState, int ownerUid,
+ int ownerPid) {
+ EmbeddedWindow embeddedWindow = new EmbeddedWindow(window, hostWindowState, ownerUid,
+ ownerPid);
+ try {
+ mWindows.put(inputToken, embeddedWindow);
+ window.asBinder().linkToDeath(()-> {
+ synchronized (mWmLock) {
+ mWindows.remove(inputToken);
+ }
+ }, 0);
+ } catch (RemoteException e) {
+ // The caller has died, remove from the map
+ mWindows.remove(inputToken);
+ }
+ }
+
+ WindowState getHostWindow(IBinder inputToken) {
+ EmbeddedWindow embeddedWindow = mWindows.get(inputToken);
+ return embeddedWindow != null ? embeddedWindow.mHostWindowState : null;
+ }
+
+ int getOwnerPid(IBinder inputToken) {
+ EmbeddedWindow embeddedWindow = mWindows.get(inputToken);
+ return embeddedWindow != null ? embeddedWindow.mOwnerPid : INVALID_PID;
+ }
+
+ void remove(IWindow client) {
+ for (ArrayMap.Entry<IBinder, EmbeddedWindow> entry: mWindows.entrySet()) {
+ if (entry.getValue().mClient.asBinder() == client.asBinder()) {
+ mWindows.remove(entry.getKey());
+ return;
+ }
+ }
+ }
+
+ void removeWindowsWithHost(WindowState host) {
+ for (ArrayMap.Entry<IBinder, EmbeddedWindow> entry: mWindows.entrySet()) {
+ if (entry.getValue().mHostWindowState == host) {
+ mWindows.remove(entry.getKey());
+ }
+ }
+ }
+
+ private static class EmbeddedWindow {
+ final IWindow mClient;
+ final WindowState mHostWindowState;
+ final int mOwnerUid;
+ final int mOwnerPid;
+
+ EmbeddedWindow(IWindow clientToken, WindowState hostWindowState, int ownerUid,
+ int ownerPid) {
+ mClient = clientToken;
+ mHostWindowState = hostWindowState;
+ mOwnerUid = ownerUid;
+ mOwnerPid = ownerPid;
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/wm/InputManagerCallback.java b/services/core/java/com/android/server/wm/InputManagerCallback.java
index 1ebbb02..2a6fb4a 100644
--- a/services/core/java/com/android/server/wm/InputManagerCallback.java
+++ b/services/core/java/com/android/server/wm/InputManagerCallback.java
@@ -87,12 +87,20 @@
ActivityRecord activity = null;
WindowState windowState = null;
boolean aboveSystem = false;
+ int windowPid = INVALID_PID;
//TODO(b/141764879) Limit scope of wm lock when input calls notifyANR
synchronized (mService.mGlobalLock) {
if (token != null) {
windowState = mService.mInputToWindowMap.get(token);
if (windowState != null) {
activity = windowState.mActivityRecord;
+ windowPid = windowState.mSession.mPid;
+ } else {
+ // Check if this is an embedded window and if so get the embedded app pid
+ windowPid = mService.mEmbeddedWindowController.getOwnerPid(token);
+ WindowState hostWindowState =
+ mService.mEmbeddedWindowController.getHostWindow(token);
+ aboveSystem = isWindowAboveSystem(hostWindowState);
}
}
@@ -107,9 +115,7 @@
// Figure out whether this window is layered above system windows.
// We need to do this here to help the activity manager know how to
// layer its ANR dialog.
- int systemAlertLayer = mService.mPolicy.getWindowLayerFromTypeLw(
- TYPE_APPLICATION_OVERLAY, windowState.mOwnerCanAddInternalSystemWindow);
- aboveSystem = windowState.mBaseLayer > systemAlertLayer;
+ aboveSystem = isWindowAboveSystem(windowState);
} else if (activity != null) {
Slog.i(TAG_WM, "Input event dispatching timed out "
+ "sending to application " + activity.stringName
@@ -128,18 +134,17 @@
if (activity != null && activity.appToken != null) {
// Notify the activity manager about the timeout and let it decide whether
// to abort dispatching or keep waiting.
- final boolean abort = activity.keyDispatchingTimedOut(reason,
- (windowState != null) ? windowState.mSession.mPid : INVALID_PID);
+ final boolean abort = activity.keyDispatchingTimedOut(reason, windowPid);
if (!abort) {
// The activity manager declined to abort dispatching.
// Wait a bit longer and timeout again later.
return activity.mInputDispatchingTimeoutNanos;
}
- } else if (windowState != null) {
+ } else if (windowState != null || windowPid != INVALID_PID) {
// Notify the activity manager about the timeout and let it decide whether
// to abort dispatching or keep waiting.
- long timeout = mService.mAmInternal.inputDispatchingTimedOut(
- windowState.mSession.mPid, aboveSystem, reason);
+ long timeout = mService.mAmInternal.inputDispatchingTimedOut(windowPid, aboveSystem,
+ reason);
if (timeout >= 0) {
// The activity manager declined to abort dispatching.
// Wait a bit longer and timeout again later.
@@ -149,6 +154,12 @@
return 0; // abort dispatching
}
+ private boolean isWindowAboveSystem(WindowState windowState) {
+ int systemAlertLayer = mService.mPolicy.getWindowLayerFromTypeLw(
+ TYPE_APPLICATION_OVERLAY, windowState.mOwnerCanAddInternalSystemWindow);
+ return windowState.mBaseLayer > systemAlertLayer;
+ }
+
/** Notifies that the input device configuration has changed. */
@Override
public void notifyConfigurationChanged() {
diff --git a/services/core/java/com/android/server/wm/Session.java b/services/core/java/com/android/server/wm/Session.java
index 06e7d66..96be7cc 100644
--- a/services/core/java/com/android/server/wm/Session.java
+++ b/services/core/java/com/android/server/wm/Session.java
@@ -623,13 +623,14 @@
return false;
}
- public void blessInputSurface(int displayId, SurfaceControl surface,
- InputChannel outInputChannel) {
+ public void grantInputChannel(int displayId, SurfaceControl surface,
+ IWindow window, IBinder hostInputToken, InputChannel outInputChannel) {
final int callerUid = Binder.getCallingUid();
final int callerPid = Binder.getCallingPid();
final long identity = Binder.clearCallingIdentity();
try {
- mService.blessInputSurface(callerUid, callerPid, displayId, surface, outInputChannel);
+ mService.grantInputChannel(callerUid, callerPid, displayId, surface, window,
+ hostInputToken, outInputChannel);
} finally {
Binder.restoreCallingIdentity(identity);
}
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 2c56b11..56d36e0 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -224,6 +224,7 @@
import android.view.IWindowManager;
import android.view.IWindowSession;
import android.view.IWindowSessionCallback;
+import android.view.InputApplicationHandle;
import android.view.InputChannel;
import android.view.InputDevice;
import android.view.InputEvent;
@@ -698,6 +699,7 @@
boolean mHardKeyboardAvailable;
WindowManagerInternal.OnHardKeyboardStatusChangeListener mHardKeyboardStatusChangeListener;
SettingsObserver mSettingsObserver;
+ final EmbeddedWindowController mEmbeddedWindowController;
@VisibleForTesting
final class SettingsObserver extends ContentObserver {
@@ -1283,6 +1285,7 @@
new HandlerExecutor(mH), mPropertiesChangedListener);
LocalServices.addService(WindowManagerInternal.class, new LocalService());
+ mEmbeddedWindowController = new EmbeddedWindowController(mGlobalLock);
}
/**
@@ -1868,10 +1871,13 @@
void removeWindow(Session session, IWindow client) {
synchronized (mGlobalLock) {
WindowState win = windowForClientLocked(session, client, false);
- if (win == null) {
+ if (win != null) {
+ win.removeIfPossible();
return;
}
- win.removeIfPossible();
+
+ // Remove embedded window map if the token belongs to an embedded window
+ mEmbeddedWindowController.remove(client);
}
}
@@ -1894,6 +1900,7 @@
if (dc.mCurrentFocus == null) {
dc.mWinRemovedSinceNullFocus.add(win);
}
+ mEmbeddedWindowController.removeWindowsWithHost(win);
mPendingRemove.remove(win);
mResizingWindows.remove(win);
updateNonSystemOverlayWindowsVisibilityIfNeeded(win, false /* surfaceShown */);
@@ -4574,7 +4581,7 @@
}
// First notify the accessibility manager for the change so it has
- // the windows before the newly focused one starts firing eventgs.
+ // the windows before the newly focused one starts firing events.
if (accessibilityController != null) {
accessibilityController.onWindowFocusChangedNotLocked(
displayContent.getDisplayId());
@@ -7643,7 +7650,12 @@
}
private void onPointerDownOutsideFocusLocked(IBinder touchedToken) {
- final WindowState touchedWindow = mInputToWindowMap.get(touchedToken);
+ WindowState touchedWindow = mInputToWindowMap.get(touchedToken);
+ if (touchedWindow == null) {
+ // if a user taps outside the currently focused window onto an embedded window, treat
+ // it as if the host window was tapped.
+ touchedWindow = mEmbeddedWindowController.getHostWindow(touchedToken);
+ }
if (touchedWindow == null || !touchedWindow.canReceiveKeys()) {
return;
}
@@ -7706,20 +7718,37 @@
* Used by WindowlessWindowManager to enable input on SurfaceControl embedded
* views.
*/
- void blessInputSurface(int callingUid, int callingPid, int displayId, SurfaceControl surface,
- InputChannel outInputChannel) {
- String name = "Blessed Surface";
- InputChannel[] inputChannels = InputChannel.openInputChannelPair(name);
- InputChannel inputChannel = inputChannels[0];
- InputChannel clientChannel = inputChannels[1];
+ void grantInputChannel(int callingUid, int callingPid, int displayId, SurfaceControl surface,
+ IWindow window, IBinder hostInputToken, InputChannel outInputChannel) {
+ final InputApplicationHandle applicationHandle;
+ final String name;
+ final InputChannel[] inputChannels;
+ final InputChannel clientChannel;
+ final InputChannel serverChannel;
+ synchronized (mGlobalLock) {
+ final WindowState hostWindow = mInputToWindowMap.get(hostInputToken);
+ if (hostWindow == null) {
+ Slog.e(TAG, "Failed to grant input channel");
+ return;
+ }
+ name = "EmbeddedWindow{ u" + UserHandle.getUserId(callingUid)
+ + " " + hostWindow.getWindowTag() + "}";
+
+ inputChannels = InputChannel.openInputChannelPair(name);
+ serverChannel = inputChannels[0];
+ clientChannel = inputChannels[1];
+ mInputManager.registerInputChannel(serverChannel);
+ mEmbeddedWindowController.add(serverChannel.getToken(), window, hostWindow, callingUid,
+ callingPid);
+ applicationHandle = new InputApplicationHandle(
+ hostWindow.mInputWindowHandle.inputApplicationHandle);
+ }
clientChannel.transferTo(outInputChannel);
clientChannel.dispose();
- mInputManager.registerInputChannel(inputChannel);
-
- InputWindowHandle h = new InputWindowHandle(null, displayId);
- h.token = inputChannel.getToken();
+ InputWindowHandle h = new InputWindowHandle(applicationHandle, displayId);
+ h.token = serverChannel.getToken();
h.name = name;
h.layoutParamsFlags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;
h.layoutParamsType = 0;
@@ -7744,7 +7773,7 @@
// Prevent the java finalizer from breaking the input channel. But we won't
// do any further management so we just release the java ref and let the
// InputDispatcher hold the last ref.
- inputChannel.release();
+ serverChannel.release();
}
/** Return whether layer tracing is enabled */