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 */