From 4a316973192b3f60b59d5cc311f8232420bad6c2 Mon Sep 17 00:00:00 2001 From: Andrii Kulian <akulian@google.com> Date: Tue, 21 Jan 2020 21:41:38 -0800 Subject: [PATCH] Introduce WindowContext API Test: atest WmTests CtsWindowManagerDeviceTestCases Bug: 128338354 Change-Id: I9c9dfc5e7f4edd4c968e60d2ffcbb19b5c72a853 --- api/current.txt | 4 +- api/test-current.txt | 2 - core/java/android/app/ContextImpl.java | 78 +++++++--- core/java/android/app/WindowContext.java | 123 +++++++++++++++ core/java/android/content/Context.java | 72 ++++++++- core/java/android/content/ContextWrapper.java | 11 +- core/java/android/view/Display.java | 10 +- core/java/android/view/IWindowManager.aidl | 14 ++ core/java/android/view/WindowManager.java | 2 + .../server/wm/WindowManagerService.java | 144 +++++++++++------- test-mock/api/test-current.txt | 1 - .../src/android/test/mock/MockContext.java | 7 +- 12 files changed, 378 insertions(+), 90 deletions(-) create mode 100644 core/java/android/app/WindowContext.java diff --git a/api/current.txt b/api/current.txt index 1c03c23a0e01..339c866ebe49 100644 --- a/api/current.txt +++ b/api/current.txt @@ -9940,6 +9940,7 @@ package android.content { method public abstract android.content.Context createDisplayContext(@NonNull android.view.Display); method @NonNull public android.content.Context createFeatureContext(@Nullable String); method public abstract android.content.Context createPackageContext(String, int) throws android.content.pm.PackageManager.NameNotFoundException; + method @NonNull public android.content.Context createWindowContext(int); method public abstract String[] databaseList(); method public abstract boolean deleteDatabase(String); method public abstract boolean deleteFile(String); @@ -9964,6 +9965,7 @@ package android.content { method public abstract java.io.File getDataDir(); method public abstract java.io.File getDatabasePath(String); method public abstract java.io.File getDir(String, int); + method @Nullable public android.view.Display getDisplay(); method @Nullable public final android.graphics.drawable.Drawable getDrawable(@DrawableRes int); method @Nullable public abstract java.io.File getExternalCacheDir(); method public abstract java.io.File[] getExternalCacheDirs(); @@ -54146,7 +54148,7 @@ package android.view { } public interface WindowManager extends android.view.ViewManager { - method public android.view.Display getDefaultDisplay(); + method @Deprecated public android.view.Display getDefaultDisplay(); method public void removeViewImmediate(android.view.View); } diff --git a/api/test-current.txt b/api/test-current.txt index d4b799dd405e..1d66134f8bac 100644 --- a/api/test-current.txt +++ b/api/test-current.txt @@ -754,7 +754,6 @@ package android.content { method @NonNull public android.content.Context createContextAsUser(@NonNull android.os.UserHandle, int); method @NonNull public android.content.Context createPackageContextAsUser(@NonNull String, int, @NonNull android.os.UserHandle) throws android.content.pm.PackageManager.NameNotFoundException; method @NonNull public java.io.File getCrateDir(@NonNull String); - method public abstract android.view.Display getDisplay(); method public abstract int getDisplayId(); method public android.os.UserHandle getUser(); method public int getUserId(); @@ -775,7 +774,6 @@ package android.content { } public class ContextWrapper extends android.content.Context { - method public android.view.Display getDisplay(); method public int getDisplayId(); } diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java index cd84310356b1..b7555ee1c04e 100644 --- a/core/java/android/app/ContextImpl.java +++ b/core/java/android/app/ContextImpl.java @@ -19,7 +19,6 @@ package android.app; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; -import android.annotation.TestApi; import android.compat.annotation.UnsupportedAppUsage; import android.content.AutofillOptions; import android.content.BroadcastReceiver; @@ -201,7 +200,7 @@ class ContextImpl extends Context { @UnsupportedAppUsage private @Nullable ClassLoader mClassLoader; - private final @Nullable IBinder mActivityToken; + private final @Nullable IBinder mToken; private final @NonNull UserHandle mUser; @@ -219,7 +218,7 @@ class ContextImpl extends Context { private final @NonNull ResourcesManager mResourcesManager; @UnsupportedAppUsage private @NonNull Resources mResources; - private @Nullable Display mDisplay; // may be null if default display + private @Nullable Display mDisplay; // may be null if invalid display or not initialized yet. @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) private final int mFlags; @@ -244,6 +243,9 @@ class ContextImpl extends Context { private final Object mSync = new Object(); + private boolean mIsSystemOrSystemUiContext; + private boolean mIsUiContext; + @GuardedBy("mSync") private File mDatabasesDir; @GuardedBy("mSync") @@ -1883,6 +1885,9 @@ class ContextImpl extends Context { @Override public Object getSystemService(String name) { + if (isUiComponent(name) && !isUiContext()) { + Log.w(TAG, name + " should be accessed from Activity or other visual Context"); + } return SystemServiceRegistry.getSystemService(this, name); } @@ -1891,6 +1896,15 @@ class ContextImpl extends Context { return SystemServiceRegistry.getSystemServiceName(serviceClass); } + boolean isUiContext() { + return mIsSystemOrSystemUiContext || mIsUiContext; + } + + private static boolean isUiComponent(String name) { + return WINDOW_SERVICE.equals(name) || LAYOUT_INFLATER_SERVICE.equals(name) + || WALLPAPER_SERVICE.equals(name); + } + @Override public int checkPermission(String permission, int pid, int uid) { if (permission == null) { @@ -2229,12 +2243,12 @@ class ContextImpl extends Context { LoadedApk pi = mMainThread.getPackageInfo(application, mResources.getCompatibilityInfo(), flags | CONTEXT_REGISTER_PACKAGE); if (pi != null) { - ContextImpl c = new ContextImpl(this, mMainThread, pi, null, null, mActivityToken, + ContextImpl c = new ContextImpl(this, mMainThread, pi, null, null, mToken, new UserHandle(UserHandle.getUserId(application.uid)), flags, null, null); final int displayId = getDisplayId(); - c.setResources(createResources(mActivityToken, pi, null, displayId, null, + c.setResources(createResources(mToken, pi, null, displayId, null, getDisplayAdjustments(displayId).getCompatibilityInfo())); if (c.mResources != null) { return c; @@ -2258,18 +2272,18 @@ class ContextImpl extends Context { // The system resources are loaded in every application, so we can safely copy // the context without reloading Resources. return new ContextImpl(this, mMainThread, mPackageInfo, mFeatureId, null, - mActivityToken, user, flags, null, null); + mToken, user, flags, null, null); } LoadedApk pi = mMainThread.getPackageInfo(packageName, mResources.getCompatibilityInfo(), flags | CONTEXT_REGISTER_PACKAGE, user.getIdentifier()); if (pi != null) { ContextImpl c = new ContextImpl(this, mMainThread, pi, mFeatureId, null, - mActivityToken, user, flags, null, null); + mToken, user, flags, null, null); final int displayId = getDisplayId(); - c.setResources(createResources(mActivityToken, pi, null, displayId, null, + c.setResources(createResources(mToken, pi, null, displayId, null, getDisplayAdjustments(displayId).getCompatibilityInfo())); if (c.mResources != null) { return c; @@ -2301,12 +2315,12 @@ class ContextImpl extends Context { final String[] paths = mPackageInfo.getSplitPaths(splitName); final ContextImpl context = new ContextImpl(this, mMainThread, mPackageInfo, - mFeatureId, splitName, mActivityToken, mUser, mFlags, classLoader, null); + mFeatureId, splitName, mToken, mUser, mFlags, classLoader, null); final int displayId = getDisplayId(); context.setResources(ResourcesManager.getInstance().getResources( - mActivityToken, + mToken, mPackageInfo.getResDir(), paths, mPackageInfo.getOverlayDirs(), @@ -2325,10 +2339,10 @@ class ContextImpl extends Context { } ContextImpl context = new ContextImpl(this, mMainThread, mPackageInfo, mFeatureId, - mSplitName, mActivityToken, mUser, mFlags, mClassLoader, null); + mSplitName, mToken, mUser, mFlags, mClassLoader, null); final int displayId = getDisplayId(); - context.setResources(createResources(mActivityToken, mPackageInfo, mSplitName, displayId, + context.setResources(createResources(mToken, mPackageInfo, mSplitName, displayId, overrideConfiguration, getDisplayAdjustments(displayId).getCompatibilityInfo())); return context; } @@ -2340,19 +2354,36 @@ class ContextImpl extends Context { } ContextImpl context = new ContextImpl(this, mMainThread, mPackageInfo, mFeatureId, - mSplitName, mActivityToken, mUser, mFlags, mClassLoader, null); + mSplitName, mToken, mUser, mFlags, mClassLoader, null); final int displayId = display.getDisplayId(); - context.setResources(createResources(mActivityToken, mPackageInfo, mSplitName, displayId, + context.setResources(createResources(mToken, mPackageInfo, mSplitName, displayId, null, getDisplayAdjustments(displayId).getCompatibilityInfo())); context.mDisplay = display; return context; } + @Override + public @NonNull WindowContext createWindowContext(int type) { + if (getDisplay() == null) { + throw new UnsupportedOperationException("WindowContext can only be created from " + + "other visual contexts, such as Activity or one created with " + + "Context#createDisplayContext(Display)"); + } + return new WindowContext(this, null /* token */, type); + } + + ContextImpl createBaseWindowContext(IBinder token) { + ContextImpl context = new ContextImpl(this, mMainThread, mPackageInfo, mFeatureId, + mSplitName, token, mUser, mFlags, mClassLoader, null); + context.mIsUiContext = true; + return context; + } + @Override public @NonNull Context createFeatureContext(@Nullable String featureId) { return new ContextImpl(this, mMainThread, mPackageInfo, featureId, mSplitName, - mActivityToken, mUser, mFlags, mClassLoader, null); + mToken, mUser, mFlags, mClassLoader, null); } @Override @@ -2360,7 +2391,7 @@ class ContextImpl extends Context { final int flags = (mFlags & ~Context.CONTEXT_CREDENTIAL_PROTECTED_STORAGE) | Context.CONTEXT_DEVICE_PROTECTED_STORAGE; return new ContextImpl(this, mMainThread, mPackageInfo, mFeatureId, mSplitName, - mActivityToken, mUser, flags, mClassLoader, null); + mToken, mUser, flags, mClassLoader, null); } @Override @@ -2368,7 +2399,7 @@ class ContextImpl extends Context { final int flags = (mFlags & ~Context.CONTEXT_DEVICE_PROTECTED_STORAGE) | Context.CONTEXT_CREDENTIAL_PROTECTED_STORAGE; return new ContextImpl(this, mMainThread, mPackageInfo, mFeatureId, mSplitName, - mActivityToken, mUser, flags, mClassLoader, null); + mToken, mUser, flags, mClassLoader, null); } @Override @@ -2394,8 +2425,6 @@ class ContextImpl extends Context { return (mFlags & Context.CONTEXT_IGNORE_SECURITY) != 0; } - @UnsupportedAppUsage - @TestApi @Override public Display getDisplay() { if (mDisplay == null) { @@ -2408,7 +2437,8 @@ class ContextImpl extends Context { @Override public int getDisplayId() { - return mDisplay != null ? mDisplay.getDisplayId() : Display.DEFAULT_DISPLAY; + final Display display = getDisplay(); + return display != null ? display.getDisplayId() : Display.DEFAULT_DISPLAY; } @Override @@ -2518,6 +2548,7 @@ class ContextImpl extends Context { context.setResources(packageInfo.getResources()); context.mResources.updateConfiguration(context.mResourcesManager.getConfiguration(), context.mResourcesManager.getDisplayMetrics()); + context.mIsSystemOrSystemUiContext = true; return context; } @@ -2535,6 +2566,7 @@ class ContextImpl extends Context { context.setResources(createResources(null, packageInfo, null, displayId, null, packageInfo.getCompatibilityInfo())); context.updateDisplay(displayId); + context.mIsSystemOrSystemUiContext = true; return context; } @@ -2584,6 +2616,7 @@ class ContextImpl extends Context { ContextImpl context = new ContextImpl(null, mainThread, packageInfo, null, activityInfo.splitName, activityToken, null, 0, classLoader, null); + context.mIsUiContext = true; // Clamp display ID to DEFAULT_DISPLAY if it is INVALID_DISPLAY. displayId = (displayId != Display.INVALID_DISPLAY) ? displayId : Display.DEFAULT_DISPLAY; @@ -2629,7 +2662,7 @@ class ContextImpl extends Context { } mMainThread = mainThread; - mActivityToken = activityToken; + mToken = activityToken; mFlags = flags; if (user == null) { @@ -2649,6 +2682,7 @@ class ContextImpl extends Context { opPackageName = container.mOpPackageName; setResources(container.mResources); mDisplay = container.mDisplay; + mIsSystemOrSystemUiContext = container.mIsSystemOrSystemUiContext; } else { mBasePackageName = packageInfo.mPackageName; ApplicationInfo ainfo = packageInfo.getApplicationInfo(); @@ -2710,7 +2744,7 @@ class ContextImpl extends Context { @Override @UnsupportedAppUsage public IBinder getActivityToken() { - return mActivityToken; + return mToken; } private void checkMode(int mode) { diff --git a/core/java/android/app/WindowContext.java b/core/java/android/app/WindowContext.java new file mode 100644 index 000000000000..22cc14bd5ed6 --- /dev/null +++ b/core/java/android/app/WindowContext.java @@ -0,0 +1,123 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package android.app; + +import android.annotation.NonNull; +import android.content.Context; +import android.content.ContextWrapper; +import android.os.Binder; +import android.os.Bundle; +import android.os.IBinder; +import android.os.RemoteException; +import android.view.IWindowManager; +import android.view.WindowManagerGlobal; +import android.view.WindowManagerImpl; + +/** + * {@link WindowContext} is a context for non-activity windows such as + * {@link android.view.WindowManager.LayoutParams#TYPE_APPLICATION_OVERLAY} windows or system + * windows. Its resources and configuration are adjusted to the area of the display that will be + * used when a new window is added via {@link android.view.WindowManager.addView}. + * + * @see Context#createWindowContext(int) + * @hide + */ +// TODO(b/128338354): Handle config/display changes from server side. +public class WindowContext extends ContextWrapper { + private final WindowManagerImpl mWindowManager; + private final IWindowManager mWms; + private final IBinder mToken; + private final int mDisplayId; + private boolean mOwnsToken; + + /** + * Default constructor. Can either accept an existing token or generate one and registers it + * with the server if necessary. + * + * @param base Base {@link Context} for this new instance. + * @param token A valid {@link com.android.server.wm.WindowToken}. Pass {@code null} to generate + * one. + * @param type Window type to be used with this context. + * @hide + */ + public WindowContext(Context base, IBinder token, int type) { + super(null /* base */); + + mWms = WindowManagerGlobal.getWindowManagerService(); + if (token != null && !isWindowToken(token)) { + throw new IllegalArgumentException("Token must be registered to server."); + } + + final ContextImpl contextImpl = createBaseWindowContext(base, token); + attachBaseContext(contextImpl); + contextImpl.setOuterContext(this); + + mToken = token != null ? token : new Binder(); + mDisplayId = getDisplayId(); + mWindowManager = new WindowManagerImpl(this); + mWindowManager.setDefaultToken(mToken); + + // TODO(b/128338354): Obtain the correct config from WM and adjust resources. + if (token != null) { + mOwnsToken = false; + return; + } + try { + mWms.addWindowContextToken(mToken, type, mDisplayId, getPackageName()); + // TODO(window-context): remove token with a DeathObserver + } catch (RemoteException e) { + mOwnsToken = false; + throw e.rethrowFromSystemServer(); + } + mOwnsToken = true; + } + + /** Check if the passed window token is registered with the server. */ + private boolean isWindowToken(@NonNull IBinder token) { + try { + return mWms.isWindowToken(token); + } catch (RemoteException e) { + e.rethrowFromSystemServer(); + } + return false; + } + + private static ContextImpl createBaseWindowContext(Context outer, IBinder token) { + final ContextImpl contextImpl = ContextImpl.getImpl(outer); + return contextImpl.createBaseWindowContext(token); + } + + @Override + public Object getSystemService(String name) { + if (WINDOW_SERVICE.equals(name)) { + return mWindowManager; + } + return super.getSystemService(name); + } + + @Override + protected void finalize() throws Throwable { + if (mOwnsToken) { + try { + mWms.removeWindowToken(mToken, mDisplayId); + mOwnsToken = false; + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + super.finalize(); + } +} diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java index 2943e398dd87..7c6d68e57c50 100644 --- a/core/java/android/content/Context.java +++ b/core/java/android/content/Context.java @@ -5719,6 +5719,63 @@ public abstract class Context { */ public abstract Context createDisplayContext(@NonNull Display display); + /** + * Creates a Context for a non-activity window. + * + * <p> + * A window context is a context that can be used to add non-activity windows, such as + * {@link android.view.WindowManager.LayoutParams#TYPE_APPLICATION_OVERLAY}. A window context + * must be created from a context that has an associated {@link Display}, such as + * {@link android.app.Activity Activity} or a context created with + * {@link #createDisplayContext(Display)}. + * + * <p> + * The window context is created with the appropriate {@link Configuration} for the area of the + * display that the windows created with it can occupy; it must be used when + * {@link android.view.LayoutInflater inflating} views, such that they can be inflated with + * proper {@link Resources}. + * + * Below is a sample code to <b>add an application overlay window on the primary display:<b/> + * <pre class="prettyprint"> + * ... + * final DisplayManager dm = anyContext.getSystemService(DisplayManager.class); + * final Display primaryDisplay = dm.getDisplay(DEFAULT_DISPLAY); + * final Context windowContext = anyContext.createDisplayContext(primaryDisplay) + * .createWindowContext(TYPE_APPLICATION_OVERLAY); + * final View overlayView = Inflater.from(windowContext).inflate(someLayoutXml, null); + * + * // WindowManager.LayoutParams initialization + * ... + * mParams.type = TYPE_APPLICATION_OVERLAY; + * ... + * + * mWindowContext.getSystemService(WindowManager.class).addView(overlayView, mParams); + * </pre> + * + * <p> + * This context's configuration and resources are adjusted to a display area where the windows + * with provided type will be added. <b>Note that all windows associated with the same context + * will have an affinity and can only be moved together between different displays or areas on a + * display.</b> If there is a need to add different window types, or non-associated windows, + * separate Contexts should be used. + * </p> + * + * @param type Window type in {@link WindowManager.LayoutParams} + * @return A {@link Context} that can be used to create windows. + * @throws UnsupportedOperationException if this is called on a non-UI context, such as + * {@link android.app.Application Application} or {@link android.app.Service Service}. + * + * @see #getSystemService(String) + * @see #getSystemService(Class) + * @see #WINDOW_SERVICE + * @see #LAYOUT_INFLATER_SERVICE + * @see #WALLPAPER_SERVICE + * @throws IllegalArgumentException if token is invalid + */ + public @NonNull Context createWindowContext(int type) { + throw new RuntimeException("Not implemented. Must override in a subclass."); + } + /** * Return a new Context object for the current Context but for a different feature in the app. * Features can be used by complex apps to separate logical parts. @@ -5803,17 +5860,22 @@ public abstract class Context { public abstract DisplayAdjustments getDisplayAdjustments(int displayId); /** + * Get the display this context is associated with. Applications should use this method with + * {@link android.app.Activity} or a context associated with a {@link Display} via + * {@link #createDisplayContext(Display)} to get a display object associated with a Context, or + * {@link android.hardware.display.DisplayManager#getDisplay} to get a display object by id. * @return Returns the {@link Display} object this context is associated with. - * @hide */ - @UnsupportedAppUsage - @TestApi - public abstract Display getDisplay(); + @Nullable + public Display getDisplay() { + throw new RuntimeException("Not implemented. Must override in a subclass."); + } /** - * Gets the display ID. + * Gets the ID of the display this context is associated with. * * @return display ID associated with this {@link Context}. + * @see #getDisplay() * @hide */ @TestApi diff --git a/core/java/android/content/ContextWrapper.java b/core/java/android/content/ContextWrapper.java index 6fe11873d327..b2b7988de896 100644 --- a/core/java/android/content/ContextWrapper.java +++ b/core/java/android/content/ContextWrapper.java @@ -976,6 +976,12 @@ public class ContextWrapper extends Context { return mBase.createDisplayContext(display); } + @Override + @NonNull + public Context createWindowContext(int type) { + return mBase.createWindowContext(type); + } + @Override public @NonNull Context createFeatureContext(@Nullable String featureId) { return mBase.createFeatureContext(featureId); @@ -992,11 +998,8 @@ public class ContextWrapper extends Context { return mBase.getDisplayAdjustments(displayId); } - /** @hide */ - @UnsupportedAppUsage - @TestApi @Override - public Display getDisplay() { + public @Nullable Display getDisplay() { return mBase.getDisplay(); } diff --git a/core/java/android/view/Display.java b/core/java/android/view/Display.java index 904c510a5b01..0304328f734a 100644 --- a/core/java/android/view/Display.java +++ b/core/java/android/view/Display.java @@ -423,10 +423,14 @@ public final class Display { /** * Internal method to create a display. * The display created with this method will have a static {@link DisplayAdjustments} applied. - * Applications should use {@link android.view.WindowManager#getDefaultDisplay()} - * or {@link android.hardware.display.DisplayManager#getDisplay} - * to get a display object. + * Applications should use {@link android.content.Context#getDisplay} with + * {@link android.app.Activity} or a context associated with a {@link Display} via + * {@link android.content.Context#createDisplayContext(Display)} + * to get a display object associated with a {@link android.app.Context}, or + * {@link android.hardware.display.DisplayManager#getDisplay} to get a display object by id. * + * @see android.content.Context#getDisplay() + * @see android.content.Context#createDisplayContext(Display) * @hide */ public Display(DisplayManagerGlobal global, int displayId, /*@NotNull*/ DisplayInfo displayInfo, diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl index 993bdc4d6543..5eb319f19cce 100644 --- a/core/java/android/view/IWindowManager.aidl +++ b/core/java/android/view/IWindowManager.aidl @@ -111,6 +111,20 @@ interface IWindowManager // These can only be called when holding the MANAGE_APP_TOKENS permission. void setEventDispatching(boolean enabled); + + /** @return {@code true} if this binder is a registered window token. */ + boolean isWindowToken(in IBinder binder); + /** + * Adds window token for a given type. + * + * @param token Token to be registered. + * @param type Window type to be used with this token. + * @param displayId The ID of the display where this token should be added. + * @param packageName The name of package to request to add window token. + * @return {@link WindowManagerGlobal#ADD_OKAY} if the addition was successful, an error code + * otherwise. + */ + int addWindowContextToken(IBinder token, int type, int displayId, String packageName); void addWindowToken(IBinder token, int type, int displayId); void removeWindowToken(IBinder token, int displayId); void prepareAppTransition(int transit, boolean alwaysKeepCurrent); diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java index cd9dee4f7329..a87e1e334688 100644 --- a/core/java/android/view/WindowManager.java +++ b/core/java/android/view/WindowManager.java @@ -421,7 +421,9 @@ public interface WindowManager extends ViewManager { * </p> * * @return The display that this window manager is managing. + * @deprecated Use {@link Context#getDisplay()} instead. */ + @Deprecated public Display getDefaultDisplay(); /** diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index cd5da08a4780..aa0aa826886c 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -71,6 +71,7 @@ import static android.view.WindowManager.LayoutParams.TYPE_TOAST; 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.RELAYOUT_DEFER_SURFACE_DESTROY; import static android.view.WindowManagerGlobal.RELAYOUT_RES_SURFACE_CHANGED; import static android.view.WindowManagerPolicyConstants.NAV_BAR_INVALID; @@ -1367,52 +1368,10 @@ public class WindowManagerService extends IWindowManager.Stub boolean addToastWindowRequiresToken = false; if (token == null) { - if (rootType >= FIRST_APPLICATION_WINDOW && rootType <= LAST_APPLICATION_WINDOW) { - ProtoLog.w(WM_ERROR, "Attempted to add application window with unknown token " - + "%s. Aborting.", attrs.token); - return WindowManagerGlobal.ADD_BAD_APP_TOKEN; - } - if (rootType == TYPE_INPUT_METHOD) { - ProtoLog.w(WM_ERROR, "Attempted to add input method window with unknown token " - + "%s. Aborting.", attrs.token); - return WindowManagerGlobal.ADD_BAD_APP_TOKEN; - } - if (rootType == TYPE_VOICE_INTERACTION) { - ProtoLog.w(WM_ERROR, - "Attempted to add voice interaction window with unknown token " - + "%s. Aborting.", attrs.token); - return WindowManagerGlobal.ADD_BAD_APP_TOKEN; - } - if (rootType == TYPE_WALLPAPER) { - ProtoLog.w(WM_ERROR, "Attempted to add wallpaper window with unknown token " - + "%s. Aborting.", attrs.token); - return WindowManagerGlobal.ADD_BAD_APP_TOKEN; - } - if (rootType == TYPE_DREAM) { - ProtoLog.w(WM_ERROR, "Attempted to add Dream window with unknown token " - + "%s. Aborting.", attrs.token); - return WindowManagerGlobal.ADD_BAD_APP_TOKEN; - } - if (rootType == TYPE_QS_DIALOG) { - ProtoLog.w(WM_ERROR, "Attempted to add QS dialog window with unknown token " - + "%s. Aborting.", attrs.token); + if (!unprivilegedAppCanCreateTokenWith(parentWindow, callingUid, type, + rootType, attrs.token, attrs.packageName)) { return WindowManagerGlobal.ADD_BAD_APP_TOKEN; } - if (rootType == TYPE_ACCESSIBILITY_OVERLAY) { - ProtoLog.w(WM_ERROR, - "Attempted to add Accessibility overlay window with unknown token " - + "%s. Aborting.", attrs.token); - return WindowManagerGlobal.ADD_BAD_APP_TOKEN; - } - if (type == TYPE_TOAST) { - // Apps targeting SDK above N MR1 cannot arbitrary add toast windows. - if (doesAddToastWindowRequireToken(attrs.packageName, callingUid, - parentWindow)) { - ProtoLog.w(WM_ERROR, "Attempted to add a toast window with unknown token " - + "%s. Aborting.", attrs.token); - return WindowManagerGlobal.ADD_BAD_APP_TOKEN; - } - } final IBinder binder = attrs.token != null ? attrs.token : client.asBinder(); final boolean isRoundedCornerOverlay = (attrs.privateFlags & PRIVATE_FLAG_IS_ROUNDED_CORNERS_OVERLAY) != 0; @@ -1697,6 +1656,56 @@ public class WindowManagerService extends IWindowManager.Stub return res; } + private boolean unprivilegedAppCanCreateTokenWith(WindowState parentWindow, + int callingUid, int type, int rootType, IBinder tokenForLog, String packageName) { + if (rootType >= FIRST_APPLICATION_WINDOW && rootType <= LAST_APPLICATION_WINDOW) { + ProtoLog.w(WM_ERROR, "Attempted to add application window with unknown token " + + "%s. Aborting.", tokenForLog); + return false; + } + if (rootType == TYPE_INPUT_METHOD) { + ProtoLog.w(WM_ERROR, "Attempted to add input method window with unknown token " + + "%s. Aborting.", tokenForLog); + return false; + } + if (rootType == TYPE_VOICE_INTERACTION) { + ProtoLog.w(WM_ERROR, + "Attempted to add voice interaction window with unknown token " + + "%s. Aborting.", tokenForLog); + return false; + } + if (rootType == TYPE_WALLPAPER) { + ProtoLog.w(WM_ERROR, "Attempted to add wallpaper window with unknown token " + + "%s. Aborting.", tokenForLog); + return false; + } + if (rootType == TYPE_DREAM) { + ProtoLog.w(WM_ERROR, "Attempted to add Dream window with unknown token " + + "%s. Aborting.", tokenForLog); + return false; + } + if (rootType == TYPE_QS_DIALOG) { + ProtoLog.w(WM_ERROR, "Attempted to add QS dialog window with unknown token " + + "%s. Aborting.", tokenForLog); + return false; + } + if (rootType == TYPE_ACCESSIBILITY_OVERLAY) { + ProtoLog.w(WM_ERROR, + "Attempted to add Accessibility overlay window with unknown token " + + "%s. Aborting.", tokenForLog); + return false; + } + if (type == TYPE_TOAST) { + // Apps targeting SDK above N MR1 cannot arbitrary add toast windows. + if (doesAddToastWindowRequireToken(packageName, callingUid, parentWindow)) { + ProtoLog.w(WM_ERROR, "Attempted to add a toast window with unknown token " + + "%s. Aborting.", tokenForLog); + return false; + } + } + return true; + } + /** * Get existing {@link DisplayContent} or create a new one if the display is registered in * DisplayManager. @@ -2501,16 +2510,36 @@ public class WindowManagerService extends IWindowManager.Stub @Override public void addWindowToken(IBinder binder, int type, int displayId) { - if (!checkCallingPermission(MANAGE_APP_TOKENS, "addWindowToken()")) { - throw new SecurityException("Requires MANAGE_APP_TOKENS permission"); + addWindowContextToken(binder, type, displayId, null); + } + + @Override + public int addWindowContextToken(IBinder binder, int type, int displayId, String packageName) { + final boolean callerCanManageAppTokens = + checkCallingPermission(MANAGE_APP_TOKENS, "addWindowToken()"); + if (!callerCanManageAppTokens) { + // TODO(window-context): refactor checkAddPermission to not take attrs. + LayoutParams attrs = new LayoutParams(type); + attrs.packageName = packageName; + final int res = mPolicy.checkAddPermission(attrs, new int[1]); + if (res != ADD_OKAY) { + return res; + } } synchronized (mGlobalLock) { + if (!callerCanManageAppTokens) { + if (!unprivilegedAppCanCreateTokenWith(null, Binder.getCallingUid(), type, type, + null, packageName) || packageName == null) { + throw new SecurityException("Requires MANAGE_APP_TOKENS permission"); + } + } + final DisplayContent dc = getDisplayContentOrCreate(displayId, null /* token */); if (dc == null) { ProtoLog.w(WM_ERROR, "addWindowToken: Attempted to add token: %s" + " for non-exiting displayId=%d", binder, displayId); - return; + return WindowManagerGlobal.ADD_INVALID_DISPLAY; } WindowToken token = dc.getWindowToken(binder); @@ -2518,14 +2547,27 @@ public class WindowManagerService extends IWindowManager.Stub ProtoLog.w(WM_ERROR, "addWindowToken: Attempted to add binder token: %s" + " for already created window token: %s" + " displayId=%d", binder, token, displayId); - return; + return WindowManagerGlobal.ADD_DUPLICATE_ADD; } + // TODO(window-container): Clean up dead tokens if (type == TYPE_WALLPAPER) { - new WallpaperWindowToken(this, binder, true, dc, - true /* ownerCanManageAppTokens */); + new WallpaperWindowToken(this, binder, true, dc, callerCanManageAppTokens); } else { - new WindowToken(this, binder, type, true, dc, true /* ownerCanManageAppTokens */); + new WindowToken(this, binder, type, true, dc, callerCanManageAppTokens); + } + } + return WindowManagerGlobal.ADD_OKAY; + } + + @Override + public boolean isWindowToken(IBinder binder) { + synchronized (mGlobalLock) { + final WindowToken windowToken = mRoot.getWindowToken(binder); + if (windowToken == null) { + return false; } + // We don't allow activity tokens in WindowContext. TODO(window-context): rename method + return windowToken.asActivityRecord() == null; } } diff --git a/test-mock/api/test-current.txt b/test-mock/api/test-current.txt index cc260ac14147..32ca250b6c74 100644 --- a/test-mock/api/test-current.txt +++ b/test-mock/api/test-current.txt @@ -2,7 +2,6 @@ package android.test.mock { public class MockContext extends android.content.Context { - method public android.view.Display getDisplay(); method public int getDisplayId(); } diff --git a/test-mock/src/android/test/mock/MockContext.java b/test-mock/src/android/test/mock/MockContext.java index 9d913b9861e5..36074edd187e 100644 --- a/test-mock/src/android/test/mock/MockContext.java +++ b/test-mock/src/android/test/mock/MockContext.java @@ -17,6 +17,7 @@ package android.test.mock; import android.annotation.NonNull; +import android.annotation.Nullable; import android.annotation.SystemApi; import android.app.IApplicationThread; import android.app.IServiceConnection; @@ -810,6 +811,11 @@ public class MockContext extends Context { throw new UnsupportedOperationException(); } + @Override + public @NonNull Context createWindowContext(int type) { + throw new UnsupportedOperationException(); + } + @Override public boolean isRestricted() { throw new UnsupportedOperationException(); @@ -821,7 +827,6 @@ public class MockContext extends Context { throw new UnsupportedOperationException(); } - /** @hide */ @Override public Display getDisplay() { throw new UnsupportedOperationException(); -- GitLab