Limit number of window context without any window

This change is to prevent misuse of window context from app
and leads to performance drop on system by limit the numer of window
context an app can use. Code snippet below is a sample to cause
this issue:
```
Rect getBounds() {
    Context windowContext = context.createWindowContext(...);
    return windowContext.getSystemService(WindowManager.class)
            .getCuttentWindowMetrics().getBounds()
}
```
This method could be invoked dozens of times and produce dozens of window
tokens. It would slow down the speed of window traversalling. These
token won't be removed until system server has been GC'd.

Test: atest WindowContextTests WindowContextPolicyTests
fixes: 152934797
Bug: 153369119

Change-Id: I927e85a45c05c4d90b51a624ea408ff3a3ffce93
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 a501414..4efbe09 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;
@@ -2596,10 +2604,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 =