Merge "Exempt TV devices from Notification Permission"
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index 6e1d1cd..6bb35db 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -7917,6 +7917,8 @@
          * @hide
          */
         public MessagingStyle setShortcutIcon(@Nullable Icon conversationIcon) {
+            // TODO(b/228941516): This icon should be downscaled to avoid using too much memory,
+            // see reduceImageSizes.
             mShortcutIcon = conversationIcon;
             return this;
         }
@@ -8423,6 +8425,51 @@
             return makeMessagingView(StandardTemplateParams.VIEW_TYPE_HEADS_UP);
         }
 
+        /**
+         * @hide
+         */
+        @Override
+        public void reduceImageSizes(Context context) {
+            super.reduceImageSizes(context);
+            Resources resources = context.getResources();
+            boolean isLowRam = ActivityManager.isLowRamDeviceStatic();
+            if (mShortcutIcon != null) {
+                int maxSize = resources.getDimensionPixelSize(
+                        isLowRam ? R.dimen.notification_small_icon_size_low_ram
+                                : R.dimen.notification_small_icon_size);
+                mShortcutIcon.scaleDownIfNecessary(maxSize, maxSize);
+            }
+
+            int maxAvatarSize = resources.getDimensionPixelSize(
+                    isLowRam ? R.dimen.notification_person_icon_max_size
+                            : R.dimen.notification_person_icon_max_size_low_ram);
+            if (mUser != null && mUser.getIcon() != null) {
+                mUser.getIcon().scaleDownIfNecessary(maxAvatarSize, maxAvatarSize);
+            }
+
+            reduceMessagesIconSizes(mMessages, maxAvatarSize);
+            reduceMessagesIconSizes(mHistoricMessages, maxAvatarSize);
+        }
+
+        /**
+         * @hide
+         */
+        private static void reduceMessagesIconSizes(@Nullable List<Message> messages, int maxSize) {
+            if (messages == null) {
+                return;
+            }
+
+            for (Message message : messages) {
+                Person sender = message.mSender;
+                if (sender != null) {
+                    Icon icon = sender.getIcon();
+                    if (icon != null) {
+                        icon.scaleDownIfNecessary(maxSize, maxSize);
+                    }
+                }
+            }
+        }
+
         public static final class Message {
             /** @hide */
             public static final String KEY_TEXT = "text";
diff --git a/core/java/android/app/PropertyInvalidatedCache.java b/core/java/android/app/PropertyInvalidatedCache.java
index df7bf7b..add891d 100644
--- a/core/java/android/app/PropertyInvalidatedCache.java
+++ b/core/java/android/app/PropertyInvalidatedCache.java
@@ -313,6 +313,7 @@
      * one of the permitted values above.  The API is a string that is a legal Java simple
      * identifier.  The api is modified to conform to the system property style guide by
      * replacing every upper case letter with an underscore and the lower case equivalent.
+     * (An initial upper case letter is not prefixed with an underscore).
      * There is no requirement that the apiName be the name of an actual API.
      *
      * Be aware that SystemProperties has a maximum length which is private to the
@@ -326,7 +327,7 @@
             @NonNull String apiName) {
         char[] api = apiName.toCharArray();
         int upper = 0;
-        for (int i = 0; i < api.length; i++) {
+        for (int i = 1; i < api.length; i++) {
             if (Character.isUpperCase(api[i])) {
                 upper++;
             }
@@ -336,7 +337,9 @@
         for (int i = 0; i < api.length; i++) {
             if (Character.isJavaIdentifierPart(api[i])) {
                 if (Character.isUpperCase(api[i])) {
-                    suffix[j++] = '_';
+                    if (i > 0) {
+                        suffix[j++] = '_';
+                    }
                     suffix[j++] = Character.toLowerCase(api[i]);
                 } else {
                     suffix[j++] = api[i];
@@ -1286,13 +1289,23 @@
     }
 
     /**
-     * Return the name of the cache, to be used in debug messages.
+     * Return the name of the cache, to be used in debug messages.  This is exposed
+     * primarily for testing.
+     * @hide
      */
-    private final @NonNull String cacheName() {
+    public final @NonNull String cacheName() {
         return mCacheName;
     }
 
     /**
+     * Return the property used by the cache.  This is primarily for test purposes.
+     * @hide
+     */
+    public final @NonNull String propertyName() {
+        return mPropertyName;
+    }
+
+    /**
      * Return the query as a string, to be used in debug messages.  New clients should not
      * override this, but should instead add the necessary toString() method to the Query
      * class.
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index d375a9e..f5cf73b 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -185,6 +185,30 @@
         mResourcesManager = new DevicePolicyResourcesManager(context, service);
     }
 
+    /**
+     * Fetch the current value of mService.  This is used in the binder cache lambda
+     * expressions.
+     */
+    private final IDevicePolicyManager getService() {
+        return mService;
+    }
+
+    /**
+     * Fetch the current value of mParentInstance.  This is used in the binder cache
+     * lambda expressions.
+     */
+    private final boolean isParentInstance() {
+        return mParentInstance;
+    }
+
+    /**
+     * Fetch the current value of mContext.  This is used in the binder cache lambda
+     * expressions.
+     */
+    private final Context getContext() {
+        return mContext;
+    }
+
     /** @hide test will override it. */
     @VisibleForTesting
     protected int myUserId() {
@@ -3783,57 +3807,21 @@
             "android.app.extra.RESOURCE_IDS";
 
     /**
-     * A convenience class that wraps some IpcDataCache methods. Instantiate it with an
-     * API string. Instances can and should be final static. All instances of this class
-     * use the same key for invalidation.
+     * This object is a single place to tack on invalidation and disable calls.  All
+     * binder caches in this class derive from this Config, so all can be invalidated or
+     * disabled through this Config.
      */
-    private static class BinderApi {
-        private final static String KEY = "DevicePolicyManager";
-        private final String mApi;
-        BinderApi(String api) {
-            mApi = api;
-        }
-        final String api() {
-            return mApi;
-        }
-        final String key() {
-            return KEY;
-        }
-        final static void invalidate() {
-            IpcDataCache.invalidateCache(IpcDataCache.MODULE_SYSTEM, KEY);
-        }
-        final void disable() {
-            IpcDataCache.disableForCurrentProcess(mApi);
-        }
-    }
+    private static final IpcDataCache.Config sDpmCaches =
+            new IpcDataCache.Config(8, IpcDataCache.MODULE_SYSTEM, "DevicePolicyManagerCaches");
 
     /** @hide */
     public static void invalidateBinderCaches() {
-        BinderApi.invalidate();
+        sDpmCaches.invalidateCache();
     }
 
-    /**
-     * A simple wrapper for binder caches in this class. All caches are created with a
-     * maximum of 8 entries, the SYSTEM module, and a cache name that is the same as the api.
-     */
-    private static class BinderCache<Q,R> extends IpcDataCache<Q,R> {
-        BinderCache(BinderApi api, IpcDataCache.QueryHandler<Q,R> handler) {
-            super(8, IpcDataCache.MODULE_SYSTEM, api.key(), api.api(), handler);
-        }
-    }
-
-    /**
-     * Disable all caches in the local process.
-     * @hide
-     */
-    public static void disableLocalProcessCaches() {
-        disableGetKeyguardDisabledFeaturesCache();
-        disableHasDeviceOwnerCache();
-        disableGetProfileOwnerOrDeviceOwnerSupervisionComponentCache();
-        disableIsOrganizationOwnedDeviceWithManagedProfileCache();
-        disableGetDeviceOwnerOrganizationNameCache();
-        disableGetOrganizationNameForUserCache();
-        disableIsNetworkLoggingEnabled();
+    /** @hide */
+    public static void disableLocalCaches() {
+        sDpmCaches.disableAllForCurrentProcess();
     }
 
     /** @hide */
@@ -8435,54 +8423,16 @@
         return getKeyguardDisabledFeatures(admin, myUserId());
     }
 
-    // A key into the keyguard cache.
-    private static class KeyguardQuery {
-        private final ComponentName mAdmin;
-        private final int mUserHandle;
-        KeyguardQuery(@Nullable ComponentName admin, int userHandle) {
-            mAdmin = admin;
-            mUserHandle = userHandle;
-        }
-        public boolean equals(Object o) {
-            if (o instanceof KeyguardQuery) {
-                KeyguardQuery r = (KeyguardQuery) o;
-                return Objects.equals(mAdmin, r.mAdmin) && mUserHandle == r.mUserHandle;
-            } else {
-                return false;
-            }
-        }
-        public int hashCode() {
-            return ((mAdmin != null) ? mAdmin.hashCode() : 0) * 13 + mUserHandle;
-        }
-    }
-
-    // The query handler does not cache wildcard user IDs, although they should never
-    // appear in the query.
-    private static final BinderApi sGetKeyguardDisabledFeatures =
-            new BinderApi("getKeyguardDisabledFeatures");
-    private BinderCache<KeyguardQuery, Integer> mGetKeyGuardDisabledFeaturesCache =
-            new BinderCache<>(sGetKeyguardDisabledFeatures,
-                    new IpcDataCache.QueryHandler<KeyguardQuery, Integer>() {
-                        @Override
-                        public Integer apply(KeyguardQuery query) {
-                            try {
-                                return mService.getKeyguardDisabledFeatures(
-                                    query.mAdmin, query.mUserHandle, mParentInstance);
-                            } catch (RemoteException e) {
-                                throw e.rethrowFromSystemServer();
-                            }
-                        }});
-
-    /** @hide */
-    public static void disableGetKeyguardDisabledFeaturesCache() {
-        sGetKeyguardDisabledFeatures.disable();
-    }
+    private IpcDataCache<Pair<ComponentName, Integer>, Integer> mGetKeyGuardDisabledFeaturesCache =
+            new IpcDataCache<>(sDpmCaches.child("getKeyguardDisabledFeatures"),
+                    (query) -> getService().getKeyguardDisabledFeatures(
+                            query.first, query.second, isParentInstance()));
 
     /** @hide per-user version */
     @UnsupportedAppUsage
     public int getKeyguardDisabledFeatures(@Nullable ComponentName admin, int userHandle) {
         if (mService != null) {
-            return mGetKeyGuardDisabledFeaturesCache.query(new KeyguardQuery(admin, userHandle));
+            return mGetKeyGuardDisabledFeaturesCache.query(new Pair<>(admin, userHandle));
         } else {
             return KEYGUARD_DISABLE_FEATURES_NONE;
         }
@@ -8864,23 +8814,9 @@
         return name != null ? name.getPackageName() : null;
     }
 
-    private static final BinderApi sHasDeviceOwner =
-            new BinderApi("hasDeviceOwner");
-    private BinderCache<Void, Boolean> mHasDeviceOwnerCache =
-            new BinderCache<>(sHasDeviceOwner,
-                    new IpcDataCache.QueryHandler<Void, Boolean>() {
-                        @Override
-                        public Boolean apply(Void query) {
-                            try {
-                                return mService.hasDeviceOwner();
-                            } catch (RemoteException e) {
-                                throw e.rethrowFromSystemServer();
-                            }
-                        }});
-    /** @hide */
-    public static void disableHasDeviceOwnerCache() {
-        sHasDeviceOwner.disable();
-    }
+    private IpcDataCache<Void, Boolean> mHasDeviceOwnerCache =
+            new IpcDataCache<>(sDpmCaches.child("hasDeviceOwner"),
+                    (query) -> getService().hasDeviceOwner());
 
     /**
      * Called by the system to find out whether the device is managed by a Device Owner.
@@ -9256,25 +9192,10 @@
         return null;
     }
 
-    private final static BinderApi sGetProfileOwnerOrDeviceOwnerSupervisionComponent =
-            new BinderApi("getProfileOwnerOrDeviceOwnerSupervisionComponent");
-    private final BinderCache<UserHandle, ComponentName>
+    private final IpcDataCache<UserHandle, ComponentName>
             mGetProfileOwnerOrDeviceOwnerSupervisionComponentCache =
-            new BinderCache(sGetProfileOwnerOrDeviceOwnerSupervisionComponent,
-                    new IpcDataCache.QueryHandler<UserHandle, ComponentName>() {
-                        @Override
-                        public ComponentName apply(UserHandle user) {
-                            try {
-                                return mService.getProfileOwnerOrDeviceOwnerSupervisionComponent(
-                                    user);
-                            } catch (RemoteException re) {
-                                throw re.rethrowFromSystemServer();
-                            }
-                        }});
-    /** @hide */
-    public static void disableGetProfileOwnerOrDeviceOwnerSupervisionComponentCache() {
-        sGetProfileOwnerOrDeviceOwnerSupervisionComponent.disable();
-    }
+            new IpcDataCache<>(sDpmCaches.child("getProfileOwnerOrDeviceOwnerSupervisionComponent"),
+                    (arg) -> getService().getProfileOwnerOrDeviceOwnerSupervisionComponent(arg));
 
     /**
      * Returns the configured supervision app if it exists and is the device owner or policy owner.
@@ -9329,23 +9250,9 @@
         return null;
     }
 
-    private final static BinderApi sIsOrganizationOwnedDeviceWithManagedProfile =
-            new BinderApi("isOrganizationOwnedDeviceWithManagedProfile");
-    private final BinderCache<Void, Boolean> mIsOrganizationOwnedDeviceWithManagedProfileCache =
-            new BinderCache(sIsOrganizationOwnedDeviceWithManagedProfile,
-                    new IpcDataCache.QueryHandler<Void, Boolean>() {
-                        @Override
-                        public Boolean apply(Void query) {
-                            try {
-                                return mService.isOrganizationOwnedDeviceWithManagedProfile();
-                            } catch (RemoteException re) {
-                                throw re.rethrowFromSystemServer();
-                            }
-                        }});
-    /** @hide */
-    public static void disableIsOrganizationOwnedDeviceWithManagedProfileCache() {
-        sIsOrganizationOwnedDeviceWithManagedProfile.disable();
-    }
+    private final IpcDataCache<Void, Boolean> mIsOrganizationOwnedDeviceWithManagedProfileCache =
+            new IpcDataCache(sDpmCaches.child("isOrganizationOwnedDeviceWithManagedProfile"),
+                    (query) -> getService().isOrganizationOwnedDeviceWithManagedProfile());
 
     /**
      * Apps can use this method to find out if the device was provisioned as
@@ -12927,23 +12834,9 @@
         }
     }
 
-    private final static BinderApi sGetDeviceOwnerOrganizationName =
-            new BinderApi("getDeviceOwnerOrganizationName");
-    private final BinderCache<Void, CharSequence> mGetDeviceOwnerOrganizationNameCache =
-            new BinderCache(sGetDeviceOwnerOrganizationName,
-                    new IpcDataCache.QueryHandler<Void, CharSequence>() {
-                        @Override
-                        public CharSequence apply(Void query) {
-                            try {
-                                return mService.getDeviceOwnerOrganizationName();
-                            } catch (RemoteException re) {
-                                throw re.rethrowFromSystemServer();
-                            }
-                        }});
-    /** @hide */
-    public static void disableGetDeviceOwnerOrganizationNameCache() {
-        sGetDeviceOwnerOrganizationName.disable();
-    }
+    private final IpcDataCache<Void, CharSequence> mGetDeviceOwnerOrganizationNameCache =
+            new IpcDataCache(sDpmCaches.child("getDeviceOwnerOrganizationName"),
+                    (query) -> getService().getDeviceOwnerOrganizationName());
 
     /**
      * Called by the system to retrieve the name of the organization managing the device.
@@ -12960,23 +12853,9 @@
         return mGetDeviceOwnerOrganizationNameCache.query(null);
     }
 
-    private final static BinderApi sGetOrganizationNameForUser =
-            new BinderApi("getOrganizationNameForUser");
-    private final BinderCache<Integer, CharSequence> mGetOrganizationNameForUserCache =
-            new BinderCache(sGetOrganizationNameForUser,
-                    new IpcDataCache.QueryHandler<Integer, CharSequence>() {
-                        @Override
-                        public CharSequence apply(Integer userHandle) {
-                            try {
-                                return mService.getOrganizationNameForUser(userHandle);
-                            } catch (RemoteException re) {
-                                throw re.rethrowFromSystemServer();
-                            }
-                        }});
-    /** @hide */
-    public static void disableGetOrganizationNameForUserCache() {
-        sGetOrganizationNameForUser.disable();
-    }
+    private final IpcDataCache<Integer, CharSequence> mGetOrganizationNameForUserCache =
+            new IpcDataCache<>(sDpmCaches.child("getOrganizationNameForUser"),
+                    (query) -> getService().getOrganizationNameForUser(query));
 
     /**
      * Retrieve the default title message used in the confirm credentials screen for a given user.
@@ -13372,24 +13251,10 @@
         }
     }
 
-    private final static BinderApi sNetworkLoggingApi = new BinderApi("isNetworkLoggingEnabled");
-    private BinderCache<ComponentName, Boolean> mIsNetworkLoggingEnabledCache =
-            new BinderCache<>(sNetworkLoggingApi,
-                    new IpcDataCache.QueryHandler<ComponentName, Boolean>() {
-                        @Override
-                        public Boolean apply(ComponentName admin) {
-                            try {
-                                return mService.isNetworkLoggingEnabled(
-                                    admin, mContext.getPackageName());
-                            } catch (RemoteException re) {
-                                throw re.rethrowFromSystemServer();
-                            }
-                        }});
-
-    /** @hide */
-    public static void disableIsNetworkLoggingEnabled() {
-        sNetworkLoggingApi.disable();
-    }
+    private IpcDataCache<ComponentName, Boolean> mIsNetworkLoggingEnabledCache =
+            new IpcDataCache<>(sDpmCaches.child("isNetworkLoggingEnabled"),
+                    (admin) -> getService().isNetworkLoggingEnabled(admin,
+                            getContext().getPackageName()));
 
     /**
      * Return whether network logging is enabled by a device owner or profile owner of
diff --git a/core/java/android/os/IpcDataCache.java b/core/java/android/os/IpcDataCache.java
index 0efa34c..bf44d65 100644
--- a/core/java/android/os/IpcDataCache.java
+++ b/core/java/android/os/IpcDataCache.java
@@ -23,7 +23,7 @@
 import android.annotation.TestApi;
 import android.app.PropertyInvalidatedCache;
 import android.text.TextUtils;
-import android.util.Log;
+import android.util.ArraySet;
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.util.FastPrintWriter;
@@ -35,7 +35,6 @@
 import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.HashMap;
-import java.util.HashSet;
 import java.util.LinkedHashMap;
 import java.util.Map;
 import java.util.Objects;
@@ -324,8 +323,8 @@
     @SystemApi(client=SystemApi.Client.MODULE_LIBRARIES)
     @TestApi
     public IpcDataCache(int maxEntries, @NonNull @IpcDataCacheModule String module,
-            @NonNull String api,
-            @NonNull String cacheName, @NonNull QueryHandler<Query, Result> computer) {
+            @NonNull String api, @NonNull String cacheName,
+            @NonNull QueryHandler<Query, Result> computer) {
         super(maxEntries, module, api, cacheName, computer);
     }
 
@@ -382,4 +381,210 @@
             @NonNull String api) {
         PropertyInvalidatedCache.invalidateCache(module, api);
     }
+
+    /**
+     * This is a convenience class that encapsulates configuration information for a
+     * cache.  It may be supplied to the cache constructors in lieu of the other
+     * parameters.  The class captures maximum entry count, the module, the key, and the
+     * api.
+     *
+     * There are three specific use cases supported by this class.
+     *
+     * 1. Instance-per-cache: create a static instance of this class using the same
+     *    parameters as would have been given to IpcDataCache (or
+     *    PropertyInvalidatedCache).  This static instance provides a hook for the
+     *    invalidateCache() and disableForLocalProcess() calls, which, generally, must
+     *    also be static.
+     *
+     * 2. Short-hand for shared configuration parameters: create an instance of this class
+     *    to capture the maximum number of entries and the module to be used by more than
+     *    one cache in the class.  Refer to this instance when creating new configs.  Only
+     *    the api and (optionally key) for the new cache must be supplied.
+     *
+     * 3. Tied caches: create a static instance of this class to capture the maximum
+     *    number of entries, the module, and the key.  Refer to this instance when
+     *    creating a new config that differs in only the api.  The new config can be
+     *    created as part of the cache constructor.  All caches that trace back to the
+     *    root config share the same key and are invalidated by the invalidateCache()
+     *    method of the root config.  All caches that trace back to the root config can be
+     *    disabled in the local process by the disableAllForCurrentProcess() method of the
+     *    root config.
+     *
+     * @hide
+     */
+    public static class Config {
+        private final int mMaxEntries;
+        @IpcDataCacheModule
+        private final String mModule;
+        private final String mApi;
+        private final String mName;
+
+        /**
+         * The list of cache names that were created extending this Config.  If
+         * disableForCurrentProcess() is invoked on this config then all children will be
+         * disabled.  Furthermore, any new children based off of this config will be
+         * disabled.  The construction order guarantees that new caches will be disabled
+         * before they are created (the Config must be created before the IpcDataCache is
+         * created).
+         */
+        private ArraySet<String> mChildren;
+
+        /**
+         * True if registered children are disabled in the current process.  If this is
+         * true then all new children are disabled as they are registered.
+         */
+        private boolean mDisabled = false;
+
+        public Config(int maxEntries, @NonNull @IpcDataCacheModule String module,
+                @NonNull String api, @NonNull String name) {
+            mMaxEntries = maxEntries;
+            mModule = module;
+            mApi = api;
+            mName = name;
+        }
+
+        /**
+         * A short-hand constructor that makes the name the same as the api.
+         */
+        public Config(int maxEntries, @NonNull @IpcDataCacheModule String module,
+                @NonNull String api) {
+            this(maxEntries, module, api, api);
+        }
+
+        /**
+         * Copy the module and max entries from the Config and take the api and name from
+         * the parameter list.
+         */
+        public Config(@NonNull Config root, @NonNull String api, @NonNull String name) {
+            this(root.maxEntries(), root.module(), api, name);
+        }
+
+        /**
+         * Copy the module and max entries from the Config and take the api and name from
+         * the parameter list.
+         */
+        public Config(@NonNull Config root, @NonNull String api) {
+            this(root.maxEntries(), root.module(), api, api);
+        }
+
+        /**
+         * Fetch a config that is a child of <this>.  The child shares the same api as the
+         * parent and is registered with the parent for the purposes of disabling in the
+         * current process.
+         */
+        public Config child(@NonNull String name) {
+            final Config result = new Config(this, api(), name);
+            registerChild(name);
+            return result;
+        }
+
+        public final int maxEntries() {
+            return mMaxEntries;
+        }
+
+        @IpcDataCacheModule
+        public final @NonNull String module() {
+            return mModule;
+        }
+
+        public final @NonNull String api() {
+            return mApi;
+        }
+
+        public final @NonNull String name() {
+            return mName;
+        }
+
+        /**
+         * Register a child cache name.  If disableForCurrentProcess() has been called
+         * against this cache, disable th new child.
+         */
+        private final void registerChild(String name) {
+            synchronized (this) {
+                if (mChildren == null) {
+                    mChildren = new ArraySet<>();
+                }
+                mChildren.add(name);
+                if (mDisabled) {
+                    IpcDataCache.disableForCurrentProcess(name);
+                }
+            }
+        }
+
+        /**
+         * Invalidate all caches that share this Config's module and api.
+         */
+        public void invalidateCache() {
+            IpcDataCache.invalidateCache(mModule, mApi);
+        }
+
+        /**
+         * Disable all caches that share this Config's name.
+         */
+        public void disableForCurrentProcess() {
+            IpcDataCache.disableForCurrentProcess(mName);
+        }
+
+        /**
+         * Disable this cache and all children.  Any child that is added in the future
+         * will alwo be disabled.
+         */
+        public void disableAllForCurrentProcess() {
+            synchronized (this) {
+                mDisabled = true;
+                disableForCurrentProcess();
+                if (mChildren != null) {
+                    for (String c : mChildren) {
+                        IpcDataCache.disableForCurrentProcess(c);
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * Create a new cache using a config.
+     * @hide
+     */
+    public IpcDataCache(@NonNull Config config, @NonNull QueryHandler<Query, Result> computer) {
+        super(config.maxEntries(), config.module(), config.api(), config.name(), computer);
+    }
+
+    /**
+     * An interface suitable for a lambda expression instead of a QueryHandler.
+     * @hide
+     */
+    public interface RemoteCall<Query, Result> {
+        Result apply(Query query) throws RemoteException;
+    }
+
+    /**
+     * This is a query handler that is created with a lambda expression that is invoked
+     * every time the handler is called.  The handler is specifically meant for services
+     * hosted by system_server; the handler automatically rethrows RemoteException as a
+     * RuntimeException, which is the usual handling for failed binder calls.
+     */
+    private static class SystemServerCallHandler<Query, Result>
+            extends IpcDataCache.QueryHandler<Query, Result> {
+        private final RemoteCall<Query, Result> mHandler;
+        public SystemServerCallHandler(RemoteCall handler) {
+            mHandler = handler;
+        }
+        @Override
+        public Result apply(Query query) {
+            try {
+                return mHandler.apply(query);
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+    }
+
+    /**
+     * Create a cache using a config and a lambda expression.
+     * @hide
+     */
+    public IpcDataCache(@NonNull Config config, @NonNull RemoteCall<Query, Result> computer) {
+        this(config, new SystemServerCallHandler<>(computer));
+    }
 }
diff --git a/core/java/android/service/quicksettings/TileService.java b/core/java/android/service/quicksettings/TileService.java
index 0829d28..8550219 100644
--- a/core/java/android/service/quicksettings/TileService.java
+++ b/core/java/android/service/quicksettings/TileService.java
@@ -353,7 +353,13 @@
         try {
             mTile = mService.getTile(mTileToken);
         } catch (RemoteException e) {
-            throw new RuntimeException("Unable to reach IQSService", e);
+            String name = TileService.this.getClass().getSimpleName();
+            Log.w(TAG, name + " - Couldn't get tile from IQSService.", e);
+            // If we couldn't receive the tile, there's not much reason to continue as users won't
+            // be able to interact. Returning `null` will trigger an unbind in SystemUI and
+            // eventually we'll rebind when needed. This usually means that SystemUI crashed
+            // right after binding and therefore `mService` is outdated.
+            return null;
         }
         if (mTile != null) {
             mTile.setService(mService, mTileToken);
diff --git a/core/java/android/view/Window.java b/core/java/android/view/Window.java
index 555fd43..02027e4 100644
--- a/core/java/android/view/Window.java
+++ b/core/java/android/view/Window.java
@@ -67,9 +67,7 @@
  * window manager. It provides standard UI policies such as a background, title
  * area, default key processing, etc.
  *
- * <p>The only existing implementation of this abstract class is
- * android.view.PhoneWindow, which you should instantiate when needing a
- * Window.
+ * <p>The framework will instantiate an implementation of this class on behalf of the application.
  */
 public abstract class Window {
     /** Flag for the "options panel" feature.  This is enabled by default. */
diff --git a/core/java/android/view/WindowInsets.java b/core/java/android/view/WindowInsets.java
index ba34179..1c6d93d 100644
--- a/core/java/android/view/WindowInsets.java
+++ b/core/java/android/view/WindowInsets.java
@@ -675,7 +675,7 @@
     @Deprecated
     @NonNull
     public Insets getStableInsets() {
-        return getInsets(mTypeMaxInsetsMap, mCompatInsetsTypes);
+        return getInsets(mTypeMaxInsetsMap, systemBars());
     }
 
     /**
diff --git a/core/java/com/android/internal/app/LocalePickerWithRegion.java b/core/java/com/android/internal/app/LocalePickerWithRegion.java
index 9fe4d67..fadeea4 100644
--- a/core/java/com/android/internal/app/LocalePickerWithRegion.java
+++ b/core/java/com/android/internal/app/LocalePickerWithRegion.java
@@ -129,6 +129,12 @@
         boolean isForCountryMode = parent != null;
 
         if (!TextUtils.isEmpty(appPackageName) && !isForCountryMode) {
+            // Filter current system locale to add them into suggestion
+            LocaleList systemLangList = LocaleList.getDefault();
+            for(int i = 0; i < systemLangList.size(); i++) {
+                langTagsToIgnore.add(systemLangList.get(i).toLanguageTag());
+            }
+
             if (appCurrentLocale != null) {
                 Log.d(TAG, "appCurrentLocale: " + appCurrentLocale.getLocale().toLanguageTag());
                 langTagsToIgnore.add(appCurrentLocale.getLocale().toLanguageTag());
@@ -168,9 +174,20 @@
                     result.mLocaleStatus == LocaleStatus.GET_SUPPORTED_LANGUAGE_FROM_LOCAL_CONFIG
                     || result.mLocaleStatus == LocaleStatus.GET_SUPPORTED_LANGUAGE_FROM_ASSET;
 
+            // Add current system language into suggestion list
+            for(LocaleStore.LocaleInfo localeInfo: LocaleStore.getSystemCurrentLocaleInfo()) {
+                if (appCurrentLocale == null ||
+                        !localeInfo.getLocale().equals(appCurrentLocale.getLocale())) {
+                    mLocaleList.add(localeInfo);
+                }
+            }
+
+            // Filter the language not support in app
             mLocaleList = filterTheLanguagesNotSupportedInApp(
                     shouldShowList, result.mAppSupportedLocales);
 
+            Log.d(TAG, "mLocaleList after app-supported filter:  " + mLocaleList.size());
+
             // Add "system language"
             if (!isForCountryMode && shouldShowList) {
                 mLocaleList.add(LocaleStore.getSystemDefaultLocaleInfo(appCurrentLocale == null));
@@ -190,7 +207,6 @@
                     }
                 }
             }
-            Log.d(TAG, "mLocaleList after app-supported filter:  " + filteredList.size());
         }
 
         return filteredList;
diff --git a/core/java/com/android/internal/app/LocaleStore.java b/core/java/com/android/internal/app/LocaleStore.java
index eb11b9b..9480362 100644
--- a/core/java/com/android/internal/app/LocaleStore.java
+++ b/core/java/com/android/internal/app/LocaleStore.java
@@ -25,9 +25,11 @@
 import android.util.Log;
 
 import java.io.Serializable;
+import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.IllformedLocaleException;
+import java.util.List;
 import java.util.Locale;
 import java.util.Set;
 
@@ -278,6 +280,22 @@
     }
 
     /**
+     * Returns a list of system languages with LocaleInfo
+     */
+    public static List<LocaleInfo> getSystemCurrentLocaleInfo() {
+        List<LocaleInfo> localeList = new ArrayList<>();
+
+        LocaleList systemLangList = LocaleList.getDefault();
+        for(int i = 0; i < systemLangList.size(); i++) {
+            LocaleInfo systemLocaleInfo = new LocaleInfo(systemLangList.get(i));
+            systemLocaleInfo.mSuggestionFlags |= LocaleInfo.SUGGESTION_TYPE_SIM;
+            systemLocaleInfo.mIsTranslated = true;
+            localeList.add(systemLocaleInfo);
+        }
+        return localeList;
+    }
+
+    /**
      * The "system default" is special case for per-app picker. Intentionally keep the locale
      * empty to let activity know "system default" been selected.
      */
diff --git a/core/java/com/android/internal/app/ResolverActivity.java b/core/java/com/android/internal/app/ResolverActivity.java
index bfd8ff9..17c2dc0 100644
--- a/core/java/com/android/internal/app/ResolverActivity.java
+++ b/core/java/com/android/internal/app/ResolverActivity.java
@@ -1480,7 +1480,7 @@
             if (intent != null) {
                 prepareIntentForCrossProfileLaunch(intent);
             }
-            safelyStartActivityInternal(otherProfileResolveInfo,
+            safelyStartActivityAsUser(otherProfileResolveInfo,
                     mMultiProfilePagerAdapter.getInactiveListAdapter().mResolverListController
                             .getUserHandle());
         });
diff --git a/core/java/com/android/internal/app/ResolverListAdapter.java b/core/java/com/android/internal/app/ResolverListAdapter.java
index 988ecc78..351ac45 100644
--- a/core/java/com/android/internal/app/ResolverListAdapter.java
+++ b/core/java/com/android/internal/app/ResolverListAdapter.java
@@ -169,107 +169,206 @@
     }
 
     /**
-     * Rebuild the list of resolvers. In some cases some parts will need some asynchronous work
-     * to complete.
+     * Rebuild the list of resolvers. When rebuilding is complete, queue the {@code onPostListReady}
+     * callback on the main handler with {@code rebuildCompleted} true.
      *
-     * The {@code doPostProcessing } parameter is used to specify whether to update the UI and
-     * load additional targets (e.g. direct share) after the list has been rebuilt. This is used
-     * in the case where we want to load the inactive profile's resolved apps to know the
+     * In some cases some parts will need some asynchronous work to complete. Then this will first
+     * immediately queue {@code onPostListReady} (on the main handler) with {@code rebuildCompleted}
+     * false; only when the asynchronous work completes will this then go on to queue another
+     * {@code onPostListReady} callback with {@code rebuildCompleted} true.
+     *
+     * The {@code doPostProcessing} parameter is used to specify whether to update the UI and
+     * load additional targets (e.g. direct share) after the list has been rebuilt. We may choose
+     * to skip that step if we're only loading the inactive profile's resolved apps to know the
      * number of targets.
      *
-     * @return Whether or not the list building is completed.
+     * @return Whether the list building was completed synchronously. If not, we'll queue the
+     * {@code onPostListReady} callback first with {@code rebuildCompleted} false, and then again
+     * with {@code rebuildCompleted} true at the end of some newly-launched asynchronous work.
+     * Otherwise the callback is only queued once, with {@code rebuildCompleted} true.
      */
     protected boolean rebuildList(boolean doPostProcessing) {
-        List<ResolvedComponentInfo> currentResolveList = null;
-        // Clear the value of mOtherProfile from previous call.
-        mOtherProfile = null;
-        mLastChosen = null;
-        mLastChosenPosition = -1;
         mDisplayList.clear();
         mIsTabLoaded = false;
+        mLastChosenPosition = -1;
 
-        if (mBaseResolveList != null) {
-            currentResolveList = mUnfilteredResolveList = new ArrayList<>();
-            mResolverListController.addResolveListDedupe(currentResolveList,
-                    mResolverListCommunicator.getTargetIntent(),
-                    mBaseResolveList);
-        } else {
-            currentResolveList = mUnfilteredResolveList =
-                    mResolverListController.getResolversForIntent(
-                            /* shouldGetResolvedFilter= */ true,
-                            mResolverListCommunicator.shouldGetActivityMetadata(),
-                            mIntents);
-            if (currentResolveList == null) {
-                processSortedList(currentResolveList, doPostProcessing);
-                return true;
-            }
-            List<ResolvedComponentInfo> originalList =
-                    mResolverListController.filterIneligibleActivities(currentResolveList,
-                            true);
-            if (originalList != null) {
-                mUnfilteredResolveList = originalList;
-            }
-        }
+        List<ResolvedComponentInfo> currentResolveList = getInitialRebuiltResolveList();
+
+        /* TODO: this seems like unnecessary extra complexity; why do we need to do this "primary"
+         * (i.e. "eligibility") filtering before evaluating the "other profile" special-treatment,
+         * but the "secondary" (i.e. "priority") filtering after? Are there in fact cases where the
+         * eligibility conditions will filter out a result that would've otherwise gotten the "other
+         * profile" treatment? Or, are there cases where the priority conditions *would* filter out
+         * a result, but we *want* that result to get the "other profile" treatment, so we only
+         * filter *after* evaluating the special-treatment conditions? If the answer to either is
+         * "no," then the filtering steps can be consolidated. (And that also makes the "unfiltered
+         * list" bookkeeping a little cleaner.)
+         */
+        mUnfilteredResolveList = performPrimaryResolveListFiltering(currentResolveList);
 
         // So far we only support a single other profile at a time.
         // The first one we see gets special treatment.
-        for (ResolvedComponentInfo info : currentResolveList) {
-            ResolveInfo resolveInfo = info.getResolveInfoAt(0);
-            if (resolveInfo.targetUserId != UserHandle.USER_CURRENT) {
-                Intent pOrigIntent = mResolverListCommunicator.getReplacementIntent(
-                        resolveInfo.activityInfo,
-                        info.getIntentAt(0));
-                Intent replacementIntent = mResolverListCommunicator.getReplacementIntent(
-                        resolveInfo.activityInfo,
-                        mResolverListCommunicator.getTargetIntent());
-                mOtherProfile = new DisplayResolveInfo(info.getIntentAt(0),
-                        resolveInfo,
-                        resolveInfo.loadLabel(mPm),
-                        resolveInfo.loadLabel(mPm),
-                        pOrigIntent != null ? pOrigIntent : replacementIntent,
-                        makePresentationGetter(resolveInfo));
-                currentResolveList.remove(info);
-                break;
-            }
+        ResolvedComponentInfo otherProfileInfo =
+                getFirstNonCurrentUserResolvedComponentInfo(currentResolveList);
+        updateOtherProfileTreatment(otherProfileInfo);
+        if (otherProfileInfo != null) {
+            currentResolveList.remove(otherProfileInfo);
+            /* TODO: the previous line removed the "other profile info" item from
+             * mUnfilteredResolveList *ONLY IF* that variable is an alias for the same List instance
+             * as currentResolveList (i.e., if no items were filtered out as the result of the
+             * earlier "primary" filtering). It seems wrong for our behavior to depend on that.
+             * Should we:
+             *  A. replicate the above removal to mUnfilteredResolveList (which is idempotent, so we
+             *     don't even have to check whether they're aliases); or
+             *  B. break the alias relationship by copying currentResolveList to a new
+             *  mUnfilteredResolveList instance if necessary before removing otherProfileInfo?
+             * In other words: do we *want* otherProfileInfo in the "unfiltered" results? Either
+             * way, we'll need one of the changes suggested above.
+             */
         }
 
-        if (mOtherProfile == null) {
+        // If no results have yet been filtered, mUnfilteredResolveList is an alias for the same
+        // List instance as currentResolveList. Then we need to make a copy to store as the
+        // mUnfilteredResolveList if we go on to filter any more items. Otherwise we've already
+        // copied the original unfiltered items to a separate List instance and can now filter
+        // the remainder in-place without any further bookkeeping.
+        boolean needsCopyOfUnfiltered = (mUnfilteredResolveList == currentResolveList);
+        mUnfilteredResolveList = performSecondaryResolveListFiltering(
+                currentResolveList, needsCopyOfUnfiltered);
+
+        return finishRebuildingListWithFilteredResults(currentResolveList, doPostProcessing);
+    }
+
+    /**
+     * Get the full (unfiltered) set of {@code ResolvedComponentInfo} records for all resolvers
+     * to be considered in a newly-rebuilt list. This list will be filtered and ranked before the
+     * rebuild is complete.
+     */
+    List<ResolvedComponentInfo> getInitialRebuiltResolveList() {
+        if (mBaseResolveList != null) {
+            List<ResolvedComponentInfo> currentResolveList = new ArrayList<>();
+            mResolverListController.addResolveListDedupe(currentResolveList,
+                    mResolverListCommunicator.getTargetIntent(),
+                    mBaseResolveList);
+            return currentResolveList;
+        } else {
+            return mResolverListController.getResolversForIntent(
+                            /* shouldGetResolvedFilter= */ true,
+                            mResolverListCommunicator.shouldGetActivityMetadata(),
+                            mIntents);
+        }
+    }
+
+    /**
+     * Remove ineligible activities from {@code currentResolveList} (if non-null), in-place. More
+     * broadly, filtering logic should apply in the "primary" stage if it should preclude items from
+     * receiving the "other profile" special-treatment described in {@code rebuildList()}.
+     *
+     * @return A copy of the original {@code currentResolveList}, if any items were removed, or a
+     * (possibly null) reference to the original list otherwise. (That is, this always returns a
+     * list of all the unfiltered items, but if no items were filtered, it's just an alias for the
+     * same list that was passed in).
+     */
+    @Nullable
+    List<ResolvedComponentInfo> performPrimaryResolveListFiltering(
+            @Nullable List<ResolvedComponentInfo> currentResolveList) {
+        /* TODO: mBaseResolveList appears to be(?) some kind of configured mode. Why is it not
+         * subject to filterIneligibleActivities, even though all the other logic still applies
+         * (including "secondary" filtering)? (This also relates to the earlier question; do we
+         * believe there's an item that would be eligible for "other profile" special treatment,
+         * except we want to filter it out as ineligible... but only if we're not in
+         * "mBaseResolveList mode"? */
+        if ((mBaseResolveList != null) || (currentResolveList == null)) {
+            return currentResolveList;
+        }
+
+        List<ResolvedComponentInfo> originalList =
+                mResolverListController.filterIneligibleActivities(currentResolveList, true);
+        return (originalList == null) ? currentResolveList : originalList;
+    }
+
+    /**
+     * Remove low-priority activities from {@code currentResolveList} (if non-null), in place. More
+     * broadly, filtering logic should apply in the "secondary" stage to prevent items from
+     * appearing in the rebuilt-list results, while still considering those items for the "other
+     * profile" special-treatment described in {@code rebuildList()}.
+     *
+     * @return the same (possibly null) List reference as {@code currentResolveList}, if the list is
+     * unmodified as a result of filtering; or, if some item(s) were removed, then either a copy of
+     * the original {@code currentResolveList} (if {@code returnCopyOfOriginalListIfModified} is
+     * true), or null (otherwise).
+     */
+    @Nullable
+    List<ResolvedComponentInfo> performSecondaryResolveListFiltering(
+            @Nullable List<ResolvedComponentInfo> currentResolveList,
+            boolean returnCopyOfOriginalListIfModified) {
+        if ((currentResolveList == null) || currentResolveList.isEmpty()) {
+            return currentResolveList;
+        }
+        return mResolverListController.filterLowPriority(
+                currentResolveList, returnCopyOfOriginalListIfModified);
+    }
+
+    /**
+     * Update the special "other profile" UI treatment based on the components resolved for a
+     * newly-built list.
+     *
+     * @param otherProfileInfo the first {@code ResolvedComponentInfo} specifying a
+     * {@code targetUserId} other than {@code USER_CURRENT}, or null if no such component info was
+     * found in the process of rebuilding the list (or if any such candidates were already removed
+     * due to "primary filtering").
+     */
+    void updateOtherProfileTreatment(@Nullable ResolvedComponentInfo otherProfileInfo) {
+        mLastChosen = null;
+
+        if (otherProfileInfo != null) {
+            mOtherProfile = makeOtherProfileDisplayResolveInfo(
+                    mContext, otherProfileInfo, mPm, mResolverListCommunicator, mIconDpi);
+        } else {
+            mOtherProfile = null;
             try {
                 mLastChosen = mResolverListController.getLastChosen();
+                // TODO: does this also somehow need to update mLastChosenPosition? If so, maybe
+                // the current method should also take responsibility for re-initializing
+                // mLastChosenPosition, where it's currently done at the start of rebuildList()?
+                // (Why is this related to the presence of mOtherProfile in fhe first place?)
             } catch (RemoteException re) {
                 Log.d(TAG, "Error calling getLastChosenActivity\n" + re);
             }
         }
+    }
 
-        setPlaceholderCount(0);
-        int n;
-        if ((currentResolveList != null) && ((n = currentResolveList.size()) > 0)) {
-            // We only care about fixing the unfilteredList if the current resolve list and
-            // current resolve list are currently the same.
-            List<ResolvedComponentInfo> originalList =
-                    mResolverListController.filterLowPriority(currentResolveList,
-                            mUnfilteredResolveList == currentResolveList);
-            if (originalList != null) {
-                mUnfilteredResolveList = originalList;
-            }
-
-            if (currentResolveList.size() > 1) {
-                int placeholderCount = currentResolveList.size();
-                if (mResolverListCommunicator.useLayoutWithDefault()) {
-                    --placeholderCount;
-                }
-                setPlaceholderCount(placeholderCount);
-                createSortingTask(doPostProcessing).execute(currentResolveList);
-                postListReadyRunnable(doPostProcessing, /* rebuildCompleted */ false);
-                return false;
-            } else {
-                processSortedList(currentResolveList, doPostProcessing);
-                return true;
-            }
-        } else {
-            processSortedList(currentResolveList, doPostProcessing);
+    /**
+     * Prepare the appropriate placeholders to eventually display the final set of resolved
+     * components in a newly-rebuilt list, and spawn an asynchronous sorting task if necessary.
+     * This eventually results in a {@code onPostListReady} callback with {@code rebuildCompleted}
+     * true; if any asynchronous work is required, that will first be preceded by a separate
+     * occurrence of the callback with {@code rebuildCompleted} false (once there are placeholders
+     * set up to represent the pending asynchronous results).
+     * @return Whether we were able to do all the work to prepare the list for display
+     * synchronously; if false, there will eventually be two separate {@code onPostListReady}
+     * callbacks, first with placeholders to represent pending asynchronous results, then later when
+     * the results are ready for presentation.
+     */
+    boolean finishRebuildingListWithFilteredResults(
+            @Nullable List<ResolvedComponentInfo> filteredResolveList, boolean doPostProcessing) {
+        if (filteredResolveList == null || filteredResolveList.size() < 2) {
+            // No asynchronous work to do.
+            setPlaceholderCount(0);
+            processSortedList(filteredResolveList, doPostProcessing);
             return true;
         }
+
+        int placeholderCount = filteredResolveList.size();
+        if (mResolverListCommunicator.useLayoutWithDefault()) {
+            --placeholderCount;
+        }
+        setPlaceholderCount(placeholderCount);
+
+        // Send an "incomplete" list-ready while the async task is running.
+        postListReadyRunnable(doPostProcessing, /* rebuildCompleted */ false);
+        createSortingTask(doPostProcessing).execute(filteredResolveList);
+        return false;
     }
 
     AsyncTask<List<ResolvedComponentInfo>,
@@ -636,6 +735,59 @@
     }
 
     /**
+     * Find the first element in a list of {@code ResolvedComponentInfo} objects whose
+     * {@code ResolveInfo} specifies a {@code targetUserId} other than the current user.
+     * @return the first ResolvedComponentInfo targeting a non-current user, or null if there are
+     * none (or if the list itself is null).
+     */
+    private static ResolvedComponentInfo getFirstNonCurrentUserResolvedComponentInfo(
+            @Nullable List<ResolvedComponentInfo> resolveList) {
+        if (resolveList == null) {
+            return null;
+        }
+
+        for (ResolvedComponentInfo info : resolveList) {
+            ResolveInfo resolveInfo = info.getResolveInfoAt(0);
+            if (resolveInfo.targetUserId != UserHandle.USER_CURRENT) {
+                return info;
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Set up a {@code DisplayResolveInfo} to provide "special treatment" for the first "other"
+     * profile in the resolve list (i.e., the first non-current profile to appear as the target user
+     * of an element in the resolve list).
+     */
+    private static DisplayResolveInfo makeOtherProfileDisplayResolveInfo(
+            Context context,
+            ResolvedComponentInfo resolvedComponentInfo,
+            PackageManager pm,
+            ResolverListCommunicator resolverListCommunicator,
+            int iconDpi) {
+        ResolveInfo resolveInfo = resolvedComponentInfo.getResolveInfoAt(0);
+
+        Intent pOrigIntent = resolverListCommunicator.getReplacementIntent(
+                resolveInfo.activityInfo,
+                resolvedComponentInfo.getIntentAt(0));
+        Intent replacementIntent = resolverListCommunicator.getReplacementIntent(
+                resolveInfo.activityInfo,
+                resolverListCommunicator.getTargetIntent());
+
+        ResolveInfoPresentationGetter presentationGetter =
+                new ResolveInfoPresentationGetter(context, iconDpi, resolveInfo);
+
+        return new DisplayResolveInfo(
+                resolvedComponentInfo.getIntentAt(0),
+                resolveInfo,
+                resolveInfo.loadLabel(pm),
+                resolveInfo.loadLabel(pm),
+                pOrigIntent != null ? pOrigIntent : replacementIntent,
+                presentationGetter);
+    }
+
+    /**
      * Necessary methods to communicate between {@link ResolverListAdapter}
      * and {@link ResolverActivity}.
      */
diff --git a/core/java/com/android/internal/policy/SystemBarUtils.java b/core/java/com/android/internal/policy/SystemBarUtils.java
index 6bf1333..7a1ac07 100644
--- a/core/java/com/android/internal/policy/SystemBarUtils.java
+++ b/core/java/com/android/internal/policy/SystemBarUtils.java
@@ -43,7 +43,7 @@
      * Gets the status bar height with a specific display cutout.
      */
     public static int getStatusBarHeight(Resources res, DisplayCutout cutout) {
-        final int defaultSize = res.getDimensionPixelSize(R.dimen.status_bar_height);
+        final int defaultSize = res.getDimensionPixelSize(R.dimen.status_bar_height_default);
         final int safeInsetTop = cutout == null ? 0 : cutout.getSafeInsetTop();
         final int waterfallInsetTop = cutout == null ? 0 : cutout.getWaterfallInsets().top;
         // The status bar height should be:
@@ -73,7 +73,7 @@
             }
         }
         final int defaultSize =
-                context.getResources().getDimensionPixelSize(R.dimen.status_bar_height);
+                context.getResources().getDimensionPixelSize(R.dimen.status_bar_height_default);
         // The status bar height should be:
         // Max(top cutout size, (status bar default height + waterfall top size))
         return Math.max(insets.top, defaultSize + waterfallInsets.top);
diff --git a/core/res/res/values-land/dimens.xml b/core/res/res/values-land/dimens.xml
index f1e5888..ca549ae 100644
--- a/core/res/res/values-land/dimens.xml
+++ b/core/res/res/values-land/dimens.xml
@@ -27,6 +27,8 @@
     <dimen name="password_keyboard_spacebar_vertical_correction">2dip</dimen>
     <dimen name="preference_widget_width">72dp</dimen>
 
+    <!-- Height of the status bar -->
+    <dimen name="status_bar_height">@dimen/status_bar_height_landscape</dimen>
     <!-- Height of area above QQS where battery/time go -->
     <dimen name="quick_qs_offset_height">48dp</dimen>
     <!-- Default height of an action bar. -->
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 30fb45e..dece426 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -3807,6 +3807,9 @@
     <!-- True if the device supports running activities on secondary displays. -->
     <bool name="config_supportsMultiDisplay">true</bool>
 
+    <!-- Indicates whether the device supports bubble notifications or not. -->
+    <bool name="config_supportsBubble">true</bool>
+
     <!-- True if the device has no home screen. That is a launcher activity
          where the user can launch other applications from.  -->
     <bool name="config_noHomeScreen">false</bool>
diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml
index 323c726..d9d1a08 100644
--- a/core/res/res/values/dimens.xml
+++ b/core/res/res/values/dimens.xml
@@ -39,14 +39,17 @@
     <!-- Elevation of toast view -->
     <dimen name="toast_elevation">2dp</dimen>
 
+    <!-- The default height of the status bar used in {@link SystemBarUtils#getStatusBarHeight} to
+         calculate the status bar height. -->
+    <dimen name="status_bar_height_default">24dp</dimen>
     <!-- Height of the status bar.
          Do not read this dimen directly. Use {@link SystemBarUtils#getStatusBarHeight} instead.
          -->
-    <dimen name="status_bar_height">24dp</dimen>
+    <dimen name="status_bar_height">@dimen/status_bar_height_portrait</dimen>
     <!-- Height of the status bar in portrait.
          Do not read this dimen directly. Use {@link SystemBarUtils#getStatusBarHeight} instead.
          -->
-    <dimen name="status_bar_height_portrait">@dimen/status_bar_height</dimen>
+    <dimen name="status_bar_height_portrait">24dp</dimen>
     <!-- Height of the status bar in landscape.
          Do not read this dimen directly. Use {@link SystemBarUtils#getStatusBarHeight} instead.
          -->
@@ -779,6 +782,10 @@
     <dimen name="notification_left_icon_start">@dimen/notification_icon_circle_start</dimen>
     <!-- The alpha of a disabled notification button -->
     <item type="dimen" format="float" name="notification_action_disabled_alpha">0.5</item>
+    <!-- The maximum size of Person avatar image in MessagingStyle notifications.
+         This is bigger than displayed because listeners can use it for other displays
+         e.g. wearables. -->
+    <dimen name="notification_person_icon_max_size">144dp</dimen>
 
     <!-- The maximum size of the small notification icon on low memory devices. -->
     <dimen name="notification_small_icon_size_low_ram">@dimen/notification_small_icon_size</dimen>
@@ -792,6 +799,10 @@
     <dimen name="notification_big_picture_max_width_low_ram">294dp</dimen>
     <!-- The size of the right icon image when on low ram -->
     <dimen name="notification_right_icon_size_low_ram">@dimen/notification_right_icon_size</dimen>
+    <!-- The maximum size of Person avatar image in MessagingStyle notifications.
+     This is bigger than displayed because listeners can use it for other displays
+     e.g. wearables. -->
+    <dimen name="notification_person_icon_max_size_low_ram">96dp</dimen>
     <!-- The maximum size of the grayscale icon -->
     <dimen name="notification_grayscale_icon_max_size">256dp</dimen>
 
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 004cb4c..6104701 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -6292,12 +6292,12 @@
     <!-- Title for the notification channel notifying user of abusive background apps. [CHAR LIMIT=NONE] -->
     <string name="notification_channel_abusive_bg_apps">Background Activity</string>
     <!-- Title of notification indicating abusive background apps. [CHAR LIMIT=NONE] -->
-    <string name="notification_title_abusive_bg_apps">An app is using battery</string>
+    <string name="notification_title_abusive_bg_apps">An app is draining battery</string>
     <!-- Title of notification indicating long running foreground services. [CHAR LIMIT=NONE] -->
     <string name="notification_title_long_running_fgs">An app is still active</string>
     <!-- Content of notification indicating abusive background apps. [CHAR LIMIT=NONE] -->
     <string name="notification_content_abusive_bg_apps">
-        <xliff:g id="app" example="Gmail">%1$s</xliff:g> is using battery in the background. Tap to review.
+        <xliff:g id="app" example="Gmail">%1$s</xliff:g> is running in the background. Tap to manage battery usage.
     </string>
     <!-- Content of notification indicating long running foreground service. [CHAR LIMIT=NONE] -->
     <string name="notification_content_long_running_fgs">
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 61ca18c..e07326b 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -379,6 +379,7 @@
   <java-symbol type="bool" name="config_supportSpeakerNearUltrasound" />
   <java-symbol type="bool" name="config_supportAudioSourceUnprocessed" />
   <java-symbol type="bool" name="config_freeformWindowManagement" />
+  <java-symbol type="bool" name="config_supportsBubble" />
   <java-symbol type="bool" name="config_supportsMultiWindow" />
   <java-symbol type="bool" name="config_supportsSplitScreenMultiWindow" />
   <java-symbol type="bool" name="config_supportsMultiDisplay" />
@@ -3604,6 +3605,7 @@
   <java-symbol type="dimen" name="notification_actions_icon_drawable_size"/>
   <java-symbol type="dimen" name="notification_custom_view_max_image_height"/>
   <java-symbol type="dimen" name="notification_custom_view_max_image_width"/>
+  <java-symbol type="dimen" name="notification_person_icon_max_size" />
 
   <java-symbol type="dimen" name="notification_small_icon_size_low_ram"/>
   <java-symbol type="dimen" name="notification_big_picture_max_height_low_ram"/>
@@ -3612,6 +3614,7 @@
   <java-symbol type="dimen" name="notification_grayscale_icon_max_size"/>
   <java-symbol type="dimen" name="notification_custom_view_max_image_height_low_ram"/>
   <java-symbol type="dimen" name="notification_custom_view_max_image_width_low_ram"/>
+  <java-symbol type="dimen" name="notification_person_icon_max_size_low_ram" />
 
   <!-- Accessibility fingerprint gestures -->
   <java-symbol type="string" name="capability_title_canCaptureFingerprintGestures" />
@@ -4774,4 +4777,6 @@
   <java-symbol type="layout" name="app_language_picker_current_locale_item" />
   <java-symbol type="id" name="system_locale_subtitle" />
   <java-symbol type="id" name="language_picker_item" />
+
+  <java-symbol type="dimen" name="status_bar_height_default" />
 </resources>
diff --git a/core/tests/coretests/src/android/app/NotificationTest.java b/core/tests/coretests/src/android/app/NotificationTest.java
index e6d2364..a5da442 100644
--- a/core/tests/coretests/src/android/app/NotificationTest.java
+++ b/core/tests/coretests/src/android/app/NotificationTest.java
@@ -521,6 +521,70 @@
     }
 
     @Test
+    public void testBuild_ensureMessagingUserIsNotTooBig_resizesIcon() {
+        Icon hugeIcon = Icon.createWithBitmap(
+                Bitmap.createBitmap(3000, 3000, Bitmap.Config.ARGB_8888));
+        Icon hugeMessageAvatar = Icon.createWithBitmap(
+                Bitmap.createBitmap(3000, 3000, Bitmap.Config.ARGB_8888));
+        Icon hugeHistoricMessageAvatar = Icon.createWithBitmap(
+                Bitmap.createBitmap(3000, 3000, Bitmap.Config.ARGB_8888));
+
+        Notification.MessagingStyle style = new Notification.MessagingStyle(
+                new Person.Builder().setIcon(hugeIcon).setName("A User").build());
+        style.addMessage(new Notification.MessagingStyle.Message("A message", 123456,
+                new Person.Builder().setIcon(hugeMessageAvatar).setName("A Sender").build()));
+        style.addHistoricMessage(new Notification.MessagingStyle.Message("A message", 123456,
+                new Person.Builder().setIcon(hugeHistoricMessageAvatar).setName(
+                        "A Historic Sender").build()));
+        Notification notification = new Notification.Builder(mContext, "Channel").setStyle(
+                style).build();
+
+        Bitmap personIcon = style.getUser().getIcon().getBitmap();
+        assertThat(personIcon.getWidth()).isEqualTo(
+                mContext.getResources().getDimensionPixelSize(
+                        R.dimen.notification_person_icon_max_size));
+        assertThat(personIcon.getHeight()).isEqualTo(
+                mContext.getResources().getDimensionPixelSize(
+                        R.dimen.notification_person_icon_max_size));
+
+        Bitmap avatarIcon = style.getMessages().get(0).getSenderPerson().getIcon().getBitmap();
+        assertThat(avatarIcon.getWidth()).isEqualTo(
+                mContext.getResources().getDimensionPixelSize(
+                        R.dimen.notification_person_icon_max_size));
+        assertThat(avatarIcon.getHeight()).isEqualTo(
+                mContext.getResources().getDimensionPixelSize(
+                        R.dimen.notification_person_icon_max_size));
+
+        Bitmap historicAvatarIcon = style.getHistoricMessages().get(
+                0).getSenderPerson().getIcon().getBitmap();
+        assertThat(historicAvatarIcon.getWidth()).isEqualTo(
+                mContext.getResources().getDimensionPixelSize(
+                        R.dimen.notification_person_icon_max_size));
+        assertThat(historicAvatarIcon.getHeight()).isEqualTo(
+                mContext.getResources().getDimensionPixelSize(
+                        R.dimen.notification_person_icon_max_size));
+    }
+
+    @Test
+    public void testBuild_ensureMessagingShortcutIconIsNotTooBig_resizesIcon() {
+        Icon hugeIcon = Icon.createWithBitmap(
+                Bitmap.createBitmap(3000, 3000, Bitmap.Config.ARGB_8888));
+        Notification.MessagingStyle style = new Notification.MessagingStyle(
+                new Person.Builder().setName("A User").build()).setShortcutIcon(hugeIcon);
+
+        Notification notification = new Notification.Builder(mContext, "Channel").setStyle(
+                style).build();
+        Bitmap shortcutIcon = style.getShortcutIcon().getBitmap();
+
+        assertThat(shortcutIcon.getWidth()).isEqualTo(
+                mContext.getResources().getDimensionPixelSize(
+                        R.dimen.notification_small_icon_size));
+        assertThat(shortcutIcon.getHeight()).isEqualTo(
+                mContext.getResources().getDimensionPixelSize(
+                        R.dimen.notification_small_icon_size));
+    }
+
+    @Test
     public void testColors_ensureColors_dayMode_producesValidPalette() {
         Notification.Colors c = new Notification.Colors();
         boolean colorized = false;
diff --git a/core/tests/coretests/src/android/os/IpcDataCacheTest.java b/core/tests/coretests/src/android/os/IpcDataCacheTest.java
index fa7d721..34712ce 100644
--- a/core/tests/coretests/src/android/os/IpcDataCacheTest.java
+++ b/core/tests/coretests/src/android/os/IpcDataCacheTest.java
@@ -53,6 +53,14 @@
             return value(x);
         }
 
+        // A single query but this can throw an exception.
+        boolean query(int x, boolean y) throws RemoteException {
+            if (y) {
+                throw new RemoteException();
+            }
+            return query(x);
+        }
+
         // Return the expected value of an input, without incrementing the query count.
         boolean value(int x) {
             return x % 3 == 0;
@@ -138,6 +146,47 @@
         tester.verify(9);
     }
 
+    // This test is disabled pending an sepolicy change that allows any app to set the
+    // test property.
+    @Test
+    public void testRemoteCall() {
+
+        // A stand-in for the binder.  The test verifies that calls are passed through to
+        // this class properly.
+        ServerProxy tester = new ServerProxy();
+
+        // Create a cache that uses simple arithmetic to computer its values.
+        IpcDataCache.Config config = new IpcDataCache.Config(4, MODULE, API, "testCache2");
+        IpcDataCache<Integer, Boolean> testCache =
+                new IpcDataCache<>(config, (x) -> tester.query(x, x % 10 == 9));
+
+        IpcDataCache.setTestMode(true);
+        testCache.testPropertyName();
+
+        tester.verify(0);
+        assertEquals(tester.value(3), testCache.query(3));
+        tester.verify(1);
+        assertEquals(tester.value(3), testCache.query(3));
+        tester.verify(2);
+        testCache.invalidateCache();
+        assertEquals(tester.value(3), testCache.query(3));
+        tester.verify(3);
+        assertEquals(tester.value(5), testCache.query(5));
+        tester.verify(4);
+        assertEquals(tester.value(5), testCache.query(5));
+        tester.verify(4);
+        assertEquals(tester.value(3), testCache.query(3));
+        tester.verify(4);
+
+        try {
+            testCache.query(9);
+            assertEquals(false, true);          // The code should not reach this point.
+        } catch (RuntimeException e) {
+            assertEquals(e.getCause() instanceof RemoteException, true);
+        }
+        tester.verify(4);
+    }
+
     @Test
     public void testDisableCache() {
 
@@ -225,6 +274,17 @@
             testPropertyName();
         }
 
+        TestCache(IpcDataCache.Config c) {
+            this(c, new TestQuery());
+        }
+
+        TestCache(IpcDataCache.Config c, TestQuery query) {
+            super(c, query);
+            mQuery = query;
+            setTestMode(true);
+            testPropertyName();
+        }
+
         int getRecomputeCount() {
             return mQuery.getRecomputeCount();
         }
@@ -309,4 +369,48 @@
         assertEquals("foo5", cache.query(5));
         assertEquals(3, cache.getRecomputeCount());
     }
+
+    @Test
+    public void testConfig() {
+        IpcDataCache.Config a = new IpcDataCache.Config(8, MODULE, "apiA");
+        TestCache ac = new TestCache(a);
+        assertEquals(8, a.maxEntries());
+        assertEquals(MODULE, a.module());
+        assertEquals("apiA", a.api());
+        assertEquals("apiA", a.name());
+        IpcDataCache.Config b = new IpcDataCache.Config(a, "apiB");
+        TestCache bc = new TestCache(b);
+        assertEquals(8, b.maxEntries());
+        assertEquals(MODULE, b.module());
+        assertEquals("apiB", b.api());
+        assertEquals("apiB", b.name());
+        IpcDataCache.Config c = new IpcDataCache.Config(a, "apiC", "nameC");
+        TestCache cc = new TestCache(c);
+        assertEquals(8, c.maxEntries());
+        assertEquals(MODULE, c.module());
+        assertEquals("apiC", c.api());
+        assertEquals("nameC", c.name());
+        IpcDataCache.Config d = a.child("nameD");
+        TestCache dc = new TestCache(d);
+        assertEquals(8, d.maxEntries());
+        assertEquals(MODULE, d.module());
+        assertEquals("apiA", d.api());
+        assertEquals("nameD", d.name());
+
+        a.disableForCurrentProcess();
+        assertEquals(ac.isDisabled(), true);
+        assertEquals(bc.isDisabled(), false);
+        assertEquals(cc.isDisabled(), false);
+        assertEquals(dc.isDisabled(), false);
+
+        a.disableAllForCurrentProcess();
+        assertEquals(ac.isDisabled(), true);
+        assertEquals(bc.isDisabled(), false);
+        assertEquals(cc.isDisabled(), false);
+        assertEquals(dc.isDisabled(), true);
+
+        IpcDataCache.Config e = a.child("nameE");
+        TestCache ec = new TestCache(e);
+        assertEquals(ec.isDisabled(), true);
+    }
 }
diff --git a/core/tests/coretests/src/android/service/quicksettings/TileServiceTest.java b/core/tests/coretests/src/android/service/quicksettings/TileServiceTest.java
new file mode 100644
index 0000000..d28eeff
--- /dev/null
+++ b/core/tests/coretests/src/android/service/quicksettings/TileServiceTest.java
@@ -0,0 +1,107 @@
+/*
+ * Copyright (C) 2022 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.service.quicksettings;
+
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.Intent;
+import android.os.Binder;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.testing.TestableLooper;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+@TestableLooper.RunWithLooper(setAsMainLooper = true)
+public class TileServiceTest {
+
+    @Mock
+    private IQSService.Stub mIQSService;
+
+    private IBinder mTileToken;
+    private TileService mTileService;
+    private Tile mTile;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+
+        mTileToken = new Binder();
+        when(mIQSService.asBinder()).thenCallRealMethod();
+        when(mIQSService.queryLocalInterface(anyString())).thenReturn(mIQSService);
+
+        mTile = new Tile();
+
+        mTileService = new TileService();
+    }
+
+    @Test
+    public void testErrorRetrievingTile_nullBinding() throws RemoteException {
+        Intent intent = new Intent();
+        intent.putExtra(TileService.EXTRA_SERVICE, mIQSService);
+        intent.putExtra(TileService.EXTRA_TOKEN, mTileToken);
+        when(mIQSService.getTile(mTileToken)).thenThrow(new RemoteException());
+
+        IBinder result = mTileService.onBind(intent);
+        assertNull(result);
+    }
+
+    @Test
+    public void testNullTile_doesntSendStartSuccessful() throws RemoteException {
+        Intent intent = new Intent();
+        intent.putExtra(TileService.EXTRA_SERVICE, mIQSService);
+        intent.putExtra(TileService.EXTRA_TOKEN, mTileToken);
+        when(mIQSService.getTile(mTileToken)).thenReturn(null);
+
+        IBinder result = mTileService.onBind(intent);
+
+        assertNotNull(result);
+        verify(mIQSService, never()).onStartSuccessful(any());
+    }
+
+    @Test
+    public void testBindSuccessful() throws RemoteException {
+        Intent intent = new Intent();
+        intent.putExtra(TileService.EXTRA_SERVICE, mIQSService);
+        intent.putExtra(TileService.EXTRA_TOKEN, mTileToken);
+        when(mIQSService.getTile(mTileToken)).thenReturn(mTile);
+
+        IBinder result = mTileService.onBind(intent);
+
+        assertNotNull(result);
+        verify(mIQSService).onStartSuccessful(mTileToken);
+
+        mTile.updateTile();
+        verify(mIQSService).updateQsTile(mTile, mTileToken);
+    }
+
+}
diff --git a/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json
index c21aa2d..0318760 100644
--- a/data/etc/services.core.protolog.json
+++ b/data/etc/services.core.protolog.json
@@ -3427,6 +3427,12 @@
       "group": "WM_DEBUG_FOCUS_LIGHT",
       "at": "com\/android\/server\/wm\/WindowState.java"
     },
+    "1288920916": {
+      "message": "Error sending initial insets change to WindowContainer overlay",
+      "level": "ERROR",
+      "group": "WM_DEBUG_ANIM",
+      "at": "com\/android\/server\/wm\/WindowContainer.java"
+    },
     "1305412562": {
       "message": "Report configuration: %s %s",
       "level": "VERBOSE",
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
index 01f5feb..3ec8843 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
@@ -16,6 +16,8 @@
 
 package androidx.window.extensions.embedding;
 
+import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
+
 import static androidx.window.extensions.embedding.SplitContainer.getFinishPrimaryWithSecondaryBehavior;
 import static androidx.window.extensions.embedding.SplitContainer.getFinishSecondaryWithPrimaryBehavior;
 import static androidx.window.extensions.embedding.SplitContainer.isStickyPlaceholderRule;
@@ -93,7 +95,7 @@
         mSplitRules.clear();
         mSplitRules.addAll(rules);
         for (int i = mTaskContainers.size() - 1; i >= 0; i--) {
-            updateAnimationOverride(mTaskContainers.keyAt(i));
+            updateAnimationOverride(mTaskContainers.valueAt(i));
         }
     }
 
@@ -147,15 +149,31 @@
             return;
         }
 
+        final boolean wasInPip = isInPictureInPicture(container);
         container.setInfo(taskFragmentInfo);
+        final boolean isInPip = isInPictureInPicture(container);
         // Check if there are no running activities - consider the container empty if there are no
         // non-finishing activities left.
         if (!taskFragmentInfo.hasRunningActivity()) {
+            // TODO(b/225371112): Don't finish dependent if the last activity is moved to the PIP
+            // Task.
             // Do not finish the dependents if this TaskFragment was cleared due to launching
             // activity in the Task.
             final boolean shouldFinishDependent =
                     !taskFragmentInfo.isTaskClearedForReuse();
             mPresenter.cleanupContainer(container, shouldFinishDependent);
+        } else if (wasInPip && isInPip) {
+            // No update until exit PIP.
+            return;
+        } else if (isInPip) {
+            // Enter PIP.
+            // All overrides will be cleanup.
+            container.setLastRequestedBounds(null /* bounds */);
+            cleanupForEnterPip(container);
+        } else if (wasInPip) {
+            // Exit PIP.
+            // Updates the presentation of the container. Expand or launch placeholder if needed.
+            mPresenter.updateContainer(container);
         }
         updateCallbackIfNecessary();
     }
@@ -174,10 +192,13 @@
     @Override
     public void onTaskFragmentParentInfoChanged(@NonNull IBinder fragmentToken,
             @NonNull Configuration parentConfig) {
-        TaskFragmentContainer container = getContainer(fragmentToken);
+        final TaskFragmentContainer container = getContainer(fragmentToken);
         if (container != null) {
-            onTaskBoundsMayChange(container.getTaskId(),
-                    parentConfig.windowConfiguration.getBounds());
+            onTaskConfigurationChanged(container.getTaskId(), parentConfig);
+            if (isInPictureInPicture(parentConfig)) {
+                // No need to update presentation in PIP until the Task exit PIP.
+                return;
+            }
             mPresenter.updateContainer(container);
             updateCallbackIfNecessary();
         }
@@ -199,44 +220,70 @@
         }
     }
 
-    private void onTaskBoundsMayChange(int taskId, @NonNull Rect taskBounds) {
+    private void onTaskConfigurationChanged(int taskId, @NonNull Configuration config) {
         final TaskContainer taskContainer = mTaskContainers.get(taskId);
-        if (taskContainer != null && !taskBounds.isEmpty()
-                && !taskContainer.mTaskBounds.equals(taskBounds)) {
-            taskContainer.mTaskBounds.set(taskBounds);
-            updateAnimationOverride(taskId);
+        if (taskContainer == null) {
+            return;
         }
+        final boolean wasInPip = isInPictureInPicture(taskContainer.mConfiguration);
+        final boolean isInPIp = isInPictureInPicture(config);
+        taskContainer.mConfiguration = config;
+
+        // We need to check the animation override when enter/exit PIP or has bounds changed.
+        boolean shouldUpdateAnimationOverride = wasInPip != isInPIp;
+        if (onTaskBoundsMayChange(taskContainer, config.windowConfiguration.getBounds())
+                && !isInPIp) {
+            // We don't care the bounds change when it has already entered PIP.
+            shouldUpdateAnimationOverride = true;
+        }
+        if (shouldUpdateAnimationOverride) {
+            updateAnimationOverride(taskContainer);
+        }
+    }
+
+    /** Returns {@code true} if the bounds is changed. */
+    private boolean onTaskBoundsMayChange(@NonNull TaskContainer taskContainer,
+            @NonNull Rect taskBounds) {
+        if (!taskBounds.isEmpty() && !taskContainer.mTaskBounds.equals(taskBounds)) {
+            taskContainer.mTaskBounds.set(taskBounds);
+            return true;
+        }
+        return false;
     }
 
     /**
      * Updates if we should override transition animation. We only want to override if the Task
      * bounds is large enough for at least one split rule.
      */
-    private void updateAnimationOverride(int taskId) {
-        final TaskContainer taskContainer = mTaskContainers.get(taskId);
-        if (taskContainer == null || !taskContainer.isTaskBoundsInitialized()) {
+    private void updateAnimationOverride(@NonNull TaskContainer taskContainer) {
+        if (!taskContainer.isTaskBoundsInitialized()) {
             // We don't know about the Task bounds yet.
             return;
         }
 
+        // We only want to override if it supports split.
+        if (supportSplit(taskContainer)) {
+            mPresenter.startOverrideSplitAnimation(taskContainer.mTaskId);
+        } else {
+            mPresenter.stopOverrideSplitAnimation(taskContainer.mTaskId);
+        }
+    }
+
+    private boolean supportSplit(@NonNull TaskContainer taskContainer) {
+        // No split inside PIP.
+        if (isInPictureInPicture(taskContainer.mConfiguration)) {
+            return false;
+        }
         // Check if the parent container bounds can support any split rule.
-        boolean supportSplit = false;
         for (EmbeddingRule rule : mSplitRules) {
             if (!(rule instanceof SplitRule)) {
                 continue;
             }
             if (mPresenter.shouldShowSideBySide(taskContainer.mTaskBounds, (SplitRule) rule)) {
-                supportSplit = true;
-                break;
+                return true;
             }
         }
-
-        // We only want to override if it supports split.
-        if (supportSplit) {
-            mPresenter.startOverrideSplitAnimation(taskId);
-        } else {
-            mPresenter.stopOverrideSplitAnimation(taskId);
-        }
+        return false;
     }
 
     void onActivityCreated(@NonNull Activity launchedActivity) {
@@ -250,6 +297,10 @@
      */
     // TODO(b/190433398): Break down into smaller functions.
     void handleActivityCreated(@NonNull Activity launchedActivity) {
+        if (isInPictureInPicture(launchedActivity)) {
+            // We don't embed activity when it is in PIP.
+            return;
+        }
         final List<EmbeddingRule> splitRules = getSplitRules();
         final TaskFragmentContainer currentContainer = getContainerWithActivity(
                 launchedActivity.getActivityToken());
@@ -324,6 +375,10 @@
     }
 
     private void onActivityConfigurationChanged(@NonNull Activity activity) {
+        if (isInPictureInPicture(activity)) {
+            // We don't embed activity when it is in PIP.
+            return;
+        }
         final TaskFragmentContainer currentContainer = getContainerWithActivity(
                 activity.getActivityToken());
 
@@ -365,9 +420,11 @@
         }
         final TaskContainer taskContainer = mTaskContainers.get(taskId);
         taskContainer.mContainers.add(container);
-        if (activity != null && !taskContainer.isTaskBoundsInitialized()) {
+        if (activity != null && !taskContainer.isTaskBoundsInitialized()
+                && onTaskBoundsMayChange(taskContainer,
+                SplitPresenter.getTaskBoundsFromActivity(activity))) {
             // Initial check before any TaskFragment has appeared.
-            onTaskBoundsMayChange(taskId, SplitPresenter.getTaskBoundsFromActivity(activity));
+            updateAnimationOverride(taskContainer);
         }
         return container;
     }
@@ -389,6 +446,40 @@
         mTaskContainers.get(primaryContainer.getTaskId()).mSplitContainers.add(splitContainer);
     }
 
+    /** Cleanups all the dependencies when the TaskFragment is entering PIP. */
+    private void cleanupForEnterPip(@NonNull TaskFragmentContainer container) {
+        final int taskId = container.getTaskId();
+        final TaskContainer taskContainer = mTaskContainers.get(taskId);
+        if (taskContainer == null) {
+            return;
+        }
+        final List<SplitContainer> splitsToRemove = new ArrayList<>();
+        final Set<TaskFragmentContainer> containersToUpdate = new ArraySet<>();
+        for (SplitContainer splitContainer : taskContainer.mSplitContainers) {
+            if (splitContainer.getPrimaryContainer() != container
+                    && splitContainer.getSecondaryContainer() != container) {
+                continue;
+            }
+            splitsToRemove.add(splitContainer);
+            final TaskFragmentContainer splitTf = splitContainer.getPrimaryContainer() == container
+                    ? splitContainer.getSecondaryContainer()
+                    : splitContainer.getPrimaryContainer();
+            containersToUpdate.add(splitTf);
+            // We don't want the PIP TaskFragment to be removed as a result of any of its dependents
+            // being removed.
+            splitTf.removeContainerToFinishOnExit(container);
+            if (container.getTopNonFinishingActivity() != null) {
+                splitTf.removeActivityToFinishOnExit(container.getTopNonFinishingActivity());
+            }
+        }
+        container.resetDependencies();
+        taskContainer.mSplitContainers.removeAll(splitsToRemove);
+        // If there is any TaskFragment split with the PIP TaskFragment, update their presentations
+        // since the split is dismissed.
+        // We don't want to close any of them even if they are dependencies of the PIP TaskFragment.
+        mPresenter.updateContainers(containersToUpdate);
+    }
+
     /**
      * Removes the container from bookkeeping records.
      */
@@ -916,6 +1007,10 @@
                 return super.onStartActivity(who, intent, options);
             }
             final Activity launchingActivity = (Activity) who;
+            if (isInPictureInPicture(launchingActivity)) {
+                // We don't embed activity when it is in PIP.
+                return super.onStartActivity(who, intent, options);
+            }
 
             if (shouldExpand(null, intent, getSplitRules())) {
                 setLaunchingInExpandedContainer(launchingActivity, options);
@@ -1079,6 +1174,19 @@
         return !pairRule.shouldClearTop();
     }
 
+    private static boolean isInPictureInPicture(@NonNull Activity activity) {
+        return isInPictureInPicture(activity.getResources().getConfiguration());
+    }
+
+    private static boolean isInPictureInPicture(@NonNull TaskFragmentContainer tf) {
+        return isInPictureInPicture(tf.getInfo().getConfiguration());
+    }
+
+    private static boolean isInPictureInPicture(@Nullable Configuration configuration) {
+        return configuration != null
+                && configuration.windowConfiguration.getWindowingMode() == WINDOWING_MODE_PINNED;
+    }
+
     /** Represents TaskFragments and split pairs below a Task. */
     @VisibleForTesting
     static class TaskContainer {
@@ -1095,6 +1203,9 @@
         final Set<IBinder> mFinishedContainer = new ArraySet<>();
         /** Available window bounds of this Task. */
         final Rect mTaskBounds = new Rect();
+        /** Configuration of the Task. */
+        @Nullable
+        Configuration mConfiguration;
 
         TaskContainer(int taskId) {
             mTaskId = taskId;
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
index e4d9ede..b55c16e 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
@@ -36,6 +36,7 @@
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 
+import java.util.Collection;
 import java.util.concurrent.Executor;
 
 /**
@@ -65,13 +66,27 @@
     /**
      * Updates the presentation of the provided container.
      */
-    void updateContainer(TaskFragmentContainer container) {
+    void updateContainer(@NonNull TaskFragmentContainer container) {
         final WindowContainerTransaction wct = new WindowContainerTransaction();
         mController.updateContainer(wct, container);
         applyTransaction(wct);
     }
 
     /**
+     * Updates the presentation of the provided containers.
+     */
+    void updateContainers(@NonNull Collection<TaskFragmentContainer> containers) {
+        if (containers.isEmpty()) {
+            return;
+        }
+        final WindowContainerTransaction wct = new WindowContainerTransaction();
+        for (TaskFragmentContainer container : containers) {
+            mController.updateContainer(wct, container);
+        }
+        applyTransaction(wct);
+    }
+
+    /**
      * Deletes the specified container and all other associated and dependent containers in the same
      * transaction.
      */
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java
index 9a12669..20c929b 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java
@@ -192,6 +192,13 @@
     }
 
     /**
+     * Removes a container that should be finished when this container is finished.
+     */
+    void removeContainerToFinishOnExit(@NonNull TaskFragmentContainer containerToRemove) {
+        mContainersToFinishOnExit.remove(containerToRemove);
+    }
+
+    /**
      * Adds an activity that should be finished when this container is finished.
      */
     void addActivityToFinishOnExit(@NonNull Activity activityToFinish) {
@@ -199,6 +206,19 @@
     }
 
     /**
+     * Removes an activity that should be finished when this container is finished.
+     */
+    void removeActivityToFinishOnExit(@NonNull Activity activityToRemove) {
+        mActivitiesToFinishOnExit.remove(activityToRemove);
+    }
+
+    /** Removes all dependencies that should be finished when this container is finished. */
+    void resetDependencies() {
+        mContainersToFinishOnExit.clear();
+        mActivitiesToFinishOnExit.clear();
+    }
+
+    /**
      * Removes all activities that belong to this process and finishes other containers/activities
      * configured to finish together.
      */
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
index 1a1cd5b..b6fb828 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
@@ -1225,12 +1225,6 @@
                 mOverflowListener.applyUpdate(update);
             }
 
-            // Collapsing? Do this first before remaining steps.
-            if (update.expandedChanged && !update.expanded) {
-                mStackView.setExpanded(false);
-                mSysuiProxy.requestNotificationShadeTopUi(false, TAG);
-            }
-
             // Do removals, if any.
             ArrayList<Pair<Bubble, Integer>> removedBubbles =
                     new ArrayList<>(update.removedBubbles);
@@ -1307,6 +1301,11 @@
                 mStackView.updateBubbleOrder(update.bubbles);
             }
 
+            if (update.expandedChanged && !update.expanded) {
+                mStackView.setExpanded(false);
+                mSysuiProxy.requestNotificationShadeTopUi(false, TAG);
+            }
+
             if (update.selectionChanged && mStackView != null) {
                 mStackView.setSelectedBubble(update.selectedBubble);
                 if (update.selectedBubble != null) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsState.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsState.java
index ea07499..a3048bd 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsState.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsState.java
@@ -35,7 +35,7 @@
 import java.lang.annotation.RetentionPolicy;
 
 /**
- * TV specific values of the current state of the PIP bounds.
+ * TV specific values of the current state of the PiP bounds.
  */
 public class TvPipBoundsState extends PipBoundsState {
 
@@ -86,7 +86,7 @@
         mTvPipGravity = DEFAULT_TV_GRAVITY;
     }
 
-    /** Set the tv expanded bounds of PIP */
+    /** Set the tv expanded bounds of PiP */
     public void setTvExpandedSize(@Nullable Size size) {
         mTvExpandedSize = size;
     }
@@ -97,20 +97,24 @@
         return mTvExpandedSize;
     }
 
-    /** Set the PIP aspect ratio for the expanded PIP (TV) that is desired by the app. */
+    /** Set the PiP aspect ratio for the expanded PiP (TV) that is desired by the app. */
     public void setDesiredTvExpandedAspectRatio(float aspectRatio, boolean override) {
-        if (override || mTvFixedPipOrientation == ORIENTATION_UNDETERMINED || aspectRatio == 0) {
+        if (override || mTvFixedPipOrientation == ORIENTATION_UNDETERMINED) {
             mDesiredTvExpandedAspectRatio = aspectRatio;
             resetTvPipState();
             return;
         }
         if ((aspectRatio > 1 && mTvFixedPipOrientation == ORIENTATION_HORIZONTAL)
-                || (aspectRatio <= 1 && mTvFixedPipOrientation == ORIENTATION_VERTICAL)) {
+                || (aspectRatio <= 1 && mTvFixedPipOrientation == ORIENTATION_VERTICAL)
+                || aspectRatio == 0) {
             mDesiredTvExpandedAspectRatio = aspectRatio;
         }
     }
 
-    /** Get the PIP aspect ratio for the expanded PIP (TV) that is desired by the app. */
+    /**
+     * Get the aspect ratio for the expanded PiP (TV) that is desired, or {@code 0} if it is not
+     * enabled by the app.
+     */
     public float getDesiredTvExpandedAspectRatio() {
         return mDesiredTvExpandedAspectRatio;
     }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipKeepClearAlgorithm.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipKeepClearAlgorithm.kt
index 4e8e71b..09d202a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipKeepClearAlgorithm.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipKeepClearAlgorithm.kt
@@ -133,7 +133,8 @@
         val pipAnchorBoundsWithAllDecors =
                 getNormalPipAnchorBounds(pipSizeWithAllDecors, transformedMovementBounds)
 
-        val pipAnchorBoundsWithPermanentDecors = removeTemporaryDecors(pipAnchorBoundsWithAllDecors)
+        val pipAnchorBoundsWithPermanentDecors =
+                removeTemporaryDecorsTransformed(pipAnchorBoundsWithAllDecors)
         val result = calculatePipPositionTransformed(
             pipAnchorBoundsWithPermanentDecors,
             transformedRestrictedAreas,
@@ -471,12 +472,10 @@
     }
 
     fun setPipPermanentDecorInsets(insets: Insets) {
-        if (pipPermanentDecorInsets == insets) return
         pipPermanentDecorInsets = insets
     }
 
     fun setPipTemporaryDecorInsets(insets: Insets) {
-        if (pipTemporaryDecorInsets == insets) return
         pipTemporaryDecorInsets = insets
     }
 
@@ -781,6 +780,7 @@
 
     /**
      * Removes the space that was reserved for permanent decorations around the pip
+     * @param bounds the bounds (in screen space) to remove the insets from
      */
     private fun removePermanentDecors(bounds: Rect): Rect {
         val pipDecorReverseInsets = Insets.subtract(Insets.NONE, pipPermanentDecorInsets)
@@ -790,11 +790,15 @@
 
     /**
      * Removes the space that was reserved for temporary decorations around the pip
+     * @param bounds the bounds (in base case) to remove the insets from
      */
-    private fun removeTemporaryDecors(bounds: Rect): Rect {
-        val pipDecorReverseInsets = Insets.subtract(Insets.NONE, pipTemporaryDecorInsets)
-        bounds.inset(pipDecorReverseInsets)
-        return bounds
+    private fun removeTemporaryDecorsTransformed(bounds: Rect): Rect {
+        if (pipTemporaryDecorInsets == Insets.NONE) return bounds
+
+        var reverseInsets = Insets.subtract(Insets.NONE, pipTemporaryDecorInsets)
+        var boundsInScreenSpace = fromTransformedSpace(bounds)
+        boundsInScreenSpace.inset(reverseInsets)
+        return toTransformedSpace(boundsInScreenSpace)
     }
 
     private fun Rect.offsetCopy(dx: Int, dy: Int) = Rect(this).apply { offset(dx, dy) }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
index 3ea57b0..9154226 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
@@ -417,7 +417,8 @@
                             || type == TRANSIT_TO_FRONT
                             || type == TRANSIT_TO_BACK;
                     final boolean isTranslucent = (change.getFlags() & FLAG_TRANSLUCENT) != 0;
-                    if (isOpenOrCloseTransition && !isTranslucent) {
+                    if (isOpenOrCloseTransition && !isTranslucent
+                            && wallpaperTransit == WALLPAPER_TRANSITION_NONE) {
                         // Use the overview background as the background for the animation
                         final Context uiContext = ActivityThread.currentActivityThread()
                                 .getSystemUiContext();
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/tv/TvPipKeepClearAlgorithmTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/tv/TvPipKeepClearAlgorithmTest.kt
index e6ba70e..9919214 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/tv/TvPipKeepClearAlgorithmTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/tv/TvPipKeepClearAlgorithmTest.kt
@@ -16,6 +16,7 @@
 
 package com.android.wm.shell.pip.tv
 
+import android.graphics.Insets
 import android.graphics.Rect
 import android.testing.AndroidTestingRunner
 import android.util.Size
@@ -432,6 +433,64 @@
         assertEquals(currentTime + algorithm.stashDuration, placement.unstashTime)
     }
 
+    @Test
+    fun test_PipInsets() {
+        val permInsets = Insets.of(-1, -2, -3, -4)
+        algorithm.setPipPermanentDecorInsets(permInsets)
+        testInsetsForAllPositions(permInsets)
+
+        val tempInsets = Insets.of(-4, -3, -2, -1)
+        algorithm.setPipPermanentDecorInsets(Insets.NONE)
+        algorithm.setPipTemporaryDecorInsets(tempInsets)
+        testInsetsForAllPositions(tempInsets)
+
+        algorithm.setPipPermanentDecorInsets(permInsets)
+        algorithm.setPipTemporaryDecorInsets(tempInsets)
+        testInsetsForAllPositions(Insets.add(permInsets, tempInsets))
+    }
+
+    private fun testInsetsForAllPositions(insets: Insets) {
+        gravity = Gravity.BOTTOM or Gravity.RIGHT
+        testAnchorPositionWithInsets(insets)
+
+        gravity = Gravity.BOTTOM or Gravity.LEFT
+        testAnchorPositionWithInsets(insets)
+
+        gravity = Gravity.TOP or Gravity.LEFT
+        testAnchorPositionWithInsets(insets)
+
+        gravity = Gravity.TOP or Gravity.RIGHT
+        testAnchorPositionWithInsets(insets)
+
+        pipSize = EXPANDED_WIDE_PIP_SIZE
+
+        gravity = Gravity.BOTTOM
+        testAnchorPositionWithInsets(insets)
+
+        gravity = Gravity.TOP
+        testAnchorPositionWithInsets(insets)
+
+        pipSize = Size(pipSize.height, pipSize.width)
+
+        gravity = Gravity.LEFT
+        testAnchorPositionWithInsets(insets)
+
+        gravity = Gravity.RIGHT
+        testAnchorPositionWithInsets(insets)
+    }
+
+    private fun testAnchorPositionWithInsets(insets: Insets) {
+        var pipRect = Rect(0, 0, pipSize.width, pipSize.height)
+        pipRect.inset(insets)
+        var expectedBounds = Rect()
+        Gravity.apply(gravity, pipRect.width(), pipRect.height(), movementBounds, expectedBounds)
+        val reverseInsets = Insets.subtract(Insets.NONE, insets)
+        expectedBounds.inset(reverseInsets)
+
+        var placement = getActualPlacement()
+        assertEquals(expectedBounds, placement.bounds)
+    }
+
     private fun makeSideBar(width: Int, @Gravity.GravityFlags side: Int): Rect {
         val sidebar = Rect(0, 0, width, SCREEN_SIZE.height)
         if (side == Gravity.RIGHT) {
diff --git a/packages/SystemUI/res-keyguard/values-sw600dp-land/dimens.xml b/packages/SystemUI/res-keyguard/values-sw600dp-land/dimens.xml
index ca8554c..9694999 100644
--- a/packages/SystemUI/res-keyguard/values-sw600dp-land/dimens.xml
+++ b/packages/SystemUI/res-keyguard/values-sw600dp-land/dimens.xml
@@ -22,6 +22,6 @@
     <!-- Overload default clock widget parameters -->
     <dimen name="widget_big_font_size">88dp</dimen>
 
-    <dimen name="qs_panel_padding_top">@dimen/qqs_layout_margin_top</dimen>
+    <dimen name="qs_panel_padding_top">16dp</dimen>
 
 </resources>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/fgs_manager_app_item.xml b/packages/SystemUI/res/layout/fgs_manager_app_item.xml
index d034f4e..30bce9d 100644
--- a/packages/SystemUI/res/layout/fgs_manager_app_item.xml
+++ b/packages/SystemUI/res/layout/fgs_manager_app_item.xml
@@ -26,7 +26,7 @@
       android:id="@+id/fgs_manager_app_item_icon"
       android:layout_width="28dp"
       android:layout_height="28dp"
-      android:layout_marginRight="12dp" />
+      android:layout_marginEnd="12dp" />
 
   <LinearLayout
       android:layout_width="0dp"
@@ -38,7 +38,7 @@
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
         android:gravity="start"
-        style="@style/TextAppearance.Dialog.Body" />
+        style="@style/FgsManagerAppLabel" />
     <TextView
         android:id="@+id/fgs_manager_app_item_duration"
         android:layout_width="match_parent"
@@ -52,6 +52,6 @@
       android:layout_width="wrap_content"
       android:layout_height="48dp"
       android:text="@string/fgs_manager_app_item_stop_button_label"
-      android:layout_marginLeft="12dp"
+      android:layout_marginStart="12dp"
       style="?android:attr/buttonBarNeutralButtonStyle" />
 </LinearLayout>
diff --git a/packages/SystemUI/res/layout/quick_status_bar_expanded_header.xml b/packages/SystemUI/res/layout/quick_status_bar_expanded_header.xml
index 2c29f07..2fb6d6c 100644
--- a/packages/SystemUI/res/layout/quick_status_bar_expanded_header.xml
+++ b/packages/SystemUI/res/layout/quick_status_bar_expanded_header.xml
@@ -55,7 +55,7 @@
             android:clipChildren="false"
             android:clipToPadding="false"
             android:focusable="true"
-            android:paddingBottom="24dp"
+            android:paddingBottom="@dimen/qqs_layout_padding_bottom"
             android:importantForAccessibility="yes">
         </com.android.systemui.qs.QuickQSPanel>
     </RelativeLayout>
diff --git a/packages/SystemUI/res/values-sw600dp-port/dimens.xml b/packages/SystemUI/res/values-sw600dp-port/dimens.xml
index f5c0509..589d12f 100644
--- a/packages/SystemUI/res/values-sw600dp-port/dimens.xml
+++ b/packages/SystemUI/res/values-sw600dp-port/dimens.xml
@@ -20,4 +20,6 @@
     <dimen name="keyguard_clock_top_margin">40dp</dimen>
     <dimen name="keyguard_status_view_bottom_margin">40dp</dimen>
     <dimen name="bouncer_user_switcher_y_trans">20dp</dimen>
+
+    <dimen name="qqs_layout_padding_bottom">16dp</dimen>
 </resources>
diff --git a/packages/SystemUI/res/values-sw720dp-port/dimens.xml b/packages/SystemUI/res/values-sw720dp-port/dimens.xml
index 44f8f3a..fc12d41 100644
--- a/packages/SystemUI/res/values-sw720dp-port/dimens.xml
+++ b/packages/SystemUI/res/values-sw720dp-port/dimens.xml
@@ -25,6 +25,8 @@
     <dimen name="keyguard_status_view_bottom_margin">80dp</dimen>
     <dimen name="bouncer_user_switcher_y_trans">90dp</dimen>
 
+    <dimen name="qqs_layout_padding_bottom">40dp</dimen>
+
     <dimen name="notification_panel_margin_horizontal">96dp</dimen>
     <dimen name="notification_side_paddings">40dp</dimen>
     <dimen name="notification_section_divider_height">16dp</dimen>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index ff3cb5f..d148403 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -474,6 +474,7 @@
     <dimen name="qs_brightness_margin_top">8dp</dimen>
     <dimen name="qs_brightness_margin_bottom">24dp</dimen>
     <dimen name="qqs_layout_margin_top">16dp</dimen>
+    <dimen name="qqs_layout_padding_bottom">24dp</dimen>
 
     <dimen name="qs_customize_internal_side_paddings">8dp</dimen>
     <dimen name="qs_icon_size">20dp</dimen>
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index 7cd2158..2806ce8 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -1083,6 +1083,10 @@
         <item name="android:textDirection">locale</item>
     </style>
 
+    <style name="FgsManagerAppLabel" parent="TextAppearance.Dialog.Body">
+        <item name="android:textDirection">locale</item>
+    </style>
+
     <style name="FgsManagerAppDuration">
         <item name="android:fontFamily">?android:attr/textAppearanceSmall</item>
         <item name="android:textDirection">locale</item>
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
index b3cf927..04c9a45 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
@@ -130,6 +130,24 @@
         }
     };
 
+    private final KeyguardUnlockAnimationController.KeyguardUnlockAnimationListener
+            mKeyguardUnlockAnimationListener =
+            new KeyguardUnlockAnimationController.KeyguardUnlockAnimationListener() {
+                @Override
+                public void onSmartspaceSharedElementTransitionStarted() {
+                    // The smartspace needs to be able to translate out of bounds in order to
+                    // end up where the launcher's smartspace is, while its container is being
+                    // swiped off the top of the screen.
+                    setClipChildrenForUnlock(false);
+                }
+
+                @Override
+                public void onUnlockAnimationFinished() {
+                    // For performance reasons, reset this once the unlock animation ends.
+                    setClipChildrenForUnlock(true);
+                }
+            };
+
     @Inject
     public KeyguardClockSwitchController(
             KeyguardClockSwitch keyguardClockSwitch,
@@ -162,22 +180,6 @@
         mUiExecutor = uiExecutor;
         mKeyguardUnlockAnimationController = keyguardUnlockAnimationController;
         mDumpManager = dumpManager;
-        mKeyguardUnlockAnimationController.addKeyguardUnlockAnimationListener(
-                new KeyguardUnlockAnimationController.KeyguardUnlockAnimationListener() {
-                    @Override
-                    public void onSmartspaceSharedElementTransitionStarted() {
-                        // The smartspace needs to be able to translate out of bounds in order to
-                        // end up where the launcher's smartspace is, while its container is being
-                        // swiped off the top of the screen.
-                        setClipChildrenForUnlock(false);
-                    }
-
-                    @Override
-                    public void onUnlockAnimationFinished() {
-                        // For performance reasons, reset this once the unlock animation ends.
-                        setClipChildrenForUnlock(true);
-                    }
-                });
     }
 
     /**
@@ -272,6 +274,9 @@
         );
 
         updateDoubleLineClock();
+
+        mKeyguardUnlockAnimationController.addKeyguardUnlockAnimationListener(
+                mKeyguardUnlockAnimationListener);
     }
 
     int getNotificationIconAreaHeight() {
@@ -287,6 +292,9 @@
         mView.setClockPlugin(null, mStatusBarStateController.getState());
 
         mSecureSettings.unregisterContentObserver(mDoubleLineClockObserver);
+
+        mKeyguardUnlockAnimationController.removeKeyguardUnlockAnimationListener(
+                mKeyguardUnlockAnimationListener);
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java
index 807ff21..a1428f3 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java
@@ -353,6 +353,8 @@
         mSourceBounds.setEmpty();
         updateSystemUIStateIfNeeded();
         mContext.unregisterComponentCallbacks(this);
+        // Notify source bounds empty when magnification is deleted.
+        mWindowMagnifierCallback.onSourceBoundsChanged(mDisplayId, new Rect());
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt
index 33126b3..76c1dbc 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt
@@ -145,6 +145,7 @@
         val lightRevealScrim = centralSurfaces.lightRevealScrim
         if (statusBarStateController.isDozing || biometricUnlockController.isWakeAndUnlock) {
             circleReveal?.let {
+                lightRevealScrim?.revealAmount = 0f
                 lightRevealScrim?.revealEffect = it
                 startLightRevealScrimOnKeyguardFadingAway = true
             }
@@ -168,7 +169,8 @@
                     startDelay = keyguardStateController.keyguardFadingAwayDelay
                     addUpdateListener { animator ->
                         if (lightRevealScrim.revealEffect != circleReveal) {
-                            // if something else took over the reveal, let's do nothing.
+                            // if something else took over the reveal, let's cancel ourselves
+                            cancel()
                             return@addUpdateListener
                         }
                         lightRevealScrim.revealAmount = animator.animatedValue as Float
diff --git a/packages/SystemUI/src/com/android/systemui/broadcast/ActionReceiver.kt b/packages/SystemUI/src/com/android/systemui/broadcast/ActionReceiver.kt
index 4315cb0..ca36375 100644
--- a/packages/SystemUI/src/com/android/systemui/broadcast/ActionReceiver.kt
+++ b/packages/SystemUI/src/com/android/systemui/broadcast/ActionReceiver.kt
@@ -39,6 +39,13 @@
  *
  * This class has no sync controls, so make sure to only make modifications from the background
  * thread.
+ *
+ * This class takes the following actions:
+ * * [registerAction]: action to register this receiver (with the proper filter) with [Context].
+ * * [unregisterAction]: action to unregister this receiver with [Context].
+ * * [testPendingRemovalAction]: action to check if a particular [BroadcastReceiver] registered
+ *   with [BroadcastDispatcher] has been unregistered and is pending removal. See
+ *   [PendingRemovalStore].
  */
 class ActionReceiver(
     private val action: String,
@@ -46,7 +53,8 @@
     private val registerAction: BroadcastReceiver.(IntentFilter) -> Unit,
     private val unregisterAction: BroadcastReceiver.() -> Unit,
     private val bgExecutor: Executor,
-    private val logger: BroadcastDispatcherLogger
+    private val logger: BroadcastDispatcherLogger,
+    private val testPendingRemovalAction: (BroadcastReceiver, Int) -> Boolean
 ) : BroadcastReceiver(), Dumpable {
 
     companion object {
@@ -106,7 +114,8 @@
         // Immediately return control to ActivityManager
         bgExecutor.execute {
             receiverDatas.forEach {
-                if (it.filter.matchCategories(intent.categories) == null) {
+                if (it.filter.matchCategories(intent.categories) == null &&
+                    !testPendingRemovalAction(it.receiver, userId)) {
                     it.executor.execute {
                         it.receiver.pendingResult = pendingResult
                         it.receiver.onReceive(context, intent)
diff --git a/packages/SystemUI/src/com/android/systemui/broadcast/BroadcastDispatcher.kt b/packages/SystemUI/src/com/android/systemui/broadcast/BroadcastDispatcher.kt
index 1c27e32..b7aebc1 100644
--- a/packages/SystemUI/src/com/android/systemui/broadcast/BroadcastDispatcher.kt
+++ b/packages/SystemUI/src/com/android/systemui/broadcast/BroadcastDispatcher.kt
@@ -63,13 +63,14 @@
  * Broadcast handling may be asynchronous *without* calling goAsync(), as it's running within sysui
  * and doesn't need to worry about being killed.
  */
-open class BroadcastDispatcher constructor (
+open class BroadcastDispatcher @JvmOverloads constructor (
     private val context: Context,
     private val bgLooper: Looper,
     private val bgExecutor: Executor,
     private val dumpManager: DumpManager,
     private val logger: BroadcastDispatcherLogger,
-    private val userTracker: UserTracker
+    private val userTracker: UserTracker,
+    private val removalPendingStore: PendingRemovalStore = PendingRemovalStore(logger)
 ) : Dumpable {
 
     // Only modify in BG thread
@@ -167,6 +168,7 @@
      * @param receiver The receiver to unregister. It will be unregistered for all users.
      */
     open fun unregisterReceiver(receiver: BroadcastReceiver) {
+        removalPendingStore.tagForRemoval(receiver, UserHandle.USER_ALL)
         handler.obtainMessage(MSG_REMOVE_RECEIVER, receiver).sendToTarget()
     }
 
@@ -177,13 +179,21 @@
      * @param user The user associated to the registered [receiver]. It can be [UserHandle.ALL].
      */
     open fun unregisterReceiverForUser(receiver: BroadcastReceiver, user: UserHandle) {
+        removalPendingStore.tagForRemoval(receiver, user.identifier)
         handler.obtainMessage(MSG_REMOVE_RECEIVER_FOR_USER, user.identifier, 0, receiver)
                 .sendToTarget()
     }
 
     @VisibleForTesting
     protected open fun createUBRForUser(userId: Int) =
-            UserBroadcastDispatcher(context, userId, bgLooper, bgExecutor, logger)
+            UserBroadcastDispatcher(
+                context,
+                userId,
+                bgLooper,
+                bgExecutor,
+                logger,
+                removalPendingStore
+            )
 
     override fun dump(pw: PrintWriter, args: Array<out String>) {
         pw.println("Broadcast dispatcher:")
@@ -193,6 +203,8 @@
             ipw.println("User ${receiversByUser.keyAt(index)}")
             receiversByUser.valueAt(index).dump(ipw, args)
         }
+        ipw.println("Pending removal:")
+        removalPendingStore.dump(ipw, args)
         ipw.decreaseIndent()
     }
 
@@ -223,10 +235,20 @@
                     for (it in 0 until receiversByUser.size()) {
                         receiversByUser.valueAt(it).unregisterReceiver(msg.obj as BroadcastReceiver)
                     }
+                    removalPendingStore.clearPendingRemoval(
+                        msg.obj as BroadcastReceiver,
+                        UserHandle.USER_ALL
+                    )
                 }
 
                 MSG_REMOVE_RECEIVER_FOR_USER -> {
-                    receiversByUser.get(msg.arg1)?.unregisterReceiver(msg.obj as BroadcastReceiver)
+                    val userId = if (msg.arg1 == UserHandle.USER_CURRENT) {
+                        userTracker.userId
+                    } else {
+                        msg.arg1
+                    }
+                    receiversByUser.get(userId)?.unregisterReceiver(msg.obj as BroadcastReceiver)
+                    removalPendingStore.clearPendingRemoval(msg.obj as BroadcastReceiver, userId)
                 }
                 else -> super.handleMessage(msg)
             }
diff --git a/packages/SystemUI/src/com/android/systemui/broadcast/PendingRemovalStore.kt b/packages/SystemUI/src/com/android/systemui/broadcast/PendingRemovalStore.kt
new file mode 100644
index 0000000..ebf4983
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/broadcast/PendingRemovalStore.kt
@@ -0,0 +1,58 @@
+package com.android.systemui.broadcast
+
+import android.content.BroadcastReceiver
+import android.os.UserHandle
+import android.util.SparseSetArray
+import androidx.annotation.GuardedBy
+import com.android.systemui.Dumpable
+import com.android.systemui.broadcast.logging.BroadcastDispatcherLogger
+import com.android.systemui.util.indentIfPossible
+import java.io.PrintWriter
+
+/**
+ * Store information about requests for unregistering receivers from [BroadcastDispatcher], before
+ * they have been completely removed from the system.
+ *
+ * This helps make unregistering a receiver a *sync* operation.
+ */
+class PendingRemovalStore(
+    private val logger: BroadcastDispatcherLogger
+) : Dumpable {
+    @GuardedBy("pendingRemoval")
+    private val pendingRemoval: SparseSetArray<BroadcastReceiver> = SparseSetArray()
+
+    fun tagForRemoval(broadcastReceiver: BroadcastReceiver, userId: Int) {
+        logger.logTagForRemoval(userId, broadcastReceiver)
+        synchronized(pendingRemoval) {
+            pendingRemoval.add(userId, broadcastReceiver)
+        }
+    }
+
+    fun isPendingRemoval(broadcastReceiver: BroadcastReceiver, userId: Int): Boolean {
+        return synchronized(pendingRemoval) {
+            pendingRemoval.contains(userId, broadcastReceiver) ||
+                pendingRemoval.contains(UserHandle.USER_ALL, broadcastReceiver)
+        }
+    }
+
+    fun clearPendingRemoval(broadcastReceiver: BroadcastReceiver, userId: Int) {
+        synchronized(pendingRemoval) {
+            pendingRemoval.remove(userId, broadcastReceiver)
+        }
+        logger.logClearedAfterRemoval(userId, broadcastReceiver)
+    }
+
+    override fun dump(pw: PrintWriter, args: Array<out String>) {
+        synchronized(pendingRemoval) {
+            pw.indentIfPossible {
+                val size = pendingRemoval.size()
+                for (i in 0 until size) {
+                    val user = pendingRemoval.keyAt(i)
+                    print(user)
+                    print("->")
+                    println(pendingRemoval.get(user))
+                }
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/broadcast/UserBroadcastDispatcher.kt b/packages/SystemUI/src/com/android/systemui/broadcast/UserBroadcastDispatcher.kt
index 24ce238..6b15188 100644
--- a/packages/SystemUI/src/com/android/systemui/broadcast/UserBroadcastDispatcher.kt
+++ b/packages/SystemUI/src/com/android/systemui/broadcast/UserBroadcastDispatcher.kt
@@ -20,12 +20,12 @@
 import android.content.Context
 import android.os.Handler
 import android.os.Looper
-import android.os.Message
 import android.os.UserHandle
 import android.util.ArrayMap
 import android.util.ArraySet
 import android.util.Log
 import androidx.annotation.VisibleForTesting
+import androidx.annotation.WorkerThread
 import com.android.internal.util.Preconditions
 import com.android.systemui.Dumpable
 import com.android.systemui.broadcast.logging.BroadcastDispatcherLogger
@@ -34,8 +34,6 @@
 import java.util.concurrent.Executor
 import java.util.concurrent.atomic.AtomicInteger
 
-private const val MSG_REGISTER_RECEIVER = 0
-private const val MSG_UNREGISTER_RECEIVER = 1
 private const val TAG = "UserBroadcastDispatcher"
 private const val DEBUG = false
 
@@ -50,7 +48,8 @@
     private val userId: Int,
     private val bgLooper: Looper,
     private val bgExecutor: Executor,
-    private val logger: BroadcastDispatcherLogger
+    private val logger: BroadcastDispatcherLogger,
+    private val removalPendingStore: PendingRemovalStore
 ) : Dumpable {
 
     companion object {
@@ -60,16 +59,6 @@
         val index = AtomicInteger(0)
     }
 
-    private val bgHandler = object : Handler(bgLooper) {
-        override fun handleMessage(msg: Message) {
-            when (msg.what) {
-                MSG_REGISTER_RECEIVER -> handleRegisterReceiver(msg.obj as ReceiverData, msg.arg1)
-                MSG_UNREGISTER_RECEIVER -> handleUnregisterReceiver(msg.obj as BroadcastReceiver)
-                else -> Unit
-            }
-        }
-    }
-
     // Used for key in actionsToActionsReceivers
     internal data class ReceiverProperties(
         val action: String,
@@ -77,6 +66,8 @@
         val permission: String?
     )
 
+    private val bgHandler = Handler(bgLooper)
+
     // Only modify in BG thread
     @VisibleForTesting
     internal val actionsToActionsReceivers = ArrayMap<ReceiverProperties, ActionReceiver>()
@@ -92,19 +83,21 @@
     /**
      * Register a [ReceiverData] for this user.
      */
+    @WorkerThread
     fun registerReceiver(receiverData: ReceiverData, flags: Int) {
-        bgHandler.obtainMessage(MSG_REGISTER_RECEIVER, flags, 0, receiverData).sendToTarget()
+        handleRegisterReceiver(receiverData, flags)
     }
 
     /**
      * Unregister a given [BroadcastReceiver] for this user.
      */
+    @WorkerThread
     fun unregisterReceiver(receiver: BroadcastReceiver) {
-        bgHandler.obtainMessage(MSG_UNREGISTER_RECEIVER, receiver).sendToTarget()
+        handleUnregisterReceiver(receiver)
     }
 
     private fun handleRegisterReceiver(receiverData: ReceiverData, flags: Int) {
-        Preconditions.checkState(bgHandler.looper.isCurrentThread,
+        Preconditions.checkState(bgLooper.isCurrentThread,
                 "This method should only be called from BG thread")
         if (DEBUG) Log.w(TAG, "Register receiver: ${receiverData.receiver}")
         receiverToActions
@@ -151,12 +144,13 @@
                     }
                 },
                 bgExecutor,
-                logger
+                logger,
+                removalPendingStore::isPendingRemoval
         )
     }
 
     private fun handleUnregisterReceiver(receiver: BroadcastReceiver) {
-        Preconditions.checkState(bgHandler.looper.isCurrentThread,
+        Preconditions.checkState(bgLooper.isCurrentThread,
                 "This method should only be called from BG thread")
         if (DEBUG) Log.w(TAG, "Unregister receiver: $receiver")
         receiverToActions.getOrDefault(receiver, mutableSetOf()).forEach {
diff --git a/packages/SystemUI/src/com/android/systemui/broadcast/logging/BroadcastDispatcherLogger.kt b/packages/SystemUI/src/com/android/systemui/broadcast/logging/BroadcastDispatcherLogger.kt
index 8da6519..5b3a982 100644
--- a/packages/SystemUI/src/com/android/systemui/broadcast/logging/BroadcastDispatcherLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/broadcast/logging/BroadcastDispatcherLogger.kt
@@ -87,6 +87,26 @@
         })
     }
 
+    fun logTagForRemoval(user: Int, receiver: BroadcastReceiver) {
+        val receiverString = receiver.toString()
+        log(DEBUG, {
+            int1 = user
+            str1 = receiverString
+        }, {
+            "Receiver $str1 tagged for removal from user $int1"
+        })
+    }
+
+    fun logClearedAfterRemoval(user: Int, receiver: BroadcastReceiver) {
+        val receiverString = receiver.toString()
+        log(DEBUG, {
+            int1 = user
+            str1 = receiverString
+        }, {
+            "Receiver $str1 has been completely removed for user $int1"
+        })
+    }
+
     fun logReceiverUnregistered(user: Int, receiver: BroadcastReceiver) {
         val receiverString = receiver.toString()
         log(INFO, {
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandler.java b/packages/SystemUI/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandler.java
index aa28c54..517fce1 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandler.java
@@ -144,9 +144,8 @@
 
     private void setPanelExpansion(float expansion) {
         mCurrentExpansion = expansion;
-
-        mCentralSurfaces.setBouncerShowing(expansion != KeyguardBouncer.EXPANSION_HIDDEN);
-
+        mCentralSurfaces.setBouncerShowingOverDream(
+                mCurrentExpansion != KeyguardBouncer.EXPANSION_HIDDEN);
         mStatusBarKeyguardViewManager.onPanelExpansionChanged(mCurrentExpansion, false, true);
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.java b/packages/SystemUI/src/com/android/systemui/flags/Flags.java
index 0819f30..44580aa 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.java
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.java
@@ -149,7 +149,7 @@
     /***************************************/
     // 900 - media
     public static final BooleanFlag MEDIA_TAP_TO_TRANSFER = new BooleanFlag(900, true);
-    public static final BooleanFlag MEDIA_SESSION_ACTIONS = new BooleanFlag(901, true);
+    public static final BooleanFlag MEDIA_SESSION_ACTIONS = new BooleanFlag(901, false);
     public static final BooleanFlag MEDIA_NEARBY_DEVICES = new BooleanFlag(903, true);
     public static final BooleanFlag MEDIA_MUTE_AWAIT = new BooleanFlag(904, true);
 
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt
index 94b33e1..a8c2862 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt
@@ -389,6 +389,10 @@
         listeners.add(listener)
     }
 
+    fun removeKeyguardUnlockAnimationListener(listener: KeyguardUnlockAnimationListener) {
+        listeners.remove(listener)
+    }
+
     /**
      * Called from [KeyguardViewMediator] to tell us that the RemoteAnimation on the surface behind
      * the keyguard has started successfully. We can use these parameters to directly manipulate the
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialog.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialog.java
index 3cffd02..fc10397 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialog.java
@@ -88,8 +88,11 @@
 
     @Override
     int getStopButtonVisibility() {
-        boolean isActiveRemoteDevice = mMediaOutputController.isActiveRemoteDevice(
-                mMediaOutputController.getCurrentConnectedMediaDevice());
+        boolean isActiveRemoteDevice = false;
+        if (mMediaOutputController.getCurrentConnectedMediaDevice() != null) {
+            isActiveRemoteDevice = mMediaOutputController.isActiveRemoteDevice(
+                    mMediaOutputController.getCurrentConnectedMediaDevice());
+        }
         boolean isBroadCastSupported = isBroadcastSupported();
 
         return (isActiveRemoteDevice || isBroadCastSupported) ? View.VISIBLE : View.GONE;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/FgsManagerController.kt b/packages/SystemUI/src/com/android/systemui/qs/FgsManagerController.kt
index e639128..cc37ef4 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/FgsManagerController.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/FgsManagerController.kt
@@ -228,6 +228,10 @@
         synchronized(lock) {
             if (dialog == null) {
 
+                runningServiceTokens.keys.forEach {
+                    it.updateUiControl()
+                }
+
                 val dialog = SystemUIDialog(context)
                 dialog.setTitle(R.string.fgs_manager_dialog_title)
 
@@ -396,10 +400,20 @@
         val userId: Int,
         val packageName: String
     ) {
-        val uiControl: UIControl by lazy {
-            val uid = packageManager.getPackageUidAsUser(packageName, userId)
+        val uid by lazy { packageManager.getPackageUidAsUser(packageName, userId) }
 
-            when (activityManager.getBackgroundRestrictionExemptionReason(uid)) {
+        private var uiControlInitialized = false
+        var uiControl: UIControl = UIControl.NORMAL
+            get() {
+                if (!uiControlInitialized) {
+                    updateUiControl()
+                }
+                return field
+            }
+            private set
+
+        fun updateUiControl() {
+            uiControl = when (activityManager.getBackgroundRestrictionExemptionReason(uid)) {
                 PowerExemptionManager.REASON_SYSTEM_UID,
                 PowerExemptionManager.REASON_DEVICE_DEMO_MODE -> UIControl.HIDE_ENTRY
 
@@ -412,6 +426,7 @@
                 PowerExemptionManager.REASON_SYSTEM_MODULE -> UIControl.HIDE_BUTTON
                 else -> UIControl.NORMAL
             }
+            uiControlInitialized = true
         }
 
         override fun equals(other: Any?): Boolean {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
index 9fbdd3c..9de132f 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
@@ -364,7 +364,7 @@
         setPaddingRelative(getPaddingStart(),
                 paddingTop,
                 getPaddingEnd(),
-                getPaddingEnd());
+                getPaddingBottom());
     }
 
     void addOnConfigurationChangedListener(OnConfigurationChangedListener listener) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java
index 3c95da8..112b1e8 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java
@@ -71,7 +71,11 @@
 
     @Override
     protected void updatePadding() {
-        // QS Panel is setting a top padding by default, which we don't need.
+        int bottomPadding = getResources().getDimensionPixelSize(R.dimen.qqs_layout_padding_bottom);
+        setPaddingRelative(getPaddingStart(),
+                getPaddingTop(),
+                getPaddingEnd(),
+                bottomPadding);
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
index 61f49e0..83138f0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
@@ -896,21 +896,8 @@
         }
     }
 
-    private void showTryFingerprintMsg(int msgId, String a11yString) {
-        if (mKeyguardUpdateMonitor.isUdfpsSupported()) {
-            // if udfps available, there will always be a tappable affordance to unlock
-            // For example, the lock icon
-            if (mKeyguardBypassController.getUserHasDeviceEntryIntent()) {
-                showBiometricMessage(R.string.keyguard_unlock_press);
-            } else if (msgId == FaceManager.FACE_ERROR_LOCKOUT_PERMANENT) {
-                // since face is locked out, simply show "try fingerprint"
-                showBiometricMessage(R.string.keyguard_try_fingerprint);
-            } else {
-                showBiometricMessage(R.string.keyguard_face_failed_use_fp);
-            }
-        } else {
-            showBiometricMessage(R.string.keyguard_try_fingerprint);
-        }
+    private void showFaceFailedTryFingerprintMsg(int msgId, String a11yString) {
+        showBiometricMessage(R.string.keyguard_face_failed_use_fp);
 
         // Although we suppress face auth errors visually, we still announce them for a11y
         if (!TextUtils.isEmpty(a11yString)) {
@@ -1002,7 +989,7 @@
             } else if (mScreenLifecycle.getScreenState() == SCREEN_ON) {
                 if (biometricSourceType == BiometricSourceType.FACE
                         && shouldSuppressFaceMsgAndShowTryFingerprintMsg()) {
-                    showTryFingerprintMsg(msgId, helpString);
+                    showFaceFailedTryFingerprintMsg(msgId, helpString);
                     return;
                 }
                 showBiometricMessage(helpString);
@@ -1022,7 +1009,7 @@
                     && shouldSuppressFaceMsgAndShowTryFingerprintMsg()
                     && !mStatusBarKeyguardViewManager.isBouncerShowing()
                     && mScreenLifecycle.getScreenState() == SCREEN_ON) {
-                showTryFingerprintMsg(msgId, errString);
+                showFaceFailedTryFingerprintMsg(msgId, errString);
                 return;
             }
             if (msgId == FaceManager.FACE_ERROR_TIMEOUT) {
@@ -1031,10 +1018,10 @@
                 if (!mStatusBarKeyguardViewManager.isBouncerShowing()
                         && mKeyguardUpdateMonitor.isUdfpsEnrolled()
                         && mKeyguardUpdateMonitor.isFingerprintDetectionRunning()) {
-                    showTryFingerprintMsg(msgId, errString);
+                    showFaceFailedTryFingerprintMsg(msgId, errString);
                 } else if (mStatusBarKeyguardViewManager.isShowingAlternateAuth()) {
                     mStatusBarKeyguardViewManager.showBouncerMessage(
-                            mContext.getResources().getString(R.string.keyguard_unlock_press),
+                            mContext.getResources().getString(R.string.keyguard_try_fingerprint),
                             mInitialTextColorState
                     );
                 } else {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/LightRevealScrim.kt b/packages/SystemUI/src/com/android/systemui/statusbar/LightRevealScrim.kt
index 9a932ba..270bdc7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/LightRevealScrim.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/LightRevealScrim.kt
@@ -11,6 +11,7 @@
 import android.graphics.PorterDuffXfermode
 import android.graphics.RadialGradient
 import android.graphics.Shader
+import android.os.Trace
 import android.util.AttributeSet
 import android.util.MathUtils.lerp
 import android.view.View
@@ -222,6 +223,8 @@
 
                 revealEffect.setRevealAmountOnScrim(value, this)
                 updateScrimOpaque()
+                Trace.traceCounter(Trace.TRACE_TAG_APP, "light_reveal_amount",
+                        (field * 100).toInt())
                 invalidate()
             }
         }
@@ -355,8 +358,8 @@
     }
 
     override fun onDraw(canvas: Canvas?) {
-        if (canvas == null || revealGradientWidth <= 0 || revealGradientHeight <= 0
-            || revealAmount == 0f) {
+        if (canvas == null || revealGradientWidth <= 0 || revealGradientHeight <= 0 ||
+            revealAmount == 0f) {
             if (revealAmount < 1f) {
                 canvas?.drawColor(revealGradientEndColor)
             }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java
index eefa10b..a1dbf0c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java
@@ -468,6 +468,7 @@
      */
     protected int mState; // TODO: remove this. Just use StatusBarStateController
     protected boolean mBouncerShowing;
+    private boolean mBouncerShowingOverDream;
 
     private final PhoneStatusBarPolicy mIconPolicy;
 
@@ -3251,9 +3252,13 @@
     }
 
     public boolean onBackPressed() {
-        boolean isScrimmedBouncer = mScrimController.getState() == ScrimState.BOUNCER_SCRIMMED;
-        if (mStatusBarKeyguardViewManager.onBackPressed(isScrimmedBouncer /* hideImmediately */)) {
-            if (isScrimmedBouncer) {
+        final boolean isScrimmedBouncer =
+                mScrimController.getState() == ScrimState.BOUNCER_SCRIMMED;
+        final boolean isBouncerOverDream = isBouncerShowingOverDream();
+
+        if (mStatusBarKeyguardViewManager.onBackPressed(
+                isScrimmedBouncer || isBouncerOverDream /* hideImmediately */)) {
+            if (isScrimmedBouncer || isBouncerOverDream) {
                 mStatusBarStateController.setLeaveOpenOnKeyguardHide(false);
             } else {
                 mNotificationPanelViewController.expandWithoutQs();
@@ -3275,7 +3280,8 @@
         if (mNotificationPanelViewController.closeUserSwitcherIfOpen()) {
             return true;
         }
-        if (mState != StatusBarState.KEYGUARD && mState != StatusBarState.SHADE_LOCKED) {
+        if (mState != StatusBarState.KEYGUARD && mState != StatusBarState.SHADE_LOCKED
+                && !isBouncerOverDream) {
             if (mNotificationPanelViewController.canPanelBeCollapsed()) {
                 mShadeController.animateCollapsePanels();
             }
@@ -3467,7 +3473,19 @@
         mStatusBarHideIconsForBouncerManager.setBouncerShowingAndTriggerUpdate(bouncerShowing);
         mCommandQueue.recomputeDisableFlags(mDisplayId, true /* animate */);
         updateScrimController();
-        updateNotificationPanelTouchState();
+        if (!mBouncerShowing) {
+            updatePanelExpansionForKeyguard();
+        }
+    }
+
+    /**
+     * Sets whether the bouncer over dream is showing. Note that the bouncer over dream is handled
+     * independently of the rest of the notification panel. As a result, setting this state via
+     * {@link #setBouncerShowing(boolean)} leads to unintended side effects from states modified
+     * behind the dream.
+     */
+    public void setBouncerShowingOverDream(boolean bouncerShowingOverDream) {
+        mBouncerShowingOverDream = bouncerShowingOverDream;
     }
 
     /**
@@ -3612,13 +3630,10 @@
      * collapse the panel after we expanded it, and thus we would end up with a blank
      * Keyguard.
      */
-    public void updateNotificationPanelTouchState() {
+    void updateNotificationPanelTouchState() {
         boolean goingToSleepWithoutAnimation = isGoingToSleep()
                 && !mDozeParameters.shouldControlScreenOff();
-        boolean bouncerShowingOverDream = isBouncerShowing()
-                && mDreamOverlayStateController.isOverlayActive();
-        boolean disabled = bouncerShowingOverDream
-                || (!mDeviceInteractive && !mDozeServiceHost.isPulsing())
+        boolean disabled = (!mDeviceInteractive && !mDozeServiceHost.isPulsing())
                 || goingToSleepWithoutAnimation;
         mNotificationPanelViewController.setTouchAndAnimationDisabled(disabled);
         mNotificationIconAreaController.setAnimationsEnabled(!disabled);
@@ -4146,7 +4161,7 @@
     }
 
     public boolean isBouncerShowingOverDream() {
-        return isBouncerShowing() && mDreamOverlayStateController.isOverlayActive();
+        return mBouncerShowingOverDream;
     }
 
     /**
@@ -4405,8 +4420,7 @@
                 @Override
                 public void onDozeAmountChanged(float linear, float eased) {
                     if (mFeatureFlags.isEnabled(Flags.LOCKSCREEN_ANIMATIONS)
-                            && !(mLightRevealScrim.getRevealEffect() instanceof CircleReveal)
-                            && !mBiometricUnlockController.isWakeAndUnlock()) {
+                            && !(mLightRevealScrim.getRevealEffect() instanceof CircleReveal)) {
                         mLightRevealScrim.setRevealAmount(1f - linear);
                     }
                 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java
index 829cd3a..a19d5f1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java
@@ -31,6 +31,8 @@
 import android.util.Log;
 import android.util.MathUtils;
 
+import android.util.Log;
+
 import androidx.annotation.NonNull;
 import androidx.annotation.VisibleForTesting;
 
@@ -291,6 +293,11 @@
     }
 
     public void updateControlScreenOff() {
+        Log.i("TEST", "Display needs blanking?" + getDisplayNeedsBlanking());
+        Log.i("TEST", "Should control screen off?" + shouldControlUnlockedScreenOff());
+        Log.i("TEST", "alwaysOn?" + getAlwaysOn());
+        Log.i("TEST", "keyguard showing?" + mKeyguardShowing);
+        Log.i("TEST", "Flag enabled? " + mFeatureFlags.isEnabled(Flags.LOCKSCREEN_ANIMATIONS));
         if (!getDisplayNeedsBlanking()) {
             final boolean controlScreenOff =
                     getAlwaysOn() && (mKeyguardShowing || shouldControlUnlockedScreenOff());
@@ -299,6 +306,17 @@
     }
 
     /**
+     * Whether we're capable of controlling the screen off animation if we want to. This isn't
+     * possible if AOD isn't even enabled or if the flag is disabled, or if the display needs
+     * blanking.
+     */
+    public boolean canControlUnlockedScreenOff() {
+        return getAlwaysOn()
+                && mFeatureFlags.isEnabled(Flags.LOCKSCREEN_ANIMATIONS)
+                && !getDisplayNeedsBlanking();
+    }
+
+    /**
      * Whether we want to control the screen off animation when the device is unlocked. If we do,
      * we'll animate in AOD before turning off the screen, rather than simply fading to black and
      * then abruptly showing AOD.
@@ -308,8 +326,7 @@
      * disabled for a11y.
      */
     public boolean shouldControlUnlockedScreenOff() {
-        return canControlUnlockedScreenOff()
-                && mUnlockedScreenOffAnimationController.shouldPlayUnlockedScreenOffAnimation();
+        return mUnlockedScreenOffAnimationController.shouldPlayUnlockedScreenOffAnimation();
     }
 
     public boolean shouldDelayKeyguardShow() {
@@ -341,16 +358,6 @@
         return getAlwaysOn() && mKeyguardShowing;
     }
 
-    /**
-     * Whether we're capable of controlling the screen off animation if we want to. This isn't
-     * possible if AOD isn't even enabled or if the flag is disabled.
-     */
-    public boolean canControlUnlockedScreenOff() {
-        return getAlwaysOn()
-                && mFeatureFlags.isEnabled(Flags.LOCKSCREEN_ANIMATIONS)
-                && !getDisplayNeedsBlanking();
-    }
-
     private boolean getBoolean(String propName, int resId) {
         return SystemProperties.getBoolean(propName, mResources.getBoolean(resId));
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerImpl.java
index 56c74bf..24660b2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerImpl.java
@@ -288,7 +288,7 @@
         final boolean keyguardOrAod = state.mKeyguardShowing
                 || (state.mDozing && mDozeParameters.getAlwaysOn());
         if ((keyguardOrAod && !state.mBackdropShowing && !state.mLightRevealScrimOpaque)
-                || mKeyguardViewMediator.isAnimatingBetweenKeyguardAndSurfaceBehindOrWillBe()) {
+                || mKeyguardViewMediator.isAnimatingBetweenKeyguardAndSurfaceBehind()) {
             // Show the wallpaper if we're on keyguard/AOD and the wallpaper is not occluded by a
             // solid backdrop. Also, show it if we are currently animating between the
             // keyguard and the surface behind the keyguard - we want to use the wallpaper as a
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java
index c850755..ad304c5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java
@@ -418,14 +418,14 @@
                     expand = false;
                 } else if (onKeyguard) {
                     expand = true;
-                } else if (mCentralSurfaces.isBouncerShowingOverDream()) {
-                    expand = false;
                 } else if (mKeyguardStateController.isKeyguardFadingAway()) {
                     // If we're in the middle of dismissing the keyguard, don't expand due to the
                     // cancelled gesture. Gesture cancellation during an unlock is expected in some
                     // situations, such keeping your finger down while swiping to unlock to an app
                     // that is locked in landscape (the rotation will cancel the touch event).
                     expand = false;
+                } else if (mCentralSurfaces.isBouncerShowingOverDream()) {
+                    expand = false;
                 } else {
                     // If we get a cancel, put the shade back to the state it was in when the
                     // gesture started
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt
index c11d450..935f87d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt
@@ -17,6 +17,7 @@
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.keyguard.KeyguardViewMediator
 import com.android.systemui.keyguard.WakefulnessLifecycle
+import com.android.systemui.statusbar.CircleReveal
 import com.android.systemui.statusbar.LightRevealScrim
 import com.android.systemui.statusbar.StatusBarState
 import com.android.systemui.statusbar.StatusBarStateControllerImpl
@@ -59,8 +60,14 @@
     private val powerManager: PowerManager,
     private val handler: Handler = Handler()
 ) : WakefulnessLifecycle.Observer, ScreenOffAnimation {
-
     private lateinit var mCentralSurfaces: CentralSurfaces
+    /**
+     * Whether or not [initialize] has been called to provide us with the StatusBar,
+     * NotificationPanelViewController, and LightRevealSrim so that we can run the unlocked screen
+     * off animation.
+     */
+    private var initialized = false
+
     private lateinit var lightRevealScrim: LightRevealScrim
 
     private var animatorDurationScale = 1f
@@ -79,7 +86,9 @@
         duration = LIGHT_REVEAL_ANIMATION_DURATION
         interpolator = Interpolators.LINEAR
         addUpdateListener {
-            lightRevealScrim.revealAmount = it.animatedValue as Float
+            if (lightRevealScrim.revealEffect !is CircleReveal) {
+                lightRevealScrim.revealAmount = it.animatedValue as Float
+            }
             if (lightRevealScrim.isScrimAlmostOccludes &&
                     interactionJankMonitor.isInstrumenting(CUJ_SCREEN_OFF)) {
                 // ends the instrument when the scrim almost occludes the screen.
@@ -89,9 +98,9 @@
         }
         addListener(object : AnimatorListenerAdapter() {
             override fun onAnimationCancel(animation: Animator?) {
-                lightRevealScrim.revealAmount = 1f
-                lightRevealAnimationPlaying = false
-                interactionJankMonitor.cancel(CUJ_SCREEN_OFF)
+                if (lightRevealScrim.revealEffect !is CircleReveal) {
+                    lightRevealScrim.revealAmount = 1f
+                }
             }
 
             override fun onAnimationEnd(animation: Animator?) {
@@ -116,6 +125,7 @@
         centralSurfaces: CentralSurfaces,
         lightRevealScrim: LightRevealScrim
     ) {
+        this.initialized = true
         this.lightRevealScrim = lightRevealScrim
         this.mCentralSurfaces = centralSurfaces
 
@@ -262,6 +272,18 @@
      * on the current state of the device.
      */
     fun shouldPlayUnlockedScreenOffAnimation(): Boolean {
+        // If we haven't been initialized yet, we don't have a StatusBar/LightRevealScrim yet, so we
+        // can't perform the animation.
+        if (!initialized) {
+            return false
+        }
+
+        // If the device isn't in a state where we can control unlocked screen off (no AOD enabled,
+        // power save, etc.) then we shouldn't try to do so.
+        if (!dozeParameters.get().canControlUnlockedScreenOff()) {
+            return false
+        }
+
         // If we explicitly already decided not to play the screen off animation, then never change
         // our mind.
         if (decidedToAnimateGoingToSleep == false) {
@@ -304,7 +326,7 @@
     }
 
     override fun shouldDelayDisplayDozeTransition(): Boolean =
-        dozeParameters.get().shouldControlUnlockedScreenOff()
+        shouldPlayUnlockedScreenOffAnimation()
 
     /**
      * Whether we're doing the light reveal animation or we're done with that and animating in the
diff --git a/packages/SystemUI/src/com/android/systemui/util/condition/Monitor.java b/packages/SystemUI/src/com/android/systemui/util/condition/Monitor.java
index d3c6e9a..313d56f 100644
--- a/packages/SystemUI/src/com/android/systemui/util/condition/Monitor.java
+++ b/packages/SystemUI/src/com/android/systemui/util/condition/Monitor.java
@@ -18,6 +18,7 @@
 
 import android.util.Log;
 
+import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.statusbar.policy.CallbackController;
 
 import org.jetbrains.annotations.NotNull;
@@ -60,21 +61,13 @@
     };
 
     @Inject
-    public Monitor(Executor executor, Set<Condition> conditions, Set<Callback> callbacks) {
+    public Monitor(@Main Executor executor, Set<Condition> conditions) {
         mConditions = new HashSet<>();
         mExecutor = executor;
 
         if (conditions != null) {
             mConditions.addAll(conditions);
         }
-
-        if (callbacks == null) {
-            return;
-        }
-
-        for (Callback callback : callbacks) {
-            addCallbackLocked(callback);
-        }
     }
 
     private void updateConditionMetState() {
diff --git a/packages/SystemUI/src/com/android/systemui/util/condition/dagger/MonitorComponent.java b/packages/SystemUI/src/com/android/systemui/util/condition/dagger/MonitorComponent.java
index fc67973..8e739d6 100644
--- a/packages/SystemUI/src/com/android/systemui/util/condition/dagger/MonitorComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/util/condition/dagger/MonitorComponent.java
@@ -34,8 +34,7 @@
      */
     @Subcomponent.Factory
     interface Factory {
-        MonitorComponent create(@BindsInstance Set<Condition> conditions,
-                @BindsInstance Set<Monitor.Callback> callbacks);
+        MonitorComponent create(@BindsInstance Set<Condition> conditions);
     }
 
     /**
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java
index ee150ca..18ba7dc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java
@@ -293,6 +293,22 @@
     }
 
     @Test
+    public void deleteWindowMagnification_notifySourceBoundsChanged() {
+        mInstrumentation.runOnMainSync(
+                () -> mWindowMagnificationController.enableWindowMagnificationInternal(Float.NaN,
+                        Float.NaN,
+                        Float.NaN));
+
+        mInstrumentation.runOnMainSync(
+                () -> mWindowMagnificationController.deleteWindowMagnification());
+
+        // The first time is for notifying magnification enabled and the second time is for
+        // notifying magnification disabled.
+        verify(mWindowMagnifierCallback, times(2)).onSourceBoundsChanged(
+                (eq(mContext.getDisplayId())), any());
+    }
+
+    @Test
     public void moveMagnifier_schedulesFrame() {
         mInstrumentation.runOnMainSync(() -> {
             mWindowMagnificationController.enableWindowMagnificationInternal(Float.NaN, Float.NaN,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/broadcast/ActionReceiverTest.kt b/packages/SystemUI/tests/src/com/android/systemui/broadcast/ActionReceiverTest.kt
index e95eb4e..f5990be 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/broadcast/ActionReceiverTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/broadcast/ActionReceiverTest.kt
@@ -42,6 +42,7 @@
 import org.mockito.Captor
 import org.mockito.Mock
 import org.mockito.Mockito
+import org.mockito.Mockito.`when`
 import org.mockito.Mockito.mock
 import org.mockito.Mockito.never
 import org.mockito.Mockito.verify
@@ -81,6 +82,8 @@
     @Mock
     private lateinit var unregisterFunction: BroadcastReceiver.() -> Unit
     @Mock
+    private lateinit var isPendingRemovalFunction: (BroadcastReceiver, Int) -> Boolean
+    @Mock
     private lateinit var receiver1: BroadcastReceiver
     @Mock
     private lateinit var receiver2: BroadcastReceiver
@@ -98,13 +101,16 @@
         MockitoAnnotations.initMocks(this)
         executor = FakeExecutor(FakeSystemClock())
 
+        `when`(isPendingRemovalFunction(any(), anyInt())).thenReturn(false)
+
         actionReceiver = ActionReceiver(
                 ACTION1,
                 USER.identifier,
                 registerFunction,
                 unregisterFunction,
                 executor,
-                logger
+                logger,
+                isPendingRemovalFunction
         )
     }
 
@@ -249,6 +255,20 @@
         verify(logger).logBroadcastDispatched(anyInt(), eq(ACTION1), sameNotNull(receiver1))
     }
 
+    @Test
+    fun testBroadcastNotDispatchingOnPendingRemoval() {
+        `when`(isPendingRemovalFunction(receiver1, USER.identifier)).thenReturn(true)
+
+        val receiverData = ReceiverData(receiver1, IntentFilter(ACTION1), directExecutor, USER)
+
+        actionReceiver.addReceiverData(receiverData)
+
+        val intent = Intent(ACTION1)
+        actionReceiver.onReceive(mContext, intent)
+        executor.runAllReady()
+        verify(receiver1, never()).onReceive(any(), eq(intent))
+    }
+
     @Test(expected = IllegalStateException::class)
     fun testBroadcastWithWrongAction_throwsException() {
         actionReceiver.onReceive(mContext, Intent(ACTION2))
diff --git a/packages/SystemUI/tests/src/com/android/systemui/broadcast/BroadcastDispatcherTest.kt b/packages/SystemUI/tests/src/com/android/systemui/broadcast/BroadcastDispatcherTest.kt
index a1d1933..7795d2c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/broadcast/BroadcastDispatcherTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/broadcast/BroadcastDispatcherTest.kt
@@ -41,6 +41,8 @@
 import org.mockito.Captor
 import org.mockito.Mock
 import org.mockito.Mockito.`when`
+import org.mockito.Mockito.anyInt
+import org.mockito.Mockito.inOrder
 import org.mockito.Mockito.mock
 import org.mockito.Mockito.never
 import org.mockito.Mockito.verify
@@ -85,6 +87,8 @@
     private lateinit var logger: BroadcastDispatcherLogger
     @Mock
     private lateinit var userTracker: UserTracker
+    @Mock
+    private lateinit var removalPendingStore: PendingRemovalStore
 
     private lateinit var executor: Executor
 
@@ -108,6 +112,7 @@
                 mock(DumpManager::class.java),
                 logger,
                 userTracker,
+                removalPendingStore,
                 mapOf(0 to mockUBRUser0, 1 to mockUBRUser1))
 
         // These should be valid filters
@@ -325,6 +330,57 @@
         broadcastDispatcher.registerReceiver(broadcastReceiver, testFilter)
     }
 
+    @Test
+    fun testTaggedReceiverForRemovalImmediately_allUsers() {
+        broadcastDispatcher.unregisterReceiver(broadcastReceiver)
+
+        verify(removalPendingStore).tagForRemoval(broadcastReceiver, UserHandle.USER_ALL)
+        verify(removalPendingStore, never()).clearPendingRemoval(eq(broadcastReceiver), anyInt())
+    }
+
+    @Test
+    fun testTaggedReceiverForRemovalImmediately_singleUser() {
+        val user = 0
+        broadcastDispatcher.unregisterReceiverForUser(broadcastReceiver, UserHandle.of(user))
+
+        verify(removalPendingStore).tagForRemoval(broadcastReceiver, user)
+        verify(removalPendingStore, never()).clearPendingRemoval(eq(broadcastReceiver), anyInt())
+    }
+
+    @Test
+    fun testUnregisterReceiverClearsPendingRemovalAfterRemoving_allUsers() {
+        broadcastDispatcher.registerReceiver(broadcastReceiver, intentFilter, null, user0)
+        broadcastDispatcher.registerReceiver(broadcastReceiver, intentFilter, null, user1)
+
+        broadcastDispatcher.unregisterReceiver(broadcastReceiver)
+
+        testableLooper.processAllMessages()
+
+        val inOrderUser0 = inOrder(mockUBRUser0, removalPendingStore)
+        inOrderUser0.verify(mockUBRUser0).unregisterReceiver(broadcastReceiver)
+        inOrderUser0.verify(removalPendingStore)
+            .clearPendingRemoval(broadcastReceiver, UserHandle.USER_ALL)
+
+        val inOrderUser1 = inOrder(mockUBRUser1, removalPendingStore)
+        inOrderUser1.verify(mockUBRUser1).unregisterReceiver(broadcastReceiver)
+        inOrderUser1.verify(removalPendingStore)
+            .clearPendingRemoval(broadcastReceiver, UserHandle.USER_ALL)
+    }
+
+    @Test
+    fun testUnregisterReceiverclearPendingRemovalAfterRemoving_singleUser() {
+        broadcastDispatcher.registerReceiver(broadcastReceiver, intentFilter, null, user1)
+
+        broadcastDispatcher.unregisterReceiverForUser(broadcastReceiver, user1)
+
+        testableLooper.processAllMessages()
+
+        val inOrderUser1 = inOrder(mockUBRUser1, removalPendingStore)
+        inOrderUser1.verify(mockUBRUser1).unregisterReceiver(broadcastReceiver)
+        inOrderUser1.verify(removalPendingStore)
+            .clearPendingRemoval(broadcastReceiver, user1.identifier)
+    }
+
     private fun setUserMock(mockContext: Context, user: UserHandle) {
         `when`(mockContext.user).thenReturn(user)
         `when`(mockContext.userId).thenReturn(user.identifier)
@@ -337,8 +393,17 @@
         dumpManager: DumpManager,
         logger: BroadcastDispatcherLogger,
         userTracker: UserTracker,
+        removalPendingStore: PendingRemovalStore,
         var mockUBRMap: Map<Int, UserBroadcastDispatcher>
-    ) : BroadcastDispatcher(context, bgLooper, executor, dumpManager, logger, userTracker) {
+    ) : BroadcastDispatcher(
+        context,
+        bgLooper,
+        executor,
+        dumpManager,
+        logger,
+        userTracker,
+        removalPendingStore
+    ) {
         override fun createUBRForUser(userId: Int): UserBroadcastDispatcher {
             return mockUBRMap.getOrDefault(userId, mock(UserBroadcastDispatcher::class.java))
         }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/broadcast/PendingRemovalStoreTest.kt b/packages/SystemUI/tests/src/com/android/systemui/broadcast/PendingRemovalStoreTest.kt
new file mode 100644
index 0000000..43d2cb8
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/broadcast/PendingRemovalStoreTest.kt
@@ -0,0 +1,81 @@
+package com.android.systemui.broadcast
+
+import android.content.BroadcastReceiver
+import android.os.UserHandle
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.broadcast.logging.BroadcastDispatcherLogger
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+class PendingRemovalStoreTest : SysuiTestCase() {
+
+    @Mock
+    private lateinit var logger: BroadcastDispatcherLogger
+    @Mock
+    private lateinit var receiverOne: BroadcastReceiver
+    @Mock
+    private lateinit var receiverTwo: BroadcastReceiver
+
+    private lateinit var store: PendingRemovalStore
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+
+        store = PendingRemovalStore(logger)
+    }
+
+    @Test
+    fun testTagForRemoval_logged() {
+        val user = 10
+        store.tagForRemoval(receiverOne, 10)
+
+        verify(logger).logTagForRemoval(user, receiverOne)
+    }
+
+    @Test
+    fun testClearedPendingRemoval_logged() {
+        val user = UserHandle.USER_ALL
+        store.clearPendingRemoval(receiverOne, user)
+
+        verify(logger).logClearedAfterRemoval(user, receiverOne)
+    }
+
+    @Test
+    fun testTaggedReceiverMarkedAsPending_specificUser() {
+        val user = 10
+        store.tagForRemoval(receiverOne, user)
+
+        assertThat(store.isPendingRemoval(receiverOne, user)).isTrue()
+        assertThat(store.isPendingRemoval(receiverOne, user + 1)).isFalse()
+        assertThat(store.isPendingRemoval(receiverOne, UserHandle.USER_ALL)).isFalse()
+    }
+
+    @Test
+    fun testTaggedReceiverMarkedAsPending_allUsers() {
+        val user = 10
+        store.tagForRemoval(receiverOne, UserHandle.USER_ALL)
+
+        assertThat(store.isPendingRemoval(receiverOne, user)).isTrue()
+        assertThat(store.isPendingRemoval(receiverOne, user + 1)).isTrue()
+        assertThat(store.isPendingRemoval(receiverOne, UserHandle.USER_ALL)).isTrue()
+    }
+
+    @Test
+    fun testOnlyBlockCorrectReceiver() {
+        val user = 10
+        store.tagForRemoval(receiverOne, user)
+
+        assertThat(store.isPendingRemoval(receiverOne, user)).isTrue()
+        assertThat(store.isPendingRemoval(receiverTwo, user)).isFalse()
+    }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/broadcast/UserBroadcastDispatcherTest.kt b/packages/SystemUI/tests/src/com/android/systemui/broadcast/UserBroadcastDispatcherTest.kt
index 116b81d..39e4467 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/broadcast/UserBroadcastDispatcherTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/broadcast/UserBroadcastDispatcherTest.kt
@@ -68,6 +68,8 @@
     private lateinit var mockContext: Context
     @Mock
     private lateinit var logger: BroadcastDispatcherLogger
+    @Mock
+    private lateinit var removalPendingStore: PendingRemovalStore
 
     private lateinit var testableLooper: TestableLooper
     private lateinit var userBroadcastDispatcher: UserBroadcastDispatcher
@@ -84,7 +86,13 @@
         fakeExecutor = FakeExecutor(FakeSystemClock())
 
         userBroadcastDispatcher = object : UserBroadcastDispatcher(
-                mockContext, USER_ID, testableLooper.looper, mock(Executor::class.java), logger) {
+                mockContext,
+                USER_ID,
+                testableLooper.looper,
+                mock(Executor::class.java),
+                logger,
+                removalPendingStore
+        ) {
             override fun createActionReceiver(
                 action: String,
                 permission: String?,
@@ -216,7 +224,8 @@
                 USER_ID,
                 testableLooper.looper,
                 fakeExecutor,
-                logger
+                logger,
+                removalPendingStore
         )
         uBR.registerReceiver(
                 ReceiverData(
@@ -243,7 +252,8 @@
             USER_ID,
             testableLooper.looper,
             fakeExecutor,
-            logger
+            logger,
+            removalPendingStore
         )
         uBR.registerReceiver(
             ReceiverData(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandlerTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandlerTest.java
index 33fb5a2..535023f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandlerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandlerTest.java
@@ -160,28 +160,6 @@
                         2)).isTrue();
     }
 
-    /**
-     * Ensures {@link CentralSurfaces}
-     */
-    @Test
-    public void testInformBouncerShowingOnExpand() {
-        swipeToPosition(1f, Direction.UP, 0);
-        verify(mCentralSurfaces).setBouncerShowing(true);
-    }
-
-    /**
-     * Ensures {@link CentralSurfaces}
-     */
-    @Test
-    public void testInformBouncerHidingOnCollapse() {
-        // Must swipe up to set initial state.
-        swipeToPosition(1f, Direction.UP, 0);
-        Mockito.clearInvocations(mCentralSurfaces);
-
-        swipeToPosition(0f, Direction.DOWN, 0);
-        verify(mCentralSurfaces).setBouncerShowing(false);
-    }
-
     private enum Direction {
         DOWN,
         UP,
@@ -442,6 +420,29 @@
         verify(mUiEventLogger).log(BouncerSwipeTouchHandler.DreamEvent.DREAM_BOUNCER_FULLY_VISIBLE);
     }
 
+    /**
+     * Ensures {@link CentralSurfaces}
+     */
+    @Test
+    public void testInformBouncerShowingOnExpand() {
+        swipeToPosition(1f, Direction.UP, 0);
+        verify(mCentralSurfaces).setBouncerShowingOverDream(true);
+    }
+
+    /**
+     * Ensures {@link CentralSurfaces}
+     */
+    @Test
+    public void testInformBouncerHidingOnCollapse() {
+        // Must swipe up to set initial state.
+        swipeToPosition(1f, Direction.UP, 0);
+        Mockito.clearInvocations(mCentralSurfaces);
+
+        swipeToPosition(0f, Direction.DOWN, 0);
+        verify(mCentralSurfaces).setBouncerShowingOverDream(false);
+    }
+
+
     private void swipeToPosition(float percent, Direction direction, float velocityY) {
         Mockito.clearInvocations(mTouchSession);
         mTouchHandler.onSessionStart(mTouchSession);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeParametersTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeParametersTest.java
index 5f2bbd3..077b41a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeParametersTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeParametersTest.java
@@ -126,6 +126,12 @@
         setAodEnabledForTest(true);
         setShouldControlUnlockedScreenOffForTest(true);
         setDisplayNeedsBlankingForTest(false);
+
+        // Default to false here (with one test to make sure that when it returns true, we respect
+        // that). We'll test the specific conditions for this to return true/false in the
+        // UnlockedScreenOffAnimationController's tests.
+        when(mUnlockedScreenOffAnimationController.shouldPlayUnlockedScreenOffAnimation())
+                .thenReturn(false);
     }
 
     @Test
@@ -174,9 +180,12 @@
      */
     @Test
     public void testControlUnlockedScreenOffAnimation_dozeAfterScreenOff_false() {
+        mDozeParameters.mKeyguardVisibilityCallback.onKeyguardVisibilityChanged(true);
+
         // If AOD is disabled, we shouldn't want to control screen off. Also, let's double check
         // that when that value is updated, we called through to PowerManager.
         setAodEnabledForTest(false);
+
         assertFalse(mDozeParameters.shouldControlScreenOff());
         assertTrue(mPowerManagerDozeAfterScreenOff);
 
@@ -188,7 +197,6 @@
 
     @Test
     public void testControlUnlockedScreenOffAnimationDisabled_dozeAfterScreenOff() {
-        setShouldControlUnlockedScreenOffForTest(true);
         when(mFeatureFlags.isEnabled(Flags.LOCKSCREEN_ANIMATIONS)).thenReturn(false);
 
         assertFalse(mDozeParameters.shouldControlUnlockedScreenOff());
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationControllerTest.kt
index 050563a..0936b77 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationControllerTest.kt
@@ -31,6 +31,7 @@
 import com.android.systemui.statusbar.StatusBarStateControllerImpl
 import com.android.systemui.statusbar.policy.KeyguardStateController
 import com.android.systemui.util.settings.GlobalSettings
+import junit.framework.Assert.assertFalse
 import org.junit.After
 import org.junit.Before
 import org.junit.Test
@@ -133,7 +134,7 @@
      */
     @Test
     fun testAodUiShownIfNotInteractive() {
-        `when`(dozeParameters.shouldControlUnlockedScreenOff()).thenReturn(true)
+        `when`(dozeParameters.canControlUnlockedScreenOff()).thenReturn(true)
         `when`(powerManager.isInteractive).thenReturn(false)
 
         val callbackCaptor = ArgumentCaptor.forClass(Runnable::class.java)
@@ -156,7 +157,7 @@
      */
     @Test
     fun testAodUiNotShownIfInteractive() {
-        `when`(dozeParameters.shouldControlUnlockedScreenOff()).thenReturn(true)
+        `when`(dozeParameters.canControlUnlockedScreenOff()).thenReturn(true)
         `when`(powerManager.isInteractive).thenReturn(true)
 
         val callbackCaptor = ArgumentCaptor.forClass(Runnable::class.java)
@@ -167,4 +168,13 @@
 
         verify(notificationPanelViewController, never()).showAodUi()
     }
+
+    @Test
+    fun testNoAnimationPlaying_dozeParamsCanNotControlScreenOff() {
+        `when`(dozeParameters.canControlUnlockedScreenOff()).thenReturn(false)
+
+        assertFalse(controller.shouldPlayUnlockedScreenOffAnimation())
+        controller.startAnimation()
+        assertFalse(controller.isAnimationPlaying())
+    }
 }
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/condition/ConditionMonitorTest.java b/packages/SystemUI/tests/src/com/android/systemui/util/condition/ConditionMonitorTest.java
index 5118637..7589616 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/util/condition/ConditionMonitorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/condition/ConditionMonitorTest.java
@@ -65,7 +65,7 @@
         mCondition3 = spy(new FakeCondition());
         mConditions = new HashSet<>(Arrays.asList(mCondition1, mCondition2, mCondition3));
 
-        mConditionMonitor = new Monitor(mExecutor, mConditions, null /*callbacks*/);
+        mConditionMonitor = new Monitor(mExecutor, mConditions);
     }
 
     @Test
@@ -76,8 +76,10 @@
 
         final Monitor monitor = new Monitor(
                 mExecutor,
-                new HashSet<>(Arrays.asList(overridingCondition, regularCondition)),
-                new HashSet<>(Arrays.asList(callback)));
+                new HashSet<>(Arrays.asList(overridingCondition, regularCondition)));
+
+        monitor.addCallback(callback);
+        mExecutor.runAllReady();
 
         when(overridingCondition.isOverridingCondition()).thenReturn(true);
         when(overridingCondition.isConditionMet()).thenReturn(true);
@@ -123,8 +125,9 @@
         final Monitor monitor = new Monitor(
                 mExecutor,
                 new HashSet<>(Arrays.asList(overridingCondition, overridingCondition2,
-                        regularCondition)),
-                new HashSet<>(Arrays.asList(callback)));
+                        regularCondition)));
+        monitor.addCallback(callback);
+        mExecutor.runAllReady();
 
         when(overridingCondition.isOverridingCondition()).thenReturn(true);
         when(overridingCondition.isConditionMet()).thenReturn(true);
@@ -174,8 +177,8 @@
                 mock(Monitor.Callback.class);
         final Condition condition = mock(Condition.class);
         when(condition.isConditionMet()).thenReturn(true);
-        final Monitor monitor = new Monitor(mExecutor, new HashSet<>(Arrays.asList(condition)),
-                new HashSet<>(Arrays.asList(callback1)));
+        final Monitor monitor = new Monitor(mExecutor, new HashSet<>(Arrays.asList(condition)));
+        monitor.addCallback(callback1);
 
         final Monitor.Callback callback2 =
                 mock(Monitor.Callback.class);
@@ -186,7 +189,7 @@
 
     @Test
     public void addCallback_noConditions_reportAllConditionsMet() {
-        final Monitor monitor = new Monitor(mExecutor, new HashSet<>(), null /*callbacks*/);
+        final Monitor monitor = new Monitor(mExecutor, new HashSet<>());
         final Monitor.Callback callback = mock(Monitor.Callback.class);
 
         monitor.addCallback(callback);
@@ -196,7 +199,7 @@
 
     @Test
     public void addCallback_withMultipleInstancesOfTheSameCallback_registerOnlyOne() {
-        final Monitor monitor = new Monitor(mExecutor, new HashSet<>(), null /*callbacks*/);
+        final Monitor monitor = new Monitor(mExecutor, new HashSet<>());
         final Monitor.Callback callback = mock(Monitor.Callback.class);
 
         // Adds the same instance multiple times.
@@ -212,8 +215,7 @@
     @Test
     public void removeCallback_shouldNoLongerReceiveUpdate() {
         final Condition condition = mock(Condition.class);
-        final Monitor monitor = new Monitor(mExecutor, new HashSet<>(Arrays.asList(condition)),
-                null);
+        final Monitor monitor = new Monitor(mExecutor, new HashSet<>(Arrays.asList(condition)));
         final Monitor.Callback callback =
                 mock(Monitor.Callback.class);
         monitor.addCallback(callback);
diff --git a/packages/overlays/DisplayCutoutEmulationCornerOverlay/res/values/config.xml b/packages/overlays/DisplayCutoutEmulationCornerOverlay/res/values/config.xml
index c340432..67d405d 100644
--- a/packages/overlays/DisplayCutoutEmulationCornerOverlay/res/values/config.xml
+++ b/packages/overlays/DisplayCutoutEmulationCornerOverlay/res/values/config.xml
@@ -44,6 +44,9 @@
      -->
     <bool name="config_fillMainBuiltInDisplayCutout">true</bool>
 
+    <!-- Height of the status bar -->
+    <dimen name="status_bar_height_portrait">48dp</dimen>
+    <dimen name="status_bar_height_landscape">28dp</dimen>
 </resources>
 
 
diff --git a/packages/overlays/DisplayCutoutEmulationDoubleOverlay/res/values/config.xml b/packages/overlays/DisplayCutoutEmulationDoubleOverlay/res/values/config.xml
index 928d9df..e08c32f 100644
--- a/packages/overlays/DisplayCutoutEmulationDoubleOverlay/res/values/config.xml
+++ b/packages/overlays/DisplayCutoutEmulationDoubleOverlay/res/values/config.xml
@@ -56,6 +56,9 @@
      -->
     <bool name="config_fillMainBuiltInDisplayCutout">true</bool>
 
+    <!-- Height of the status bar -->
+    <dimen name="status_bar_height_portrait">48dp</dimen>
+    <dimen name="status_bar_height_landscape">28dp</dimen>
 </resources>
 
 
diff --git a/packages/overlays/DisplayCutoutEmulationHoleOverlay/res/values/config.xml b/packages/overlays/DisplayCutoutEmulationHoleOverlay/res/values/config.xml
index 62f0535..68916cc 100644
--- a/packages/overlays/DisplayCutoutEmulationHoleOverlay/res/values/config.xml
+++ b/packages/overlays/DisplayCutoutEmulationHoleOverlay/res/values/config.xml
@@ -48,6 +48,9 @@
      -->
     <bool name="config_fillMainBuiltInDisplayCutout">true</bool>
 
+    <!-- Height of the status bar -->
+    <dimen name="status_bar_height_portrait">136px</dimen>
+    <dimen name="status_bar_height_landscape">28dp</dimen>
 </resources>
 
 
diff --git a/packages/overlays/DisplayCutoutEmulationNarrowOverlay/res/values/config.xml b/packages/overlays/DisplayCutoutEmulationNarrowOverlay/res/values/config.xml
index a9f8b4b..605059b2 100644
--- a/packages/overlays/DisplayCutoutEmulationNarrowOverlay/res/values/config.xml
+++ b/packages/overlays/DisplayCutoutEmulationNarrowOverlay/res/values/config.xml
@@ -47,6 +47,9 @@
      -->
     <bool name="config_fillMainBuiltInDisplayCutout">true</bool>
 
+    <!-- Height of the status bar -->
+    <dimen name="status_bar_height_portrait">48dp</dimen>
+    <dimen name="status_bar_height_landscape">28dp</dimen>
 </resources>
 
 
diff --git a/packages/overlays/DisplayCutoutEmulationTallOverlay/res/values/config.xml b/packages/overlays/DisplayCutoutEmulationTallOverlay/res/values/config.xml
index be7d0e4..370d730 100644
--- a/packages/overlays/DisplayCutoutEmulationTallOverlay/res/values/config.xml
+++ b/packages/overlays/DisplayCutoutEmulationTallOverlay/res/values/config.xml
@@ -47,6 +47,9 @@
      -->
     <bool name="config_fillMainBuiltInDisplayCutout">true</bool>
 
+    <!-- Height of the status bar -->
+    <dimen name="status_bar_height_portrait">48dp</dimen>
+    <dimen name="status_bar_height_landscape">28dp</dimen>
 </resources>
 
 
diff --git a/packages/overlays/DisplayCutoutEmulationWaterfallOverlay/res/values/config.xml b/packages/overlays/DisplayCutoutEmulationWaterfallOverlay/res/values/config.xml
index cc51ebe..98779f0 100644
--- a/packages/overlays/DisplayCutoutEmulationWaterfallOverlay/res/values/config.xml
+++ b/packages/overlays/DisplayCutoutEmulationWaterfallOverlay/res/values/config.xml
@@ -19,6 +19,12 @@
     <string translatable="false" name="config_mainBuiltInDisplayCutout"></string>
     <string translatable="false" name="config_mainBuiltInDisplayCutoutRectApproximation"></string>
 
+    <!-- Height of the status bar in portrait. The height should be
+         Max((status bar content height + waterfall top size), top cutout size) -->
+    <dimen name="status_bar_height_portrait">28dp</dimen>
+    <!-- Max((28 + 20), 0) = 48 -->
+    <dimen name="status_bar_height_landscape">48dp</dimen>
+
     <dimen name="waterfall_display_left_edge_size">20dp</dimen>
     <dimen name="waterfall_display_top_edge_size">0dp</dimen>
     <dimen name="waterfall_display_right_edge_size">20dp</dimen>
diff --git a/packages/overlays/DisplayCutoutEmulationWideOverlay/res/values/config.xml b/packages/overlays/DisplayCutoutEmulationWideOverlay/res/values/config.xml
index 78cc7e0..176f1dc 100644
--- a/packages/overlays/DisplayCutoutEmulationWideOverlay/res/values/config.xml
+++ b/packages/overlays/DisplayCutoutEmulationWideOverlay/res/values/config.xml
@@ -47,6 +47,9 @@
      -->
     <bool name="config_fillMainBuiltInDisplayCutout">true</bool>
 
+    <!-- Height of the status bar -->
+    <dimen name="status_bar_height_portrait">48dp</dimen>
+    <dimen name="status_bar_height_landscape">28dp</dimen>
 </resources>
 
 
diff --git a/packages/overlays/NoCutoutOverlay/res/values/config.xml b/packages/overlays/NoCutoutOverlay/res/values/config.xml
index 84b91b8..ed0340b 100644
--- a/packages/overlays/NoCutoutOverlay/res/values/config.xml
+++ b/packages/overlays/NoCutoutOverlay/res/values/config.xml
@@ -25,4 +25,7 @@
          by shrinking the display such that it does not overlap the cutout area. -->
     <bool name="config_maskMainBuiltInDisplayCutout">true</bool>
 
+    <!-- Height of the status bar -->
+    <dimen name="status_bar_height_portrait">28dp</dimen>
+    <dimen name="status_bar_height_landscape">28dp</dimen>
 </resources>
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationController.java b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationController.java
index ecc45eb..6cfbfb8 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationController.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationController.java
@@ -356,13 +356,6 @@
                         mSpecAnimationBridge, spec, animationCallback);
                 mControllerCtx.getHandler().sendMessage(m);
             }
-
-            final boolean lastMagnificationActivated = mMagnificationActivated;
-            mMagnificationActivated = spec.scale > 1.0f;
-            if (mMagnificationActivated != lastMagnificationActivated) {
-                mMagnificationInfoChangedCallback.onFullScreenMagnificationActivationState(
-                        mDisplayId, mMagnificationActivated);
-            }
         }
 
         /**
@@ -376,9 +369,17 @@
 
         @GuardedBy("mLock")
         void onMagnificationChangedLocked() {
+            final float scale = getScale();
+            final boolean lastMagnificationActivated = mMagnificationActivated;
+            mMagnificationActivated = scale > 1.0f;
+            if (mMagnificationActivated != lastMagnificationActivated) {
+                mMagnificationInfoChangedCallback.onFullScreenMagnificationActivationState(
+                        mDisplayId, mMagnificationActivated);
+            }
+
             final MagnificationConfig config = new MagnificationConfig.Builder()
                     .setMode(MAGNIFICATION_MODE_FULLSCREEN)
-                    .setScale(getScale())
+                    .setScale(scale)
                     .setCenterX(getCenterX())
                     .setCenterY(getCenterY()).build();
             mMagnificationInfoChangedCallback.onFullScreenMagnificationChanged(mDisplayId,
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java
index b263fb3..bb286e6 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java
@@ -16,6 +16,7 @@
 
 package com.android.server.accessibility.magnification;
 
+import static android.accessibilityservice.MagnificationConfig.MAGNIFICATION_MODE_FULLSCREEN;
 import static android.accessibilityservice.MagnificationConfig.MAGNIFICATION_MODE_WINDOW;
 import static android.content.pm.PackageManager.FEATURE_WINDOW_MAGNIFICATION;
 import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_ALL;
@@ -111,6 +112,15 @@
     @GuardedBy("mLock")
     private final SparseLongArray mFullScreenModeEnabledTimeArray = new SparseLongArray();
 
+    /**
+     * The transitioning magnification modes on the displays. The controller notifies
+     * magnification change depending on the target config mode.
+     * If the target mode is null, it means the config mode of the display is not
+     * transitioning.
+     */
+    @GuardedBy("mLock")
+    private final SparseArray<Integer> mTransitionModes = new SparseArray();
+
     @GuardedBy("mLock")
     private final SparseArray<WindowManagerInternal.AccessibilityControllerInternal
             .UiChangesForAccessibilityCallbacks> mAccessibilityCallbacksDelegateArray =
@@ -213,6 +223,7 @@
         final PointF currentCenter = getCurrentMagnificationCenterLocked(displayId, targetMode);
         final DisableMagnificationCallback animationCallback =
                 getDisableMagnificationEndRunnableLocked(displayId);
+
         if (currentCenter == null && animationCallback == null) {
             transitionCallBack.onResult(displayId, true);
             return;
@@ -233,6 +244,9 @@
             transitionCallBack.onResult(displayId, true);
             return;
         }
+
+        setTransitionState(displayId, targetMode);
+
         final FullScreenMagnificationController screenMagnificationController =
                 getFullScreenMagnificationController();
         final WindowMagnificationManager windowMagnificationMgr = getWindowMagnificationMgr();
@@ -286,26 +300,51 @@
                 Slog.w(TAG, "Discard previous animation request");
                 animationCallback.setExpiredAndRemoveFromListLocked();
             }
-
             final FullScreenMagnificationController screenMagnificationController =
                     getFullScreenMagnificationController();
             final WindowMagnificationManager windowMagnificationMgr = getWindowMagnificationMgr();
             final float targetScale = Float.isNaN(config.getScale())
                     ? getTargetModeScaleFromCurrentMagnification(displayId, targetMode)
                     : config.getScale();
-            if (targetMode == ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW) {
-                screenMagnificationController.reset(displayId, false);
-                windowMagnificationMgr.enableWindowMagnification(displayId,
-                        targetScale, magnificationCenter.x, magnificationCenter.y,
-                        animate ? STUB_ANIMATION_CALLBACK : null, id);
-            } else if (targetMode == ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN) {
-                windowMagnificationMgr.disableWindowMagnification(displayId, false, null);
-                if (!screenMagnificationController.isRegistered(displayId)) {
-                    screenMagnificationController.register(displayId);
+            try {
+                setTransitionState(displayId, targetMode);
+
+                if (targetMode == MAGNIFICATION_MODE_WINDOW) {
+                    screenMagnificationController.reset(displayId, false);
+                    windowMagnificationMgr.enableWindowMagnification(displayId,
+                            targetScale, magnificationCenter.x, magnificationCenter.y,
+                            animate ? STUB_ANIMATION_CALLBACK : null, id);
+                } else if (targetMode == MAGNIFICATION_MODE_FULLSCREEN) {
+                    windowMagnificationMgr.disableWindowMagnification(displayId, false, null);
+                    if (!screenMagnificationController.isRegistered(displayId)) {
+                        screenMagnificationController.register(displayId);
+                    }
+                    screenMagnificationController.setScaleAndCenter(displayId, targetScale,
+                            magnificationCenter.x, magnificationCenter.y, animate,
+                            id);
                 }
-                screenMagnificationController.setScaleAndCenter(displayId, targetScale,
-                        magnificationCenter.x, magnificationCenter.y, animate,
-                        id);
+            } finally {
+                // Reset transition state after enabling target mode.
+                setTransitionState(displayId, null);
+            }
+        }
+    }
+
+    /**
+     * Sets magnification config mode transition state. Called when the mode transition starts and
+     * ends. If the targetMode and the display id are null, it resets all
+     * the transition state.
+     *
+     * @param displayId  The logical display id
+     * @param targetMode The transition target mode. It is not transitioning, if the target mode
+     *                   is set null
+     */
+    private void setTransitionState(Integer displayId, Integer targetMode) {
+        synchronized (mLock) {
+            if (targetMode == null && displayId == null) {
+                mTransitionModes.clear();
+            } else {
+                mTransitionModes.put(displayId, targetMode);
             }
         }
     }
@@ -413,18 +452,57 @@
 
     @Override
     public void onSourceBoundsChanged(int displayId, Rect bounds) {
-        final MagnificationConfig config = new MagnificationConfig.Builder()
-                .setMode(MAGNIFICATION_MODE_WINDOW)
-                .setScale(getWindowMagnificationMgr().getScale(displayId))
-                .setCenterX(bounds.exactCenterX())
-                .setCenterY(bounds.exactCenterY()).build();
-        mAms.notifyMagnificationChanged(displayId, new Region(bounds), config);
+        if (shouldNotifyMagnificationChange(displayId, MAGNIFICATION_MODE_WINDOW)) {
+            final MagnificationConfig config = new MagnificationConfig.Builder()
+                    .setMode(MAGNIFICATION_MODE_WINDOW)
+                    .setScale(getWindowMagnificationMgr().getScale(displayId))
+                    .setCenterX(bounds.exactCenterX())
+                    .setCenterY(bounds.exactCenterY()).build();
+            mAms.notifyMagnificationChanged(displayId, new Region(bounds), config);
+        }
     }
 
     @Override
     public void onFullScreenMagnificationChanged(int displayId, @NonNull Region region,
             @NonNull MagnificationConfig config) {
-        mAms.notifyMagnificationChanged(displayId, region, config);
+        if (shouldNotifyMagnificationChange(displayId, MAGNIFICATION_MODE_FULLSCREEN)) {
+            mAms.notifyMagnificationChanged(displayId, region, config);
+        }
+    }
+
+    /**
+     * Should notify magnification change for the given display under the conditions below
+     *
+     * <ol>
+     *   <li> 1. No mode transitioning and the change mode is active. </li>
+     *   <li> 2. No mode transitioning and all the modes are inactive. </li>
+     *   <li> 3. It is mode transitioning and the change mode is the transition mode. </li>
+     * </ol>
+     *
+     * @param displayId  The logical display id
+     * @param changeMode The mode that has magnification spec change
+     */
+    private boolean shouldNotifyMagnificationChange(int displayId, int changeMode) {
+        synchronized (mLock) {
+            final boolean fullScreenMagnifying = mFullScreenMagnificationController != null
+                    && mFullScreenMagnificationController.isMagnifying(displayId);
+            final boolean windowEnabled = mWindowMagnificationMgr != null
+                    && mWindowMagnificationMgr.isWindowMagnifierEnabled(displayId);
+            final Integer transitionMode = mTransitionModes.get(displayId);
+            if (((changeMode == MAGNIFICATION_MODE_FULLSCREEN && fullScreenMagnifying)
+                    || (changeMode == MAGNIFICATION_MODE_WINDOW && windowEnabled))
+                    && (transitionMode == null)) {
+                return true;
+            }
+            if ((!fullScreenMagnifying && !windowEnabled)
+                    && (transitionMode == null)) {
+                return true;
+            }
+            if (transitionMode != null && changeMode == transitionMode) {
+                return true;
+            }
+        }
+        return false;
     }
 
     private void disableFullScreenMagnificationIfNeeded(int displayId) {
@@ -740,9 +818,32 @@
                     return;
                 }
                 setExpiredAndRemoveFromListLocked();
+                setTransitionState(mDisplayId, null);
+
                 if (success) {
                     adjustCurrentCenterIfNeededLocked();
                     applyMagnificationModeLocked(mTargetMode);
+                } else {
+                    // Notify magnification change if magnification is inactive when the
+                    // transition is failed. This is for the failed transition from
+                    // full-screen to window mode. Disable magnification callback helps to send
+                    // magnification inactive change since FullScreenMagnificationController
+                    // would not notify magnification change if the spec is not changed.
+                    final FullScreenMagnificationController screenMagnificationController =
+                            getFullScreenMagnificationController();
+                    if (mCurrentMode == ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN
+                            && !screenMagnificationController.isMagnifying(mDisplayId)) {
+                        MagnificationConfig.Builder configBuilder =
+                                new MagnificationConfig.Builder();
+                        Region region = new Region();
+                        configBuilder.setMode(MAGNIFICATION_MODE_FULLSCREEN)
+                                .setScale(screenMagnificationController.getScale(mDisplayId))
+                                .setCenterX(screenMagnificationController.getCenterX(mDisplayId))
+                                .setCenterY(screenMagnificationController.getCenterY(mDisplayId));
+                        screenMagnificationController.getMagnificationRegion(mDisplayId,
+                                region);
+                        mAms.notifyMagnificationChanged(mDisplayId, region, configBuilder.build());
+                    }
                 }
                 updateMagnificationButton(mDisplayId, mTargetMode);
                 if (mTransitionCallBack != null) {
@@ -770,6 +871,7 @@
                     return;
                 }
                 setExpiredAndRemoveFromListLocked();
+                setTransitionState(mDisplayId, null);
                 applyMagnificationModeLocked(mCurrentMode);
                 updateMagnificationButton(mDisplayId, mCurrentMode);
                 if (mTransitionCallBack != null) {
diff --git a/services/companion/java/com/android/server/companion/virtual/CameraAccessController.java b/services/companion/java/com/android/server/companion/virtual/CameraAccessController.java
index adc8459..ec0da49 100644
--- a/services/companion/java/com/android/server/companion/virtual/CameraAccessController.java
+++ b/services/companion/java/com/android/server/companion/virtual/CameraAccessController.java
@@ -30,6 +30,8 @@
 
 import com.android.internal.annotations.GuardedBy;
 
+import java.util.Set;
+
 /**
  * Handles blocking access to the camera for apps running on virtual devices.
  */
@@ -50,11 +52,23 @@
     @GuardedBy("mLock")
     private ArrayMap<String, InjectionSessionData> mPackageToSessionData = new ArrayMap<>();
 
+    /**
+     * Mapping from camera ID to open camera app associations. Key is the camera id, value is the
+     * information of the app's uid and package name.
+     */
+    @GuardedBy("mLock")
+    private ArrayMap<String, OpenCameraInfo> mAppsToBlockOnVirtualDevice = new ArrayMap<>();
+
     static class InjectionSessionData {
         public int appUid;
         public ArrayMap<String, CameraInjectionSession> cameraIdToSession = new ArrayMap<>();
     }
 
+    static class OpenCameraInfo {
+        public String packageName;
+        public int packageUid;
+    }
+
     interface CameraAccessBlockedCallback {
         /**
          * Called whenever an app was blocked from accessing a camera.
@@ -98,6 +112,33 @@
         }
     }
 
+    /**
+     * Need to block camera access for applications running on virtual displays.
+     * <p>
+     * Apps that open the camera on the main display will need to block camera access if moved to a
+     * virtual display.
+     *
+     * @param runningUids uids of the application running on the virtual display
+     */
+    public void blockCameraAccessIfNeeded(Set<Integer> runningUids) {
+        synchronized (mLock) {
+            for (int i = 0; i < mAppsToBlockOnVirtualDevice.size(); i++) {
+                final String cameraId = mAppsToBlockOnVirtualDevice.keyAt(i);
+                final OpenCameraInfo openCameraInfo = mAppsToBlockOnVirtualDevice.get(cameraId);
+                int packageUid = openCameraInfo.packageUid;
+                if (runningUids.contains(packageUid)) {
+                    final String packageName = openCameraInfo.packageName;
+                    InjectionSessionData data = mPackageToSessionData.get(packageName);
+                    if (data == null) {
+                        data = new InjectionSessionData();
+                        data.appUid = packageUid;
+                        mPackageToSessionData.put(packageName, data);
+                    }
+                    startBlocking(packageName, cameraId);
+                }
+            }
+        }
+    }
 
     @Override
     public void close() {
@@ -115,10 +156,13 @@
     public void onCameraOpened(@NonNull String cameraId, @NonNull String packageName) {
         synchronized (mLock) {
             try {
-                final ApplicationInfo ainfo =
-                        mPackageManager.getApplicationInfo(packageName, 0);
+                final ApplicationInfo ainfo = mPackageManager.getApplicationInfo(packageName, 0);
                 InjectionSessionData data = mPackageToSessionData.get(packageName);
                 if (!mVirtualDeviceManagerInternal.isAppRunningOnAnyVirtualDevice(ainfo.uid)) {
+                    OpenCameraInfo openCameraInfo = new OpenCameraInfo();
+                    openCameraInfo.packageName = packageName;
+                    openCameraInfo.packageUid = ainfo.uid;
+                    mAppsToBlockOnVirtualDevice.put(cameraId, openCameraInfo);
                     CameraInjectionSession existingSession =
                             (data != null) ? data.cameraIdToSession.get(cameraId) : null;
                     if (existingSession != null) {
@@ -149,6 +193,7 @@
     @Override
     public void onCameraClosed(@NonNull String cameraId) {
         synchronized (mLock) {
+            mAppsToBlockOnVirtualDevice.remove(cameraId);
             for (int i = mPackageToSessionData.size() - 1; i >= 0; i--) {
                 InjectionSessionData data = mPackageToSessionData.valueAt(i);
                 CameraInjectionSession session = data.cameraIdToSession.get(cameraId);
@@ -168,6 +213,9 @@
      */
     private void startBlocking(String packageName, String cameraId) {
         try {
+            Slog.d(
+                    TAG,
+                    "startBlocking() cameraId: " + cameraId + " packageName: " + packageName);
             mCameraManager.injectCamera(packageName, cameraId, /* externalCamId */ "",
                     mContext.getMainExecutor(),
                     new CameraInjectionSession.InjectionStatusCallback() {
diff --git a/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java b/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java
index 27de8cd..0b43744 100644
--- a/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java
+++ b/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java
@@ -93,9 +93,8 @@
     final ArraySet<Integer> mRunningUids = new ArraySet<>();
     @Nullable private final ActivityListener mActivityListener;
     private final Handler mHandler = new Handler(Looper.getMainLooper());
-
-    @Nullable
-    private RunningAppsChangedListener mRunningAppsChangedListener;
+    private final ArraySet<RunningAppsChangedListener> mRunningAppsChangedListener =
+            new ArraySet<>();
 
     /**
      * Creates a window policy controller that is generic to the different use cases of virtual
@@ -142,9 +141,14 @@
         mActivityListener = activityListener;
     }
 
-    /** Sets listener for running applications change. */
-    public void setRunningAppsChangedListener(@Nullable RunningAppsChangedListener listener) {
-        mRunningAppsChangedListener = listener;
+    /** Register a listener for running applications changes. */
+    public void registerRunningAppsChangedListener(@NonNull RunningAppsChangedListener listener) {
+        mRunningAppsChangedListener.add(listener);
+    }
+
+    /** Unregister a listener for running applications changes. */
+    public void unregisterRunningAppsChangedListener(@NonNull RunningAppsChangedListener listener) {
+        mRunningAppsChangedListener.remove(listener);
     }
 
     @Override
@@ -237,9 +241,11 @@
                 mHandler.post(() -> mActivityListener.onDisplayEmpty(Display.INVALID_DISPLAY));
             }
         }
-        if (mRunningAppsChangedListener != null) {
-            mRunningAppsChangedListener.onRunningAppsChanged(runningUids);
-        }
+        mHandler.post(() -> {
+            for (RunningAppsChangedListener listener : mRunningAppsChangedListener) {
+                listener.onRunningAppsChanged(runningUids);
+            }
+        });
     }
 
     /**
diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
index 90c879a..de14ef6 100644
--- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
+++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
@@ -68,16 +68,18 @@
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.app.BlockedAppStreamingActivity;
+import com.android.server.companion.virtual.GenericWindowPolicyController.RunningAppsChangedListener;
 import com.android.server.companion.virtual.audio.VirtualAudioController;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
 import java.util.Map;
 import java.util.Set;
+import java.util.function.Consumer;
 
 
 final class VirtualDeviceImpl extends IVirtualDevice.Stub
-        implements IBinder.DeathRecipient {
+        implements IBinder.DeathRecipient, RunningAppsChangedListener {
 
     private static final String TAG = "VirtualDeviceImpl";
 
@@ -101,6 +103,8 @@
     private final VirtualDeviceParams mParams;
     private final Map<Integer, PowerManager.WakeLock> mPerDisplayWakelocks = new ArrayMap<>();
     private final IVirtualDeviceActivityListener mActivityListener;
+    @NonNull
+    private Consumer<ArraySet<Integer>> mRunningAppsChangedCallback;
     // The default setting for showing the pointer on new displays.
     @GuardedBy("mVirtualDeviceLock")
     private boolean mDefaultShowPointerIcon = true;
@@ -139,21 +143,25 @@
             IBinder token, int ownerUid, OnDeviceCloseListener listener,
             PendingTrampolineCallback pendingTrampolineCallback,
             IVirtualDeviceActivityListener activityListener,
+            Consumer<ArraySet<Integer>> runningAppsChangedCallback,
             VirtualDeviceParams params) {
         this(context, associationInfo, token, ownerUid, /* inputController= */ null, listener,
-                pendingTrampolineCallback, activityListener, params);
+                pendingTrampolineCallback, activityListener, runningAppsChangedCallback, params);
     }
 
     @VisibleForTesting
     VirtualDeviceImpl(Context context, AssociationInfo associationInfo, IBinder token,
             int ownerUid, InputController inputController, OnDeviceCloseListener listener,
             PendingTrampolineCallback pendingTrampolineCallback,
-            IVirtualDeviceActivityListener activityListener, VirtualDeviceParams params) {
+            IVirtualDeviceActivityListener activityListener,
+            Consumer<ArraySet<Integer>> runningAppsChangedCallback,
+            VirtualDeviceParams params) {
         UserHandle ownerUserHandle = UserHandle.getUserHandleForUid(ownerUid);
         mContext = context.createContextAsUser(ownerUserHandle, 0);
         mAssociationInfo = associationInfo;
         mPendingTrampolineCallback = pendingTrampolineCallback;
         mActivityListener = activityListener;
+        mRunningAppsChangedCallback = runningAppsChangedCallback;
         mOwnerUid = ownerUid;
         mAppToken = token;
         mParams = params;
@@ -278,6 +286,11 @@
         close();
     }
 
+    @Override
+    public void onRunningAppsChanged(ArraySet<Integer> runningUids) {
+        mRunningAppsChangedCallback.accept(runningUids);
+    }
+
     @VisibleForTesting
     VirtualAudioController getVirtualAudioControllerForTesting() {
         return mVirtualAudioController;
@@ -529,7 +542,7 @@
             // reentrancy  problems.
             mContext.getMainThreadHandler().post(() -> addWakeLockForDisplay(displayId));
 
-            final GenericWindowPolicyController dwpc =
+            final GenericWindowPolicyController gwpc =
                     new GenericWindowPolicyController(FLAG_SECURE,
                             SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS,
                             getAllowedUserHandles(),
@@ -540,8 +553,9 @@
                             mParams.getDefaultActivityPolicy(),
                             createListenerAdapter(displayId),
                             activityInfo -> onActivityBlocked(displayId, activityInfo));
-            mWindowPolicyControllers.put(displayId, dwpc);
-            return dwpc;
+            gwpc.registerRunningAppsChangedListener(/* listener= */ this);
+            mWindowPolicyControllers.put(displayId, gwpc);
+            return gwpc;
         }
     }
 
@@ -599,6 +613,10 @@
                 wakeLock.release();
                 mPerDisplayWakelocks.remove(displayId);
             }
+            GenericWindowPolicyController gwpc = mWindowPolicyControllers.get(displayId);
+            if (gwpc != null) {
+                gwpc.unregisterRunningAppsChangedListener(/* listener= */ this);
+            }
             mVirtualDisplayIds.remove(displayId);
             mWindowPolicyControllers.remove(displayId);
         }
diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java
index 9acca80..6398b21 100644
--- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java
+++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java
@@ -251,7 +251,10 @@
                                 }
                             }
                         },
-                        this, activityListener, params);
+                        this, activityListener,
+                        runningUids -> cameraAccessController.blockCameraAccessIfNeeded(
+                                runningUids),
+                        params);
                 if (cameraAccessController != null) {
                     cameraAccessController.startObservingIfNeeded();
                 } else {
diff --git a/services/companion/java/com/android/server/companion/virtual/audio/VirtualAudioController.java b/services/companion/java/com/android/server/companion/virtual/audio/VirtualAudioController.java
index 13a47d6..c91877a 100644
--- a/services/companion/java/com/android/server/companion/virtual/audio/VirtualAudioController.java
+++ b/services/companion/java/com/android/server/companion/virtual/audio/VirtualAudioController.java
@@ -85,7 +85,7 @@
             @NonNull IAudioRoutingCallback routingCallback,
             @Nullable IAudioConfigChangedCallback configChangedCallback) {
         mGenericWindowPolicyController = genericWindowPolicyController;
-        mGenericWindowPolicyController.setRunningAppsChangedListener(/* listener= */ this);
+        mGenericWindowPolicyController.registerRunningAppsChangedListener(/* listener= */ this);
         synchronized (mCallbackLock) {
             mRoutingCallback = routingCallback;
             mConfigChangedCallback = configChangedCallback;
@@ -111,7 +111,8 @@
         mAudioPlaybackDetector.unregister();
         mAudioRecordingDetector.unregister();
         if (mGenericWindowPolicyController != null) {
-            mGenericWindowPolicyController.setRunningAppsChangedListener(/* listener= */ null);
+            mGenericWindowPolicyController.unregisterRunningAppsChangedListener(
+                    /* listener= */ this);
             mGenericWindowPolicyController = null;
         }
         synchronized (mCallbackLock) {
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 52577ef..6bce95a 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -49,6 +49,12 @@
 import static android.content.pm.PackageManager.MATCH_SYSTEM_ONLY;
 import static android.content.pm.PackageManager.MATCH_UNINSTALLED_PACKAGES;
 import static android.content.pm.PackageManager.PERMISSION_GRANTED;
+import static android.net.ConnectivityManager.BLOCKED_METERED_REASON_DATA_SAVER;
+import static android.net.ConnectivityManager.BLOCKED_METERED_REASON_USER_RESTRICTED;
+import static android.net.ConnectivityManager.BLOCKED_REASON_APP_STANDBY;
+import static android.net.ConnectivityManager.BLOCKED_REASON_BATTERY_SAVER;
+import static android.net.ConnectivityManager.BLOCKED_REASON_DOZE;
+import static android.net.ConnectivityManager.BLOCKED_REASON_LOW_POWER_STANDBY;
 import static android.net.ConnectivityManager.BLOCKED_REASON_NONE;
 import static android.os.FactoryTest.FACTORY_TEST_OFF;
 import static android.os.IServiceManager.DUMP_FLAG_PRIORITY_CRITICAL;
@@ -17306,8 +17312,22 @@
                 // TODO: We can reuse this data in
                 // ProcessList#incrementProcStateSeqAndNotifyAppsLOSP instead of calling into
                 // NetworkManagementService.
-                return mUidNetworkBlockedReasons.get(uid, BLOCKED_REASON_NONE)
-                        != BLOCKED_REASON_NONE;
+                final int uidBlockedReasons = mUidNetworkBlockedReasons.get(
+                        uid, BLOCKED_REASON_NONE);
+                if (uidBlockedReasons == BLOCKED_REASON_NONE) {
+                    return false;
+                }
+                final int topExemptedBlockedReasons = BLOCKED_REASON_BATTERY_SAVER
+                        | BLOCKED_REASON_DOZE
+                        | BLOCKED_REASON_APP_STANDBY
+                        | BLOCKED_REASON_LOW_POWER_STANDBY
+                        | BLOCKED_METERED_REASON_DATA_SAVER
+                        | BLOCKED_METERED_REASON_USER_RESTRICTED;
+                final int effectiveBlockedReasons =
+                        uidBlockedReasons & ~topExemptedBlockedReasons;
+                // Only consider it as blocked if it is not blocked by a reason
+                // that is not exempted by app being in the top state.
+                return effectiveBlockedReasons == BLOCKED_REASON_NONE;
             }
         }
 
diff --git a/services/core/java/com/android/server/clipboard/ClipboardService.java b/services/core/java/com/android/server/clipboard/ClipboardService.java
index 9f46bd6..3f00244 100644
--- a/services/core/java/com/android/server/clipboard/ClipboardService.java
+++ b/services/core/java/com/android/server/clipboard/ClipboardService.java
@@ -665,7 +665,7 @@
 
     void setPrimaryClipInternal(PerUserClipboard clipboard, @Nullable ClipData clip,
             int uid) {
-        synchronized ("mLock") {
+        synchronized (mLock) {
             setPrimaryClipInternalLocked(clipboard, clip, uid, null);
         }
     }
diff --git a/services/core/java/com/android/server/location/gnss/GnssConfiguration.java b/services/core/java/com/android/server/location/gnss/GnssConfiguration.java
index 5fe7710..12f8776 100644
--- a/services/core/java/com/android/server/location/gnss/GnssConfiguration.java
+++ b/services/core/java/com/android/server/location/gnss/GnssConfiguration.java
@@ -70,9 +70,14 @@
             "USE_EMERGENCY_PDN_FOR_EMERGENCY_SUPL";
     private static final String CONFIG_GPS_LOCK = "GPS_LOCK";
     private static final String CONFIG_ES_EXTENSION_SEC = "ES_EXTENSION_SEC";
-    public static final String CONFIG_NFW_PROXY_APPS = "NFW_PROXY_APPS";
-    public static final String CONFIG_ENABLE_PSDS_PERIODIC_DOWNLOAD =
+    static final String CONFIG_NFW_PROXY_APPS = "NFW_PROXY_APPS";
+    private static final String CONFIG_ENABLE_PSDS_PERIODIC_DOWNLOAD =
             "ENABLE_PSDS_PERIODIC_DOWNLOAD";
+    static final String CONFIG_LONGTERM_PSDS_SERVER_1 = "LONGTERM_PSDS_SERVER_1";
+    static final String CONFIG_LONGTERM_PSDS_SERVER_2 = "LONGTERM_PSDS_SERVER_2";
+    static final String CONFIG_LONGTERM_PSDS_SERVER_3 = "LONGTERM_PSDS_SERVER_3";
+    static final String CONFIG_NORMAL_PSDS_SERVER = "NORMAL_PSDS_SERVER";
+    static final String CONFIG_REALTIME_PSDS_SERVER = "REALTIME_PSDS_SERVER";
     // Limit on NI emergency mode time extension after emergency sessions ends
     private static final int MAX_EMERGENCY_MODE_EXTENSION_SECONDS = 300;  // 5 minute maximum
 
@@ -202,6 +207,15 @@
     }
 
     /**
+     * Returns true if a long-term PSDS server is configured.
+     */
+    boolean isLongTermPsdsServerConfigured() {
+        return (mProperties.getProperty(CONFIG_LONGTERM_PSDS_SERVER_1) != null
+                || mProperties.getProperty(CONFIG_LONGTERM_PSDS_SERVER_2) != null
+                || mProperties.getProperty(CONFIG_LONGTERM_PSDS_SERVER_3) != null);
+    }
+
+    /**
      * Updates the GNSS HAL satellite denylist.
      */
     void setSatelliteBlocklist(int[] constellations, int[] svids) {
diff --git a/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java b/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java
index dae2fbb..ea99e79 100644
--- a/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java
+++ b/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java
@@ -1574,6 +1574,8 @@
         pw.print(mGnssMetrics.dumpGnssMetricsAsText());
         if (dumpAll) {
             pw.println("mSupportsPsds=" + mSupportsPsds);
+            pw.println(
+                    "PsdsServerConfigured=" + mGnssConfiguration.isLongTermPsdsServerConfigured());
             pw.println("native internal state: ");
             pw.println("  " + mGnssNative.getInternalState());
         }
diff --git a/services/core/java/com/android/server/location/gnss/GnssManagerService.java b/services/core/java/com/android/server/location/gnss/GnssManagerService.java
index 0f9945c..69385a9 100644
--- a/services/core/java/com/android/server/location/gnss/GnssManagerService.java
+++ b/services/core/java/com/android/server/location/gnss/GnssManagerService.java
@@ -319,7 +319,7 @@
         }
 
         if (mGnssAntennaInfoProvider.isSupported()) {
-            ipw.println("Navigation Message Provider:");
+            ipw.println("Antenna Info Provider:");
             ipw.increaseIndent();
             ipw.println("Antenna Infos: " + mGnssAntennaInfoProvider.getAntennaInfos());
             mGnssAntennaInfoProvider.dump(fd, ipw, args);
diff --git a/services/core/java/com/android/server/location/gnss/GnssPsdsDownloader.java b/services/core/java/com/android/server/location/gnss/GnssPsdsDownloader.java
index dce9a12..243910d 100644
--- a/services/core/java/com/android/server/location/gnss/GnssPsdsDownloader.java
+++ b/services/core/java/com/android/server/location/gnss/GnssPsdsDownloader.java
@@ -61,9 +61,12 @@
     GnssPsdsDownloader(Properties properties) {
         // read PSDS servers from the Properties object
         int count = 0;
-        String longTermPsdsServer1 = properties.getProperty("LONGTERM_PSDS_SERVER_1");
-        String longTermPsdsServer2 = properties.getProperty("LONGTERM_PSDS_SERVER_2");
-        String longTermPsdsServer3 = properties.getProperty("LONGTERM_PSDS_SERVER_3");
+        String longTermPsdsServer1 = properties.getProperty(
+                GnssConfiguration.CONFIG_LONGTERM_PSDS_SERVER_1);
+        String longTermPsdsServer2 = properties.getProperty(
+                GnssConfiguration.CONFIG_LONGTERM_PSDS_SERVER_2);
+        String longTermPsdsServer3 = properties.getProperty(
+                GnssConfiguration.CONFIG_LONGTERM_PSDS_SERVER_3);
         if (longTermPsdsServer1 != null) count++;
         if (longTermPsdsServer2 != null) count++;
         if (longTermPsdsServer3 != null) count++;
@@ -83,8 +86,10 @@
             mNextServerIndex = random.nextInt(count);
         }
 
-        String normalPsdsServer = properties.getProperty("NORMAL_PSDS_SERVER");
-        String realtimePsdsServer = properties.getProperty("REALTIME_PSDS_SERVER");
+        String normalPsdsServer = properties.getProperty(
+                GnssConfiguration.CONFIG_NORMAL_PSDS_SERVER);
+        String realtimePsdsServer = properties.getProperty(
+                GnssConfiguration.CONFIG_REALTIME_PSDS_SERVER);
         mPsdsServers = new String[MAX_PSDS_TYPE_INDEX + 1];
         mPsdsServers[NORMAL_PSDS_SERVER_INDEX] = normalPsdsServer;
         mPsdsServers[REALTIME_PSDS_SERVER_INDEX] = realtimePsdsServer;
diff --git a/services/core/java/com/android/server/notification/BubbleExtractor.java b/services/core/java/com/android/server/notification/BubbleExtractor.java
index 41e067e..a561390 100644
--- a/services/core/java/com/android/server/notification/BubbleExtractor.java
+++ b/services/core/java/com/android/server/notification/BubbleExtractor.java
@@ -32,6 +32,7 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.ActivityInfo;
+import android.content.res.Resources;
 import android.util.Slog;
 
 import com.android.internal.annotations.VisibleForTesting;
@@ -49,10 +50,15 @@
     private ActivityManager mActivityManager;
     private Context mContext;
 
+    boolean mSupportsBubble;
+
     public void initialize(Context context, NotificationUsageStats usageStats) {
         if (DBG) Slog.d(TAG, "Initializing  " + getClass().getSimpleName() + ".");
         mContext = context;
         mActivityManager = (ActivityManager) mContext.getSystemService(Context.ACTIVITY_SERVICE);
+
+        mSupportsBubble = Resources.getSystem().getBoolean(
+                com.android.internal.R.bool.config_supportsBubble);
     }
 
     public RankingReconsideration process(NotificationRecord record) {
@@ -138,6 +144,10 @@
      */
     @VisibleForTesting
     boolean canPresentAsBubble(NotificationRecord r) {
+        if (!mSupportsBubble) {
+            return false;
+        }
+
         Notification notification = r.getNotification();
         Notification.BubbleMetadata metadata = notification.getBubbleMetadata();
         String pkg = r.getSbn().getPackageName();
diff --git a/services/core/java/com/android/server/notification/NotificationRecord.java b/services/core/java/com/android/server/notification/NotificationRecord.java
index bbdea32..f979343 100644
--- a/services/core/java/com/android/server/notification/NotificationRecord.java
+++ b/services/core/java/com/android/server/notification/NotificationRecord.java
@@ -1348,14 +1348,14 @@
     protected void calculateGrantableUris() {
         final Notification notification = getNotification();
         notification.visitUris((uri) -> {
-            visitGrantableUri(uri, false);
+            visitGrantableUri(uri, false, false);
         });
 
         if (notification.getChannelId() != null) {
             NotificationChannel channel = getChannel();
             if (channel != null) {
                 visitGrantableUri(channel.getSound(), (channel.getUserLockedFields()
-                        & NotificationChannel.USER_LOCKED_SOUND) != 0);
+                        & NotificationChannel.USER_LOCKED_SOUND) != 0, true);
             }
         }
     }
@@ -1368,7 +1368,7 @@
      * {@link #mGrantableUris}. Otherwise, this will either log or throw
      * {@link SecurityException} depending on target SDK of enqueuing app.
      */
-    private void visitGrantableUri(Uri uri, boolean userOverriddenUri) {
+    private void visitGrantableUri(Uri uri, boolean userOverriddenUri, boolean isSound) {
         if (uri == null || !ContentResolver.SCHEME_CONTENT.equals(uri.getScheme())) return;
 
         // We can't grant Uri permissions from system
@@ -1389,10 +1389,16 @@
             mGrantableUris.add(uri);
         } catch (SecurityException e) {
             if (!userOverriddenUri) {
-                if (mTargetSdkVersion >= Build.VERSION_CODES.P) {
-                    throw e;
+                if (isSound) {
+                    mSound = Settings.System.DEFAULT_NOTIFICATION_URI;
+                    Log.w(TAG, "Replacing " + uri + " from " + sourceUid + ": " + e.getMessage());
                 } else {
-                    Log.w(TAG, "Ignoring " + uri + " from " + sourceUid + ": " + e.getMessage());
+                    if (mTargetSdkVersion >= Build.VERSION_CODES.P) {
+                        throw e;
+                    } else {
+                        Log.w(TAG,
+                                "Ignoring " + uri + " from " + sourceUid + ": " + e.getMessage());
+                    }
                 }
             }
         } finally {
diff --git a/services/core/java/com/android/server/policy/PermissionPolicyService.java b/services/core/java/com/android/server/policy/PermissionPolicyService.java
index e8546a7..32e7a6a 100644
--- a/services/core/java/com/android/server/policy/PermissionPolicyService.java
+++ b/services/core/java/com/android/server/policy/PermissionPolicyService.java
@@ -59,6 +59,8 @@
 import android.content.pm.PermissionInfo;
 import android.os.Build;
 import android.os.Bundle;
+import android.os.Handler;
+import android.os.Looper;
 import android.os.Process;
 import android.os.RemoteException;
 import android.os.ServiceManager;
@@ -160,11 +162,13 @@
     private NotificationManagerInternal mNotificationManager;
     private final KeyguardManager mKeyguardManager;
     private final PackageManager mPackageManager;
+    private final Handler mHandler;
 
     public PermissionPolicyService(@NonNull Context context) {
         super(context);
 
         mContext = context;
+        mHandler = new Handler(Looper.getMainLooper());
         mPackageManager = context.getPackageManager();
         mKeyguardManager = context.getSystemService(KeyguardManager.class);
         LocalServices.addService(PermissionPolicyInternal.class, new Internal());
@@ -1068,8 +1072,11 @@
                                 activityInfo.packageName, user)) {
                             clearNotificationReviewFlagsIfNeeded(activityInfo.packageName, user);
                         } else {
-                            showNotificationPromptIfNeeded(activityInfo.packageName,
-                                    taskInfo.userId, taskInfo.taskId, info);
+                            // Post the activity start checks to ensure the notification channel
+                            // checks happen outside the WindowManager global lock.
+                            mHandler.post(() -> showNotificationPromptIfNeeded(
+                                    activityInfo.packageName, taskInfo.userId, taskInfo.taskId,
+                                    info));
                         }
                     }
                 };
diff --git a/services/core/java/com/android/server/wm/ActivityInterceptorCallback.java b/services/core/java/com/android/server/wm/ActivityInterceptorCallback.java
index d2053fa..400460a1 100644
--- a/services/core/java/com/android/server/wm/ActivityInterceptorCallback.java
+++ b/services/core/java/com/android/server/wm/ActivityInterceptorCallback.java
@@ -29,7 +29,8 @@
 import java.lang.annotation.RetentionPolicy;
 
 /**
- * Callback to intercept activity starts and possibly block/redirect them.
+ * Callback to intercept activity starts and possibly block/redirect them. The callback methods will
+ * be called with the WindowManagerGlobalLock held.
  */
 public abstract class ActivityInterceptorCallback {
     /**
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 2be9b34..dc4e117 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -3967,6 +3967,8 @@
         } else {
             onRemovedFromDisplay();
         }
+        mActivityRecordInputSink.releaseSurfaceControl();
+
         super.removeImmediately();
     }
 
@@ -5551,7 +5553,7 @@
      * this activity when embedded in untrusted mode.
      */
     boolean hasOverlayOverUntrustedModeEmbedded() {
-        if (!isEmbeddedInUntrustedMode() || getRootTask() == null) {
+        if (!isEmbeddedInUntrustedMode() || getTask() == null) {
             // The activity is not embedded in untrusted mode.
             return false;
         }
@@ -5559,7 +5561,7 @@
         // Check if there are any activities with different UID over the activity that is embedded
         // in untrusted mode. Traverse bottom to top with boundary so that it will only check
         // activities above this activity.
-        final ActivityRecord differentUidOverlayActivity = getRootTask().getActivity(
+        final ActivityRecord differentUidOverlayActivity = getTask().getActivity(
                 a -> a.getUid() != getUid(), this /* boundary */, false /* includeBoundary */,
                 false /* traverseTopToBottom */);
         return differentUidOverlayActivity != null;
@@ -6298,7 +6300,7 @@
         // starting window is drawn, the transition can start earlier. Exclude finishing and bubble
         // because it may be a trampoline.
         if (!wasTaskVisible && mStartingData != null && !finishing && !mLaunchedFromBubble
-                && !mDisplayContent.mAppTransition.isReady()
+                && mVisibleRequested && !mDisplayContent.mAppTransition.isReady()
                 && !mDisplayContent.mAppTransition.isRunning()
                 && mDisplayContent.isNextTransitionForward()) {
             // The pending transition state will be cleared after the transition is started, so
diff --git a/services/core/java/com/android/server/wm/ActivityRecordInputSink.java b/services/core/java/com/android/server/wm/ActivityRecordInputSink.java
index ce49a86..23a8324 100644
--- a/services/core/java/com/android/server/wm/ActivityRecordInputSink.java
+++ b/services/core/java/com/android/server/wm/ActivityRecordInputSink.java
@@ -99,4 +99,11 @@
         return inputWindowHandle;
     }
 
+    void releaseSurfaceControl() {
+        if (mSurfaceControl != null) {
+            mSurfaceControl.release();
+            mSurfaceControl = null;
+        }
+    }
+
 }
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index eb912d4..36a7c77 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -2481,6 +2481,12 @@
             if (inTaskFragment == null) {
                 inTaskFragment = TaskFragment.fromTaskFragmentToken(
                         mOptions.getLaunchTaskFragmentToken(), mService);
+                if (inTaskFragment != null && inTaskFragment.isEmbeddedTaskFragmentInPip()) {
+                    // Do not start activity in TaskFragment in a PIP Task.
+                    Slog.w(TAG, "Can not start activity in TaskFragment in PIP: "
+                            + inTaskFragment);
+                    inTaskFragment = null;
+                }
             }
         }
 
diff --git a/services/core/java/com/android/server/wm/ConfigurationContainer.java b/services/core/java/com/android/server/wm/ConfigurationContainer.java
index 08a9da4..dbc08cd 100644
--- a/services/core/java/com/android/server/wm/ConfigurationContainer.java
+++ b/services/core/java/com/android/server/wm/ConfigurationContainer.java
@@ -190,6 +190,14 @@
      * @see #mFullConfiguration
      */
     public void onRequestedOverrideConfigurationChanged(Configuration overrideConfiguration) {
+        updateRequestedOverrideConfiguration(overrideConfiguration);
+        // Update full configuration of this container and all its children.
+        final ConfigurationContainer parent = getParent();
+        onConfigurationChanged(parent != null ? parent.getConfiguration() : Configuration.EMPTY);
+    }
+
+    /** Updates override configuration without recalculate full config. */
+    void updateRequestedOverrideConfiguration(Configuration overrideConfiguration) {
         // Pre-compute this here, so we don't need to go through the entire Configuration when
         // writing to proto (which has significant cost if we write a lot of empty configurations).
         mHasOverrideConfiguration = !Configuration.EMPTY.equals(overrideConfiguration);
@@ -199,9 +207,6 @@
                 && diffRequestedOverrideMaxBounds(newBounds) != BOUNDS_CHANGE_NONE) {
             mRequestedOverrideConfiguration.windowConfiguration.setMaxBounds(newBounds);
         }
-        // Update full configuration of this container and all its children.
-        final ConfigurationContainer parent = getParent();
-        onConfigurationChanged(parent != null ? parent.getConfiguration() : Configuration.EMPTY);
     }
 
     /**
diff --git a/services/core/java/com/android/server/wm/DisplayAreaPolicy.java b/services/core/java/com/android/server/wm/DisplayAreaPolicy.java
index 5919806..c18377d 100644
--- a/services/core/java/com/android/server/wm/DisplayAreaPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayAreaPolicy.java
@@ -23,6 +23,7 @@
 import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR;
 import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL;
 import static android.view.WindowManager.LayoutParams.TYPE_NOTIFICATION_SHADE;
+import static android.view.WindowManager.LayoutParams.TYPE_SECURE_SYSTEM_OVERLAY;
 import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR;
 import static android.window.DisplayAreaOrganizer.FEATURE_DEFAULT_TASK_CONTAINER;
 import static android.window.DisplayAreaOrganizer.FEATURE_FULLSCREEN_MAGNIFICATION;
@@ -139,7 +140,8 @@
                         .addFeature(new Feature.Builder(wmService.mPolicy, "OneHanded",
                                 FEATURE_ONE_HANDED)
                                 .all()
-                                .except(TYPE_NAVIGATION_BAR, TYPE_NAVIGATION_BAR_PANEL)
+                                .except(TYPE_NAVIGATION_BAR, TYPE_NAVIGATION_BAR_PANEL,
+                                        TYPE_SECURE_SYSTEM_OVERLAY)
                                 .build());
             }
             rootHierarchy
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index 079e1ac..3cb9a56 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -24,6 +24,7 @@
 import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
 import static android.content.pm.ActivityInfo.LAUNCH_SINGLE_INSTANCE;
 import static android.content.pm.ActivityInfo.LAUNCH_SINGLE_TASK;
+import static android.content.res.Configuration.EMPTY;
 import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
 import static android.view.Display.DEFAULT_DISPLAY;
 import static android.view.Display.INVALID_DISPLAY;
@@ -2005,7 +2006,8 @@
             // of the activity entering PIP
             r.getDisplayContent().prepareAppTransition(TRANSIT_NONE);
 
-            final boolean singleActivity = task.getChildCount() == 1;
+            // TODO: Does it make sense to only count non-finishing activities?
+            final boolean singleActivity = task.getActivityCount() == 1;
             if (singleActivity) {
                 rootTask = task;
 
@@ -2081,6 +2083,15 @@
             // TODO(task-org): Figure-out more structured way to do this long term.
             r.setWindowingMode(intermediateWindowingMode);
             r.mWaitForEnteringPinnedMode = true;
+            rootTask.forAllTaskFragments(tf -> {
+                // When the Task is entering picture-in-picture, we should clear all override from
+                // the client organizer, so the PIP activity can get the correct config from the
+                // Task, and prevent conflict with the PipTaskOrganizer.
+                if (tf.isOrganizedTaskFragment()) {
+                    tf.resetAdjacentTaskFragment();
+                    tf.updateRequestedOverrideConfiguration(EMPTY);
+                }
+            });
             rootTask.setWindowingMode(WINDOWING_MODE_PINNED);
             // Set the launch bounds for launch-into-pip Activity on the root task.
             if (r.getOptions() != null && r.getOptions().isLaunchIntoPip()) {
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index a46544d..ac04843 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -1380,6 +1380,14 @@
         return getActivity(ActivityRecord::canBeTopRunning);
     }
 
+    int getActivityCount() {
+        final int[] activityCount = new int[1];
+        forAllActivities(ar -> {
+            activityCount[0]++;
+        });
+        return activityCount[0];
+    }
+
     /**
      * Return true if any activities in this task belongs to input uid.
      */
diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java
index b96b461..83bd979 100644
--- a/services/core/java/com/android/server/wm/TaskFragment.java
+++ b/services/core/java/com/android/server/wm/TaskFragment.java
@@ -339,7 +339,7 @@
         }
     }
 
-    private void resetAdjacentTaskFragment() {
+    void resetAdjacentTaskFragment() {
         // Reset the adjacent TaskFragment if its adjacent TaskFragment is also this TaskFragment.
         if (mAdjacentTaskFragment != null && mAdjacentTaskFragment.mAdjacentTaskFragment == this) {
             mAdjacentTaskFragment.mAdjacentTaskFragment = null;
@@ -2317,6 +2317,14 @@
         mMinHeight = minHeight;
     }
 
+    /**
+     * Whether this is an embedded TaskFragment in PIP Task. We don't allow any client config
+     * override for such TaskFragment to prevent flight with PipTaskOrganizer.
+     */
+    boolean isEmbeddedTaskFragmentInPip() {
+        return isOrganizedTaskFragment() && getTask() != null && getTask().inPinnedWindowingMode();
+    }
+
     boolean shouldRemoveSelfOnLastChildRemoval() {
         return !mCreatedByOrganizer || mIsRemovalRequested;
     }
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index 5969e85..4dbcea1 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -2761,7 +2761,10 @@
     boolean canStartChangeTransition() {
         return !mWmService.mDisableTransitionAnimation && mDisplayContent != null
                 && getSurfaceControl() != null && !mDisplayContent.inTransition()
-                && isVisible() && isVisibleRequested() && okToAnimate();
+                && isVisible() && isVisibleRequested() && okToAnimate()
+                // Pip animation will be handled by PipTaskOrganizer.
+                && !inPinnedWindowingMode() && getParent() != null
+                && !getParent().inPinnedWindowingMode();
     }
 
     /**
@@ -3860,7 +3863,8 @@
                 @AnimationType int type, @Nullable AnimationAdapter snapshotAnim);
     }
 
-    void addTrustedOverlay(SurfaceControlViewHost.SurfacePackage overlay) {
+    void addTrustedOverlay(SurfaceControlViewHost.SurfacePackage overlay,
+            @Nullable WindowState initialWindowState) {
         if (mOverlayHost == null) {
             mOverlayHost = new TrustedOverlayHost(mWmService);
         }
@@ -3876,6 +3880,20 @@
                     "Error sending initial configuration change to WindowContainer overlay");
             removeTrustedOverlay(overlay);
         }
+
+        // Emit an initial WindowState so that proper insets are available to overlay views
+        // shortly after the overlay is added.
+        if (initialWindowState != null) {
+            final InsetsState insetsState = initialWindowState.getInsetsState();
+            final Rect dispBounds = getBounds();
+            try {
+                overlay.getRemoteInterface().onInsetsChanged(insetsState, dispBounds);
+            } catch (Exception e) {
+                ProtoLog.e(WM_DEBUG_ANIM,
+                        "Error sending initial insets change to WindowContainer overlay");
+                removeTrustedOverlay(overlay);
+            }
+        }
     }
 
     void removeTrustedOverlay(SurfaceControlViewHost.SurfacePackage overlay) {
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 77d31df..4d262ef 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -8135,7 +8135,7 @@
                 if (task == null) {
                     throw new IllegalArgumentException("no task with taskId" + taskId);
                 }
-                task.addTrustedOverlay(overlay);
+                task.addTrustedOverlay(overlay, task.getTopVisibleAppMainWindow());
             }
         }
 
diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java
index b5cf708..c1c8b81 100644
--- a/services/core/java/com/android/server/wm/WindowOrganizerController.java
+++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java
@@ -677,15 +677,21 @@
             }
             case HIERARCHY_OP_TYPE_START_ACTIVITY_IN_TASK_FRAGMENT: {
                 final IBinder fragmentToken = hop.getContainer();
-                if (!mLaunchTaskFragments.containsKey(fragmentToken)) {
+                final TaskFragment tf = mLaunchTaskFragments.get(fragmentToken);
+                if (tf == null) {
                     final Throwable exception = new IllegalArgumentException(
                             "Not allowed to operate with invalid fragment token");
                     sendTaskFragmentOperationFailure(organizer, errorCallbackToken, exception);
                     break;
                 }
+                if (tf.isEmbeddedTaskFragmentInPip()) {
+                    final Throwable exception = new IllegalArgumentException(
+                            "Not allowed to start activity in PIP TaskFragment");
+                    sendTaskFragmentOperationFailure(organizer, errorCallbackToken, exception);
+                    break;
+                }
                 final Intent activityIntent = hop.getActivityIntent();
                 final Bundle activityOptions = hop.getLaunchOptions();
-                final TaskFragment tf = mLaunchTaskFragments.get(fragmentToken);
                 final int result = mService.getActivityStartController()
                         .startActivityInTaskFragment(tf, activityIntent, activityOptions,
                                 hop.getCallingActivity(), caller.mUid, caller.mPid);
@@ -707,6 +713,12 @@
                     sendTaskFragmentOperationFailure(organizer, errorCallbackToken, exception);
                     break;
                 }
+                if (parent.isEmbeddedTaskFragmentInPip()) {
+                    final Throwable exception = new IllegalArgumentException(
+                            "Not allowed to reparent activity to PIP TaskFragment");
+                    sendTaskFragmentOperationFailure(organizer, errorCallbackToken, exception);
+                    break;
+                }
                 if (!parent.isAllowedToEmbedActivity(activity)) {
                     final Throwable exception = new SecurityException(
                             "The task fragment is not trusted to embed the given activity.");
@@ -730,6 +742,13 @@
                     sendTaskFragmentOperationFailure(organizer, errorCallbackToken, exception);
                     break;
                 }
+                if (tf1.isEmbeddedTaskFragmentInPip()
+                        || (tf2 != null && tf2.isEmbeddedTaskFragmentInPip())) {
+                    final Throwable exception = new IllegalArgumentException(
+                            "Not allowed to set adjacent on TaskFragment in PIP Task");
+                    sendTaskFragmentOperationFailure(organizer, errorCallbackToken, exception);
+                    break;
+                }
                 tf1.setAdjacentTaskFragment(tf2, false /* moveAdjacentTogether */);
                 effects |= TRANSACT_EFFECTS_LIFECYCLE;
 
@@ -1092,6 +1111,10 @@
             throw new IllegalArgumentException("setAdjacentRootsHierarchyOp: Not created by"
                     + " organizer root1=" + root1 + " root2=" + root2);
         }
+        if (root1.isEmbeddedTaskFragmentInPip() || root2.isEmbeddedTaskFragmentInPip()) {
+            Slog.e(TAG, "Attempt to set adjacent TaskFragment in PIP Task");
+            return 0;
+        }
         root1.setAdjacentTaskFragment(root2, hop.getMoveAdjacentTogether());
         return TRANSACT_EFFECTS_LIFECYCLE;
     }
@@ -1105,6 +1128,10 @@
     private int applyWindowContainerChange(WindowContainer wc,
             WindowContainerTransaction.Change c) {
         sanitizeWindowContainer(wc);
+        if (wc.asTaskFragment() != null && wc.asTaskFragment().isEmbeddedTaskFragmentInPip()) {
+            // No override from organizer for embedded TaskFragment in a PIP Task.
+            return 0;
+        }
 
         int effects = applyChanges(wc, c);
 
@@ -1420,21 +1447,28 @@
             return;
         }
         // The ownerActivity has to belong to the same app as the target Task.
-        if (ownerActivity.getTask().effectiveUid != ownerActivity.getUid()
-                || ownerActivity.getTask().effectiveUid != caller.mUid) {
+        final Task ownerTask = ownerActivity.getTask();
+        if (ownerTask.effectiveUid != ownerActivity.getUid()
+                || ownerTask.effectiveUid != caller.mUid) {
             final Throwable exception =
                     new SecurityException("Not allowed to operate with the ownerToken while "
                             + "the root activity of the target task belong to the different app");
             sendTaskFragmentOperationFailure(organizer, errorCallbackToken, exception);
             return;
         }
+        if (ownerTask.inPinnedWindowingMode()) {
+            final Throwable exception = new IllegalArgumentException(
+                    "Not allowed to create TaskFragment in PIP Task");
+            sendTaskFragmentOperationFailure(organizer, errorCallbackToken, exception);
+            return;
+        }
         final TaskFragment taskFragment = new TaskFragment(mService,
                 creationParams.getFragmentToken(), true /* createdByOrganizer */);
         // Set task fragment organizer immediately, since it might have to be notified about further
         // actions.
         taskFragment.setTaskFragmentOrganizer(creationParams.getOrganizer(),
                 ownerActivity.getUid(), ownerActivity.info.processName);
-        ownerActivity.getTask().addChild(taskFragment, POSITION_TOP);
+        ownerTask.addChild(taskFragment, POSITION_TOP);
         taskFragment.setWindowingMode(creationParams.getWindowingMode());
         taskFragment.setBounds(creationParams.getInitialBounds());
         mLaunchTaskFragments.put(creationParams.getFragmentToken(), taskFragment);
@@ -1467,6 +1501,12 @@
                 return;
             }
         }
+        if (newParentTF.isEmbeddedTaskFragmentInPip() || oldParent.isEmbeddedTaskFragmentInPip()) {
+            final Throwable exception = new SecurityException(
+                    "Not allow to reparent in TaskFragment in PIP Task.");
+            sendTaskFragmentOperationFailure(organizer, errorCallbackToken, exception);
+            return;
+        }
         while (oldParent.hasChild()) {
             oldParent.getChildAt(0).reparent(newParentTF, POSITION_TOP);
         }
@@ -1482,6 +1522,12 @@
             sendTaskFragmentOperationFailure(organizer, errorCallbackToken, exception);
             return 0;
         }
+        if (taskFragment.isEmbeddedTaskFragmentInPip()) {
+            final Throwable exception = new IllegalArgumentException(
+                    "Not allowed to delete TaskFragment in PIP Task");
+            sendTaskFragmentOperationFailure(organizer, errorCallbackToken, exception);
+            return 0;
+        }
         mLaunchTaskFragments.removeAt(index);
         taskFragment.remove(true /* withTransition */, "deleteTaskFragment");
         return TRANSACT_EFFECTS_LIFECYCLE;
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index b7005a5..e0fb562 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -1781,7 +1781,7 @@
 
     @VisibleForTesting
     DevicePolicyManagerService(Injector injector) {
-        DevicePolicyManager.disableGetKeyguardDisabledFeaturesCache();
+        DevicePolicyManager.disableLocalCaches();
 
         mInjector = injector;
         mContext = Objects.requireNonNull(injector.mContext);
diff --git a/services/tests/mockingservicestests/src/com/android/server/companion/virtual/CameraAccessControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/companion/virtual/CameraAccessControllerTest.java
index 1b9cb28..c4c3abc 100644
--- a/services/tests/mockingservicestests/src/com/android/server/companion/virtual/CameraAccessControllerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/companion/virtual/CameraAccessControllerTest.java
@@ -24,6 +24,7 @@
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
@@ -34,6 +35,7 @@
 import android.hardware.camera2.CameraManager;
 import android.os.Process;
 import android.testing.TestableContext;
+import android.util.ArraySet;
 
 import androidx.test.InstrumentationRegistry;
 import androidx.test.ext.junit.runners.AndroidJUnit4;
@@ -73,11 +75,11 @@
 
     private ApplicationInfo mTestAppInfo = new ApplicationInfo();
     private ApplicationInfo mOtherAppInfo = new ApplicationInfo();
+    private ArraySet<Integer> mRunningUids = new ArraySet<>();
 
     @Captor
     ArgumentCaptor<CameraInjectionSession.InjectionStatusCallback> mInjectionCallbackCaptor;
 
-
     @Before
     public void setUp() throws PackageManager.NameNotFoundException {
         MockitoAnnotations.initMocks(this);
@@ -89,6 +91,7 @@
                 mBlockedCallback);
         mTestAppInfo.uid = Process.FIRST_APPLICATION_UID;
         mOtherAppInfo.uid = Process.FIRST_APPLICATION_UID + 1;
+        mRunningUids.add(Process.FIRST_APPLICATION_UID);
         when(mPackageManager.getApplicationInfo(eq(TEST_APP_PACKAGE), anyInt())).thenReturn(
                 mTestAppInfo);
         when(mPackageManager.getApplicationInfo(eq(OTHER_APP_PACKAGE), anyInt())).thenReturn(
@@ -104,7 +107,6 @@
         verify(mCameraManager, never()).injectCamera(any(), any(), any(), any(), any());
     }
 
-
     @Test
     public void onCameraOpened_uidRunning_cameraBlocked() throws CameraAccessException {
         when(mDeviceManagerInternal.isAppRunningOnAnyVirtualDevice(
@@ -128,7 +130,6 @@
         verify(session).close();
     }
 
-
     @Test
     public void onCameraClosed_otherCameraClosed_cameraNotUnblocked() throws CameraAccessException {
         when(mDeviceManagerInternal.isAppRunningOnAnyVirtualDevice(
@@ -197,4 +198,33 @@
         mInjectionCallbackCaptor.getValue().onInjectionError(ERROR_INJECTION_UNSUPPORTED);
         verify(mBlockedCallback).onCameraAccessBlocked(eq(mTestAppInfo.uid));
     }
+
+    @Test
+    public void twoCameraAccessesBySameUid_secondOnVirtualDisplay_noCallbackButCameraCanBlocked()
+            throws CameraAccessException {
+        when(mDeviceManagerInternal.isAppRunningOnAnyVirtualDevice(
+                eq(mTestAppInfo.uid))).thenReturn(false);
+        mController.onCameraOpened(FRONT_CAMERA, TEST_APP_PACKAGE);
+        mController.blockCameraAccessIfNeeded(mRunningUids);
+
+        verify(mCameraManager).injectCamera(eq(TEST_APP_PACKAGE), eq(FRONT_CAMERA), anyString(),
+                any(), mInjectionCallbackCaptor.capture());
+        CameraInjectionSession session = mock(CameraInjectionSession.class);
+        mInjectionCallbackCaptor.getValue().onInjectionSucceeded(session);
+        mInjectionCallbackCaptor.getValue().onInjectionError(ERROR_INJECTION_UNSUPPORTED);
+        verify(mBlockedCallback).onCameraAccessBlocked(eq(mTestAppInfo.uid));
+    }
+
+    @Test
+    public void twoCameraAccessesBySameUid_secondOnVirtualDisplay_firstCloseThenOpenCameraUnblock()
+            throws CameraAccessException {
+        when(mDeviceManagerInternal.isAppRunningOnAnyVirtualDevice(
+                eq(mTestAppInfo.uid))).thenReturn(false);
+        mController.onCameraOpened(FRONT_CAMERA, TEST_APP_PACKAGE);
+        mController.blockCameraAccessIfNeeded(mRunningUids);
+        mController.onCameraClosed(FRONT_CAMERA);
+        mController.onCameraOpened(FRONT_CAMERA, TEST_APP_PACKAGE);
+
+        verify(mCameraManager, times(1)).injectCamera(any(), any(), any(), any(), any());
+    }
 }
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationControllerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationControllerTest.java
index ca22f80..eac8671 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationControllerTest.java
@@ -198,6 +198,31 @@
     }
 
     @Test
+    public void transitionToWindowModeFailedByReset_fullScreenMagnifying_notifyTransitionFailed()
+            throws RemoteException {
+        setMagnificationEnabled(MODE_FULLSCREEN);
+
+        mMagnificationController.transitionMagnificationModeLocked(TEST_DISPLAY,
+                MODE_WINDOW,
+                mTransitionCallBack);
+
+        verify(mScreenMagnificationController).reset(eq(TEST_DISPLAY),
+                mCallbackArgumentCaptor.capture());
+        // The transition is interrupted and failed by calling reset.
+        mCallbackArgumentCaptor.getValue().onResult(false);
+        verify(mTransitionCallBack).onResult(TEST_DISPLAY, false);
+        final ArgumentCaptor<MagnificationConfig> configCaptor = ArgumentCaptor.forClass(
+                MagnificationConfig.class);
+        // The first time is for notifying full-screen enabled and the second time is for notifying
+        // the target mode transitions failed.
+        verify(mService, times(2)).notifyMagnificationChanged(eq(TEST_DISPLAY), any(Region.class),
+                configCaptor.capture());
+        final MagnificationConfig actualConfig = configCaptor.getValue();
+        assertEquals(MODE_FULLSCREEN, actualConfig.getMode(), 0);
+        assertEquals(1.0f, actualConfig.getScale(), 0);
+    }
+
+    @Test
     public void transitionToWindowMode_disablingWindowMode_enablingWindowWithFormerCenter()
             throws RemoteException {
         setMagnificationEnabled(MODE_WINDOW);
@@ -479,6 +504,92 @@
     }
 
     @Test
+    public void transitionMagnificationMode_windowEnabled_notifyTargetMagnificationChanged()
+            throws RemoteException {
+        setMagnificationEnabled(MODE_WINDOW);
+
+        mMagnificationController.transitionMagnificationModeLocked(TEST_DISPLAY,
+                MODE_FULLSCREEN, mTransitionCallBack);
+        mMockConnection.invokeCallbacks();
+
+        final ArgumentCaptor<MagnificationConfig> configCaptor = ArgumentCaptor.forClass(
+                MagnificationConfig.class);
+        // The first time is for notifying window enabled and the second time is for notifying
+        // the target mode transitions.
+        verify(mService, times(2)).notifyMagnificationChanged(eq(TEST_DISPLAY), any(Region.class),
+                configCaptor.capture());
+        final MagnificationConfig actualConfig = configCaptor.getValue();
+        assertEquals(MODE_FULLSCREEN, actualConfig.getMode(), 0);
+    }
+
+    @Test
+    public void transitionConfigMode_windowEnabled_notifyTargetMagnificationChanged()
+            throws RemoteException {
+        setMagnificationEnabled(MODE_WINDOW);
+
+        final MagnificationConfig config = obtainMagnificationConfig(MODE_FULLSCREEN);
+        mMagnificationController.transitionMagnificationConfigMode(TEST_DISPLAY,
+                config, true, TEST_SERVICE_ID);
+        mMockConnection.invokeCallbacks();
+
+        final ArgumentCaptor<MagnificationConfig> configCaptor = ArgumentCaptor.forClass(
+                MagnificationConfig.class);
+        // The first time is for notifying window enabled and the second time is for notifying
+        // the target mode transitions.
+        verify(mService, times(2)).notifyMagnificationChanged(eq(TEST_DISPLAY), any(Region.class),
+                configCaptor.capture());
+        final MagnificationConfig actualConfig = configCaptor.getValue();
+        assertEquals(config.getCenterX(), actualConfig.getCenterX(), 0);
+        assertEquals(config.getCenterY(), actualConfig.getCenterY(), 0);
+        assertEquals(config.getScale(), actualConfig.getScale(), 0);
+    }
+
+    @Test
+    public void transitionMagnificationMode_fullScreenEnabled_notifyTargetMagnificationChanged()
+            throws RemoteException {
+        setMagnificationEnabled(MODE_FULLSCREEN);
+
+        mMagnificationController.transitionMagnificationModeLocked(TEST_DISPLAY,
+                MODE_WINDOW, mTransitionCallBack);
+        verify(mScreenMagnificationController).reset(eq(TEST_DISPLAY),
+                mCallbackArgumentCaptor.capture());
+        mCallbackArgumentCaptor.getValue().onResult(true);
+        mMockConnection.invokeCallbacks();
+
+        final ArgumentCaptor<MagnificationConfig> configCaptor = ArgumentCaptor.forClass(
+                MagnificationConfig.class);
+        // The first time is for notifying full-screen enabled and the second time is for notifying
+        // the target mode transitions.
+        verify(mService, times(2)).notifyMagnificationChanged(eq(TEST_DISPLAY), any(Region.class),
+                configCaptor.capture());
+        final MagnificationConfig actualConfig = configCaptor.getValue();
+        assertEquals(MODE_WINDOW, actualConfig.getMode(), 0);
+    }
+
+    @Test
+    public void transitionConfigMode_fullScreenEnabled_notifyTargetMagnificationChanged()
+            throws RemoteException {
+        setMagnificationEnabled(MODE_FULLSCREEN);
+
+        final MagnificationConfig config = obtainMagnificationConfig(MODE_WINDOW);
+        mMagnificationController.transitionMagnificationConfigMode(TEST_DISPLAY,
+                config, true, TEST_SERVICE_ID);
+        mMockConnection.invokeCallbacks();
+
+        final ArgumentCaptor<MagnificationConfig> configCaptor = ArgumentCaptor.forClass(
+                MagnificationConfig.class);
+        // The first time is for notifying full-screen enabled and the second time is for notifying
+        // the target mode transitions.
+        verify(mService, times(2)).notifyMagnificationChanged(eq(TEST_DISPLAY), any(Region.class),
+                configCaptor.capture());
+        final MagnificationConfig actualConfig = configCaptor.getValue();
+        assertEquals(config.getCenterX(), actualConfig.getCenterX(), 0);
+        assertEquals(config.getCenterY(), actualConfig.getCenterY(), 0);
+        assertEquals(config.getScale(), actualConfig.getScale(), 0);
+    }
+
+
+    @Test
     public void onAccessibilityActionPerformed_magnifierEnabled_showMagnificationButton()
             throws RemoteException {
         setMagnificationEnabled(MODE_WINDOW);
@@ -743,7 +854,7 @@
     }
 
     @Test
-    public void disableWindowMode_windowModeInActive_removeMagnificationButton()
+    public void disableWindowMode_windowEnabled_removeMagnificationButton()
             throws RemoteException {
         setMagnificationEnabled(MODE_WINDOW);
 
@@ -753,7 +864,7 @@
     }
 
     @Test
-    public void onFullScreenDeactivated_fullscreenModeInActive_removeMagnificationButton()
+    public void onFullScreenDeactivated_fullScreenEnabled_removeMagnificationButton()
             throws RemoteException {
         setMagnificationEnabled(MODE_FULLSCREEN);
         mScreenMagnificationController.setScaleAndCenter(TEST_DISPLAY,
@@ -766,7 +877,7 @@
     }
 
     @Test
-    public void transitionToFullScreenMode_fullscreenModeInActive_showMagnificationButton()
+    public void transitionToFullScreenMode_windowEnabled_showMagnificationButton()
             throws RemoteException {
         setMagnificationEnabled(MODE_WINDOW);
 
@@ -779,7 +890,7 @@
     }
 
     @Test
-    public void transitionToWindow_fullscreenModeInActive_showMagnificationButton()
+    public void transitionToWindow_fullScreenEnabled_showMagnificationButton()
             throws RemoteException {
         setMagnificationEnabled(MODE_FULLSCREEN);
 
@@ -1018,7 +1129,6 @@
                     reset();
                 }
 
-
                 final MagnificationConfig config = new MagnificationConfig.Builder().setMode(
                         MODE_FULLSCREEN).setScale(mScale).setCenterX(mCenterX).setCenterY(
                         mCenterY).build();
diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
index d1b0156..808f8c2c 100644
--- a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
@@ -92,6 +92,7 @@
 
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.function.Consumer;
 
 @Presubmit
 @RunWith(AndroidTestingRunner.class)
@@ -133,6 +134,8 @@
     @Mock
     private IVirtualDeviceActivityListener mActivityListener;
     @Mock
+    private Consumer<ArraySet<Integer>> mRunningAppsChangedCallback;
+    @Mock
     IPowerManager mIPowerManagerMock;
     @Mock
     IThermalService mIThermalServiceMock;
@@ -207,7 +210,7 @@
         mDeviceImpl = new VirtualDeviceImpl(mContext,
                 mAssociationInfo, new Binder(), /* uid */ 0, mInputController,
                 (int associationId) -> {
-                }, mPendingTrampolineCallback, mActivityListener,
+                }, mPendingTrampolineCallback, mActivityListener, mRunningAppsChangedCallback,
                 params);
     }
 
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceMigrationTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceMigrationTest.java
index f834b34..9ba7a9a 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceMigrationTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceMigrationTest.java
@@ -33,8 +33,6 @@
 import static org.mockito.Mockito.when;
 
 import android.app.admin.DevicePolicyManager;
-import android.app.admin.DevicePolicyManagerInternal;
-import android.app.admin.DevicePolicyManagerLiteInternal;
 import android.content.ComponentName;
 import android.content.Intent;
 import android.content.pm.PackageManager;
@@ -50,7 +48,6 @@
 import androidx.test.runner.AndroidJUnit4;
 
 import com.android.frameworks.servicestests.R;
-import com.android.server.LocalServices;
 import com.android.server.SystemService;
 
 import org.junit.Before;
@@ -164,9 +161,6 @@
 
         final long ident = mContext.binder.clearCallingIdentity();
         try {
-            LocalServices.removeServiceForTest(DevicePolicyManagerLiteInternal.class);
-            LocalServices.removeServiceForTest(DevicePolicyManagerInternal.class);
-
             dpms = new DevicePolicyManagerServiceTestable(getServices(), mContext);
 
             dpms.systemReady(SystemService.PHASE_LOCK_SETTINGS_READY);
@@ -278,9 +272,6 @@
 
         final long ident = mContext.binder.clearCallingIdentity();
         try {
-            LocalServices.removeServiceForTest(DevicePolicyManagerLiteInternal.class);
-            LocalServices.removeServiceForTest(DevicePolicyManagerInternal.class);
-
             dpms = new DevicePolicyManagerServiceTestable(getServices(), mContext);
 
             dpms.systemReady(SystemService.PHASE_LOCK_SETTINGS_READY);
@@ -347,9 +338,6 @@
         // (Need clearCallingIdentity() to pass permission checks.)
         final long ident = mContext.binder.clearCallingIdentity();
         try {
-            LocalServices.removeServiceForTest(DevicePolicyManagerLiteInternal.class);
-            LocalServices.removeServiceForTest(DevicePolicyManagerInternal.class);
-
             dpms = new DevicePolicyManagerServiceTestable(getServices(), mContext);
 
             dpms.systemReady(SystemService.PHASE_LOCK_SETTINGS_READY);
@@ -508,9 +496,6 @@
         DevicePolicyManagerServiceTestable dpms;
         final long ident = mContext.binder.clearCallingIdentity();
         try {
-            LocalServices.removeServiceForTest(DevicePolicyManagerLiteInternal.class);
-            LocalServices.removeServiceForTest(DevicePolicyManagerInternal.class);
-
             dpms = new DevicePolicyManagerServiceTestable(getServices(), mContext);
 
             dpms.systemReady(SystemService.PHASE_LOCK_SETTINGS_READY);
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java
index 4f87f9d..2dbf728 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java
@@ -21,6 +21,8 @@
 import android.app.IActivityTaskManager;
 import android.app.NotificationManager;
 import android.app.PendingIntent;
+import android.app.admin.DevicePolicyManagerInternal;
+import android.app.admin.DevicePolicyManagerLiteInternal;
 import android.app.backup.IBackupManager;
 import android.app.usage.UsageStatsManagerInternal;
 import android.content.Context;
@@ -49,6 +51,7 @@
 import com.android.internal.util.FunctionalUtils.ThrowingRunnable;
 import com.android.internal.widget.LockPatternUtils;
 import com.android.internal.widget.LockSettingsInternal;
+import com.android.server.LocalServices;
 import com.android.server.PersistentDataBlockManagerInternal;
 import com.android.server.net.NetworkPolicyManagerInternal;
 import com.android.server.pm.UserManagerInternal;
@@ -99,11 +102,22 @@
     }
 
     private DevicePolicyManagerServiceTestable(MockInjector injector) {
-        super(injector);
+        super(unregisterLocalServices(injector));
         mMockInjector = injector;
         this.context = injector.context;
     }
 
+    /**
+     * Unregisters local services to avoid IllegalStateException when DPMS ctor re-registers them.
+     * This is made into a static method to circumvent the requirement to call super() first.
+     * Returns its parameter as is.
+     */
+    private static MockInjector unregisterLocalServices(MockInjector injector) {
+        LocalServices.removeServiceForTest(DevicePolicyManagerLiteInternal.class);
+        LocalServices.removeServiceForTest(DevicePolicyManagerInternal.class);
+        return injector;
+    }
+
     public void notifyChangeToContentObserver(Uri uri, int userHandle) {
         ContentObserver co = mMockInjector.mContentObservers.get(new Pair<>(uri, userHandle));
         if (co != null) {
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
index ea136da..7b11876d0 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
@@ -328,8 +328,6 @@
     private void initializeDpms() {
         // Need clearCallingIdentity() to pass permission checks.
         final long ident = mContext.binder.clearCallingIdentity();
-        LocalServices.removeServiceForTest(DevicePolicyManagerLiteInternal.class);
-        LocalServices.removeServiceForTest(DevicePolicyManagerInternal.class);
 
         dpms = new DevicePolicyManagerServiceTestable(getServices(), mContext);
         dpms.systemReady(SystemService.PHASE_LOCK_SETTINGS_READY);
@@ -417,8 +415,6 @@
         when(getServices().packageManager.hasSystemFeature(eq(PackageManager.FEATURE_DEVICE_ADMIN)))
                 .thenReturn(false);
 
-        LocalServices.removeServiceForTest(DevicePolicyManagerInternal.class);
-        LocalServices.removeServiceForTest(DevicePolicyManagerLiteInternal.class);
         new DevicePolicyManagerServiceTestable(getServices(), mContext);
 
         // If the device has no DPMS feature, it shouldn't register the local service.
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/NetworkEventTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/NetworkEventTest.java
index 69aaf01..d55f379 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/NetworkEventTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/NetworkEventTest.java
@@ -27,7 +27,6 @@
 
 import android.app.admin.ConnectEvent;
 import android.app.admin.DeviceAdminReceiver;
-import android.app.admin.DevicePolicyManagerInternal;
 import android.app.admin.DnsEvent;
 import android.app.admin.NetworkEvent;
 import android.content.Intent;
@@ -40,8 +39,6 @@
 import android.os.test.TestLooper;
 import android.test.suitebuilder.annotation.SmallTest;
 
-import com.android.server.LocalServices;
-
 import org.junit.Before;
 import org.junit.Test;
 import org.mockito.ArgumentCaptor;
@@ -66,7 +63,6 @@
                 android.Manifest.permission.MANAGE_DEVICE_ADMINS);
         doNothing().when(mSpiedDpmMockContext).sendBroadcastAsUser(any(Intent.class),
                 any(UserHandle.class));
-        LocalServices.removeServiceForTest(DevicePolicyManagerInternal.class);
         mDpmTestable = new DevicePolicyManagerServiceTestable(getServices(), mSpiedDpmMockContext);
         setUpPackageManagerForAdmin(admin1, DpmMockContext.CALLER_UID);
         mDpmTestable.setActiveAdmin(admin1, true, DpmMockContext.CALLER_USER_HANDLE);
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordTest.java
index 7e27e54..d89141c 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordTest.java
@@ -816,8 +816,10 @@
         when(ugm.checkGrantUriPermission(anyInt(), eq(null), any(Uri.class),
                 anyInt(), anyInt())).thenThrow(new SecurityException());
 
-        Notification n = mock(Notification.class);
-        when(n.getChannelId()).thenReturn(channel.getId());
+        channel.setSound(null, null);
+        Notification n = new Notification.Builder(mContext, channel.getId())
+                .setSmallIcon(Icon.createWithContentUri(Uri.parse("content://something")))
+                .build();
         StatusBarNotification sbn =
                 new StatusBarNotification(PKG_P, PKG_P, id1, tag1, uid, uid, n, mUser, null, uid);
         NotificationRecord record = new NotificationRecord(mMockContext, sbn, channel);
@@ -833,6 +835,27 @@
     }
 
     @Test
+    public void testCalculateGrantableUris_PappProvided_invalidSound() {
+        IActivityManager am = mock(IActivityManager.class);
+        UriGrantsManagerInternal ugm = mock(UriGrantsManagerInternal.class);
+        when(ugm.checkGrantUriPermission(anyInt(), eq(null), any(Uri.class),
+                anyInt(), anyInt())).thenThrow(new SecurityException());
+
+        channel.setSound(Uri.parse("content://something"), mock(AudioAttributes.class));
+
+        Notification n = mock(Notification.class);
+        when(n.getChannelId()).thenReturn(channel.getId());
+        StatusBarNotification sbn =
+                new StatusBarNotification(PKG_P, PKG_P, id1, tag1, uid, uid, n, mUser, null, uid);
+        NotificationRecord record = new NotificationRecord(mMockContext, sbn, channel);
+        record.mAm = am;
+        record.mUgmInternal = ugm;
+
+        record.calculateGrantableUris();
+        assertEquals(Settings.System.DEFAULT_NOTIFICATION_URI, record.getSound());
+    }
+
+    @Test
     public void testCalculateGrantableUris_PuserOverridden() {
         IActivityManager am = mock(IActivityManager.class);
         UriGrantsManagerInternal ugm = mock(UriGrantsManagerInternal.class);
diff --git a/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java b/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java
index 762c08f..8ef9ada 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java
@@ -300,6 +300,81 @@
         assertTrue(firstActivity.mRequestForceTransition);
     }
 
+    /**
+     * When there is only one activity in the Task, and the activity is requesting to enter PIP, the
+     * whole Task should enter PIP.
+     */
+    @Test
+    public void testSingleActivityTaskEnterPip() {
+        final Task fullscreenTask = mRootWindowContainer.getDefaultTaskDisplayArea().createRootTask(
+                WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */);
+        final ActivityRecord activity = new ActivityBuilder(mAtm)
+                .setTask(fullscreenTask)
+                .build();
+        final Task task = activity.getTask();
+
+        // Move activity to pinned root task.
+        mRootWindowContainer.moveActivityToPinnedRootTask(activity,
+                null /* launchIntoPipHostActivity */, "test");
+
+        // Ensure a task has moved over.
+        ensureTaskPlacement(task, activity);
+        assertTrue(task.inPinnedWindowingMode());
+    }
+
+    /**
+     * When there is only one activity in the Task, and the activity is requesting to enter PIP, the
+     * whole Task should enter PIP even if the activity is in a TaskFragment.
+     */
+    @Test
+    public void testSingleActivityInTaskFragmentEnterPip() {
+        final Task fullscreenTask = mRootWindowContainer.getDefaultTaskDisplayArea().createRootTask(
+                WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */);
+        final TaskFragment taskFragment = new TaskFragmentBuilder(mAtm)
+                .setParentTask(fullscreenTask)
+                .createActivityCount(1)
+                .build();
+        final ActivityRecord activity = taskFragment.getTopMostActivity();
+        final Task task = activity.getTask();
+
+        // Move activity to pinned root task.
+        mRootWindowContainer.moveActivityToPinnedRootTask(activity,
+                null /* launchIntoPipHostActivity */, "test");
+
+        // Ensure a task has moved over.
+        ensureTaskPlacement(task, activity);
+        assertTrue(task.inPinnedWindowingMode());
+    }
+
+    /**
+     * When there is one TaskFragment with two activities in the Task, the activity requests to
+     * enter PIP, that activity will be move to PIP root task.
+     */
+    @Test
+    public void testMultipleActivitiesInTaskFragmentEnterPip() {
+        final Task fullscreenTask = mRootWindowContainer.getDefaultTaskDisplayArea().createRootTask(
+                WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */);
+        final TaskFragment taskFragment = new TaskFragmentBuilder(mAtm)
+                .setParentTask(fullscreenTask)
+                .createActivityCount(2)
+                .build();
+        final ActivityRecord firstActivity = taskFragment.getTopMostActivity();
+        final ActivityRecord secondActivity = taskFragment.getBottomMostActivity();
+
+        // Move first activity to pinned root task.
+        mRootWindowContainer.moveActivityToPinnedRootTask(firstActivity,
+                null /* launchIntoPipHostActivity */, "test");
+
+        final TaskDisplayArea taskDisplayArea = fullscreenTask.getDisplayArea();
+        final Task pinnedRootTask = taskDisplayArea.getRootPinnedTask();
+
+        // Ensure a task has moved over.
+        ensureTaskPlacement(pinnedRootTask, firstActivity);
+        ensureTaskPlacement(fullscreenTask, secondActivity);
+        assertTrue(pinnedRootTask.inPinnedWindowingMode());
+        assertEquals(WINDOWING_MODE_FULLSCREEN, fullscreenTask.getWindowingMode());
+    }
+
     private static void ensureTaskPlacement(Task task, ActivityRecord... activities) {
         final ArrayList<ActivityRecord> taskActivities = new ArrayList<>();
 
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java
index 37719fe..d135de0 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java
@@ -16,6 +16,9 @@
 
 package com.android.server.wm;
 
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
+import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
+
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
 import static com.android.server.wm.WindowContainer.POSITION_TOP;
@@ -28,6 +31,8 @@
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.clearInvocations;
 import static org.mockito.Mockito.mock;
@@ -435,6 +440,107 @@
     }
 
     @Test
+    public void testTaskFragmentInPip_startActivityInTaskFragment() {
+        setupTaskFragmentInPip();
+        final ActivityRecord activity = mTaskFragment.getTopMostActivity();
+        final IBinder errorToken = new Binder();
+        spyOn(mAtm.getActivityStartController());
+        spyOn(mAtm.mWindowOrganizerController);
+
+        // Not allow to start activity in a TaskFragment that is in a PIP Task.
+        mTransaction.startActivityInTaskFragment(
+                mFragmentToken, activity.token, new Intent(), null /* activityOptions */)
+                .setErrorCallbackToken(errorToken);
+        mAtm.mWindowOrganizerController.applyTransaction(mTransaction);
+
+        verify(mAtm.getActivityStartController(), never()).startActivityInTaskFragment(any(), any(),
+                any(), any(), anyInt(), anyInt());
+        verify(mAtm.mWindowOrganizerController).sendTaskFragmentOperationFailure(eq(mIOrganizer),
+                eq(errorToken), any(IllegalArgumentException.class));
+    }
+
+    @Test
+    public void testTaskFragmentInPip_reparentActivityToTaskFragment() {
+        setupTaskFragmentInPip();
+        final ActivityRecord activity = createActivityRecord(mDisplayContent);
+        final IBinder errorToken = new Binder();
+        spyOn(mAtm.mWindowOrganizerController);
+
+        // Not allow to reparent activity to a TaskFragment that is in a PIP Task.
+        mTransaction.reparentActivityToTaskFragment(mFragmentToken, activity.token)
+                .setErrorCallbackToken(errorToken);
+        mAtm.mWindowOrganizerController.applyTransaction(mTransaction);
+
+        verify(mAtm.mWindowOrganizerController).sendTaskFragmentOperationFailure(eq(mIOrganizer),
+                eq(errorToken), any(IllegalArgumentException.class));
+        assertNull(activity.getOrganizedTaskFragment());
+    }
+
+    @Test
+    public void testTaskFragmentInPip_setAdjacentTaskFragment() {
+        setupTaskFragmentInPip();
+        final IBinder errorToken = new Binder();
+        spyOn(mAtm.mWindowOrganizerController);
+
+        // Not allow to set adjacent on a TaskFragment that is in a PIP Task.
+        mTransaction.setAdjacentTaskFragments(mFragmentToken, null /* fragmentToken2 */,
+                null /* options */)
+                .setErrorCallbackToken(errorToken);
+        mAtm.mWindowOrganizerController.applyTransaction(mTransaction);
+
+        verify(mAtm.mWindowOrganizerController).sendTaskFragmentOperationFailure(eq(mIOrganizer),
+                eq(errorToken), any(IllegalArgumentException.class));
+        verify(mTaskFragment, never()).setAdjacentTaskFragment(any(), anyBoolean());
+    }
+
+    @Test
+    public void testTaskFragmentInPip_createTaskFragment() {
+        mController.registerOrganizer(mIOrganizer);
+        final Task pipTask = createTask(mDisplayContent, WINDOWING_MODE_PINNED,
+                ACTIVITY_TYPE_STANDARD);
+        final ActivityRecord activity = createActivityRecord(pipTask);
+        final IBinder fragmentToken = new Binder();
+        final IBinder errorToken = new Binder();
+        spyOn(mAtm.mWindowOrganizerController);
+
+        // Not allow to create TaskFragment in a PIP Task.
+        createTaskFragmentFromOrganizer(mTransaction, activity, fragmentToken);
+        mTransaction.setErrorCallbackToken(errorToken);
+        mAtm.mWindowOrganizerController.applyTransaction(mTransaction);
+
+        verify(mAtm.mWindowOrganizerController).sendTaskFragmentOperationFailure(eq(mIOrganizer),
+                eq(errorToken), any(IllegalArgumentException.class));
+        assertNull(mAtm.mWindowOrganizerController.getTaskFragment(fragmentToken));
+    }
+
+    @Test
+    public void testTaskFragmentInPip_deleteTaskFragment() {
+        setupTaskFragmentInPip();
+        final IBinder errorToken = new Binder();
+        spyOn(mAtm.mWindowOrganizerController);
+
+        // Not allow to delete a TaskFragment that is in a PIP Task.
+        mTransaction.deleteTaskFragment(mFragmentWindowToken)
+                .setErrorCallbackToken(errorToken);
+        mAtm.mWindowOrganizerController.applyTransaction(mTransaction);
+
+        verify(mAtm.mWindowOrganizerController).sendTaskFragmentOperationFailure(eq(mIOrganizer),
+                eq(errorToken), any(IllegalArgumentException.class));
+        assertNotNull(mAtm.mWindowOrganizerController.getTaskFragment(mFragmentToken));
+    }
+
+    @Test
+    public void testTaskFragmentInPip_setConfig() {
+        setupTaskFragmentInPip();
+        spyOn(mAtm.mWindowOrganizerController);
+
+        // Set bounds is ignored on a TaskFragment that is in a PIP Task.
+        mTransaction.setBounds(mFragmentWindowToken, new Rect(0, 0, 100, 100));
+
+        verify(mTaskFragment, never()).setBounds(any());
+    }
+
+    @Test
     public void testDeferPendingTaskFragmentEventsOfInvisibleTask() {
         // Task - TaskFragment - Activity.
         final Task task = createTask(mDisplayContent);
@@ -643,4 +749,20 @@
             fail();
         }
     }
+
+    /** Setups an embedded TaskFragment in a PIP Task. */
+    private void setupTaskFragmentInPip() {
+        mOrganizer.applyTransaction(mTransaction);
+        mController.registerOrganizer(mIOrganizer);
+        mTaskFragment = new TaskFragmentBuilder(mAtm)
+                .setCreateParentTask()
+                .setFragmentToken(mFragmentToken)
+                .setOrganizer(mOrganizer)
+                .createActivityCount(1)
+                .build();
+        mFragmentWindowToken = mTaskFragment.mRemoteToken.toWindowContainerToken();
+        mAtm.mWindowOrganizerController.mLaunchTaskFragments
+                .put(mFragmentToken, mTaskFragment);
+        mTaskFragment.getTask().setWindowingMode(WINDOWING_MODE_PINNED);
+    }
 }
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java
index 971826d..30c2413 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java
@@ -16,6 +16,10 @@
 
 package com.android.server.wm;
 
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
+
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.any;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.never;
@@ -28,6 +32,7 @@
 import static org.junit.Assert.assertTrue;
 import static org.mockito.Mockito.clearInvocations;
 
+import android.content.res.Configuration;
 import android.graphics.Rect;
 import android.os.Binder;
 import android.platform.test.annotations.Presubmit;
@@ -202,4 +207,89 @@
         assertTrue(primaryActivity.supportsEnterPipOnTaskSwitch);
         assertFalse(secondaryActivity.supportsEnterPipOnTaskSwitch);
     }
+
+    @Test
+    public void testEmbeddedTaskFragmentEnterPip_resetOrganizerOverrideConfig() {
+        final TaskFragment taskFragment = new TaskFragmentBuilder(mAtm)
+                .setOrganizer(mOrganizer)
+                .setFragmentToken(new Binder())
+                .setCreateParentTask()
+                .createActivityCount(1)
+                .build();
+        final Task task = taskFragment.getTask();
+        final ActivityRecord activity = taskFragment.getTopMostActivity();
+        final Rect taskFragmentBounds = new Rect(0, 0, 300, 1000);
+        task.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
+        taskFragment.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW);
+        taskFragment.setBounds(taskFragmentBounds);
+
+        assertEquals(taskFragmentBounds, activity.getBounds());
+        assertEquals(WINDOWING_MODE_MULTI_WINDOW, activity.getWindowingMode());
+
+        // Move activity to pinned root task.
+        mRootWindowContainer.moveActivityToPinnedRootTask(activity,
+                null /* launchIntoPipHostActivity */, "test");
+
+        // Ensure taskFragment requested config is reset.
+        assertEquals(taskFragment, activity.getOrganizedTaskFragment());
+        assertEquals(task, activity.getTask());
+        assertTrue(task.inPinnedWindowingMode());
+        assertTrue(taskFragment.inPinnedWindowingMode());
+        final Rect taskBounds = task.getBounds();
+        assertEquals(taskBounds, taskFragment.getBounds());
+        assertEquals(taskBounds, activity.getBounds());
+        assertEquals(Configuration.EMPTY, taskFragment.getRequestedOverrideConfiguration());
+    }
+
+    @Test
+    public void testActivityHasOverlayOverUntrustedModeEmbedded() {
+        final Task rootTask = createTask(mDisplayContent, WINDOWING_MODE_MULTI_WINDOW,
+                ACTIVITY_TYPE_STANDARD);
+        final Task leafTask0 = new TaskBuilder(mSupervisor)
+                .setParentTaskFragment(rootTask)
+                .build();
+        final TaskFragment organizedTf = new TaskFragmentBuilder(mAtm)
+                .createActivityCount(2)
+                .setParentTask(leafTask0)
+                .setFragmentToken(new Binder())
+                .setOrganizer(mOrganizer)
+                .build();
+        final ActivityRecord activity0 = organizedTf.getBottomMostActivity();
+        final ActivityRecord activity1 = organizedTf.getTopMostActivity();
+        // Bottom activity is untrusted embedding. Top activity is trusted embedded.
+        // Activity0 has overlay over untrusted mode embedded.
+        activity0.info.applicationInfo.uid = DEFAULT_TASK_FRAGMENT_ORGANIZER_UID + 1;
+        activity1.info.applicationInfo.uid = DEFAULT_TASK_FRAGMENT_ORGANIZER_UID;
+        doReturn(true).when(organizedTf).isAllowedToEmbedActivityInUntrustedMode(activity0);
+
+        assertTrue(activity0.hasOverlayOverUntrustedModeEmbedded());
+        assertFalse(activity1.hasOverlayOverUntrustedModeEmbedded());
+
+        // Both activities are trusted embedded.
+        // None of the two has overlay over untrusted mode embedded.
+        activity0.info.applicationInfo.uid = DEFAULT_TASK_FRAGMENT_ORGANIZER_UID;
+
+        assertFalse(activity0.hasOverlayOverUntrustedModeEmbedded());
+        assertFalse(activity1.hasOverlayOverUntrustedModeEmbedded());
+
+        // Bottom activity is trusted embedding. Top activity is untrusted embedded.
+        // None of the two has overlay over untrusted mode embedded.
+        activity1.info.applicationInfo.uid = DEFAULT_TASK_FRAGMENT_ORGANIZER_UID + 1;
+
+        assertFalse(activity0.hasOverlayOverUntrustedModeEmbedded());
+        assertFalse(activity1.hasOverlayOverUntrustedModeEmbedded());
+
+        // There is an activity in a different leaf task on top of activity0 and activity1.
+        // None of the two has overlay over untrusted mode embedded because it is not the same Task.
+        final Task leafTask1 = new TaskBuilder(mSupervisor)
+                .setParentTaskFragment(rootTask)
+                .setOnTop(true)
+                .setCreateActivity(true)
+                .build();
+        final ActivityRecord activity2 = leafTask1.getTopMostActivity();
+        activity2.info.applicationInfo.uid = DEFAULT_TASK_FRAGMENT_ORGANIZER_UID + 2;
+
+        assertFalse(activity0.hasOverlayOverUntrustedModeEmbedded());
+        assertFalse(activity1.hasOverlayOverUntrustedModeEmbedded());
+    }
 }
diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java
index ee2d235..b716690 100644
--- a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java
+++ b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java
@@ -84,9 +84,6 @@
 
     private static final int INVALID_VALUE = Integer.MIN_VALUE;
 
-    /** Maximum time to wait for a model stop confirmation before giving up. */
-    private static final long STOP_TIMEOUT_MS = 5000;
-
     /** The {@link ModuleProperties} for the system, or null if none exists. */
     final ModuleProperties mModuleProperties;
 
@@ -398,20 +395,8 @@
                 return STATUS_OK;
             }
 
-            int status = prepareForRecognition(modelData);
-            if (status != STATUS_OK) {
-                Slog.w(TAG, "startRecognition failed to prepare model for recognition");
-                return status;
-            }
-            status = startRecognitionLocked(modelData,
+            return updateRecognitionLocked(modelData,
                     false /* Don't notify for synchronous calls */);
-
-            // Initialize power save, call active state monitoring logic.
-            if (status == STATUS_OK) {
-                initializeDeviceStateListeners();
-            }
-
-            return status;
         }
     }
 
@@ -560,7 +545,7 @@
             }
         }
 
-        if (unloadModel && modelData.isModelLoaded()) {
+        if (unloadModel && (modelData.isModelLoaded() || modelData.isStopPending())) {
             Slog.d(TAG, "Unloading previously loaded stale model.");
             if (mModule == null) {
                 return STATUS_ERROR;
@@ -834,7 +819,7 @@
         }
 
         if (!event.recognitionStillActive) {
-            model.setStoppedLocked();
+            model.setStopped();
         }
 
         try {
@@ -920,8 +905,8 @@
         Slog.w(TAG, "Recognition aborted");
         MetricsLogger.count(mContext, "sth_recognition_aborted", 1);
         ModelData modelData = getModelDataForLocked(event.soundModelHandle);
-        if (modelData != null && modelData.isModelStarted()) {
-            modelData.setStoppedLocked();
+        if (modelData != null && (modelData.isModelStarted() || modelData.isStopPending())) {
+            modelData.setStopped();
             try {
                 IRecognitionStatusCallback callback = modelData.getCallback();
                 if (callback != null) {
@@ -932,6 +917,7 @@
             } catch (RemoteException e) {
                 Slog.w(TAG, "RemoteException in onRecognitionPaused", e);
             }
+            updateRecognitionLocked(modelData, true);
         }
     }
 
@@ -978,7 +964,7 @@
         }
 
         if (!event.recognitionStillActive) {
-            modelData.setStoppedLocked();
+            modelData.setStopped();
         }
 
         try {
@@ -1011,16 +997,22 @@
 
     private int updateRecognitionLocked(ModelData model, boolean notifyClientOnError) {
         boolean shouldStartModel = model.isRequested() && isRecognitionAllowedByDeviceState(model);
-        if (shouldStartModel == model.isModelStarted()) {
+        if (shouldStartModel == model.isModelStarted() || model.isStopPending()) {
             // No-op.
             return STATUS_OK;
         }
         if (shouldStartModel) {
             int status = prepareForRecognition(model);
             if (status != STATUS_OK) {
+                Slog.w(TAG, "startRecognition failed to prepare model for recognition");
                 return status;
             }
-            return startRecognitionLocked(model, notifyClientOnError);
+            status = startRecognitionLocked(model, notifyClientOnError);
+            // Initialize power save, call active state monitoring logic.
+            if (status == STATUS_OK) {
+                initializeDeviceStateListeners();
+            }
+            return status;
         } else {
             return stopRecognitionLocked(model, notifyClientOnError);
         }
@@ -1203,10 +1195,13 @@
         if (mModule == null) {
             return;
         }
-        if (modelData.isModelStarted()) {
+        if (modelData.isStopPending()) {
+            // No need to wait for the stop to be confirmed.
+            modelData.setStopped();
+        } else if (modelData.isModelStarted()) {
             Slog.d(TAG, "Stopping previously started dangling model " + modelData.getHandle());
             if (mModule.stopRecognition(modelData.getHandle()) == STATUS_OK) {
-                modelData.setStoppedLocked();
+                modelData.setStopped();
                 modelData.setRequested(false);
             } else {
                 Slog.e(TAG, "Failed to stop model " + modelData.getHandle());
@@ -1255,7 +1250,7 @@
     private ModelData getOrCreateGenericModelDataLocked(UUID modelId) {
         ModelData modelData = mModelDataMap.get(modelId);
         if (modelData == null) {
-            modelData = createGenericModelData(modelId);
+            modelData = ModelData.createGenericModelData(modelId);
             mModelDataMap.put(modelId, modelData);
         } else if (!modelData.isGenericModel()) {
             Slog.e(TAG, "UUID already used for non-generic model.");
@@ -1287,7 +1282,7 @@
         mKeyphraseUuidMap.remove(keyphraseId);
         mModelDataMap.remove(modelId);
         mKeyphraseUuidMap.put(keyphraseId, modelId);
-        ModelData modelData = createKeyphraseModelData(modelId);
+        ModelData modelData = ModelData.createKeyphraseModelData(modelId);
         mModelDataMap.put(modelId, modelData);
         return modelData;
     }
@@ -1419,26 +1414,18 @@
                     Slog.w(TAG, "RemoteException in onError", e);
                 }
             }
-            return status;
-        }
-
-        // Wait for model to be stopped.
-        try {
-            modelData.waitStoppedLocked(STOP_TIMEOUT_MS);
-        } catch (InterruptedException e) {
-            Slog.e(TAG, "Didn't receive model stop callback");
-            return SoundTrigger.STATUS_ERROR;
-        }
-
-        MetricsLogger.count(mContext, "sth_stop_recognition_success", 1);
-        // Notify of pause if needed.
-        if (notify) {
-            try {
-                callback.onRecognitionPaused();
-            } catch (DeadObjectException e) {
-                forceStopAndUnloadModelLocked(modelData, e);
-            } catch (RemoteException e) {
-                Slog.w(TAG, "RemoteException in onRecognitionPaused", e);
+        } else {
+            modelData.setStopPending();
+            MetricsLogger.count(mContext, "sth_stop_recognition_success", 1);
+            // Notify of pause if needed.
+            if (notify) {
+                try {
+                    callback.onRecognitionPaused();
+                } catch (DeadObjectException e) {
+                    forceStopAndUnloadModelLocked(modelData, e);
+                } catch (RemoteException e) {
+                    Slog.w(TAG, "RemoteException in onRecognitionPaused", e);
+                }
             }
         }
         if (DBG) {
@@ -1473,7 +1460,7 @@
 
     // This class encapsulates the callbacks, state, handles and any other information that
     // represents a model.
-    private class ModelData {
+    private static class ModelData {
         // Model not loaded (and hence not started).
         static final int MODEL_NOTLOADED = 0;
 
@@ -1483,6 +1470,9 @@
         // Started implies model was successfully loaded and start was called.
         static final int MODEL_STARTED = 2;
 
+        // Model stop request has been sent. Waiting for an event to signal model being stopped.
+        static final int MODEL_STOP_PENDING = 3;
+
         // One of MODEL_NOTLOADED, MODEL_LOADED, MODEL_STARTED (which implies loaded).
         private int mModelState;
         private UUID mModelId;
@@ -1530,9 +1520,17 @@
             mModelType = modelType;
         }
 
+        static ModelData createKeyphraseModelData(UUID modelId) {
+            return new ModelData(modelId, SoundModel.TYPE_KEYPHRASE);
+        }
+
+        static ModelData createGenericModelData(UUID modelId) {
+            return new ModelData(modelId, SoundModel.TYPE_GENERIC_SOUND);
+        }
+
         // Note that most of the functionality in this Java class will not work for
         // SoundModel.TYPE_UNKNOWN nevertheless we have it since lower layers support it.
-        ModelData createModelDataOfUnknownType(UUID modelId) {
+        static ModelData createModelDataOfUnknownType(UUID modelId) {
             return new ModelData(modelId, SoundModel.TYPE_UNKNOWN);
         }
 
@@ -1552,24 +1550,20 @@
             return mModelState == MODEL_NOTLOADED;
         }
 
+        synchronized boolean isStopPending() {
+            return mModelState == MODEL_STOP_PENDING;
+        }
+
         synchronized void setStarted() {
             mModelState = MODEL_STARTED;
         }
 
-        synchronized void setStoppedLocked() {
+        synchronized void setStopped() {
             mModelState = MODEL_LOADED;
-            mLock.notifyAll();
         }
 
-        void waitStoppedLocked(long timeoutMs) throws InterruptedException {
-            long deadline = System.currentTimeMillis() + timeoutMs;
-            while (mModelState == MODEL_STARTED) {
-                long waitTime = deadline - System.currentTimeMillis();
-                if (waitTime <= 0) {
-                    throw new InterruptedException();
-                }
-                mLock.wait(waitTime);
-            }
+        synchronized void setStopPending() {
+            mModelState = MODEL_STOP_PENDING;
         }
 
         synchronized void setLoaded() {
@@ -1589,7 +1583,6 @@
             mRecognitionConfig = null;
             mRequested = false;
             mCallback = null;
-            notifyAll();
         }
 
         synchronized void clearCallback() {
@@ -1694,12 +1687,4 @@
             return "Model type: " + type + "\n";
         }
     }
-
-    ModelData createKeyphraseModelData(UUID modelId) {
-        return new ModelData(modelId, SoundModel.TYPE_KEYPHRASE);
-    }
-
-    ModelData createGenericModelData(UUID modelId) {
-        return new ModelData(modelId, SoundModel.TYPE_GENERIC_SOUND);
-    }
 }
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index c92f898..999b55a 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -14199,7 +14199,8 @@
             UPDATE_AVAILABLE_NETWORKS_MULTIPLE_NETWORKS_NOT_SUPPORTED,
             UPDATE_AVAILABLE_NETWORKS_NO_OPPORTUNISTIC_SUB_AVAILABLE,
             UPDATE_AVAILABLE_NETWORKS_REMOTE_SERVICE_EXCEPTION,
-            UPDATE_AVAILABLE_NETWORKS_SERVICE_IS_DISABLED})
+            UPDATE_AVAILABLE_NETWORKS_SERVICE_IS_DISABLED,
+            UPDATE_AVAILABLE_NETWORKS_SIM_PORT_NOT_AVAILABLE})
     public @interface UpdateAvailableNetworksResult {}
 
     /**
@@ -14258,6 +14259,12 @@
     public static final int UPDATE_AVAILABLE_NETWORKS_SERVICE_IS_DISABLED = 10;
 
     /**
+     * SIM port is not available to switch to opportunistic subscription.
+     * @hide
+     */
+    public static final int UPDATE_AVAILABLE_NETWORKS_SIM_PORT_NOT_AVAILABLE = 11;
+
+    /**
      * Set preferred opportunistic data subscription id.
      *
      * Switch internet data to preferred opportunistic data subscription id. This api
diff --git a/tools/apilint/deprecated_at_birth.py b/tools/apilint/deprecated_at_birth.py
old mode 100644
new mode 100755
index 297d9c3b..da9f19f
--- a/tools/apilint/deprecated_at_birth.py
+++ b/tools/apilint/deprecated_at_birth.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
 
 # Copyright (C) 2021 The Android Open Source Project
 #
@@ -208,17 +208,17 @@
 
 def _parse_stream_path(path):
     api = {}
-    print "Parsing", path
+    print("Parsing %s" % path)
     for f in os.listdir(path):
         f = os.path.join(path, f)
         if not os.path.isfile(f): continue
         if not f.endswith(".txt"): continue
         if f.endswith("removed.txt"): continue
-        print "\t", f
+        print("\t%s" % f)
         with open(f) as s:
             api = _parse_stream(s, api)
-    print "Parsed", len(api), "APIs"
-    print
+    print("Parsed %d APIs" % len(api))
+    print()
     return api
 
 
@@ -306,8 +306,8 @@
             if "@Deprecated " in i.raw:
                 error(clazz, i, None, "Found API deprecation at birth " + i.ident)
 
-    print "%s Deprecated at birth %s\n" % ((format(fg=WHITE, bg=BLUE, bold=True),
-                                            format(reset=True)))
+    print("%s Deprecated at birth %s\n" % ((format(fg=WHITE, bg=BLUE, bold=True),
+                                            format(reset=True))))
     for f in sorted(failures):
-        print failures[f]
-        print
+        print(failures[f])
+        print()