summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Charles Chen <charlesccchen@google.com> 2020-04-15 14:28:13 +0800
committer Charles Chen <charlesccchen@google.com> 2020-04-22 18:16:56 +0800
commit2622d3ef7ea02aaf254e15b54358d83e0066f17c (patch)
tree0ca4e52ddf74886d2ea4f7ac9dbd65342180425d
parent1b8c7de05d71f0e68e06a6227b873f14cd615fa6 (diff)
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
-rw-r--r--core/java/android/app/WindowContext.java6
-rw-r--r--core/java/android/content/Context.java10
-rw-r--r--core/java/android/view/WindowManagerGlobal.java1
-rw-r--r--services/core/java/com/android/server/wm/DisplayContent.java30
-rw-r--r--services/core/java/com/android/server/wm/WindowManagerService.java28
5 files changed, 74 insertions, 1 deletions
diff --git a/core/java/android/app/WindowContext.java b/core/java/android/app/WindowContext.java
index 3a06c9d79fee..cb416c923c60 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 @@ public class WindowContext extends ContextWrapper {
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 7c1b62fc9b8e..09c684971549 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -5812,6 +5812,12 @@ public abstract class Context {
* 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 @@ public abstract class Context {
* @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 fba6a55ef6db..94591eafe72d 100644
--- a/core/java/android/view/WindowManagerGlobal.java
+++ b/core/java/android/view/WindowManagerGlobal.java
@@ -145,6 +145,7 @@ public final class WindowManagerGlobal {
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 e26163247020..8f7fc9e354a0 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 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo
}
}
}
+
+ /**
+ * 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 a5014145aa60..4efbe09e180d 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.MANAGE_APP_TOKENS;
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_VOICE_INTERACTION;
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 @@ public class WindowManagerService extends IWindowManager.Stub
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 @@ public class WindowManagerService extends IWindowManager.Stub
@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 =