diff options
author | 2020-04-15 14:28:13 +0800 | |
---|---|---|
committer | 2020-04-22 18:16:56 +0800 | |
commit | 2622d3ef7ea02aaf254e15b54358d83e0066f17c (patch) | |
tree | 0ca4e52ddf74886d2ea4f7ac9dbd65342180425d | |
parent | 1b8c7de05d71f0e68e06a6227b873f14cd615fa6 (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
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 = |