Associate WindowToken object with only one display at a time

WindowTokens were global objects that contained windows that could
be on multiple displays. This model does not work with the
WindowContainer hierarachy as children (window tokens) can not have
mulitple parents (displays).
We now:
- Track the mapping of binder tokens to window tokens per display
instead of globally . So, you can have a binder token map to
individual WindowToken objects per display.
- WMS.addWindowToken is used to create a WindowToken that clients
can then later add windows to. However, when addWindowToken is called
we don't know the display the client(s) would like to add window to.
So, we track binder tokens that we are allowed to add window for in
the RootWindowContainer and create a window token for the binder on
a specific display when we try to add a window.

Bug: 30060889
Test: Manual testing and existing tests pass.
Change-Id: I81a52a32b01c33ed32169d2da0506b688ea9bc8a
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index c553b52..3c13439 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -22,6 +22,7 @@
 import android.hardware.power.V1_0.PowerHint;
 import android.os.Binder;
 import android.os.Debug;
+import android.os.IBinder;
 import android.os.ParcelFileDescriptor;
 import android.os.PowerManager;
 import android.os.RemoteException;
@@ -42,9 +43,11 @@
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
 import java.util.ArrayList;
+import java.util.HashMap;
 import java.util.LinkedList;
 
 import static android.app.ActivityManager.StackId.DOCKED_STACK_ID;
+import static android.view.Display.DEFAULT_DISPLAY;
 import static android.view.WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON;
 import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER;
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_KEYGUARD;
@@ -58,8 +61,11 @@
 import static android.view.WindowManagerPolicy.FINISH_LAYOUT_REDO_LAYOUT;
 import static android.view.WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER;
 import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER;
+import static com.android.server.wm.AppTransition.TRANSIT_UNSET;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ADD_REMOVE;
+import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_APP_TRANSITIONS;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_DISPLAY;
+import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_FOCUS_LIGHT;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_KEEP_SCREEN_ON;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_LAYOUT;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_LAYOUT_REPEATS;
@@ -79,6 +85,7 @@
 import static com.android.server.wm.WindowManagerService.H.REPORT_LOSING_FOCUS;
 import static com.android.server.wm.WindowManagerService.H.SEND_NEW_CONFIGURATION;
 import static com.android.server.wm.WindowManagerService.LAYOUT_REPEAT_THRESHOLD;
+import static com.android.server.wm.WindowManagerService.UPDATE_FOCUS_NORMAL;
 import static com.android.server.wm.WindowManagerService.UPDATE_FOCUS_PLACING_SURFACES;
 import static com.android.server.wm.WindowManagerService.UPDATE_FOCUS_WILL_PLACE_SURFACES;
 import static com.android.server.wm.WindowManagerService.WINDOWS_FREEZING_SCREENS_NONE;
@@ -137,6 +144,15 @@
 
     private final LinkedList<AppWindowToken> mTmpUpdateAllDrawn = new LinkedList();
 
+    private final ArrayList<WindowToken> mTmpTokensList = new ArrayList();
+
+    // Collection of binder tokens mapped to their window type we are allowed to create window
+    // tokens for but that are not current attached to any display. We need to track this here
+    // because a binder token can be added through {@link WindowManagerService#addWindowToken},
+    // but we don't know what display windows for the token will be added to until
+    // {@link WindowManagerService#addWindow} is called.
+    private final HashMap<IBinder, Integer> mUnattachedBinderTokens = new HashMap();
+
     // State for the RemoteSurfaceTrace system used in testing. If this is enabled SurfaceControl
     // instances will be replaced with an instance that writes a binary representation of all
     // commands to mSurfaceTraceFd.
@@ -179,7 +195,7 @@
         return dc;
     }
 
-    private DisplayContent getDisplayContent(int displayId) {
+    DisplayContent getDisplayContent(int displayId) {
         for (int i = mChildren.size() - 1; i >= 0; --i) {
             final DisplayContent current = mChildren.get(i);
             if (current.getDisplayId() == displayId) {
@@ -253,7 +269,6 @@
         }
 
         if (!attachedToDisplay) {
-            stack.attachDisplayContent(dc);
             dc.attachStack(stack, onTop);
         }
 
@@ -338,6 +353,202 @@
         return null;
     }
 
+    /** Return the window token associated with the input binder token on the input display */
+    WindowToken getWindowToken(IBinder binder, DisplayContent dc) {
+        final WindowToken token = dc.getWindowToken(binder);
+        if (token != null) {
+            return token;
+        }
+
+        // There is no window token mapped to the binder on the display. Create and map a window
+        // token if it is currently allowed.
+        if (!mUnattachedBinderTokens.containsKey(binder)) {
+            return null;
+        }
+
+        final int type = mUnattachedBinderTokens.get(binder);
+        return new WindowToken(mService, binder, type, true, dc);
+    }
+
+    /** Returns all window tokens mapped to the input binder. */
+    ArrayList<WindowToken> getWindowTokens(IBinder binder) {
+        mTmpTokensList.clear();
+        for (int i = mChildren.size() - 1; i >= 0; --i) {
+            final DisplayContent dc = mChildren.get(i);
+            final WindowToken token = dc.getWindowToken(binder);
+            if (token != null) {
+                mTmpTokensList.add(token);
+            }
+        }
+        return mTmpTokensList;
+    }
+
+    /**
+     * Returns the app window token for the input binder if it exist in the system.
+     * NOTE: Only one AppWindowToken is allowed to exist in the system for a binder token, since
+     * AppWindowToken represents an activity which can only exist on one display.
+     */
+    AppWindowToken getAppWindowToken(IBinder binder) {
+        for (int i = mChildren.size() - 1; i >= 0; --i) {
+            final DisplayContent dc = mChildren.get(i);
+            final AppWindowToken atoken = dc.getAppWindowToken(binder);
+            if (atoken != null) {
+                return atoken;
+            }
+        }
+        return null;
+    }
+
+    /** Returns the display object the input window token is currently mapped on. */
+    DisplayContent getWindowTokenDisplay(WindowToken token) {
+        if (token == null) {
+            return null;
+        }
+
+        for (int i = mChildren.size() - 1; i >= 0; --i) {
+            final DisplayContent dc = mChildren.get(i);
+            final WindowToken current = dc.getWindowToken(token.token);
+            if (current == token) {
+                return dc;
+            }
+        }
+
+        return null;
+    }
+
+    void addWindowToken(IBinder binder, int type) {
+        if (mUnattachedBinderTokens.containsKey(binder)) {
+            Slog.w(TAG_WM, "addWindowToken: Attempted to add existing binder token: " + binder);
+            return;
+        }
+
+        final ArrayList<WindowToken> tokens = getWindowTokens(binder);
+
+        if (!tokens.isEmpty()) {
+            Slog.w(TAG_WM, "addWindowToken: Attempted to add binder token: " + binder
+                    + " for already created window tokens: " + tokens);
+            return;
+        }
+
+        mUnattachedBinderTokens.put(binder, type);
+
+        // TODO(multi-display): By default we add this to the default display, but maybe we
+        // should provide an API for a token to be added to any display?
+        final WindowToken token = new WindowToken(mService, binder, type, true,
+                getDisplayContent(DEFAULT_DISPLAY));
+        if (type == TYPE_WALLPAPER) {
+            mService.mWallpaperControllerLocked.addWallpaperToken(token);
+        }
+    }
+
+    ArrayList<WindowToken> removeWindowToken(IBinder binder) {
+        mUnattachedBinderTokens.remove(binder);
+
+        mTmpTokensList.clear();
+        for (int i = mChildren.size() - 1; i >= 0; --i) {
+            final DisplayContent dc = mChildren.get(i);
+            final WindowToken token = dc.removeWindowToken(binder);
+            if (token != null) {
+                mTmpTokensList.add(token);
+            }
+        }
+        return mTmpTokensList;
+    }
+
+    /**
+     * Removed the mapping to the input binder for the system if it no longer as a window token
+     * associated with it on any display.
+     */
+    void removeWindowTokenIfPossible(IBinder binder) {
+        for (int i = mChildren.size() - 1; i >= 0; --i) {
+            final DisplayContent dc = mChildren.get(i);
+            final WindowToken token = dc.getWindowToken(binder);
+            if (token != null) {
+                return;
+            }
+        }
+
+        mUnattachedBinderTokens.remove(binder);
+    }
+
+    void removeAppToken(IBinder binder) {
+        final ArrayList<WindowToken> removedTokens = removeWindowToken(binder);
+        if (removedTokens == null || removedTokens.isEmpty()) {
+            Slog.w(TAG_WM, "removeAppToken: Attempted to remove non-existing token: " + binder);
+            return;
+        }
+
+        for (int i = removedTokens.size() - 1; i >= 0; --i) {
+            WindowToken wtoken = removedTokens.get(i);
+            AppWindowToken appToken = wtoken.asAppWindowToken();
+
+            if (appToken == null) {
+                Slog.w(TAG_WM,
+                        "Attempted to remove non-App token: " + binder + " wtoken=" + wtoken);
+                continue;
+            }
+
+            AppWindowToken startingToken = null;
+
+            if (DEBUG_APP_TRANSITIONS) Slog.v(TAG_WM, "Removing app token: " + appToken);
+
+            boolean delayed = appToken.setVisibility(null, false, TRANSIT_UNSET, true,
+                    appToken.voiceInteraction);
+
+            mService.mOpeningApps.remove(appToken);
+            appToken.waitingToShow = false;
+            if (mService.mClosingApps.contains(appToken)) {
+                delayed = true;
+            } else if (mService.mAppTransition.isTransitionSet()) {
+                mService.mClosingApps.add(appToken);
+                delayed = true;
+            }
+
+            if (DEBUG_APP_TRANSITIONS) Slog.v(TAG_WM, "Removing app " + appToken
+                    + " delayed=" + delayed
+                    + " animation=" + appToken.mAppAnimator.animation
+                    + " animating=" + appToken.mAppAnimator.animating);
+
+            if (DEBUG_ADD_REMOVE || DEBUG_TOKEN_MOVEMENT) Slog.v(TAG_WM, "removeAppToken: "
+                    + appToken + " delayed=" + delayed + " Callers=" + Debug.getCallers(4));
+
+            final TaskStack stack = appToken.mTask.mStack;
+            if (delayed && !appToken.isEmpty()) {
+                // set the token aside because it has an active animation to be finished
+                if (DEBUG_ADD_REMOVE || DEBUG_TOKEN_MOVEMENT) Slog.v(TAG_WM,
+                        "removeAppToken make exiting: " + appToken);
+                stack.mExitingAppTokens.add(appToken);
+                appToken.mIsExiting = true;
+            } else {
+                // Make sure there is no animation running on this token, so any windows associated
+                // with it will be removed as soon as their animations are complete
+                appToken.mAppAnimator.clearAnimation();
+                appToken.mAppAnimator.animating = false;
+                appToken.removeIfPossible();
+            }
+
+            appToken.removed = true;
+            if (appToken.startingData != null) {
+                startingToken = appToken;
+            }
+            appToken.stopFreezingScreen(true, true);
+            if (mService.mFocusedApp == appToken) {
+                if (DEBUG_FOCUS_LIGHT) Slog.v(TAG_WM, "Removing focused app token:" + appToken);
+                mService.mFocusedApp = null;
+                mService.updateFocusedWindowLocked(
+                        UPDATE_FOCUS_NORMAL, true /*updateInputWindows*/);
+                mService.mInputMonitor.setFocusedAppLw(null);
+            }
+
+            if (!delayed) {
+                appToken.updateReportedVisibilityLocked();
+            }
+
+            // Will only remove if startingToken non null.
+            mService.scheduleRemoveStartingWindowLocked(startingToken);
+        }
+    }
+
     // TODO: Users would have their own window containers under the display container?
     void switchUser() {
         final int count = mChildren.size();
@@ -1361,6 +1572,13 @@
         }
     }
 
+    void dumpTokens(PrintWriter pw, boolean dumpAll) {
+        pw.println("  All tokens:");
+        for (int i = mChildren.size() - 1; i >= 0; --i) {
+            mChildren.get(i).dumpTokens(pw, dumpAll);
+        }
+    }
+
     @Override
     String getName() {
         return "ROOT";