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