Merge "Limit number of window context without any window" into rvc-dev
diff --git a/core/java/android/app/WindowContext.java b/core/java/android/app/WindowContext.java
index 3a06c9d..cb416c9 100644
--- a/core/java/android/app/WindowContext.java
+++ b/core/java/android/app/WindowContext.java
@@ -16,6 +16,7 @@
 package android.app;
 
 import static android.view.WindowManagerGlobal.ADD_OKAY;
+import static android.view.WindowManagerGlobal.ADD_TOO_MANY_TOKENS;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -81,6 +82,11 @@
             mOwnsToken = false;
             throw e.rethrowFromSystemServer();
         }
+        if (result == ADD_TOO_MANY_TOKENS) {
+            throw new UnsupportedOperationException("createWindowContext failed! Too many unused "
+                    + "window contexts. Please see Context#createWindowContext documentation for "
+                    + "detail.");
+        }
         mOwnsToken = result == ADD_OKAY;
         Reference.reachabilityFence(this);
     }
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index 7c1b62f..09c6849 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -5812,6 +5812,12 @@
      * display.</b> If there is a need to add different window types, or non-associated windows,
      * separate Contexts should be used.
      * </p>
+     * <p>
+     * Creating a window context is an expensive operation. Misuse of this API may lead to a huge
+     * performance drop. The best practice is to use the same window context when possible.
+     * An approach is to create one window context with specific window type and display and
+     * use it everywhere it's needed..
+     * </p>
      *
      * @param type Window type in {@link WindowManager.LayoutParams}
      * @param options Bundle used to pass window-related options.
@@ -5824,7 +5830,9 @@
      * @see #WINDOW_SERVICE
      * @see #LAYOUT_INFLATER_SERVICE
      * @see #WALLPAPER_SERVICE
-     * @throws IllegalArgumentException if token is invalid
+     * @throws UnsupportedOperationException if this {@link Context} does not attach to a display or
+     * the current number of window contexts without adding any view by
+     * {@link WindowManager#addView} <b>exceeds five</b>.
      */
     public @NonNull Context createWindowContext(@WindowType int type, @Nullable Bundle options)  {
         throw new RuntimeException("Not implemented. Must override in a subclass.");
diff --git a/core/java/android/view/WindowManagerGlobal.java b/core/java/android/view/WindowManagerGlobal.java
index fba6a55..94591eaf 100644
--- a/core/java/android/view/WindowManagerGlobal.java
+++ b/core/java/android/view/WindowManagerGlobal.java
@@ -145,6 +145,7 @@
     public static final int ADD_INVALID_DISPLAY = -9;
     public static final int ADD_INVALID_TYPE = -10;
     public static final int ADD_INVALID_USER = -11;
+    public static final int ADD_TOO_MANY_TOKENS = -12;
 
     @UnsupportedAppUsage
     private static WindowManagerGlobal sDefaultWindowManager;
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index e261632..8f7fc9e 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -5534,4 +5534,34 @@
             }
         }
     }
+
+    /**
+     * Returns the number of window tokens without surface on this display. A {@link WindowToken}
+     * won't have its {@link SurfaceControl} until a window is added to a {@link WindowToken}.
+     * The purpose of this method is to accumulate non-window containing {@link WindowToken}s and
+     * limit the usage if the count exceeds a number.
+     *
+     * @param callingUid app calling uid
+     * @return the number of window tokens without surface on this display
+     * @see WindowToken#addWindow(WindowState)
+     */
+    int getWindowTokensWithoutSurfaceCount(int callingUid) {
+        List<WindowToken> tokens = new ArrayList<>(mTokenMap.values());
+        int count = 0;
+        for (int i = tokens.size() - 1; i >= 0; i--) {
+            final WindowToken token = tokens.get(i);
+            if (callingUid != token.getOwnerUid()) {
+                continue;
+            }
+            // Skip if token is an Activity
+            if (token.asActivityRecord() != null) {
+                continue;
+            }
+            if (token.mSurfaceControl != null) {
+                continue;
+            }
+            count++;
+        }
+        return count;
+    }
 }
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index fbdea01..8eb4b26 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -23,6 +23,7 @@
 import static android.Manifest.permission.READ_FRAME_BUFFER;
 import static android.Manifest.permission.REGISTER_WINDOW_MANAGER_LISTENERS;
 import static android.Manifest.permission.RESTRICTED_VR_ACCESS;
+import static android.Manifest.permission.STATUS_BAR_SERVICE;
 import static android.Manifest.permission.WRITE_SECURE_SETTINGS;
 import static android.app.ActivityManagerInternal.ALLOW_FULL_ONLY;
 import static android.app.ActivityManagerInternal.ALLOW_NON_FULL;
@@ -74,6 +75,7 @@
 import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER;
 import static android.view.WindowManager.REMOVE_CONTENT_MODE_UNDEFINED;
 import static android.view.WindowManagerGlobal.ADD_OKAY;
+import static android.view.WindowManagerGlobal.ADD_TOO_MANY_TOKENS;
 import static android.view.WindowManagerGlobal.RELAYOUT_DEFER_SURFACE_DESTROY;
 import static android.view.WindowManagerGlobal.RELAYOUT_RES_BLAST_SYNC;
 import static android.view.WindowManagerGlobal.RELAYOUT_RES_SURFACE_CHANGED;
@@ -413,6 +415,12 @@
 
     private static final int ANIMATION_COMPLETED_TIMEOUT_MS = 5000;
 
+    /** The maximum count of window tokens without surface that an app can register. */
+    private static final int MAXIMUM_WINDOW_TOKEN_COUNT_WITHOUT_SURFACE = 5;
+
+    /** System UI can create more window context... */
+    private static final int SYSTEM_UI_MULTIPLIER = 2;
+
     // TODO(b/143053092): Remove the settings if it becomes stable.
     private static final String FIXED_ROTATION_TRANSFORM_SETTING_NAME = "fixed_rotation_transform";
     boolean mIsFixedRotationTransformEnabled;
@@ -2594,10 +2602,30 @@
     @Override
     public int addWindowTokenWithOptions(IBinder binder, int type, int displayId, Bundle options,
             String packageName) {
+        if (tokenCountExceed()) {
+            return ADD_TOO_MANY_TOKENS;
+        }
         return addWindowTokenWithOptions(binder, type, displayId, options, packageName,
                 true /* fromClientToken */);
     }
 
+    private boolean tokenCountExceed() {
+        final int callingUid = Binder.getCallingUid();
+        // Don't check if caller is from system server.
+        if (callingUid == myPid()) {
+            return false;
+        }
+        final int limit =
+                (checkCallingPermission(STATUS_BAR_SERVICE, "addWindowTokenWithOptions"))
+                        ?  MAXIMUM_WINDOW_TOKEN_COUNT_WITHOUT_SURFACE * SYSTEM_UI_MULTIPLIER
+                        : MAXIMUM_WINDOW_TOKEN_COUNT_WITHOUT_SURFACE;
+        synchronized (mGlobalLock) {
+            int[] count = new int[1];
+            mRoot.forAllDisplays(d -> count[0] += d.getWindowTokensWithoutSurfaceCount(callingUid));
+            return count[0] >= limit;
+        }
+    }
+
     private int addWindowTokenWithOptions(IBinder binder, int type, int displayId, Bundle options,
             String packageName, boolean fromClientToken) {
         final boolean callerCanManageAppTokens =