Merge "Expose 'ParcelableHolder' as SystemApi" am: 135efb0b73 am: 5e1cacaa28 am: d5bae241d4

Original change: https://android-review.googlesource.com/c/platform/frameworks/base/+/1458065

Change-Id: I5f8d912b43a546aca9cbbd8d9c480ffa101c1452
diff --git a/apex/blobstore/service/java/com/android/server/blob/BlobStoreConfig.java b/apex/blobstore/service/java/com/android/server/blob/BlobStoreConfig.java
index bb9f13f..5cebf8d 100644
--- a/apex/blobstore/service/java/com/android/server/blob/BlobStoreConfig.java
+++ b/apex/blobstore/service/java/com/android/server/blob/BlobStoreConfig.java
@@ -128,7 +128,7 @@
          */
         public static final String KEY_USE_REVOCABLE_FD_FOR_READS =
                 "use_revocable_fd_for_reads";
-        public static final boolean DEFAULT_USE_REVOCABLE_FD_FOR_READS = true;
+        public static final boolean DEFAULT_USE_REVOCABLE_FD_FOR_READS = false;
         public static boolean USE_REVOCABLE_FD_FOR_READS =
                 DEFAULT_USE_REVOCABLE_FD_FOR_READS;
 
diff --git a/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java b/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java
index 0cadbfd..36ccaf9 100644
--- a/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java
+++ b/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java
@@ -455,9 +455,6 @@
 
             mSystemServicesReady = true;
 
-            // Offload to handler thread to avoid boot time impact.
-            mHandler.post(AppStandbyController.this::updatePowerWhitelistCache);
-
             boolean userFileExists;
             synchronized (mAppIdleLock) {
                 userFileExists = mAppIdleHistory.userFileExists(UserHandle.USER_SYSTEM);
@@ -474,7 +471,9 @@
             setChargingState(mInjector.isCharging());
 
             // Offload to handler thread after boot completed to avoid boot time impact. This means
-            // that headless system apps may be put in a lower bucket until boot has completed.
+            // that app standby buckets may be slightly out of date and headless system apps may be
+            // put in a lower bucket until boot has completed.
+            mHandler.post(AppStandbyController.this::updatePowerWhitelistCache);
             mHandler.post(this::loadHeadlessSystemAppCache);
         }
     }
@@ -1121,6 +1120,10 @@
             if (isDeviceProvisioningPackage(packageName)) {
                 return STANDBY_BUCKET_EXEMPTED;
             }
+
+            if (mInjector.isWellbeingPackage(packageName)) {
+                return STANDBY_BUCKET_WORKING_SET;
+            }
         }
 
         // Check this last, as it can be the most expensive check
@@ -1930,6 +1933,7 @@
          */
         @GuardedBy("mPowerWhitelistedApps")
         private final ArraySet<String> mPowerWhitelistedApps = new ArraySet<>();
+        private String mWellbeingApp = null;
 
         Injector(Context context, Looper looper) {
             mContext = context;
@@ -1963,6 +1967,9 @@
                 if (activityManager.isLowRamDevice() || ActivityManager.isSmallBatteryDevice()) {
                     mAutoRestrictedBucketDelayMs = 12 * ONE_HOUR;
                 }
+
+                final PackageManager packageManager = mContext.getPackageManager();
+                mWellbeingApp = packageManager.getWellbeingPackageName();
             }
             mBootPhase = phase;
         }
@@ -2007,6 +2014,14 @@
             }
         }
 
+        /**
+         * Returns {@code true} if the supplied package is the wellbeing app. Otherwise,
+         * returns {@code false}.
+         */
+        boolean isWellbeingPackage(String packageName) {
+            return mWellbeingApp != null && mWellbeingApp.equals(packageName);
+        }
+
         void updatePowerWhitelistCache() {
             try {
                 // Don't call out to DeviceIdleController with the lock held.
diff --git a/api/current.txt b/api/current.txt
index 22b8c45..9bfdf27 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -46951,7 +46951,7 @@
     method public long getNci();
     method @IntRange(from=0, to=3279165) public int getNrarfcn();
     method @IntRange(from=0, to=1007) public int getPci();
-    method @IntRange(from=0, to=16777215) public int getTac();
+    method @IntRange(from=0, to=65535) public int getTac();
     method public void writeToParcel(android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.telephony.CellIdentityNr> CREATOR;
   }
diff --git a/api/test-current.txt b/api/test-current.txt
index bdf797e..6670148 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -36,6 +36,7 @@
   public static final class R.bool {
     field public static final int config_assistantOnTopOfDream = 17891333; // 0x1110005
     field public static final int config_perDisplayFocusEnabled = 17891332; // 0x1110004
+    field public static final int config_remoteInsetsControllerControlsSystemBars = 17891334; // 0x1110006
   }
 
   public static final class R.string {
diff --git a/cmds/incidentd/src/IncidentService.cpp b/cmds/incidentd/src/IncidentService.cpp
index dc16125..13bf197 100644
--- a/cmds/incidentd/src/IncidentService.cpp
+++ b/cmds/incidentd/src/IncidentService.cpp
@@ -554,6 +554,10 @@
             return NO_ERROR;
         }
         if (!args[0].compare(String8("section"))) {
+            if (argCount == 1) {
+                fprintf(out, "Not enough arguments for section\n");
+                return NO_ERROR;
+            }
             int id = atoi(args[1]);
             int idx = 0;
             while (SECTION_LIST[idx] != NULL) {
diff --git a/cmds/statsd/src/atoms.proto b/cmds/statsd/src/atoms.proto
index 12ef9ba..46a8c7e 100644
--- a/cmds/statsd/src/atoms.proto
+++ b/cmds/statsd/src/atoms.proto
@@ -4440,15 +4440,12 @@
         UNKNOWN = 0;
         CHIP_VIEWED = 1;
         CHIP_CLICKED = 2;
-        DIALOG_PRIVACY_SETTINGS = 3;
+        reserved 3; // Used only in beta builds, never shipped 
         DIALOG_DISMISS = 4;
         DIALOG_LINE_ITEM = 5;
     }
 
     optional Type type = 1 [(state_field_option).exclusive_state = true];
-
-    // Used if the type is LINE_ITEM
-    optional string package_name = 2;
 }
 
 /**
diff --git a/core/java/android/accessibilityservice/AccessibilityServiceInfo.java b/core/java/android/accessibilityservice/AccessibilityServiceInfo.java
index ca22bf4..a41fa64 100644
--- a/core/java/android/accessibilityservice/AccessibilityServiceInfo.java
+++ b/core/java/android/accessibilityservice/AccessibilityServiceInfo.java
@@ -364,6 +364,18 @@
      */
     public static final int FLAG_REQUEST_MULTI_FINGER_GESTURES = 0x0001000;
 
+    /**
+     * This flag requests that when when {@link #FLAG_REQUEST_MULTI_FINGER_GESTURES} is enabled,
+     * two-finger passthrough gestures are re-enabled. Two-finger swipe gestures are not detected,
+     * but instead passed through as one-finger gestures. In addition, three-finger swipes from the
+     * bottom of the screen are not detected, and instead are passed through unchanged. If {@link
+     * #FLAG_REQUEST_MULTI_FINGER_GESTURES} is disabled this flag has no effect.
+     *
+     * @see #FLAG_REQUEST_TOUCH_EXPLORATION_MODE
+     * @hide
+     */
+    public static final int FLAG_REQUEST_2_FINGER_PASSTHROUGH = 0x0002000;
+
     /** {@hide} */
     public static final int FLAG_FORCE_DIRECT_BOOT_AWARE = 0x00010000;
 
@@ -1261,6 +1273,8 @@
                 return "FLAG_SERVICE_HANDLES_DOUBLE_TAP";
             case FLAG_REQUEST_MULTI_FINGER_GESTURES:
                 return "FLAG_REQUEST_MULTI_FINGER_GESTURES";
+            case FLAG_REQUEST_2_FINGER_PASSTHROUGH:
+                return "FLAG_REQUEST_2_FINGER_PASSTHROUGH";
             case FLAG_REQUEST_ENHANCED_WEB_ACCESSIBILITY:
                 return "FLAG_REQUEST_ENHANCED_WEB_ACCESSIBILITY";
             case FLAG_REPORT_VIEW_IDS:
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 7afe8b79..3466a78 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -3250,12 +3250,6 @@
         sendMessage(H.CLEAN_UP_CONTEXT, cci);
     }
 
-    @Override
-    public void handleFixedRotationAdjustments(@NonNull IBinder token,
-            @Nullable FixedRotationAdjustments fixedRotationAdjustments) {
-        handleFixedRotationAdjustments(token, fixedRotationAdjustments, null /* overrideConfig */);
-    }
-
     /**
      * Applies the rotation adjustments to override display information in resources belong to the
      * provided token. If the token is activity token, the adjustments also apply to application
@@ -3265,51 +3259,39 @@
      * @param fixedRotationAdjustments The information to override the display adjustments of
      *                                 corresponding resources. If it is null, the exiting override
      *                                 will be cleared.
-     * @param overrideConfig The override configuration of activity. It is used to override
-     *                       application configuration. If it is non-null, it means the token is
-     *                       confirmed as activity token. Especially when launching new activity,
-     *                       {@link #mActivities} hasn't put the new token.
      */
-    private void handleFixedRotationAdjustments(@NonNull IBinder token,
-            @Nullable FixedRotationAdjustments fixedRotationAdjustments,
-            @Nullable Configuration overrideConfig) {
-        // The element of application configuration override is set only if the application
-        // adjustments are needed, because activity already has its own override configuration.
-        final Configuration[] appConfigOverride;
-        final Consumer<DisplayAdjustments> override;
-        if (fixedRotationAdjustments != null) {
-            appConfigOverride = new Configuration[1];
-            override = displayAdjustments -> {
-                displayAdjustments.setFixedRotationAdjustments(fixedRotationAdjustments);
-                if (appConfigOverride[0] != null) {
-                    displayAdjustments.getConfiguration().updateFrom(appConfigOverride[0]);
-                }
-            };
-        } else {
-            appConfigOverride = null;
-            override = null;
-        }
+    @Override
+    public void handleFixedRotationAdjustments(@NonNull IBinder token,
+            @Nullable FixedRotationAdjustments fixedRotationAdjustments) {
+        final Consumer<DisplayAdjustments> override = fixedRotationAdjustments != null
+                ? displayAdjustments -> displayAdjustments
+                        .setFixedRotationAdjustments(fixedRotationAdjustments)
+                : null;
         if (!mResourcesManager.overrideTokenDisplayAdjustments(token, override)) {
             // No resources are associated with the token.
             return;
         }
-        if (overrideConfig == null) {
-            final ActivityClientRecord r = mActivities.get(token);
-            if (r == null) {
-                // It is not an activity token. Nothing to do for application.
-                return;
-            }
-            overrideConfig = r.overrideConfig;
-        }
-        if (appConfigOverride != null) {
-            appConfigOverride[0] = overrideConfig;
+        if (mActivities.get(token) == null) {
+            // Nothing to do for application if it is not an activity token.
+            return;
         }
 
-        // Apply the last override to application resources for compatibility. Because the Resources
-        // of Display can be from application, e.g.
-        //    applicationContext.getSystemService(DisplayManager.class).getDisplay(displayId)
-        // and the deprecated usage:
-        //    applicationContext.getSystemService(WindowManager.class).getDefaultDisplay();
+        overrideApplicationDisplayAdjustments(token, override);
+    }
+
+    /**
+     * Applies the last override to application resources for compatibility. Because the Resources
+     * of Display can be from application, e.g.
+     *   applicationContext.getSystemService(DisplayManager.class).getDisplay(displayId)
+     * and the deprecated usage:
+     *   applicationContext.getSystemService(WindowManager.class).getDefaultDisplay();
+     *
+     * @param token The owner and target of the override.
+     * @param override The display adjustments override for application resources. If it is null,
+     *                 the override of the token will be removed and pop the last one to use.
+     */
+    private void overrideApplicationDisplayAdjustments(@NonNull IBinder token,
+            @Nullable Consumer<DisplayAdjustments> override) {
         final Consumer<DisplayAdjustments> appOverride;
         if (mActiveRotationAdjustments == null) {
             mActiveRotationAdjustments = new ArrayList<>(2);
@@ -3542,8 +3524,13 @@
         // The rotation adjustments must be applied before creating the activity, so the activity
         // can get the adjusted display info during creation.
         if (r.mPendingFixedRotationAdjustments != null) {
-            handleFixedRotationAdjustments(r.token, r.mPendingFixedRotationAdjustments,
-                    r.overrideConfig);
+            // The adjustments should have been set by handleLaunchActivity, so the last one is the
+            // override for activity resources.
+            if (mActiveRotationAdjustments != null && !mActiveRotationAdjustments.isEmpty()) {
+                mResourcesManager.overrideTokenDisplayAdjustments(r.token,
+                        mActiveRotationAdjustments.get(
+                                mActiveRotationAdjustments.size() - 1).second);
+            }
             r.mPendingFixedRotationAdjustments = null;
         }
 
@@ -3582,6 +3569,13 @@
             mProfiler.startProfiling();
         }
 
+        if (r.mPendingFixedRotationAdjustments != null) {
+            // The rotation adjustments must be applied before handling configuration, so process
+            // level display metrics can be adjusted.
+            overrideApplicationDisplayAdjustments(r.token, adjustments ->
+                    adjustments.setFixedRotationAdjustments(r.mPendingFixedRotationAdjustments));
+        }
+
         // Make sure we are running with the most recent config.
         handleConfigurationChanged(null, null);
 
@@ -5112,6 +5106,7 @@
                 }
             }
             r.setState(ON_DESTROY);
+            mLastReportedWindowingMode.remove(r.activity.getActivityToken());
         }
         schedulePurgeIdler();
         // updatePendingActivityConfiguration() reads from mActivities to update
@@ -5776,7 +5771,15 @@
             if (DEBUG_CONFIGURATION) Slog.v(TAG, "Handle configuration changed: "
                     + config);
 
-            mResourcesManager.applyConfigurationToResourcesLocked(config, compat);
+            final Resources appResources = mInitialApplication.getResources();
+            if (appResources.hasOverrideDisplayAdjustments()) {
+                // The value of Display#getRealSize will be adjusted by FixedRotationAdjustments,
+                // but Display#getSize refers to DisplayAdjustments#mConfiguration. So the rotated
+                // configuration also needs to set to the adjustments for consistency.
+                appResources.getDisplayAdjustments().getConfiguration().updateFrom(config);
+            }
+            mResourcesManager.applyConfigurationToResourcesLocked(config, compat,
+                    appResources.getDisplayAdjustments());
             updateLocaleListFromAppContext(mInitialApplication.getApplicationContext(),
                     mResourcesManager.getConfiguration().getLocales());
 
@@ -7389,7 +7392,8 @@
                 // We need to apply this change to the resources immediately, because upon returning
                 // the view hierarchy will be informed about it.
                 if (mResourcesManager.applyConfigurationToResourcesLocked(globalConfig,
-                        null /* compat */)) {
+                        null /* compat */,
+                        mInitialApplication.getResources().getDisplayAdjustments())) {
                     updateLocaleListFromAppContext(mInitialApplication.getApplicationContext(),
                             mResourcesManager.getConfiguration().getLocales());
 
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index 69b77a9..183a3b4 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -1111,9 +1111,32 @@
     /** @hide */
     public static final int OP_NO_ISOLATED_STORAGE = AppProtoEnums.APP_OP_NO_ISOLATED_STORAGE;
 
+    /**
+     * Phone call is using microphone
+     *
+     * @hide
+     */
+    // TODO: Add as AppProtoEnums
+    public static final int OP_PHONE_CALL_MICROPHONE = 100;
+    /**
+     * Phone call is using camera
+     *
+     * @hide
+     */
+    // TODO: Add as AppProtoEnums
+    public static final int OP_PHONE_CALL_CAMERA = 101;
+
+    /**
+     * Audio is being recorded for hotword detection.
+     *
+     * @hide
+     */
+    // TODO: Add as AppProtoEnums
+    public static final int OP_RECORD_AUDIO_HOTWORD = 102;
+
     /** @hide */
     @UnsupportedAppUsage
-    public static final int _NUM_OP = 100;
+    public static final int _NUM_OP = 103;
 
     /** Access to coarse location information. */
     public static final String OPSTR_COARSE_LOCATION = "android:coarse_location";
@@ -1430,6 +1453,26 @@
      */
     public static final String OPSTR_NO_ISOLATED_STORAGE = "android:no_isolated_storage";
 
+    /**
+     * Phone call is using microphone
+     *
+     * @hide
+     */
+    public static final String OPSTR_PHONE_CALL_MICROPHONE = "android:phone_call_microphone";
+    /**
+     * Phone call is using camera
+     *
+     * @hide
+     */
+    public static final String OPSTR_PHONE_CALL_CAMERA = "android:phone_call_camera";
+
+    /**
+     * Audio is being recorded for hotword detection.
+     *
+     * @hide
+     */
+    public static final String OPSTR_RECORD_AUDIO_HOTWORD = "android:record_audio_hotword";
+
     /** {@link #sAppOpsToNote} not initialized yet for this op */
     private static final byte SHOULD_COLLECT_NOTE_OP_NOT_INITIALIZED = 0;
     /** Should not collect noting of this app-op in {@link #sAppOpsToNote} */
@@ -1619,6 +1662,9 @@
             OP_AUTO_REVOKE_PERMISSIONS_IF_UNUSED, //AUTO_REVOKE_PERMISSIONS_IF_UNUSED
             OP_AUTO_REVOKE_MANAGED_BY_INSTALLER, //OP_AUTO_REVOKE_MANAGED_BY_INSTALLER
             OP_NO_ISOLATED_STORAGE,             // NO_ISOLATED_STORAGE
+            OP_PHONE_CALL_MICROPHONE,           // OP_PHONE_CALL_MICROPHONE
+            OP_PHONE_CALL_CAMERA,               // OP_PHONE_CALL_CAMERA
+            OP_RECORD_AUDIO_HOTWORD,            // RECORD_AUDIO_HOTWORD
     };
 
     /**
@@ -1725,6 +1771,9 @@
             OPSTR_AUTO_REVOKE_PERMISSIONS_IF_UNUSED,
             OPSTR_AUTO_REVOKE_MANAGED_BY_INSTALLER,
             OPSTR_NO_ISOLATED_STORAGE,
+            OPSTR_PHONE_CALL_MICROPHONE,
+            OPSTR_PHONE_CALL_CAMERA,
+            OPSTR_RECORD_AUDIO_HOTWORD,
     };
 
     /**
@@ -1832,6 +1881,9 @@
             "AUTO_REVOKE_PERMISSIONS_IF_UNUSED",
             "AUTO_REVOKE_MANAGED_BY_INSTALLER",
             "NO_ISOLATED_STORAGE",
+            "PHONE_CALL_MICROPHONE",
+            "PHONE_CALL_CAMERA",
+            "RECORD_AUDIO_HOTWORD",
     };
 
     /**
@@ -1940,6 +1992,9 @@
             null, // no permission for OP_AUTO_REVOKE_PERMISSIONS_IF_UNUSED
             null, // no permission for OP_AUTO_REVOKE_MANAGED_BY_INSTALLER
             null, // no permission for OP_NO_ISOLATED_STORAGE
+            null, // no permission for OP_PHONE_CALL_MICROPHONE
+            null, // no permission for OP_PHONE_CALL_CAMERA
+            null, // no permission for OP_RECORD_AUDIO_HOTWORD
     };
 
     /**
@@ -2048,6 +2103,9 @@
             null, // AUTO_REVOKE_PERMISSIONS_IF_UNUSED
             null, // AUTO_REVOKE_MANAGED_BY_INSTALLER
             null, // NO_ISOLATED_STORAGE
+            null, // PHONE_CALL_MICROPHONE
+            null, // PHONE_CALL_MICROPHONE
+            null, // RECORD_AUDIO_HOTWORD
     };
 
     /**
@@ -2155,6 +2213,9 @@
             null, // AUTO_REVOKE_PERMISSIONS_IF_UNUSED
             null, // AUTO_REVOKE_MANAGED_BY_INSTALLER
             null, // NO_ISOLATED_STORAGE
+            null, // PHONE_CALL_MICROPHONE
+            null, // PHONE_CALL_CAMERA
+            null, // RECORD_AUDIO_HOTWORD
     };
 
     /**
@@ -2261,6 +2322,9 @@
             AppOpsManager.MODE_DEFAULT, // OP_AUTO_REVOKE_PERMISSIONS_IF_UNUSED
             AppOpsManager.MODE_ALLOWED, // OP_AUTO_REVOKE_MANAGED_BY_INSTALLER
             AppOpsManager.MODE_ERRORED, // OP_NO_ISOLATED_STORAGE
+            AppOpsManager.MODE_ALLOWED, // PHONE_CALL_MICROPHONE
+            AppOpsManager.MODE_ALLOWED, // PHONE_CALL_CAMERA
+            AppOpsManager.MODE_ALLOWED, // OP_RECORD_AUDIO_HOTWORD
     };
 
     /**
@@ -2371,6 +2435,9 @@
             false, // AUTO_REVOKE_PERMISSIONS_IF_UNUSED
             false, // AUTO_REVOKE_MANAGED_BY_INSTALLER
             true, // NO_ISOLATED_STORAGE
+            false, // PHONE_CALL_MICROPHONE
+            false, // PHONE_CALL_CAMERA
+            false, // RECORD_AUDIO_HOTWORD
     };
 
     /**
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java
index 505b498..7effbb3 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -1901,10 +1901,8 @@
     @Override
     public Object getSystemService(String name) {
         if (vmIncorrectContextUseEnabled()) {
-            // We may override this API from outer context.
-            final boolean isUiContext = isUiContext() || isOuterUiContext();
             // Check incorrect Context usage.
-            if (isUiComponent(name) && !isUiContext) {
+            if (isUiComponent(name) && !isSelfOrOuterUiContext()) {
                 final String errorMessage = "Tried to access visual service "
                         + SystemServiceRegistry.getSystemServiceClassName(name)
                         + " from a non-visual Context:" + getOuterContext();
@@ -1921,15 +1919,17 @@
         return SystemServiceRegistry.getSystemService(this, name);
     }
 
-    private boolean isOuterUiContext() {
-        return getOuterContext() != null && getOuterContext().isUiContext();
-    }
-
     @Override
     public String getSystemServiceName(Class<?> serviceClass) {
         return SystemServiceRegistry.getSystemServiceName(serviceClass);
     }
 
+    // TODO(b/149463653): check if we still need this method after migrating IMS to WindowContext.
+    private boolean isSelfOrOuterUiContext() {
+        // We may override outer context's isUiContext
+        return isUiContext() || getOuterContext() != null && getOuterContext().isUiContext();
+    }
+
     /** @hide */
     @Override
     public boolean isUiContext() {
@@ -2376,7 +2376,6 @@
         context.setResources(createResources(mToken, mPackageInfo, mSplitName, displayId,
                 overrideConfiguration, getDisplayAdjustments(displayId).getCompatibilityInfo(),
                 mResources.getLoaders()));
-        context.mIsUiContext = isUiContext() || isOuterUiContext();
         return context;
     }
 
@@ -2396,6 +2395,11 @@
                 mResources.getLoaders()));
         context.mDisplay = display;
         context.mIsAssociatedWithDisplay = true;
+        // Note that even if a display context is derived from an UI context, it should not be
+        // treated as UI context because it does not handle configuration changes from the server
+        // side. If the context does need to handle configuration changes, please use
+        // Context#createWindowContext(int, Bundle).
+        context.mIsUiContext = false;
         return context;
     }
 
@@ -2481,9 +2485,9 @@
 
     @Override
     public Display getDisplay() {
-        if (!mIsSystemOrSystemUiContext && !mIsAssociatedWithDisplay) {
+        if (!mIsSystemOrSystemUiContext && !mIsAssociatedWithDisplay && !isSelfOrOuterUiContext()) {
             throw new UnsupportedOperationException("Tried to obtain display from a Context not "
-                    + "associated with  one. Only visual Contexts (such as Activity or one created "
+                    + "associated with one. Only visual Contexts (such as Activity or one created "
                     + "with Context#createWindowContext) or ones created with "
                     + "Context#createDisplayContext are associated with displays. Other types of "
                     + "Contexts are typically related to background entities and may return an "
@@ -2757,6 +2761,7 @@
             mDisplay = container.mDisplay;
             mIsAssociatedWithDisplay = container.mIsAssociatedWithDisplay;
             mIsSystemOrSystemUiContext = container.mIsSystemOrSystemUiContext;
+            mIsUiContext = container.isSelfOrOuterUiContext();
         } else {
             mBasePackageName = packageInfo.mPackageName;
             ApplicationInfo ainfo = packageInfo.getApplicationInfo();
diff --git a/core/java/android/app/IActivityManager.aidl b/core/java/android/app/IActivityManager.aidl
index 9459577..2fe7eea 100644
--- a/core/java/android/app/IActivityManager.aidl
+++ b/core/java/android/app/IActivityManager.aidl
@@ -683,4 +683,14 @@
      * Kills uid with the reason of permission change.
      */
     void killUidForPermissionChange(int appId, int userId, String reason);
+
+    /**
+     * Control the app freezer state. Returns true in case of success, false if the operation
+     * didn't succeed (for example, when the app freezer isn't supported). 
+     * Handling the freezer state via this method is reentrant, that is it can be 
+     * disabled and re-enabled multiple times in parallel. As long as there's a 1:1 disable to
+     * enable match, the freezer is re-enabled at last enable only.
+     * @param enable set it to true to enable the app freezer, false to disable it.
+     */
+    boolean enableAppFreezer(in boolean enable);
 }
diff --git a/core/java/android/app/LoadedApk.java b/core/java/android/app/LoadedApk.java
index f9b48e7..ffd02c92 100644
--- a/core/java/android/app/LoadedApk.java
+++ b/core/java/android/app/LoadedApk.java
@@ -802,12 +802,9 @@
 
         makePaths(mActivityThread, isBundledApp, mApplicationInfo, zipPaths, libPaths);
 
-        String libraryPermittedPath = mDataDir;
-        if (mActivityThread == null) {
-            // In a zygote context where mActivityThread is null we can't access the app data dir
-            // and including this in libraryPermittedPath would cause SELinux denials.
-            libraryPermittedPath = "";
-        }
+        // Including an inaccessible dir in libraryPermittedPath would cause SELinux denials
+        // when the loader attempts to canonicalise the path. so we don't.
+        String libraryPermittedPath = canAccessDataDir() ? mDataDir : "";
 
         if (isBundledApp) {
             // For bundled apps, add the base directory of the app (e.g.,
@@ -951,6 +948,33 @@
         }
     }
 
+    /**
+     * Return whether we can access the package's private data directory in order to be able to
+     * load code from it.
+     */
+    private boolean canAccessDataDir() {
+        // In a zygote context where mActivityThread is null we can't access the app data dir.
+        if (mActivityThread == null) {
+            return false;
+        }
+
+        // A package can access its own data directory (the common case, so short-circuit it).
+        if (Objects.equals(mPackageName, ActivityThread.currentPackageName())) {
+            return true;
+        }
+
+        // Temporarily disable logging of disk reads on the Looper thread as this is necessary -
+        // and the loader will access the directory anyway if we don't check it.
+        StrictMode.ThreadPolicy oldPolicy = allowThreadDiskReads();
+        try {
+            // We are constructing a classloader for a different package. It is likely,
+            // but not certain, that we can't acccess its app data dir - so check.
+            return new File(mDataDir).canExecute();
+        } finally {
+            setThreadPolicy(oldPolicy);
+        }
+    }
+
     @UnsupportedAppUsage
     public ClassLoader getClassLoader() {
         synchronized (this) {
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index 7391b0c..6707e74 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -4801,7 +4801,6 @@
             contentView.setViewVisibility(R.id.time, View.GONE);
             contentView.setImageViewIcon(R.id.profile_badge, null);
             contentView.setViewVisibility(R.id.profile_badge, View.GONE);
-            contentView.setViewVisibility(R.id.alerted_icon, View.GONE);
             mN.mUsesStandardHeader = false;
         }
 
diff --git a/core/java/android/app/ResourcesManager.java b/core/java/android/app/ResourcesManager.java
index 7477899..771528a 100644
--- a/core/java/android/app/ResourcesManager.java
+++ b/core/java/android/app/ResourcesManager.java
@@ -39,7 +39,6 @@
 import android.util.ArrayMap;
 import android.util.DisplayMetrics;
 import android.util.Log;
-import android.util.LruCache;
 import android.util.Pair;
 import android.util.Slog;
 import android.view.Display;
@@ -62,7 +61,6 @@
 import java.util.Objects;
 import java.util.WeakHashMap;
 import java.util.function.Consumer;
-import java.util.function.Predicate;
 
 /** @hide */
 public class ResourcesManager {
@@ -129,17 +127,30 @@
         }
     }
 
-    private static final boolean ENABLE_APK_ASSETS_CACHE = false;
-
     /**
-     * The ApkAssets we are caching and intend to hold strong references to.
+     * Loads {@link ApkAssets} and caches them to prevent their garbage collection while the
+     * instance is alive and reachable.
      */
-    private final LruCache<ApkKey, ApkAssets> mLoadedApkAssets =
-            (ENABLE_APK_ASSETS_CACHE) ? new LruCache<>(3) : null;
+    private class ApkAssetsSupplier {
+        final ArrayMap<ApkKey, ApkAssets> mLocalCache = new ArrayMap<>();
+
+        /**
+         * Retrieves the {@link ApkAssets} corresponding to the specified key, caches the ApkAssets
+         * within this instance, and inserts the loaded ApkAssets into the {@link #mCachedApkAssets}
+         * cache.
+         */
+        ApkAssets load(final ApkKey apkKey) throws IOException {
+            ApkAssets apkAssets = mLocalCache.get(apkKey);
+            if (apkAssets == null) {
+                apkAssets = loadApkAssets(apkKey);
+                mLocalCache.put(apkKey, apkAssets);
+            }
+            return apkAssets;
+        }
+    }
 
     /**
-     * The ApkAssets that are being referenced in the wild that we can reuse, even if they aren't
-     * in our LRU cache. Bonus resources :)
+     * The ApkAssets that are being referenced in the wild that we can reuse.
      */
     private final ArrayMap<ApkKey, WeakReference<ApkAssets>> mCachedApkAssets = new ArrayMap<>();
 
@@ -337,49 +348,78 @@
         return "/data/resource-cache/" + path.substring(1).replace('/', '@') + "@idmap";
     }
 
-    private @NonNull ApkAssets loadApkAssets(String path, boolean sharedLib, boolean overlay)
-            throws IOException {
-        final ApkKey newKey = new ApkKey(path, sharedLib, overlay);
-        ApkAssets apkAssets = null;
-        if (mLoadedApkAssets != null) {
-            apkAssets = mLoadedApkAssets.get(newKey);
-            if (apkAssets != null && apkAssets.isUpToDate()) {
-                return apkAssets;
-            }
-        }
+    private @NonNull ApkAssets loadApkAssets(@NonNull final ApkKey key) throws IOException {
+        ApkAssets apkAssets;
 
         // Optimistically check if this ApkAssets exists somewhere else.
-        final WeakReference<ApkAssets> apkAssetsRef = mCachedApkAssets.get(newKey);
-        if (apkAssetsRef != null) {
-            apkAssets = apkAssetsRef.get();
-            if (apkAssets != null && apkAssets.isUpToDate()) {
-                if (mLoadedApkAssets != null) {
-                    mLoadedApkAssets.put(newKey, apkAssets);
+        synchronized (this) {
+            final WeakReference<ApkAssets> apkAssetsRef = mCachedApkAssets.get(key);
+            if (apkAssetsRef != null) {
+                apkAssets = apkAssetsRef.get();
+                if (apkAssets != null && apkAssets.isUpToDate()) {
+                    return apkAssets;
+                } else {
+                    // Clean up the reference.
+                    mCachedApkAssets.remove(key);
                 }
-
-                return apkAssets;
-            } else {
-                // Clean up the reference.
-                mCachedApkAssets.remove(newKey);
             }
         }
 
         // We must load this from disk.
-        if (overlay) {
-            apkAssets = ApkAssets.loadOverlayFromPath(overlayPathToIdmapPath(path), 0 /*flags*/);
+        if (key.overlay) {
+            apkAssets = ApkAssets.loadOverlayFromPath(overlayPathToIdmapPath(key.path),
+                    0 /*flags*/);
         } else {
-            apkAssets = ApkAssets.loadFromPath(path, sharedLib ? ApkAssets.PROPERTY_DYNAMIC : 0);
+            apkAssets = ApkAssets.loadFromPath(key.path,
+                    key.sharedLib ? ApkAssets.PROPERTY_DYNAMIC : 0);
         }
 
-        if (mLoadedApkAssets != null) {
-            mLoadedApkAssets.put(newKey, apkAssets);
+        synchronized (this) {
+            mCachedApkAssets.put(key, new WeakReference<>(apkAssets));
         }
 
-        mCachedApkAssets.put(newKey, new WeakReference<>(apkAssets));
         return apkAssets;
     }
 
     /**
+     * Retrieves a list of apk keys representing the ApkAssets that should be loaded for
+     * AssetManagers mapped to the {@param key}.
+     */
+    private static @NonNull ArrayList<ApkKey> extractApkKeys(@NonNull final ResourcesKey key) {
+        final ArrayList<ApkKey> apkKeys = new ArrayList<>();
+
+        // resDir can be null if the 'android' package is creating a new Resources object.
+        // This is fine, since each AssetManager automatically loads the 'android' package
+        // already.
+        if (key.mResDir != null) {
+            apkKeys.add(new ApkKey(key.mResDir, false /*sharedLib*/, false /*overlay*/));
+        }
+
+        if (key.mSplitResDirs != null) {
+            for (final String splitResDir : key.mSplitResDirs) {
+                apkKeys.add(new ApkKey(splitResDir, false /*sharedLib*/, false /*overlay*/));
+            }
+        }
+
+        if (key.mLibDirs != null) {
+            for (final String libDir : key.mLibDirs) {
+                // Avoid opening files we know do not have resources, like code-only .jar files.
+                if (libDir.endsWith(".apk")) {
+                    apkKeys.add(new ApkKey(libDir, true /*sharedLib*/, false /*overlay*/));
+                }
+            }
+        }
+
+        if (key.mOverlayDirs != null) {
+            for (final String idmapPath : key.mOverlayDirs) {
+                apkKeys.add(new ApkKey(idmapPath, false /*sharedLib*/, true /*overlay*/));
+            }
+        }
+
+        return apkKeys;
+    }
+
+    /**
      * Creates an AssetManager from the paths within the ResourcesKey.
      *
      * This can be overridden in tests so as to avoid creating a real AssetManager with
@@ -390,64 +430,38 @@
     @VisibleForTesting
     @UnsupportedAppUsage
     protected @Nullable AssetManager createAssetManager(@NonNull final ResourcesKey key) {
+        return createAssetManager(key, /* apkSupplier */ null);
+    }
+
+    /**
+     * Variant of {@link #createAssetManager(ResourcesKey)} that attempts to load ApkAssets
+     * from an {@link ApkAssetsSupplier} if non-null; otherwise ApkAssets are loaded using
+     * {@link #loadApkAssets(ApkKey)}.
+     */
+    private @Nullable AssetManager createAssetManager(@NonNull final ResourcesKey key,
+            @Nullable ApkAssetsSupplier apkSupplier) {
         final AssetManager.Builder builder = new AssetManager.Builder();
 
-        // resDir can be null if the 'android' package is creating a new Resources object.
-        // This is fine, since each AssetManager automatically loads the 'android' package
-        // already.
-        if (key.mResDir != null) {
+        final ArrayList<ApkKey> apkKeys = extractApkKeys(key);
+        for (int i = 0, n = apkKeys.size(); i < n; i++) {
+            final ApkKey apkKey = apkKeys.get(i);
             try {
-                builder.addApkAssets(loadApkAssets(key.mResDir, false /*sharedLib*/,
-                        false /*overlay*/));
+                builder.addApkAssets(
+                        (apkSupplier != null) ? apkSupplier.load(apkKey) : loadApkAssets(apkKey));
             } catch (IOException e) {
-                Log.e(TAG, "failed to add asset path " + key.mResDir);
-                return null;
-            }
-        }
-
-        if (key.mSplitResDirs != null) {
-            for (final String splitResDir : key.mSplitResDirs) {
-                try {
-                    builder.addApkAssets(loadApkAssets(splitResDir, false /*sharedLib*/,
-                            false /*overlay*/));
-                } catch (IOException e) {
-                    Log.e(TAG, "failed to add split asset path " + splitResDir);
+                if (apkKey.overlay) {
+                    Log.w(TAG, String.format("failed to add overlay path '%s'", apkKey.path), e);
+                } else if (apkKey.sharedLib) {
+                    Log.w(TAG, String.format(
+                            "asset path '%s' does not exist or contains no resources",
+                            apkKey.path), e);
+                } else {
+                    Log.e(TAG, String.format("failed to add asset path '%s'", apkKey.path), e);
                     return null;
                 }
             }
         }
 
-        if (key.mLibDirs != null) {
-            for (final String libDir : key.mLibDirs) {
-                if (libDir.endsWith(".apk")) {
-                    // Avoid opening files we know do not have resources,
-                    // like code-only .jar files.
-                    try {
-                        builder.addApkAssets(loadApkAssets(libDir, true /*sharedLib*/,
-                                false /*overlay*/));
-                    } catch (IOException e) {
-                        Log.w(TAG, "Asset path '" + libDir +
-                                "' does not exist or contains no resources.");
-
-                        // continue.
-                    }
-                }
-            }
-        }
-
-        if (key.mOverlayDirs != null) {
-            for (final String idmapPath : key.mOverlayDirs) {
-                try {
-                    builder.addApkAssets(loadApkAssets(idmapPath, false /*sharedLib*/,
-                            true /*overlay*/));
-                } catch (IOException e) {
-                    Log.w(TAG, "failed to add overlay path " + idmapPath);
-
-                    // continue.
-                }
-            }
-        }
-
         if (key.mLoaders != null) {
             for (final ResourcesLoader loader : key.mLoaders) {
                 builder.addLoader(loader);
@@ -480,24 +494,6 @@
 
             pw.println("ResourcesManager:");
             pw.increaseIndent();
-            if (mLoadedApkAssets != null) {
-                pw.print("cached apks: total=");
-                pw.print(mLoadedApkAssets.size());
-                pw.print(" created=");
-                pw.print(mLoadedApkAssets.createCount());
-                pw.print(" evicted=");
-                pw.print(mLoadedApkAssets.evictionCount());
-                pw.print(" hit=");
-                pw.print(mLoadedApkAssets.hitCount());
-                pw.print(" miss=");
-                pw.print(mLoadedApkAssets.missCount());
-                pw.print(" max=");
-                pw.print(mLoadedApkAssets.maxSize());
-            } else {
-                pw.print("cached apks: 0 [cache disabled]");
-            }
-            pw.println();
-
             pw.print("total apks: ");
             pw.println(countLiveReferences(mCachedApkAssets.values()));
 
@@ -533,11 +529,12 @@
         return config;
     }
 
-    private @Nullable ResourcesImpl createResourcesImpl(@NonNull ResourcesKey key) {
+    private @Nullable ResourcesImpl createResourcesImpl(@NonNull ResourcesKey key,
+            @Nullable ApkAssetsSupplier apkSupplier) {
         final DisplayAdjustments daj = new DisplayAdjustments(key.mOverrideConfiguration);
         daj.setCompatibilityInfo(key.mCompatInfo);
 
-        final AssetManager assets = createAssetManager(key);
+        final AssetManager assets = createAssetManager(key, apkSupplier);
         if (assets == null) {
             return null;
         }
@@ -575,9 +572,18 @@
      */
     private @Nullable ResourcesImpl findOrCreateResourcesImplForKeyLocked(
             @NonNull ResourcesKey key) {
+        return findOrCreateResourcesImplForKeyLocked(key, /* apkSupplier */ null);
+    }
+
+    /**
+     * Variant of {@link #findOrCreateResourcesImplForKeyLocked(ResourcesKey)} that attempts to
+     * load ApkAssets from a {@link ApkAssetsSupplier} when creating a new ResourcesImpl.
+     */
+    private @Nullable ResourcesImpl findOrCreateResourcesImplForKeyLocked(
+            @NonNull ResourcesKey key, @Nullable ApkAssetsSupplier apkSupplier) {
         ResourcesImpl impl = findResourcesImplForKeyLocked(key);
         if (impl == null) {
-            impl = createResourcesImpl(key);
+            impl = createResourcesImpl(key, apkSupplier);
             if (impl != null) {
                 mResourceImpls.put(key, new WeakReference<>(impl));
             }
@@ -766,7 +772,7 @@
             }
 
             // Now request an actual Resources object.
-            return createResources(token, key, classLoader);
+            return createResources(token, key, classLoader, /* apkSupplier */ null);
         } finally {
             Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
         }
@@ -810,18 +816,45 @@
     }
 
     /**
+     * Creates an {@link ApkAssetsSupplier} and loads all the ApkAssets required by the {@param key}
+     * into the supplier. This should be done while the lock is not held to prevent performing I/O
+     * while holding the lock.
+     */
+    private @NonNull ApkAssetsSupplier createApkAssetsSupplierNotLocked(@NonNull ResourcesKey key) {
+        Trace.traceBegin(Trace.TRACE_TAG_RESOURCES,
+                "ResourcesManager#createApkAssetsSupplierNotLocked");
+        try {
+            final ApkAssetsSupplier supplier = new ApkAssetsSupplier();
+            final ArrayList<ApkKey> apkKeys = extractApkKeys(key);
+            for (int i = 0, n = apkKeys.size(); i < n; i++) {
+                final ApkKey apkKey = apkKeys.get(i);
+                try {
+                    supplier.load(apkKey);
+                } catch (IOException e) {
+                    Log.w(TAG, String.format("failed to preload asset path '%s'", apkKey.path), e);
+                }
+            }
+            return supplier;
+        } finally {
+            Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
+        }
+    }
+
+    /**
      * Creates a Resources object set with a ResourcesImpl object matching the given key.
      *
      * @param activityToken The Activity this Resources object should be associated with.
      * @param key The key describing the parameters of the ResourcesImpl object.
      * @param classLoader The classloader to use for the Resources object.
      *                    If null, {@link ClassLoader#getSystemClassLoader()} is used.
+     * @param apkSupplier The apk assets supplier to use when creating a new ResourcesImpl object.
      * @return A Resources object that gets updated when
      *         {@link #applyConfigurationToResourcesLocked(Configuration, CompatibilityInfo)}
      *         is called.
      */
     private @Nullable Resources createResources(@Nullable IBinder activityToken,
-            @NonNull ResourcesKey key, @NonNull ClassLoader classLoader) {
+            @NonNull ResourcesKey key, @NonNull ClassLoader classLoader,
+            @Nullable ApkAssetsSupplier apkSupplier) {
         synchronized (this) {
             if (DEBUG) {
                 Throwable here = new Throwable();
@@ -829,7 +862,7 @@
                 Slog.w(TAG, "!! Get resources for activity=" + activityToken + " key=" + key, here);
             }
 
-            ResourcesImpl resourcesImpl = findOrCreateResourcesImplForKeyLocked(key);
+            ResourcesImpl resourcesImpl = findOrCreateResourcesImplForKeyLocked(key, apkSupplier);
             if (resourcesImpl == null) {
                 return null;
             }
@@ -898,7 +931,10 @@
                 rebaseKeyForActivity(activityToken, key);
             }
 
-            return createResources(activityToken, key, classLoader);
+            // Preload the ApkAssets required by the key to prevent performing heavy I/O while the
+            // ResourcesManager lock is held.
+            final ApkAssetsSupplier assetsSupplier = createApkAssetsSupplierNotLocked(key);
+            return createResources(activityToken, key, classLoader, assetsSupplier);
         } finally {
             Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
         }
@@ -969,7 +1005,13 @@
                     final ResourcesKey newKey = rebaseActivityOverrideConfig(resources, oldConfig,
                             overrideConfig, displayId);
                     if (newKey != null) {
-                        updateActivityResources(resources, newKey, false);
+                        final ResourcesImpl resourcesImpl =
+                                findOrCreateResourcesImplForKeyLocked(newKey);
+                        if (resourcesImpl != null && resourcesImpl != resources.getImpl()) {
+                            // Set the ResourcesImpl, updating it for all users of this Resources
+                            // object.
+                            resources.setImpl(resourcesImpl);
+                        }
                     }
                 }
             }
@@ -1024,33 +1066,21 @@
         return newKey;
     }
 
-    private void updateActivityResources(Resources resources, ResourcesKey newKey,
-            boolean hasLoader) {
-        final ResourcesImpl resourcesImpl;
-
-        if (hasLoader) {
-            // Loaders always get new Impls because they cannot be shared
-            resourcesImpl = createResourcesImpl(newKey);
-        } else {
-            resourcesImpl = findOrCreateResourcesImplForKeyLocked(newKey);
-        }
-
-        if (resourcesImpl != null && resourcesImpl != resources.getImpl()) {
-            // Set the ResourcesImpl, updating it for all users of this Resources
-            // object.
-            resources.setImpl(resourcesImpl);
-        }
-    }
-
     public final boolean applyConfigurationToResources(@NonNull Configuration config,
             @Nullable CompatibilityInfo compat) {
         synchronized(this) {
-            return applyConfigurationToResourcesLocked(config, compat);
+            return applyConfigurationToResourcesLocked(config, compat, null /* adjustments */);
         }
     }
 
     public final boolean applyConfigurationToResourcesLocked(@NonNull Configuration config,
-                                                             @Nullable CompatibilityInfo compat) {
+            @Nullable CompatibilityInfo compat) {
+        return applyConfigurationToResourcesLocked(config, compat, null /* adjustments */);
+    }
+
+    /** Applies the global configuration to the managed resources. */
+    public final boolean applyConfigurationToResourcesLocked(@NonNull Configuration config,
+            @Nullable CompatibilityInfo compat, @Nullable DisplayAdjustments adjustments) {
         try {
             Trace.traceBegin(Trace.TRACE_TAG_RESOURCES,
                     "ResourcesManager#applyConfigurationToResourcesLocked");
@@ -1074,6 +1104,11 @@
                         | ActivityInfo.CONFIG_SMALLEST_SCREEN_SIZE;
             }
 
+            if (adjustments != null) {
+                // Currently the only case where the adjustment takes effect is to simulate placing
+                // an app in a rotated display.
+                adjustments.adjustGlobalAppMetrics(defaultDisplayMetrics);
+            }
             Resources.updateSystemConfiguration(config, defaultDisplayMetrics, compat);
 
             ApplicationPackageManager.configurationChanged();
diff --git a/core/java/android/app/TaskInfo.java b/core/java/android/app/TaskInfo.java
index f3f00e5..f99d4e9 100644
--- a/core/java/android/app/TaskInfo.java
+++ b/core/java/android/app/TaskInfo.java
@@ -176,6 +176,13 @@
      */
     public boolean isResizeable;
 
+    /**
+     * Screen orientation set by {@link #baseActivity} via
+     * {@link Activity#setRequestedOrientation(int)}.
+     * @hide
+     */
+    public @ActivityInfo.ScreenOrientation int requestedOrientation;
+
     TaskInfo() {
         // Do nothing
     }
@@ -247,6 +254,7 @@
                 ? ActivityInfo.CREATOR.createFromParcel(source)
                 : null;
         isResizeable = source.readBoolean();
+        requestedOrientation = source.readInt();
     }
 
     /**
@@ -297,6 +305,7 @@
             topActivityInfo.writeToParcel(dest, flags);
         }
         dest.writeBoolean(isResizeable);
+        dest.writeInt(requestedOrientation);
     }
 
     @Override
@@ -315,6 +324,7 @@
                 + " token=" + token
                 + " topActivityType=" + topActivityType
                 + " pictureInPictureParams=" + pictureInPictureParams
-                + " topActivityInfo=" + topActivityInfo;
+                + " topActivityInfo=" + topActivityInfo
+                + " requestedOrientation=" + requestedOrientation;
     }
 }
diff --git a/core/java/android/app/UiModeManager.java b/core/java/android/app/UiModeManager.java
index 558e222..e2fc5dbf 100644
--- a/core/java/android/app/UiModeManager.java
+++ b/core/java/android/app/UiModeManager.java
@@ -509,6 +509,9 @@
     }
 
     /**
+     * Activating night mode for the current user
+     *
+     * @return {@code true} if the change is successful
      * @hide
      */
     public boolean setNightModeActivated(boolean active) {
diff --git a/core/java/android/app/prediction/AppPredictor.java b/core/java/android/app/prediction/AppPredictor.java
index 4517c6d..fd1b9e3 100644
--- a/core/java/android/app/prediction/AppPredictor.java
+++ b/core/java/android/app/prediction/AppPredictor.java
@@ -82,6 +82,8 @@
     private final AppPredictionSessionId mSessionId;
     private final ArrayMap<Callback, CallbackWrapper> mRegisteredCallbacks = new ArrayMap<>();
 
+    private final IBinder mToken = new Binder();
+
     /**
      * Creates a new Prediction client.
      * <p>
@@ -97,7 +99,7 @@
         mSessionId = new AppPredictionSessionId(
                 context.getPackageName() + ":" + UUID.randomUUID().toString(), context.getUserId());
         try {
-            mPredictionManager.createPredictionSession(predictionContext, mSessionId);
+            mPredictionManager.createPredictionSession(predictionContext, mSessionId, mToken);
         } catch (RemoteException e) {
             Log.e(TAG, "Failed to create predictor", e);
             e.rethrowAsRuntimeException();
diff --git a/core/java/android/app/prediction/IPredictionManager.aidl b/core/java/android/app/prediction/IPredictionManager.aidl
index 587e3fd..863fc6f9 100644
--- a/core/java/android/app/prediction/IPredictionManager.aidl
+++ b/core/java/android/app/prediction/IPredictionManager.aidl
@@ -29,7 +29,7 @@
 interface IPredictionManager {
 
     void createPredictionSession(in AppPredictionContext context,
-            in AppPredictionSessionId sessionId);
+            in AppPredictionSessionId sessionId, in IBinder token);
 
     void notifyAppTargetEvent(in AppPredictionSessionId sessionId, in AppTargetEvent event);
 
diff --git a/core/java/android/app/slice/SliceProvider.java b/core/java/android/app/slice/SliceProvider.java
index cef6ab0..46be548 100644
--- a/core/java/android/app/slice/SliceProvider.java
+++ b/core/java/android/app/slice/SliceProvider.java
@@ -153,6 +153,11 @@
      */
     public static final String EXTRA_PKG = "pkg";
     /**
+     * @Deprecated provider pkg is now being extracted in SlicePermissionActivity
+     * @hide
+     */
+    public static final String EXTRA_PROVIDER_PKG = "provider_pkg";
+    /**
      * @hide
      */
     public static final String EXTRA_RESULT = "result";
@@ -515,6 +520,7 @@
                 com.android.internal.R.string.config_slicePermissionComponent)));
         intent.putExtra(EXTRA_BIND_URI, sliceUri);
         intent.putExtra(EXTRA_PKG, callingPackage);
+        intent.putExtra(EXTRA_PROVIDER_PKG, context.getPackageName());
         // Unique pending intent.
         intent.setData(sliceUri.buildUpon().appendQueryParameter("package", callingPackage)
                 .build());
diff --git a/core/java/android/bluetooth/BluetoothCodecConfig.java b/core/java/android/bluetooth/BluetoothCodecConfig.java
index e07bc02..a52fc89 100644
--- a/core/java/android/bluetooth/BluetoothCodecConfig.java
+++ b/core/java/android/bluetooth/BluetoothCodecConfig.java
@@ -51,19 +51,25 @@
     @Retention(RetentionPolicy.SOURCE)
     public @interface SourceCodecType {}
 
+    @UnsupportedAppUsage
     public static final int SOURCE_CODEC_TYPE_SBC = 0;
 
+    @UnsupportedAppUsage
     public static final int SOURCE_CODEC_TYPE_AAC = 1;
 
+    @UnsupportedAppUsage
     public static final int SOURCE_CODEC_TYPE_APTX = 2;
 
+    @UnsupportedAppUsage
     public static final int SOURCE_CODEC_TYPE_APTX_HD = 3;
 
+    @UnsupportedAppUsage
     public static final int SOURCE_CODEC_TYPE_LDAC = 4;
 
+    @UnsupportedAppUsage
     public static final int SOURCE_CODEC_TYPE_MAX = 5;
 
-
+    @UnsupportedAppUsage
     public static final int SOURCE_CODEC_TYPE_INVALID = 1000 * 1000;
 
     /** @hide */
@@ -75,10 +81,13 @@
     @Retention(RetentionPolicy.SOURCE)
     public @interface CodecPriority {}
 
+    @UnsupportedAppUsage
     public static final int CODEC_PRIORITY_DISABLED = -1;
 
+    @UnsupportedAppUsage
     public static final int CODEC_PRIORITY_DEFAULT = 0;
 
+    @UnsupportedAppUsage
     public static final int CODEC_PRIORITY_HIGHEST = 1000 * 1000;
 
 
@@ -95,18 +104,25 @@
     @Retention(RetentionPolicy.SOURCE)
     public @interface SampleRate {}
 
+    @UnsupportedAppUsage
     public static final int SAMPLE_RATE_NONE = 0;
 
+    @UnsupportedAppUsage
     public static final int SAMPLE_RATE_44100 = 0x1 << 0;
 
+    @UnsupportedAppUsage
     public static final int SAMPLE_RATE_48000 = 0x1 << 1;
 
+    @UnsupportedAppUsage
     public static final int SAMPLE_RATE_88200 = 0x1 << 2;
 
+    @UnsupportedAppUsage
     public static final int SAMPLE_RATE_96000 = 0x1 << 3;
 
+    @UnsupportedAppUsage
     public static final int SAMPLE_RATE_176400 = 0x1 << 4;
 
+    @UnsupportedAppUsage
     public static final int SAMPLE_RATE_192000 = 0x1 << 5;
 
 
@@ -120,12 +136,16 @@
     @Retention(RetentionPolicy.SOURCE)
     public @interface BitsPerSample {}
 
+    @UnsupportedAppUsage
     public static final int BITS_PER_SAMPLE_NONE = 0;
 
+    @UnsupportedAppUsage
     public static final int BITS_PER_SAMPLE_16 = 0x1 << 0;
 
+    @UnsupportedAppUsage
     public static final int BITS_PER_SAMPLE_24 = 0x1 << 1;
 
+    @UnsupportedAppUsage
     public static final int BITS_PER_SAMPLE_32 = 0x1 << 2;
 
 
@@ -138,10 +158,13 @@
     @Retention(RetentionPolicy.SOURCE)
     public @interface ChannelMode {}
 
+    @UnsupportedAppUsage
     public static final int CHANNEL_MODE_NONE = 0;
 
+    @UnsupportedAppUsage
     public static final int CHANNEL_MODE_MONO = 0x1 << 0;
 
+    @UnsupportedAppUsage
     public static final int CHANNEL_MODE_STEREO = 0x1 << 1;
 
     private final @SourceCodecType int mCodecType;
@@ -154,6 +177,7 @@
     private final long mCodecSpecific3;
     private final long mCodecSpecific4;
 
+    @UnsupportedAppUsage
     public BluetoothCodecConfig(@SourceCodecType int codecType, @CodecPriority int codecPriority,
             @SampleRate int sampleRate, @BitsPerSample int bitsPerSample,
             @ChannelMode int channelMode, long codecSpecific1,
@@ -170,6 +194,7 @@
         mCodecSpecific4 = codecSpecific4;
     }
 
+    @UnsupportedAppUsage
     public BluetoothCodecConfig(@SourceCodecType int codecType) {
         mCodecType = codecType;
         mCodecPriority = BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT;
@@ -391,6 +416,7 @@
      *
      * @return the codec type
      */
+    @UnsupportedAppUsage
     public @SourceCodecType int getCodecType() {
         return mCodecType;
     }
@@ -411,6 +437,7 @@
      *
      * @return the codec priority
      */
+    @UnsupportedAppUsage
     public @CodecPriority int getCodecPriority() {
         return mCodecPriority;
     }
@@ -441,6 +468,7 @@
      *
      * @return the codec sample rate
      */
+    @UnsupportedAppUsage
     public @SampleRate int getSampleRate() {
         return mSampleRate;
     }
@@ -455,6 +483,7 @@
      *
      * @return the codec bits per sample
      */
+    @UnsupportedAppUsage
     public @BitsPerSample int getBitsPerSample() {
         return mBitsPerSample;
     }
@@ -479,6 +508,7 @@
      *
      * @return a codec specific value1.
      */
+    @UnsupportedAppUsage
     public long getCodecSpecific1() {
         return mCodecSpecific1;
     }
diff --git a/core/java/android/bluetooth/BluetoothCodecStatus.java b/core/java/android/bluetooth/BluetoothCodecStatus.java
index 1e394b8..7b567b4 100644
--- a/core/java/android/bluetooth/BluetoothCodecStatus.java
+++ b/core/java/android/bluetooth/BluetoothCodecStatus.java
@@ -17,6 +17,7 @@
 package android.bluetooth;
 
 import android.annotation.Nullable;
+import android.compat.annotation.UnsupportedAppUsage;
 import android.os.Parcel;
 import android.os.Parcelable;
 
@@ -38,6 +39,7 @@
      * This extra represents the current codec status of the A2DP
      * profile.
      */
+    @UnsupportedAppUsage
     public static final String EXTRA_CODEC_STATUS =
             "android.bluetooth.extra.CODEC_STATUS";
 
@@ -196,6 +198,7 @@
      *
      * @return the current codec configuration
      */
+    @UnsupportedAppUsage
     public @Nullable BluetoothCodecConfig getCodecConfig() {
         return mCodecConfig;
     }
@@ -205,6 +208,7 @@
      *
      * @return an array with the codecs local capabilities
      */
+    @UnsupportedAppUsage
     public @Nullable BluetoothCodecConfig[] getCodecsLocalCapabilities() {
         return mCodecsLocalCapabilities;
     }
@@ -214,6 +218,7 @@
      *
      * @return an array with the codecs selectable capabilities
      */
+    @UnsupportedAppUsage
     public @Nullable BluetoothCodecConfig[] getCodecsSelectableCapabilities() {
         return mCodecsSelectableCapabilities;
     }
diff --git a/core/java/android/content/pm/PackageInstaller.java b/core/java/android/content/pm/PackageInstaller.java
index 6a553cf..ddcd459 100644
--- a/core/java/android/content/pm/PackageInstaller.java
+++ b/core/java/android/content/pm/PackageInstaller.java
@@ -1291,9 +1291,8 @@
          *
          * @throws PackageManager.NameNotFoundException if the new owner could not be found.
          * @throws SecurityException if called after the session has been committed or abandoned.
-         * @throws SecurityException if the session does not update the original installer
-         * @throws SecurityException if streams opened through
-         *                           {@link #openWrite(String, long, long) are still open.
+         * @throws IllegalArgumentException if streams opened through
+         *                                  {@link #openWrite(String, long, long) are still open.
          */
         public void transfer(@NonNull String packageName)
                 throws PackageManager.NameNotFoundException {
diff --git a/core/java/android/content/pm/UserInfo.java b/core/java/android/content/pm/UserInfo.java
index aca5b45..08b23b0 100644
--- a/core/java/android/content/pm/UserInfo.java
+++ b/core/java/android/content/pm/UserInfo.java
@@ -221,6 +221,14 @@
     public boolean preCreated;
 
     /**
+     * When {@code true}, it indicates this user was created by converting a {@link #preCreated}
+     * user.
+     *
+     * <p><b>NOTE: </b>only used for debugging purposes, it's not set when marshalled to a parcel.
+     */
+    public boolean convertedFromPreCreated;
+
+    /**
      * Creates a UserInfo whose user type is determined automatically by the flags according to
      * {@link #getDefaultUserType}; can only be used for user types handled there.
      */
@@ -413,6 +421,7 @@
         lastLoggedInFingerprint = orig.lastLoggedInFingerprint;
         partial = orig.partial;
         preCreated = orig.preCreated;
+        convertedFromPreCreated = orig.convertedFromPreCreated;
         profileGroupId = orig.profileGroupId;
         restrictedProfileParentId = orig.restrictedProfileParentId;
         guestToRemove = orig.guestToRemove;
@@ -440,6 +449,7 @@
                 + ", type=" + userType
                 + ", flags=" + flagsToString(flags)
                 + (preCreated ? " (pre-created)" : "")
+                + (convertedFromPreCreated ? " (converted)" : "")
                 + (partial ? " (partial)" : "")
                 + "]";
     }
diff --git a/core/java/android/content/res/Configuration.java b/core/java/android/content/res/Configuration.java
index 89653b3..6407f9b 100644
--- a/core/java/android/content/res/Configuration.java
+++ b/core/java/android/content/res/Configuration.java
@@ -45,7 +45,6 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.TestApi;
-import android.app.UiModeManager;
 import android.app.WindowConfiguration;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.LocaleProto;
@@ -928,7 +927,13 @@
         fontScale = o.fontScale;
         mcc = o.mcc;
         mnc = o.mnc;
-        locale = o.locale == null ? null : (Locale) o.locale.clone();
+        if (o.locale == null) {
+            locale = null;
+        } else if (!o.locale.equals(locale)) {
+            // Only clone a new Locale instance if we need to:  the clone() is
+            // both CPU and GC intensive.
+            locale = (Locale) o.locale.clone();
+        }
         o.fixUpLocaleList();
         mLocaleList = o.mLocaleList;
         userSetLocale = o.userSetLocale;
@@ -1624,7 +1629,10 @@
         if ((mask & ActivityInfo.CONFIG_LOCALE) != 0) {
             mLocaleList = delta.mLocaleList;
             if (!mLocaleList.isEmpty()) {
-                locale = (Locale) delta.locale.clone();
+                if (!delta.locale.equals(locale)) {
+                    // Don't churn a new Locale clone unless we're actually changing it
+                    locale = (Locale) delta.locale.clone();
+                }
             }
         }
         if ((mask & ActivityInfo.CONFIG_LAYOUT_DIRECTION) != 0) {
diff --git a/core/java/android/database/sqlite/SQLiteQueryBuilder.java b/core/java/android/database/sqlite/SQLiteQueryBuilder.java
index 669d046..e9c59f5 100644
--- a/core/java/android/database/sqlite/SQLiteQueryBuilder.java
+++ b/core/java/android/database/sqlite/SQLiteQueryBuilder.java
@@ -798,6 +798,7 @@
     }
 
     private void enforceStrictToken(@NonNull String token) {
+        if (TextUtils.isEmpty(token)) return;
         if (isTableOrColumn(token)) return;
         if (SQLiteTokenizer.isFunction(token)) return;
         if (SQLiteTokenizer.isType(token)) return;
diff --git a/core/java/android/hardware/camera2/impl/CameraMetadataNative.java b/core/java/android/hardware/camera2/impl/CameraMetadataNative.java
index 986e6ea..9d4ab0b 100644
--- a/core/java/android/hardware/camera2/impl/CameraMetadataNative.java
+++ b/core/java/android/hardware/camera2/impl/CameraMetadataNative.java
@@ -72,6 +72,7 @@
 import android.util.Size;
 
 import dalvik.annotation.optimization.FastNative;
+import dalvik.system.VMRuntime;
 
 import java.io.IOException;
 import java.nio.ByteBuffer;
@@ -351,6 +352,7 @@
         if (mMetadataPtr == 0) {
             throw new OutOfMemoryError("Failed to allocate native CameraMetadata");
         }
+        updateNativeAllocation();
     }
 
     /**
@@ -362,6 +364,7 @@
         if (mMetadataPtr == 0) {
             throw new OutOfMemoryError("Failed to allocate native CameraMetadata");
         }
+        updateNativeAllocation();
     }
 
     /**
@@ -443,6 +446,7 @@
 
     public void readFromParcel(Parcel in) {
         nativeReadFromParcel(in, mMetadataPtr);
+        updateNativeAllocation();
     }
 
     /**
@@ -533,6 +537,11 @@
         // Delete native pointer, but does not clear it
         nativeClose(mMetadataPtr);
         mMetadataPtr = 0;
+
+        if (mBufferSize > 0) {
+            VMRuntime.getRuntime().registerNativeFree(mBufferSize);
+        }
+        mBufferSize = 0;
     }
 
     private <T> T getBase(CameraCharacteristics.Key<T> key) {
@@ -1645,9 +1654,26 @@
         return true;
     }
 
+    private void updateNativeAllocation() {
+        long currentBufferSize = nativeGetBufferSize(mMetadataPtr);
+
+        if (currentBufferSize != mBufferSize) {
+            if (mBufferSize > 0) {
+                VMRuntime.getRuntime().registerNativeFree(mBufferSize);
+            }
+
+            mBufferSize = currentBufferSize;
+
+            if (mBufferSize > 0) {
+                VMRuntime.getRuntime().registerNativeAllocation(mBufferSize);
+            }
+        }
+    }
+
     private int mCameraId = -1;
     private boolean mHasMandatoryConcurrentStreams = false;
     private Size mDisplaySize = new Size(0, 0);
+    private long mBufferSize = 0;
 
     /**
      * Set the current camera Id.
@@ -1705,6 +1731,8 @@
     private static synchronized native boolean nativeIsEmpty(long ptr);
     @FastNative
     private static synchronized native int nativeGetEntryCount(long ptr);
+    @FastNative
+    private static synchronized native long nativeGetBufferSize(long ptr);
 
     @UnsupportedAppUsage
     @FastNative
@@ -1744,6 +1772,8 @@
         mCameraId = other.mCameraId;
         mHasMandatoryConcurrentStreams = other.mHasMandatoryConcurrentStreams;
         mDisplaySize = other.mDisplaySize;
+        updateNativeAllocation();
+        other.updateNativeAllocation();
     }
 
     /**
diff --git a/core/java/android/hardware/fingerprint/FingerprintManager.java b/core/java/android/hardware/fingerprint/FingerprintManager.java
index d57a7e4b..6900105 100644
--- a/core/java/android/hardware/fingerprint/FingerprintManager.java
+++ b/core/java/android/hardware/fingerprint/FingerprintManager.java
@@ -19,6 +19,7 @@
 import static android.Manifest.permission.INTERACT_ACROSS_USERS;
 import static android.Manifest.permission.MANAGE_FINGERPRINT;
 import static android.Manifest.permission.USE_BIOMETRIC;
+import static android.Manifest.permission.USE_BIOMETRIC_INTERNAL;
 import static android.Manifest.permission.USE_FINGERPRINT;
 
 import android.annotation.NonNull;
@@ -75,11 +76,13 @@
     private static final int MSG_ERROR = 104;
     private static final int MSG_REMOVED = 105;
     private static final int MSG_ENUMERATED = 106;
+    private static final int MSG_FINGERPRINT_DETECTED = 107;
 
     private IFingerprintService mService;
     private Context mContext;
     private IBinder mToken = new Binder();
     private AuthenticationCallback mAuthenticationCallback;
+    private FingerprintDetectionCallback mFingerprintDetectionCallback;
     private EnrollmentCallback mEnrollmentCallback;
     private RemovalCallback mRemovalCallback;
     private EnumerateCallback mEnumerateCallback;
@@ -107,6 +110,13 @@
         }
     }
 
+    private class OnFingerprintDetectionCancelListener implements OnCancelListener {
+        @Override
+        public void onCancel() {
+            cancelFingerprintDetect();
+        }
+    }
+
     /**
      * A wrapper class for the crypto objects supported by FingerprintManager. Currently the
      * framework supports {@link Signature}, {@link Cipher} and {@link Mac} objects.
@@ -272,6 +282,18 @@
     };
 
     /**
+     * Callback structure provided for {@link #detectFingerprint(CancellationSignal,
+     * FingerprintDetectionCallback, int)}.
+     * @hide
+     */
+    public interface FingerprintDetectionCallback {
+        /**
+         * Invoked when a fingerprint has been detected.
+         */
+        void onFingerprintDetected(int userId, boolean isStrongBiometric);
+    }
+
+    /**
      * Callback structure provided to {@link FingerprintManager#enroll(byte[], CancellationSignal,
      * int, int, EnrollmentCallback)} must provide an implementation of this for listening to
      * fingerprint events.
@@ -454,6 +476,35 @@
     }
 
     /**
+     * Uses the fingerprint hardware to detect for the presence of a finger, without giving details
+     * about accept/reject/lockout.
+     * @hide
+     */
+    @RequiresPermission(USE_BIOMETRIC_INTERNAL)
+    public void detectFingerprint(@NonNull CancellationSignal cancel,
+            @NonNull FingerprintDetectionCallback callback, int userId) {
+        if (mService == null) {
+            return;
+        }
+
+        if (cancel.isCanceled()) {
+            Slog.w(TAG, "Detection already cancelled");
+            return;
+        } else {
+            cancel.setOnCancelListener(new OnFingerprintDetectionCancelListener());
+        }
+
+        mFingerprintDetectionCallback = callback;
+
+        try {
+            mService.detectFingerprint(mToken, userId, mServiceReceiver,
+                    mContext.getOpPackageName());
+        } catch (RemoteException e) {
+            Slog.w(TAG, "Remote exception when requesting finger detect", e);
+        }
+    }
+
+    /**
      * Request fingerprint enrollment. This call warms up the fingerprint hardware
      * and starts scanning for fingerprints. Progress will be indicated by callbacks to the
      * {@link EnrollmentCallback} object. It terminates when
@@ -797,6 +848,10 @@
                     sendEnumeratedResult((Long) msg.obj /* deviceId */, msg.arg1 /* fingerId */,
                             msg.arg2 /* groupId */);
                     break;
+                case MSG_FINGERPRINT_DETECTED:
+                    sendFingerprintDetected(msg.arg1 /* userId */,
+                            (boolean) msg.obj /* isStrongBiometric */);
+                    break;
             }
         }
     };
@@ -891,6 +946,14 @@
         }
     }
 
+    private void sendFingerprintDetected(int userId, boolean isStrongBiometric) {
+        if (mFingerprintDetectionCallback == null) {
+            Slog.e(TAG, "sendFingerprintDetected, callback null");
+            return;
+        }
+        mFingerprintDetectionCallback.onFingerprintDetected(userId, isStrongBiometric);
+    }
+
     /**
      * @hide
      */
@@ -927,6 +990,18 @@
         }
     }
 
+    private void cancelFingerprintDetect() {
+        if (mService == null) {
+            return;
+        }
+
+        try {
+            mService.cancelFingerprintDetect(mToken, mContext.getOpPackageName());
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
     /**
      * @hide
      */
@@ -1032,6 +1107,12 @@
                     fp).sendToTarget();
         }
 
+        @Override
+        public void onFingerprintDetected(long deviceId, int userId, boolean isStrongBiometric) {
+            mHandler.obtainMessage(MSG_FINGERPRINT_DETECTED, userId, 0, isStrongBiometric)
+                    .sendToTarget();
+        }
+
         @Override // binder call
         public void onAuthenticationFailed(long deviceId) {
             mHandler.obtainMessage(MSG_AUTHENTICATION_FAILED).sendToTarget();
diff --git a/core/java/android/hardware/fingerprint/IFingerprintService.aidl b/core/java/android/hardware/fingerprint/IFingerprintService.aidl
index c5c3755..8aa36d7 100644
--- a/core/java/android/hardware/fingerprint/IFingerprintService.aidl
+++ b/core/java/android/hardware/fingerprint/IFingerprintService.aidl
@@ -33,6 +33,11 @@
     void authenticate(IBinder token, long sessionId, int userId,
             IFingerprintServiceReceiver receiver, int flags, String opPackageName);
 
+    // Uses the fingerprint hardware to detect for the presence of a finger, without giving details
+    // about accept/reject/lockout.
+    void detectFingerprint(IBinder token, int userId, IFingerprintServiceReceiver receiver,
+            String opPackageName);
+
     // This method prepares the service to start authenticating, but doesn't start authentication.
     // This is protected by the MANAGE_BIOMETRIC signatuer permission. This method should only be
     // called from BiometricService. The additional uid, pid, userId arguments should be determined
@@ -48,6 +53,9 @@
     // Cancel authentication for the given sessionId
     void cancelAuthentication(IBinder token, String opPackageName);
 
+    // Cancel finger detection
+    void cancelFingerprintDetect(IBinder token, String opPackageName);
+
     // Same as above, except this is protected by the MANAGE_BIOMETRIC signature permission. Takes
     // an additional uid, pid, userid.
     void cancelAuthenticationFromService(IBinder token, String opPackageName,
diff --git a/core/java/android/hardware/fingerprint/IFingerprintServiceReceiver.aidl b/core/java/android/hardware/fingerprint/IFingerprintServiceReceiver.aidl
index 4412cee..a84b81e1 100644
--- a/core/java/android/hardware/fingerprint/IFingerprintServiceReceiver.aidl
+++ b/core/java/android/hardware/fingerprint/IFingerprintServiceReceiver.aidl
@@ -26,6 +26,7 @@
     void onAcquired(long deviceId, int acquiredInfo, int vendorCode);
     void onAuthenticationSucceeded(long deviceId, in Fingerprint fp, int userId,
             boolean isStrongBiometric);
+    void onFingerprintDetected(long deviceId, int userId, boolean isStrongBiometric);
     void onAuthenticationFailed(long deviceId);
     void onError(long deviceId, int error, int vendorCode);
     void onRemoved(long deviceId, int fingerId, int groupId, int remaining);
diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java
index 1c5df48..17f413b 100644
--- a/core/java/android/inputmethodservice/InputMethodService.java
+++ b/core/java/android/inputmethodservice/InputMethodService.java
@@ -1208,15 +1208,19 @@
         mWindow.getWindow().getAttributes().setFitInsetsIgnoringVisibility(true);
 
         // IME layout should always be inset by navigation bar, no matter its current visibility,
-        // unless automotive requests it, since automotive may hide the navigation bar.
+        // unless automotive requests it. Automotive devices may request the navigation bar to be
+        // hidden when the IME shows up (controlled via config_automotiveHideNavBarForKeyboard)
+        // in order to maximize the visible screen real estate. When this happens, the IME window
+        // should animate from the bottom of the screen to reduce the jank that happens from the
+        // lack of synchronization between the bottom system window and the IME window.
+        if (mIsAutomotive && mAutomotiveHideNavBarForKeyboard) {
+            mWindow.getWindow().setDecorFitsSystemWindows(false);
+        }
         mWindow.getWindow().getDecorView().setOnApplyWindowInsetsListener(
                 (v, insets) -> v.onApplyWindowInsets(
                         new WindowInsets.Builder(insets).setInsets(
                                 navigationBars(),
-                                mIsAutomotive && mAutomotiveHideNavBarForKeyboard
-                                        ? android.graphics.Insets.NONE
-                                        : insets.getInsetsIgnoringVisibility(navigationBars())
-                                )
+                                insets.getInsetsIgnoringVisibility(navigationBars()))
                                 .build()));
 
         // For ColorView in DecorView to work, FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS needs to be set
diff --git a/core/java/android/os/BinderProxy.java b/core/java/android/os/BinderProxy.java
index 683993f7..0185ba4 100644
--- a/core/java/android/os/BinderProxy.java
+++ b/core/java/android/os/BinderProxy.java
@@ -18,6 +18,7 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.app.ActivityManager;
 import android.app.AppOpsManager;
 import android.util.Log;
 import android.util.SparseIntArray;
@@ -255,7 +256,12 @@
             // out of system_server to all processes hosting binder objects it holds a reference to;
             // since some of those processes might be frozen, we don't want to block here
             // forever. Disable the freezer.
-            Process.enableFreezer(false);
+            try {
+                ActivityManager.getService().enableAppFreezer(false);
+            } catch (RemoteException e) {
+                Log.e(Binder.TAG, "RemoteException while disabling app freezer");
+            }
+
             for (WeakReference<BinderProxy> weakRef : proxiesToQuery) {
                 BinderProxy bp = weakRef.get();
                 String key;
@@ -278,7 +284,11 @@
                     counts.put(key, i + 1);
                 }
             }
-            Process.enableFreezer(true);
+            try {
+                ActivityManager.getService().enableAppFreezer(true);
+            } catch (RemoteException e) {
+                Log.e(Binder.TAG, "RemoteException while re-enabling app freezer");
+            }
             Map.Entry<String, Integer>[] sorted = counts.entrySet().toArray(
                     new Map.Entry[counts.size()]);
 
diff --git a/core/java/android/os/Process.java b/core/java/android/os/Process.java
index 2aa5c13..9a87dc9 100644
--- a/core/java/android/os/Process.java
+++ b/core/java/android/os/Process.java
@@ -946,7 +946,7 @@
 
     /**
      * Enable or disable the freezer. When enable == false all frozen processes are unfrozen,
-     * but aren't removed from the freezer. Processes can still be added or removed
+     * but aren't removed from the freezer. While in this state, processes can be added or removed
      * by using setProcessFrozen, but they won't actually be frozen until the freezer is enabled
      * again. If enable == true the freezer is enabled again, and all processes
      * in the freezer (including the ones added while the freezer was disabled) are frozen.
diff --git a/core/java/android/os/storage/StorageManagerInternal.java b/core/java/android/os/storage/StorageManagerInternal.java
index e05991b..a79a1cf 100644
--- a/core/java/android/os/storage/StorageManagerInternal.java
+++ b/core/java/android/os/storage/StorageManagerInternal.java
@@ -118,7 +118,7 @@
      * affects them.
      */
     public abstract void onAppOpsChanged(int code, int uid,
-            @Nullable String packageName, int mode);
+            @Nullable String packageName, int mode, int previousMode);
 
     /**
      * Asks the StorageManager to reset all state for the provided user; this will result
diff --git a/core/java/android/permission/PermissionControllerService.java b/core/java/android/permission/PermissionControllerService.java
index 8ad35e7..e2e6140 100644
--- a/core/java/android/permission/PermissionControllerService.java
+++ b/core/java/android/permission/PermissionControllerService.java
@@ -35,6 +35,8 @@
 import android.annotation.SystemApi;
 import android.app.Service;
 import android.app.admin.DevicePolicyManager.PermissionGrantState;
+import android.compat.annotation.ChangeId;
+import android.compat.annotation.Disabled;
 import android.content.Intent;
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
@@ -83,6 +85,15 @@
     public static final String SERVICE_INTERFACE = "android.permission.PermissionControllerService";
 
     /**
+     * A ChangeId indicating that this device supports camera and mic indicators. Will be "false"
+     * if present, because the CompatChanges#isChangeEnabled method returns true if the change id
+     * is not present.
+     */
+    @ChangeId
+    @Disabled
+    private static final long CAMERA_MIC_INDICATORS_NOT_PRESENT = 162547999L;
+
+    /**
      * Revoke a set of runtime permissions for various apps.
      *
      * @param requests The permissions to revoke as {@code Map<packageName, List<permission>>}
diff --git a/core/java/android/provider/DocumentsProvider.java b/core/java/android/provider/DocumentsProvider.java
index 1a59e8d..b704d66 100644
--- a/core/java/android/provider/DocumentsProvider.java
+++ b/core/java/android/provider/DocumentsProvider.java
@@ -218,8 +218,15 @@
     }
 
     /** {@hide} */
-    private void enforceTree(Uri documentUri) {
-        if (isTreeUri(documentUri)) {
+    private void enforceTreeForExtraUris(Bundle extras) {
+        enforceTree(extras.getParcelable(DocumentsContract.EXTRA_URI));
+        enforceTree(extras.getParcelable(DocumentsContract.EXTRA_PARENT_URI));
+        enforceTree(extras.getParcelable(DocumentsContract.EXTRA_TARGET_URI));
+    }
+
+    /** {@hide} */
+    private void enforceTree(@Nullable Uri documentUri) {
+        if (documentUri != null && isTreeUri(documentUri)) {
             final String parent = getTreeDocumentId(documentUri);
             final String child = getDocumentId(documentUri);
             if (Objects.equals(parent, child)) {
@@ -232,6 +239,10 @@
         }
     }
 
+    private Uri validateIncomingNullableUri(@Nullable Uri uri) {
+        return uri == null ? null : validateIncomingUri(uri);
+    }
+
     /**
      * Create a new document and return its newly generated
      * {@link Document#COLUMN_DOCUMENT_ID}. You must allocate a new
@@ -1076,11 +1087,21 @@
         final Context context = getContext();
         final Bundle out = new Bundle();
 
+        // If the URI is a tree URI performs some validation.
+        enforceTreeForExtraUris(extras);
+
+        final Uri extraUri = validateIncomingNullableUri(
+                extras.getParcelable(DocumentsContract.EXTRA_URI));
+        final Uri extraTargetUri = validateIncomingNullableUri(
+                extras.getParcelable(DocumentsContract.EXTRA_TARGET_URI));
+        final Uri extraParentUri = validateIncomingNullableUri(
+                extras.getParcelable(DocumentsContract.EXTRA_PARENT_URI));
+
         if (METHOD_EJECT_ROOT.equals(method)) {
             // Given that certain system apps can hold MOUNT_UNMOUNT permission, but only apps
             // signed with platform signature can hold MANAGE_DOCUMENTS, we are going to check for
             // MANAGE_DOCUMENTS or associated URI permission here instead
-            final Uri rootUri = extras.getParcelable(DocumentsContract.EXTRA_URI);
+            final Uri rootUri = extraUri;
             enforceWritePermissionInner(rootUri, getCallingPackage(), getCallingAttributionTag(),
                     null);
 
@@ -1090,7 +1111,7 @@
             return out;
         }
 
-        final Uri documentUri = extras.getParcelable(DocumentsContract.EXTRA_URI);
+        final Uri documentUri = extraUri;
         final String authority = documentUri.getAuthority();
         final String documentId = DocumentsContract.getDocumentId(documentUri);
 
@@ -1099,14 +1120,11 @@
                     "Requested authority " + authority + " doesn't match provider " + mAuthority);
         }
 
-        // If the URI is a tree URI performs some validation.
-        enforceTree(documentUri);
-
         if (METHOD_IS_CHILD_DOCUMENT.equals(method)) {
             enforceReadPermissionInner(documentUri, getCallingPackage(),
                     getCallingAttributionTag(), null);
 
-            final Uri childUri = extras.getParcelable(DocumentsContract.EXTRA_TARGET_URI);
+            final Uri childUri = extraTargetUri;
             final String childAuthority = childUri.getAuthority();
             final String childId = DocumentsContract.getDocumentId(childUri);
 
@@ -1173,7 +1191,7 @@
             revokeDocumentPermission(documentId);
 
         } else if (METHOD_COPY_DOCUMENT.equals(method)) {
-            final Uri targetUri = extras.getParcelable(DocumentsContract.EXTRA_TARGET_URI);
+            final Uri targetUri = extraTargetUri;
             final String targetId = DocumentsContract.getDocumentId(targetUri);
 
             enforceReadPermissionInner(documentUri, getCallingPackage(),
@@ -1197,9 +1215,9 @@
             }
 
         } else if (METHOD_MOVE_DOCUMENT.equals(method)) {
-            final Uri parentSourceUri = extras.getParcelable(DocumentsContract.EXTRA_PARENT_URI);
+            final Uri parentSourceUri = extraParentUri;
             final String parentSourceId = DocumentsContract.getDocumentId(parentSourceUri);
-            final Uri targetUri = extras.getParcelable(DocumentsContract.EXTRA_TARGET_URI);
+            final Uri targetUri = extraTargetUri;
             final String targetId = DocumentsContract.getDocumentId(targetUri);
 
             enforceWritePermissionInner(documentUri, getCallingPackage(),
@@ -1225,7 +1243,7 @@
             }
 
         } else if (METHOD_REMOVE_DOCUMENT.equals(method)) {
-            final Uri parentSourceUri = extras.getParcelable(DocumentsContract.EXTRA_PARENT_URI);
+            final Uri parentSourceUri = extraParentUri;
             final String parentSourceId = DocumentsContract.getDocumentId(parentSourceUri);
 
             enforceReadPermissionInner(parentSourceUri, getCallingPackage(),
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 2f6a479..4489a4d 100755
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -7883,6 +7883,12 @@
         public static final String UI_NIGHT_MODE_OVERRIDE_ON = "ui_night_mode_override_on";
 
         /**
+         * The last computed night mode bool the last time the phone was on
+         * @hide
+         */
+        public static final String UI_NIGHT_MODE_LAST_COMPUTED = "ui_night_mode_last_computed";
+
+        /**
          * The current night mode that has been overridden to turn off by the system.  Owned
          * and controlled by UiModeManagerService.  Constants are as per
          * UiModeManager.
@@ -8941,6 +8947,13 @@
         public static final int ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW = 0x2;
 
         /**
+         * Whether the Adaptive connectivity option is enabled.
+         *
+         * @hide
+         */
+        public static final String ADAPTIVE_CONNECTIVITY_ENABLED = "adaptive_connectivity_enabled";
+
+        /**
          * Keys we no longer back up under the current schema, but want to continue to
          * process when restoring historical backup datasets.
          *
@@ -8956,6 +8969,22 @@
         };
 
         /**
+         * How long Assistant handles have enabled in milliseconds.
+         *
+         * @hide
+         */
+        public static final String ASSIST_HANDLES_LEARNING_TIME_ELAPSED_MILLIS =
+                "reminder_exp_learning_time_elapsed";
+
+        /**
+         * How many times the Assistant has been triggered using the touch gesture.
+         *
+         * @hide
+         */
+        public static final String ASSIST_HANDLES_LEARNING_EVENT_COUNT =
+                "reminder_exp_learning_event_count";
+
+        /**
          * These entries are considered common between the personal and the managed profile,
          * since the managed profile doesn't get to change them.
          */
diff --git a/core/java/android/service/notification/NotificationListenerService.java b/core/java/android/service/notification/NotificationListenerService.java
index 536c9c0..5419e29 100644
--- a/core/java/android/service/notification/NotificationListenerService.java
+++ b/core/java/android/service/notification/NotificationListenerService.java
@@ -1901,6 +1901,17 @@
         /**
          * @hide
          */
+        public @NonNull Ranking withAudiblyAlertedInfo(@Nullable Ranking previous) {
+            if (previous != null && previous.mLastAudiblyAlertedMs > 0
+                    && this.mLastAudiblyAlertedMs <= 0) {
+                this.mLastAudiblyAlertedMs = previous.mLastAudiblyAlertedMs;
+            }
+            return this;
+        }
+
+        /**
+         * @hide
+         */
         public void populate(Ranking other) {
             populate(other.mKey,
                     other.mRank,
diff --git a/core/java/android/view/DisplayAdjustments.java b/core/java/android/view/DisplayAdjustments.java
index c726bee..7c01f7a8 100644
--- a/core/java/android/view/DisplayAdjustments.java
+++ b/core/java/android/view/DisplayAdjustments.java
@@ -130,14 +130,16 @@
         w = metrics.noncompatWidthPixels;
         metrics.noncompatWidthPixels = metrics.noncompatHeightPixels;
         metrics.noncompatHeightPixels = w;
+    }
 
-        float x = metrics.xdpi;
-        metrics.xdpi = metrics.ydpi;
-        metrics.ydpi = x;
-
-        x = metrics.noncompatXdpi;
-        metrics.noncompatXdpi = metrics.noncompatYdpi;
-        metrics.noncompatYdpi = x;
+    /** Adjusts global display metrics that is available to applications. */
+    public void adjustGlobalAppMetrics(@NonNull DisplayMetrics metrics) {
+        final FixedRotationAdjustments rotationAdjustments = mFixedRotationAdjustments;
+        if (rotationAdjustments == null) {
+            return;
+        }
+        metrics.noncompatWidthPixels = metrics.widthPixels = rotationAdjustments.mAppWidth;
+        metrics.noncompatHeightPixels = metrics.heightPixels = rotationAdjustments.mAppHeight;
     }
 
     /** Returns the adjusted cutout if available. Otherwise the original cutout is returned. */
@@ -178,7 +180,7 @@
 
     /**
      * An application can be launched in different rotation than the real display. This class
-     * provides the information to adjust the values returned by {@link #Display}.
+     * provides the information to adjust the values returned by {@link Display}.
      * @hide
      */
     public static class FixedRotationAdjustments implements Parcelable {
@@ -186,12 +188,24 @@
         @Surface.Rotation
         final int mRotation;
 
+        /**
+         * The rotated {@link DisplayInfo#appWidth}. The value cannot be simply swapped according
+         * to rotation because it minus the region of screen decorations.
+         */
+        final int mAppWidth;
+
+        /** The rotated {@link DisplayInfo#appHeight}. */
+        final int mAppHeight;
+
         /** Non-null if the device has cutout. */
         @Nullable
         final DisplayCutout mRotatedDisplayCutout;
 
-        public FixedRotationAdjustments(@Surface.Rotation int rotation, DisplayCutout cutout) {
+        public FixedRotationAdjustments(@Surface.Rotation int rotation, int appWidth, int appHeight,
+                DisplayCutout cutout) {
             mRotation = rotation;
+            mAppWidth = appWidth;
+            mAppHeight = appHeight;
             mRotatedDisplayCutout = cutout;
         }
 
@@ -199,6 +213,8 @@
         public int hashCode() {
             int hash = 17;
             hash = hash * 31 + mRotation;
+            hash = hash * 31 + mAppWidth;
+            hash = hash * 31 + mAppHeight;
             hash = hash * 31 + Objects.hashCode(mRotatedDisplayCutout);
             return hash;
         }
@@ -210,12 +226,14 @@
             }
             final FixedRotationAdjustments other = (FixedRotationAdjustments) o;
             return mRotation == other.mRotation
+                    && mAppWidth == other.mAppWidth && mAppHeight == other.mAppHeight
                     && Objects.equals(mRotatedDisplayCutout, other.mRotatedDisplayCutout);
         }
 
         @Override
         public String toString() {
             return "FixedRotationAdjustments{rotation=" + Surface.rotationToString(mRotation)
+                    + " appWidth=" + mAppWidth + " appHeight=" + mAppHeight
                     + " cutout=" + mRotatedDisplayCutout + "}";
         }
 
@@ -227,12 +245,16 @@
         @Override
         public void writeToParcel(Parcel dest, int flags) {
             dest.writeInt(mRotation);
+            dest.writeInt(mAppWidth);
+            dest.writeInt(mAppHeight);
             dest.writeTypedObject(
                     new DisplayCutout.ParcelableWrapper(mRotatedDisplayCutout), flags);
         }
 
         private FixedRotationAdjustments(Parcel in) {
             mRotation = in.readInt();
+            mAppWidth = in.readInt();
+            mAppHeight = in.readInt();
             final DisplayCutout.ParcelableWrapper cutoutWrapper =
                     in.readTypedObject(DisplayCutout.ParcelableWrapper.CREATOR);
             mRotatedDisplayCutout = cutoutWrapper != null ? cutoutWrapper.get() : null;
diff --git a/core/java/android/view/FocusFinder.java b/core/java/android/view/FocusFinder.java
index 713cfb4..064bc69 100644
--- a/core/java/android/view/FocusFinder.java
+++ b/core/java/android/view/FocusFinder.java
@@ -311,6 +311,9 @@
         }
 
         final int count = focusables.size();
+        if (count < 2) {
+            return null;
+        }
         switch (direction) {
             case View.FOCUS_FORWARD:
                 return getNextFocusable(focused, focusables, count);
@@ -373,29 +376,29 @@
     }
 
     private static View getNextFocusable(View focused, ArrayList<View> focusables, int count) {
+        if (count < 2) {
+            return null;
+        }
         if (focused != null) {
             int position = focusables.lastIndexOf(focused);
             if (position >= 0 && position + 1 < count) {
                 return focusables.get(position + 1);
             }
         }
-        if (!focusables.isEmpty()) {
-            return focusables.get(0);
-        }
-        return null;
+        return focusables.get(0);
     }
 
     private static View getPreviousFocusable(View focused, ArrayList<View> focusables, int count) {
+        if (count < 2) {
+            return null;
+        }
         if (focused != null) {
             int position = focusables.indexOf(focused);
             if (position > 0) {
                 return focusables.get(position - 1);
             }
         }
-        if (!focusables.isEmpty()) {
-            return focusables.get(count - 1);
-        }
-        return null;
+        return focusables.get(count - 1);
     }
 
     private static View getNextKeyboardNavigationCluster(
diff --git a/core/java/android/view/IDisplayWindowInsetsController.aidl b/core/java/android/view/IDisplayWindowInsetsController.aidl
index 429c3ae..a0d4a65 100644
--- a/core/java/android/view/IDisplayWindowInsetsController.aidl
+++ b/core/java/android/view/IDisplayWindowInsetsController.aidl
@@ -27,6 +27,13 @@
 oneway interface IDisplayWindowInsetsController {
 
     /**
+     * Called when top focused window changes to determine whether or not to take over insets
+     * control. Won't be called if config_remoteInsetsControllerControlsSystemBars is false.
+     * @param packageName: Passes the top package name
+     */
+    void topFocusedWindowChanged(String packageName);
+
+    /**
      * @see IWindow#insetsChanged
      */
     void insetsChanged(in InsetsState insetsState);
diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl
index 58597cf..00fc672 100644
--- a/core/java/android/view/IWindowManager.aidl
+++ b/core/java/android/view/IWindowManager.aidl
@@ -388,16 +388,6 @@
     oneway void hideTransientBars(int displayId);
 
     /**
-    * When set to {@code true} the system bars will always be shown. This is true even if an app
-    * requests to be fullscreen by setting the system ui visibility flags. The
-    * functionality was added for the automotive case as a way to guarantee required content stays
-    * on screen at all times.
-    *
-    * @hide
-    */
-    oneway void setForceShowSystemBars(boolean show);
-
-    /**
      * Called by System UI to notify of changes to the visibility of Recents.
      */
     oneway void setRecentsVisibility(boolean visible);
diff --git a/core/java/android/view/InsetsState.java b/core/java/android/view/InsetsState.java
index 593b37a..8137883 100644
--- a/core/java/android/view/InsetsState.java
+++ b/core/java/android/view/InsetsState.java
@@ -76,6 +76,10 @@
             ITYPE_BOTTOM_GESTURES,
             ITYPE_LEFT_GESTURES,
             ITYPE_RIGHT_GESTURES,
+            ITYPE_TOP_MANDATORY_GESTURES,
+            ITYPE_BOTTOM_MANDATORY_GESTURES,
+            ITYPE_LEFT_MANDATORY_GESTURES,
+            ITYPE_RIGHT_MANDATORY_GESTURES,
             ITYPE_TOP_TAPPABLE_ELEMENT,
             ITYPE_BOTTOM_TAPPABLE_ELEMENT,
             ITYPE_LEFT_DISPLAY_CUTOUT,
@@ -104,20 +108,27 @@
     public static final int ITYPE_BOTTOM_GESTURES = 4;
     public static final int ITYPE_LEFT_GESTURES = 5;
     public static final int ITYPE_RIGHT_GESTURES = 6;
-    public static final int ITYPE_TOP_TAPPABLE_ELEMENT = 7;
-    public static final int ITYPE_BOTTOM_TAPPABLE_ELEMENT = 8;
 
-    public static final int ITYPE_LEFT_DISPLAY_CUTOUT = 9;
-    public static final int ITYPE_TOP_DISPLAY_CUTOUT = 10;
-    public static final int ITYPE_RIGHT_DISPLAY_CUTOUT = 11;
-    public static final int ITYPE_BOTTOM_DISPLAY_CUTOUT = 12;
+    /** Additional gesture inset types that map into {@link Type.MANDATORY_SYSTEM_GESTURES}. */
+    public static final int ITYPE_TOP_MANDATORY_GESTURES = 7;
+    public static final int ITYPE_BOTTOM_MANDATORY_GESTURES = 8;
+    public static final int ITYPE_LEFT_MANDATORY_GESTURES = 9;
+    public static final int ITYPE_RIGHT_MANDATORY_GESTURES = 10;
+
+    public static final int ITYPE_TOP_TAPPABLE_ELEMENT = 11;
+    public static final int ITYPE_BOTTOM_TAPPABLE_ELEMENT = 12;
+
+    public static final int ITYPE_LEFT_DISPLAY_CUTOUT = 13;
+    public static final int ITYPE_TOP_DISPLAY_CUTOUT = 14;
+    public static final int ITYPE_RIGHT_DISPLAY_CUTOUT = 15;
+    public static final int ITYPE_BOTTOM_DISPLAY_CUTOUT = 16;
 
     /** Input method window. */
-    public static final int ITYPE_IME = 13;
+    public static final int ITYPE_IME = 17;
 
     /** Additional system decorations inset type. */
-    public static final int ITYPE_CLIMATE_BAR = 14;
-    public static final int ITYPE_EXTRA_NAVIGATION_BAR = 15;
+    public static final int ITYPE_CLIMATE_BAR = 18;
+    public static final int ITYPE_EXTRA_NAVIGATION_BAR = 19;
 
     static final int LAST_TYPE = ITYPE_EXTRA_NAVIGATION_BAR;
     public static final int SIZE = LAST_TYPE + 1;
@@ -453,9 +464,11 @@
         final ArraySet<Integer> result = new ArraySet<>();
         if ((types & Type.STATUS_BARS) != 0) {
             result.add(ITYPE_STATUS_BAR);
+            result.add(ITYPE_CLIMATE_BAR);
         }
         if ((types & Type.NAVIGATION_BARS) != 0) {
             result.add(ITYPE_NAVIGATION_BAR);
+            result.add(ITYPE_EXTRA_NAVIGATION_BAR);
         }
         if ((types & Type.CAPTION_BAR) != 0) {
             result.add(ITYPE_CAPTION_BAR);
@@ -491,6 +504,10 @@
                 return Type.IME;
             case ITYPE_TOP_GESTURES:
             case ITYPE_BOTTOM_GESTURES:
+            case ITYPE_TOP_MANDATORY_GESTURES:
+            case ITYPE_BOTTOM_MANDATORY_GESTURES:
+            case ITYPE_LEFT_MANDATORY_GESTURES:
+            case ITYPE_RIGHT_MANDATORY_GESTURES:
                 return Type.MANDATORY_SYSTEM_GESTURES;
             case ITYPE_LEFT_GESTURES:
             case ITYPE_RIGHT_GESTURES:
@@ -550,6 +567,14 @@
                 return "ITYPE_LEFT_GESTURES";
             case ITYPE_RIGHT_GESTURES:
                 return "ITYPE_RIGHT_GESTURES";
+            case ITYPE_TOP_MANDATORY_GESTURES:
+                return "ITYPE_TOP_MANDATORY_GESTURES";
+            case ITYPE_BOTTOM_MANDATORY_GESTURES:
+                return "ITYPE_BOTTOM_MANDATORY_GESTURES";
+            case ITYPE_LEFT_MANDATORY_GESTURES:
+                return "ITYPE_LEFT_MANDATORY_GESTURES";
+            case ITYPE_RIGHT_MANDATORY_GESTURES:
+                return "ITYPE_RIGHT_MANDATORY_GESTURES";
             case ITYPE_TOP_TAPPABLE_ELEMENT:
                 return "ITYPE_TOP_TAPPABLE_ELEMENT";
             case ITYPE_BOTTOM_TAPPABLE_ELEMENT:
diff --git a/core/java/android/view/ViewConfiguration.java b/core/java/android/view/ViewConfiguration.java
index ffeeb80..8fd2f07f 100644
--- a/core/java/android/view/ViewConfiguration.java
+++ b/core/java/android/view/ViewConfiguration.java
@@ -99,7 +99,7 @@
      * Defines the duration in milliseconds a user needs to hold down the
      * appropriate buttons (power + volume down) to trigger the screenshot chord.
      */
-    private static final int SCREENSHOT_CHORD_KEY_TIMEOUT = 500;
+    private static final int SCREENSHOT_CHORD_KEY_TIMEOUT = 0;
 
     /**
      * Defines the duration in milliseconds a user needs to hold down the
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index bcf3b49..3763728 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -53,6 +53,9 @@
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_BEHAVIOR_CONTROLLED;
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_FIT_INSETS_CONTROLLED;
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_FORCE_DECOR_VIEW_VISIBILITY;
+import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_INSET_PARENT_FRAME_BY_IME;
+import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE;
+import static android.view.WindowManager.LayoutParams.SOFT_INPUT_MASK_ADJUST;
 import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD;
 import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR_ADDITIONAL;
 import static android.view.WindowManager.LayoutParams.TYPE_SYSTEM_ALERT;
@@ -1449,14 +1452,13 @@
             }
 
             // Don't lose the mode we last auto-computed.
-            if ((attrs.softInputMode & WindowManager.LayoutParams.SOFT_INPUT_MASK_ADJUST)
+            if ((attrs.softInputMode & SOFT_INPUT_MASK_ADJUST)
                     == WindowManager.LayoutParams.SOFT_INPUT_ADJUST_UNSPECIFIED) {
                 mWindowAttributes.softInputMode = (mWindowAttributes.softInputMode
-                        & ~WindowManager.LayoutParams.SOFT_INPUT_MASK_ADJUST)
-                        | (oldSoftInputMode & WindowManager.LayoutParams.SOFT_INPUT_MASK_ADJUST);
+                        & ~SOFT_INPUT_MASK_ADJUST) | (oldSoftInputMode & SOFT_INPUT_MASK_ADJUST);
             }
 
-            if ((changes & LayoutParams.SOFT_INPUT_MODE_CHANGED) != 0) {
+            if (mWindowAttributes.softInputMode != oldSoftInputMode) {
                 requestFitSystemWindows();
             }
 
@@ -1979,11 +1981,7 @@
             mCompatibleVisibilityInfo.globalVisibility =
                     (mCompatibleVisibilityInfo.globalVisibility & ~View.SYSTEM_UI_FLAG_LOW_PROFILE)
                             | (mAttachInfo.mSystemUiVisibility & View.SYSTEM_UI_FLAG_LOW_PROFILE);
-            if (mDispatchedSystemUiVisibility != mCompatibleVisibilityInfo.globalVisibility) {
-                mHandler.removeMessages(MSG_DISPATCH_SYSTEM_UI_VISIBILITY);
-                mHandler.sendMessage(mHandler.obtainMessage(
-                        MSG_DISPATCH_SYSTEM_UI_VISIBILITY, mCompatibleVisibilityInfo));
-            }
+            dispatchDispatchSystemUiVisibilityChanged(mCompatibleVisibilityInfo);
             if (mAttachInfo.mKeepScreenOn != oldScreenOn
                     || mAttachInfo.mSystemUiVisibility != params.subtreeSystemUiVisibility
                     || mAttachInfo.mHasSystemUiListeners != params.hasSystemUiListeners) {
@@ -2037,9 +2035,30 @@
             info.globalVisibility |= systemUiFlag;
             info.localChanges &= ~systemUiFlag;
         }
-        if (mDispatchedSystemUiVisibility != info.globalVisibility) {
+        dispatchDispatchSystemUiVisibilityChanged(info);
+    }
+
+    /**
+     * If the system is forcing showing any system bar, the legacy low profile flag should be
+     * cleared for compatibility.
+     *
+     * @param showTypes {@link InsetsType types} shown by the system.
+     * @param fromIme {@code true} if the invocation is from IME.
+     */
+    private void clearLowProfileModeIfNeeded(@InsetsType int showTypes, boolean fromIme) {
+        final SystemUiVisibilityInfo info = mCompatibleVisibilityInfo;
+        if ((showTypes & Type.systemBars()) != 0 && !fromIme
+                && (info.globalVisibility & SYSTEM_UI_FLAG_LOW_PROFILE) != 0) {
+            info.globalVisibility &= ~SYSTEM_UI_FLAG_LOW_PROFILE;
+            info.localChanges |= SYSTEM_UI_FLAG_LOW_PROFILE;
+            dispatchDispatchSystemUiVisibilityChanged(info);
+        }
+    }
+
+    private void dispatchDispatchSystemUiVisibilityChanged(SystemUiVisibilityInfo args) {
+        if (mDispatchedSystemUiVisibility != args.globalVisibility) {
             mHandler.removeMessages(MSG_DISPATCH_SYSTEM_UI_VISIBILITY);
-            mHandler.sendMessage(mHandler.obtainMessage(MSG_DISPATCH_SYSTEM_UI_VISIBILITY, info));
+            mHandler.sendMessage(mHandler.obtainMessage(MSG_DISPATCH_SYSTEM_UI_VISIBILITY, args));
         }
     }
 
@@ -2073,6 +2092,7 @@
         final int sysUiVis = inOutParams.systemUiVisibility | inOutParams.subtreeSystemUiVisibility;
         final int flags = inOutParams.flags;
         final int type = inOutParams.type;
+        final int adjust = inOutParams.softInputMode & SOFT_INPUT_MASK_ADJUST;
 
         if ((inOutParams.privateFlags & PRIVATE_FLAG_APPEARANCE_CONTROLLED) == 0) {
             inOutParams.insetsFlags.appearance = 0;
@@ -2098,12 +2118,13 @@
             }
         }
 
+        inOutParams.privateFlags &= ~PRIVATE_FLAG_INSET_PARENT_FRAME_BY_IME;
+
         if ((inOutParams.privateFlags & PRIVATE_FLAG_FIT_INSETS_CONTROLLED) != 0) {
             return;
         }
 
         int types = inOutParams.getFitInsetsTypes();
-        int sides = inOutParams.getFitInsetsSides();
         boolean ignoreVis = inOutParams.isFitInsetsIgnoringVisibility();
 
         if (((sysUiVis & SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN) != 0
@@ -2118,10 +2139,13 @@
         if (type == TYPE_TOAST || type == TYPE_SYSTEM_ALERT) {
             ignoreVis = true;
         } else if ((types & Type.systemBars()) == Type.systemBars()) {
-            types |= Type.ime();
+            if (adjust == SOFT_INPUT_ADJUST_RESIZE) {
+                types |= Type.ime();
+            } else {
+                inOutParams.privateFlags |= PRIVATE_FLAG_INSET_PARENT_FRAME_BY_IME;
+            }
         }
         inOutParams.setFitInsetsTypes(types);
-        inOutParams.setFitInsetsSides(sides);
         inOutParams.setFitInsetsIgnoringVisibility(ignoreVis);
 
         // The fitting of insets are not really controlled by the clients, so we remove the flag.
@@ -2491,8 +2515,7 @@
 
         if (mFirst || mAttachInfo.mViewVisibilityChanged) {
             mAttachInfo.mViewVisibilityChanged = false;
-            int resizeMode = mSoftInputMode &
-                    WindowManager.LayoutParams.SOFT_INPUT_MASK_ADJUST;
+            int resizeMode = mSoftInputMode & SOFT_INPUT_MASK_ADJUST;
             // If we are in auto resize mode, then we need to determine
             // what mode to use now.
             if (resizeMode == WindowManager.LayoutParams.SOFT_INPUT_ADJUST_UNSPECIFIED) {
@@ -2505,11 +2528,8 @@
                 if (resizeMode == 0) {
                     resizeMode = WindowManager.LayoutParams.SOFT_INPUT_ADJUST_PAN;
                 }
-                if ((lp.softInputMode &
-                        WindowManager.LayoutParams.SOFT_INPUT_MASK_ADJUST) != resizeMode) {
-                    lp.softInputMode = (lp.softInputMode &
-                            ~WindowManager.LayoutParams.SOFT_INPUT_MASK_ADJUST) |
-                            resizeMode;
+                if ((lp.softInputMode & SOFT_INPUT_MASK_ADJUST) != resizeMode) {
+                    lp.softInputMode = (lp.softInputMode & ~SOFT_INPUT_MASK_ADJUST) | resizeMode;
                     params = lp;
                 }
             }
@@ -5005,6 +5025,7 @@
                                 String.format("Calling showInsets(%d,%b) on window that no longer"
                                         + " has views.", msg.arg1, msg.arg2 == 1));
                     }
+                    clearLowProfileModeIfNeeded(msg.arg1, msg.arg2 == 1);
                     mInsetsController.show(msg.arg1, msg.arg2 == 1);
                     break;
                 }
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java
index 5c62694..2a711f6 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -2033,6 +2033,12 @@
         public static final int PRIVATE_FLAG_FIT_INSETS_CONTROLLED = 0x10000000;
 
         /**
+         * Flag to indicate that the parent frame of a window should be inset by IME.
+         * @hide
+         */
+        public static final int PRIVATE_FLAG_INSET_PARENT_FRAME_BY_IME = 0x40000000;
+
+        /**
          * An internal annotation for flags that can be specified to {@link #softInputMode}.
          *
          * @hide
diff --git a/core/java/android/view/WindowManagerPolicyConstants.java b/core/java/android/view/WindowManagerPolicyConstants.java
index 492ab6f..8c35520 100644
--- a/core/java/android/view/WindowManagerPolicyConstants.java
+++ b/core/java/android/view/WindowManagerPolicyConstants.java
@@ -49,6 +49,13 @@
     int PRESENCE_INTERNAL = 1 << 0;
     int PRESENCE_EXTERNAL = 1 << 1;
 
+    // Alternate bars position values
+    int ALT_BAR_UNKNOWN = -1;
+    int ALT_BAR_LEFT = 1 << 0;
+    int ALT_BAR_RIGHT = 1 << 1;
+    int ALT_BAR_BOTTOM = 1 << 2;
+    int ALT_BAR_TOP = 1 << 3;
+
     // Navigation bar position values
     int NAV_BAR_INVALID = -1;
     int NAV_BAR_LEFT = 1 << 0;
diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java
index 0d21673..5f8b13b 100644
--- a/core/java/android/view/inputmethod/InputMethodManager.java
+++ b/core/java/android/view/inputmethod/InputMethodManager.java
@@ -611,7 +611,8 @@
         @Override
         public void startInputAsyncOnWindowFocusGain(View focusedView,
                 @SoftInputModeFlags int softInputMode, int windowFlags, boolean forceNewFocus) {
-            final int startInputFlags = getStartInputFlags(focusedView, 0);
+            int startInputFlags = getStartInputFlags(focusedView, 0);
+            startInputFlags |= StartInputFlags.WINDOW_GAINED_FOCUS;
 
             final ImeFocusController controller = getFocusController();
             if (controller == null) {
diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java
index 07a721f..49d456b 100644
--- a/core/java/android/widget/Editor.java
+++ b/core/java/android/widget/Editor.java
@@ -387,6 +387,7 @@
     private final SuggestionHelper mSuggestionHelper = new SuggestionHelper();
 
     private boolean mFlagCursorDragFromAnywhereEnabled;
+    private float mCursorDragDirectionMinXYRatio;
     private boolean mFlagInsertionHandleGesturesEnabled;
 
     // Specifies whether the new magnifier (with fish-eye effect) is enabled.
@@ -423,6 +424,11 @@
         mFlagCursorDragFromAnywhereEnabled = AppGlobals.getIntCoreSetting(
                 WidgetFlags.KEY_ENABLE_CURSOR_DRAG_FROM_ANYWHERE,
                 WidgetFlags.ENABLE_CURSOR_DRAG_FROM_ANYWHERE_DEFAULT ? 1 : 0) != 0;
+        final int cursorDragMinAngleFromVertical = AppGlobals.getIntCoreSetting(
+                WidgetFlags.KEY_CURSOR_DRAG_MIN_ANGLE_FROM_VERTICAL,
+                WidgetFlags.CURSOR_DRAG_MIN_ANGLE_FROM_VERTICAL_DEFAULT);
+        mCursorDragDirectionMinXYRatio = EditorTouchState.getXYRatio(
+                cursorDragMinAngleFromVertical);
         mFlagInsertionHandleGesturesEnabled = AppGlobals.getIntCoreSetting(
                 WidgetFlags.KEY_ENABLE_INSERTION_HANDLE_GESTURES,
                 WidgetFlags.ENABLE_INSERTION_HANDLE_GESTURES_DEFAULT ? 1 : 0) != 0;
@@ -432,6 +438,8 @@
         if (TextView.DEBUG_CURSOR) {
             logCursor("Editor", "Cursor drag from anywhere is %s.",
                     mFlagCursorDragFromAnywhereEnabled ? "enabled" : "disabled");
+            logCursor("Editor", "Cursor drag min angle from vertical is %d (= %f x/y ratio)",
+                    cursorDragMinAngleFromVertical, mCursorDragDirectionMinXYRatio);
             logCursor("Editor", "Insertion handle gestures is %s.",
                     mFlagInsertionHandleGesturesEnabled ? "enabled" : "disabled");
             logCursor("Editor", "New magnifier is %s.",
@@ -458,6 +466,11 @@
     }
 
     @VisibleForTesting
+    public void setCursorDragMinAngleFromVertical(int degreesFromVertical) {
+        mCursorDragDirectionMinXYRatio = EditorTouchState.getXYRatio(degreesFromVertical);
+    }
+
+    @VisibleForTesting
     public boolean getFlagInsertionHandleGesturesEnabled() {
         return mFlagInsertionHandleGesturesEnabled;
     }
@@ -6059,12 +6072,7 @@
             return trueLine;
         }
 
-        final int lineHeight = layout.getLineBottom(prevLine) - layout.getLineTop(prevLine);
-        int slop = (int)(LINE_SLOP_MULTIPLIER_FOR_HANDLEVIEWS
-                * (layout.getLineBottom(trueLine) - layout.getLineTop(trueLine)));
-        slop = Math.max(mLineChangeSlopMin,
-                Math.min(mLineChangeSlopMax, lineHeight + slop)) - lineHeight;
-        slop = Math.max(0, slop);
+        final int slop = (int)(LINE_SLOP_MULTIPLIER_FOR_HANDLEVIEWS * mTextView.getLineHeight());
 
         final float verticalOffset = mTextView.viewportToContentVerticalOffset();
         if (trueLine > prevLine && y >= layout.getLineBottom(prevLine) + slop + verticalOffset) {
@@ -6134,10 +6142,11 @@
                     if (mIsDraggingCursor) {
                         performCursorDrag(event);
                     } else if (mFlagCursorDragFromAnywhereEnabled
-                                && mTextView.getLayout() != null
-                                && mTextView.isFocused()
-                                && mTouchState.isMovedEnoughForDrag()
-                                && !mTouchState.isDragCloseToVertical()) {
+                            && mTextView.getLayout() != null
+                            && mTextView.isFocused()
+                            && mTouchState.isMovedEnoughForDrag()
+                            && (mTouchState.getInitialDragDirectionXYRatio()
+                            > mCursorDragDirectionMinXYRatio || mTouchState.isOnHandle())) {
                         startCursorDrag(event);
                     }
                     break;
diff --git a/core/java/android/widget/EditorTouchState.java b/core/java/android/widget/EditorTouchState.java
index 9eb63087..75143686 100644
--- a/core/java/android/widget/EditorTouchState.java
+++ b/core/java/android/widget/EditorTouchState.java
@@ -59,7 +59,7 @@
     private boolean mMultiTapInSameArea;
 
     private boolean mMovedEnoughForDrag;
-    private boolean mIsDragCloseToVertical;
+    private float mInitialDragDirectionXYRatio;
 
     public float getLastDownX() {
         return mLastDownX;
@@ -98,8 +98,23 @@
         return mMovedEnoughForDrag;
     }
 
-    public boolean isDragCloseToVertical() {
-        return mIsDragCloseToVertical && !mIsOnHandle;
+    /**
+     * When {@link #isMovedEnoughForDrag()} is {@code true}, this function returns the x/y ratio for
+     * the initial drag direction. Smaller values indicate that the direction is closer to vertical,
+     * while larger values indicate that the direction is closer to horizontal. For example:
+     * <ul>
+     *     <li>if the drag direction is exactly vertical, this returns 0
+     *     <li>if the drag direction is exactly horizontal, this returns {@link Float#MAX_VALUE}
+     *     <li>if the drag direction is 45 deg from vertical, this returns 1
+     *     <li>if the drag direction is 30 deg from vertical, this returns 0.58 (x delta is smaller
+     *     than y delta)
+     *     <li>if the drag direction is 60 deg from vertical, this returns 1.73 (x delta is bigger
+     *     than y delta)
+     * </ul>
+     * This function never returns negative values, regardless of the direction of the drag.
+     */
+    public float getInitialDragDirectionXYRatio() {
+        return mInitialDragDirectionXYRatio;
     }
 
     public void setIsOnHandle(boolean onHandle) {
@@ -155,7 +170,7 @@
             mLastDownY = event.getY();
             mLastDownMillis = event.getEventTime();
             mMovedEnoughForDrag = false;
-            mIsDragCloseToVertical = false;
+            mInitialDragDirectionXYRatio = 0.0f;
         } else if (action == MotionEvent.ACTION_UP) {
             if (TextView.DEBUG_CURSOR) {
                 logCursor("EditorTouchState", "ACTION_UP");
@@ -164,7 +179,7 @@
             mLastUpY = event.getY();
             mLastUpMillis = event.getEventTime();
             mMovedEnoughForDrag = false;
-            mIsDragCloseToVertical = false;
+            mInitialDragDirectionXYRatio = 0.0f;
         } else if (action == MotionEvent.ACTION_MOVE) {
             if (!mMovedEnoughForDrag) {
                 float deltaX = event.getX() - mLastDownX;
@@ -174,9 +189,8 @@
                 int touchSlop = config.getScaledTouchSlop();
                 mMovedEnoughForDrag = distanceSquared > touchSlop * touchSlop;
                 if (mMovedEnoughForDrag) {
-                    // If the direction of the swipe motion is within 45 degrees of vertical, it is
-                    // considered a vertical drag.
-                    mIsDragCloseToVertical = Math.abs(deltaX) <= Math.abs(deltaY);
+                    mInitialDragDirectionXYRatio = (deltaY == 0) ? Float.MAX_VALUE :
+                            Math.abs(deltaX / deltaY);
                 }
             }
         } else if (action == MotionEvent.ACTION_CANCEL) {
@@ -185,7 +199,7 @@
             mMultiTapStatus = MultiTapStatus.NONE;
             mMultiTapInSameArea = false;
             mMovedEnoughForDrag = false;
-            mIsDragCloseToVertical = false;
+            mInitialDragDirectionXYRatio = 0.0f;
         }
     }
 
@@ -201,4 +215,27 @@
         float distanceSquared = (deltaX * deltaX) + (deltaY * deltaY);
         return distanceSquared <= maxDistance * maxDistance;
     }
+
+    /**
+     * Returns the x/y ratio corresponding to the given angle relative to vertical. Smaller angle
+     * values (ie, closer to vertical) will result in a smaller x/y ratio. For example:
+     * <ul>
+     *     <li>if the angle is 45 deg, the ratio is 1
+     *     <li>if the angle is 30 deg, the ratio is 0.58 (x delta is smaller than y delta)
+     *     <li>if the angle is 60 deg, the ratio is 1.73 (x delta is bigger than y delta)
+     * </ul>
+     * If the passed-in value is <= 0, this function returns 0. If the passed-in value is >= 90,
+     * this function returns {@link Float#MAX_VALUE}.
+     *
+     * @see #getInitialDragDirectionXYRatio()
+     */
+    public static float getXYRatio(int angleFromVerticalInDegrees) {
+        if (angleFromVerticalInDegrees <= 0) {
+            return 0.0f;
+        }
+        if (angleFromVerticalInDegrees >= 90) {
+            return Float.MAX_VALUE;
+        }
+        return (float) Math.tan(Math.toRadians(angleFromVerticalInDegrees));
+    }
 }
diff --git a/core/java/android/widget/ProgressBar.java b/core/java/android/widget/ProgressBar.java
index 4b32e10..5b5468a 100644
--- a/core/java/android/widget/ProgressBar.java
+++ b/core/java/android/widget/ProgressBar.java
@@ -54,6 +54,7 @@
 import android.view.ViewDebug;
 import android.view.ViewHierarchyEncoder;
 import android.view.accessibility.AccessibilityEvent;
+import android.view.accessibility.AccessibilityManager;
 import android.view.accessibility.AccessibilityNodeInfo;
 import android.view.animation.AlphaAnimation;
 import android.view.animation.Animation;
@@ -310,9 +311,6 @@
         setMax(a.getInt(R.styleable.ProgressBar_max, mMax));
 
         setProgress(a.getInt(R.styleable.ProgressBar_progress, mProgress));
-        // onProgressRefresh() is only called when the progress changes. So we should set
-        // stateDescription during initialization here.
-        super.setStateDescription(formatStateDescription(mProgress));
 
         setSecondaryProgress(a.getInt(
                 R.styleable.ProgressBar_secondaryProgress, mSecondaryProgress));
@@ -1616,7 +1614,8 @@
     }
 
     void onProgressRefresh(float scale, boolean fromUser, int progress) {
-        if (mCustomStateDescription == null) {
+        if (AccessibilityManager.getInstance(mContext).isEnabled()
+                && mCustomStateDescription == null) {
             super.setStateDescription(formatStateDescription(mProgress));
         }
     }
@@ -2340,6 +2339,7 @@
                     AccessibilityNodeInfo.RangeInfo.RANGE_TYPE_INT, getMin(), getMax(),
                     getProgress());
             info.setRangeInfo(rangeInfo);
+            info.setStateDescription(formatStateDescription(mProgress));
         }
     }
 
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index f2bc89e..e16bf5a 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -395,6 +395,10 @@
     private static final int EMS = LINES;
     private static final int PIXELS = 2;
 
+    // Maximum text length for single line input.
+    private static final int MAX_LENGTH_FOR_SINGLE_LINE_EDIT_TEXT = 5000;
+    private InputFilter.LengthFilter mSingleLineLengthFilter = null;
+
     private static final RectF TEMP_RECTF = new RectF();
 
     /** @hide */
@@ -1589,7 +1593,11 @@
         // Same as setSingleLine(), but make sure the transformation method and the maximum number
         // of lines of height are unchanged for multi-line TextViews.
         setInputTypeSingleLine(singleLine);
-        applySingleLine(singleLine, singleLine, singleLine);
+        applySingleLine(singleLine, singleLine, singleLine,
+                // Does not apply automated max length filter since length filter will be resolved
+                // later in this function.
+                false
+        );
 
         if (singleLine && getKeyListener() == null && ellipsize == ELLIPSIZE_NOT_SET) {
             ellipsize = ELLIPSIZE_END;
@@ -1633,7 +1641,16 @@
             setTransformationMethod(PasswordTransformationMethod.getInstance());
         }
 
-        if (maxlength >= 0) {
+        // For addressing b/145128646
+        // For the performance reason, we limit characters for single line text field.
+        if (bufferType == BufferType.EDITABLE && singleLine && maxlength == -1) {
+            mSingleLineLengthFilter = new InputFilter.LengthFilter(
+                MAX_LENGTH_FOR_SINGLE_LINE_EDIT_TEXT);
+        }
+
+        if (mSingleLineLengthFilter != null) {
+            setFilters(new InputFilter[] { mSingleLineLengthFilter });
+        } else if (maxlength >= 0) {
             setFilters(new InputFilter[] { new InputFilter.LengthFilter(maxlength) });
         } else {
             setFilters(NO_FILTERS);
@@ -6590,7 +6607,7 @@
         if (mSingleLine != singleLine || forceUpdate) {
             // Change single line mode, but only change the transformation if
             // we are not in password mode.
-            applySingleLine(singleLine, !isPassword, true);
+            applySingleLine(singleLine, !isPassword, true, true);
         }
 
         if (!isSuggestionsEnabled()) {
@@ -10229,6 +10246,9 @@
      * Note that the default conditions are not necessarily those that were in effect prior this
      * method, and you may want to reset these properties to your custom values.
      *
+     * Note that due to performance reasons, by setting single line for the EditText, the maximum
+     * text length is set to 5000 if no other character limitation are applied.
+     *
      * @attr ref android.R.styleable#TextView_singleLine
      */
     @android.view.RemotableViewMethod
@@ -10236,7 +10256,7 @@
         // Could be used, but may break backward compatibility.
         // if (mSingleLine == singleLine) return;
         setInputTypeSingleLine(singleLine);
-        applySingleLine(singleLine, true, true);
+        applySingleLine(singleLine, true, true, true);
     }
 
     /**
@@ -10256,14 +10276,40 @@
     }
 
     private void applySingleLine(boolean singleLine, boolean applyTransformation,
-            boolean changeMaxLines) {
+            boolean changeMaxLines, boolean changeMaxLength) {
         mSingleLine = singleLine;
+
         if (singleLine) {
             setLines(1);
             setHorizontallyScrolling(true);
             if (applyTransformation) {
                 setTransformationMethod(SingleLineTransformationMethod.getInstance());
             }
+
+            if (!changeMaxLength) return;
+
+            // Single line length filter is only applicable editable text.
+            if (mBufferType != BufferType.EDITABLE) return;
+
+            final InputFilter[] prevFilters = getFilters();
+            for (InputFilter filter: getFilters()) {
+                // We don't add LengthFilter if already there.
+                if (filter instanceof InputFilter.LengthFilter) return;
+            }
+
+            if (mSingleLineLengthFilter == null) {
+                mSingleLineLengthFilter = new InputFilter.LengthFilter(
+                    MAX_LENGTH_FOR_SINGLE_LINE_EDIT_TEXT);
+            }
+
+            final InputFilter[] newFilters = new InputFilter[prevFilters.length + 1];
+            System.arraycopy(prevFilters, 0, newFilters, 0, prevFilters.length);
+            newFilters[prevFilters.length] = mSingleLineLengthFilter;
+
+            setFilters(newFilters);
+
+            // Since filter doesn't apply to existing text, trigger filter by setting text.
+            setText(getText());
         } else {
             if (changeMaxLines) {
                 setMaxLines(Integer.MAX_VALUE);
@@ -10272,6 +10318,47 @@
             if (applyTransformation) {
                 setTransformationMethod(null);
             }
+
+            if (!changeMaxLength) return;
+
+            // Single line length filter is only applicable editable text.
+            if (mBufferType != BufferType.EDITABLE) return;
+
+            final InputFilter[] prevFilters = getFilters();
+            if (prevFilters.length == 0) return;
+
+            // Short Circuit: if mSingleLineLengthFilter is not allocated, nobody sets automated
+            // single line char limit filter.
+            if (mSingleLineLengthFilter == null) return;
+
+            // If we need to remove mSingleLineLengthFilter, we need to allocate another array.
+            // Since filter list is expected to be small and want to avoid unnecessary array
+            // allocation, check if there is mSingleLengthFilter first.
+            int targetIndex = -1;
+            for (int i = 0; i < prevFilters.length; ++i) {
+                if (prevFilters[i] == mSingleLineLengthFilter) {
+                    targetIndex = i;
+                    break;
+                }
+            }
+            if (targetIndex == -1) return;  // not found. Do nothing.
+
+            if (prevFilters.length == 1) {
+                setFilters(NO_FILTERS);
+                return;
+            }
+
+            // Create new array which doesn't include mSingleLengthFilter.
+            final InputFilter[] newFilters = new InputFilter[prevFilters.length - 1];
+            System.arraycopy(prevFilters, 0, newFilters, 0, targetIndex);
+            System.arraycopy(
+                    prevFilters,
+                    targetIndex + 1,
+                    newFilters,
+                    targetIndex,
+                    prevFilters.length - targetIndex - 1);
+            setFilters(newFilters);
+            mSingleLineLengthFilter = null;
         }
     }
 
diff --git a/core/java/android/widget/WidgetFlags.java b/core/java/android/widget/WidgetFlags.java
index 09ab5aa..e272086 100644
--- a/core/java/android/widget/WidgetFlags.java
+++ b/core/java/android/widget/WidgetFlags.java
@@ -41,6 +41,28 @@
     public static final boolean ENABLE_CURSOR_DRAG_FROM_ANYWHERE_DEFAULT = true;
 
     /**
+     * Threshold for the direction of a swipe gesture in order for it to be handled as a cursor drag
+     * rather than a scroll. The direction angle of the swipe gesture must exceed this value in
+     * order to trigger cursor drag; otherwise, the swipe will be assumed to be a scroll gesture.
+     * The value units for this flag is degrees and the valid range is [0,90] inclusive. If a value
+     * < 0 is set, 0 will be used instead; if a value > 90 is set, 90 will be used instead.
+     */
+    public static final String CURSOR_DRAG_MIN_ANGLE_FROM_VERTICAL =
+            "CursorControlFeature__min_angle_from_vertical_to_start_cursor_drag";
+
+    /**
+     * The key used in app core settings for the flag
+     * {@link #CURSOR_DRAG_MIN_ANGLE_FROM_VERTICAL}.
+     */
+    public static final String KEY_CURSOR_DRAG_MIN_ANGLE_FROM_VERTICAL =
+            "widget__min_angle_from_vertical_to_start_cursor_drag";
+
+    /**
+     * Default value for the flag {@link #CURSOR_DRAG_MIN_ANGLE_FROM_VERTICAL}.
+     */
+    public static final int CURSOR_DRAG_MIN_ANGLE_FROM_VERTICAL_DEFAULT = 45;
+
+    /**
      * The flag of finger-to-cursor distance in DP for cursor dragging.
      * The value unit is DP and the range is {0..100}. If the value is out of range, the legacy
      * value, which is based on handle size, will be used.
diff --git a/core/java/com/android/internal/app/ChooserActivity.java b/core/java/com/android/internal/app/ChooserActivity.java
index 236e67a..9679a8c 100644
--- a/core/java/com/android/internal/app/ChooserActivity.java
+++ b/core/java/com/android/internal/app/ChooserActivity.java
@@ -273,8 +273,6 @@
     private int mLastNumberOfChildren = -1;
 
     private static final String TARGET_DETAILS_FRAGMENT_TAG = "targetDetailsFragment";
-    // TODO: Update to handle landscape instead of using static value
-    private static final int MAX_RANKED_TARGETS = 4;
 
     private final List<ChooserTargetServiceConnection> mServiceConnections = new ArrayList<>();
     private final Set<Pair<ComponentName, UserHandle>> mServicesRequested = new HashSet<>();
@@ -784,8 +782,8 @@
                 FrameworkStatsLog.SHARESHEET_STARTED,
                 getReferrerPackageName(),
                 target.getType(),
-                initialIntents == null ? 0 : initialIntents.length,
                 mCallerChooserTargets == null ? 0 : mCallerChooserTargets.length,
+                initialIntents == null ? 0 : initialIntents.length,
                 isWorkProfile(),
                 findPreferredContentPreview(getTargetIntent(), getContentResolver()),
                 target.getAction()
@@ -1763,7 +1761,7 @@
                 case ChooserListAdapter.TARGET_CALLER:
                 case ChooserListAdapter.TARGET_STANDARD:
                     cat = MetricsEvent.ACTION_ACTIVITY_CHOOSER_PICKED_APP_TARGET;
-                    value -= currentListAdapter.getSelectableServiceTargetCount();
+                    value -= currentListAdapter.getSurfacedTargetInfo().size();
                     numCallerProvided = currentListAdapter.getCallerTargetCount();
                     getChooserActivityLogger().logShareTargetSelected(
                             SELECTION_TYPE_APP,
@@ -2641,7 +2639,10 @@
         }
         RecyclerView recyclerView = mChooserMultiProfilePagerAdapter.getActiveAdapterView();
         ChooserGridAdapter gridAdapter = mChooserMultiProfilePagerAdapter.getCurrentRootAdapter();
-        if (gridAdapter == null || recyclerView == null) {
+        // Skip height calculation if recycler view was scrolled to prevent it inaccurately
+        // calculating the height, as the logic below does not account for the scrolled offset.
+        if (gridAdapter == null || recyclerView == null
+                || recyclerView.computeVerticalScrollOffset() != 0) {
             return;
         }
 
@@ -3128,6 +3129,13 @@
         ChooserGridAdapter currentRootAdapter =
                 mChooserMultiProfilePagerAdapter.getCurrentRootAdapter();
         currentRootAdapter.updateDirectShareExpansion();
+        // This fixes an edge case where after performing a variety of gestures, vertical scrolling
+        // ends up disabled. That's because at some point the old tab's vertical scrolling is
+        // disabled and the new tab's is enabled. For context, see b/159997845
+        setVerticalScrollEnabled(true);
+        if (mResolverDrawerLayout != null) {
+            mResolverDrawerLayout.scrollNestedScrollableChildBackToTop();
+        }
     }
 
     @Override
@@ -3286,9 +3294,8 @@
         }
 
         /**
-         * Returns either {@code 0} or {@code 1} depending on whether we want to show the list item
-         * content preview. Not to be confused with the sticky content preview which is above the
-         * personal and work tabs.
+         * Whether the "system" row of targets is displayed.
+         * This area includes the content preview (if present) and action row.
          */
         public int getSystemRowCount() {
             // For the tabbed case we show the sticky content preview above the tabs,
@@ -3296,6 +3303,7 @@
             if (shouldShowTabs()) {
                 return 0;
             }
+
             if (!isSendAction(getTargetIntent())) {
                 return 0;
             }
diff --git a/core/java/com/android/internal/app/ChooserMultiProfilePagerAdapter.java b/core/java/com/android/internal/app/ChooserMultiProfilePagerAdapter.java
index ffa6041..3a65a32 100644
--- a/core/java/com/android/internal/app/ChooserMultiProfilePagerAdapter.java
+++ b/core/java/com/android/internal/app/ChooserMultiProfilePagerAdapter.java
@@ -252,8 +252,10 @@
 
     @Override
     protected void setupContainerPadding(View container) {
+        int initialBottomPadding = getContext().getResources().getDimensionPixelSize(
+                R.dimen.resolver_empty_state_container_padding_bottom);
         container.setPadding(container.getPaddingLeft(), container.getPaddingTop(),
-                container.getPaddingRight(), container.getPaddingBottom() + mBottomOffset);
+                container.getPaddingRight(), initialBottomPadding + mBottomOffset);
     }
 
     class ChooserProfileDescriptor extends ProfileDescriptor {
diff --git a/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java b/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java
index d238d0e..b4cd145 100644
--- a/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java
+++ b/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java
@@ -120,6 +120,18 @@
      */
     public static final String HASH_SALT_MAX_DAYS = "hash_salt_max_days";
 
+    // Flag related to Privacy Indicators
+
+    /**
+     * Whether to show the complete ongoing app ops chip.
+     */
+    public static final String PROPERTY_PERMISSIONS_HUB_ENABLED = "permissions_hub_2_enabled";
+
+    /**
+     * Whether to show app ops chip for just microphone + camera.
+     */
+    public static final String PROPERTY_MIC_CAMERA_ENABLED = "camera_mic_icons_enabled";
+
     // Flags related to Assistant
 
     /**
@@ -378,6 +390,20 @@
     public static final String CHOOSER_TARGET_RANKING_ENABLED = "chooser_target_ranking_enabled";
 
     /**
+     * (float) Weight bonus applied on top sharing shortcuts as per native ranking provided by apps.
+     * Its range need to be 0 ~ 1.
+     */
+    public static final String TOP_NATIVE_RANKED_SHARING_SHORTCUTS_BOOSTER =
+            "top_native_ranked_sharing_shortcut_booster";
+
+    /**
+     * (float) Weight bonus applied on 2nd top sharing shortcuts as per native ranking provided by
+     * apps. Its range need to be 0 ~ 1.
+     */
+    public static final String NON_TOP_NATIVE_RANKED_SHARING_SHORTCUTS_BOOSTER =
+            "non_top_native_ranked_sharing_shortcut_booster";
+
+    /**
      * (boolean) Whether to enable user-drag resizing for PIP.
      */
     public static final String PIP_USER_RESIZE = "pip_user_resize";
@@ -397,6 +423,11 @@
      */
     public static final String BACK_GESTURE_SLOP_MULTIPLIER = "back_gesture_slop_multiplier";
 
+    /**
+     * (long) Screenshot keychord delay (how long the buttons must be pressed), in ms
+     */
+    public static final String SCREENSHOT_KEYCHORD_DELAY = "screenshot_keychord_delay";
+
     private SystemUiDeviceConfigFlags() {
     }
 }
diff --git a/core/java/com/android/internal/content/FileSystemProvider.java b/core/java/com/android/internal/content/FileSystemProvider.java
index a50a522..3b5fecf 100644
--- a/core/java/com/android/internal/content/FileSystemProvider.java
+++ b/core/java/com/android/internal/content/FileSystemProvider.java
@@ -113,6 +113,14 @@
         // Default is no-op
     }
 
+    /**
+     * Callback indicating that the given document has been deleted or moved. This gives
+     * the provider a hook to revoke the uri permissions.
+     */
+    protected void onDocIdDeleted(String docId) {
+        // Default is no-op
+    }
+
     @Override
     public boolean onCreate() {
         throw new UnsupportedOperationException(
@@ -283,6 +291,7 @@
 
         final String afterDocId = getDocIdForFile(after);
         onDocIdChanged(docId);
+        onDocIdDeleted(docId);
         onDocIdChanged(afterDocId);
 
         final File afterVisibleFile = getFileForDocId(afterDocId, true);
@@ -312,6 +321,7 @@
 
         final String docId = getDocIdForFile(after);
         onDocIdChanged(sourceDocumentId);
+        onDocIdDeleted(sourceDocumentId);
         onDocIdChanged(docId);
         moveInMediaStore(visibleFileBefore, getFileForDocId(docId, true));
 
@@ -343,6 +353,7 @@
         }
 
         onDocIdChanged(docId);
+        onDocIdDeleted(docId);
         removeFromMediaStore(visibleFile);
     }
 
diff --git a/core/java/com/android/internal/inputmethod/InputMethodDebug.java b/core/java/com/android/internal/inputmethod/InputMethodDebug.java
index 37f6823..3bcba75 100644
--- a/core/java/com/android/internal/inputmethod/InputMethodDebug.java
+++ b/core/java/com/android/internal/inputmethod/InputMethodDebug.java
@@ -224,6 +224,8 @@
                 return "HIDE_DOCKED_STACK_ATTACHED";
             case SoftInputShowHideReason.HIDE_RECENTS_ANIMATION:
                 return "HIDE_RECENTS_ANIMATION";
+            case SoftInputShowHideReason.HIDE_SAME_WINDOW_FOCUSED_WITHOUT_EDITOR:
+                return "HIDE_SAME_WINDOW_FOCUSED_WITHOUT_EDITOR";
             default:
                 return "Unknown=" + reason;
         }
diff --git a/core/java/com/android/internal/inputmethod/SoftInputShowHideReason.java b/core/java/com/android/internal/inputmethod/SoftInputShowHideReason.java
index 4b968b4..f46626b 100644
--- a/core/java/com/android/internal/inputmethod/SoftInputShowHideReason.java
+++ b/core/java/com/android/internal/inputmethod/SoftInputShowHideReason.java
@@ -47,7 +47,8 @@
         SoftInputShowHideReason.HIDE_POWER_BUTTON_GO_HOME,
         SoftInputShowHideReason.HIDE_DOCKED_STACK_ATTACHED,
         SoftInputShowHideReason.HIDE_RECENTS_ANIMATION,
-        SoftInputShowHideReason.HIDE_BUBBLES})
+        SoftInputShowHideReason.HIDE_BUBBLES,
+        SoftInputShowHideReason.HIDE_SAME_WINDOW_FOCUSED_WITHOUT_EDITOR})
 public @interface SoftInputShowHideReason {
     /** Show soft input by {@link android.view.inputmethod.InputMethodManager#showSoftInput}. */
     int SHOW_SOFT_INPUT = 0;
@@ -147,4 +148,17 @@
      * switching, or collapsing Bubbles.
      */
     int HIDE_BUBBLES = 19;
+
+    /**
+     * Hide soft input when focusing the same window (e.g. screen turned-off and turn-on) which no
+     * valid focused editor.
+     *
+     * Note: From Android R, the window focus change callback is processed by InputDispatcher,
+     * some focus behavior changes (e.g. There are an activity with a dialog window, after
+     * screen turned-off and turned-on, before Android R the window focus sequence would be
+     * the activity first and then the dialog focused, however, in R the focus sequence would be
+     * only the dialog focused as it's the latest window with input focus) makes we need to hide
+     * soft-input when the same window focused again to align with the same behavior prior to R.
+     */
+    int HIDE_SAME_WINDOW_FOCUSED_WITHOUT_EDITOR = 20;
 }
diff --git a/core/java/com/android/internal/inputmethod/StartInputFlags.java b/core/java/com/android/internal/inputmethod/StartInputFlags.java
index 5a8d2c2..ac83987 100644
--- a/core/java/com/android/internal/inputmethod/StartInputFlags.java
+++ b/core/java/com/android/internal/inputmethod/StartInputFlags.java
@@ -47,4 +47,10 @@
      * documented hence we probably need to revisit this though.
      */
     int INITIAL_CONNECTION = 4;
+
+    /**
+     * The start input happens when the window gained focus to call
+     * {@code android.view.inputmethod.InputMethodManager#startInputAsyncOnWindowFocusGain}.
+     */
+    int WINDOW_GAINED_FOCUS = 8;
 }
diff --git a/core/java/com/android/internal/os/logging/MetricsLoggerWrapper.java b/core/java/com/android/internal/os/logging/MetricsLoggerWrapper.java
index ba60fa5..b42ea7d 100644
--- a/core/java/com/android/internal/os/logging/MetricsLoggerWrapper.java
+++ b/core/java/com/android/internal/os/logging/MetricsLoggerWrapper.java
@@ -16,14 +16,8 @@
 
 package com.android.internal.os.logging;
 
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.pm.PackageManager.NameNotFoundException;
-import android.util.Pair;
 import android.view.WindowManager.LayoutParams;
 
-import com.android.internal.logging.MetricsLogger;
-import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 import com.android.internal.util.FrameworkStatsLog;
 
 /**
@@ -32,81 +26,6 @@
  */
 public class MetricsLoggerWrapper {
 
-    private static final int METRIC_VALUE_DISMISSED_BY_TAP = 0;
-    private static final int METRIC_VALUE_DISMISSED_BY_DRAG = 1;
-
-    public static void logPictureInPictureDismissByTap(Context context,
-            Pair<ComponentName, Integer> topActivityInfo) {
-        MetricsLogger.action(context, MetricsEvent.ACTION_PICTURE_IN_PICTURE_DISMISSED,
-                METRIC_VALUE_DISMISSED_BY_TAP);
-        FrameworkStatsLog.write(FrameworkStatsLog.PICTURE_IN_PICTURE_STATE_CHANGED,
-                getUid(context, topActivityInfo.first, topActivityInfo.second),
-                topActivityInfo.first.flattenToString(),
-                FrameworkStatsLog.PICTURE_IN_PICTURE_STATE_CHANGED__STATE__DISMISSED);
-    }
-
-    public static void logPictureInPictureDismissByDrag(Context context,
-            Pair<ComponentName, Integer> topActivityInfo) {
-        MetricsLogger.action(context,
-                MetricsEvent.ACTION_PICTURE_IN_PICTURE_DISMISSED,
-                METRIC_VALUE_DISMISSED_BY_DRAG);
-        FrameworkStatsLog.write(FrameworkStatsLog.PICTURE_IN_PICTURE_STATE_CHANGED,
-                getUid(context, topActivityInfo.first, topActivityInfo.second),
-                topActivityInfo.first.flattenToString(),
-                FrameworkStatsLog.PICTURE_IN_PICTURE_STATE_CHANGED__STATE__DISMISSED);
-    }
-
-    public static void logPictureInPictureMinimize(Context context, boolean isMinimized,
-            Pair<ComponentName, Integer> topActivityInfo) {
-        MetricsLogger.action(context, MetricsEvent.ACTION_PICTURE_IN_PICTURE_MINIMIZED,
-                isMinimized);
-        FrameworkStatsLog.write(FrameworkStatsLog.PICTURE_IN_PICTURE_STATE_CHANGED,
-                getUid(context, topActivityInfo.first, topActivityInfo.second),
-                topActivityInfo.first.flattenToString(),
-                FrameworkStatsLog.PICTURE_IN_PICTURE_STATE_CHANGED__STATE__MINIMIZED);
-    }
-
-    /**
-     * Get uid from component name and user Id
-     * @return uid. -1 if not found.
-     */
-    private static int getUid(Context context, ComponentName componentName, int userId) {
-        int uid = -1;
-        if (componentName == null) {
-            return uid;
-        }
-        try {
-            uid = context.getPackageManager().getApplicationInfoAsUser(
-                    componentName.getPackageName(), 0, userId).uid;
-        } catch (NameNotFoundException e) {
-        }
-        return uid;
-    }
-
-    public static void logPictureInPictureMenuVisible(Context context, boolean menuStateFull) {
-        MetricsLogger.visibility(context, MetricsEvent.ACTION_PICTURE_IN_PICTURE_MENU,
-                menuStateFull);
-    }
-
-    public static void logPictureInPictureEnter(Context context,
-            int uid, String shortComponentName, boolean supportsEnterPipOnTaskSwitch) {
-        MetricsLogger.action(context, MetricsEvent.ACTION_PICTURE_IN_PICTURE_ENTERED,
-                supportsEnterPipOnTaskSwitch);
-        FrameworkStatsLog.write(FrameworkStatsLog.PICTURE_IN_PICTURE_STATE_CHANGED, uid,
-                shortComponentName,
-                FrameworkStatsLog.PICTURE_IN_PICTURE_STATE_CHANGED__STATE__ENTERED);
-    }
-
-    public static void logPictureInPictureFullScreen(Context context, int uid,
-            String shortComponentName) {
-        MetricsLogger.action(context,
-                MetricsEvent.ACTION_PICTURE_IN_PICTURE_EXPANDED_TO_FULLSCREEN);
-        FrameworkStatsLog.write(FrameworkStatsLog.PICTURE_IN_PICTURE_STATE_CHANGED,
-                uid,
-                shortComponentName,
-                FrameworkStatsLog.PICTURE_IN_PICTURE_STATE_CHANGED__STATE__EXPANDED_TO_FULL_SCREEN);
-    }
-
     public static void logAppOverlayEnter(int uid, String packageName, boolean changed, int type, boolean usingAlertWindow) {
         if (changed) {
             if (type != LayoutParams.TYPE_APPLICATION_OVERLAY) {
diff --git a/core/java/com/android/internal/policy/DecorView.java b/core/java/com/android/internal/policy/DecorView.java
index b12c5e9..fbbf791 100644
--- a/core/java/com/android/internal/policy/DecorView.java
+++ b/core/java/com/android/internal/policy/DecorView.java
@@ -1094,13 +1094,15 @@
             mLastWindowFlags = attrs.flags;
 
             if (insets != null) {
-                final Insets systemBarInsets = insets.getInsets(WindowInsets.Type.systemBars());
                 final Insets stableBarInsets = insets.getInsetsIgnoringVisibility(
                         WindowInsets.Type.systemBars());
-                mLastTopInset = systemBarInsets.top;
-                mLastBottomInset = systemBarInsets.bottom;
-                mLastRightInset = systemBarInsets.right;
-                mLastLeftInset = systemBarInsets.left;
+                final Insets systemInsets = Insets.min(
+                        insets.getInsets(WindowInsets.Type.systemBars()
+                                | WindowInsets.Type.displayCutout()), stableBarInsets);
+                mLastTopInset = systemInsets.top;
+                mLastBottomInset = systemInsets.bottom;
+                mLastRightInset = systemInsets.right;
+                mLastLeftInset = systemInsets.left;
 
                 // Don't animate if the presence of stable insets has changed, because that
                 // indicates that the window was either just added and received them for the
diff --git a/core/java/com/android/internal/policy/PhoneWindow.java b/core/java/com/android/internal/policy/PhoneWindow.java
index 046981c..d90a022 100644
--- a/core/java/com/android/internal/policy/PhoneWindow.java
+++ b/core/java/com/android/internal/policy/PhoneWindow.java
@@ -1510,11 +1510,13 @@
         if (drawable != mBackgroundDrawable) {
             mBackgroundDrawable = drawable;
             if (mDecor != null) {
+                mDecor.startChanging();
                 mDecor.setWindowBackground(drawable);
                 if (mBackgroundFallbackDrawable != null) {
                     mDecor.setBackgroundFallback(drawable != null ? null :
                             mBackgroundFallbackDrawable);
                 }
+                mDecor.finishChanging();
             }
         }
     }
diff --git a/core/java/com/android/internal/util/ScreenshotHelper.java b/core/java/com/android/internal/util/ScreenshotHelper.java
index 9bf0513..a23fc4b 100644
--- a/core/java/com/android/internal/util/ScreenshotHelper.java
+++ b/core/java/com/android/internal/util/ScreenshotHelper.java
@@ -291,7 +291,7 @@
             };
 
             Message msg = Message.obtain(null, screenshotType, screenshotRequest);
-            final ServiceConnection myConn = mScreenshotConnection;
+
             Handler h = new Handler(handler.getLooper()) {
                 @Override
                 public void handleMessage(Message msg) {
@@ -304,8 +304,8 @@
                             break;
                         case SCREENSHOT_MSG_PROCESS_COMPLETE:
                             synchronized (mScreenshotLock) {
-                                if (myConn != null && mScreenshotConnection == myConn) {
-                                    mContext.unbindService(myConn);
+                                if (mScreenshotConnection != null) {
+                                    mContext.unbindService(mScreenshotConnection);
                                     mScreenshotConnection = null;
                                     mScreenshotService = null;
                                 }
@@ -368,6 +368,7 @@
                 }
             } else {
                 Messenger messenger = new Messenger(mScreenshotService);
+
                 try {
                     messenger.send(msg);
                 } catch (RemoteException e) {
diff --git a/core/java/com/android/internal/widget/LocalImageResolver.java b/core/java/com/android/internal/widget/LocalImageResolver.java
index 2302de2..b4e108f 100644
--- a/core/java/com/android/internal/widget/LocalImageResolver.java
+++ b/core/java/com/android/internal/widget/LocalImageResolver.java
@@ -23,6 +23,7 @@
 import android.graphics.drawable.BitmapDrawable;
 import android.graphics.drawable.Drawable;
 import android.net.Uri;
+import android.util.Log;
 
 import java.io.IOException;
 import java.io.InputStream;
@@ -31,6 +32,7 @@
  * A class to extract Bitmaps from a MessagingStyle message.
  */
 public class LocalImageResolver {
+    private static final String TAG = LocalImageResolver.class.getSimpleName();
 
     private static final int MAX_SAFE_ICON_SIZE_PX = 480;
 
@@ -60,11 +62,18 @@
 
     private static BitmapFactory.Options getBoundsOptionsForImage(Uri uri, Context context)
             throws IOException {
-        InputStream input = context.getContentResolver().openInputStream(uri);
         BitmapFactory.Options onlyBoundsOptions = new BitmapFactory.Options();
-        onlyBoundsOptions.inJustDecodeBounds = true;
-        BitmapFactory.decodeStream(input, null, onlyBoundsOptions);
-        input.close();
+        try (InputStream input = context.getContentResolver().openInputStream(uri)) {
+            if (input == null) {
+                throw new IllegalArgumentException();
+            }
+            onlyBoundsOptions.inJustDecodeBounds = true;
+            BitmapFactory.decodeStream(input, null, onlyBoundsOptions);
+        } catch (IllegalArgumentException iae) {
+            onlyBoundsOptions.outWidth = -1;
+            onlyBoundsOptions.outHeight = -1;
+            Log.e(TAG, "error loading image", iae);
+        }
         return onlyBoundsOptions;
     }
 
diff --git a/core/java/com/android/internal/widget/ResolverDrawerLayout.java b/core/java/com/android/internal/widget/ResolverDrawerLayout.java
index 3f708f8..90eeabb 100644
--- a/core/java/com/android/internal/widget/ResolverDrawerLayout.java
+++ b/core/java/com/android/internal/widget/ResolverDrawerLayout.java
@@ -464,11 +464,7 @@
                             smoothScrollTo(mCollapsibleHeight + mUncollapsibleHeight, yvel);
                             mDismissOnScrollerFinished = true;
                         } else {
-                            if (isNestedListChildScrolled()) {
-                                mNestedListChild.smoothScrollToPosition(0);
-                            } else if (isNestedRecyclerChildScrolled()) {
-                                mNestedRecyclerChild.smoothScrollToPosition(0);
-                            }
+                            scrollNestedScrollableChildBackToTop();
                             smoothScrollTo(yvel < 0 ? 0 : mCollapsibleHeight, yvel);
                         }
                     }
@@ -493,6 +489,17 @@
         return handled;
     }
 
+    /**
+     * Scroll nested scrollable child back to top if it has been scrolled.
+     */
+    public void scrollNestedScrollableChildBackToTop() {
+        if (isNestedListChildScrolled()) {
+            mNestedListChild.smoothScrollToPosition(0);
+        } else if (isNestedRecyclerChildScrolled()) {
+            mNestedRecyclerChild.smoothScrollToPosition(0);
+        }
+    }
+
     private void onSecondaryPointerUp(MotionEvent ev) {
         final int pointerIndex = ev.getActionIndex();
         final int pointerId = ev.getPointerId(pointerIndex);
diff --git a/core/jni/android_hardware_camera2_CameraMetadata.cpp b/core/jni/android_hardware_camera2_CameraMetadata.cpp
index 9ad4cd9..b5955c4 100644
--- a/core/jni/android_hardware_camera2_CameraMetadata.cpp
+++ b/core/jni/android_hardware_camera2_CameraMetadata.cpp
@@ -249,6 +249,16 @@
     return metadata->entryCount();
 }
 
+static jlong CameraMetadata_getBufferSize(JNIEnv *env, jclass thiz, jlong ptr) {
+    ALOGV("%s", __FUNCTION__);
+
+    CameraMetadata* metadata = CameraMetadata_getPointerThrow(env, ptr);
+
+    if (metadata == NULL) return 0;
+
+    return metadata->bufferSize();
+}
+
 // idempotent. calling more than once has no effect.
 static void CameraMetadata_close(JNIEnv *env, jclass thiz, jlong ptr) {
     ALOGV("%s", __FUNCTION__);
@@ -552,6 +562,9 @@
   { "nativeGetEntryCount",
     "(J)I",
     (void*)CameraMetadata_getEntryCount },
+  { "nativeGetBufferSize",
+    "(J)J",
+    (void*)CameraMetadata_getBufferSize },
   { "nativeClose",
     "(J)V",
     (void*)CameraMetadata_close },
diff --git a/core/jni/android_media_AudioTrack.cpp b/core/jni/android_media_AudioTrack.cpp
index 5c045b6..7a5c383 100644
--- a/core/jni/android_media_AudioTrack.cpp
+++ b/core/jni/android_media_AudioTrack.cpp
@@ -20,6 +20,7 @@
 #include "android_media_AudioTrack.h"
 
 #include <nativehelper/JNIHelp.h>
+#include <nativehelper/ScopedUtfChars.h>
 #include "core_jni_helpers.h"
 
 #include <utils/Log.h>
@@ -251,7 +252,7 @@
                                            jint audioFormat, jint buffSizeInBytes, jint memoryMode,
                                            jintArray jSession, jlong nativeAudioTrack,
                                            jboolean offload, jint encapsulationMode,
-                                           jobject tunerConfiguration) {
+                                           jobject tunerConfiguration, jstring opPackageName) {
     ALOGV("sampleRates=%p, channel mask=%x, index mask=%x, audioFormat(Java)=%d, buffSize=%d,"
           " nativeAudioTrack=0x%" PRIX64 ", offload=%d encapsulationMode=%d tuner=%p",
           jSampleRate, channelPositionMask, channelIndexMask, audioFormat, buffSizeInBytes,
@@ -337,7 +338,8 @@
         }
 
         // create the native AudioTrack object
-        lpTrack = new AudioTrack();
+        ScopedUtfChars opPackageNameStr(env, opPackageName);
+        lpTrack = new AudioTrack(opPackageNameStr.c_str());
 
         // read the AudioAttributes values
         auto paa = JNIAudioAttributeHelper::makeUnique();
@@ -371,23 +373,24 @@
         status_t status = NO_ERROR;
         switch (memoryMode) {
         case MODE_STREAM:
-            status = lpTrack->set(
-                    AUDIO_STREAM_DEFAULT,// stream type, but more info conveyed in paa (last argument)
-                    sampleRateInHertz,
-                    format,// word length, PCM
-                    nativeChannelMask,
-                    offload ? 0 : frameCount,
-                    offload ? AUDIO_OUTPUT_FLAG_COMPRESS_OFFLOAD : AUDIO_OUTPUT_FLAG_NONE,
-                    audioCallback, &(lpJniStorage->mCallbackData),//callback, callback data (user)
-                    0,// notificationFrames == 0 since not using EVENT_MORE_DATA to feed the AudioTrack
-                    0,// shared mem
-                    true,// thread can call Java
-                    sessionId,// audio session ID
-                    offload ? AudioTrack::TRANSFER_SYNC_NOTIF_CALLBACK : AudioTrack::TRANSFER_SYNC,
-                    offload ? &offloadInfo : NULL,
-                    -1, -1,                       // default uid, pid values
-                    paa.get());
-
+            status = lpTrack->set(AUDIO_STREAM_DEFAULT, // stream type, but more info conveyed
+                                                        // in paa (last argument)
+                                  sampleRateInHertz,
+                                  format, // word length, PCM
+                                  nativeChannelMask, offload ? 0 : frameCount,
+                                  offload ? AUDIO_OUTPUT_FLAG_COMPRESS_OFFLOAD
+                                          : AUDIO_OUTPUT_FLAG_NONE,
+                                  audioCallback,
+                                  &(lpJniStorage->mCallbackData), // callback, callback data (user)
+                                  0,    // notificationFrames == 0 since not using EVENT_MORE_DATA
+                                        // to feed the AudioTrack
+                                  0,    // shared mem
+                                  true, // thread can call Java
+                                  sessionId, // audio session ID
+                                  offload ? AudioTrack::TRANSFER_SYNC_NOTIF_CALLBACK
+                                          : AudioTrack::TRANSFER_SYNC,
+                                  offload ? &offloadInfo : NULL, -1, -1, // default uid, pid values
+                                  paa.get());
             break;
 
         case MODE_STATIC:
@@ -398,22 +401,22 @@
                 goto native_init_failure;
             }
 
-            status = lpTrack->set(
-                    AUDIO_STREAM_DEFAULT,// stream type, but more info conveyed in paa (last argument)
-                    sampleRateInHertz,
-                    format,// word length, PCM
-                    nativeChannelMask,
-                    frameCount,
-                    AUDIO_OUTPUT_FLAG_NONE,
-                    audioCallback, &(lpJniStorage->mCallbackData),//callback, callback data (user));
-                    0,// notificationFrames == 0 since not using EVENT_MORE_DATA to feed the AudioTrack
-                    lpJniStorage->mMemBase,// shared mem
-                    true,// thread can call Java
-                    sessionId,// audio session ID
-                    AudioTrack::TRANSFER_SHARED,
-                    NULL,                         // default offloadInfo
-                    -1, -1,                       // default uid, pid values
-                    paa.get());
+            status = lpTrack->set(AUDIO_STREAM_DEFAULT, // stream type, but more info conveyed
+                                                        // in paa (last argument)
+                                  sampleRateInHertz,
+                                  format, // word length, PCM
+                                  nativeChannelMask, frameCount, AUDIO_OUTPUT_FLAG_NONE,
+                                  audioCallback,
+                                  &(lpJniStorage->mCallbackData), // callback, callback data (user)
+                                  0, // notificationFrames == 0 since not using EVENT_MORE_DATA
+                                     // to feed the AudioTrack
+                                  lpJniStorage->mMemBase, // shared mem
+                                  true,                   // thread can call Java
+                                  sessionId,              // audio session ID
+                                  AudioTrack::TRANSFER_SHARED,
+                                  NULL,   // default offloadInfo
+                                  -1, -1, // default uid, pid values
+                                  paa.get());
             break;
 
         default:
@@ -1428,7 +1431,8 @@
         {"native_stop", "()V", (void *)android_media_AudioTrack_stop},
         {"native_pause", "()V", (void *)android_media_AudioTrack_pause},
         {"native_flush", "()V", (void *)android_media_AudioTrack_flush},
-        {"native_setup", "(Ljava/lang/Object;Ljava/lang/Object;[IIIIII[IJZILjava/lang/Object;)I",
+        {"native_setup",
+         "(Ljava/lang/Object;Ljava/lang/Object;[IIIIII[IJZILjava/lang/Object;Ljava/lang/String;)I",
          (void *)android_media_AudioTrack_setup},
         {"native_finalize", "()V", (void *)android_media_AudioTrack_finalize},
         {"native_release", "()V", (void *)android_media_AudioTrack_release},
diff --git a/core/jni/android_util_Process.cpp b/core/jni/android_util_Process.cpp
index 7c32ca6..6becb07 100644
--- a/core/jni/android_util_Process.cpp
+++ b/core/jni/android_util_Process.cpp
@@ -346,22 +346,6 @@
     }
 }
 
-void android_os_Process_enableFreezer(
-        JNIEnv *env, jobject clazz, jboolean enable)
-{
-    bool success = true;
-
-    if (enable) {
-        success = SetTaskProfiles(0, {"FreezerFrozen"}, true);
-    } else {
-        success = SetTaskProfiles(0, {"FreezerThawed"}, true);
-    }
-
-    if (!success) {
-        jniThrowException(env, "java/lang/RuntimeException", "Unknown error");
-    }
-}
-
 jint android_os_Process_getProcessGroup(JNIEnv* env, jobject clazz, jint pid)
 {
     SchedPolicy sp;
@@ -1360,7 +1344,6 @@
         {"sendSignal", "(II)V", (void*)android_os_Process_sendSignal},
         {"sendSignalQuiet", "(II)V", (void*)android_os_Process_sendSignalQuiet},
         {"setProcessFrozen", "(IIZ)V", (void*)android_os_Process_setProcessFrozen},
-        {"enableFreezer", "(Z)V", (void*)android_os_Process_enableFreezer},
         {"getFreeMemory", "()J", (void*)android_os_Process_getFreeMemory},
         {"getTotalMemory", "()J", (void*)android_os_Process_getTotalMemory},
         {"readProcLines", "(Ljava/lang/String;[Ljava/lang/String;[J)V",
diff --git a/core/proto/android/app/settings_enums.proto b/core/proto/android/app/settings_enums.proto
index b3b464d..9008112 100644
--- a/core/proto/android/app/settings_enums.proto
+++ b/core/proto/android/app/settings_enums.proto
@@ -2683,4 +2683,9 @@
     // CATEGORY: SETTINGS
     // OS: R
     MEDIA_CONTROLS_SETTINGS = 1845;
+
+    // OPEN: Settings > Network & internet > Adaptive connectivity
+    // CATEGORY: SETTINGS
+    // OS: R QPR
+    ADAPTIVE_CONNECTIVITY_CATEGORY = 1850;
 }
diff --git a/core/proto/android/providers/settings/secure.proto b/core/proto/android/providers/settings/secure.proto
index fe8a0f1..cfd428c 100644
--- a/core/proto/android/providers/settings/secure.proto
+++ b/core/proto/android/providers/settings/secure.proto
@@ -119,6 +119,14 @@
     }
     optional Assist assist = 7;
 
+    message AssistHandles {
+        option (android.msg_privacy).dest = DEST_EXPLICIT;
+
+        optional SettingProto learning_time_elapsed_millis = 1 [ (android.privacy).dest = DEST_AUTOMATIC ];
+        optional SettingProto learning_event_count = 2 [ (android.privacy).dest = DEST_AUTOMATIC ];
+    }
+    optional AssistHandles assist_handles = 86;
+
     message Autofill {
         option (android.msg_privacy).dest = DEST_EXPLICIT;
 
@@ -180,6 +188,7 @@
     optional SettingProto cmas_additional_broadcast_pkg = 14 [ (android.privacy).dest = DEST_AUTOMATIC ];
     repeated SettingProto completed_categories = 15;
     optional SettingProto connectivity_release_pending_intent_delay_ms = 16 [ (android.privacy).dest = DEST_AUTOMATIC ];
+    optional SettingProto adaptive_connectivity_enabled = 84 [ (android.privacy).dest = DEST_AUTOMATIC ];
 
     message Controls {
         option (android.msg_privacy).dest = DEST_EXPLICIT;
@@ -595,5 +604,5 @@
 
     // Please insert fields in alphabetical order and group them into messages
     // if possible (to avoid reaching the method limit).
-    // Next tag = 82;
+    // Next tag = 87;
 }
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 6d6d735..3f96e06 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -144,7 +144,7 @@
     <protected-broadcast android:name="android.bluetooth.adapter.action.CONNECTION_STATE_CHANGED" />
     <protected-broadcast android:name="android.bluetooth.device.action.UUID" />
     <protected-broadcast android:name="android.bluetooth.device.action.MAS_INSTANCE" />
-    <protected-broadcast android:name="android.bluetooth.action.ALIAS_CHANGED" />
+    <protected-broadcast android:name="android.bluetooth.device.action.ALIAS_CHANGED" />
     <protected-broadcast android:name="android.bluetooth.device.action.FOUND" />
     <protected-broadcast android:name="android.bluetooth.device.action.CLASS_CHANGED" />
     <protected-broadcast android:name="android.bluetooth.device.action.ACL_CONNECTED" />
@@ -5056,6 +5056,10 @@
     <permission android:name="android.permission.ACCESS_LOCUS_ID_USAGE_STATS"
                 android:protectionLevel="signature|appPredictor" />
 
+    <!-- @hide Allows an application to create/destroy input consumer. -->
+    <permission android:name="android.permission.INPUT_CONSUMER"
+                android:protectionLevel="signature" />
+
     <!-- Attribution for Country Detector. -->
     <attribution android:tag="CountryDetector" android:label="@string/country_detector"/>
     <!-- Attribution for Location service. -->
diff --git a/core/res/res/drawable-car-night/car_dialog_button_background.xml b/core/res/res/drawable-car-night/car_dialog_button_background.xml
deleted file mode 100644
index 138cb38..0000000
--- a/core/res/res/drawable-car-night/car_dialog_button_background.xml
+++ /dev/null
@@ -1,32 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-  ~ Copyright (C) 2020 The Android Open Source Project
-  ~
-  ~ Licensed under the Apache License, Version 2.0 (the "License");
-  ~ you may not use this file except in compliance with the License.
-  ~ You may obtain a copy of the License at
-  ~
-  ~      http://www.apache.org/licenses/LICENSE-2.0
-  ~
-  ~ Unless required by applicable law or agreed to in writing, software
-  ~ distributed under the License is distributed on an "AS IS" BASIS,
-  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  ~ See the License for the specific language governing permissions and
-  ~ limitations under the License.
-  -->
-<selector xmlns:android="http://schemas.android.com/apk/res/android">
-    <item android:state_focused="true">
-        <ripple android:color="#2371cd">
-            <item android:id="@android:id/mask">
-                <color android:color="@*android:color/car_white_1000"/>
-            </item>
-        </ripple>
-    </item>
-    <item>
-        <ripple android:color="?android:attr/colorControlHighlight">
-            <item android:id="@android:id/mask">
-                <color android:color="@*android:color/car_white_1000"/>
-            </item>
-        </ripple>
-    </item>
-</selector>
diff --git a/core/res/res/drawable-car/car_dialog_button_background.xml b/core/res/res/drawable-car/car_dialog_button_background.xml
index a7d40bcd..72e5af3 100644
--- a/core/res/res/drawable-car/car_dialog_button_background.xml
+++ b/core/res/res/drawable-car/car_dialog_button_background.xml
@@ -16,11 +16,18 @@
   -->
 <selector xmlns:android="http://schemas.android.com/apk/res/android">
     <item android:state_focused="true">
-        <ripple android:color="#4b9eff">
-            <item android:id="@android:id/mask">
-                <color android:color="@*android:color/car_white_1000"/>
+        <layer-list>
+            <item>
+                <shape android:shape="rectangle">
+                    <solid android:color="#3D94CBFF"/>
+                </shape>
             </item>
-        </ripple>
+            <item>
+                <shape android:shape="rectangle">
+                    <stroke android:width="8dp" android:color="#94CBFF"/>
+                </shape>
+            </item>
+        </layer-list>
     </item>
     <item>
         <ripple android:color="?android:attr/colorControlHighlight">
diff --git a/core/res/res/layout/notification_material_action_list.xml b/core/res/res/layout/notification_material_action_list.xml
index 3615b9e..df271f0 100644
--- a/core/res/res/layout/notification_material_action_list.xml
+++ b/core/res/res/layout/notification_material_action_list.xml
@@ -25,6 +25,7 @@
             android:id="@+id/actions_container_layout"
             android:layout_width="match_parent"
             android:layout_height="wrap_content"
+            android:gravity="end"
             android:orientation="horizontal"
             android:paddingEnd="@dimen/bubble_gone_padding_end"
             >
diff --git a/core/res/res/values-mcc260/config.xml b/core/res/res/values-mcc260/config.xml
new file mode 100644
index 0000000..79eefb7
--- /dev/null
+++ b/core/res/res/values-mcc260/config.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+** Copyright 2020, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<!-- These resources are around just to allow their values to be customized
+     for different hardware and product builds. -->
+<resources>
+  <!-- Set to false to disable emergency alert. -->
+  <bool name="config_cellBroadcastAppLinks">false</bool>
+</resources>
\ No newline at end of file
diff --git a/core/res/res/values-mcc262/config.xml b/core/res/res/values-mcc262/config.xml
new file mode 100644
index 0000000..79eefb7
--- /dev/null
+++ b/core/res/res/values-mcc262/config.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+** Copyright 2020, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<!-- These resources are around just to allow their values to be customized
+     for different hardware and product builds. -->
+<resources>
+  <!-- Set to false to disable emergency alert. -->
+  <bool name="config_cellBroadcastAppLinks">false</bool>
+</resources>
\ No newline at end of file
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index 8a4676d..2f1bcdc 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -1259,7 +1259,12 @@
         <!-- Can be combined with <var>text</var> and its variations to
              allow multiple lines of text in the field.  If this flag is not set,
              the text field will be constrained to a single line.  Corresponds to
-             {@link android.text.InputType#TYPE_TEXT_FLAG_MULTI_LINE}. -->
+             {@link android.text.InputType#TYPE_TEXT_FLAG_MULTI_LINE}.
+
+             Note: If this flag is not set and the text field doesn't have max length limit, the
+             framework automatically set maximum length of the characters to 5000 for the
+             performance reasons.
+             -->
         <flag name="textMultiLine" value="0x00020001" />
         <!-- Can be combined with <var>text</var> and its variations to
              indicate that though the regular text view should not be multiple
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 28e2eb0..fd3b8cb 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -712,10 +712,17 @@
           case, this can be disabled (set to false). -->
     <bool name="config_enableCarDockHomeLaunch">true</bool>
 
-    <!-- Control whether to force the display of System UI Bars at all times regardless of
-         System Ui Flags. This can be useful in the Automotive case if there's a requirement for
-         a UI element to be on screen at all times. -->
-    <bool name="config_forceShowSystemBars">false</bool>
+    <!-- Control whether to force apps to give up control over the display of system bars at all
+         times regardless of System Ui Flags.
+         In the Automotive case, this is helpful if there's a requirement for an UI element to be on
+         screen at all times. Setting this to true also gives System UI the ability to override the
+         visibility controls for the system through the usage of the
+         "SYSTEM_BAR_VISIBILITY_OVERRIDE" setting.
+         Ex: Only setting the config to true will force show system bars for the entire system.
+         Ex: Setting the config to true and the "SYSTEM_BAR_VISIBILITY_OVERRIDE" setting to
+         "immersive.status=apps" will force show navigation bar for all apps and force hide status
+         bar for all apps. -->
+    <bool name="config_remoteInsetsControllerControlsSystemBars">false</bool>
 
     <!-- HDMI behavior -->
 
@@ -2267,7 +2274,7 @@
 
     <!-- Amount of time in ms the user needs to press the relevant keys to trigger the
          screenshot chord -->
-    <integer name="config_screenshotChordKeyTimeout">500</integer>
+    <integer name="config_screenshotChordKeyTimeout">0</integer>
 
     <!-- Default width of a vertical scrollbar and height of a horizontal scrollbar.
          Takes effect only if the scrollbar drawables have no intrinsic size. -->
@@ -2297,7 +2304,7 @@
     </integer-array>
 
     <!-- Set to true to add links to Cell Broadcast app from Settings and MMS app. -->
-    <bool name="config_cellBroadcastAppLinks">false</bool>
+    <bool name="config_cellBroadcastAppLinks">true</bool>
 
     <!-- The default value if the SyncStorageEngine should sync automatically or not -->
     <bool name="config_syncstorageengine_masterSyncAutomatically">true</bool>
@@ -3507,6 +3514,7 @@
      mode -->
     <string-array translatable="false" name="config_priorityOnlyDndExemptPackages">
         <item>com.android.dialer</item>
+        <item>com.android.server.telecom</item>
         <item>com.android.systemui</item>
         <item>android</item>
     </string-array>
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index e2fbbf4..e00aff1 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -3024,6 +3024,8 @@
 
     <!-- @hide @TestApi -->
     <public type="bool" name="config_assistantOnTopOfDream" id="0x01110005" />
+    <!-- @hide @TestApi -->
+    <public type="bool" name="config_remoteInsetsControllerControlsSystemBars" id="0x01110006" />
   <!-- ===============================================================
        Resources added in version S of the platform
 
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 4b303a3..c1f3028 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -2122,7 +2122,7 @@
     <!-- On the unlock pattern screen, shown at the top of the unlock screen to tell the user what to do. Below this text is the place for theu ser to draw the pattern. -->
     <string name="lockscreen_pattern_instructions">Draw pattern to unlock</string>
     <!-- Button at the bottom of the unlock screen to make an emergency call or access other emergency assistance functions. -->
-    <string name="lockscreen_emergency_call">Emergency</string>
+    <string name="lockscreen_emergency_call">Emergency call</string>
     <!-- Button at the bottom of the unlock screen that lets the user return to a call -->
     <string name="lockscreen_return_to_call">Return to call</string>
     <!-- Shown to confirm that the user entered their lock pattern correctly. -->
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index c51dca1..ad3d43f 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -1671,7 +1671,7 @@
   <java-symbol type="bool" name="config_enableCarDockHomeLaunch" />
   <java-symbol type="bool" name="config_enableLockBeforeUnlockScreen" />
   <java-symbol type="bool" name="config_enableLockScreenRotation" />
-  <java-symbol type="bool" name="config_forceShowSystemBars" />
+  <java-symbol type="bool" name="config_remoteInsetsControllerControlsSystemBars" />
   <java-symbol type="bool" name="config_lidControlsScreenLock" />
   <java-symbol type="bool" name="config_lidControlsSleep" />
   <java-symbol type="bool" name="config_lockDayNightMode" />
diff --git a/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java b/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java
index f11adef..7d2e32a 100644
--- a/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java
+++ b/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java
@@ -191,7 +191,7 @@
         PersistableBundle persistableBundle = new PersistableBundle();
         persistableBundle.putInt("k", 4);
         FixedRotationAdjustments fixedRotationAdjustments = new FixedRotationAdjustments(
-                Surface.ROTATION_90, DisplayCutout.NO_CUTOUT);
+                Surface.ROTATION_90, 1920, 1080, DisplayCutout.NO_CUTOUT);
 
         LaunchActivityItem item = LaunchActivityItem.obtain(intent, ident, activityInfo,
                 config(), overrideConfig, compat, referrer, null /* voiceInteractor */,
@@ -351,7 +351,8 @@
         ClientTransaction transaction = ClientTransaction.obtain(new StubAppThread(),
                 null /* activityToken */);
         transaction.addCallback(FixedRotationAdjustmentsItem.obtain(new Binder(),
-                new FixedRotationAdjustments(Surface.ROTATION_270, DisplayCutout.NO_CUTOUT)));
+                new FixedRotationAdjustments(Surface.ROTATION_270, 1920, 1080,
+                        DisplayCutout.NO_CUTOUT)));
 
         writeAndPrepareForReading(transaction);
 
diff --git a/core/tests/coretests/src/android/content/ContextTest.java b/core/tests/coretests/src/android/content/ContextTest.java
index 777f4a3..de81ff4 100644
--- a/core/tests/coretests/src/android/content/ContextTest.java
+++ b/core/tests/coretests/src/android/content/ContextTest.java
@@ -19,6 +19,7 @@
 import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY;
 import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC;
 import static android.view.Display.DEFAULT_DISPLAY;
+import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
 
 import static com.google.common.truth.Truth.assertThat;
 
@@ -188,19 +189,38 @@
 
         assertFalse(wrapper.isUiContext());
 
-        wrapper = new ContextWrapper(new TestUiContext());
+        wrapper = new ContextWrapper(getUiContext());
 
         assertTrue(wrapper.isUiContext());
     }
 
-    private static class TestUiContext extends ContextWrapper {
-        TestUiContext() {
-            super(null /* base */);
-        }
+    @Test
+    public void testIsUiContext_UiContextDerivedContext() {
+        final Context uiContext = getUiContext();
+        Context context = uiContext.createAttributionContext(null /* attributionTag */);
 
-        @Override
-        public boolean isUiContext() {
-            return true;
-        }
+        assertTrue(context.isUiContext());
+
+        context = uiContext.createConfigurationContext(new Configuration());
+
+        assertTrue(context.isUiContext());
+    }
+
+    @Test
+    public void testIsUiContext_UiContextDerivedDisplayContext() {
+        final Context uiContext = getUiContext();
+        final Display secondaryDisplay =
+                getSecondaryDisplay(uiContext.getSystemService(DisplayManager.class));
+        final Context context = uiContext.createDisplayContext(secondaryDisplay);
+
+        assertFalse(context.isUiContext());
+    }
+
+    private Context getUiContext() {
+        final Context appContext = ApplicationProvider.getApplicationContext();
+        final DisplayManager displayManager = appContext.getSystemService(DisplayManager.class);
+        final Display display = displayManager.getDisplay(DEFAULT_DISPLAY);
+        return appContext.createDisplayContext(display)
+                .createWindowContext(TYPE_APPLICATION_OVERLAY, null /* options */);
     }
 }
diff --git a/core/tests/coretests/src/android/view/DisplayAdjustmentsTests.java b/core/tests/coretests/src/android/view/DisplayAdjustmentsTests.java
index 2fc42e9..3cf1722 100644
--- a/core/tests/coretests/src/android/view/DisplayAdjustmentsTests.java
+++ b/core/tests/coretests/src/android/view/DisplayAdjustmentsTests.java
@@ -77,8 +77,10 @@
         final int realRotation = Surface.ROTATION_0;
         final int fixedRotation = Surface.ROTATION_90;
 
-        mDisplayAdjustments.setFixedRotationAdjustments(
-                new FixedRotationAdjustments(fixedRotation, null /* cutout */));
+        final int appWidth = 1080;
+        final int appHeight = 1920;
+        mDisplayAdjustments.setFixedRotationAdjustments(new FixedRotationAdjustments(
+                fixedRotation, appWidth, appHeight, null /* cutout */));
 
         final int w = 1000;
         final int h = 2000;
@@ -95,13 +97,21 @@
         metrics.heightPixels = metrics.noncompatHeightPixels = h;
 
         final DisplayMetrics flippedMetrics = new DisplayMetrics();
-        flippedMetrics.xdpi = flippedMetrics.noncompatXdpi = h;
+        // The physical dpi should not be adjusted.
+        flippedMetrics.xdpi = flippedMetrics.noncompatXdpi = w;
         flippedMetrics.widthPixels = flippedMetrics.noncompatWidthPixels = h;
-        flippedMetrics.ydpi = flippedMetrics.noncompatYdpi = w;
+        flippedMetrics.ydpi = flippedMetrics.noncompatYdpi = h;
         flippedMetrics.heightPixels = flippedMetrics.noncompatHeightPixels = w;
 
         mDisplayAdjustments.adjustMetrics(metrics, realRotation);
 
         assertEquals(flippedMetrics, metrics);
+
+        mDisplayAdjustments.adjustGlobalAppMetrics(metrics);
+
+        assertEquals(appWidth, metrics.widthPixels);
+        assertEquals(appWidth, metrics.noncompatWidthPixels);
+        assertEquals(appHeight, metrics.heightPixels);
+        assertEquals(appHeight, metrics.noncompatHeightPixels);
     }
 }
diff --git a/core/tests/coretests/src/android/view/InsetsStateTest.java b/core/tests/coretests/src/android/view/InsetsStateTest.java
index c7d835c..576cd78 100644
--- a/core/tests/coretests/src/android/view/InsetsStateTest.java
+++ b/core/tests/coretests/src/android/view/InsetsStateTest.java
@@ -124,6 +124,24 @@
     }
 
     @Test
+    public void testCalculateInsets_extraNavRightClimateTop() throws Exception {
+        try (final InsetsModeSession session =
+                     new InsetsModeSession(ViewRootImpl.NEW_INSETS_MODE_FULL)) {
+            mState.getSource(ITYPE_CLIMATE_BAR).setFrame(new Rect(0, 0, 100, 100));
+            mState.getSource(ITYPE_CLIMATE_BAR).setVisible(true);
+            mState.getSource(ITYPE_EXTRA_NAVIGATION_BAR).setFrame(new Rect(80, 0, 100, 300));
+            mState.getSource(ITYPE_EXTRA_NAVIGATION_BAR).setVisible(true);
+            WindowInsets insets = mState.calculateInsets(new Rect(0, 0, 100, 300), null, false,
+                    false, DisplayCutout.NO_CUTOUT, 0, 0, 0, null);
+            // ITYPE_CLIMATE_BAR is a type of status bar and ITYPE_EXTRA_NAVIGATION_BAR is a type
+            // of navigation bar.
+            assertEquals(Insets.of(0, 100, 20, 0), insets.getSystemWindowInsets());
+            assertEquals(Insets.of(0, 100, 0, 0), insets.getInsets(Type.statusBars()));
+            assertEquals(Insets.of(0, 0, 20, 0), insets.getInsets(Type.navigationBars()));
+        }
+    }
+
+    @Test
     public void testCalculateInsets_imeIgnoredWithoutAdjustResize() {
         try (final InsetsModeSession session =
                      new InsetsModeSession(ViewRootImpl.NEW_INSETS_MODE_FULL)) {
@@ -331,6 +349,8 @@
     public void testGetDefaultVisibility() {
         assertTrue(InsetsState.getDefaultVisibility(ITYPE_STATUS_BAR));
         assertTrue(InsetsState.getDefaultVisibility(ITYPE_NAVIGATION_BAR));
+        assertTrue(InsetsState.getDefaultVisibility(ITYPE_CLIMATE_BAR));
+        assertTrue(InsetsState.getDefaultVisibility(ITYPE_EXTRA_NAVIGATION_BAR));
         assertTrue(InsetsState.getDefaultVisibility(ITYPE_CAPTION_BAR));
         assertFalse(InsetsState.getDefaultVisibility(ITYPE_IME));
     }
diff --git a/core/tests/coretests/src/android/view/ViewRootImplTest.java b/core/tests/coretests/src/android/view/ViewRootImplTest.java
index 5c16772..4cf6715 100644
--- a/core/tests/coretests/src/android/view/ViewRootImplTest.java
+++ b/core/tests/coretests/src/android/view/ViewRootImplTest.java
@@ -24,6 +24,7 @@
 import static android.view.View.SYSTEM_UI_FLAG_LOW_PROFILE;
 import static android.view.WindowInsetsController.BEHAVIOR_SHOW_BARS_BY_TOUCH;
 import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN;
+import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE;
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
 import static android.view.WindowManager.LayoutParams.TYPE_SYSTEM_ALERT;
 import static android.view.WindowManager.LayoutParams.TYPE_TOAST;
@@ -117,7 +118,15 @@
         final WindowManager.LayoutParams attrs = new WindowManager.LayoutParams(TYPE_APPLICATION);
         ViewRootImpl.adjustLayoutParamsForCompatibility(attrs);
 
-        // A window which fits system bars must fit IME, unless its type is toast or system alert.
+        assertEquals(Type.systemBars(), attrs.getFitInsetsTypes());
+    }
+
+    @Test
+    public void adjustLayoutParamsForCompatibility_fitSystemBarsAndIme() {
+        final WindowManager.LayoutParams attrs = new WindowManager.LayoutParams(TYPE_APPLICATION);
+        attrs.softInputMode |= SOFT_INPUT_ADJUST_RESIZE;
+        ViewRootImpl.adjustLayoutParamsForCompatibility(attrs);
+
         assertEquals(Type.systemBars() | Type.ime(), attrs.getFitInsetsTypes());
     }
 
diff --git a/core/tests/coretests/src/android/widget/EditorCursorDragTest.java b/core/tests/coretests/src/android/widget/EditorCursorDragTest.java
index df2946c..c37a34a 100644
--- a/core/tests/coretests/src/android/widget/EditorCursorDragTest.java
+++ b/core/tests/coretests/src/android/widget/EditorCursorDragTest.java
@@ -201,6 +201,38 @@
     }
 
     @Test
+    public void testCursorDrag_diagonal_thresholdConfig() throws Throwable {
+        TextView tv = mActivity.findViewById(R.id.textview);
+        Editor editor = tv.getEditorForTesting();
+
+        StringBuilder sb = new StringBuilder();
+        for (int i = 1; i <= 9; i++) {
+            sb.append("here is some text").append(i).append("\n");
+        }
+        sb.append(Strings.repeat("abcdefghij\n", 400)).append("Last");
+        String text = sb.toString();
+        onView(withId(R.id.textview)).perform(replaceText(text));
+
+        int index = text.indexOf("text9");
+        onView(withId(R.id.textview)).perform(clickOnTextAtIndex(index));
+        onView(withId(R.id.textview)).check(hasInsertionPointerAtIndex(index));
+
+        // Configure the drag direction threshold to require the drag to be exactly horizontal. With
+        // this set, a swipe that is slightly off horizontal should not trigger cursor drag.
+        editor.setCursorDragMinAngleFromVertical(90);
+        int startIdx = text.indexOf("5");
+        int endIdx = text.indexOf("here is some text3");
+        onView(withId(R.id.textview)).perform(dragOnText(startIdx, endIdx));
+        onView(withId(R.id.textview)).check(hasInsertionPointerAtIndex(index));
+
+        // Configure the drag direction threshold to require the drag to be 45 degrees or more from
+        // vertical. With this set, the same swipe gesture as above should now trigger cursor drag.
+        editor.setCursorDragMinAngleFromVertical(45);
+        onView(withId(R.id.textview)).perform(dragOnText(startIdx, endIdx));
+        onView(withId(R.id.textview)).check(hasInsertionPointerAtIndex(endIdx));
+    }
+
+    @Test
     public void testCursorDrag_vertical_whenTextViewContentsFitOnScreen() throws Throwable {
         String text = "012345_aaa\n"
                 + "0123456789\n"
diff --git a/core/tests/coretests/src/android/widget/EditorTouchStateTest.java b/core/tests/coretests/src/android/widget/EditorTouchStateTest.java
index 35fd4bd..94f43de 100644
--- a/core/tests/coretests/src/android/widget/EditorTouchStateTest.java
+++ b/core/tests/coretests/src/android/widget/EditorTouchStateTest.java
@@ -165,7 +165,7 @@
         long event2Time = 1001;
         MotionEvent event2 = moveEvent(event1Time, event2Time, 200f, 31f);
         mTouchState.update(event2, mConfig);
-        assertDrag(mTouchState, 20f, 30f, 0, 0, false);
+        assertDrag(mTouchState, 20f, 30f, 0, 0, 180f);
 
         // Simulate an ACTION_UP event with a delay that's longer than the double-tap timeout.
         long event3Time = 5000;
@@ -280,7 +280,7 @@
         long event3Time = 1002;
         MotionEvent event3 = moveEvent(event3Time, event3Time, newX, newY);
         mTouchState.update(event3, mConfig);
-        assertDrag(mTouchState, 20f, 30f, 0, 0, false);
+        assertDrag(mTouchState, 20f, 30f, 0, 0, Float.MAX_VALUE);
 
         // Simulate an ACTION_UP event.
         long event4Time = 1003;
@@ -301,15 +301,15 @@
         long event2Time = 1002;
         MotionEvent event2 = moveEvent(event1Time, event2Time, 100f, 174f);
         mTouchState.update(event2, mConfig);
-        assertDrag(mTouchState, 0f, 0f, 0, 0, true);
+        assertDrag(mTouchState, 0f, 0f, 0, 0, 100f / 174f);
 
         // Simulate another ACTION_MOVE event that is horizontal from the original down event.
-        // The value of `isDragCloseToVertical` should NOT change since it should only reflect the
-        // initial direction of movement.
+        // The drag direction ratio should NOT change since it should only reflect the initial
+        // direction of movement.
         long event3Time = 1003;
         MotionEvent event3 = moveEvent(event1Time, event3Time, 200f, 0f);
         mTouchState.update(event3, mConfig);
-        assertDrag(mTouchState, 0f, 0f, 0, 0, true);
+        assertDrag(mTouchState, 0f, 0f, 0, 0, 100f / 174f);
 
         // Simulate an ACTION_UP event.
         long event4Time = 1004;
@@ -330,15 +330,15 @@
         long event2Time = 1002;
         MotionEvent event2 = moveEvent(event1Time, event2Time, 100f, 90f);
         mTouchState.update(event2, mConfig);
-        assertDrag(mTouchState, 0f, 0f, 0, 0, false);
+        assertDrag(mTouchState, 0f, 0f, 0, 0, 100f / 90f);
 
         // Simulate another ACTION_MOVE event that is vertical from the original down event.
-        // The value of `isDragCloseToVertical` should NOT change since it should only reflect the
-        // initial direction of movement.
+        // The drag direction ratio should NOT change since it should only reflect the initial
+        // direction of movement.
         long event3Time = 1003;
         MotionEvent event3 = moveEvent(event1Time, event3Time, 0f, 200f);
         mTouchState.update(event3, mConfig);
-        assertDrag(mTouchState, 0f, 0f, 0, 0, false);
+        assertDrag(mTouchState, 0f, 0f, 0, 0, 100f / 90f);
 
         // Simulate an ACTION_UP event.
         long event4Time = 1004;
@@ -374,7 +374,7 @@
         long event2Time = 1002;
         MotionEvent event2 = moveEvent(event2Time, event2Time, 200f, 30f);
         mTouchState.update(event2, mConfig);
-        assertDrag(mTouchState, 20f, 30f, 0, 0, false);
+        assertDrag(mTouchState, 20f, 30f, 0, 0, Float.MAX_VALUE);
 
         // Simulate an ACTION_CANCEL event.
         long event3Time = 1003;
@@ -411,6 +411,84 @@
         assertSingleTap(mTouchState, 22f, 33f, 20f, 30f);
     }
 
+    @Test
+    public void testGetXYRatio() throws Exception {
+        doTestGetXYRatio(-1, 0.0f);
+        doTestGetXYRatio(0, 0.0f);
+        doTestGetXYRatio(30, 0.58f);
+        doTestGetXYRatio(45, 1.0f);
+        doTestGetXYRatio(60, 1.73f);
+        doTestGetXYRatio(90, Float.MAX_VALUE);
+        doTestGetXYRatio(91, Float.MAX_VALUE);
+    }
+
+    private void doTestGetXYRatio(int angleFromVerticalInDegrees, float expectedXYRatioRounded) {
+        float result = EditorTouchState.getXYRatio(angleFromVerticalInDegrees);
+        String msg = String.format(
+                "%d deg should give an x/y ratio of %f; actual unrounded result is %f",
+                angleFromVerticalInDegrees, expectedXYRatioRounded, result);
+        float roundedResult = (result == 0.0f || result == Float.MAX_VALUE) ? result :
+                Math.round(result * 100) / 100f;
+        assertThat(msg, roundedResult, is(expectedXYRatioRounded));
+    }
+
+    @Test
+    public void testUpdate_dragDirection() throws Exception {
+        // Simulate moving straight up.
+        doTestDragDirection(100f, 100f, 100f, 50f, 0f);
+
+        // Simulate moving straight down.
+        doTestDragDirection(100f, 100f, 100f, 150f, 0f);
+
+        // Simulate moving straight left.
+        doTestDragDirection(100f, 100f, 50f, 100f, Float.MAX_VALUE);
+
+        // Simulate moving straight right.
+        doTestDragDirection(100f, 100f, 150f, 100f, Float.MAX_VALUE);
+
+        // Simulate moving up and right, < 45 deg from vertical.
+        doTestDragDirection(100f, 100f, 110f, 50f, 10f / 50f);
+
+        // Simulate moving up and right, > 45 deg from vertical.
+        doTestDragDirection(100f, 100f, 150f, 90f, 50f / 10f);
+
+        // Simulate moving down and right, < 45 deg from vertical.
+        doTestDragDirection(100f, 100f, 110f, 150f, 10f / 50f);
+
+        // Simulate moving down and right, > 45 deg from vertical.
+        doTestDragDirection(100f, 100f, 150f, 110f, 50f / 10f);
+
+        // Simulate moving down and left, < 45 deg from vertical.
+        doTestDragDirection(100f, 100f, 90f, 150f, 10f / 50f);
+
+        // Simulate moving down and left, > 45 deg from vertical.
+        doTestDragDirection(100f, 100f, 50f, 110f, 50f / 10f);
+
+        // Simulate moving up and left, < 45 deg from vertical.
+        doTestDragDirection(100f, 100f, 90f, 50f, 10f / 50f);
+
+        // Simulate moving up and left, > 45 deg from vertical.
+        doTestDragDirection(100f, 100f, 50f, 90f, 50f / 10f);
+    }
+
+    private void doTestDragDirection(float downX, float downY, float moveX, float moveY,
+            float expectedInitialDragDirectionXYRatio) {
+        EditorTouchState touchState = new EditorTouchState();
+
+        // Simulate an ACTION_DOWN event.
+        long event1Time = 1001;
+        MotionEvent event1 = downEvent(event1Time, event1Time, downX, downY);
+        touchState.update(event1, mConfig);
+
+        // Simulate an ACTION_MOVE event.
+        long event2Time = 1002;
+        MotionEvent event2 = moveEvent(event1Time, event2Time, moveX, moveY);
+        touchState.update(event2, mConfig);
+        String msg = String.format("(%.0f,%.0f)=>(%.0f,%.0f)", downX, downY, moveX, moveY);
+        assertThat(msg, touchState.getInitialDragDirectionXYRatio(),
+                is(expectedInitialDragDirectionXYRatio));
+    }
+
     private static MotionEvent downEvent(long downTime, long eventTime, float x, float y) {
         return MotionEvent.obtain(downTime, eventTime, MotionEvent.ACTION_DOWN, x, y, 0);
     }
@@ -441,7 +519,7 @@
     }
 
     private static void assertDrag(EditorTouchState touchState, float lastDownX,
-            float lastDownY, float lastUpX, float lastUpY, boolean isDragCloseToVertical) {
+            float lastDownY, float lastUpX, float lastUpY, float initialDragDirectionXYRatio) {
         assertThat(touchState.getLastDownX(), is(lastDownX));
         assertThat(touchState.getLastDownY(), is(lastDownY));
         assertThat(touchState.getLastUpX(), is(lastUpX));
@@ -451,7 +529,7 @@
         assertThat(touchState.isMultiTap(), is(false));
         assertThat(touchState.isMultiTapInSameArea(), is(false));
         assertThat(touchState.isMovedEnoughForDrag(), is(true));
-        assertThat(touchState.isDragCloseToVertical(), is(isDragCloseToVertical));
+        assertThat(touchState.getInitialDragDirectionXYRatio(), is(initialDragDirectionXYRatio));
     }
 
     private static void assertMultiTap(EditorTouchState touchState,
@@ -467,6 +545,5 @@
                 || multiTapStatus == MultiTapStatus.TRIPLE_CLICK));
         assertThat(touchState.isMultiTapInSameArea(), is(isMultiTapInSameArea));
         assertThat(touchState.isMovedEnoughForDrag(), is(false));
-        assertThat(touchState.isDragCloseToVertical(), is(false));
     }
 }
diff --git a/data/etc/car/com.google.android.car.kitchensink.xml b/data/etc/car/com.google.android.car.kitchensink.xml
index 7292e07..f21e9bf 100644
--- a/data/etc/car/com.google.android.car.kitchensink.xml
+++ b/data/etc/car/com.google.android.car.kitchensink.xml
@@ -43,5 +43,8 @@
         <!-- use for CarServiceTest -->
         <permission name="android.permission.SET_ACTIVITY_WATCHER"/>
         <permission name="android.permission.WRITE_SECURE_SETTINGS"/>
+
+        <!-- use for rotary fragment to enable/disable packages related to rotary -->
+        <permission name="android.permission.CHANGE_COMPONENT_ENABLED_STATE"/>
     </privapp-permissions>
 </permissions>
diff --git a/data/etc/com.android.systemui.xml b/data/etc/com.android.systemui.xml
index a5a2221..06f1dae 100644
--- a/data/etc/com.android.systemui.xml
+++ b/data/etc/com.android.systemui.xml
@@ -35,10 +35,12 @@
         <permission name="android.permission.MANAGE_USERS"/>
         <permission name="android.permission.MASTER_CLEAR"/>
         <permission name="android.permission.MEDIA_CONTENT_CONTROL"/>
+        <permission name="android.permission.MODIFY_AUDIO_ROUTING" />
         <permission name="android.permission.MODIFY_DAY_NIGHT_MODE"/>
         <permission name="android.permission.MODIFY_PHONE_STATE"/>
         <permission name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS"/>
         <permission name="android.permission.OBSERVE_NETWORK_POLICY"/>
+        <permission name="android.permission.OBSERVE_GRANT_REVOKE_PERMISSIONS" />
         <permission name="android.permission.OVERRIDE_WIFI_CONFIG"/>
         <permission name="android.permission.PACKAGE_USAGE_STATS" />
         <permission name="android.permission.READ_DREAM_STATE"/>
diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml
index 0806e71..a0ef47e 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -143,6 +143,10 @@
         <permission name="android.permission.SUBSTITUTE_NOTIFICATION_APP_NAME" />
         <permission name="android.permission.PACKAGE_USAGE_STATS" />
         <permission name="android.permission.CHANGE_COMPONENT_ENABLED_STATE" />
+        <permission name="android.permission.MODIFY_AUDIO_ROUTING" />
+
+        <!-- For permission hub 2 debugging only -->
+        <permission name="android.permission.GET_ACCOUNTS_PRIVILEGED"/>
     </privapp-permissions>
 
     <privapp-permissions package="com.android.phone">
diff --git a/diff b/diff
new file mode 100644
index 0000000..5c75d88
--- /dev/null
+++ b/diff
@@ -0,0 +1,25 @@
+diff --git a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
+index fc43882..832dc91 100644
+--- a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
++++ b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
+@@ -67,6 +67,7 @@ import java.util.Arrays;
+ import java.util.HashSet;
+ import java.util.List;
+ import java.util.Set;
++import java.util.NoSuchElementException;
+ 
+ /**
+  * This class represents an accessibility client - either an AccessibilityService or a UiAutomation.
+@@ -978,7 +979,11 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ
+                 /* ignore */
+         }
+         if (mService != null) {
+-            mService.unlinkToDeath(this, 0);
++            try {
++                mService.unlinkToDeath(this, 0);
++            }catch(NoSuchElementException e) {
++                Slog.e(LOG_TAG, "Failed unregistering death link");
++            }
+             mService = null;
+         }
+ 
diff --git a/drm/jni/Android.bp b/drm/jni/Android.bp
index 1e33f0e..68757d8 100644
--- a/drm/jni/Android.bp
+++ b/drm/jni/Android.bp
@@ -21,6 +21,7 @@
 
     shared_libs: [
         "libdrmframework",
+        "libdrmframeworkcommon",
         "liblog",
         "libutils",
         "libandroid_runtime",
diff --git a/libs/WindowManager/Jetpack/Android.bp b/libs/WindowManager/Jetpack/Android.bp
index 4f4364f..7fbbb61 100644
--- a/libs/WindowManager/Jetpack/Android.bp
+++ b/libs/WindowManager/Jetpack/Android.bp
@@ -24,14 +24,14 @@
     static_libs: ["window-sidecar"],
     installable: true,
     sdk_version: "core_platform",
-    vendor: true,
+    system_ext_specific: true,
     libs: ["framework", "androidx.annotation_annotation",],
     required: ["androidx.window.sidecar.xml",],
 }
 
 prebuilt_etc {
     name: "androidx.window.sidecar.xml",
-    vendor: true,
+    system_ext_specific: true,
     sub_dir: "permissions",
     src: "androidx.window.sidecar.xml",
     filename_from_src: true,
diff --git a/libs/WindowManager/Jetpack/androidx.window.sidecar.xml b/libs/WindowManager/Jetpack/androidx.window.sidecar.xml
index f88a5f4..359e69f 100644
--- a/libs/WindowManager/Jetpack/androidx.window.sidecar.xml
+++ b/libs/WindowManager/Jetpack/androidx.window.sidecar.xml
@@ -17,5 +17,5 @@
 <permissions>
     <library
         name="androidx.window.sidecar"
-        file="/vendor/framework/androidx.window.sidecar.jar"/>
+        file="/system_ext/framework/androidx.window.sidecar.jar"/>
 </permissions>
diff --git a/location/java/android/location/ILocationManager.aidl b/location/java/android/location/ILocationManager.aidl
index 75ea0cb..7ed5b57 100644
--- a/location/java/android/location/ILocationManager.aidl
+++ b/location/java/android/location/ILocationManager.aidl
@@ -45,9 +45,9 @@
 interface ILocationManager
 {
     Location getLastLocation(in LocationRequest request, String packageName, String featureId);
-    boolean getCurrentLocation(in LocationRequest request,
-            in ICancellationSignal cancellationSignal, in ILocationListener listener,
-            String packageName, String featureId, String listenerId);
+    @nullable ICancellationSignal getCurrentLocation(in LocationRequest request,
+            in ILocationListener listener, String packageName, String featureId,
+            String listenerId);
 
     void requestLocationUpdates(in LocationRequest request, in ILocationListener listener,
             in PendingIntent intent, String packageName, String featureId, String listenerId);
diff --git a/location/java/android/location/LocationManager.java b/location/java/android/location/LocationManager.java
index 0c7d96d..33c6951 100644
--- a/location/java/android/location/LocationManager.java
+++ b/location/java/android/location/LocationManager.java
@@ -724,16 +724,15 @@
             cancellationSignal.throwIfCanceled();
         }
 
-        ICancellationSignal remoteCancellationSignal = CancellationSignal.createTransport();
-
         try {
-            if (mService.getCurrentLocation(currentLocationRequest, remoteCancellationSignal,
-                    transport, mContext.getPackageName(), mContext.getAttributionTag(),
-                    transport.getListenerId())) {
+            ICancellationSignal cancelRemote = mService.getCurrentLocation(
+                    currentLocationRequest, transport, mContext.getPackageName(),
+                    mContext.getAttributionTag(), transport.getListenerId());
+            if (cancelRemote != null) {
                 transport.register(mContext.getSystemService(AlarmManager.class),
-                        remoteCancellationSignal);
+                        cancellationSignal, cancelRemote);
                 if (cancellationSignal != null) {
-                    cancellationSignal.setOnCancelListener(transport::cancel);
+                    cancellationSignal.setRemote(cancelRemote);
                 }
             } else {
                 transport.fail();
@@ -2535,7 +2534,7 @@
     }
 
     private static class GetCurrentLocationTransport extends ILocationListener.Stub implements
-            AlarmManager.OnAlarmListener {
+            AlarmManager.OnAlarmListener, CancellationSignal.OnCancelListener {
 
         @GuardedBy("this")
         @Nullable
@@ -2567,6 +2566,7 @@
         }
 
         public synchronized void register(AlarmManager alarmManager,
+                CancellationSignal cancellationSignal,
                 ICancellationSignal remoteCancellationSignal) {
             if (mConsumer == null) {
                 return;
@@ -2580,10 +2580,18 @@
                     this,
                     null);
 
+            if (cancellationSignal != null) {
+                cancellationSignal.setOnCancelListener(this);
+            }
+
             mRemoteCancellationSignal = remoteCancellationSignal;
         }
 
-        public void cancel() {
+        @Override
+        public void onCancel() {
+            synchronized (this) {
+                mRemoteCancellationSignal = null;
+            }
             remove();
         }
 
@@ -2632,11 +2640,6 @@
 
         @Override
         public void onLocationChanged(Location location) {
-            synchronized (this) {
-                // save ourselves a pointless x-process call to cancel the location request
-                mRemoteCancellationSignal = null;
-            }
-
             deliverResult(location);
         }
 
@@ -3034,7 +3037,7 @@
         protected GnssRequest merge(@NonNull List<GnssRequest> requests) {
             Preconditions.checkArgument(!requests.isEmpty());
             for (GnssRequest request : requests) {
-                if (request.isFullTracking()) {
+                if (request != null && request.isFullTracking()) {
                     return request;
                 }
             }
diff --git a/media/java/android/media/AudioTrack.java b/media/java/android/media/AudioTrack.java
index eff56f3..fa6a4ff 100644
--- a/media/java/android/media/AudioTrack.java
+++ b/media/java/android/media/AudioTrack.java
@@ -807,7 +807,8 @@
         int initResult = native_setup(new WeakReference<AudioTrack>(this), mAttributes,
                 sampleRate, mChannelMask, mChannelIndexMask, mAudioFormat,
                 mNativeBufferSizeInBytes, mDataLoadMode, session, 0 /*nativeTrackInJavaObj*/,
-                offload, encapsulationMode, tunerConfiguration);
+                offload, encapsulationMode, tunerConfiguration,
+                getCurrentOpPackageName());
         if (initResult != SUCCESS) {
             loge("Error code "+initResult+" when initializing AudioTrack.");
             return; // with mState == STATE_UNINITIALIZED
@@ -893,7 +894,8 @@
                     nativeTrackInJavaObj,
                     false /*offload*/,
                     ENCAPSULATION_MODE_NONE,
-                    null /* tunerConfiguration */);
+                    null /* tunerConfiguration */,
+                    "" /* opPackagename */);
             if (initResult != SUCCESS) {
                 loge("Error code "+initResult+" when initializing AudioTrack.");
                 return; // with mState == STATE_UNINITIALIZED
@@ -4068,7 +4070,8 @@
             Object /*AudioAttributes*/ attributes,
             int[] sampleRate, int channelMask, int channelIndexMask, int audioFormat,
             int buffSizeInBytes, int mode, int[] sessionId, long nativeAudioTrack,
-            boolean offload, int encapsulationMode, Object tunerConfiguration);
+            boolean offload, int encapsulationMode, Object tunerConfiguration,
+            @NonNull String opPackageName);
 
     private native final void native_finalize();
 
diff --git a/media/java/android/media/ExifInterface.java b/media/java/android/media/ExifInterface.java
index 5fce5ee..3293c40 100644
--- a/media/java/android/media/ExifInterface.java
+++ b/media/java/android/media/ExifInterface.java
@@ -70,6 +70,7 @@
 import java.util.Map;
 import java.util.Set;
 import java.util.TimeZone;
+import java.util.UUID;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 import java.util.zip.CRC32;
@@ -2095,7 +2096,10 @@
         try {
             // Move the original file to temporary file.
             if (mFilename != null) {
-                tempFile = new File(mFilename + ".tmp");
+                String parent = originalFile.getParent();
+                String name = originalFile.getName();
+                String tempPrefix = UUID.randomUUID().toString() + "_";
+                tempFile = new File(parent, tempPrefix + name);
                 if (!originalFile.renameTo(tempFile)) {
                     throw new IOException("Couldn't rename to " + tempFile.getAbsolutePath());
                 }
diff --git a/media/java/android/media/MediaPlayer.java b/media/java/android/media/MediaPlayer.java
index 49e4160..36ae3ec 100644
--- a/media/java/android/media/MediaPlayer.java
+++ b/media/java/android/media/MediaPlayer.java
@@ -672,7 +672,8 @@
         /* Native setup requires a weak reference to our object.
          * It's easier to create it here than in C++.
          */
-        native_setup(new WeakReference<MediaPlayer>(this));
+        native_setup(new WeakReference<MediaPlayer>(this),
+                getCurrentOpPackageName());
 
         baseRegisterPlayer();
     }
@@ -2378,7 +2379,7 @@
     private native final int native_setMetadataFilter(Parcel request);
 
     private static native final void native_init();
-    private native final void native_setup(Object mediaplayer_this);
+    private native void native_setup(Object mediaplayerThis, @NonNull String opPackageName);
     private native final void native_finalize();
 
     /**
diff --git a/media/java/android/media/PlayerBase.java b/media/java/android/media/PlayerBase.java
index ee8f1b3..df5e85e 100644
--- a/media/java/android/media/PlayerBase.java
+++ b/media/java/android/media/PlayerBase.java
@@ -27,6 +27,7 @@
 import android.os.Process;
 import android.os.RemoteException;
 import android.os.ServiceManager;
+import android.text.TextUtils;
 import android.util.Log;
 
 import com.android.internal.annotations.GuardedBy;
@@ -622,4 +623,8 @@
         Log.w(className, "See the documentation of " + opName + " for what to use instead with " +
                 "android.media.AudioAttributes to qualify your playback use case");
     }
+
+    protected String getCurrentOpPackageName() {
+        return TextUtils.emptyIfNull(ActivityThread.currentOpPackageName());
+    }
 }
diff --git a/media/java/android/media/browse/MediaBrowser.java b/media/java/android/media/browse/MediaBrowser.java
index 3d27e4b..84ec93d 100644
--- a/media/java/android/media/browse/MediaBrowser.java
+++ b/media/java/android/media/browse/MediaBrowser.java
@@ -185,7 +185,7 @@
                 boolean bound = false;
                 try {
                     bound = mContext.bindService(intent, mServiceConnection,
-                            Context.BIND_AUTO_CREATE);
+                            Context.BIND_AUTO_CREATE | Context.BIND_INCLUDE_CAPABILITIES);
                 } catch (Exception ex) {
                     Log.e(TAG, "Failed binding to service " + mServiceComponent);
                 }
diff --git a/media/jni/android_media_MediaPlayer.cpp b/media/jni/android_media_MediaPlayer.cpp
index 82b746f..d63b1ad 100644
--- a/media/jni/android_media_MediaPlayer.cpp
+++ b/media/jni/android_media_MediaPlayer.cpp
@@ -33,6 +33,7 @@
 #include <utils/threads.h>
 #include "jni.h"
 #include <nativehelper/JNIPlatformHelp.h>
+#include <nativehelper/ScopedUtfChars.h>
 #include "android_runtime/AndroidRuntime.h"
 #include "android_runtime/android_view_Surface.h"
 #include "android_runtime/Log.h"
@@ -944,10 +945,12 @@
 }
 
 static void
-android_media_MediaPlayer_native_setup(JNIEnv *env, jobject thiz, jobject weak_this)
+android_media_MediaPlayer_native_setup(JNIEnv *env, jobject thiz, jobject weak_this,
+                                       jstring opPackageName)
 {
     ALOGV("native_setup");
-    sp<MediaPlayer> mp = new MediaPlayer();
+    ScopedUtfChars opPackageNameStr(env, opPackageName);
+    sp<MediaPlayer> mp = new MediaPlayer(opPackageNameStr.c_str());
     if (mp == NULL) {
         jniThrowException(env, "java/lang/RuntimeException", "Out of memory");
         return;
@@ -1403,7 +1406,7 @@
     {"native_setMetadataFilter", "(Landroid/os/Parcel;)I",      (void *)android_media_MediaPlayer_setMetadataFilter},
     {"native_getMetadata", "(ZZLandroid/os/Parcel;)Z",          (void *)android_media_MediaPlayer_getMetadata},
     {"native_init",         "()V",                              (void *)android_media_MediaPlayer_native_init},
-    {"native_setup",        "(Ljava/lang/Object;)V",            (void *)android_media_MediaPlayer_native_setup},
+    {"native_setup",        "(Ljava/lang/Object;Ljava/lang/String;)V",(void *)android_media_MediaPlayer_native_setup},
     {"native_finalize",     "()V",                              (void *)android_media_MediaPlayer_native_finalize},
     {"getAudioSessionId",   "()I",                              (void *)android_media_MediaPlayer_get_audio_session_id},
     {"setAudioSessionId",   "(I)V",                             (void *)android_media_MediaPlayer_set_audio_session_id},
diff --git a/non-updatable-api/current.txt b/non-updatable-api/current.txt
index da3d0f7..8cc4790 100644
--- a/non-updatable-api/current.txt
+++ b/non-updatable-api/current.txt
@@ -45119,7 +45119,7 @@
     method public long getNci();
     method @IntRange(from=0, to=3279165) public int getNrarfcn();
     method @IntRange(from=0, to=1007) public int getPci();
-    method @IntRange(from=0, to=16777215) public int getTac();
+    method @IntRange(from=0, to=65535) public int getTac();
     method public void writeToParcel(android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.telephony.CellIdentityNr> CREATOR;
   }
diff --git a/packages/CarSystemUI/TEST_MAPPING b/packages/CarSystemUI/TEST_MAPPING
index 6056ddf..c18a12a 100644
--- a/packages/CarSystemUI/TEST_MAPPING
+++ b/packages/CarSystemUI/TEST_MAPPING
@@ -8,5 +8,16 @@
         }
       ]
     }
+  ],
+
+  "carsysui-presubmit": [
+    {
+      "name": "CarSystemUITests",
+      "options" : [
+        {
+          "include-annotation": "com.android.systemui.car.CarSystemUiTest"
+        }
+      ]
+    }
   ]
 }
diff --git a/packages/CarSystemUI/res-keyguard/layout/keyguard_password_view.xml b/packages/CarSystemUI/res-keyguard/layout/keyguard_password_view.xml
index 8b235e6..d57875f 100644
--- a/packages/CarSystemUI/res-keyguard/layout/keyguard_password_view.xml
+++ b/packages/CarSystemUI/res-keyguard/layout/keyguard_password_view.xml
@@ -53,7 +53,7 @@
             android:textColor="?attr/wallpaperTextColor"
             android:textAppearance="?android:attr/textAppearanceMedium"
             android:imeOptions="flagForceAscii|actionDone"
-            android:maxLength="@integer/password_text_view_scale"
+            android:maxLength="@integer/password_max_length"
          />
 
         <TextView
diff --git a/packages/CarSystemUI/res/layout/car_user_switching_dialog.xml b/packages/CarSystemUI/res/layout/car_user_switching_dialog.xml
index 0a29424..09fbf7a 100644
--- a/packages/CarSystemUI/res/layout/car_user_switching_dialog.xml
+++ b/packages/CarSystemUI/res/layout/car_user_switching_dialog.xml
@@ -15,7 +15,6 @@
   ~ limitations under the License.
   -->
 <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
-    android:fitsSystemWindows="true"
     android:layout_width="wrap_content"
     android:layout_height="wrap_content"
     android:gravity="center"
diff --git a/packages/CarSystemUI/res/layout/headsup_container_bottom.xml b/packages/CarSystemUI/res/layout/headsup_container_bottom.xml
index 1782d25..5aab0a1 100644
--- a/packages/CarSystemUI/res/layout/headsup_container_bottom.xml
+++ b/packages/CarSystemUI/res/layout/headsup_container_bottom.xml
@@ -29,13 +29,12 @@
         android:orientation="horizontal"
         app:layout_constraintGuide_begin="@dimen/headsup_scrim_height"/>
 
-    <!-- Include a FocusParkingView at the beginning or end. The rotary controller "parks" the
-         focus here when the user navigates to another window. This is also used to prevent
-         wrap-around which is why it must be first or last in Tab order. -->
+    <!-- Include a FocusParkingView at the beginning. The rotary controller "parks" the focus here
+         when the user navigates to another window. This is also used to prevent wrap-around. -->
     <com.android.car.ui.FocusParkingView
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
-        app:layout_constraintLeft_toLeftOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
         app:layout_constraintTop_toTopOf="parent"/>
 
     <View
diff --git a/packages/CarSystemUI/res/layout/super_notification_shade.xml b/packages/CarSystemUI/res/layout/super_notification_shade.xml
deleted file mode 100644
index db71c91..0000000
--- a/packages/CarSystemUI/res/layout/super_notification_shade.xml
+++ /dev/null
@@ -1,80 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-**
-** Copyright 2020, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-**     http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
-*/
--->
-
-<!-- This is the notification shade window. -->
-<com.android.systemui.statusbar.phone.NotificationShadeWindowView
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:sysui="http://schemas.android.com/apk/res-auto"
-    android:layout_width="match_parent"
-    android:layout_height="match_parent"
-    android:fitsSystemWindows="true">
-
-    <com.android.systemui.statusbar.BackDropView
-        android:id="@+id/backdrop"
-        android:layout_width="match_parent"
-        android:layout_height="match_parent"
-        android:visibility="gone"
-        sysui:ignoreRightInset="true"
-    >
-        <ImageView android:id="@+id/backdrop_back"
-            android:layout_width="match_parent"
-            android:layout_height="match_parent"
-            android:scaleType="centerCrop"/>
-        <ImageView android:id="@+id/backdrop_front"
-            android:layout_width="match_parent"
-            android:layout_height="match_parent"
-            android:scaleType="centerCrop"
-            android:visibility="invisible"/>
-    </com.android.systemui.statusbar.BackDropView>
-
-    <com.android.systemui.statusbar.ScrimView
-        android:id="@+id/scrim_behind"
-        android:layout_width="match_parent"
-        android:layout_height="match_parent"
-        android:importantForAccessibility="no"
-        sysui:ignoreRightInset="true"
-    />
-
-    <include layout="@layout/brightness_mirror"/>
-
-    <ViewStub android:id="@+id/fullscreen_user_switcher_stub"
-        android:layout_width="match_parent"
-        android:layout_height="match_parent"
-        android:layout="@layout/car_fullscreen_user_switcher"/>
-
-    <include layout="@layout/notification_center_activity"
-        android:layout_width="match_parent"
-        android:layout_height="match_parent"
-        android:layout_marginBottom="@dimen/navigation_bar_height"
-        android:visibility="invisible"/>
-
-    <include layout="@layout/status_bar_expanded"
-        android:layout_width="match_parent"
-        android:layout_height="match_parent"
-        android:visibility="invisible"/>
-
-    <com.android.systemui.statusbar.ScrimView
-        android:id="@+id/scrim_in_front"
-        android:layout_width="match_parent"
-        android:layout_height="match_parent"
-        android:importantForAccessibility="no"
-        sysui:ignoreRightInset="true"
-    />
-
-</com.android.systemui.statusbar.phone.NotificationShadeWindowView>
diff --git a/packages/CarSystemUI/res/layout/super_status_bar.xml b/packages/CarSystemUI/res/layout/super_status_bar.xml
deleted file mode 100644
index d93f62f..0000000
--- a/packages/CarSystemUI/res/layout/super_status_bar.xml
+++ /dev/null
@@ -1,46 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-**
-** Copyright 2020, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-**     http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
-*/
--->
-
-<!-- This is the status bar window. -->
-<com.android.systemui.statusbar.phone.StatusBarWindowView
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:sysui="http://schemas.android.com/apk/res-auto"
-    android:layout_width="match_parent"
-    android:layout_height="match_parent"
-    android:fitsSystemWindows="true">
-
-    <LinearLayout
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:orientation="vertical"
-    >
-        <FrameLayout
-            android:id="@+id/status_bar_container"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:visibility="gone"
-        />
-
-        <FrameLayout
-            android:id="@+id/car_top_navigation_bar_container"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"/>
-    </LinearLayout>
-
-</com.android.systemui.statusbar.phone.StatusBarWindowView>
diff --git a/packages/CarSystemUI/res/layout/sysui_overlay_window.xml b/packages/CarSystemUI/res/layout/sysui_overlay_window.xml
index 2dc499c..e7295aa 100644
--- a/packages/CarSystemUI/res/layout/sysui_overlay_window.xml
+++ b/packages/CarSystemUI/res/layout/sysui_overlay_window.xml
@@ -22,12 +22,11 @@
     android:layout_width="match_parent"
     android:layout_height="match_parent">
 
-    <!-- TODO(b/151617493): replace marginBottom with insets. -->
     <ViewStub android:id="@+id/notification_panel_stub"
               android:layout_width="match_parent"
               android:layout_height="match_parent"
               android:layout="@layout/notification_panel_container"
-              android:layout_marginBottom="@dimen/navigation_bar_height"/>
+              android:layout_marginBottom="@dimen/car_bottom_navigation_bar_height"/>
 
     <ViewStub android:id="@+id/keyguard_stub"
               android:layout_width="match_parent"
diff --git a/packages/CarSystemUI/res/values/config.xml b/packages/CarSystemUI/res/values/config.xml
index cf967c0..24157f9 100644
--- a/packages/CarSystemUI/res/values/config.xml
+++ b/packages/CarSystemUI/res/values/config.xml
@@ -24,12 +24,45 @@
 
     <bool name="config_enableFullscreenUserSwitcher">true</bool>
 
-    <!-- configure which system ui bars should be displayed -->
+    <!-- Configure which system bars should be displayed. -->
     <bool name="config_enableTopNavigationBar">true</bool>
     <bool name="config_enableLeftNavigationBar">false</bool>
     <bool name="config_enableRightNavigationBar">false</bool>
     <bool name="config_enableBottomNavigationBar">true</bool>
 
+    <!-- Configure the type of each system bar. Each system bar must have a unique type. -->
+    <!--    STATUS_BAR = 0-->
+    <!--    NAVIGATION_BAR = 1-->
+    <!--    STATUS_BAR_EXTRA = 2-->
+    <!--    NAVIGATION_BAR_EXTRA = 3-->
+    <integer name="config_topSystemBarType">0</integer>
+    <integer name="config_leftSystemBarType">2</integer>
+    <integer name="config_rightSystemBarType">3</integer>
+    <integer name="config_bottomSystemBarType">1</integer>
+
+    <!-- Configure the relative z-order among the system bars. When two system bars overlap (e.g.
+         if both top bar and left bar are enabled, it creates an overlapping space in the upper left
+         corner), the system bar with the higher z-order takes the overlapping space and padding is
+         applied to the other bar.-->
+    <!-- NOTE: If two overlapping system bars have the same z-order, SystemBarConfigs will throw a
+         RuntimeException, since their placing order cannot be determined. Bars that do not overlap
+         are allowed to have the same z-order. -->
+    <!-- NOTE: If the z-order of a bar is 10 or above, it will also appear on top of HUN's.    -->
+    <integer name="config_topSystemBarZOrder">1</integer>
+    <integer name="config_leftSystemBarZOrder">0</integer>
+    <integer name="config_rightSystemBarZOrder">0</integer>
+    <integer name="config_bottomSystemBarZOrder">10</integer>
+
+    <!-- If set to true, the corresponding system bar will be hidden when Keyboard (IME) appears.
+         NOTE: hideBottomSystemBarKeyboard must not be overlaid directly here. To change its value,
+         overlay config_automotiveHideNavBarForKeyboard in framework/base/core/res/res. -->
+    <bool name="config_hideTopSystemBarForKeyboard">false</bool>
+    <bool name="config_hideBottomSystemBarForKeyboard">
+        @*android:bool/config_automotiveHideNavBarForKeyboard
+    </bool>
+    <bool name="config_hideLeftSystemBarForKeyboard">false</bool>
+    <bool name="config_hideRightSystemBarForKeyboard">false</bool>
+
     <!-- Disable normal notification rendering; we handle that ourselves -->
     <bool name="config_renderNotifications">false</bool>
 
diff --git a/packages/CarSystemUI/res/values/dimens.xml b/packages/CarSystemUI/res/values/dimens.xml
index cb321cd..e8ac9e1 100644
--- a/packages/CarSystemUI/res/values/dimens.xml
+++ b/packages/CarSystemUI/res/values/dimens.xml
@@ -81,6 +81,21 @@
     <dimen name="car_keyline_2">96dp</dimen>
     <dimen name="car_keyline_3">128dp</dimen>
 
+    <!-- Height of icons in Ongoing App Ops dialog. Both App Op icon and application icon -->
+    <dimen name="ongoing_appops_dialog_icon_height">48dp</dimen>
+    <!-- Margin between text lines in Ongoing App Ops dialog -->
+    <dimen name="ongoing_appops_dialog_text_margin">15dp</dimen>
+    <!-- Padding around Ongoing App Ops dialog content -->
+    <dimen name="ongoing_appops_dialog_content_padding">24dp</dimen>
+    <!-- Margins around the Ongoing App Ops chip. In landscape, the side margins are 0 -->
+    <dimen name="ongoing_appops_chip_margin">12dp</dimen>
+    <!-- Start and End padding for Ongoing App Ops chip -->
+    <dimen name="ongoing_appops_chip_side_padding">6dp</dimen>
+    <!-- Padding between background of Ongoing App Ops chip and content -->
+    <dimen name="ongoing_appops_chip_bg_padding">4dp</dimen>
+    <!-- Radius of Ongoing App Ops chip corners -->
+    <dimen name="ongoing_appops_chip_bg_corner_radius">12dp</dimen>
+
     <!-- Car volume dimens. -->
     <dimen name="car_volume_item_icon_size">@dimen/car_primary_icon_size</dimen>
     <dimen name="car_volume_item_height">@*android:dimen/car_single_line_list_item_height</dimen>
@@ -179,6 +194,12 @@
     <dimen name="car_navigation_bar_width">760dp</dimen>
     <dimen name="car_left_navigation_bar_width">96dp</dimen>
     <dimen name="car_right_navigation_bar_width">96dp</dimen>
+    <!-- In order to change the height of the bottom nav bar, overlay navigation_bar_height in
+         frameworks/base/core/res/res instead. -->
+    <dimen name="car_bottom_navigation_bar_height">@*android:dimen/navigation_bar_height</dimen>
+    <!-- In order to change the height of the top nav bar, overlay status_bar_height in
+         frameworks/base/core/res/res instead. -->
+    <dimen name="car_top_navigation_bar_height">@*android:dimen/status_bar_height</dimen>
 
     <dimen name="car_user_switcher_container_height">420dp</dimen>
     <!-- This must be the negative of car_user_switcher_container_height for the animation. -->
diff --git a/packages/CarSystemUI/src/com/android/systemui/CarSystemUIBinder.java b/packages/CarSystemUI/src/com/android/systemui/CarSystemUIBinder.java
index 34afb132..b3102e24 100644
--- a/packages/CarSystemUI/src/com/android/systemui/CarSystemUIBinder.java
+++ b/packages/CarSystemUI/src/com/android/systemui/CarSystemUIBinder.java
@@ -21,8 +21,8 @@
 import com.android.systemui.car.navigationbar.CarNavigationBar;
 import com.android.systemui.car.notification.CarNotificationModule;
 import com.android.systemui.car.sideloaded.SideLoadedAppController;
-import com.android.systemui.car.statusbar.CarStatusBar;
-import com.android.systemui.car.statusbar.CarStatusBarModule;
+import com.android.systemui.car.statusbar.UnusedStatusBar;
+import com.android.systemui.car.statusbar.UnusedStatusBarModule;
 import com.android.systemui.car.voicerecognition.ConnectedDeviceVoiceRecognitionNotifier;
 import com.android.systemui.car.volume.VolumeUI;
 import com.android.systemui.car.window.OverlayWindowModule;
@@ -39,7 +39,6 @@
 import com.android.systemui.statusbar.notification.InstantAppNotifier;
 import com.android.systemui.statusbar.notification.dagger.NotificationsModule;
 import com.android.systemui.statusbar.phone.StatusBar;
-import com.android.systemui.statusbar.tv.TvStatusBar;
 import com.android.systemui.theme.ThemeOverlayController;
 import com.android.systemui.toast.ToastUI;
 import com.android.systemui.util.leak.GarbageMonitor;
@@ -50,9 +49,9 @@
 import dagger.multibindings.IntoMap;
 
 /** Binder for car specific {@link SystemUI} modules. */
-@Module(includes = {RecentsModule.class, CarStatusBarModule.class, NotificationsModule.class,
+@Module(includes = {RecentsModule.class, NotificationsModule.class,
         BubbleModule.class, KeyguardModule.class, OverlayWindowModule.class,
-        CarNotificationModule.class})
+        CarNotificationModule.class, UnusedStatusBarModule.class})
 public abstract class CarSystemUIBinder {
     /** Inject into AuthController. */
     @Binds
@@ -155,19 +154,7 @@
     @Binds
     @IntoMap
     @ClassKey(StatusBar.class)
-    public abstract SystemUI bindsStatusBar(CarStatusBar sysui);
-
-    /** Inject into TvStatusBar. */
-    @Binds
-    @IntoMap
-    @ClassKey(TvStatusBar.class)
-    public abstract SystemUI bindsTvStatusBar(TvStatusBar sysui);
-
-    /** Inject into StatusBarGoogle. */
-    @Binds
-    @IntoMap
-    @ClassKey(CarStatusBar.class)
-    public abstract SystemUI bindsCarStatusBar(CarStatusBar sysui);
+    public abstract SystemUI bindsStatusBar(UnusedStatusBar sysui);
 
     /** Inject into VolumeUI. */
     @Binds
diff --git a/packages/CarSystemUI/src/com/android/systemui/CarSystemUIModule.java b/packages/CarSystemUI/src/com/android/systemui/CarSystemUIModule.java
index 5bf989a9..0e3c7f3 100644
--- a/packages/CarSystemUI/src/com/android/systemui/CarSystemUIModule.java
+++ b/packages/CarSystemUI/src/com/android/systemui/CarSystemUIModule.java
@@ -28,10 +28,9 @@
 import com.android.systemui.car.CarDeviceProvisionedController;
 import com.android.systemui.car.CarDeviceProvisionedControllerImpl;
 import com.android.systemui.car.keyguard.CarKeyguardViewController;
-import com.android.systemui.car.statusbar.CarStatusBar;
-import com.android.systemui.car.statusbar.CarStatusBarKeyguardViewManager;
 import com.android.systemui.car.statusbar.DozeServiceHost;
 import com.android.systemui.car.statusbar.DummyNotificationShadeWindowController;
+import com.android.systemui.car.statusbar.UnusedStatusBar;
 import com.android.systemui.car.volume.CarVolumeDialogComponent;
 import com.android.systemui.dagger.SystemUIRootComponent;
 import com.android.systemui.dagger.qualifiers.Background;
@@ -60,13 +59,14 @@
 import com.android.systemui.statusbar.phone.ShadeController;
 import com.android.systemui.statusbar.phone.ShadeControllerImpl;
 import com.android.systemui.statusbar.phone.StatusBar;
-import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
 import com.android.systemui.statusbar.policy.BatteryController;
 import com.android.systemui.statusbar.policy.BatteryControllerImpl;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.statusbar.policy.DeviceProvisionedController;
 import com.android.systemui.statusbar.policy.HeadsUpManager;
 import com.android.systemui.volume.VolumeDialogComponent;
+import com.android.systemui.wm.DisplayImeController;
+import com.android.systemui.wm.DisplaySystemBarsController;
 
 import javax.inject.Named;
 import javax.inject.Singleton;
@@ -97,6 +97,10 @@
                 groupManager, configurationController);
     }
 
+    @Binds
+    abstract DisplayImeController bindDisplayImeController(
+            DisplaySystemBarsController displaySystemBarsController);
+
     @Singleton
     @Provides
     @Named(LEAK_REPORT_EMAIL_NAME)
@@ -152,17 +156,10 @@
             CarSystemUIRootComponent systemUIRootComponent);
 
     @Binds
-    public abstract StatusBar bindStatusBar(CarStatusBar statusBar);
-
-    @Binds
     abstract VolumeDialogComponent bindVolumeDialogComponent(
             CarVolumeDialogComponent carVolumeDialogComponent);
 
     @Binds
-    abstract StatusBarKeyguardViewManager bindStatusBarKeyguardViewManager(
-            CarStatusBarKeyguardViewManager keyguardViewManager);
-
-    @Binds
     abstract KeyguardViewController bindKeyguardViewController(
             CarKeyguardViewController carKeyguardViewController);
 
@@ -180,4 +177,7 @@
 
     @Binds
     abstract DozeHost bindDozeHost(DozeServiceHost dozeServiceHost);
+
+    @Binds
+    abstract StatusBar bindStatusBar(UnusedStatusBar statusBar);
 }
diff --git a/packages/CarSystemUI/src/com/android/systemui/car/CarSystemUiTest.java b/packages/CarSystemUI/src/com/android/systemui/car/CarSystemUiTest.java
new file mode 100644
index 0000000..5f593b0
--- /dev/null
+++ b/packages/CarSystemUI/src/com/android/systemui/car/CarSystemUiTest.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.car;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Annotates that a test class should be run as part of CarSystemUI presubmit
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.TYPE)
+@Documented
+public @interface CarSystemUiTest {
+}
diff --git a/packages/CarSystemUI/src/com/android/systemui/car/keyguard/CarKeyguardViewController.java b/packages/CarSystemUI/src/com/android/systemui/car/keyguard/CarKeyguardViewController.java
index 69766cc..ec018f9 100644
--- a/packages/CarSystemUI/src/com/android/systemui/car/keyguard/CarKeyguardViewController.java
+++ b/packages/CarSystemUI/src/com/android/systemui/car/keyguard/CarKeyguardViewController.java
@@ -21,10 +21,13 @@
 import android.content.Context;
 import android.os.Bundle;
 import android.os.Handler;
+import android.os.UserHandle;
 import android.util.Log;
 import android.view.View;
 import android.view.ViewGroup;
 import android.view.ViewRootImpl;
+import android.view.inputmethod.InputMethodManager;
+import android.widget.EditText;
 
 import androidx.annotation.VisibleForTesting;
 
@@ -61,7 +64,7 @@
 public class CarKeyguardViewController extends OverlayViewController implements
         KeyguardViewController {
     private static final String TAG = "CarKeyguardViewController";
-    private static final boolean DEBUG = true;
+    private static final boolean DEBUG = false;
 
     private final Context mContext;
     private final Handler mHandler;
@@ -75,9 +78,10 @@
     private final DismissCallbackRegistry mDismissCallbackRegistry;
     private final ViewMediatorCallback mViewMediatorCallback;
     private final CarNavigationBarController mCarNavigationBarController;
+    private final InputMethodManager mInputMethodManager;
     // Needed to instantiate mBouncer.
-    private final KeyguardBouncer.BouncerExpansionCallback
-            mExpansionCallback = new KeyguardBouncer.BouncerExpansionCallback() {
+    private final KeyguardBouncer.BouncerExpansionCallback mExpansionCallback =
+            new KeyguardBouncer.BouncerExpansionCallback() {
                 @Override
                 public void onFullyShown() {
                 }
@@ -96,7 +100,8 @@
             };
     private final CarUserManager.UserLifecycleListener mUserLifecycleListener = (e) -> {
         if (e.getEventType() == CarUserManager.USER_LIFECYCLE_EVENT_TYPE_SWITCHING) {
-            revealKeyguardIfBouncerPrepared();
+            UserHandle currentUser = e.getUserHandle();
+            revealKeyguardIfBouncerPrepared(currentUser);
         }
     };
 
@@ -136,11 +141,18 @@
         mDismissCallbackRegistry = dismissCallbackRegistry;
         mViewMediatorCallback = viewMediatorCallback;
         mCarNavigationBarController = carNavigationBarController;
+        // TODO(b/169280588): Inject InputMethodManager instead.
+        mInputMethodManager = mContext.getSystemService(InputMethodManager.class);
 
         registerUserSwitchedListener();
     }
 
     @Override
+    protected boolean shouldShowNavigationBarInsets() {
+        return true;
+    }
+
+    @Override
     public void onFinishInflate() {
         mBouncer = SystemUIFactory.getInstance().createKeyguardBouncer(mContext,
                 mViewMediatorCallback, mLockPatternUtils,
@@ -172,7 +184,6 @@
         mKeyguardStateController.notifyKeyguardState(mShowing, /* occluded= */ false);
         mCarNavigationBarController.showAllKeyguardButtons(/* isSetUp= */ true);
         start();
-        getOverlayViewGlobalStateController().setWindowFocusable(/* focusable= */ true);
         reset(/* hideBouncerWhenShowing= */ false);
         notifyKeyguardUpdateMonitor();
     }
@@ -187,7 +198,6 @@
         mBouncer.hide(/* destroyView= */ true);
         mCarNavigationBarController.hideAllKeyguardButtons(/* isSetUp= */ true);
         stop();
-        getOverlayViewGlobalStateController().setWindowFocusable(/* focusable= */ false);
         mKeyguardStateController.notifyKeyguardDoneFading();
         mHandler.post(mViewMediatorCallback::keyguardGone);
         notifyKeyguardUpdateMonitor();
@@ -232,7 +242,6 @@
     public void onCancelClicked() {
         if (mBouncer == null) return;
 
-        getOverlayViewGlobalStateController().setWindowFocusable(/* focusable= */ false);
         getOverlayViewGlobalStateController().setWindowNeedsInput(/* needsInput= */ false);
 
         mBouncer.hide(/* destroyView= */ true);
@@ -361,9 +370,9 @@
     }
 
     /**
-     *  Hides Keyguard so that the transitioning Bouncer can be hidden until it is prepared. To be
-     *  called by {@link com.android.systemui.car.userswitcher.FullscreenUserSwitcherViewMediator}
-     *  when a new user is selected.
+     * Hides Keyguard so that the transitioning Bouncer can be hidden until it is prepared. To be
+     * called by {@link com.android.systemui.car.userswitcher.FullscreenUserSwitcherViewMediator}
+     * when a new user is selected.
      */
     public void hideKeyguardToPrepareBouncer() {
         getLayout().setVisibility(View.INVISIBLE);
@@ -374,29 +383,41 @@
         mBouncer = keyguardBouncer;
     }
 
-    private void revealKeyguardIfBouncerPrepared() {
+    private void revealKeyguardIfBouncerPrepared(UserHandle currentUser) {
         int reattemptDelayMillis = 50;
         Runnable revealKeyguard = () -> {
             if (mBouncer == null) {
                 if (DEBUG) {
                     Log.d(TAG, "revealKeyguardIfBouncerPrepared: revealKeyguard request is ignored "
-                                    + "since the Bouncer has not been initialized yet.");
+                            + "since the Bouncer has not been initialized yet.");
                 }
                 return;
             }
             if (!mBouncer.inTransit() || !mBouncer.isSecure()) {
                 getLayout().setVisibility(View.VISIBLE);
+                updateCurrentUserForPasswordEntry(currentUser);
             } else {
                 if (DEBUG) {
                     Log.d(TAG, "revealKeyguardIfBouncerPrepared: Bouncer is not prepared "
                             + "yet so reattempting after " + reattemptDelayMillis + "ms.");
                 }
-                mHandler.postDelayed(this::revealKeyguardIfBouncerPrepared, reattemptDelayMillis);
+                mHandler.postDelayed(() -> revealKeyguardIfBouncerPrepared(currentUser),
+                        reattemptDelayMillis);
             }
         };
         mHandler.post(revealKeyguard);
     }
 
+    private void updateCurrentUserForPasswordEntry(UserHandle currentUser) {
+        EditText passwordEntry = getLayout().findViewById(R.id.passwordEntry);
+        if (passwordEntry != null) {
+            mHandler.post(() -> {
+                mInputMethodManager.restartInput(passwordEntry);
+                passwordEntry.setTextOperationUser(currentUser);
+            });
+        }
+    }
+
     private void notifyKeyguardUpdateMonitor() {
         mKeyguardUpdateMonitor.onKeyguardVisibilityChanged(mShowing);
         if (mBouncer != null) {
diff --git a/packages/CarSystemUI/src/com/android/systemui/car/navigationbar/CarNavigationBar.java b/packages/CarSystemUI/src/com/android/systemui/car/navigationbar/CarNavigationBar.java
index 37dfce4..b6d251f 100644
--- a/packages/CarSystemUI/src/com/android/systemui/car/navigationbar/CarNavigationBar.java
+++ b/packages/CarSystemUI/src/com/android/systemui/car/navigationbar/CarNavigationBar.java
@@ -16,8 +16,6 @@
 
 package com.android.systemui.car.navigationbar;
 
-import static android.view.InsetsState.ITYPE_CLIMATE_BAR;
-import static android.view.InsetsState.ITYPE_EXTRA_NAVIGATION_BAR;
 import static android.view.InsetsState.ITYPE_NAVIGATION_BAR;
 import static android.view.InsetsState.ITYPE_STATUS_BAR;
 import static android.view.InsetsState.containsType;
@@ -28,13 +26,11 @@
 
 import android.content.Context;
 import android.content.res.Resources;
-import android.graphics.PixelFormat;
 import android.inputmethodservice.InputMethodService;
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.view.Display;
-import android.view.Gravity;
 import android.view.View;
 import android.view.ViewGroup;
 import android.view.WindowInsetsController;
@@ -45,7 +41,6 @@
 import com.android.internal.statusbar.IStatusBarService;
 import com.android.internal.statusbar.RegisterStatusBarResult;
 import com.android.internal.view.AppearanceRegion;
-import com.android.systemui.R;
 import com.android.systemui.SystemUI;
 import com.android.systemui.car.CarDeviceProvisionedController;
 import com.android.systemui.car.CarDeviceProvisionedListener;
@@ -74,7 +69,6 @@
 
 /** Navigation bars customized for the automotive use case. */
 public class CarNavigationBar extends SystemUI implements CommandQueue.Callbacks {
-
     private final Resources mResources;
     private final CarNavigationBarController mCarNavigationBarController;
     private final SysuiDarkIconDispatcher mStatusBarIconController;
@@ -91,12 +85,17 @@
     private final Lazy<StatusBarIconController> mIconControllerLazy;
 
     private final int mDisplayId;
+    private final SystemBarConfigs mSystemBarConfigs;
 
     private StatusBarSignalPolicy mSignalPolicy;
     private ActivityManagerWrapper mActivityManagerWrapper;
 
     // If the nav bar should be hidden when the soft keyboard is visible.
-    private boolean mHideNavBarForKeyboard;
+    private boolean mHideTopBarForKeyboard;
+    private boolean mHideLeftBarForKeyboard;
+    private boolean mHideRightBarForKeyboard;
+    private boolean mHideBottomBarForKeyboard;
+
     private boolean mBottomNavBarVisible;
 
     // Nav bar views.
@@ -139,7 +138,8 @@
             IStatusBarService barService,
             Lazy<KeyguardStateController> keyguardStateControllerLazy,
             Lazy<PhoneStatusBarPolicy> iconPolicyLazy,
-            Lazy<StatusBarIconController> iconControllerLazy
+            Lazy<StatusBarIconController> iconControllerLazy,
+            SystemBarConfigs systemBarConfigs
     ) {
         super(context);
         mResources = resources;
@@ -156,6 +156,7 @@
         mKeyguardStateControllerLazy = keyguardStateControllerLazy;
         mIconPolicyLazy = iconPolicyLazy;
         mIconControllerLazy = iconControllerLazy;
+        mSystemBarConfigs = systemBarConfigs;
 
         mDisplayId = context.getDisplayId();
     }
@@ -163,8 +164,13 @@
     @Override
     public void start() {
         // Set initial state.
-        mHideNavBarForKeyboard = mResources.getBoolean(
-                com.android.internal.R.bool.config_automotiveHideNavBarForKeyboard);
+        mHideTopBarForKeyboard = mSystemBarConfigs.getHideForKeyboardBySide(SystemBarConfigs.TOP);
+        mHideBottomBarForKeyboard = mSystemBarConfigs.getHideForKeyboardBySide(
+                SystemBarConfigs.BOTTOM);
+        mHideLeftBarForKeyboard = mSystemBarConfigs.getHideForKeyboardBySide(SystemBarConfigs.LEFT);
+        mHideRightBarForKeyboard = mSystemBarConfigs.getHideForKeyboardBySide(
+                SystemBarConfigs.RIGHT);
+
         mBottomNavBarVisible = false;
 
         // Connect into the status bar manager service
@@ -342,100 +348,63 @@
     private void buildNavBarContent() {
         mTopNavigationBarView = mCarNavigationBarController.getTopBar(isDeviceSetupForUser());
         if (mTopNavigationBarView != null) {
+            mSystemBarConfigs.insetSystemBar(SystemBarConfigs.TOP, mTopNavigationBarView);
             mTopNavigationBarWindow.addView(mTopNavigationBarView);
         }
 
         mBottomNavigationBarView = mCarNavigationBarController.getBottomBar(isDeviceSetupForUser());
         if (mBottomNavigationBarView != null) {
+            mSystemBarConfigs.insetSystemBar(SystemBarConfigs.BOTTOM, mBottomNavigationBarView);
             mBottomNavigationBarWindow.addView(mBottomNavigationBarView);
         }
 
         mLeftNavigationBarView = mCarNavigationBarController.getLeftBar(isDeviceSetupForUser());
         if (mLeftNavigationBarView != null) {
+            mSystemBarConfigs.insetSystemBar(SystemBarConfigs.LEFT, mLeftNavigationBarView);
             mLeftNavigationBarWindow.addView(mLeftNavigationBarView);
         }
 
         mRightNavigationBarView = mCarNavigationBarController.getRightBar(isDeviceSetupForUser());
         if (mRightNavigationBarView != null) {
+            mSystemBarConfigs.insetSystemBar(SystemBarConfigs.RIGHT, mRightNavigationBarView);
             mRightNavigationBarWindow.addView(mRightNavigationBarView);
         }
     }
 
     private void attachNavBarWindows() {
-        if (mTopNavigationBarWindow != null) {
-            int height = mResources.getDimensionPixelSize(
-                    com.android.internal.R.dimen.status_bar_height);
-            WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
-                    ViewGroup.LayoutParams.MATCH_PARENT,
-                    height,
-                    WindowManager.LayoutParams.TYPE_STATUS_BAR,
-                    WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
-                            | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
-                            | WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH
-                            | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH,
-                    PixelFormat.TRANSLUCENT);
-            lp.setTitle("TopCarNavigationBar");
-            lp.windowAnimations = 0;
-            lp.gravity = Gravity.TOP;
-            mWindowManager.addView(mTopNavigationBarWindow, lp);
-        }
+        mSystemBarConfigs.getSystemBarSidesByZOrder().forEach(this::attachNavBarBySide);
+    }
 
-        if (mBottomNavigationBarWindow != null && !mBottomNavBarVisible) {
-            mBottomNavBarVisible = true;
-            int height = mResources.getDimensionPixelSize(
-                    com.android.internal.R.dimen.navigation_bar_height);
+    private void attachNavBarBySide(int side) {
+        switch(side) {
+            case SystemBarConfigs.TOP:
+                if (mTopNavigationBarWindow != null) {
+                    mWindowManager.addView(mTopNavigationBarWindow,
+                            mSystemBarConfigs.getLayoutParamsBySide(SystemBarConfigs.TOP));
+                }
+                break;
+            case SystemBarConfigs.BOTTOM:
+                if (mBottomNavigationBarWindow != null && !mBottomNavBarVisible) {
+                    mBottomNavBarVisible = true;
 
-            WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
-                    ViewGroup.LayoutParams.MATCH_PARENT,
-                    height,
-                    WindowManager.LayoutParams.TYPE_NAVIGATION_BAR,
-                    WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
-                            | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
-                            | WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH
-                            | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH,
-                    PixelFormat.TRANSLUCENT);
-            lp.setTitle("BottomCarNavigationBar");
-            lp.windowAnimations = 0;
-            lp.gravity = Gravity.BOTTOM;
-            mWindowManager.addView(mBottomNavigationBarWindow, lp);
-        }
-
-        if (mLeftNavigationBarWindow != null) {
-            int width = mResources.getDimensionPixelSize(
-                    R.dimen.car_left_navigation_bar_width);
-            WindowManager.LayoutParams leftlp = new WindowManager.LayoutParams(
-                    width, ViewGroup.LayoutParams.MATCH_PARENT,
-                    WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL,
-                    WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
-                            | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
-                            | WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH
-                            | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH,
-                    PixelFormat.TRANSLUCENT);
-            leftlp.setTitle("LeftCarNavigationBar");
-            leftlp.providesInsetsTypes = new int[]{ITYPE_CLIMATE_BAR};
-            leftlp.setFitInsetsTypes(0);
-            leftlp.windowAnimations = 0;
-            leftlp.gravity = Gravity.LEFT;
-            mWindowManager.addView(mLeftNavigationBarWindow, leftlp);
-        }
-
-        if (mRightNavigationBarWindow != null) {
-            int width = mResources.getDimensionPixelSize(
-                    R.dimen.car_right_navigation_bar_width);
-            WindowManager.LayoutParams rightlp = new WindowManager.LayoutParams(
-                    width, ViewGroup.LayoutParams.MATCH_PARENT,
-                    WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL,
-                    WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
-                            | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
-                            | WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH
-                            | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH,
-                    PixelFormat.TRANSLUCENT);
-            rightlp.setTitle("RightCarNavigationBar");
-            rightlp.providesInsetsTypes = new int[]{ITYPE_EXTRA_NAVIGATION_BAR};
-            rightlp.setFitInsetsTypes(0);
-            rightlp.windowAnimations = 0;
-            rightlp.gravity = Gravity.RIGHT;
-            mWindowManager.addView(mRightNavigationBarWindow, rightlp);
+                    mWindowManager.addView(mBottomNavigationBarWindow,
+                            mSystemBarConfigs.getLayoutParamsBySide(SystemBarConfigs.BOTTOM));
+                }
+                break;
+            case SystemBarConfigs.LEFT:
+                if (mLeftNavigationBarWindow != null) {
+                    mWindowManager.addView(mLeftNavigationBarWindow,
+                            mSystemBarConfigs.getLayoutParamsBySide(SystemBarConfigs.LEFT));
+                }
+                break;
+            case SystemBarConfigs.RIGHT:
+                if (mRightNavigationBarWindow != null) {
+                    mWindowManager.addView(mRightNavigationBarWindow,
+                            mSystemBarConfigs.getLayoutParamsBySide(SystemBarConfigs.RIGHT));
+                }
+                break;
+            default:
+                return;
         }
     }
 
@@ -447,17 +416,30 @@
     @Override
     public void setImeWindowStatus(int displayId, IBinder token, int vis, int backDisposition,
             boolean showImeSwitcher) {
-        if (!mHideNavBarForKeyboard) {
-            return;
-        }
-
         if (mContext.getDisplayId() != displayId) {
             return;
         }
 
         boolean isKeyboardVisible = (vis & InputMethodService.IME_VISIBLE) != 0;
-        mCarNavigationBarController.setBottomWindowVisibility(
-                isKeyboardVisible ? View.GONE : View.VISIBLE);
+
+        if (mHideTopBarForKeyboard) {
+            mCarNavigationBarController.setTopWindowVisibility(
+                    isKeyboardVisible ? View.GONE : View.VISIBLE);
+        }
+
+        if (mHideBottomBarForKeyboard) {
+            mCarNavigationBarController.setBottomWindowVisibility(
+                    isKeyboardVisible ? View.GONE : View.VISIBLE);
+        }
+
+        if (mHideLeftBarForKeyboard) {
+            mCarNavigationBarController.setLeftWindowVisibility(
+                    isKeyboardVisible ? View.GONE : View.VISIBLE);
+        }
+        if (mHideRightBarForKeyboard) {
+            mCarNavigationBarController.setRightWindowVisibility(
+                    isKeyboardVisible ? View.GONE : View.VISIBLE);
+        }
     }
 
     @Override
diff --git a/packages/CarSystemUI/src/com/android/systemui/car/navigationbar/CarNavigationBarController.java b/packages/CarSystemUI/src/com/android/systemui/car/navigationbar/CarNavigationBarController.java
index ca780ae..e522d19 100644
--- a/packages/CarSystemUI/src/com/android/systemui/car/navigationbar/CarNavigationBarController.java
+++ b/packages/CarSystemUI/src/com/android/systemui/car/navigationbar/CarNavigationBarController.java
@@ -22,7 +22,6 @@
 
 import androidx.annotation.Nullable;
 
-import com.android.systemui.R;
 import com.android.systemui.car.hvac.HvacController;
 
 import javax.inject.Inject;
@@ -61,7 +60,8 @@
             NavigationBarViewFactory navigationBarViewFactory,
             ButtonSelectionStateController buttonSelectionStateController,
             Lazy<HvacController> hvacControllerLazy,
-            ButtonRoleHolderController buttonRoleHolderController) {
+            ButtonRoleHolderController buttonRoleHolderController,
+            SystemBarConfigs systemBarConfigs) {
         mContext = context;
         mNavigationBarViewFactory = navigationBarViewFactory;
         mButtonSelectionStateController = buttonSelectionStateController;
@@ -69,19 +69,17 @@
         mButtonRoleHolderController = buttonRoleHolderController;
 
         // Read configuration.
-        mShowTop = mContext.getResources().getBoolean(R.bool.config_enableTopNavigationBar);
-        mShowBottom = mContext.getResources().getBoolean(R.bool.config_enableBottomNavigationBar);
-        mShowLeft = mContext.getResources().getBoolean(R.bool.config_enableLeftNavigationBar);
-        mShowRight = mContext.getResources().getBoolean(R.bool.config_enableRightNavigationBar);
+        mShowTop = systemBarConfigs.getEnabledStatusBySide(SystemBarConfigs.TOP);
+        mShowBottom = systemBarConfigs.getEnabledStatusBySide(SystemBarConfigs.BOTTOM);
+        mShowLeft = systemBarConfigs.getEnabledStatusBySide(SystemBarConfigs.LEFT);
+        mShowRight = systemBarConfigs.getEnabledStatusBySide(SystemBarConfigs.RIGHT);
     }
 
     /**
      * Hides all system bars.
      */
     public void hideBars() {
-        if (mTopView != null) {
-            mTopView.setVisibility(View.GONE);
-        }
+        setTopWindowVisibility(View.GONE);
         setBottomWindowVisibility(View.GONE);
         setLeftWindowVisibility(View.GONE);
         setRightWindowVisibility(View.GONE);
@@ -91,9 +89,7 @@
      * Shows all system bars.
      */
     public void showBars() {
-        if (mTopView != null) {
-            mTopView.setVisibility(View.VISIBLE);
-        }
+        setTopWindowVisibility(View.VISIBLE);
         setBottomWindowVisibility(View.VISIBLE);
         setLeftWindowVisibility(View.VISIBLE);
         setRightWindowVisibility(View.VISIBLE);
@@ -135,6 +131,11 @@
         return mShowRight ? mNavigationBarViewFactory.getRightWindow() : null;
     }
 
+    /** Toggles the top nav bar visibility. */
+    public boolean setTopWindowVisibility(@View.Visibility int visibility) {
+        return setWindowVisibility(getTopWindow(), visibility);
+    }
+
     /** Toggles the bottom nav bar visibility. */
     public boolean setBottomWindowVisibility(@View.Visibility int visibility) {
         return setWindowVisibility(getBottomWindow(), visibility);
diff --git a/packages/CarSystemUI/src/com/android/systemui/car/navigationbar/CarNavigationBarView.java b/packages/CarSystemUI/src/com/android/systemui/car/navigationbar/CarNavigationBarView.java
index 029d4c7..ab401bb 100644
--- a/packages/CarSystemUI/src/com/android/systemui/car/navigationbar/CarNavigationBarView.java
+++ b/packages/CarSystemUI/src/com/android/systemui/car/navigationbar/CarNavigationBarView.java
@@ -20,7 +20,6 @@
 import android.util.AttributeSet;
 import android.view.MotionEvent;
 import android.view.View;
-import android.view.WindowInsets;
 import android.widget.LinearLayout;
 
 import com.android.systemui.Dependency;
@@ -77,11 +76,6 @@
         setFocusable(false);
     }
 
-    @Override
-    public WindowInsets onApplyWindowInsets(WindowInsets windowInsets) {
-        return windowInsets;
-    }
-
     // Used to forward touch events even if the touch was initiated from a child component
     @Override
     public boolean onInterceptTouchEvent(MotionEvent ev) {
diff --git a/packages/CarSystemUI/src/com/android/systemui/car/navigationbar/NavigationBarViewFactory.java b/packages/CarSystemUI/src/com/android/systemui/car/navigationbar/NavigationBarViewFactory.java
index d60bc41..adf8d4d 100644
--- a/packages/CarSystemUI/src/com/android/systemui/car/navigationbar/NavigationBarViewFactory.java
+++ b/packages/CarSystemUI/src/com/android/systemui/car/navigationbar/NavigationBarViewFactory.java
@@ -148,10 +148,9 @@
         CarNavigationBarView view = (CarNavigationBarView) View.inflate(mContext, barLayout,
                 /* root= */ null);
 
-        // Include a FocusParkingView at the end. The rotary controller "parks" the focus here when
-        // the user navigates to another window. This is also used to prevent wrap-around which is
-        // why it must be first or last in Tab order.
-        view.addView(new FocusParkingView(mContext));
+        // Include a FocusParkingView at the beginning. The rotary controller "parks" the focus here
+        // when the user navigates to another window. This is also used to prevent wrap-around.
+        view.addView(new FocusParkingView(mContext), 0);
 
         mCachedViewMap.put(type, view);
         return mCachedViewMap.get(type);
diff --git a/packages/CarSystemUI/src/com/android/systemui/car/navigationbar/SystemBarConfigs.java b/packages/CarSystemUI/src/com/android/systemui/car/navigationbar/SystemBarConfigs.java
new file mode 100644
index 0000000..2efa2b3d
--- /dev/null
+++ b/packages/CarSystemUI/src/com/android/systemui/car/navigationbar/SystemBarConfigs.java
@@ -0,0 +1,458 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.car.navigationbar;
+
+import android.annotation.IntDef;
+import android.content.res.Resources;
+import android.graphics.PixelFormat;
+import android.util.ArrayMap;
+import android.util.ArraySet;
+import android.util.Log;
+import android.view.Gravity;
+import android.view.InsetsState;
+import android.view.ViewGroup;
+import android.view.WindowManager;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.systemui.R;
+import com.android.systemui.car.notification.BottomNotificationPanelViewMediator;
+import com.android.systemui.car.notification.TopNotificationPanelViewMediator;
+import com.android.systemui.dagger.qualifiers.Main;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Target;
+import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import javax.inject.Inject;
+import javax.inject.Singleton;
+
+/**
+ * Reads configs for system bars for each side (TOP, BOTTOM, LEFT, and RIGHT) and returns the
+ * corresponding {@link android.view.WindowManager.LayoutParams} per the configuration.
+ */
+@Singleton
+public class SystemBarConfigs {
+
+    private static final String TAG = SystemBarConfigs.class.getSimpleName();
+    // The z-order from which system bars will start to appear on top of HUN's.
+    private static final int HUN_ZORDER = 10;
+
+    @IntDef(value = {TOP, BOTTOM, LEFT, RIGHT})
+    @Target({ElementType.TYPE_PARAMETER, ElementType.TYPE_USE})
+    private @interface SystemBarSide {
+    }
+
+    public static final int TOP = 0;
+    public static final int BOTTOM = 1;
+    public static final int LEFT = 2;
+    public static final int RIGHT = 3;
+
+    /*
+        NOTE: The elements' order in the map below must be preserved as-is since the correct
+        corresponding values are obtained by the index.
+     */
+    private static final int[] BAR_TYPE_MAP = {
+            InsetsState.ITYPE_STATUS_BAR,
+            InsetsState.ITYPE_NAVIGATION_BAR,
+            InsetsState.ITYPE_CLIMATE_BAR,
+            InsetsState.ITYPE_EXTRA_NAVIGATION_BAR
+    };
+
+    private static final Map<@SystemBarSide Integer, Integer> BAR_GRAVITY_MAP = new ArrayMap<>();
+    private static final Map<@SystemBarSide Integer, String> BAR_TITLE_MAP = new ArrayMap<>();
+    private static final Map<@SystemBarSide Integer, Integer> BAR_GESTURE_MAP = new ArrayMap<>();
+
+    private final Resources mResources;
+    private final Map<@SystemBarSide Integer, SystemBarConfig> mSystemBarConfigMap =
+            new ArrayMap<>();
+    private final List<@SystemBarSide Integer> mSystemBarSidesByZOrder = new ArrayList<>();
+
+    private boolean mTopNavBarEnabled;
+    private boolean mBottomNavBarEnabled;
+    private boolean mLeftNavBarEnabled;
+    private boolean mRightNavBarEnabled;
+
+    @Inject
+    public SystemBarConfigs(@Main Resources resources) {
+        mResources = resources;
+
+        populateMaps();
+        readConfigs();
+
+        checkEnabledBarsHaveUniqueBarTypes();
+        checkSystemBarEnabledForNotificationPanel();
+        checkHideBottomBarForKeyboardConfigSync();
+
+        setInsetPaddingsForOverlappingCorners();
+        sortSystemBarSidesByZOrder();
+    }
+
+    protected WindowManager.LayoutParams getLayoutParamsBySide(@SystemBarSide int side) {
+        return mSystemBarConfigMap.get(side) != null
+                ? mSystemBarConfigMap.get(side).getLayoutParams() : null;
+    }
+
+    protected boolean getEnabledStatusBySide(@SystemBarSide int side) {
+        switch (side) {
+            case TOP:
+                return mTopNavBarEnabled;
+            case BOTTOM:
+                return mBottomNavBarEnabled;
+            case LEFT:
+                return mLeftNavBarEnabled;
+            case RIGHT:
+                return mRightNavBarEnabled;
+            default:
+                return false;
+        }
+    }
+
+    protected boolean getHideForKeyboardBySide(@SystemBarSide int side) {
+        return mSystemBarConfigMap.get(side) != null
+                && mSystemBarConfigMap.get(side).getHideForKeyboard();
+    }
+
+    protected void insetSystemBar(@SystemBarSide int side, CarNavigationBarView view) {
+        int[] paddings = mSystemBarConfigMap.get(side).getPaddings();
+        view.setPadding(paddings[2], paddings[0], paddings[3], paddings[1]);
+    }
+
+    protected List<Integer> getSystemBarSidesByZOrder() {
+        return mSystemBarSidesByZOrder;
+    }
+
+    @VisibleForTesting
+    protected static int getHunZOrder() {
+        return HUN_ZORDER;
+    }
+
+    private static void populateMaps() {
+        BAR_GRAVITY_MAP.put(TOP, Gravity.TOP);
+        BAR_GRAVITY_MAP.put(BOTTOM, Gravity.BOTTOM);
+        BAR_GRAVITY_MAP.put(LEFT, Gravity.LEFT);
+        BAR_GRAVITY_MAP.put(RIGHT, Gravity.RIGHT);
+
+        BAR_TITLE_MAP.put(TOP, "TopCarSystemBar");
+        BAR_TITLE_MAP.put(BOTTOM, "BottomCarSystemBar");
+        BAR_TITLE_MAP.put(LEFT, "LeftCarSystemBar");
+        BAR_TITLE_MAP.put(RIGHT, "RightCarSystemBar");
+
+        BAR_GESTURE_MAP.put(TOP, InsetsState.ITYPE_TOP_MANDATORY_GESTURES);
+        BAR_GESTURE_MAP.put(BOTTOM, InsetsState.ITYPE_BOTTOM_MANDATORY_GESTURES);
+        BAR_GESTURE_MAP.put(LEFT, InsetsState.ITYPE_LEFT_MANDATORY_GESTURES);
+        BAR_GESTURE_MAP.put(RIGHT, InsetsState.ITYPE_RIGHT_MANDATORY_GESTURES);
+    }
+
+    private void readConfigs() {
+        mTopNavBarEnabled = mResources.getBoolean(R.bool.config_enableTopNavigationBar);
+        mBottomNavBarEnabled = mResources.getBoolean(R.bool.config_enableBottomNavigationBar);
+        mLeftNavBarEnabled = mResources.getBoolean(R.bool.config_enableLeftNavigationBar);
+        mRightNavBarEnabled = mResources.getBoolean(R.bool.config_enableRightNavigationBar);
+
+        if (mTopNavBarEnabled) {
+            SystemBarConfig topBarConfig =
+                    new SystemBarConfigBuilder()
+                            .setSide(TOP)
+                            .setGirth(mResources.getDimensionPixelSize(
+                                    R.dimen.car_top_navigation_bar_height))
+                            .setBarType(mResources.getInteger(R.integer.config_topSystemBarType))
+                            .setZOrder(mResources.getInteger(R.integer.config_topSystemBarZOrder))
+                            .setHideForKeyboard(mResources.getBoolean(
+                                    R.bool.config_hideTopSystemBarForKeyboard))
+                            .build();
+            mSystemBarConfigMap.put(TOP, topBarConfig);
+        }
+
+        if (mBottomNavBarEnabled) {
+            SystemBarConfig bottomBarConfig =
+                    new SystemBarConfigBuilder()
+                            .setSide(BOTTOM)
+                            .setGirth(mResources.getDimensionPixelSize(
+                                    R.dimen.car_bottom_navigation_bar_height))
+                            .setBarType(mResources.getInteger(R.integer.config_bottomSystemBarType))
+                            .setZOrder(
+                                    mResources.getInteger(R.integer.config_bottomSystemBarZOrder))
+                            .setHideForKeyboard(mResources.getBoolean(
+                                    R.bool.config_hideBottomSystemBarForKeyboard))
+                            .build();
+            mSystemBarConfigMap.put(BOTTOM, bottomBarConfig);
+        }
+
+        if (mLeftNavBarEnabled) {
+            SystemBarConfig leftBarConfig =
+                    new SystemBarConfigBuilder()
+                            .setSide(LEFT)
+                            .setGirth(mResources.getDimensionPixelSize(
+                                    R.dimen.car_left_navigation_bar_width))
+                            .setBarType(mResources.getInteger(R.integer.config_leftSystemBarType))
+                            .setZOrder(mResources.getInteger(R.integer.config_leftSystemBarZOrder))
+                            .setHideForKeyboard(mResources.getBoolean(
+                                    R.bool.config_hideLeftSystemBarForKeyboard))
+                            .build();
+            mSystemBarConfigMap.put(LEFT, leftBarConfig);
+        }
+
+        if (mRightNavBarEnabled) {
+            SystemBarConfig rightBarConfig =
+                    new SystemBarConfigBuilder()
+                            .setSide(RIGHT)
+                            .setGirth(mResources.getDimensionPixelSize(
+                                    R.dimen.car_right_navigation_bar_width))
+                            .setBarType(mResources.getInteger(R.integer.config_rightSystemBarType))
+                            .setZOrder(mResources.getInteger(R.integer.config_rightSystemBarZOrder))
+                            .setHideForKeyboard(mResources.getBoolean(
+                                    R.bool.config_hideRightSystemBarForKeyboard))
+                            .build();
+            mSystemBarConfigMap.put(RIGHT, rightBarConfig);
+        }
+    }
+
+    private void checkEnabledBarsHaveUniqueBarTypes() throws RuntimeException {
+        Set<Integer> barTypesUsed = new ArraySet<>();
+        int enabledNavBarCount = mSystemBarConfigMap.size();
+
+        for (SystemBarConfig systemBarConfig : mSystemBarConfigMap.values()) {
+            barTypesUsed.add(systemBarConfig.getBarType());
+        }
+
+        // The number of bar types used cannot be fewer than that of enabled system bars.
+        if (barTypesUsed.size() < enabledNavBarCount) {
+            throw new RuntimeException("Each enabled system bar must have a unique bar type. Check "
+                    + "the configuration in config.xml");
+        }
+    }
+
+    private void checkSystemBarEnabledForNotificationPanel() throws RuntimeException {
+
+        String notificationPanelMediatorName =
+                mResources.getString(R.string.config_notificationPanelViewMediator);
+        if (notificationPanelMediatorName == null) {
+            return;
+        }
+
+        Class<?> notificationPanelMediatorUsed = null;
+        try {
+            notificationPanelMediatorUsed = Class.forName(notificationPanelMediatorName);
+        } catch (ClassNotFoundException e) {
+            e.printStackTrace();
+        }
+
+        if (!mTopNavBarEnabled && TopNotificationPanelViewMediator.class.isAssignableFrom(
+                notificationPanelMediatorUsed)) {
+            throw new RuntimeException(
+                    "Top System Bar must be enabled to use " + notificationPanelMediatorName);
+        }
+
+        if (!mBottomNavBarEnabled && BottomNotificationPanelViewMediator.class.isAssignableFrom(
+                notificationPanelMediatorUsed)) {
+            throw new RuntimeException("Bottom System Bar must be enabled to use "
+                    + notificationPanelMediatorName);
+        }
+    }
+
+    private void checkHideBottomBarForKeyboardConfigSync() throws RuntimeException {
+        if (mBottomNavBarEnabled) {
+            boolean actual = mResources.getBoolean(R.bool.config_hideBottomSystemBarForKeyboard);
+            boolean expected = mResources.getBoolean(
+                    com.android.internal.R.bool.config_automotiveHideNavBarForKeyboard);
+
+            if (actual != expected) {
+                throw new RuntimeException("config_hideBottomSystemBarForKeyboard must not be "
+                        + "overlaid directly and should always refer to"
+                        + "config_automotiveHideNavBarForKeyboard. However, their values "
+                        + "currently do not sync. Set config_hideBottomSystemBarForKeyguard to "
+                        + "@*android:bool/config_automotiveHideNavBarForKeyboard. To change its "
+                        + "value, overlay config_automotiveHideNavBarForKeyboard in "
+                        + "framework/base/core/res/res.");
+            }
+        }
+    }
+
+    private void setInsetPaddingsForOverlappingCorners() {
+        setInsetPaddingForOverlappingCorner(TOP, LEFT);
+        setInsetPaddingForOverlappingCorner(TOP, RIGHT);
+        setInsetPaddingForOverlappingCorner(BOTTOM, LEFT);
+        setInsetPaddingForOverlappingCorner(BOTTOM, RIGHT);
+    }
+
+    private void setInsetPaddingForOverlappingCorner(@SystemBarSide int horizontalSide,
+            @SystemBarSide int verticalSide) {
+
+        if (isVerticalBar(horizontalSide) || isHorizontalBar(verticalSide)) {
+            Log.w(TAG, "configureBarPaddings: Returning immediately since the horizontal and "
+                    + "vertical sides were not provided correctly.");
+            return;
+        }
+
+        SystemBarConfig horizontalBarConfig = mSystemBarConfigMap.get(horizontalSide);
+        SystemBarConfig verticalBarConfig = mSystemBarConfigMap.get(verticalSide);
+
+        if (verticalBarConfig != null && horizontalBarConfig != null) {
+            int horizontalBarZOrder = horizontalBarConfig.getZOrder();
+            int horizontalBarGirth = horizontalBarConfig.getGirth();
+            int verticalBarZOrder = verticalBarConfig.getZOrder();
+            int verticalBarGirth = verticalBarConfig.getGirth();
+
+            if (horizontalBarZOrder > verticalBarZOrder) {
+                verticalBarConfig.setPaddingBySide(horizontalSide, horizontalBarGirth);
+            } else if (horizontalBarZOrder < verticalBarZOrder) {
+                horizontalBarConfig.setPaddingBySide(verticalSide, verticalBarGirth);
+            } else {
+                throw new RuntimeException(
+                        BAR_TITLE_MAP.get(horizontalSide) + " " + BAR_TITLE_MAP.get(verticalSide)
+                                + " have the same Z-Order, and so their placing order cannot be "
+                                + "determined. Determine which bar should be placed on top of the "
+                                + "other bar and change the Z-order in config.xml accordingly."
+                );
+            }
+        }
+    }
+
+    private void sortSystemBarSidesByZOrder() {
+        List<SystemBarConfig> systemBarsByZOrder = new ArrayList<>(mSystemBarConfigMap.values());
+
+        systemBarsByZOrder.sort(new Comparator<SystemBarConfig>() {
+            @Override
+            public int compare(SystemBarConfig o1, SystemBarConfig o2) {
+                return o1.getZOrder() - o2.getZOrder();
+            }
+        });
+
+        systemBarsByZOrder.forEach(systemBarConfig -> {
+            mSystemBarSidesByZOrder.add(systemBarConfig.getSide());
+        });
+    }
+
+    private static boolean isHorizontalBar(@SystemBarSide int side) {
+        return side == TOP || side == BOTTOM;
+    }
+
+    private static boolean isVerticalBar(@SystemBarSide int side) {
+        return side == LEFT || side == RIGHT;
+    }
+
+    private static final class SystemBarConfig {
+        private final int mSide;
+        private final int mBarType;
+        private final int mGirth;
+        private final int mZOrder;
+        private final boolean mHideForKeyboard;
+
+        private int[] mPaddings = new int[]{0, 0, 0, 0};
+
+        private SystemBarConfig(@SystemBarSide int side, int barType, int girth, int zOrder,
+                boolean hideForKeyboard) {
+            mSide = side;
+            mBarType = barType;
+            mGirth = girth;
+            mZOrder = zOrder;
+            mHideForKeyboard = hideForKeyboard;
+        }
+
+        private int getSide() {
+            return mSide;
+        }
+
+        private int getBarType() {
+            return mBarType;
+        }
+
+        private int getGirth() {
+            return mGirth;
+        }
+
+        private int getZOrder() {
+            return mZOrder;
+        }
+
+        private boolean getHideForKeyboard() {
+            return mHideForKeyboard;
+        }
+
+        private int[] getPaddings() {
+            return mPaddings;
+        }
+
+        private WindowManager.LayoutParams getLayoutParams() {
+            WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
+                    isHorizontalBar(mSide) ? ViewGroup.LayoutParams.MATCH_PARENT : mGirth,
+                    isHorizontalBar(mSide) ? mGirth : ViewGroup.LayoutParams.MATCH_PARENT,
+                    mapZOrderToBarType(mZOrder),
+                    WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
+                            | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
+                            | WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH
+                            | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH,
+                    PixelFormat.TRANSLUCENT);
+            lp.setTitle(BAR_TITLE_MAP.get(mSide));
+            lp.providesInsetsTypes = new int[]{BAR_TYPE_MAP[mBarType], BAR_GESTURE_MAP.get(mSide)};
+            lp.setFitInsetsTypes(0);
+            lp.windowAnimations = 0;
+            lp.gravity = BAR_GRAVITY_MAP.get(mSide);
+            return lp;
+        }
+
+        private int mapZOrderToBarType(int zOrder) {
+            return zOrder >= HUN_ZORDER ? WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL
+                    : WindowManager.LayoutParams.TYPE_STATUS_BAR_ADDITIONAL;
+        }
+
+        private void setPaddingBySide(@SystemBarSide int side, int padding) {
+            mPaddings[side] = padding;
+        }
+    }
+
+    private static final class SystemBarConfigBuilder {
+        private int mSide;
+        private int mBarType;
+        private int mGirth;
+        private int mZOrder;
+        private boolean mHideForKeyboard;
+
+        private SystemBarConfigBuilder setSide(@SystemBarSide int side) {
+            mSide = side;
+            return this;
+        }
+
+        private SystemBarConfigBuilder setBarType(int type) {
+            mBarType = type;
+            return this;
+        }
+
+        private SystemBarConfigBuilder setGirth(int girth) {
+            mGirth = girth;
+            return this;
+        }
+
+        private SystemBarConfigBuilder setZOrder(int zOrder) {
+            mZOrder = zOrder;
+            return this;
+        }
+
+        private SystemBarConfigBuilder setHideForKeyboard(boolean hide) {
+            mHideForKeyboard = hide;
+            return this;
+        }
+
+        private SystemBarConfig build() {
+            return new SystemBarConfig(mSide, mBarType, mGirth, mZOrder, mHideForKeyboard);
+        }
+    }
+}
diff --git a/packages/CarSystemUI/src/com/android/systemui/car/notification/NotificationPanelViewController.java b/packages/CarSystemUI/src/com/android/systemui/car/notification/NotificationPanelViewController.java
index 1eead62..fd804c7 100644
--- a/packages/CarSystemUI/src/com/android/systemui/car/notification/NotificationPanelViewController.java
+++ b/packages/CarSystemUI/src/com/android/systemui/car/notification/NotificationPanelViewController.java
@@ -23,6 +23,8 @@
 import android.content.res.Resources;
 import android.graphics.Rect;
 import android.graphics.drawable.Drawable;
+import android.inputmethodservice.InputMethodService;
+import android.os.IBinder;
 import android.os.RemoteException;
 import android.util.Log;
 import android.view.GestureDetector;
@@ -80,6 +82,7 @@
     private final StatusBarStateController mStatusBarStateController;
     private final boolean mEnableHeadsUpNotificationWhenNotificationShadeOpen;
     private final NotificationVisibilityLogger mNotificationVisibilityLogger;
+    private final int mNavBarHeight;
 
     private float mInitialBackgroundAlpha;
     private float mBackgroundAlphaDiff;
@@ -89,7 +92,6 @@
     private RecyclerView mNotificationList;
     private NotificationViewController mNotificationViewController;
 
-    private boolean mIsTracking;
     private boolean mNotificationListAtEnd;
     private float mFirstTouchDownOnGlassPane;
     private boolean mNotificationListAtEndAtTimeOfTouch;
@@ -137,7 +139,10 @@
         mStatusBarStateController = statusBarStateController;
         mNotificationVisibilityLogger = notificationVisibilityLogger;
 
+        mNavBarHeight = mResources.getDimensionPixelSize(R.dimen.car_bottom_navigation_bar_height);
+
         mCommandQueue.addCallback(this);
+
         // Notification background setup.
         mInitialBackgroundAlpha = (float) mResources.getInteger(
                 R.integer.config_initialNotificationBackgroundAlpha) / 100;
@@ -178,6 +183,27 @@
         }
     }
 
+    @Override
+    public void setImeWindowStatus(int displayId, IBinder token, int vis, int backDisposition,
+            boolean showImeSwitcher) {
+        if (mContext.getDisplayId() != displayId) {
+            return;
+        }
+        boolean isKeyboardVisible = (vis & InputMethodService.IME_VISIBLE) != 0;
+        int bottomMargin = isKeyboardVisible ? 0 : mNavBarHeight;
+        ViewGroup container = (ViewGroup) getLayout();
+        if (container == null) {
+            // Notification panel hasn't been inflated before. We shouldn't try to update the layout
+            // params.
+            return;
+        }
+
+        ViewGroup.MarginLayoutParams params =
+                (ViewGroup.MarginLayoutParams) container.getLayoutParams();
+        params.setMargins(params.leftMargin, params.topMargin, params.rightMargin, bottomMargin);
+        container.setLayoutParams(params);
+    }
+
     // OverlayViewController
 
     @Override
@@ -192,11 +218,21 @@
     }
 
     @Override
-    protected boolean shouldShowNavigationBar() {
+    protected boolean shouldShowNavigationBarInsets() {
         return true;
     }
 
     @Override
+    protected boolean shouldShowStatusBarInsets() {
+        return true;
+    }
+
+    @Override
+    protected int getInsetTypesToFit() {
+        return 0;
+    }
+
+    @Override
     protected boolean shouldShowHUN() {
         return mEnableHeadsUpNotificationWhenNotificationShadeOpen;
     }
@@ -287,14 +323,14 @@
         // The glass pane is used to view touch events before passed to the notification list.
         // This allows us to initialize gesture listeners and detect when to close the notifications
         glassPane.setOnTouchListener((v, event) -> {
-            if (event.getActionMasked() == MotionEvent.ACTION_UP) {
+            if (isClosingAction(event)) {
                 mNotificationListAtEndAtTimeOfTouch = false;
             }
-            if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
+            if (isOpeningAction(event)) {
                 mFirstTouchDownOnGlassPane = event.getRawX();
                 mNotificationListAtEndAtTimeOfTouch = mNotificationListAtEnd;
                 // Reset the tracker when there is a touch down on the glass pane.
-                mIsTracking = false;
+                setIsTracking(false);
                 // Pass the down event to gesture detector so that it knows where the touch event
                 // started.
                 closeGestureDetector.onTouchEvent(event);
@@ -329,22 +365,21 @@
 
             // If the card is swiping we should not allow the notification shade to close.
             // Hence setting mNotificationListAtEndAtTimeOfTouch to false will stop that
-            // for us. We are also checking for mIsTracking because while swiping the
+            // for us. We are also checking for isTracking() because while swiping the
             // notification shade to close if the user goes a bit horizontal while swiping
             // upwards then also this should close.
-            if (mIsNotificationCardSwiping && !mIsTracking) {
+            if (mIsNotificationCardSwiping && !isTracking()) {
                 mNotificationListAtEndAtTimeOfTouch = false;
             }
 
             boolean handled = closeGestureDetector.onTouchEvent(event);
-            boolean isTracking = mIsTracking;
+            boolean isTracking = isTracking();
             Rect rect = getLayout().getClipBounds();
             float clippedHeight = 0;
             if (rect != null) {
                 clippedHeight = rect.bottom;
             }
-            if (!handled && event.getActionMasked() == MotionEvent.ACTION_UP
-                    && mIsSwipingVerticallyToClose) {
+            if (!handled && isClosingAction(event) && mIsSwipingVerticallyToClose) {
                 if (getSettleClosePercentage() < getPercentageFromEndingEdge() && isTracking) {
                     animatePanel(DEFAULT_FLING_VELOCITY, false);
                 } else if (clippedHeight != getLayout().getHeight() && isTracking) {
@@ -357,7 +392,7 @@
             // Updating the mNotificationListAtEndAtTimeOfTouch state has to be done after
             // the event has been passed to the closeGestureDetector above, such that the
             // closeGestureDetector sees the up event before the state has changed.
-            if (event.getActionMasked() == MotionEvent.ACTION_UP) {
+            if (isClosingAction(event)) {
                 mNotificationListAtEndAtTimeOfTouch = false;
             }
             return handled || isTracking;
diff --git a/packages/CarSystemUI/src/com/android/systemui/car/statusbar/CarStatusBar.java b/packages/CarSystemUI/src/com/android/systemui/car/statusbar/CarStatusBar.java
deleted file mode 100644
index d692487..0000000
--- a/packages/CarSystemUI/src/com/android/systemui/car/statusbar/CarStatusBar.java
+++ /dev/null
@@ -1,519 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.car.statusbar;
-
-import static com.android.systemui.Dependency.TIME_TICK_HANDLER_NAME;
-
-import android.annotation.Nullable;
-import android.content.Context;
-import android.graphics.drawable.Drawable;
-import android.os.Handler;
-import android.os.PowerManager;
-import android.util.DisplayMetrics;
-import android.util.Log;
-import android.view.View;
-
-import com.android.internal.logging.MetricsLogger;
-import com.android.internal.statusbar.RegisterStatusBarResult;
-import com.android.keyguard.KeyguardUpdateMonitor;
-import com.android.keyguard.ViewMediatorCallback;
-import com.android.systemui.BatteryMeterView;
-import com.android.systemui.Dependency;
-import com.android.systemui.InitController;
-import com.android.systemui.Prefs;
-import com.android.systemui.R;
-import com.android.systemui.assist.AssistManager;
-import com.android.systemui.broadcast.BroadcastDispatcher;
-import com.android.systemui.bubbles.BubbleController;
-import com.android.systemui.car.CarDeviceProvisionedController;
-import com.android.systemui.car.CarDeviceProvisionedListener;
-import com.android.systemui.car.bluetooth.CarBatteryController;
-import com.android.systemui.car.navigationbar.CarNavigationBarController;
-import com.android.systemui.classifier.FalsingLog;
-import com.android.systemui.colorextraction.SysuiColorExtractor;
-import com.android.systemui.dagger.qualifiers.UiBackground;
-import com.android.systemui.fragments.FragmentHostManager;
-import com.android.systemui.keyguard.DismissCallbackRegistry;
-import com.android.systemui.keyguard.KeyguardViewMediator;
-import com.android.systemui.keyguard.ScreenLifecycle;
-import com.android.systemui.keyguard.WakefulnessLifecycle;
-import com.android.systemui.plugins.DarkIconDispatcher;
-import com.android.systemui.plugins.FalsingManager;
-import com.android.systemui.plugins.PluginDependencyProvider;
-import com.android.systemui.plugins.qs.QS;
-import com.android.systemui.recents.Recents;
-import com.android.systemui.recents.ScreenPinningRequest;
-import com.android.systemui.shared.plugins.PluginManager;
-import com.android.systemui.stackdivider.Divider;
-import com.android.systemui.statusbar.CommandQueue;
-import com.android.systemui.statusbar.KeyguardIndicationController;
-import com.android.systemui.statusbar.NavigationBarController;
-import com.android.systemui.statusbar.NotificationLockscreenUserManager;
-import com.android.systemui.statusbar.NotificationMediaManager;
-import com.android.systemui.statusbar.NotificationRemoteInputManager;
-import com.android.systemui.statusbar.NotificationShadeDepthController;
-import com.android.systemui.statusbar.NotificationViewHierarchyManager;
-import com.android.systemui.statusbar.PulseExpansionHandler;
-import com.android.systemui.statusbar.SuperStatusBarViewFactory;
-import com.android.systemui.statusbar.SysuiStatusBarStateController;
-import com.android.systemui.statusbar.VibratorHelper;
-import com.android.systemui.statusbar.notification.DynamicPrivacyController;
-import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator;
-import com.android.systemui.statusbar.notification.VisualStabilityManager;
-import com.android.systemui.statusbar.notification.collection.NotificationEntry;
-import com.android.systemui.statusbar.notification.init.NotificationsController;
-import com.android.systemui.statusbar.notification.interruption.BypassHeadsUpNotifier;
-import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider;
-import com.android.systemui.statusbar.notification.interruption.NotificationInterruptSuppressor;
-import com.android.systemui.statusbar.notification.logging.NotificationLogger;
-import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
-import com.android.systemui.statusbar.phone.AutoHideController;
-import com.android.systemui.statusbar.phone.BiometricUnlockController;
-import com.android.systemui.statusbar.phone.CollapsedStatusBarFragment;
-import com.android.systemui.statusbar.phone.DozeParameters;
-import com.android.systemui.statusbar.phone.DozeScrimController;
-import com.android.systemui.statusbar.phone.DozeServiceHost;
-import com.android.systemui.statusbar.phone.HeadsUpManagerPhone;
-import com.android.systemui.statusbar.phone.KeyguardBypassController;
-import com.android.systemui.statusbar.phone.KeyguardDismissUtil;
-import com.android.systemui.statusbar.phone.LightBarController;
-import com.android.systemui.statusbar.phone.LightsOutNotifController;
-import com.android.systemui.statusbar.phone.LockscreenLockIconController;
-import com.android.systemui.statusbar.phone.LockscreenWallpaper;
-import com.android.systemui.statusbar.phone.NotificationGroupManager;
-import com.android.systemui.statusbar.phone.NotificationShadeWindowController;
-import com.android.systemui.statusbar.phone.PhoneStatusBarPolicy;
-import com.android.systemui.statusbar.phone.ScrimController;
-import com.android.systemui.statusbar.phone.ShadeController;
-import com.android.systemui.statusbar.phone.StatusBar;
-import com.android.systemui.statusbar.phone.StatusBarIconController;
-import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
-import com.android.systemui.statusbar.phone.StatusBarNotificationActivityStarter;
-import com.android.systemui.statusbar.phone.StatusBarTouchableRegionManager;
-import com.android.systemui.statusbar.phone.dagger.StatusBarComponent;
-import com.android.systemui.statusbar.policy.BatteryController;
-import com.android.systemui.statusbar.policy.ConfigurationController;
-import com.android.systemui.statusbar.policy.ExtensionController;
-import com.android.systemui.statusbar.policy.KeyguardStateController;
-import com.android.systemui.statusbar.policy.NetworkController;
-import com.android.systemui.statusbar.policy.RemoteInputQuickSettingsDisabler;
-import com.android.systemui.statusbar.policy.UserInfoControllerImpl;
-import com.android.systemui.statusbar.policy.UserSwitcherController;
-import com.android.systemui.volume.VolumeComponent;
-
-import java.io.FileDescriptor;
-import java.io.PrintWriter;
-import java.util.Map;
-import java.util.Optional;
-import java.util.concurrent.Executor;
-
-import javax.inject.Named;
-import javax.inject.Provider;
-
-import dagger.Lazy;
-
-/**
- * A status bar tailored for the automotive use case.
- */
-public class CarStatusBar extends StatusBar implements CarBatteryController.BatteryViewHandler {
-    private static final String TAG = "CarStatusBar";
-
-    private final UserSwitcherController mUserSwitcherController;
-    private final ScrimController mScrimController;
-
-    private CarBatteryController mCarBatteryController;
-    private BatteryMeterView mBatteryMeterView;
-    private Drawable mNotificationPanelBackground;
-
-    private final Object mQueueLock = new Object();
-    private final CarNavigationBarController mCarNavigationBarController;
-    private final CarDeviceProvisionedController mCarDeviceProvisionedController;
-    private final ScreenLifecycle mScreenLifecycle;
-
-    private boolean mDeviceIsSetUpForUser = true;
-    private boolean mIsUserSetupInProgress = false;
-
-    public CarStatusBar(
-            Context context,
-            NotificationsController notificationsController,
-            LightBarController lightBarController,
-            AutoHideController autoHideController,
-            KeyguardUpdateMonitor keyguardUpdateMonitor,
-            StatusBarIconController statusBarIconController,
-            PulseExpansionHandler pulseExpansionHandler,
-            NotificationWakeUpCoordinator notificationWakeUpCoordinator,
-            KeyguardBypassController keyguardBypassController,
-            KeyguardStateController keyguardStateController,
-            HeadsUpManagerPhone headsUpManagerPhone,
-            DynamicPrivacyController dynamicPrivacyController,
-            BypassHeadsUpNotifier bypassHeadsUpNotifier,
-            FalsingManager falsingManager,
-            BroadcastDispatcher broadcastDispatcher,
-            RemoteInputQuickSettingsDisabler remoteInputQuickSettingsDisabler,
-            NotificationGutsManager notificationGutsManager,
-            NotificationLogger notificationLogger,
-            NotificationInterruptStateProvider notificationInterruptStateProvider,
-            NotificationViewHierarchyManager notificationViewHierarchyManager,
-            KeyguardViewMediator keyguardViewMediator,
-            DisplayMetrics displayMetrics,
-            MetricsLogger metricsLogger,
-            @UiBackground Executor uiBgExecutor,
-            NotificationMediaManager notificationMediaManager,
-            NotificationLockscreenUserManager lockScreenUserManager,
-            NotificationRemoteInputManager remoteInputManager,
-            UserSwitcherController userSwitcherController,
-            NetworkController networkController,
-            BatteryController batteryController,
-            SysuiColorExtractor colorExtractor,
-            ScreenLifecycle screenLifecycle,
-            WakefulnessLifecycle wakefulnessLifecycle,
-            SysuiStatusBarStateController statusBarStateController,
-            VibratorHelper vibratorHelper,
-            BubbleController bubbleController,
-            NotificationGroupManager groupManager,
-            VisualStabilityManager visualStabilityManager,
-            CarDeviceProvisionedController carDeviceProvisionedController,
-            NavigationBarController navigationBarController,
-            Lazy<AssistManager> assistManagerLazy,
-            ConfigurationController configurationController,
-            NotificationShadeWindowController notificationShadeWindowController,
-            LockscreenLockIconController lockscreenLockIconController,
-            DozeParameters dozeParameters,
-            ScrimController scrimController,
-            Lazy<LockscreenWallpaper> lockscreenWallpaperLazy,
-            Lazy<BiometricUnlockController> biometricUnlockControllerLazy,
-            DozeServiceHost dozeServiceHost,
-            PowerManager powerManager,
-            ScreenPinningRequest screenPinningRequest,
-            DozeScrimController dozeScrimController,
-            VolumeComponent volumeComponent,
-            CommandQueue commandQueue,
-            Optional<Recents> recents,
-            Provider<StatusBarComponent.Builder> statusBarComponentBuilder,
-            PluginManager pluginManager,
-            Optional<Divider> dividerOptional,
-            SuperStatusBarViewFactory superStatusBarViewFactory,
-            LightsOutNotifController lightsOutNotifController,
-            StatusBarNotificationActivityStarter.Builder
-                    statusBarNotificationActivityStarterBuilder,
-            ShadeController shadeController,
-            StatusBarKeyguardViewManager statusBarKeyguardViewManager,
-            ViewMediatorCallback viewMediatorCallback,
-            InitController initController,
-            DarkIconDispatcher darkIconDispatcher,
-            @Named(TIME_TICK_HANDLER_NAME) Handler timeTickHandler,
-            PluginDependencyProvider pluginDependencyProvider,
-            KeyguardDismissUtil keyguardDismissUtil,
-            ExtensionController extensionController,
-            UserInfoControllerImpl userInfoControllerImpl,
-            PhoneStatusBarPolicy phoneStatusBarPolicy,
-            KeyguardIndicationController keyguardIndicationController,
-            DismissCallbackRegistry dismissCallbackRegistry,
-            StatusBarTouchableRegionManager statusBarTouchableRegionManager,
-            Lazy<NotificationShadeDepthController> depthControllerLazy,
-            /* Car Settings injected components. */
-            CarNavigationBarController carNavigationBarController) {
-        super(
-                context,
-                notificationsController,
-                lightBarController,
-                autoHideController,
-                keyguardUpdateMonitor,
-                statusBarIconController,
-                pulseExpansionHandler,
-                notificationWakeUpCoordinator,
-                keyguardBypassController,
-                keyguardStateController,
-                headsUpManagerPhone,
-                dynamicPrivacyController,
-                bypassHeadsUpNotifier,
-                falsingManager,
-                broadcastDispatcher,
-                remoteInputQuickSettingsDisabler,
-                notificationGutsManager,
-                notificationLogger,
-                notificationInterruptStateProvider,
-                notificationViewHierarchyManager,
-                keyguardViewMediator,
-                displayMetrics,
-                metricsLogger,
-                uiBgExecutor,
-                notificationMediaManager,
-                lockScreenUserManager,
-                remoteInputManager,
-                userSwitcherController,
-                networkController,
-                batteryController,
-                colorExtractor,
-                screenLifecycle,
-                wakefulnessLifecycle,
-                statusBarStateController,
-                vibratorHelper,
-                bubbleController,
-                groupManager,
-                visualStabilityManager,
-                carDeviceProvisionedController,
-                navigationBarController,
-                assistManagerLazy,
-                configurationController,
-                notificationShadeWindowController,
-                lockscreenLockIconController,
-                dozeParameters,
-                scrimController,
-                null /* keyguardLiftController */,
-                lockscreenWallpaperLazy,
-                biometricUnlockControllerLazy,
-                dozeServiceHost,
-                powerManager,
-                screenPinningRequest,
-                dozeScrimController,
-                volumeComponent,
-                commandQueue,
-                recents,
-                statusBarComponentBuilder,
-                pluginManager,
-                dividerOptional,
-                lightsOutNotifController,
-                statusBarNotificationActivityStarterBuilder,
-                shadeController,
-                superStatusBarViewFactory,
-                statusBarKeyguardViewManager,
-                viewMediatorCallback,
-                initController,
-                darkIconDispatcher,
-                timeTickHandler,
-                pluginDependencyProvider,
-                keyguardDismissUtil,
-                extensionController,
-                userInfoControllerImpl,
-                phoneStatusBarPolicy,
-                keyguardIndicationController,
-                dismissCallbackRegistry,
-                depthControllerLazy,
-                statusBarTouchableRegionManager);
-        mUserSwitcherController = userSwitcherController;
-        mScrimController = scrimController;
-        mCarDeviceProvisionedController = carDeviceProvisionedController;
-        mCarNavigationBarController = carNavigationBarController;
-        mScreenLifecycle = screenLifecycle;
-    }
-
-    @Override
-    public void start() {
-        mDeviceIsSetUpForUser = mCarDeviceProvisionedController.isCurrentUserSetup();
-        mIsUserSetupInProgress = mCarDeviceProvisionedController.isCurrentUserSetupInProgress();
-
-        super.start();
-
-        createBatteryController();
-        mCarBatteryController.startListening();
-
-        mCarDeviceProvisionedController.addCallback(
-                new CarDeviceProvisionedListener() {
-                    @Override
-                    public void onUserSetupInProgressChanged() {
-                        mDeviceIsSetUpForUser = mCarDeviceProvisionedController
-                                .isCurrentUserSetup();
-                        mIsUserSetupInProgress = mCarDeviceProvisionedController
-                                .isCurrentUserSetupInProgress();
-                    }
-
-                    @Override
-                    public void onUserSetupChanged() {
-                        mDeviceIsSetUpForUser = mCarDeviceProvisionedController
-                                .isCurrentUserSetup();
-                        mIsUserSetupInProgress = mCarDeviceProvisionedController
-                                .isCurrentUserSetupInProgress();
-                    }
-
-                    @Override
-                    public void onUserSwitched() {
-                        mDeviceIsSetUpForUser = mCarDeviceProvisionedController
-                                .isCurrentUserSetup();
-                        mIsUserSetupInProgress = mCarDeviceProvisionedController
-                                .isCurrentUserSetupInProgress();
-                    }
-                });
-
-        mNotificationInterruptStateProvider.addSuppressor(new NotificationInterruptSuppressor() {
-            @Override
-            public String getName() {
-                return TAG;
-            }
-
-            @Override
-            public boolean suppressInterruptions(NotificationEntry entry) {
-                // Because space is usually constrained in the auto use-case, there should not be a
-                // pinned notification when the shade has been expanded.
-                // Ensure this by not allowing any interruptions (ie: pinning any notifications) if
-                // the shade is already opened.
-                return !getPresenter().isPresenterFullyCollapsed();
-            }
-        });
-    }
-
-    @Override
-    public boolean hideKeyguard() {
-        boolean result = super.hideKeyguard();
-        mCarNavigationBarController.hideAllKeyguardButtons(isDeviceSetupForUser());
-        return result;
-    }
-
-    @Override
-    public void showKeyguard() {
-        super.showKeyguard();
-        mCarNavigationBarController.showAllKeyguardButtons(isDeviceSetupForUser());
-    }
-
-    private boolean isDeviceSetupForUser() {
-        return mDeviceIsSetUpForUser && !mIsUserSetupInProgress;
-    }
-
-    @Override
-    protected void makeStatusBarView(@Nullable RegisterStatusBarResult result) {
-        super.makeStatusBarView(result);
-
-        mNotificationPanelBackground = getDefaultWallpaper();
-        mScrimController.setScrimBehindDrawable(mNotificationPanelBackground);
-
-        FragmentHostManager manager = FragmentHostManager.get(mPhoneStatusBarWindow);
-        manager.addTagListener(CollapsedStatusBarFragment.TAG, (tag, fragment) -> {
-            mBatteryMeterView = fragment.getView().findViewById(R.id.battery);
-
-            // By default, the BatteryMeterView should not be visible. It will be toggled
-            // when a device has connected by bluetooth.
-            mBatteryMeterView.setVisibility(View.GONE);
-        });
-    }
-
-    @Override
-    public void animateExpandNotificationsPanel() {
-        // No op.
-    }
-
-    @Override
-    protected QS createDefaultQSFragment() {
-        return null;
-    }
-
-    private BatteryController createBatteryController() {
-        mCarBatteryController = new CarBatteryController(mContext);
-        mCarBatteryController.addBatteryViewHandler(this);
-        return mCarBatteryController;
-    }
-
-    @Override
-    protected void createNavigationBar(@Nullable RegisterStatusBarResult result) {
-        // No op.
-    }
-
-    @Override
-    public void notifyBiometricAuthModeChanged() {
-        // No op.
-    }
-
-    @Override
-    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
-        //When executing dump() function simultaneously, we need to serialize them
-        //to get mStackScroller's position correctly.
-        synchronized (mQueueLock) {
-            pw.println("  mStackScroller: " + viewInfo(mStackScroller));
-            pw.println("  mStackScroller: " + viewInfo(mStackScroller)
-                    + " scroll " + mStackScroller.getScrollX()
-                    + "," + mStackScroller.getScrollY());
-        }
-        pw.print("  mCarBatteryController=");
-        pw.println(mCarBatteryController);
-        pw.print("  mBatteryMeterView=");
-        pw.println(mBatteryMeterView);
-
-        if (Dependency.get(KeyguardUpdateMonitor.class) != null) {
-            Dependency.get(KeyguardUpdateMonitor.class).dump(fd, pw, args);
-        }
-
-        FalsingLog.dump(pw);
-
-        pw.println("SharedPreferences:");
-        for (Map.Entry<String, ?> entry : Prefs.getAll(mContext).entrySet()) {
-            pw.print("  ");
-            pw.print(entry.getKey());
-            pw.print("=");
-            pw.println(entry.getValue());
-        }
-    }
-
-    @Override
-    public void showBatteryView() {
-        if (Log.isLoggable(TAG, Log.DEBUG)) {
-            Log.d(TAG, "showBatteryView(). mBatteryMeterView: " + mBatteryMeterView);
-        }
-
-        if (mBatteryMeterView != null) {
-            mBatteryMeterView.setVisibility(View.VISIBLE);
-        }
-    }
-
-    @Override
-    public void hideBatteryView() {
-        if (Log.isLoggable(TAG, Log.DEBUG)) {
-            Log.d(TAG, "hideBatteryView(). mBatteryMeterView: " + mBatteryMeterView);
-        }
-
-        if (mBatteryMeterView != null) {
-            mBatteryMeterView.setVisibility(View.GONE);
-        }
-    }
-
-    @Override
-    protected void createUserSwitcher() {
-        if (!mUserSwitcherController.useFullscreenUserSwitcher()) {
-            super.createUserSwitcher();
-        }
-    }
-
-    /**
-     * Dismisses the keyguard and shows bouncer if authentication is necessary.
-     */
-    public void dismissKeyguard() {
-        // Don't dismiss keyguard when the screen is off.
-        if (mScreenLifecycle.getScreenState() == ScreenLifecycle.SCREEN_OFF) {
-            return;
-        }
-        executeRunnableDismissingKeyguard(null/* runnable */, null /* cancelAction */,
-                true /* dismissShade */, true /* afterKeyguardGone */, true /* deferred */);
-    }
-
-    /**
-     * Ensures that relevant child views are appropriately recreated when the device's density
-     * changes.
-     */
-    @Override
-    public void onDensityOrFontScaleChanged() {
-        super.onDensityOrFontScaleChanged();
-        // Need to update the background on density changed in case the change was due to night
-        // mode.
-        mNotificationPanelBackground = getDefaultWallpaper();
-        mScrimController.setScrimBehindDrawable(mNotificationPanelBackground);
-    }
-
-    /**
-     * Returns the {@link Drawable} that represents the wallpaper that the user has currently set.
-     */
-    private Drawable getDefaultWallpaper() {
-        return mContext.getDrawable(com.android.internal.R.drawable.default_wallpaper);
-    }
-}
diff --git a/packages/CarSystemUI/src/com/android/systemui/car/statusbar/CarStatusBarKeyguardViewManager.java b/packages/CarSystemUI/src/com/android/systemui/car/statusbar/CarStatusBarKeyguardViewManager.java
deleted file mode 100644
index 96a998a..0000000
--- a/packages/CarSystemUI/src/com/android/systemui/car/statusbar/CarStatusBarKeyguardViewManager.java
+++ /dev/null
@@ -1,137 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.car.statusbar;
-
-import android.content.Context;
-import android.view.View;
-
-import com.android.internal.widget.LockPatternUtils;
-import com.android.keyguard.KeyguardUpdateMonitor;
-import com.android.keyguard.ViewMediatorCallback;
-import com.android.systemui.R;
-import com.android.systemui.car.navigationbar.CarNavigationBarController;
-import com.android.systemui.dock.DockManager;
-import com.android.systemui.statusbar.NotificationMediaManager;
-import com.android.systemui.statusbar.SysuiStatusBarStateController;
-import com.android.systemui.statusbar.phone.NavigationModeController;
-import com.android.systemui.statusbar.phone.NotificationShadeWindowController;
-import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
-import com.android.systemui.statusbar.policy.ConfigurationController;
-import com.android.systemui.statusbar.policy.KeyguardStateController;
-
-import java.util.HashSet;
-import java.util.Set;
-
-import javax.inject.Inject;
-import javax.inject.Singleton;
-
-/** Car implementation of the {@link StatusBarKeyguardViewManager}. */
-@Singleton
-public class CarStatusBarKeyguardViewManager extends StatusBarKeyguardViewManager {
-
-    protected boolean mShouldHideNavBar;
-    private final CarNavigationBarController mCarNavigationBarController;
-    private Set<OnKeyguardCancelClickedListener> mKeygaurdCancelClickedListenerSet;
-
-    @Inject
-    public CarStatusBarKeyguardViewManager(Context context,
-            ViewMediatorCallback callback,
-            LockPatternUtils lockPatternUtils,
-            SysuiStatusBarStateController sysuiStatusBarStateController,
-            ConfigurationController configurationController,
-            KeyguardUpdateMonitor keyguardUpdateMonitor,
-            NavigationModeController navigationModeController,
-            DockManager dockManager,
-            NotificationShadeWindowController notificationShadeWindowController,
-            KeyguardStateController keyguardStateController,
-            NotificationMediaManager notificationMediaManager,
-            CarNavigationBarController carNavigationBarController) {
-        super(context, callback, lockPatternUtils, sysuiStatusBarStateController,
-                configurationController, keyguardUpdateMonitor, navigationModeController,
-                dockManager, notificationShadeWindowController, keyguardStateController,
-                notificationMediaManager);
-        mShouldHideNavBar = context.getResources()
-                .getBoolean(R.bool.config_hideNavWhenKeyguardBouncerShown);
-        mCarNavigationBarController = carNavigationBarController;
-        mKeygaurdCancelClickedListenerSet = new HashSet<>();
-    }
-
-    @Override
-    protected void updateNavigationBarVisibility(boolean navBarVisible) {
-        if (!mShouldHideNavBar) {
-            return;
-        }
-        int visibility = navBarVisible ? View.VISIBLE : View.GONE;
-        mCarNavigationBarController.setBottomWindowVisibility(visibility);
-        mCarNavigationBarController.setLeftWindowVisibility(visibility);
-        mCarNavigationBarController.setRightWindowVisibility(visibility);
-    }
-
-    /**
-     * Car is a multi-user system.  There's a cancel button on the bouncer that allows the user to
-     * go back to the user switcher and select another user.  Different user may have different
-     * security mode which requires bouncer container to be resized.  For this reason, the bouncer
-     * view is destroyed on cancel.
-     */
-    @Override
-    protected boolean shouldDestroyViewOnReset() {
-        return true;
-    }
-
-    /**
-     * Called when cancel button in bouncer is pressed.
-     */
-    @Override
-    public void onCancelClicked() {
-        mKeygaurdCancelClickedListenerSet.forEach(OnKeyguardCancelClickedListener::onCancelClicked);
-    }
-
-    /**
-     * Do nothing on this change.
-     * The base class hides the keyguard which for automotive we want to avoid b/c this would happen
-     * on a configuration change due to day/night (headlight state).
-     */
-    @Override
-    public void onDensityOrFontScaleChanged() {  }
-
-    /**
-     * Add listener for keyguard cancel clicked.
-     */
-    public void addOnKeyguardCancelClickedListener(
-            OnKeyguardCancelClickedListener keyguardCancelClickedListener) {
-        mKeygaurdCancelClickedListenerSet.add(keyguardCancelClickedListener);
-    }
-
-    /**
-     * Remove listener for keyguard cancel clicked.
-     */
-    public void removeOnKeyguardCancelClickedListener(
-            OnKeyguardCancelClickedListener keyguardCancelClickedListener) {
-        mKeygaurdCancelClickedListenerSet.remove(keyguardCancelClickedListener);
-    }
-
-
-    /**
-     * Defines a callback for keyguard cancel button clicked listeners.
-     */
-    public interface OnKeyguardCancelClickedListener {
-        /**
-         * Called when keyguard cancel button is clicked.
-         */
-        void onCancelClicked();
-    }
-}
diff --git a/packages/CarSystemUI/src/com/android/systemui/car/statusbar/CarStatusBarModule.java b/packages/CarSystemUI/src/com/android/systemui/car/statusbar/UnusedStatusBar.java
similarity index 68%
copy from packages/CarSystemUI/src/com/android/systemui/car/statusbar/CarStatusBarModule.java
copy to packages/CarSystemUI/src/com/android/systemui/car/statusbar/UnusedStatusBar.java
index dc2eb04..48334bd 100644
--- a/packages/CarSystemUI/src/com/android/systemui/car/statusbar/CarStatusBarModule.java
+++ b/packages/CarSystemUI/src/com/android/systemui/car/statusbar/UnusedStatusBar.java
@@ -16,8 +16,6 @@
 
 package com.android.systemui.car.statusbar;
 
-import static com.android.systemui.Dependency.TIME_TICK_HANDLER_NAME;
-
 import android.content.Context;
 import android.os.Handler;
 import android.os.PowerManager;
@@ -30,10 +28,7 @@
 import com.android.systemui.assist.AssistManager;
 import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.bubbles.BubbleController;
-import com.android.systemui.car.CarDeviceProvisionedController;
-import com.android.systemui.car.navigationbar.CarNavigationBarController;
 import com.android.systemui.colorextraction.SysuiColorExtractor;
-import com.android.systemui.dagger.qualifiers.UiBackground;
 import com.android.systemui.keyguard.DismissCallbackRegistry;
 import com.android.systemui.keyguard.KeyguardViewMediator;
 import com.android.systemui.keyguard.ScreenLifecycle;
@@ -57,7 +52,6 @@
 import com.android.systemui.statusbar.SuperStatusBarViewFactory;
 import com.android.systemui.statusbar.SysuiStatusBarStateController;
 import com.android.systemui.statusbar.VibratorHelper;
-import com.android.systemui.statusbar.dagger.StatusBarDependenciesModule;
 import com.android.systemui.statusbar.notification.DynamicPrivacyController;
 import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator;
 import com.android.systemui.statusbar.notification.VisualStabilityManager;
@@ -66,7 +60,6 @@
 import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider;
 import com.android.systemui.statusbar.notification.logging.NotificationLogger;
 import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
-import com.android.systemui.statusbar.notification.row.NotificationRowModule;
 import com.android.systemui.statusbar.phone.AutoHideController;
 import com.android.systemui.statusbar.phone.BiometricUnlockController;
 import com.android.systemui.statusbar.phone.DozeParameters;
@@ -75,23 +68,26 @@
 import com.android.systemui.statusbar.phone.HeadsUpManagerPhone;
 import com.android.systemui.statusbar.phone.KeyguardBypassController;
 import com.android.systemui.statusbar.phone.KeyguardDismissUtil;
+import com.android.systemui.statusbar.phone.KeyguardLiftController;
 import com.android.systemui.statusbar.phone.LightBarController;
 import com.android.systemui.statusbar.phone.LightsOutNotifController;
 import com.android.systemui.statusbar.phone.LockscreenLockIconController;
 import com.android.systemui.statusbar.phone.LockscreenWallpaper;
+import com.android.systemui.statusbar.phone.NavigationBarView;
 import com.android.systemui.statusbar.phone.NotificationGroupManager;
 import com.android.systemui.statusbar.phone.NotificationShadeWindowController;
 import com.android.systemui.statusbar.phone.PhoneStatusBarPolicy;
 import com.android.systemui.statusbar.phone.ScrimController;
 import com.android.systemui.statusbar.phone.ShadeController;
+import com.android.systemui.statusbar.phone.StatusBar;
 import com.android.systemui.statusbar.phone.StatusBarIconController;
 import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
 import com.android.systemui.statusbar.phone.StatusBarNotificationActivityStarter;
 import com.android.systemui.statusbar.phone.StatusBarTouchableRegionManager;
 import com.android.systemui.statusbar.phone.dagger.StatusBarComponent;
-import com.android.systemui.statusbar.phone.dagger.StatusBarPhoneDependenciesModule;
 import com.android.systemui.statusbar.policy.BatteryController;
 import com.android.systemui.statusbar.policy.ConfigurationController;
+import com.android.systemui.statusbar.policy.DeviceProvisionedController;
 import com.android.systemui.statusbar.policy.ExtensionController;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 import com.android.systemui.statusbar.policy.NetworkController;
@@ -103,27 +99,14 @@
 import java.util.Optional;
 import java.util.concurrent.Executor;
 
-import javax.inject.Named;
 import javax.inject.Provider;
-import javax.inject.Singleton;
 
 import dagger.Lazy;
-import dagger.Module;
-import dagger.Provides;
 
-/**
- * Dagger Module providing {@link CarStatusBar}.
- */
-@Module(includes = {StatusBarDependenciesModule.class, StatusBarPhoneDependenciesModule.class,
-        NotificationRowModule.class})
-public class CarStatusBarModule {
-    /**
-     * Provides our instance of StatusBar which is considered optional.
-     */
-    @Provides
-    @Singleton
-    static CarStatusBar provideStatusBar(
-            Context context,
+/** Unused variant of {@link StatusBar} specifically used in the automotive context. */
+public class UnusedStatusBar extends StatusBar {
+
+    public UnusedStatusBar(Context context,
             NotificationsController notificationsController,
             LightBarController lightBarController,
             AutoHideController autoHideController,
@@ -141,12 +124,12 @@
             RemoteInputQuickSettingsDisabler remoteInputQuickSettingsDisabler,
             NotificationGutsManager notificationGutsManager,
             NotificationLogger notificationLogger,
-            NotificationInterruptStateProvider notificationInterruptionStateProvider,
+            NotificationInterruptStateProvider notificationInterruptStateProvider,
             NotificationViewHierarchyManager notificationViewHierarchyManager,
             KeyguardViewMediator keyguardViewMediator,
             DisplayMetrics displayMetrics,
             MetricsLogger metricsLogger,
-            @UiBackground Executor uiBgExecutor,
+            Executor uiBgExecutor,
             NotificationMediaManager notificationMediaManager,
             NotificationLockscreenUserManager lockScreenUserManager,
             NotificationRemoteInputManager remoteInputManager,
@@ -161,7 +144,7 @@
             BubbleController bubbleController,
             NotificationGroupManager groupManager,
             VisualStabilityManager visualStabilityManager,
-            CarDeviceProvisionedController carDeviceProvisionedController,
+            DeviceProvisionedController deviceProvisionedController,
             NavigationBarController navigationBarController,
             Lazy<AssistManager> assistManagerLazy,
             ConfigurationController configurationController,
@@ -169,6 +152,7 @@
             LockscreenLockIconController lockscreenLockIconController,
             DozeParameters dozeParameters,
             ScrimController scrimController,
+            KeyguardLiftController keyguardLiftController,
             Lazy<LockscreenWallpaper> lockscreenWallpaperLazy,
             Lazy<BiometricUnlockController> biometricUnlockControllerLazy,
             DozeServiceHost dozeServiceHost,
@@ -181,16 +165,15 @@
             Provider<StatusBarComponent.Builder> statusBarComponentBuilder,
             PluginManager pluginManager,
             Optional<Divider> dividerOptional,
-            SuperStatusBarViewFactory superStatusBarViewFactory,
             LightsOutNotifController lightsOutNotifController,
-            StatusBarNotificationActivityStarter.Builder
-                    statusBarNotificationActivityStarterBuilder,
+            StatusBarNotificationActivityStarter.Builder statusBarNotifActivityStarterBuilder,
             ShadeController shadeController,
+            SuperStatusBarViewFactory superStatusBarViewFactory,
             StatusBarKeyguardViewManager statusBarKeyguardViewManager,
             ViewMediatorCallback viewMediatorCallback,
             InitController initController,
             DarkIconDispatcher darkIconDispatcher,
-            @Named(TIME_TICK_HANDLER_NAME) Handler timeTickHandler,
+            Handler timeTickHandler,
             PluginDependencyProvider pluginDependencyProvider,
             KeyguardDismissUtil keyguardDismissUtil,
             ExtensionController extensionController,
@@ -198,86 +181,44 @@
             PhoneStatusBarPolicy phoneStatusBarPolicy,
             KeyguardIndicationController keyguardIndicationController,
             DismissCallbackRegistry dismissCallbackRegistry,
-            StatusBarTouchableRegionManager statusBarTouchableRegionManager,
             Lazy<NotificationShadeDepthController> notificationShadeDepthControllerLazy,
-            CarNavigationBarController carNavigationBarController) {
-        return new CarStatusBar(
-                context,
-                notificationsController,
-                lightBarController,
-                autoHideController,
-                keyguardUpdateMonitor,
-                statusBarIconController,
-                pulseExpansionHandler,
-                notificationWakeUpCoordinator,
-                keyguardBypassController,
-                keyguardStateController,
-                headsUpManagerPhone,
-                dynamicPrivacyController,
-                bypassHeadsUpNotifier,
+            StatusBarTouchableRegionManager statusBarTouchableRegionManager) {
+        super(context, notificationsController, lightBarController, autoHideController,
+                keyguardUpdateMonitor, statusBarIconController, pulseExpansionHandler,
+                notificationWakeUpCoordinator, keyguardBypassController, keyguardStateController,
+                headsUpManagerPhone, dynamicPrivacyController, bypassHeadsUpNotifier,
                 falsingManager,
-                broadcastDispatcher,
-                remoteInputQuickSettingsDisabler,
-                notificationGutsManager,
-                notificationLogger,
-                notificationInterruptionStateProvider,
-                notificationViewHierarchyManager,
-                keyguardViewMediator,
-                displayMetrics,
+                broadcastDispatcher, remoteInputQuickSettingsDisabler, notificationGutsManager,
+                notificationLogger, notificationInterruptStateProvider,
+                notificationViewHierarchyManager, keyguardViewMediator, displayMetrics,
                 metricsLogger,
-                uiBgExecutor,
-                notificationMediaManager,
-                lockScreenUserManager,
-                remoteInputManager,
-                userSwitcherController,
-                networkController,
-                batteryController,
-                colorExtractor,
-                screenLifecycle,
-                wakefulnessLifecycle,
-                statusBarStateController,
-                vibratorHelper,
-                bubbleController,
-                groupManager,
-                visualStabilityManager,
-                carDeviceProvisionedController,
-                navigationBarController,
-                assistManagerLazy,
-                configurationController,
-                notificationShadeWindowController,
-                lockscreenLockIconController,
-                dozeParameters,
-                scrimController,
-                lockscreenWallpaperLazy,
-                biometricUnlockControllerLazy,
-                dozeServiceHost,
-                powerManager,
-                screenPinningRequest,
-                dozeScrimController,
-                volumeComponent,
-                commandQueue,
-                recentsOptional,
-                statusBarComponentBuilder,
-                pluginManager,
-                dividerOptional,
-                superStatusBarViewFactory,
-                lightsOutNotifController,
-                statusBarNotificationActivityStarterBuilder,
-                shadeController,
-                statusBarKeyguardViewManager,
-                viewMediatorCallback,
-                initController,
+                uiBgExecutor, notificationMediaManager, lockScreenUserManager, remoteInputManager,
+                userSwitcherController, networkController, batteryController, colorExtractor,
+                screenLifecycle, wakefulnessLifecycle, statusBarStateController, vibratorHelper,
+                bubbleController, groupManager, visualStabilityManager, deviceProvisionedController,
+                navigationBarController, assistManagerLazy, configurationController,
+                notificationShadeWindowController, lockscreenLockIconController, dozeParameters,
+                scrimController, keyguardLiftController, lockscreenWallpaperLazy,
+                biometricUnlockControllerLazy, dozeServiceHost, powerManager, screenPinningRequest,
+                dozeScrimController, volumeComponent, commandQueue, recentsOptional,
+                statusBarComponentBuilder, pluginManager, dividerOptional, lightsOutNotifController,
+                statusBarNotifActivityStarterBuilder, shadeController, superStatusBarViewFactory,
+                statusBarKeyguardViewManager, viewMediatorCallback, initController,
                 darkIconDispatcher,
-                timeTickHandler,
-                pluginDependencyProvider,
-                keyguardDismissUtil,
-                extensionController,
-                userInfoControllerImpl,
-                phoneStatusBarPolicy,
-                keyguardIndicationController,
-                dismissCallbackRegistry,
-                statusBarTouchableRegionManager,
-                notificationShadeDepthControllerLazy,
-                carNavigationBarController);
+                timeTickHandler, pluginDependencyProvider, keyguardDismissUtil, extensionController,
+                userInfoControllerImpl, phoneStatusBarPolicy, keyguardIndicationController,
+                dismissCallbackRegistry, notificationShadeDepthControllerLazy,
+                statusBarTouchableRegionManager);
+    }
+
+    @Override
+    public void notifyBiometricAuthModeChanged() {
+        // No-op for Automotive devices.
+    }
+
+    @Override
+    public NavigationBarView getNavigationBarView() {
+        // Return null for Automotive devices.
+        return null;
     }
 }
diff --git a/packages/CarSystemUI/src/com/android/systemui/car/statusbar/CarStatusBarModule.java b/packages/CarSystemUI/src/com/android/systemui/car/statusbar/UnusedStatusBarModule.java
similarity index 94%
rename from packages/CarSystemUI/src/com/android/systemui/car/statusbar/CarStatusBarModule.java
rename to packages/CarSystemUI/src/com/android/systemui/car/statusbar/UnusedStatusBarModule.java
index dc2eb04..2c86e4d 100644
--- a/packages/CarSystemUI/src/com/android/systemui/car/statusbar/CarStatusBarModule.java
+++ b/packages/CarSystemUI/src/com/android/systemui/car/statusbar/UnusedStatusBarModule.java
@@ -23,6 +23,8 @@
 import android.os.PowerManager;
 import android.util.DisplayMetrics;
 
+import androidx.annotation.Nullable;
+
 import com.android.internal.logging.MetricsLogger;
 import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.keyguard.ViewMediatorCallback;
@@ -30,8 +32,6 @@
 import com.android.systemui.assist.AssistManager;
 import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.bubbles.BubbleController;
-import com.android.systemui.car.CarDeviceProvisionedController;
-import com.android.systemui.car.navigationbar.CarNavigationBarController;
 import com.android.systemui.colorextraction.SysuiColorExtractor;
 import com.android.systemui.dagger.qualifiers.UiBackground;
 import com.android.systemui.keyguard.DismissCallbackRegistry;
@@ -75,6 +75,7 @@
 import com.android.systemui.statusbar.phone.HeadsUpManagerPhone;
 import com.android.systemui.statusbar.phone.KeyguardBypassController;
 import com.android.systemui.statusbar.phone.KeyguardDismissUtil;
+import com.android.systemui.statusbar.phone.KeyguardLiftController;
 import com.android.systemui.statusbar.phone.LightBarController;
 import com.android.systemui.statusbar.phone.LightsOutNotifController;
 import com.android.systemui.statusbar.phone.LockscreenLockIconController;
@@ -92,6 +93,7 @@
 import com.android.systemui.statusbar.phone.dagger.StatusBarPhoneDependenciesModule;
 import com.android.systemui.statusbar.policy.BatteryController;
 import com.android.systemui.statusbar.policy.ConfigurationController;
+import com.android.systemui.statusbar.policy.DeviceProvisionedController;
 import com.android.systemui.statusbar.policy.ExtensionController;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 import com.android.systemui.statusbar.policy.NetworkController;
@@ -112,17 +114,17 @@
 import dagger.Provides;
 
 /**
- * Dagger Module providing {@link CarStatusBar}.
+ * Dagger Module providing {@link UnusedStatusBar}.
  */
 @Module(includes = {StatusBarDependenciesModule.class, StatusBarPhoneDependenciesModule.class,
         NotificationRowModule.class})
-public class CarStatusBarModule {
+public interface UnusedStatusBarModule {
     /**
      * Provides our instance of StatusBar which is considered optional.
      */
     @Provides
     @Singleton
-    static CarStatusBar provideStatusBar(
+    static UnusedStatusBar provideStatusBar(
             Context context,
             NotificationsController notificationsController,
             LightBarController lightBarController,
@@ -141,7 +143,7 @@
             RemoteInputQuickSettingsDisabler remoteInputQuickSettingsDisabler,
             NotificationGutsManager notificationGutsManager,
             NotificationLogger notificationLogger,
-            NotificationInterruptStateProvider notificationInterruptionStateProvider,
+            NotificationInterruptStateProvider notificationInterruptStateProvider,
             NotificationViewHierarchyManager notificationViewHierarchyManager,
             KeyguardViewMediator keyguardViewMediator,
             DisplayMetrics displayMetrics,
@@ -161,7 +163,7 @@
             BubbleController bubbleController,
             NotificationGroupManager groupManager,
             VisualStabilityManager visualStabilityManager,
-            CarDeviceProvisionedController carDeviceProvisionedController,
+            DeviceProvisionedController deviceProvisionedController,
             NavigationBarController navigationBarController,
             Lazy<AssistManager> assistManagerLazy,
             ConfigurationController configurationController,
@@ -169,6 +171,7 @@
             LockscreenLockIconController lockscreenLockIconController,
             DozeParameters dozeParameters,
             ScrimController scrimController,
+            @Nullable KeyguardLiftController keyguardLiftController,
             Lazy<LockscreenWallpaper> lockscreenWallpaperLazy,
             Lazy<BiometricUnlockController> biometricUnlockControllerLazy,
             DozeServiceHost dozeServiceHost,
@@ -181,11 +184,11 @@
             Provider<StatusBarComponent.Builder> statusBarComponentBuilder,
             PluginManager pluginManager,
             Optional<Divider> dividerOptional,
-            SuperStatusBarViewFactory superStatusBarViewFactory,
             LightsOutNotifController lightsOutNotifController,
             StatusBarNotificationActivityStarter.Builder
                     statusBarNotificationActivityStarterBuilder,
             ShadeController shadeController,
+            SuperStatusBarViewFactory superStatusBarViewFactory,
             StatusBarKeyguardViewManager statusBarKeyguardViewManager,
             ViewMediatorCallback viewMediatorCallback,
             InitController initController,
@@ -197,11 +200,10 @@
             UserInfoControllerImpl userInfoControllerImpl,
             PhoneStatusBarPolicy phoneStatusBarPolicy,
             KeyguardIndicationController keyguardIndicationController,
+            Lazy<NotificationShadeDepthController> notificationShadeDepthController,
             DismissCallbackRegistry dismissCallbackRegistry,
-            StatusBarTouchableRegionManager statusBarTouchableRegionManager,
-            Lazy<NotificationShadeDepthController> notificationShadeDepthControllerLazy,
-            CarNavigationBarController carNavigationBarController) {
-        return new CarStatusBar(
+            StatusBarTouchableRegionManager statusBarTouchableRegionManager) {
+        return new UnusedStatusBar(
                 context,
                 notificationsController,
                 lightBarController,
@@ -220,7 +222,7 @@
                 remoteInputQuickSettingsDisabler,
                 notificationGutsManager,
                 notificationLogger,
-                notificationInterruptionStateProvider,
+                notificationInterruptStateProvider,
                 notificationViewHierarchyManager,
                 keyguardViewMediator,
                 displayMetrics,
@@ -240,7 +242,7 @@
                 bubbleController,
                 groupManager,
                 visualStabilityManager,
-                carDeviceProvisionedController,
+                deviceProvisionedController,
                 navigationBarController,
                 assistManagerLazy,
                 configurationController,
@@ -248,6 +250,7 @@
                 lockscreenLockIconController,
                 dozeParameters,
                 scrimController,
+                keyguardLiftController,
                 lockscreenWallpaperLazy,
                 biometricUnlockControllerLazy,
                 dozeServiceHost,
@@ -260,10 +263,10 @@
                 statusBarComponentBuilder,
                 pluginManager,
                 dividerOptional,
-                superStatusBarViewFactory,
                 lightsOutNotifController,
                 statusBarNotificationActivityStarterBuilder,
                 shadeController,
+                superStatusBarViewFactory,
                 statusBarKeyguardViewManager,
                 viewMediatorCallback,
                 initController,
@@ -276,8 +279,7 @@
                 phoneStatusBarPolicy,
                 keyguardIndicationController,
                 dismissCallbackRegistry,
-                statusBarTouchableRegionManager,
-                notificationShadeDepthControllerLazy,
-                carNavigationBarController);
+                notificationShadeDepthController,
+                statusBarTouchableRegionManager);
     }
 }
diff --git a/packages/CarSystemUI/src/com/android/systemui/car/userswitcher/FullScreenUserSwitcherViewController.java b/packages/CarSystemUI/src/com/android/systemui/car/userswitcher/FullScreenUserSwitcherViewController.java
index 10b2b97..aac4cfb 100644
--- a/packages/CarSystemUI/src/com/android/systemui/car/userswitcher/FullScreenUserSwitcherViewController.java
+++ b/packages/CarSystemUI/src/com/android/systemui/car/userswitcher/FullScreenUserSwitcherViewController.java
@@ -18,6 +18,8 @@
 
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
+import android.car.Car;
+import android.car.user.CarUserManager;
 import android.content.Context;
 import android.content.res.Resources;
 import android.view.View;
@@ -25,6 +27,7 @@
 import androidx.recyclerview.widget.GridLayoutManager;
 
 import com.android.systemui.R;
+import com.android.systemui.car.CarServiceProvider;
 import com.android.systemui.car.window.OverlayViewController;
 import com.android.systemui.car.window.OverlayViewGlobalStateController;
 import com.android.systemui.dagger.qualifiers.Main;
@@ -39,7 +42,9 @@
 public class FullScreenUserSwitcherViewController extends OverlayViewController {
     private final Context mContext;
     private final Resources mResources;
+    private final CarServiceProvider mCarServiceProvider;
     private final int mShortAnimationDuration;
+    private CarUserManager mCarUserManager;
     private UserGridRecyclerView mUserGridView;
     private UserGridRecyclerView.UserSelectionListener mUserSelectionListener;
 
@@ -47,10 +52,16 @@
     public FullScreenUserSwitcherViewController(
             Context context,
             @Main Resources resources,
+            CarServiceProvider carServiceProvider,
             OverlayViewGlobalStateController overlayViewGlobalStateController) {
         super(R.id.fullscreen_user_switcher_stub, overlayViewGlobalStateController);
         mContext = context;
         mResources = resources;
+        mCarServiceProvider = carServiceProvider;
+        mCarServiceProvider.addListener(car -> {
+            mCarUserManager = (CarUserManager) car.getCarManager(Car.CAR_USER_SERVICE);
+            registerCarUserManagerIfPossible();
+        });
         mShortAnimationDuration = mResources.getInteger(android.R.integer.config_shortAnimTime);
     }
 
@@ -63,6 +74,12 @@
         mUserGridView.setLayoutManager(layoutManager);
         mUserGridView.buildAdapter();
         mUserGridView.setUserSelectionListener(mUserSelectionListener);
+        registerCarUserManagerIfPossible();
+    }
+
+    @Override
+    protected boolean shouldFocusWindow() {
+        return false;
     }
 
     @Override
@@ -91,18 +108,6 @@
     }
 
     /**
-     * Invalidate underlying view.
-     */
-    void invalidate() {
-        if (getLayout() == null) {
-            // layout hasn't been inflated.
-            return;
-        }
-
-        getLayout().invalidate();
-    }
-
-    /**
      * Set {@link UserGridRecyclerView.UserSelectionListener}.
      */
     void setUserGridSelectionListener(
@@ -110,15 +115,9 @@
         mUserSelectionListener = userGridSelectionListener;
     }
 
-    /**
-     * Returns {@code true} when layout is visible.
-     */
-    boolean isVisible() {
-        if (getLayout() == null) {
-            // layout hasn't been inflated.
-            return false;
+    private void registerCarUserManagerIfPossible() {
+        if (mUserGridView != null && mCarUserManager != null) {
+            mUserGridView.setCarUserManager(mCarUserManager);
         }
-
-        return getLayout().getVisibility() == View.VISIBLE;
     }
 }
diff --git a/packages/CarSystemUI/src/com/android/systemui/car/userswitcher/UserGridRecyclerView.java b/packages/CarSystemUI/src/com/android/systemui/car/userswitcher/UserGridRecyclerView.java
index a526e69..a374df6 100644
--- a/packages/CarSystemUI/src/com/android/systemui/car/userswitcher/UserGridRecyclerView.java
+++ b/packages/CarSystemUI/src/com/android/systemui/car/userswitcher/UserGridRecyclerView.java
@@ -24,11 +24,15 @@
 
 import android.annotation.IntDef;
 import android.annotation.Nullable;
+import android.annotation.UserIdInt;
 import android.app.ActivityManager;
 import android.app.AlertDialog;
 import android.app.AlertDialog.Builder;
 import android.app.Dialog;
-import android.car.userlib.CarUserManagerHelper;
+import android.car.user.CarUserManager;
+import android.car.user.UserCreationResult;
+import android.car.user.UserSwitchResult;
+import android.car.userlib.UserHelper;
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.DialogInterface;
@@ -40,7 +44,9 @@
 import android.os.AsyncTask;
 import android.os.UserHandle;
 import android.os.UserManager;
+import android.sysprop.CarProperties;
 import android.util.AttributeSet;
+import android.util.Log;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
@@ -54,6 +60,7 @@
 import androidx.recyclerview.widget.GridLayoutManager;
 import androidx.recyclerview.widget.RecyclerView;
 
+import com.android.internal.infra.AndroidFuture;
 import com.android.internal.util.UserIcons;
 import com.android.systemui.R;
 
@@ -61,6 +68,7 @@
 import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
 import java.util.List;
+import java.util.concurrent.TimeUnit;
 import java.util.stream.Collectors;
 
 /**
@@ -68,9 +76,12 @@
  * One of the uses of this is for the lock screen in auto.
  */
 public class UserGridRecyclerView extends RecyclerView {
+    private static final String TAG = UserGridRecyclerView.class.getSimpleName();
+    private static final int TIMEOUT_MS = CarProperties.user_hal_timeout().orElse(5_000) + 500;
+
     private UserSelectionListener mUserSelectionListener;
     private UserAdapter mAdapter;
-    private CarUserManagerHelper mCarUserManagerHelper;
+    private CarUserManager mCarUserManager;
     private UserManager mUserManager;
     private Context mContext;
     private UserIconProvider mUserIconProvider;
@@ -85,7 +96,6 @@
     public UserGridRecyclerView(Context context, AttributeSet attrs) {
         super(context, attrs);
         mContext = context;
-        mCarUserManagerHelper = new CarUserManagerHelper(mContext);
         mUserManager = UserManager.get(mContext);
         mUserIconProvider = new UserIconProvider();
 
@@ -184,6 +194,11 @@
         mUserSelectionListener = userSelectionListener;
     }
 
+    /** Sets a {@link CarUserManager}. */
+    public void setCarUserManager(CarUserManager carUserManager) {
+        mCarUserManager = carUserManager;
+    }
+
     private void onUsersUpdate() {
         mAdapter.clearUsers();
         mAdapter.updateUsers(createUserRecords(getUsersForUserGrid()));
@@ -273,7 +288,9 @@
                         notifyUserSelected(userRecord);
                         UserInfo guest = createNewOrFindExistingGuest(mContext);
                         if (guest != null) {
-                            mCarUserManagerHelper.switchToUser(guest);
+                            if (!switchUser(guest.id)) {
+                                Log.e(TAG, "Failed to switch to guest user: " + guest.id);
+                            }
                         }
                         break;
                     case UserRecord.ADD_USER:
@@ -289,7 +306,9 @@
                         // If the user doesn't want to be a guest or add a user, switch to the user
                         // selected
                         notifyUserSelected(userRecord);
-                        mCarUserManagerHelper.switchToUser(userRecord.mInfo);
+                        if (!switchUser(userRecord.mInfo.id)) {
+                            Log.e(TAG, "Failed to switch users: " + userRecord.mInfo.id);
+                        }
                 }
             });
 
@@ -430,8 +449,9 @@
          */
         @Nullable
         public UserInfo createNewOrFindExistingGuest(Context context) {
+            AndroidFuture<UserCreationResult> future = mCarUserManager.createGuest(mGuestName);
             // CreateGuest will return null if a guest already exists.
-            UserInfo newGuest = mUserManager.createGuest(context, mGuestName);
+            UserInfo newGuest = getUserInfo(future);
             if (newGuest != null) {
                 new UserIconProvider().assignDefaultIcon(
                         mUserManager, context.getResources(), newGuest);
@@ -444,7 +464,6 @@
         @Override
         public void onClick(DialogInterface dialog, int which) {
             if (which == BUTTON_POSITIVE) {
-                notifyUserSelected(mAddUserRecord);
                 new AddNewUserTask().execute(mNewUserName);
             } else if (which == BUTTON_NEGATIVE) {
                 // Enable the add button only if cancel
@@ -462,11 +481,77 @@
             }
         }
 
+        @Nullable
+        private UserInfo getUserInfo(AndroidFuture<UserCreationResult> future) {
+            UserCreationResult userCreationResult;
+            try {
+                userCreationResult = future.get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
+            } catch (Exception e) {
+                Log.w(TAG, "Could not create user.", e);
+                return null;
+            }
+
+            if (userCreationResult == null) {
+                Log.w(TAG, "Timed out while creating user: " + TIMEOUT_MS + "ms");
+                return null;
+            }
+            if (!userCreationResult.isSuccess() || userCreationResult.getUser() == null) {
+                Log.w(TAG, "Could not create user: " + userCreationResult);
+                return null;
+            }
+
+            return userCreationResult.getUser();
+        }
+
+        private boolean switchUser(@UserIdInt int userId) {
+            AndroidFuture<UserSwitchResult> userSwitchResultFuture =
+                    mCarUserManager.switchUser(userId);
+            UserSwitchResult userSwitchResult;
+            try {
+                userSwitchResult = userSwitchResultFuture.get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
+            } catch (Exception e) {
+                Log.w(TAG, "Could not switch user.", e);
+                return false;
+            }
+
+            if (userSwitchResult == null) {
+                Log.w(TAG, "Timed out while switching user: " + TIMEOUT_MS + "ms");
+                return false;
+            }
+            if (!userSwitchResult.isSuccess()) {
+                Log.w(TAG, "Could not switch user: " + userSwitchResult);
+                return false;
+            }
+
+            return true;
+        }
+
+        // TODO(b/161539497): Replace AsyncTask with standard {@link java.util.concurrent} code.
         private class AddNewUserTask extends AsyncTask<String, Void, UserInfo> {
 
             @Override
             protected UserInfo doInBackground(String... userNames) {
-                return mCarUserManagerHelper.createNewNonAdminUser(userNames[0]);
+                AndroidFuture<UserCreationResult> future = mCarUserManager.createUser(userNames[0],
+                        /* flags= */ 0);
+                try {
+                    UserInfo user = getUserInfo(future);
+                    if (user != null) {
+                        UserHelper.setDefaultNonAdminRestrictions(mContext, user,
+                                /* enable= */ true);
+                        UserHelper.assignDefaultIcon(mContext, user);
+                        mAddUserRecord = new UserRecord(user, UserRecord.ADD_USER);
+                        return user;
+                    } else {
+                        Log.e(TAG, "Failed to create user in the background");
+                        return user;
+                    }
+                } catch (Exception e) {
+                    if (e instanceof InterruptedException) {
+                        Thread.currentThread().interrupt();
+                    }
+                    Log.e(TAG, "Error creating new user: ", e);
+                }
+                return null;
             }
 
             @Override
@@ -476,7 +561,11 @@
             @Override
             protected void onPostExecute(UserInfo user) {
                 if (user != null) {
-                    mCarUserManagerHelper.switchToUser(user);
+                    notifyUserSelected(mAddUserRecord);
+                    mAddUserView.setEnabled(true);
+                    if (!switchUser(user.id)) {
+                        Log.e(TAG, "Failed to switch to new user: " + user.id);
+                    }
                 }
                 if (mAddUserView != null) {
                     mAddUserView.setEnabled(true);
diff --git a/packages/CarSystemUI/src/com/android/systemui/car/userswitcher/UserSwitchTransitionViewController.java b/packages/CarSystemUI/src/com/android/systemui/car/userswitcher/UserSwitchTransitionViewController.java
index 45f3d34..0d77c13 100644
--- a/packages/CarSystemUI/src/com/android/systemui/car/userswitcher/UserSwitchTransitionViewController.java
+++ b/packages/CarSystemUI/src/com/android/systemui/car/userswitcher/UserSwitchTransitionViewController.java
@@ -91,6 +91,11 @@
                 R.integer.config_userSwitchTransitionViewShownTimeoutMs);
     }
 
+    @Override
+    protected int getInsetTypesToFit() {
+        return 0;
+    }
+
     /**
      * Makes the user switch transition view appear and draws the content inside of it if a user
      * that is different from the previous user is provided and if the dialog is not already
diff --git a/packages/CarSystemUI/src/com/android/systemui/car/userswitcher/UserSwitchTransitionViewMediator.java b/packages/CarSystemUI/src/com/android/systemui/car/userswitcher/UserSwitchTransitionViewMediator.java
index aea6914..7db2823 100644
--- a/packages/CarSystemUI/src/com/android/systemui/car/userswitcher/UserSwitchTransitionViewMediator.java
+++ b/packages/CarSystemUI/src/com/android/systemui/car/userswitcher/UserSwitchTransitionViewMediator.java
@@ -16,12 +16,12 @@
 
 package com.android.systemui.car.userswitcher;
 
-import android.app.ActivityManager;
 import android.car.Car;
 import android.car.user.CarUserManager;
 import android.util.Log;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.systemui.car.CarDeviceProvisionedController;
 import com.android.systemui.car.CarServiceProvider;
 import com.android.systemui.car.window.OverlayViewMediator;
 
@@ -36,13 +36,16 @@
     private static final String TAG = "UserSwitchTransitionViewMediator";
 
     private final CarServiceProvider mCarServiceProvider;
+    private final CarDeviceProvisionedController mCarDeviceProvisionedController;
     private final UserSwitchTransitionViewController mUserSwitchTransitionViewController;
 
     @Inject
     public UserSwitchTransitionViewMediator(
             CarServiceProvider carServiceProvider,
+            CarDeviceProvisionedController carDeviceProvisionedController,
             UserSwitchTransitionViewController userSwitchTransitionViewController) {
         mCarServiceProvider = carServiceProvider;
+        mCarDeviceProvisionedController = carDeviceProvisionedController;
         mUserSwitchTransitionViewController = userSwitchTransitionViewController;
     }
 
@@ -74,7 +77,7 @@
     @VisibleForTesting
     void handleUserLifecycleEvent(CarUserManager.UserLifecycleEvent event) {
         if (event.getEventType() == CarUserManager.USER_LIFECYCLE_EVENT_TYPE_STARTING
-                && ActivityManager.getCurrentUser() == event.getUserId()) {
+                && mCarDeviceProvisionedController.getCurrentUser() == event.getUserId()) {
             mUserSwitchTransitionViewController.handleShow(event.getUserId());
         }
 
diff --git a/packages/CarSystemUI/src/com/android/systemui/car/window/OverlayPanelViewController.java b/packages/CarSystemUI/src/com/android/systemui/car/window/OverlayPanelViewController.java
index 45808a8..3c9879c 100644
--- a/packages/CarSystemUI/src/com/android/systemui/car/window/OverlayPanelViewController.java
+++ b/packages/CarSystemUI/src/com/android/systemui/car/window/OverlayPanelViewController.java
@@ -191,6 +191,38 @@
         }
     }
 
+    /** Checks if a {@link MotionEvent} is an action to open the panel.
+     * @param e {@link MotionEvent} to check.
+     * @return true only if opening action.
+     */
+    protected boolean isOpeningAction(MotionEvent e) {
+        if (mAnimateDirection == POSITIVE_DIRECTION) {
+            return e.getActionMasked() == MotionEvent.ACTION_DOWN;
+        }
+
+        if (mAnimateDirection == NEGATIVE_DIRECTION) {
+            return e.getActionMasked() == MotionEvent.ACTION_UP;
+        }
+
+        return false;
+    }
+
+    /** Checks if a {@link MotionEvent} is an action to close the panel.
+     * @param e {@link MotionEvent} to check.
+     * @return true only if closing action.
+     */
+    protected boolean isClosingAction(MotionEvent e) {
+        if (mAnimateDirection == POSITIVE_DIRECTION) {
+            return e.getActionMasked() == MotionEvent.ACTION_UP;
+        }
+
+        if (mAnimateDirection == NEGATIVE_DIRECTION) {
+            return e.getActionMasked() == MotionEvent.ACTION_DOWN;
+        }
+
+        return false;
+    }
+
     /* ***************************************************************************************** *
      * Panel Animation
      * ***************************************************************************************** */
@@ -206,7 +238,6 @@
         }
 
         onAnimateCollapsePanel();
-        getOverlayViewGlobalStateController().setWindowFocusable(false);
         animatePanel(mClosingVelocity, /* isClosing= */ true);
     }
 
@@ -243,8 +274,7 @@
      * Depending on certain conditions, determines whether to fully expand or collapse the panel.
      */
     protected void maybeCompleteAnimation(MotionEvent event) {
-        if (event.getActionMasked() == MotionEvent.ACTION_UP
-                && isPanelVisible()) {
+        if (isClosingAction(event) && isPanelVisible()) {
             if (mSettleClosePercentage < mPercentageFromEndingEdge) {
                 animatePanel(DEFAULT_FLING_VELOCITY, false);
             } else {
@@ -266,14 +296,17 @@
             float from = getCurrentStartPosition(rect);
             if (from != to) {
                 animate(from, to, velocity, isClosing);
-                return;
             }
+
+            // If we swipe down the notification panel all the way to the bottom of the screen
+            // (i.e. from == to), then we have finished animating the panel.
+            return;
         }
 
         // We will only be here if the shade is being opened programmatically or via button when
         // height of the layout was not calculated.
-        ViewTreeObserver notificationTreeObserver = getLayout().getViewTreeObserver();
-        notificationTreeObserver.addOnGlobalLayoutListener(
+        ViewTreeObserver panelTreeObserver = getLayout().getViewTreeObserver();
+        panelTreeObserver.addOnGlobalLayoutListener(
                 new ViewTreeObserver.OnGlobalLayoutListener() {
                     @Override
                     public void onGlobalLayout() {
@@ -381,7 +414,6 @@
             getOverlayViewGlobalStateController().hideView(/* panelViewController= */ this);
         }
         getLayout().setVisibility(visible ? View.VISIBLE : View.INVISIBLE);
-        getOverlayViewGlobalStateController().setWindowFocusable(visible);
     }
 
     /* ***************************************************************************************** *
@@ -476,6 +508,11 @@
         return mIsTracking;
     }
 
+    /** Sets whether the panel is currently tracking or not. */
+    protected final void setIsTracking(boolean isTracking) {
+        mIsTracking = isTracking;
+    }
+
     /** Returns {@code true} if the panel is currently animating. */
     protected final boolean isAnimating() {
         return mIsAnimating;
@@ -514,7 +551,7 @@
             }
             setPanelVisible(true);
 
-            // clips the view for the notification shade when the user scrolls to open.
+            // clips the view for the panel when the user scrolls to open.
             setViewClipBounds((int) event2.getRawY());
 
             // Initially the scroll starts with height being zero. This checks protects from divide
@@ -569,11 +606,11 @@
                 boolean isInClosingDirection = mAnimateDirection * distanceY > 0;
 
                 // This check is to figure out if onScroll was called while swiping the card at
-                // bottom of the list. At that time we should not allow notification shade to
+                // bottom of the panel. At that time we should not allow panel to
                 // close. We are also checking for the upwards swipe gesture here because it is
-                // possible if a user is closing the notification shade and while swiping starts
+                // possible if a user is closing the panel and while swiping starts
                 // to open again but does not fling. At that time we should allow the
-                // notification shade to close fully or else it would stuck in between.
+                // panel to close fully or else it would stuck in between.
                 if (Math.abs(getLayout().getHeight() - y)
                         > SWIPE_DOWN_MIN_DISTANCE && isInClosingDirection) {
                     setViewClipBounds((int) y);
diff --git a/packages/CarSystemUI/src/com/android/systemui/car/window/OverlayViewController.java b/packages/CarSystemUI/src/com/android/systemui/car/window/OverlayViewController.java
index 3969f92..8adc1ad 100644
--- a/packages/CarSystemUI/src/com/android/systemui/car/window/OverlayViewController.java
+++ b/packages/CarSystemUI/src/com/android/systemui/car/window/OverlayViewController.java
@@ -16,9 +16,12 @@
 
 package com.android.systemui.car.window;
 
+import static android.view.WindowInsets.Type.statusBars;
+
 import android.view.View;
 import android.view.ViewGroup;
 import android.view.ViewStub;
+import android.view.WindowInsets;
 
 /**
  * Owns a {@link View} that is present in SystemUIOverlayWindow.
@@ -133,9 +136,18 @@
     }
 
     /**
-     * Returns {@code true} if navigation bar should be displayed over this view.
+     * Returns {@code true} if navigation bar insets should be displayed over this view. Has no
+     * effect if {@link #shouldFocusWindow} returns {@code false}.
      */
-    protected boolean shouldShowNavigationBar() {
+    protected boolean shouldShowNavigationBarInsets() {
+        return false;
+    }
+
+    /**
+     * Returns {@code true} if status bar insets should be displayed over this view. Has no
+     * effect if {@link #shouldFocusWindow} returns {@code false}.
+     */
+    protected boolean shouldShowStatusBarInsets() {
         return false;
     }
 
@@ -145,4 +157,22 @@
     protected boolean shouldShowWhenOccluded() {
         return false;
     }
+
+    /**
+     * Returns {@code true} if the window should be focued when this view is visible. Note that
+     * returning {@code false} here means that {@link #shouldShowStatusBarInsets} and
+     * {@link #shouldShowNavigationBarInsets} will have no effect.
+     */
+    protected boolean shouldFocusWindow() {
+        return true;
+    }
+
+    /**
+     * Returns the insets types to fit to the sysui overlay window when this
+     * {@link OverlayViewController} is in the foreground.
+     */
+    @WindowInsets.Type.InsetsType
+    protected int getInsetTypesToFit() {
+        return statusBars();
+    }
 }
diff --git a/packages/CarSystemUI/src/com/android/systemui/car/window/OverlayViewGlobalStateController.java b/packages/CarSystemUI/src/com/android/systemui/car/window/OverlayViewGlobalStateController.java
index 8e94109..55f0975 100644
--- a/packages/CarSystemUI/src/com/android/systemui/car/window/OverlayViewGlobalStateController.java
+++ b/packages/CarSystemUI/src/com/android/systemui/car/window/OverlayViewGlobalStateController.java
@@ -16,13 +16,16 @@
 
 package com.android.systemui.car.window;
 
+import static android.view.WindowInsets.Type.navigationBars;
+import static android.view.WindowInsets.Type.statusBars;
+
 import android.annotation.Nullable;
 import android.util.Log;
+import android.view.WindowInsets.Type.InsetsType;
+import android.view.WindowInsetsController;
 
 import androidx.annotation.VisibleForTesting;
 
-import com.android.systemui.car.navigationbar.CarNavigationBarController;
-
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Map;
@@ -48,10 +51,7 @@
     private static final String TAG = OverlayViewGlobalStateController.class.getSimpleName();
     private static final int UNKNOWN_Z_ORDER = -1;
     private final SystemUIOverlayWindowController mSystemUIOverlayWindowController;
-    private final CarNavigationBarController mCarNavigationBarController;
-
-    private boolean mIsOccluded;
-
+    private final WindowInsetsController mWindowInsetsController;
     @VisibleForTesting
     Map<OverlayViewController, Integer> mZOrderMap;
     @VisibleForTesting
@@ -60,14 +60,15 @@
     Set<OverlayViewController> mViewsHiddenForOcclusion;
     @VisibleForTesting
     OverlayViewController mHighestZOrder;
+    private boolean mIsOccluded;
 
     @Inject
     public OverlayViewGlobalStateController(
-            CarNavigationBarController carNavigationBarController,
             SystemUIOverlayWindowController systemUIOverlayWindowController) {
         mSystemUIOverlayWindowController = systemUIOverlayWindowController;
         mSystemUIOverlayWindowController.attach();
-        mCarNavigationBarController = carNavigationBarController;
+        mWindowInsetsController =
+                mSystemUIOverlayWindowController.getBaseLayout().getWindowInsetsController();
         mZOrderMap = new HashMap<>();
         mZOrderVisibleSortedMap = new TreeMap<>();
         mViewsHiddenForOcclusion = new HashSet<>();
@@ -115,7 +116,10 @@
         }
 
         updateInternalsWhenShowingView(viewController);
+        refreshInsetTypesToFit();
+        refreshWindowFocus();
         refreshNavigationBarVisibility();
+        refreshStatusBarVisibility();
 
         Log.d(TAG, "Content shown: " + viewController.getClass().getName());
         debugLog();
@@ -185,7 +189,10 @@
 
         mZOrderVisibleSortedMap.remove(mZOrderMap.get(viewController));
         refreshHighestZOrderWhenHidingView(viewController);
+        refreshInsetTypesToFit();
+        refreshWindowFocus();
         refreshNavigationBarVisibility();
+        refreshStatusBarVisibility();
 
         if (mZOrderVisibleSortedMap.isEmpty()) {
             setWindowVisible(false);
@@ -208,10 +215,42 @@
     }
 
     private void refreshNavigationBarVisibility() {
-        if (mZOrderVisibleSortedMap.isEmpty() || mHighestZOrder.shouldShowNavigationBar()) {
-            mCarNavigationBarController.showBars();
+        if (mZOrderVisibleSortedMap.isEmpty()) {
+            mWindowInsetsController.show(navigationBars());
+            return;
+        }
+
+        // Do not hide navigation bar insets if the window is not focusable.
+        if (mHighestZOrder.shouldFocusWindow() && !mHighestZOrder.shouldShowNavigationBarInsets()) {
+            mWindowInsetsController.hide(navigationBars());
         } else {
-            mCarNavigationBarController.hideBars();
+            mWindowInsetsController.show(navigationBars());
+        }
+    }
+
+    private void refreshStatusBarVisibility() {
+        if (mZOrderVisibleSortedMap.isEmpty()) {
+            mWindowInsetsController.show(statusBars());
+            return;
+        }
+
+        // Do not hide status bar insets if the window is not focusable.
+        if (mHighestZOrder.shouldFocusWindow() && !mHighestZOrder.shouldShowStatusBarInsets()) {
+            mWindowInsetsController.hide(statusBars());
+        } else {
+            mWindowInsetsController.show(statusBars());
+        }
+    }
+
+    private void refreshWindowFocus() {
+        setWindowFocusable(mHighestZOrder == null ? false : mHighestZOrder.shouldFocusWindow());
+    }
+
+    private void refreshInsetTypesToFit() {
+        if (mZOrderVisibleSortedMap.isEmpty()) {
+            setFitInsetsTypes(statusBars());
+        } else {
+            setFitInsetsTypes(mHighestZOrder.getInsetTypesToFit());
         }
     }
 
@@ -224,6 +263,10 @@
         mSystemUIOverlayWindowController.setWindowVisible(visible);
     }
 
+    private void setFitInsetsTypes(@InsetsType int types) {
+        mSystemUIOverlayWindowController.setFitInsetsTypes(types);
+    }
+
     /**
      * Sets the {@link android.view.WindowManager.LayoutParams#FLAG_ALT_FOCUSABLE_IM} flag of the
      * sysui overlay window.
diff --git a/packages/CarSystemUI/src/com/android/systemui/car/window/SystemUIOverlayWindowController.java b/packages/CarSystemUI/src/com/android/systemui/car/window/SystemUIOverlayWindowController.java
index bcd96f6..c955fab 100644
--- a/packages/CarSystemUI/src/com/android/systemui/car/window/SystemUIOverlayWindowController.java
+++ b/packages/CarSystemUI/src/com/android/systemui/car/window/SystemUIOverlayWindowController.java
@@ -16,6 +16,7 @@
 
 package com.android.systemui.car.window;
 
+import static android.view.WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE;
 import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
 
 import android.content.Context;
@@ -25,6 +26,7 @@
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
+import android.view.WindowInsets;
 import android.view.WindowManager;
 
 import com.android.systemui.R;
@@ -99,17 +101,23 @@
                 PixelFormat.TRANSLUCENT);
         mLp.token = new Binder();
         mLp.gravity = Gravity.TOP;
-        mLp.setFitInsetsTypes(/* types= */ 0);
         mLp.softInputMode = WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE;
         mLp.setTitle("SystemUIOverlayWindow");
         mLp.packageName = mContext.getPackageName();
         mLp.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
+        mLp.insetsFlags.behavior = BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE;
 
         mWindowManager.addView(mBaseLayout, mLp);
         mLpChanged.copyFrom(mLp);
         setWindowVisible(false);
     }
 
+    /** Sets the types of insets to fit. Note: This should be rarely used. */
+    public void setFitInsetsTypes(@WindowInsets.Type.InsetsType int types) {
+        mLpChanged.setFitInsetsTypes(types);
+        updateWindow();
+    }
+
     /** Sets the window to the visible state. */
     public void setWindowVisible(boolean visible) {
         mVisible = visible;
@@ -154,6 +162,7 @@
     private void updateWindow() {
         if (mLp != null && mLp.copyFrom(mLpChanged) != 0) {
             if (isAttached()) {
+                mLp.insetsFlags.behavior = BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE;
                 mWindowManager.updateViewLayout(mBaseLayout, mLp);
             }
         }
diff --git a/packages/CarSystemUI/src/com/android/systemui/wm/BarControlPolicy.java b/packages/CarSystemUI/src/com/android/systemui/wm/BarControlPolicy.java
new file mode 100644
index 0000000..5f9665f
--- /dev/null
+++ b/packages/CarSystemUI/src/com/android/systemui/wm/BarControlPolicy.java
@@ -0,0 +1,250 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.wm;
+
+import android.car.settings.CarSettings;
+import android.content.Context;
+import android.database.ContentObserver;
+import android.os.Handler;
+import android.os.UserHandle;
+import android.provider.Settings;
+import android.util.ArraySet;
+import android.util.Slog;
+import android.view.WindowInsets;
+
+import androidx.annotation.VisibleForTesting;
+
+import java.io.PrintWriter;
+import java.io.StringWriter;
+
+/**
+ * Util class to load PolicyControl and allow for querying if a package matches immersive filters.
+ * Similar to {@link com.android.server.wm.PolicyControl}, but separate due to CarSystemUI needing
+ * to set its own policies for system bar visibilities.
+ *
+ * This forces immersive mode behavior for one or both system bars (based on a package
+ * list).
+ *
+ * Control by setting {@link Settings.Global#POLICY_CONTROL_AUTO} to one or more name-value pairs.
+ * e.g.
+ *   to force immersive mode everywhere:
+ *     "immersive.full=*"
+ *   to force hide status bars for com.package1 but not com.package2:
+ *     "immersive.status=com.package1,-com.package2"
+ *
+ * Separate multiple name-value pairs with ':'
+ *   e.g. "immersive.status=com.package:immersive.navigation=*"
+ */
+public class BarControlPolicy {
+
+    private static final String TAG = "BarControlPolicy";
+    private static final boolean DEBUG = false;
+
+    private static final String NAME_IMMERSIVE_FULL = "immersive.full";
+    private static final String NAME_IMMERSIVE_STATUS = "immersive.status";
+    private static final String NAME_IMMERSIVE_NAVIGATION = "immersive.navigation";
+
+    @VisibleForTesting
+    static String sSettingValue;
+    @VisibleForTesting
+    static Filter sImmersiveStatusFilter;
+    private static Filter sImmersiveNavigationFilter;
+
+    /** Loads values from the POLICY_CONTROL setting to set filters. */
+    static boolean reloadFromSetting(Context context) {
+        if (DEBUG) Slog.d(TAG, "reloadFromSetting()");
+        String value = null;
+        try {
+            value = Settings.Global.getStringForUser(context.getContentResolver(),
+                    CarSettings.Global.SYSTEM_BAR_VISIBILITY_OVERRIDE,
+                    UserHandle.USER_CURRENT);
+            if (sSettingValue == value || sSettingValue != null && sSettingValue.equals(value)) {
+                return false;
+            }
+            setFilters(value);
+            sSettingValue = value;
+        } catch (Throwable t) {
+            Slog.w(TAG, "Error loading policy control, value=" + value, t);
+            return false;
+        }
+        return true;
+    }
+
+    /** Used in testing to reset BarControlPolicy. */
+    @VisibleForTesting
+    static void reset() {
+        sSettingValue = null;
+        sImmersiveStatusFilter = null;
+        sImmersiveNavigationFilter = null;
+    }
+
+    /**
+     * Registers a content observer to listen to updates to the SYSTEM_BAR_VISIBILITY_OVERRIDE flag.
+     */
+    static void registerContentObserver(Context context, Handler handler, FilterListener listener) {
+        context.getContentResolver().registerContentObserver(
+                Settings.Global.getUriFor(CarSettings.Global.SYSTEM_BAR_VISIBILITY_OVERRIDE), false,
+                new ContentObserver(handler) {
+                    @Override
+                    public void onChange(boolean selfChange) {
+                        if (reloadFromSetting(context)) {
+                            listener.onFilterUpdated();
+                        }
+                    }
+                }, UserHandle.USER_ALL);
+    }
+
+    /**
+     * Returns bar visibilities based on POLICY_CONTROL_AUTO filters and window policies.
+     * @return int[], where the first value is the inset types that should be shown, and the second
+     *         is the inset types that should be hidden.
+     */
+    @WindowInsets.Type.InsetsType
+    static int[] getBarVisibilities(String packageName) {
+        int hideTypes = 0;
+        int showTypes = 0;
+        if (matchesStatusFilter(packageName)) {
+            hideTypes |= WindowInsets.Type.statusBars();
+        } else {
+            showTypes |= WindowInsets.Type.statusBars();
+        }
+        if (matchesNavigationFilter(packageName)) {
+            hideTypes |= WindowInsets.Type.navigationBars();
+        } else {
+            showTypes |= WindowInsets.Type.navigationBars();
+        }
+
+        return new int[] {showTypes, hideTypes};
+    }
+
+    private static boolean matchesStatusFilter(String packageName) {
+        return sImmersiveStatusFilter != null && sImmersiveStatusFilter.matches(packageName);
+    }
+
+    private static boolean matchesNavigationFilter(String packageName) {
+        return sImmersiveNavigationFilter != null
+                && sImmersiveNavigationFilter.matches(packageName);
+    }
+
+    private static void setFilters(String value) {
+        if (DEBUG) Slog.d(TAG, "setFilters: " + value);
+        sImmersiveStatusFilter = null;
+        sImmersiveNavigationFilter = null;
+        if (value != null) {
+            String[] nvps = value.split(":");
+            for (String nvp : nvps) {
+                int i = nvp.indexOf('=');
+                if (i == -1) continue;
+                String n = nvp.substring(0, i);
+                String v = nvp.substring(i + 1);
+                if (n.equals(NAME_IMMERSIVE_FULL)) {
+                    Filter f = Filter.parse(v);
+                    sImmersiveStatusFilter = sImmersiveNavigationFilter = f;
+                } else if (n.equals(NAME_IMMERSIVE_STATUS)) {
+                    Filter f = Filter.parse(v);
+                    sImmersiveStatusFilter = f;
+                } else if (n.equals(NAME_IMMERSIVE_NAVIGATION)) {
+                    Filter f = Filter.parse(v);
+                    sImmersiveNavigationFilter = f;
+                }
+            }
+        }
+        if (DEBUG) {
+            Slog.d(TAG, "immersiveStatusFilter: " + sImmersiveStatusFilter);
+            Slog.d(TAG, "immersiveNavigationFilter: " + sImmersiveNavigationFilter);
+        }
+    }
+
+    private static class Filter {
+        private static final String ALL = "*";
+
+        private final ArraySet<String> mWhitelist;
+        private final ArraySet<String> mBlacklist;
+
+        private Filter(ArraySet<String> whitelist, ArraySet<String> blacklist) {
+            mWhitelist = whitelist;
+            mBlacklist = blacklist;
+        }
+
+        boolean matches(String packageName) {
+            if (packageName == null) return false;
+            if (onBlacklist(packageName)) return false;
+            return onWhitelist(packageName);
+        }
+
+        private boolean onBlacklist(String packageName) {
+            return mBlacklist.contains(packageName) || mBlacklist.contains(ALL);
+        }
+
+        private boolean onWhitelist(String packageName) {
+            return mWhitelist.contains(ALL) || mWhitelist.contains(packageName);
+        }
+
+        void dump(PrintWriter pw) {
+            pw.print("Filter[");
+            dump("whitelist", mWhitelist, pw); pw.print(',');
+            dump("blacklist", mBlacklist, pw); pw.print(']');
+        }
+
+        private void dump(String name, ArraySet<String> set, PrintWriter pw) {
+            pw.print(name); pw.print("=(");
+            int n = set.size();
+            for (int i = 0; i < n; i++) {
+                if (i > 0) pw.print(',');
+                pw.print(set.valueAt(i));
+            }
+            pw.print(')');
+        }
+
+        @Override
+        public String toString() {
+            StringWriter sw = new StringWriter();
+            dump(new PrintWriter(sw, true));
+            return sw.toString();
+        }
+
+        // value = comma-delimited list of tokens, where token = (package name|*)
+        // e.g. "com.package1", or "com.android.systemui, com.android.keyguard" or "*"
+        static Filter parse(String value) {
+            if (value == null) return null;
+            ArraySet<String> whitelist = new ArraySet<String>();
+            ArraySet<String> blacklist = new ArraySet<String>();
+            for (String token : value.split(",")) {
+                token = token.trim();
+                if (token.startsWith("-") && token.length() > 1) {
+                    token = token.substring(1);
+                    blacklist.add(token);
+                } else {
+                    whitelist.add(token);
+                }
+            }
+            return new Filter(whitelist, blacklist);
+        }
+    }
+
+    /**
+     * Interface to listen for updates to the filter triggered by the content observer listening to
+     * the SYSTEM_BAR_VISIBILITY_OVERRIDE flag.
+     */
+    interface FilterListener {
+
+        /** Callback triggered when the content observer updates the filter. */
+        void onFilterUpdated();
+    }
+
+    private BarControlPolicy() {}
+}
diff --git a/packages/CarSystemUI/src/com/android/systemui/wm/DisplaySystemBarsController.java b/packages/CarSystemUI/src/com/android/systemui/wm/DisplaySystemBarsController.java
new file mode 100644
index 0000000..c9ec34f
--- /dev/null
+++ b/packages/CarSystemUI/src/com/android/systemui/wm/DisplaySystemBarsController.java
@@ -0,0 +1,182 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.wm;
+
+import android.os.Handler;
+import android.os.RemoteException;
+import android.util.ArraySet;
+import android.util.Slog;
+import android.util.SparseArray;
+import android.view.IDisplayWindowInsetsController;
+import android.view.InsetsController;
+import android.view.InsetsSourceControl;
+import android.view.InsetsState;
+import android.view.WindowInsets;
+
+import androidx.annotation.VisibleForTesting;
+
+import com.android.systemui.TransactionPool;
+import com.android.systemui.dagger.qualifiers.Main;
+
+import java.util.Objects;
+
+import javax.inject.Inject;
+import javax.inject.Singleton;
+
+/**
+ * Controller that maps between displays and {@link IDisplayWindowInsetsController} in order to
+ * give system bar control to SystemUI.
+ * {@link R.bool#config_remoteInsetsControllerControlsSystemBars} determines whether this controller
+ * takes control or not.
+ */
+@Singleton
+public class DisplaySystemBarsController extends DisplayImeController {
+
+    private static final String TAG = "DisplaySystemBarsController";
+
+    private SparseArray<PerDisplay> mPerDisplaySparseArray;
+
+    @Inject
+    public DisplaySystemBarsController(
+            SystemWindows syswin,
+            DisplayController displayController,
+            @Main Handler mainHandler,
+            TransactionPool transactionPool) {
+        super(syswin, displayController, mainHandler, transactionPool);
+    }
+
+    @Override
+    public void onDisplayAdded(int displayId) {
+        PerDisplay pd = new PerDisplay(displayId);
+        try {
+            mSystemWindows.mWmService.setDisplayWindowInsetsController(displayId, pd);
+        } catch (RemoteException e) {
+            Slog.w(TAG, "Unable to set insets controller on display " + displayId);
+        }
+        // Lazy loading policy control filters instead of during boot.
+        if (mPerDisplaySparseArray == null) {
+            mPerDisplaySparseArray = new SparseArray<>();
+            BarControlPolicy.reloadFromSetting(mSystemWindows.mContext);
+            BarControlPolicy.registerContentObserver(mSystemWindows.mContext, mHandler, () -> {
+                int size = mPerDisplaySparseArray.size();
+                for (int i = 0; i < size; i++) {
+                    mPerDisplaySparseArray.valueAt(i).modifyDisplayWindowInsets();
+                }
+            });
+        }
+        mPerDisplaySparseArray.put(displayId, pd);
+    }
+
+    @Override
+    public void onDisplayRemoved(int displayId) {
+        try {
+            mSystemWindows.mWmService.setDisplayWindowInsetsController(displayId, null);
+        } catch (RemoteException e) {
+            Slog.w(TAG, "Unable to remove insets controller on display " + displayId);
+        }
+        mPerDisplaySparseArray.remove(displayId);
+    }
+
+    @VisibleForTesting
+    class PerDisplay extends DisplayImeController.PerDisplay {
+
+        int mDisplayId;
+        InsetsController mInsetsController;
+        InsetsState mInsetsState = new InsetsState();
+        String mPackageName;
+
+        PerDisplay(int displayId) {
+            super(displayId,
+                    mSystemWindows.mDisplayController.getDisplayLayout(displayId).rotation());
+            mDisplayId = displayId;
+            mInsetsController = new InsetsController(
+                    new DisplaySystemBarsInsetsControllerHost(mHandler, this));
+        }
+
+        @Override
+        public void insetsChanged(InsetsState insetsState) {
+            super.insetsChanged(insetsState);
+            if (mInsetsState.equals(insetsState)) {
+                return;
+            }
+            mInsetsState.set(insetsState, true /* copySources */);
+            mInsetsController.onStateChanged(insetsState);
+            if (mPackageName != null) {
+                modifyDisplayWindowInsets();
+            }
+        }
+
+        @Override
+        public void insetsControlChanged(InsetsState insetsState,
+                InsetsSourceControl[] activeControls) {
+            super.insetsControlChanged(insetsState, activeControls);
+            mInsetsController.onControlsChanged(activeControls);
+        }
+
+        @Override
+        public void hideInsets(@WindowInsets.Type.InsetsType int types, boolean fromIme) {
+            if ((types & WindowInsets.Type.ime()) == 0) {
+                mInsetsController.hide(types);
+            } else {
+                super.hideInsets(types, fromIme);
+            }
+
+        }
+
+        @Override
+        public void showInsets(@WindowInsets.Type.InsetsType int types, boolean fromIme) {
+            if ((types & WindowInsets.Type.ime()) == 0) {
+                mInsetsController.show(types);
+            } else {
+                super.showInsets(types, fromIme);
+            }
+
+        }
+
+        @Override
+        public void topFocusedWindowChanged(String packageName) {
+            if (Objects.equals(mPackageName, packageName)) {
+                return;
+            }
+            mPackageName = packageName;
+            modifyDisplayWindowInsets();
+        }
+
+        private void modifyDisplayWindowInsets() {
+            if (mPackageName == null) {
+                return;
+            }
+            int[] barVisibilities = BarControlPolicy.getBarVisibilities(mPackageName);
+            updateInsetsState(barVisibilities[0], /* visible= */ true);
+            updateInsetsState(barVisibilities[1], /* visible= */ false);
+            showInsets(barVisibilities[0], /* fromIme= */ false);
+            hideInsets(barVisibilities[1], /* fromIme= */ false);
+            try {
+                mSystemWindows.mWmService.modifyDisplayWindowInsets(mDisplayId, mInsetsState);
+            } catch (RemoteException e) {
+                Slog.w(TAG, "Unable to update window manager service.");
+            }
+        }
+
+        private void updateInsetsState(@WindowInsets.Type.InsetsType int types, boolean visible) {
+            ArraySet<Integer> internalTypes = InsetsState.toInternalType(types);
+            for (int i = internalTypes.size() - 1; i >= 0; i--) {
+                mInsetsState.getSource(internalTypes.valueAt(i)).setVisible(visible);
+            }
+        }
+    }
+}
diff --git a/packages/CarSystemUI/src/com/android/systemui/wm/DisplaySystemBarsInsetsControllerHost.java b/packages/CarSystemUI/src/com/android/systemui/wm/DisplaySystemBarsInsetsControllerHost.java
new file mode 100644
index 0000000..2f8da44
--- /dev/null
+++ b/packages/CarSystemUI/src/com/android/systemui/wm/DisplaySystemBarsInsetsControllerHost.java
@@ -0,0 +1,173 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.wm;
+
+import android.annotation.NonNull;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.Log;
+import android.view.IDisplayWindowInsetsController;
+import android.view.InsetsController;
+import android.view.InsetsState;
+import android.view.SurfaceControl;
+import android.view.SyncRtSurfaceTransactionApplier;
+import android.view.WindowInsets;
+import android.view.WindowInsetsAnimation;
+import android.view.WindowInsetsController;
+import android.view.inputmethod.InputMethodManager;
+
+import java.util.List;
+
+/**
+ * Implements {@link InsetsController.Host} for usage by
+ * {@link DisplaySystemBarsController.PerDisplay} instances in {@link DisplaySystemBarsController}.
+ * @hide
+ */
+public class DisplaySystemBarsInsetsControllerHost implements InsetsController.Host {
+
+    private static final String TAG = DisplaySystemBarsInsetsControllerHost.class.getSimpleName();
+
+    private final Handler mHandler;
+    private final IDisplayWindowInsetsController mController;
+    private final float[] mTmpFloat9 = new float[9];
+
+    public DisplaySystemBarsInsetsControllerHost(
+            Handler handler, IDisplayWindowInsetsController controller) {
+        mHandler = handler;
+        mController = controller;
+    }
+
+    @Override
+    public Handler getHandler() {
+        return mHandler;
+    }
+
+    @Override
+    public void notifyInsetsChanged() {
+        // no-op
+    }
+
+    @Override
+    public void dispatchWindowInsetsAnimationPrepare(@NonNull WindowInsetsAnimation animation) {
+        // no-op
+    }
+
+    @Override
+    public WindowInsetsAnimation.Bounds dispatchWindowInsetsAnimationStart(
+            @NonNull WindowInsetsAnimation animation,
+            @NonNull WindowInsetsAnimation.Bounds bounds) {
+        return null;
+    }
+
+    @Override
+    public WindowInsets dispatchWindowInsetsAnimationProgress(@NonNull WindowInsets insets,
+            @NonNull List<WindowInsetsAnimation> runningAnimations) {
+        return null;
+    }
+
+    @Override
+    public void dispatchWindowInsetsAnimationEnd(@NonNull WindowInsetsAnimation animation) {
+        // no-op
+    }
+
+    @Override
+    public void applySurfaceParams(final SyncRtSurfaceTransactionApplier.SurfaceParams... params) {
+        for (int i = params.length - 1; i >= 0; i--) {
+            SyncRtSurfaceTransactionApplier.applyParams(
+                    new SurfaceControl.Transaction(), params[i], mTmpFloat9);
+        }
+
+    }
+
+    @Override
+    public void updateCompatSysUiVisibility(
+            @InsetsState.InternalInsetsType int type, boolean visible, boolean hasControl) {
+        // no-op
+    }
+
+    @Override
+    public void onInsetsModified(InsetsState insetsState) {
+        try {
+            mController.insetsChanged(insetsState);
+        } catch (RemoteException e) {
+            Log.e(TAG, "Failed to send insets to controller");
+        }
+    }
+
+    @Override
+    public boolean hasAnimationCallbacks() {
+        return false;
+    }
+
+    @Override
+    public void setSystemBarsAppearance(
+            @WindowInsetsController.Appearance int appearance,
+            @WindowInsetsController.Appearance int mask) {
+        // no-op
+    }
+
+    @Override
+    public @WindowInsetsController.Appearance int getSystemBarsAppearance() {
+        return 0;
+    }
+
+    @Override
+    public void setSystemBarsBehavior(@WindowInsetsController.Behavior int behavior) {
+        // no-op
+    }
+
+    @Override
+    public @WindowInsetsController.Behavior int getSystemBarsBehavior() {
+        return 0;
+    }
+
+    @Override
+    public void releaseSurfaceControlFromRt(SurfaceControl surfaceControl) {
+        surfaceControl.release();
+    }
+
+    @Override
+    public void addOnPreDrawRunnable(Runnable r) {
+        mHandler.post(r);
+    }
+
+    @Override
+    public void postInsetsAnimationCallback(Runnable r) {
+        mHandler.post(r);
+    }
+
+    @Override
+    public InputMethodManager getInputMethodManager() {
+        return null;
+    }
+
+    @Override
+    public String getRootViewTitle() {
+        return null;
+    }
+
+    @Override
+    public int dipToPx(int dips) {
+        return 0;
+    }
+
+    @Override
+    public IBinder getWindowToken() {
+        return null;
+    }
+}
diff --git a/packages/CarSystemUI/tests/src/com/android/AAAPlusPlusVerifySysuiRequiredTestPropertiesTest.java b/packages/CarSystemUI/tests/src/com/android/AAAPlusPlusVerifySysuiRequiredTestPropertiesTest.java
index fe59cbf..d769cac 100644
--- a/packages/CarSystemUI/tests/src/com/android/AAAPlusPlusVerifySysuiRequiredTestPropertiesTest.java
+++ b/packages/CarSystemUI/tests/src/com/android/AAAPlusPlusVerifySysuiRequiredTestPropertiesTest.java
@@ -33,6 +33,7 @@
 
 import com.android.systemui.SysuiBaseFragmentTest;
 import com.android.systemui.SysuiTestCase;
+import com.android.systemui.car.CarSystemUiTest;
 
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -55,6 +56,7 @@
  * test suite causes errors, such as the incorrect settings provider being cached.
  * For an example, see {@link com.android.systemui.DependencyTest}.
  */
+@CarSystemUiTest
 @RunWith(AndroidTestingRunner.class)
 @SmallTest
 public class AAAPlusPlusVerifySysuiRequiredTestPropertiesTest extends SysuiTestCase {
diff --git a/packages/CarSystemUI/tests/src/com/android/systemui/car/hvac/HvacControllerTest.java b/packages/CarSystemUI/tests/src/com/android/systemui/car/hvac/HvacControllerTest.java
index 7996170..e179ef1 100644
--- a/packages/CarSystemUI/tests/src/com/android/systemui/car/hvac/HvacControllerTest.java
+++ b/packages/CarSystemUI/tests/src/com/android/systemui/car/hvac/HvacControllerTest.java
@@ -32,6 +32,7 @@
 
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.car.CarServiceProvider;
+import com.android.systemui.car.CarSystemUiTest;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -39,6 +40,7 @@
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
+@CarSystemUiTest
 @RunWith(AndroidTestingRunner.class)
 @TestableLooper.RunWithLooper
 @SmallTest
diff --git a/packages/CarSystemUI/tests/src/com/android/systemui/car/keyguard/CarKeyguardViewControllerTest.java b/packages/CarSystemUI/tests/src/com/android/systemui/car/keyguard/CarKeyguardViewControllerTest.java
index 189e240..62dc236 100644
--- a/packages/CarSystemUI/tests/src/com/android/systemui/car/keyguard/CarKeyguardViewControllerTest.java
+++ b/packages/CarSystemUI/tests/src/com/android/systemui/car/keyguard/CarKeyguardViewControllerTest.java
@@ -41,6 +41,7 @@
 import com.android.systemui.R;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.car.CarServiceProvider;
+import com.android.systemui.car.CarSystemUiTest;
 import com.android.systemui.car.navigationbar.CarNavigationBarController;
 import com.android.systemui.car.window.OverlayViewGlobalStateController;
 import com.android.systemui.keyguard.DismissCallbackRegistry;
@@ -59,6 +60,7 @@
 
 import dagger.Lazy;
 
+@CarSystemUiTest
 @RunWith(AndroidTestingRunner.class)
 @TestableLooper.RunWithLooper
 @SmallTest
diff --git a/packages/CarSystemUI/tests/src/com/android/systemui/car/navigationbar/ButtonRoleHolderControllerTest.java b/packages/CarSystemUI/tests/src/com/android/systemui/car/navigationbar/ButtonRoleHolderControllerTest.java
index a57736b..4b82680 100644
--- a/packages/CarSystemUI/tests/src/com/android/systemui/car/navigationbar/ButtonRoleHolderControllerTest.java
+++ b/packages/CarSystemUI/tests/src/com/android/systemui/car/navigationbar/ButtonRoleHolderControllerTest.java
@@ -39,6 +39,7 @@
 
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.car.CarDeviceProvisionedController;
+import com.android.systemui.car.CarSystemUiTest;
 import com.android.systemui.tests.R;
 
 import org.junit.Before;
@@ -49,6 +50,7 @@
 
 import java.util.List;
 
+@CarSystemUiTest
 @RunWith(AndroidTestingRunner.class)
 @TestableLooper.RunWithLooper
 @SmallTest
diff --git a/packages/CarSystemUI/tests/src/com/android/systemui/car/navigationbar/ButtonSelectionStateControllerTest.java b/packages/CarSystemUI/tests/src/com/android/systemui/car/navigationbar/ButtonSelectionStateControllerTest.java
index 893057e..f623c26 100644
--- a/packages/CarSystemUI/tests/src/com/android/systemui/car/navigationbar/ButtonSelectionStateControllerTest.java
+++ b/packages/CarSystemUI/tests/src/com/android/systemui/car/navigationbar/ButtonSelectionStateControllerTest.java
@@ -28,6 +28,7 @@
 import androidx.test.filters.SmallTest;
 
 import com.android.systemui.SysuiTestCase;
+import com.android.systemui.car.CarSystemUiTest;
 import com.android.systemui.tests.R;
 
 import org.junit.Before;
@@ -38,6 +39,7 @@
 import java.util.ArrayList;
 import java.util.List;
 
+@CarSystemUiTest
 @RunWith(AndroidTestingRunner.class)
 @TestableLooper.RunWithLooper
 @SmallTest
diff --git a/packages/CarSystemUI/tests/src/com/android/systemui/car/navigationbar/CarNavigationBarControllerTest.java b/packages/CarSystemUI/tests/src/com/android/systemui/car/navigationbar/CarNavigationBarControllerTest.java
index e84e42c..3fd0852 100644
--- a/packages/CarSystemUI/tests/src/com/android/systemui/car/navigationbar/CarNavigationBarControllerTest.java
+++ b/packages/CarSystemUI/tests/src/com/android/systemui/car/navigationbar/CarNavigationBarControllerTest.java
@@ -31,6 +31,7 @@
 
 import com.android.systemui.R;
 import com.android.systemui.SysuiTestCase;
+import com.android.systemui.car.CarSystemUiTest;
 import com.android.systemui.car.hvac.HvacController;
 import com.android.systemui.plugins.DarkIconDispatcher;
 import com.android.systemui.statusbar.phone.StatusBarIconController;
@@ -41,11 +42,16 @@
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
+@CarSystemUiTest
 @RunWith(AndroidTestingRunner.class)
 @TestableLooper.RunWithLooper
 @SmallTest
 public class CarNavigationBarControllerTest extends SysuiTestCase {
 
+    private static final String TOP_NOTIFICATION_PANEL =
+            "com.android.systemui.car.notification.TopNotificationPanelViewMediator";
+    private static final String BOTTOM_NOTIFICATION_PANEL =
+            "com.android.systemui.car.notification.BottomNotificationPanelViewMediator";
     private CarNavigationBarController mCarNavigationBar;
     private NavigationBarViewFactory mNavigationBarViewFactory;
     private TestableResources mTestableResources;
@@ -71,7 +77,8 @@
     private CarNavigationBarController createNavigationBarController() {
         return new CarNavigationBarController(mContext, mNavigationBarViewFactory,
                 mButtonSelectionStateController, () -> mHvacController,
-                mButtonRoleHolderController);
+                mButtonRoleHolderController,
+                new SystemBarConfigs(mTestableResources.getResources()));
     }
 
     @Test
@@ -114,6 +121,11 @@
     @Test
     public void testGetTopWindow_topDisabled_returnsNull() {
         mTestableResources.addOverride(R.bool.config_enableTopNavigationBar, false);
+        mTestableResources.addOverride(R.bool.config_enableBottomNavigationBar, true);
+        // If Top Notification Panel is used but top navigation bar is not enabled, SystemUI is
+        // expected to crash.
+        mTestableResources.addOverride(R.string.config_notificationPanelViewMediator,
+                BOTTOM_NOTIFICATION_PANEL);
         mCarNavigationBar = createNavigationBarController();
 
         ViewGroup window = mCarNavigationBar.getTopWindow();
@@ -145,6 +157,11 @@
     @Test
     public void testGetBottomWindow_bottomDisabled_returnsNull() {
         mTestableResources.addOverride(R.bool.config_enableBottomNavigationBar, false);
+        mTestableResources.addOverride(R.bool.config_enableTopNavigationBar, true);
+        // If Bottom Notification Panel is used but bottom navigation bar is not enabled,
+        // SystemUI is expected to crash.
+        mTestableResources.addOverride(R.string.config_notificationPanelViewMediator,
+                TOP_NOTIFICATION_PANEL);
         mCarNavigationBar = createNavigationBarController();
 
         ViewGroup window = mCarNavigationBar.getBottomWindow();
@@ -234,6 +251,28 @@
     }
 
     @Test
+    public void testSetTopWindowVisibility_setTrue_isVisible() {
+        mTestableResources.addOverride(R.bool.config_enableTopNavigationBar, true);
+        mCarNavigationBar = createNavigationBarController();
+
+        ViewGroup window = mCarNavigationBar.getTopWindow();
+        mCarNavigationBar.setTopWindowVisibility(View.VISIBLE);
+
+        assertThat(window.getVisibility()).isEqualTo(View.VISIBLE);
+    }
+
+    @Test
+    public void testSetTopWindowVisibility_setFalse_isGone() {
+        mTestableResources.addOverride(R.bool.config_enableTopNavigationBar, true);
+        mCarNavigationBar = createNavigationBarController();
+
+        ViewGroup window = mCarNavigationBar.getTopWindow();
+        mCarNavigationBar.setTopWindowVisibility(View.GONE);
+
+        assertThat(window.getVisibility()).isEqualTo(View.GONE);
+    }
+
+    @Test
     public void testSetBottomWindowVisibility_setTrue_isVisible() {
         mTestableResources.addOverride(R.bool.config_enableBottomNavigationBar, true);
         mCarNavigationBar = createNavigationBarController();
diff --git a/packages/CarSystemUI/tests/src/com/android/systemui/car/navigationbar/CarNavigationBarTest.java b/packages/CarSystemUI/tests/src/com/android/systemui/car/navigationbar/CarNavigationBarTest.java
index 0caa86f..2b5af71 100644
--- a/packages/CarSystemUI/tests/src/com/android/systemui/car/navigationbar/CarNavigationBarTest.java
+++ b/packages/CarSystemUI/tests/src/com/android/systemui/car/navigationbar/CarNavigationBarTest.java
@@ -45,6 +45,7 @@
 import com.android.systemui.R;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.car.CarDeviceProvisionedController;
+import com.android.systemui.car.CarSystemUiTest;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.phone.AutoHideController;
 import com.android.systemui.statusbar.phone.LightBarController;
@@ -63,6 +64,7 @@
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
+@CarSystemUiTest
 @RunWith(AndroidTestingRunner.class)
 @TestableLooper.RunWithLooper
 @SmallTest
@@ -140,7 +142,7 @@
                 mWindowManager, mDeviceProvisionedController, new CommandQueue(mContext),
                 mAutoHideController, mButtonSelectionStateListener, mHandler, mUiBgExecutor,
                 mBarService, () -> mKeyguardStateController, () -> mIconPolicy,
-                () -> mIconController);
+                () -> mIconController, new SystemBarConfigs(mTestableResources.getResources()));
     }
 
     @Test
diff --git a/packages/CarSystemUI/tests/src/com/android/systemui/car/navigationbar/CarNavigationBarViewTest.java b/packages/CarSystemUI/tests/src/com/android/systemui/car/navigationbar/CarNavigationBarViewTest.java
index 19e394f..47fd820 100644
--- a/packages/CarSystemUI/tests/src/com/android/systemui/car/navigationbar/CarNavigationBarViewTest.java
+++ b/packages/CarSystemUI/tests/src/com/android/systemui/car/navigationbar/CarNavigationBarViewTest.java
@@ -30,6 +30,7 @@
 
 import com.android.systemui.R;
 import com.android.systemui.SysuiTestCase;
+import com.android.systemui.car.CarSystemUiTest;
 
 import org.junit.After;
 import org.junit.Before;
@@ -38,6 +39,7 @@
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
+@CarSystemUiTest
 @RunWith(AndroidTestingRunner.class)
 @TestableLooper.RunWithLooper
 @SmallTest
diff --git a/packages/CarSystemUI/tests/src/com/android/systemui/car/navigationbar/CarNavigationButtonTest.java b/packages/CarSystemUI/tests/src/com/android/systemui/car/navigationbar/CarNavigationButtonTest.java
index bcaa5e9..173f548 100644
--- a/packages/CarSystemUI/tests/src/com/android/systemui/car/navigationbar/CarNavigationButtonTest.java
+++ b/packages/CarSystemUI/tests/src/com/android/systemui/car/navigationbar/CarNavigationButtonTest.java
@@ -37,6 +37,7 @@
 import androidx.test.filters.SmallTest;
 
 import com.android.systemui.SysuiTestCase;
+import com.android.systemui.car.CarSystemUiTest;
 import com.android.systemui.statusbar.AlphaOptimizedImageView;
 import com.android.systemui.tests.R;
 
@@ -45,6 +46,7 @@
 import org.junit.runner.RunWith;
 import org.mockito.ArgumentMatcher;
 
+@CarSystemUiTest
 @RunWith(AndroidTestingRunner.class)
 @TestableLooper.RunWithLooper
 @SmallTest
diff --git a/packages/CarSystemUI/tests/src/com/android/systemui/car/navigationbar/SystemBarConfigsTest.java b/packages/CarSystemUI/tests/src/com/android/systemui/car/navigationbar/SystemBarConfigsTest.java
new file mode 100644
index 0000000..96f0504
--- /dev/null
+++ b/packages/CarSystemUI/tests/src/com/android/systemui/car/navigationbar/SystemBarConfigsTest.java
@@ -0,0 +1,252 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.car.navigationbar;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.when;
+
+import android.content.res.Resources;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+import android.view.WindowManager;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.systemui.R;
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.broadcast.BroadcastDispatcher;
+import com.android.systemui.car.CarDeviceProvisionedController;
+import com.android.systemui.car.CarSystemUiTest;
+import com.android.systemui.car.notification.NotificationPanelViewController;
+import com.android.systemui.car.notification.NotificationPanelViewMediator;
+import com.android.systemui.car.notification.PowerManagerHelper;
+import com.android.systemui.car.notification.TopNotificationPanelViewMediator;
+import com.android.systemui.statusbar.policy.ConfigurationController;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@CarSystemUiTest
+@RunWith(AndroidTestingRunner.class)
[email protected]
+@SmallTest
+public class SystemBarConfigsTest extends SysuiTestCase {
+
+    private SystemBarConfigs mSystemBarConfigs;
+    @Mock
+    private Resources mResources;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        setDefaultValidConfig();
+    }
+
+    @Test
+    public void onInit_allSystemBarsEnabled_eachHasUniqueBarTypes_doesNotThrowException() {
+        mSystemBarConfigs = new SystemBarConfigs(mResources);
+    }
+
+    @Test(expected = RuntimeException.class)
+    public void onInit_allSystemBarsEnabled_twoBarsHaveDuplicateType_throwsRuntimeException() {
+        when(mResources.getInteger(R.integer.config_topSystemBarType)).thenReturn(0);
+        when(mResources.getInteger(R.integer.config_bottomSystemBarType)).thenReturn(0);
+
+        mSystemBarConfigs = new SystemBarConfigs(mResources);
+    }
+
+    @Test
+    public void onInit_allSystemBarsEnabled_systemBarSidesSortedByZOrder() {
+        mSystemBarConfigs = new SystemBarConfigs(mResources);
+        List<Integer> actualOrder = mSystemBarConfigs.getSystemBarSidesByZOrder();
+        List<Integer> expectedOrder = new ArrayList<>();
+        expectedOrder.add(SystemBarConfigs.LEFT);
+        expectedOrder.add(SystemBarConfigs.RIGHT);
+        expectedOrder.add(SystemBarConfigs.TOP);
+        expectedOrder.add(SystemBarConfigs.BOTTOM);
+
+        assertTrue(actualOrder.equals(expectedOrder));
+    }
+
+    @Test(expected = RuntimeException.class)
+    public void onInit_intersectingBarsHaveSameZOrder_throwsRuntimeException() {
+        when(mResources.getInteger(R.integer.config_topSystemBarZOrder)).thenReturn(33);
+        when(mResources.getInteger(R.integer.config_leftSystemBarZOrder)).thenReturn(33);
+
+        mSystemBarConfigs = new SystemBarConfigs(mResources);
+    }
+
+    @Test(expected = RuntimeException.class)
+    public void onInit_hideBottomSystemBarForKeyboardValueDoNotSync_throwsRuntimeException() {
+        when(mResources.getBoolean(R.bool.config_hideBottomSystemBarForKeyboard)).thenReturn(false);
+        when(mResources.getBoolean(
+                com.android.internal.R.bool.config_automotiveHideNavBarForKeyboard)).thenReturn(
+                true);
+
+        mSystemBarConfigs = new SystemBarConfigs(mResources);
+    }
+
+    @Test
+    public void onInit_topNotifPanelViewMediatorUsed_topBarEnabled_doesNotThrowException() {
+        when(mResources.getBoolean(R.bool.config_enableTopNavigationBar)).thenReturn(true);
+        when(mResources.getString(R.string.config_notificationPanelViewMediator)).thenReturn(
+                TestTopNotificationPanelViewMediator.class.getName());
+
+        mSystemBarConfigs = new SystemBarConfigs(mResources);
+    }
+
+    @Test(expected = RuntimeException.class)
+    public void onInit_topNotifPanelViewMediatorUsed_topBarNotEnabled_throwsRuntimeException() {
+        when(mResources.getBoolean(R.bool.config_enableTopNavigationBar)).thenReturn(false);
+        when(mResources.getString(R.string.config_notificationPanelViewMediator)).thenReturn(
+                TestTopNotificationPanelViewMediator.class.getName());
+
+        mSystemBarConfigs = new SystemBarConfigs(mResources);
+    }
+
+    @Test
+    public void onInit_notificationPanelViewMediatorUsed_topBarNotEnabled_doesNotThrowException() {
+        when(mResources.getBoolean(R.bool.config_enableTopNavigationBar)).thenReturn(false);
+        when(mResources.getString(R.string.config_notificationPanelViewMediator)).thenReturn(
+                NotificationPanelViewMediator.class.getName());
+
+        mSystemBarConfigs = new SystemBarConfigs(mResources);
+    }
+
+    @Test
+    public void getTopSystemBarLayoutParams_topBarEnabled_returnsTopSystemBarLayoutParams() {
+        mSystemBarConfigs = new SystemBarConfigs(mResources);
+        WindowManager.LayoutParams lp = mSystemBarConfigs.getLayoutParamsBySide(
+                SystemBarConfigs.TOP);
+
+        assertNotNull(lp);
+    }
+
+    @Test
+    public void getTopSystemBarLayoutParams_topBarNotEnabled_returnsNull() {
+        when(mResources.getBoolean(R.bool.config_enableTopNavigationBar)).thenReturn(false);
+        mSystemBarConfigs = new SystemBarConfigs(mResources);
+        WindowManager.LayoutParams lp = mSystemBarConfigs.getLayoutParamsBySide(
+                SystemBarConfigs.TOP);
+
+        assertNull(lp);
+    }
+
+    @Test
+    public void getTopSystemBarHideForKeyboard_hideBarForKeyboard_returnsTrue() {
+        when(mResources.getBoolean(R.bool.config_hideTopSystemBarForKeyboard)).thenReturn(true);
+        mSystemBarConfigs = new SystemBarConfigs(mResources);
+
+        boolean hideKeyboard = mSystemBarConfigs.getHideForKeyboardBySide(SystemBarConfigs.TOP);
+
+        assertTrue(hideKeyboard);
+    }
+
+    @Test
+    public void getTopSystemBarHideForKeyboard_topBarNotEnabled_returnsFalse() {
+        when(mResources.getBoolean(R.bool.config_enableTopNavigationBar)).thenReturn(false);
+        mSystemBarConfigs = new SystemBarConfigs(mResources);
+
+        boolean hideKeyboard = mSystemBarConfigs.getHideForKeyboardBySide(SystemBarConfigs.TOP);
+
+        assertFalse(hideKeyboard);
+    }
+
+    @Test
+    public void topSystemBarHasHigherZOrderThanHuns_topSystemBarIsNavigationBarPanelType() {
+        when(mResources.getInteger(R.integer.config_topSystemBarZOrder)).thenReturn(
+                SystemBarConfigs.getHunZOrder() + 1);
+        mSystemBarConfigs = new SystemBarConfigs(mResources);
+        WindowManager.LayoutParams lp = mSystemBarConfigs.getLayoutParamsBySide(
+                SystemBarConfigs.TOP);
+
+        assertEquals(lp.type, WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL);
+    }
+
+    @Test
+    public void topSystemBarHasLowerZOrderThanHuns_topSystemBarIsStatusBarAdditionalType() {
+        when(mResources.getInteger(R.integer.config_topSystemBarZOrder)).thenReturn(
+                SystemBarConfigs.getHunZOrder() - 1);
+        mSystemBarConfigs = new SystemBarConfigs(mResources);
+        WindowManager.LayoutParams lp = mSystemBarConfigs.getLayoutParamsBySide(
+                SystemBarConfigs.TOP);
+
+        assertEquals(lp.type, WindowManager.LayoutParams.TYPE_STATUS_BAR_ADDITIONAL);
+    }
+
+    // Set valid config where all system bars are enabled.
+    private void setDefaultValidConfig() {
+        when(mResources.getBoolean(R.bool.config_enableTopNavigationBar)).thenReturn(true);
+        when(mResources.getBoolean(R.bool.config_enableBottomNavigationBar)).thenReturn(true);
+        when(mResources.getBoolean(R.bool.config_enableLeftNavigationBar)).thenReturn(true);
+        when(mResources.getBoolean(R.bool.config_enableRightNavigationBar)).thenReturn(true);
+
+        when(mResources.getDimensionPixelSize(
+                com.android.internal.R.dimen.status_bar_height)).thenReturn(100);
+        when(mResources.getDimensionPixelSize(
+                com.android.internal.R.dimen.navigation_bar_height)).thenReturn(100);
+        when(mResources.getDimensionPixelSize(R.dimen.car_left_navigation_bar_width)).thenReturn(
+                100);
+        when(mResources.getDimensionPixelSize(R.dimen.car_right_navigation_bar_width)).thenReturn(
+                100);
+
+        when(mResources.getInteger(R.integer.config_topSystemBarType)).thenReturn(0);
+        when(mResources.getInteger(R.integer.config_bottomSystemBarType)).thenReturn(1);
+        when(mResources.getInteger(R.integer.config_leftSystemBarType)).thenReturn(2);
+        when(mResources.getInteger(R.integer.config_rightSystemBarType)).thenReturn(3);
+
+        when(mResources.getInteger(R.integer.config_topSystemBarZOrder)).thenReturn(5);
+        when(mResources.getInteger(R.integer.config_bottomSystemBarZOrder)).thenReturn(10);
+        when(mResources.getInteger(R.integer.config_leftSystemBarZOrder)).thenReturn(2);
+        when(mResources.getInteger(R.integer.config_rightSystemBarZOrder)).thenReturn(3);
+
+        when(mResources.getBoolean(R.bool.config_hideTopSystemBarForKeyboard)).thenReturn(false);
+        when(mResources.getBoolean(
+                com.android.internal.R.bool.config_automotiveHideNavBarForKeyboard)).thenReturn(
+                false);
+        when(mResources.getBoolean(R.bool.config_hideLeftSystemBarForKeyboard)).thenReturn(
+                false);
+        when(mResources.getBoolean(R.bool.config_hideRightSystemBarForKeyboard)).thenReturn(
+                false);
+    }
+
+    // Intentionally using a subclass of TopNotificationPanelViewMediator for testing purposes to
+    // ensure that OEM's will be able to implement and use their own NotificationPanelViewMediator.
+    private class TestTopNotificationPanelViewMediator extends
+            TopNotificationPanelViewMediator {
+        TestTopNotificationPanelViewMediator(
+                CarNavigationBarController carNavigationBarController,
+                NotificationPanelViewController notificationPanelViewController,
+                PowerManagerHelper powerManagerHelper,
+                BroadcastDispatcher broadcastDispatcher,
+                CarDeviceProvisionedController carDeviceProvisionedController,
+                ConfigurationController configurationController) {
+            super(carNavigationBarController, notificationPanelViewController, powerManagerHelper,
+                    broadcastDispatcher, carDeviceProvisionedController, configurationController);
+        }
+    }
+}
diff --git a/packages/CarSystemUI/tests/src/com/android/systemui/car/notification/CarHeadsUpNotificationSystemContainerTest.java b/packages/CarSystemUI/tests/src/com/android/systemui/car/notification/CarHeadsUpNotificationSystemContainerTest.java
index ccaeb45..384888a 100644
--- a/packages/CarSystemUI/tests/src/com/android/systemui/car/notification/CarHeadsUpNotificationSystemContainerTest.java
+++ b/packages/CarSystemUI/tests/src/com/android/systemui/car/notification/CarHeadsUpNotificationSystemContainerTest.java
@@ -30,6 +30,7 @@
 
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.car.CarDeviceProvisionedController;
+import com.android.systemui.car.CarSystemUiTest;
 import com.android.systemui.car.window.OverlayViewGlobalStateController;
 
 import org.junit.Before;
@@ -38,6 +39,7 @@
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
+@CarSystemUiTest
 @RunWith(AndroidTestingRunner.class)
 @TestableLooper.RunWithLooper
 @SmallTest
diff --git a/packages/CarSystemUI/tests/src/com/android/systemui/car/notification/NotificationVisibilityLoggerTest.java b/packages/CarSystemUI/tests/src/com/android/systemui/car/notification/NotificationVisibilityLoggerTest.java
index 89dac58..d51aeb1 100644
--- a/packages/CarSystemUI/tests/src/com/android/systemui/car/notification/NotificationVisibilityLoggerTest.java
+++ b/packages/CarSystemUI/tests/src/com/android/systemui/car/notification/NotificationVisibilityLoggerTest.java
@@ -37,6 +37,7 @@
 import com.android.internal.statusbar.IStatusBarService;
 import com.android.internal.statusbar.NotificationVisibility;
 import com.android.systemui.SysuiTestCase;
+import com.android.systemui.car.CarSystemUiTest;
 import com.android.systemui.util.concurrency.FakeExecutor;
 import com.android.systemui.util.time.FakeSystemClock;
 
@@ -49,6 +50,7 @@
 
 import java.util.Collections;
 
+@CarSystemUiTest
 @RunWith(AndroidTestingRunner.class)
 @TestableLooper.RunWithLooper
 @SmallTest
diff --git a/packages/CarSystemUI/tests/src/com/android/systemui/car/sideloaded/SideLoadedAppDetectorTest.java b/packages/CarSystemUI/tests/src/com/android/systemui/car/sideloaded/SideLoadedAppDetectorTest.java
index 77620f3..421e210 100644
--- a/packages/CarSystemUI/tests/src/com/android/systemui/car/sideloaded/SideLoadedAppDetectorTest.java
+++ b/packages/CarSystemUI/tests/src/com/android/systemui/car/sideloaded/SideLoadedAppDetectorTest.java
@@ -37,6 +37,7 @@
 import com.android.systemui.R;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.car.CarDeviceProvisionedController;
+import com.android.systemui.car.CarSystemUiTest;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -44,6 +45,7 @@
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
+@CarSystemUiTest
 @RunWith(AndroidTestingRunner.class)
 @TestableLooper.RunWithLooper
 @SmallTest
diff --git a/packages/CarSystemUI/tests/src/com/android/systemui/car/sideloaded/SideLoadedAppListenerTest.java b/packages/CarSystemUI/tests/src/com/android/systemui/car/sideloaded/SideLoadedAppListenerTest.java
index 73f9f6a5..67f222b 100644
--- a/packages/CarSystemUI/tests/src/com/android/systemui/car/sideloaded/SideLoadedAppListenerTest.java
+++ b/packages/CarSystemUI/tests/src/com/android/systemui/car/sideloaded/SideLoadedAppListenerTest.java
@@ -35,6 +35,7 @@
 import androidx.test.filters.SmallTest;
 
 import com.android.systemui.SysuiTestCase;
+import com.android.systemui.car.CarSystemUiTest;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -46,6 +47,7 @@
 import java.util.Collections;
 import java.util.List;
 
+@CarSystemUiTest
 @RunWith(AndroidTestingRunner.class)
 @TestableLooper.RunWithLooper
 @SmallTest
@@ -218,7 +220,7 @@
 
         verify(mSideLoadedAppStateController, never()).onUnsafeTaskCreatedOnDisplay(any());
         verify(mSideLoadedAppStateController).onSafeTaskDisplayedOnDisplay(display1);
-        verify(mSideLoadedAppStateController, never()).onUnsafeTaskDisplayedOnDisplay(display2);
+        verify(mSideLoadedAppStateController).onUnsafeTaskDisplayedOnDisplay(display2);
         verify(mSideLoadedAppStateController).onSafeTaskDisplayedOnDisplay(display3);
         verify(mSideLoadedAppStateController, never()).onUnsafeTaskDisplayedOnDisplay(display1);
         verify(mSideLoadedAppStateController).onUnsafeTaskDisplayedOnDisplay(display2);
diff --git a/packages/CarSystemUI/tests/src/com/android/systemui/car/userswitcher/UserSwitchTransitionViewControllerTest.java b/packages/CarSystemUI/tests/src/com/android/systemui/car/userswitcher/UserSwitchTransitionViewControllerTest.java
index 797dbf5..2e9d43b 100644
--- a/packages/CarSystemUI/tests/src/com/android/systemui/car/userswitcher/UserSwitchTransitionViewControllerTest.java
+++ b/packages/CarSystemUI/tests/src/com/android/systemui/car/userswitcher/UserSwitchTransitionViewControllerTest.java
@@ -36,6 +36,7 @@
 
 import com.android.systemui.R;
 import com.android.systemui.SysuiTestCase;
+import com.android.systemui.car.CarSystemUiTest;
 import com.android.systemui.car.window.OverlayViewGlobalStateController;
 
 import org.junit.Before;
@@ -44,6 +45,7 @@
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
+@CarSystemUiTest
 @RunWith(AndroidTestingRunner.class)
 @TestableLooper.RunWithLooper
 @SmallTest
diff --git a/packages/CarSystemUI/tests/src/com/android/systemui/car/userswitcher/UserSwitchTransitionViewMediatorTest.java b/packages/CarSystemUI/tests/src/com/android/systemui/car/userswitcher/UserSwitchTransitionViewMediatorTest.java
index a808e2d..7aeffce 100644
--- a/packages/CarSystemUI/tests/src/com/android/systemui/car/userswitcher/UserSwitchTransitionViewMediatorTest.java
+++ b/packages/CarSystemUI/tests/src/com/android/systemui/car/userswitcher/UserSwitchTransitionViewMediatorTest.java
@@ -16,6 +16,7 @@
 
 package com.android.systemui.car.userswitcher;
 
+import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
@@ -24,7 +25,10 @@
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.car.CarDeviceProvisionedController;
 import com.android.systemui.car.CarServiceProvider;
+import com.android.systemui.car.CarSystemUiTest;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -32,16 +36,19 @@
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
+@CarSystemUiTest
 @RunWith(AndroidTestingRunner.class)
 @TestableLooper.RunWithLooper
 @SmallTest
-public class UserSwitchTransitionViewMediatorTest {
+public class UserSwitchTransitionViewMediatorTest extends SysuiTestCase {
     private static final int TEST_USER = 100;
 
     private UserSwitchTransitionViewMediator mUserSwitchTransitionViewMediator;
     @Mock
     private CarServiceProvider mCarServiceProvider;
     @Mock
+    private CarDeviceProvisionedController mCarDeviceProvisionedController;
+    @Mock
     private UserSwitchTransitionViewController mUserSwitchTransitionViewController;
     @Mock
     private CarUserManager.UserLifecycleEvent mUserLifecycleEvent;
@@ -51,21 +58,35 @@
         MockitoAnnotations.initMocks(this);
 
         mUserSwitchTransitionViewMediator = new UserSwitchTransitionViewMediator(
-                mCarServiceProvider, mUserSwitchTransitionViewController);
-
+                mCarServiceProvider, mCarDeviceProvisionedController,
+                mUserSwitchTransitionViewController);
+        when(mCarDeviceProvisionedController.getCurrentUser()).thenReturn(TEST_USER);
     }
 
     @Test
-    public void onUserLifecycleEvent_userStarting_callsHandleShow() {
+    public void onUserLifecycleEvent_userStarting_isCurrentUser_callsHandleShow() {
         when(mUserLifecycleEvent.getEventType()).thenReturn(
                 CarUserManager.USER_LIFECYCLE_EVENT_TYPE_STARTING);
         when(mUserLifecycleEvent.getUserId()).thenReturn(TEST_USER);
+
         mUserSwitchTransitionViewMediator.handleUserLifecycleEvent(mUserLifecycleEvent);
 
         verify(mUserSwitchTransitionViewController).handleShow(TEST_USER);
     }
 
     @Test
+    public void onUserLifecycleEvent_userStarting_isNotCurrentUser_doesNotCallHandleShow() {
+        when(mUserLifecycleEvent.getEventType()).thenReturn(
+                CarUserManager.USER_LIFECYCLE_EVENT_TYPE_STARTING);
+        when(mUserLifecycleEvent.getUserId()).thenReturn(TEST_USER);
+        when(mCarDeviceProvisionedController.getCurrentUser()).thenReturn(TEST_USER + 1);
+
+        mUserSwitchTransitionViewMediator.handleUserLifecycleEvent(mUserLifecycleEvent);
+
+        verify(mUserSwitchTransitionViewController, never()).handleShow(TEST_USER);
+    }
+
+    @Test
     public void onUserLifecycleEvent_userSwitching_callsHandleHide() {
         when(mUserLifecycleEvent.getEventType()).thenReturn(
                 CarUserManager.USER_LIFECYCLE_EVENT_TYPE_SWITCHING);
diff --git a/packages/CarSystemUI/tests/src/com/android/systemui/car/voicerecognition/ConnectedDeviceVoiceRecognitionNotifierTest.java b/packages/CarSystemUI/tests/src/com/android/systemui/car/voicerecognition/ConnectedDeviceVoiceRecognitionNotifierTest.java
index eca51e3..f77294e 100644
--- a/packages/CarSystemUI/tests/src/com/android/systemui/car/voicerecognition/ConnectedDeviceVoiceRecognitionNotifierTest.java
+++ b/packages/CarSystemUI/tests/src/com/android/systemui/car/voicerecognition/ConnectedDeviceVoiceRecognitionNotifierTest.java
@@ -19,11 +19,15 @@
 import static com.android.systemui.car.voicerecognition.ConnectedDeviceVoiceRecognitionNotifier.INVALID_VALUE;
 import static com.android.systemui.car.voicerecognition.ConnectedDeviceVoiceRecognitionNotifier.VOICE_RECOGNITION_STARTED;
 
+import static com.google.common.truth.Truth.assertThat;
+
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.verify;
 
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
 import android.bluetooth.BluetoothHeadsetClient;
 import android.content.Intent;
 import android.os.Handler;
@@ -33,25 +37,36 @@
 import androidx.test.filters.SmallTest;
 
 import com.android.systemui.SysuiTestCase;
+import com.android.systemui.car.CarSystemUiTest;
 
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
 
+@CarSystemUiTest
 @RunWith(AndroidTestingRunner.class)
 @TestableLooper.RunWithLooper
 @SmallTest
+// TODO(b/162866441): Refactor to use the Executor pattern instead.
 public class ConnectedDeviceVoiceRecognitionNotifierTest extends SysuiTestCase {
 
     private static final String BLUETOOTH_PERM = android.Manifest.permission.BLUETOOTH;
+    private static final String BLUETOOTH_REMOTE_ADDRESS = "00:11:22:33:44:55";
 
     private ConnectedDeviceVoiceRecognitionNotifier mVoiceRecognitionNotifier;
+    private TestableLooper mTestableLooper;
+    private Handler mHandler;
     private Handler mTestHandler;
+    private BluetoothDevice mBluetoothDevice;
 
     @Before
     public void setUp() throws Exception {
-        TestableLooper testableLooper = TestableLooper.get(this);
-        mTestHandler = spy(new Handler(testableLooper.getLooper()));
+        mTestableLooper = TestableLooper.get(this);
+        mHandler = new Handler(mTestableLooper.getLooper());
+        mTestHandler = spy(mHandler);
+        mBluetoothDevice = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(
+                BLUETOOTH_REMOTE_ADDRESS);
         mVoiceRecognitionNotifier = new ConnectedDeviceVoiceRecognitionNotifier(
                 mContext, mTestHandler);
         mVoiceRecognitionNotifier.onBootCompleted();
@@ -61,37 +76,60 @@
     public void testReceiveIntent_started_showToast() {
         Intent intent = new Intent(BluetoothHeadsetClient.ACTION_AG_EVENT);
         intent.putExtra(BluetoothHeadsetClient.EXTRA_VOICE_RECOGNITION, VOICE_RECOGNITION_STARTED);
+        intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mBluetoothDevice);
+
         mContext.sendBroadcast(intent, BLUETOOTH_PERM);
+        mTestableLooper.processAllMessages();
         waitForIdleSync();
 
-        verify(mTestHandler).post(any());
+        mHandler.post(() -> {
+            ArgumentCaptor<Runnable> argumentCaptor = ArgumentCaptor.forClass(Runnable.class);
+            verify(mTestHandler).post(argumentCaptor.capture());
+            assertThat(argumentCaptor.getValue()).isNotNull();
+            assertThat(argumentCaptor.getValue()).isNotEqualTo(this);
+        });
     }
 
     @Test
     public void testReceiveIntent_invalidExtra_noToast() {
         Intent intent = new Intent(BluetoothHeadsetClient.ACTION_AG_EVENT);
         intent.putExtra(BluetoothHeadsetClient.EXTRA_VOICE_RECOGNITION, INVALID_VALUE);
+        intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mBluetoothDevice);
+
         mContext.sendBroadcast(intent, BLUETOOTH_PERM);
+        mTestableLooper.processAllMessages();
         waitForIdleSync();
 
-        verify(mTestHandler, never()).post(any());
+        mHandler.post(() -> {
+            verify(mTestHandler, never()).post(any());
+        });
     }
 
     @Test
     public void testReceiveIntent_noExtra_noToast() {
         Intent intent = new Intent(BluetoothHeadsetClient.ACTION_AG_EVENT);
+        intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mBluetoothDevice);
+
         mContext.sendBroadcast(intent, BLUETOOTH_PERM);
+        mTestableLooper.processAllMessages();
         waitForIdleSync();
 
-        verify(mTestHandler, never()).post(any());
+        mHandler.post(() -> {
+            verify(mTestHandler, never()).post(any());
+        });
     }
 
     @Test
     public void testReceiveIntent_invalidIntent_noToast() {
         Intent intent = new Intent(BluetoothHeadsetClient.ACTION_AUDIO_STATE_CHANGED);
+        intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mBluetoothDevice);
+
         mContext.sendBroadcast(intent, BLUETOOTH_PERM);
+        mTestableLooper.processAllMessages();
         waitForIdleSync();
 
-        verify(mTestHandler, never()).post(any());
+        mHandler.post(() -> {
+            verify(mTestHandler, never()).post(any());
+        });
     }
 }
diff --git a/packages/CarSystemUI/tests/src/com/android/systemui/car/window/OverlayPanelViewControllerTest.java b/packages/CarSystemUI/tests/src/com/android/systemui/car/window/OverlayPanelViewControllerTest.java
index 45a05ac..23e21e4 100644
--- a/packages/CarSystemUI/tests/src/com/android/systemui/car/window/OverlayPanelViewControllerTest.java
+++ b/packages/CarSystemUI/tests/src/com/android/systemui/car/window/OverlayPanelViewControllerTest.java
@@ -39,6 +39,7 @@
 
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.car.CarDeviceProvisionedController;
+import com.android.systemui.car.CarSystemUiTest;
 import com.android.systemui.statusbar.FlingAnimationUtils;
 import com.android.systemui.tests.R;
 
@@ -52,6 +53,7 @@
 import java.util.ArrayList;
 import java.util.List;
 
+@CarSystemUiTest
 @RunWith(AndroidTestingRunner.class)
 @TestableLooper.RunWithLooper
 @SmallTest
@@ -222,18 +224,6 @@
     }
 
     @Test
-    public void animateCollapsePanel_removesWindowFocus() {
-        mOverlayPanelViewController.inflate(mBaseLayout);
-        mOverlayPanelViewController.setShouldAnimateCollapsePanel(true);
-        mOverlayPanelViewController.setPanelExpanded(true);
-        mOverlayPanelViewController.setPanelVisible(true);
-
-        mOverlayPanelViewController.animateCollapsePanel();
-
-        verify(mOverlayViewGlobalStateController).setWindowFocusable(false);
-    }
-
-    @Test
     public void animateExpandPanel_shouldNotAnimateExpandPanel_doesNotExpand() {
         mOverlayPanelViewController.inflate(mBaseLayout);
         mOverlayPanelViewController.setShouldAnimateExpandPanel(false);
@@ -363,14 +353,6 @@
     }
 
     @Test
-    public void setPanelVisible_setTrue_setWindowFocusable() {
-        mOverlayPanelViewController.inflate(mBaseLayout);
-        mOverlayPanelViewController.setPanelVisible(true);
-
-        verify(mOverlayViewGlobalStateController).setWindowFocusable(true);
-    }
-
-    @Test
     public void setPanelVisible_setFalse_windowVisible_setsWindowNotVisible() {
         mOverlayPanelViewController.inflate(mBaseLayout);
         when(mOverlayViewGlobalStateController.isWindowVisible()).thenReturn(true);
@@ -402,15 +384,6 @@
     }
 
     @Test
-    public void setPanelVisible_setFalse_setWindowNotFocusable() {
-        mOverlayPanelViewController.inflate(mBaseLayout);
-
-        mOverlayPanelViewController.setPanelVisible(false);
-
-        verify(mOverlayViewGlobalStateController).setWindowFocusable(false);
-    }
-
-    @Test
     public void dragOpenTouchListener_isNotInflated_inflatesView() {
         when(mCarDeviceProvisionedController.isCurrentUserFullySetup()).thenReturn(true);
         assertThat(mOverlayPanelViewController.isInflated()).isFalse();
diff --git a/packages/CarSystemUI/tests/src/com/android/systemui/car/window/OverlayViewControllerTest.java b/packages/CarSystemUI/tests/src/com/android/systemui/car/window/OverlayViewControllerTest.java
index c24a3b5..e784761 100644
--- a/packages/CarSystemUI/tests/src/com/android/systemui/car/window/OverlayViewControllerTest.java
+++ b/packages/CarSystemUI/tests/src/com/android/systemui/car/window/OverlayViewControllerTest.java
@@ -29,6 +29,7 @@
 import androidx.test.filters.SmallTest;
 
 import com.android.systemui.SysuiTestCase;
+import com.android.systemui.car.CarSystemUiTest;
 import com.android.systemui.tests.R;
 
 import org.junit.Before;
@@ -39,6 +40,7 @@
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
+@CarSystemUiTest
 @RunWith(AndroidTestingRunner.class)
 @TestableLooper.RunWithLooper
 @SmallTest
diff --git a/packages/CarSystemUI/tests/src/com/android/systemui/car/window/OverlayViewGlobalStateControllerTest.java b/packages/CarSystemUI/tests/src/com/android/systemui/car/window/OverlayViewGlobalStateControllerTest.java
index cba42e5..294aa0d 100644
--- a/packages/CarSystemUI/tests/src/com/android/systemui/car/window/OverlayViewGlobalStateControllerTest.java
+++ b/packages/CarSystemUI/tests/src/com/android/systemui/car/window/OverlayViewGlobalStateControllerTest.java
@@ -16,9 +16,14 @@
 
 package com.android.systemui.car.window;
 
+import static android.view.WindowInsets.Type.navigationBars;
+import static android.view.WindowInsets.Type.statusBars;
+
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
@@ -28,22 +33,23 @@
 import android.view.View;
 import android.view.ViewGroup;
 import android.view.ViewStub;
+import android.view.WindowInsetsController;
 
 import androidx.test.filters.SmallTest;
 
 import com.android.systemui.SysuiTestCase;
-import com.android.systemui.car.navigationbar.CarNavigationBarController;
+import com.android.systemui.car.CarSystemUiTest;
 import com.android.systemui.tests.R;
 
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
-import org.mockito.Mockito;
 import org.mockito.MockitoAnnotations;
 
 import java.util.Arrays;
 
+@CarSystemUiTest
 @RunWith(AndroidTestingRunner.class)
 @TestableLooper.RunWithLooper
 @SmallTest
@@ -56,8 +62,6 @@
     private ViewGroup mBaseLayout;
 
     @Mock
-    private CarNavigationBarController mCarNavigationBarController;
-    @Mock
     private SystemUIOverlayWindowController mSystemUIOverlayWindowController;
     @Mock
     private OverlayViewMediator mOverlayViewMediator;
@@ -69,18 +73,22 @@
     private OverlayPanelViewController mOverlayPanelViewController;
     @Mock
     private Runnable mRunnable;
+    @Mock
+    private WindowInsetsController mWindowInsetsController;
 
     @Before
     public void setUp() {
         MockitoAnnotations.initMocks(/* testClass= */ this);
 
-        mBaseLayout = (ViewGroup) LayoutInflater.from(mContext).inflate(
-                R.layout.overlay_view_global_state_controller_test, /* root= */ null);
+        mBaseLayout = spy((ViewGroup) LayoutInflater.from(mContext).inflate(
+                R.layout.overlay_view_global_state_controller_test, /* root= */ null));
+
+        when(mBaseLayout.getWindowInsetsController()).thenReturn(mWindowInsetsController);
 
         when(mSystemUIOverlayWindowController.getBaseLayout()).thenReturn(mBaseLayout);
 
         mOverlayViewGlobalStateController = new OverlayViewGlobalStateController(
-                mCarNavigationBarController, mSystemUIOverlayWindowController);
+                mSystemUIOverlayWindowController);
 
         verify(mSystemUIOverlayWindowController).attach();
     }
@@ -100,23 +108,101 @@
     }
 
     @Test
-    public void showView_nothingAlreadyShown_shouldShowNavBarFalse_navigationBarsHidden() {
+    public void showView_nothingVisible_windowNotFocusable_shouldShowNavBar_navBarsVisible() {
         setupOverlayViewController1();
-        when(mOverlayViewController1.shouldShowNavigationBar()).thenReturn(false);
+        when(mOverlayViewController1.shouldFocusWindow()).thenReturn(false);
+        when(mOverlayViewController1.shouldShowNavigationBarInsets()).thenReturn(true);
 
         mOverlayViewGlobalStateController.showView(mOverlayViewController1, mRunnable);
 
-        verify(mCarNavigationBarController).hideBars();
+        verify(mWindowInsetsController).show(navigationBars());
+    }
+
+    @Test
+    public void showView_nothingVisible_windowNotFocusable_shouldHideNavBar_notHidden() {
+        setupOverlayViewController1();
+        when(mOverlayViewController1.shouldFocusWindow()).thenReturn(false);
+        when(mOverlayViewController1.shouldShowNavigationBarInsets()).thenReturn(false);
+
+        mOverlayViewGlobalStateController.showView(mOverlayViewController1, mRunnable);
+
+        verify(mWindowInsetsController, never()).hide(navigationBars());
+    }
+
+    @Test
+    public void showView_nothingVisible_windowNotFocusable_shouldShowStatusBar_statusBarsVisible() {
+        setupOverlayViewController1();
+        when(mOverlayViewController1.shouldFocusWindow()).thenReturn(false);
+        when(mOverlayViewController1.shouldShowStatusBarInsets()).thenReturn(true);
+
+        mOverlayViewGlobalStateController.showView(mOverlayViewController1, mRunnable);
+
+        verify(mWindowInsetsController).show(statusBars());
+    }
+
+    @Test
+    public void showView_nothingVisible_windowNotFocusable_shouldHideStatusBar_notHidden() {
+        setupOverlayViewController1();
+        when(mOverlayViewController1.shouldFocusWindow()).thenReturn(false);
+        when(mOverlayViewController1.shouldShowStatusBarInsets()).thenReturn(false);
+
+        mOverlayViewGlobalStateController.showView(mOverlayViewController1, mRunnable);
+
+        verify(mWindowInsetsController, never()).hide(statusBars());
+    }
+
+    @Test
+    public void showView_nothingAlreadyShown_shouldShowNavBarFalse_navigationBarsHidden() {
+        setupOverlayViewController1();
+        when(mOverlayViewController1.shouldFocusWindow()).thenReturn(true);
+        when(mOverlayViewController1.shouldShowNavigationBarInsets()).thenReturn(false);
+
+        mOverlayViewGlobalStateController.showView(mOverlayViewController1, mRunnable);
+
+        verify(mWindowInsetsController).hide(navigationBars());
     }
 
     @Test
     public void showView_nothingAlreadyShown_shouldShowNavBarTrue_navigationBarsShown() {
         setupOverlayViewController1();
-        when(mOverlayViewController1.shouldShowNavigationBar()).thenReturn(true);
+        when(mOverlayViewController1.shouldFocusWindow()).thenReturn(true);
+        when(mOverlayViewController1.shouldShowNavigationBarInsets()).thenReturn(true);
 
         mOverlayViewGlobalStateController.showView(mOverlayViewController1, mRunnable);
 
-        verify(mCarNavigationBarController).showBars();
+        verify(mWindowInsetsController).show(navigationBars());
+    }
+
+    @Test
+    public void showView_nothingAlreadyShown_shouldShowStatusBarFalse_statusBarsHidden() {
+        setupOverlayViewController1();
+        when(mOverlayViewController1.shouldFocusWindow()).thenReturn(true);
+        when(mOverlayViewController1.shouldShowStatusBarInsets()).thenReturn(false);
+
+        mOverlayViewGlobalStateController.showView(mOverlayViewController1, mRunnable);
+
+        verify(mWindowInsetsController).hide(statusBars());
+    }
+
+    @Test
+    public void showView_nothingAlreadyShown_shouldShowStatusBarTrue_statusBarsShown() {
+        setupOverlayViewController1();
+        when(mOverlayViewController1.shouldFocusWindow()).thenReturn(true);
+        when(mOverlayViewController1.shouldShowStatusBarInsets()).thenReturn(true);
+
+        mOverlayViewGlobalStateController.showView(mOverlayViewController1, mRunnable);
+
+        verify(mWindowInsetsController).show(statusBars());
+    }
+
+    @Test
+    public void showView_nothingAlreadyShown_fitsNavBarInsets_insetsAdjusted() {
+        setupOverlayViewController1();
+        when(mOverlayViewController1.getInsetTypesToFit()).thenReturn(navigationBars());
+
+        mOverlayViewGlobalStateController.showView(mOverlayViewController1, mRunnable);
+
+        verify(mSystemUIOverlayWindowController).setFitInsetsTypes(navigationBars());
     }
 
     @Test
@@ -163,25 +249,73 @@
     @Test
     public void showView_newHighestZOrder_shouldShowNavBarFalse_navigationBarsHidden() {
         setupOverlayViewController1();
-        setOverlayViewControllerAsShowing(mOverlayViewController1);
         setupOverlayViewController2();
-        when(mOverlayViewController2.shouldShowNavigationBar()).thenReturn(false);
+        when(mOverlayViewController1.shouldFocusWindow()).thenReturn(true);
+        when(mOverlayViewController2.shouldFocusWindow()).thenReturn(true);
+        setOverlayViewControllerAsShowing(mOverlayViewController1);
+        when(mOverlayViewController2.shouldShowNavigationBarInsets()).thenReturn(false);
+        reset(mWindowInsetsController);
 
         mOverlayViewGlobalStateController.showView(mOverlayViewController2, mRunnable);
 
-        verify(mCarNavigationBarController).hideBars();
+        verify(mWindowInsetsController).hide(navigationBars());
     }
 
     @Test
     public void showView_newHighestZOrder_shouldShowNavBarTrue_navigationBarsShown() {
         setupOverlayViewController1();
-        setOverlayViewControllerAsShowing(mOverlayViewController1);
         setupOverlayViewController2();
-        when(mOverlayViewController2.shouldShowNavigationBar()).thenReturn(true);
+        when(mOverlayViewController1.shouldFocusWindow()).thenReturn(true);
+        when(mOverlayViewController2.shouldFocusWindow()).thenReturn(true);
+        setOverlayViewControllerAsShowing(mOverlayViewController1);
+        when(mOverlayViewController2.shouldShowNavigationBarInsets()).thenReturn(true);
 
         mOverlayViewGlobalStateController.showView(mOverlayViewController2, mRunnable);
 
-        verify(mCarNavigationBarController).showBars();
+        verify(mWindowInsetsController).show(navigationBars());
+    }
+
+    @Test
+    public void showView_newHighestZOrder_shouldShowStatusBarFalse_statusBarsHidden() {
+        setupOverlayViewController1();
+        setupOverlayViewController2();
+        when(mOverlayViewController1.shouldFocusWindow()).thenReturn(true);
+        when(mOverlayViewController2.shouldFocusWindow()).thenReturn(true);
+        setOverlayViewControllerAsShowing(mOverlayViewController1);
+        when(mOverlayViewController2.shouldShowStatusBarInsets()).thenReturn(false);
+        reset(mWindowInsetsController);
+
+        mOverlayViewGlobalStateController.showView(mOverlayViewController2, mRunnable);
+
+        verify(mWindowInsetsController).hide(statusBars());
+    }
+
+    @Test
+    public void showView_newHighestZOrder_shouldShowStatusBarTrue_statusBarsShown() {
+        setupOverlayViewController1();
+        setupOverlayViewController2();
+        when(mOverlayViewController1.shouldFocusWindow()).thenReturn(true);
+        when(mOverlayViewController2.shouldFocusWindow()).thenReturn(true);
+        setOverlayViewControllerAsShowing(mOverlayViewController1);
+        when(mOverlayViewController2.shouldShowStatusBarInsets()).thenReturn(true);
+
+        mOverlayViewGlobalStateController.showView(mOverlayViewController2, mRunnable);
+
+        verify(mWindowInsetsController).show(statusBars());
+    }
+
+    @Test
+    public void showView_newHighestZOrder_fitsNavBarInsets_insetsAdjusted() {
+        setupOverlayViewController1();
+        when(mOverlayViewController1.getInsetTypesToFit()).thenReturn(statusBars());
+        setOverlayViewControllerAsShowing(mOverlayViewController1);
+        setupOverlayViewController2();
+        when(mOverlayViewController2.getInsetTypesToFit()).thenReturn(navigationBars());
+        reset(mWindowInsetsController);
+
+        mOverlayViewGlobalStateController.showView(mOverlayViewController2, mRunnable);
+
+        verify(mSystemUIOverlayWindowController).setFitInsetsTypes(navigationBars());
     }
 
     @Test
@@ -211,25 +345,71 @@
     @Test
     public void showView_oldHighestZOrder_shouldShowNavBarFalse_navigationBarsHidden() {
         setupOverlayViewController2();
+        when(mOverlayViewController1.shouldFocusWindow()).thenReturn(true);
+        when(mOverlayViewController2.shouldFocusWindow()).thenReturn(true);
         setOverlayViewControllerAsShowing(mOverlayViewController2);
-        when(mOverlayViewController1.shouldShowNavigationBar()).thenReturn(true);
-        when(mOverlayViewController2.shouldShowNavigationBar()).thenReturn(false);
+        when(mOverlayViewController1.shouldShowNavigationBarInsets()).thenReturn(true);
+        when(mOverlayViewController2.shouldShowNavigationBarInsets()).thenReturn(false);
+        reset(mWindowInsetsController);
 
         mOverlayViewGlobalStateController.showView(mOverlayViewController1, mRunnable);
 
-        verify(mCarNavigationBarController).hideBars();
+        verify(mWindowInsetsController).hide(navigationBars());
     }
 
     @Test
     public void showView_oldHighestZOrder_shouldShowNavBarTrue_navigationBarsShown() {
         setupOverlayViewController2();
+        when(mOverlayViewController1.shouldFocusWindow()).thenReturn(true);
+        when(mOverlayViewController2.shouldFocusWindow()).thenReturn(true);
         setOverlayViewControllerAsShowing(mOverlayViewController2);
-        when(mOverlayViewController1.shouldShowNavigationBar()).thenReturn(false);
-        when(mOverlayViewController2.shouldShowNavigationBar()).thenReturn(true);
+        when(mOverlayViewController1.shouldShowNavigationBarInsets()).thenReturn(false);
+        when(mOverlayViewController2.shouldShowNavigationBarInsets()).thenReturn(true);
 
         mOverlayViewGlobalStateController.showView(mOverlayViewController1, mRunnable);
 
-        verify(mCarNavigationBarController).showBars();
+        verify(mWindowInsetsController).show(navigationBars());
+    }
+
+    @Test
+    public void showView_oldHighestZOrder_shouldShowStatusBarFalse_statusBarsHidden() {
+        setupOverlayViewController2();
+        when(mOverlayViewController1.shouldFocusWindow()).thenReturn(true);
+        when(mOverlayViewController2.shouldFocusWindow()).thenReturn(true);
+        setOverlayViewControllerAsShowing(mOverlayViewController2);
+        when(mOverlayViewController1.shouldShowStatusBarInsets()).thenReturn(true);
+        when(mOverlayViewController2.shouldShowStatusBarInsets()).thenReturn(false);
+        reset(mWindowInsetsController);
+
+        mOverlayViewGlobalStateController.showView(mOverlayViewController1, mRunnable);
+
+        verify(mWindowInsetsController).hide(statusBars());
+    }
+
+    @Test
+    public void showView_oldHighestZOrder_shouldShowStatusBarTrue_statusBarsShown() {
+        setupOverlayViewController2();
+        when(mOverlayViewController1.shouldFocusWindow()).thenReturn(true);
+        when(mOverlayViewController2.shouldFocusWindow()).thenReturn(true);
+        setOverlayViewControllerAsShowing(mOverlayViewController2);
+        when(mOverlayViewController1.shouldShowStatusBarInsets()).thenReturn(false);
+        when(mOverlayViewController2.shouldShowStatusBarInsets()).thenReturn(true);
+
+        mOverlayViewGlobalStateController.showView(mOverlayViewController1, mRunnable);
+
+        verify(mWindowInsetsController).show(statusBars());
+    }
+
+    @Test
+    public void showView_oldHighestZOrder_fitsNavBarInsets_insetsAdjusted() {
+        setupOverlayViewController2();
+        setOverlayViewControllerAsShowing(mOverlayViewController2);
+        when(mOverlayViewController1.getInsetTypesToFit()).thenReturn(statusBars());
+        when(mOverlayViewController2.getInsetTypesToFit()).thenReturn(navigationBars());
+
+        mOverlayViewGlobalStateController.showView(mOverlayViewController1, mRunnable);
+
+        verify(mSystemUIOverlayWindowController).setFitInsetsTypes(navigationBars());
     }
 
     @Test
@@ -396,27 +576,79 @@
     @Test
     public void hideView_newHighestZOrder_shouldShowNavBarFalse_navigationBarHidden() {
         setupOverlayViewController1();
-        setOverlayViewControllerAsShowing(mOverlayViewController1);
         setupOverlayViewController2();
+        when(mOverlayViewController1.shouldFocusWindow()).thenReturn(true);
+        when(mOverlayViewController2.shouldFocusWindow()).thenReturn(true);
+        setOverlayViewControllerAsShowing(mOverlayViewController1);
         setOverlayViewControllerAsShowing(mOverlayViewController2);
-        when(mOverlayViewController1.shouldShowNavigationBar()).thenReturn(false);
+        when(mOverlayViewController1.shouldShowNavigationBarInsets()).thenReturn(false);
+        reset(mWindowInsetsController);
 
         mOverlayViewGlobalStateController.hideView(mOverlayViewController2, mRunnable);
 
-        verify(mCarNavigationBarController).hideBars();
+        verify(mWindowInsetsController).hide(navigationBars());
     }
 
     @Test
     public void hideView_newHighestZOrder_shouldShowNavBarTrue_navigationBarShown() {
         setupOverlayViewController1();
-        setOverlayViewControllerAsShowing(mOverlayViewController1);
         setupOverlayViewController2();
+        when(mOverlayViewController1.shouldFocusWindow()).thenReturn(true);
+        when(mOverlayViewController2.shouldFocusWindow()).thenReturn(true);
+        setOverlayViewControllerAsShowing(mOverlayViewController1);
         setOverlayViewControllerAsShowing(mOverlayViewController2);
-        when(mOverlayViewController1.shouldShowNavigationBar()).thenReturn(true);
+        when(mOverlayViewController1.shouldShowNavigationBarInsets()).thenReturn(true);
+        reset(mWindowInsetsController);
 
         mOverlayViewGlobalStateController.hideView(mOverlayViewController2, mRunnable);
 
-        verify(mCarNavigationBarController).showBars();
+        verify(mWindowInsetsController).show(navigationBars());
+    }
+
+    @Test
+    public void hideView_newHighestZOrder_shouldShowStatusBarFalse_statusBarHidden() {
+        setupOverlayViewController1();
+        setupOverlayViewController2();
+        when(mOverlayViewController1.shouldFocusWindow()).thenReturn(true);
+        when(mOverlayViewController2.shouldFocusWindow()).thenReturn(true);
+        setOverlayViewControllerAsShowing(mOverlayViewController1);
+        setOverlayViewControllerAsShowing(mOverlayViewController2);
+        when(mOverlayViewController1.shouldShowStatusBarInsets()).thenReturn(false);
+        reset(mWindowInsetsController);
+
+        mOverlayViewGlobalStateController.hideView(mOverlayViewController2, mRunnable);
+
+        verify(mWindowInsetsController).hide(statusBars());
+    }
+
+    @Test
+    public void hideView_newHighestZOrder_shouldShowStatusBarTrue_statusBarShown() {
+        setupOverlayViewController1();
+        setupOverlayViewController2();
+        when(mOverlayViewController1.shouldFocusWindow()).thenReturn(true);
+        when(mOverlayViewController2.shouldFocusWindow()).thenReturn(true);
+        setOverlayViewControllerAsShowing(mOverlayViewController1);
+        setOverlayViewControllerAsShowing(mOverlayViewController2);
+        when(mOverlayViewController1.shouldShowStatusBarInsets()).thenReturn(true);
+        reset(mWindowInsetsController);
+
+        mOverlayViewGlobalStateController.hideView(mOverlayViewController2, mRunnable);
+
+        verify(mWindowInsetsController).show(statusBars());
+    }
+
+    @Test
+    public void hideView_newHighestZOrder_fitsNavBarInsets_insetsAdjusted() {
+        setupOverlayViewController1();
+        setOverlayViewControllerAsShowing(mOverlayViewController1);
+        setupOverlayViewController2();
+        setOverlayViewControllerAsShowing(mOverlayViewController2);
+        when(mOverlayViewController1.getInsetTypesToFit()).thenReturn(navigationBars());
+        when(mOverlayViewController2.getInsetTypesToFit()).thenReturn(statusBars());
+
+        mOverlayViewGlobalStateController.hideView(mOverlayViewController2, mRunnable);
+
+        verify(mSystemUIOverlayWindowController).setFitInsetsTypes(navigationBars());
     }
 
     @Test
@@ -435,27 +667,77 @@
     @Test
     public void hideView_oldHighestZOrder_shouldShowNavBarFalse_navigationBarHidden() {
         setupOverlayViewController1();
-        setOverlayViewControllerAsShowing(mOverlayViewController1);
         setupOverlayViewController2();
+        when(mOverlayViewController1.shouldFocusWindow()).thenReturn(true);
+        when(mOverlayViewController2.shouldFocusWindow()).thenReturn(true);
+        setOverlayViewControllerAsShowing(mOverlayViewController1);
         setOverlayViewControllerAsShowing(mOverlayViewController2);
-        when(mOverlayViewController2.shouldShowNavigationBar()).thenReturn(false);
+        when(mOverlayViewController2.shouldShowNavigationBarInsets()).thenReturn(false);
+        reset(mWindowInsetsController);
 
         mOverlayViewGlobalStateController.hideView(mOverlayViewController1, mRunnable);
 
-        verify(mCarNavigationBarController).hideBars();
+        verify(mWindowInsetsController).hide(navigationBars());
     }
 
     @Test
     public void hideView_oldHighestZOrder_shouldShowNavBarTrue_navigationBarShown() {
         setupOverlayViewController1();
-        setOverlayViewControllerAsShowing(mOverlayViewController1);
         setupOverlayViewController2();
+        when(mOverlayViewController1.shouldFocusWindow()).thenReturn(true);
+        when(mOverlayViewController2.shouldFocusWindow()).thenReturn(true);
+        setOverlayViewControllerAsShowing(mOverlayViewController1);
         setOverlayViewControllerAsShowing(mOverlayViewController2);
-        when(mOverlayViewController2.shouldShowNavigationBar()).thenReturn(true);
+        when(mOverlayViewController2.shouldShowNavigationBarInsets()).thenReturn(true);
 
         mOverlayViewGlobalStateController.hideView(mOverlayViewController1, mRunnable);
 
-        verify(mCarNavigationBarController).showBars();
+        verify(mWindowInsetsController).show(navigationBars());
+    }
+
+    @Test
+    public void hideView_oldHighestZOrder_shouldShowStatusBarFalse_statusBarHidden() {
+        setupOverlayViewController1();
+        setupOverlayViewController2();
+        when(mOverlayViewController1.shouldFocusWindow()).thenReturn(true);
+        when(mOverlayViewController2.shouldFocusWindow()).thenReturn(true);
+        setOverlayViewControllerAsShowing(mOverlayViewController1);
+        setOverlayViewControllerAsShowing(mOverlayViewController2);
+        when(mOverlayViewController2.shouldShowStatusBarInsets()).thenReturn(false);
+        reset(mWindowInsetsController);
+
+        mOverlayViewGlobalStateController.hideView(mOverlayViewController1, mRunnable);
+
+        verify(mWindowInsetsController).hide(statusBars());
+    }
+
+    @Test
+    public void hideView_oldHighestZOrder_shouldShowStatusBarTrue_statusBarShown() {
+        setupOverlayViewController1();
+        setupOverlayViewController2();
+        when(mOverlayViewController1.shouldFocusWindow()).thenReturn(true);
+        when(mOverlayViewController2.shouldFocusWindow()).thenReturn(true);
+        setOverlayViewControllerAsShowing(mOverlayViewController1);
+        setOverlayViewControllerAsShowing(mOverlayViewController2);
+        when(mOverlayViewController2.shouldShowStatusBarInsets()).thenReturn(true);
+
+        mOverlayViewGlobalStateController.hideView(mOverlayViewController1, mRunnable);
+
+        verify(mWindowInsetsController).show(statusBars());
+    }
+
+    @Test
+    public void hideView_oldHighestZOrder_fitsNavBarInsets_insetsAdjusted() {
+        setupOverlayViewController1();
+        setOverlayViewControllerAsShowing(mOverlayViewController1);
+        setupOverlayViewController2();
+        setOverlayViewControllerAsShowing(mOverlayViewController2);
+        when(mOverlayViewController1.getInsetTypesToFit()).thenReturn(statusBars());
+        when(mOverlayViewController2.getInsetTypesToFit()).thenReturn(navigationBars());
+
+        mOverlayViewGlobalStateController.hideView(mOverlayViewController1, mRunnable);
+
+        verify(mSystemUIOverlayWindowController).setFitInsetsTypes(navigationBars());
     }
 
     @Test
@@ -473,11 +755,33 @@
     @Test
     public void hideView_viewControllerOnlyShown_navigationBarShown() {
         setupOverlayViewController1();
+        when(mOverlayViewController1.shouldFocusWindow()).thenReturn(true);
         setOverlayViewControllerAsShowing(mOverlayViewController1);
 
         mOverlayViewGlobalStateController.hideView(mOverlayViewController1, mRunnable);
 
-        verify(mCarNavigationBarController).showBars();
+        verify(mWindowInsetsController).show(navigationBars());
+    }
+
+    @Test
+    public void hideView_viewControllerOnlyShown_statusBarShown() {
+        setupOverlayViewController1();
+        when(mOverlayViewController1.shouldFocusWindow()).thenReturn(true);
+        setOverlayViewControllerAsShowing(mOverlayViewController1);
+
+        mOverlayViewGlobalStateController.hideView(mOverlayViewController1, mRunnable);
+
+        verify(mWindowInsetsController).show(statusBars());
+    }
+
+    @Test
+    public void hideView_viewControllerOnlyShown_insetsAdjustedToDefault() {
+        setupOverlayViewController1();
+        setOverlayViewControllerAsShowing(mOverlayViewController1);
+
+        mOverlayViewGlobalStateController.hideView(mOverlayViewController1, mRunnable);
+
+        verify(mSystemUIOverlayWindowController).setFitInsetsTypes(statusBars());
     }
 
     @Test
@@ -613,7 +917,7 @@
 
     private void setOverlayViewControllerAsShowing(OverlayViewController overlayViewController) {
         mOverlayViewGlobalStateController.showView(overlayViewController, /* show= */ null);
-        Mockito.reset(mCarNavigationBarController, mSystemUIOverlayWindowController);
+        reset(mSystemUIOverlayWindowController);
         when(mSystemUIOverlayWindowController.getBaseLayout()).thenReturn(mBaseLayout);
     }
 }
diff --git a/packages/CarSystemUI/tests/src/com/android/systemui/wm/BarControlPolicyTest.java b/packages/CarSystemUI/tests/src/com/android/systemui/wm/BarControlPolicyTest.java
new file mode 100644
index 0000000..da7cb8e
--- /dev/null
+++ b/packages/CarSystemUI/tests/src/com/android/systemui/wm/BarControlPolicyTest.java
@@ -0,0 +1,195 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.wm;
+
+import static android.view.WindowInsets.Type.navigationBars;
+import static android.view.WindowInsets.Type.statusBars;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.car.settings.CarSettings;
+import android.provider.Settings;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.systemui.SysuiTestCase;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidTestingRunner.class)
[email protected]
+@SmallTest
+public class BarControlPolicyTest extends SysuiTestCase {
+
+    private static final String PACKAGE_NAME = "sample.app";
+
+    @Before
+    public void setUp() {
+        BarControlPolicy.reset();
+    }
+
+    @After
+    public void tearDown() {
+        Settings.Global.clearProviderForTest();
+    }
+
+    @Test
+    public void reloadFromSetting_notSet_doesNotSetFilters() {
+        BarControlPolicy.reloadFromSetting(mContext);
+
+        assertThat(BarControlPolicy.sImmersiveStatusFilter).isNull();
+    }
+
+    @Test
+    public void reloadFromSetting_invalidPolicyControlString_doesNotSetFilters() {
+        String text = "sample text";
+        Settings.Global.putString(
+                mContext.getContentResolver(),
+                CarSettings.Global.SYSTEM_BAR_VISIBILITY_OVERRIDE,
+                text
+        );
+
+        BarControlPolicy.reloadFromSetting(mContext);
+
+        assertThat(BarControlPolicy.sImmersiveStatusFilter).isNull();
+    }
+
+    @Test
+    public void reloadFromSetting_validPolicyControlString_setsFilters() {
+        String text = "immersive.status=" + PACKAGE_NAME;
+        Settings.Global.putString(
+                mContext.getContentResolver(),
+                CarSettings.Global.SYSTEM_BAR_VISIBILITY_OVERRIDE,
+                text
+        );
+
+        BarControlPolicy.reloadFromSetting(mContext);
+
+        assertThat(BarControlPolicy.sImmersiveStatusFilter).isNotNull();
+    }
+
+    @Test
+    public void reloadFromSetting_filtersSet_doesNotSetFiltersAgain() {
+        String text = "immersive.status=" + PACKAGE_NAME;
+        Settings.Global.putString(
+                mContext.getContentResolver(),
+                CarSettings.Global.SYSTEM_BAR_VISIBILITY_OVERRIDE,
+                text
+        );
+
+        BarControlPolicy.reloadFromSetting(mContext);
+
+        assertThat(BarControlPolicy.reloadFromSetting(mContext)).isFalse();
+    }
+
+    @Test
+    public void getBarVisibilities_policyControlNotSet_showsSystemBars() {
+        int[] visibilities = BarControlPolicy.getBarVisibilities(PACKAGE_NAME);
+
+        assertThat(visibilities[0]).isEqualTo(statusBars() | navigationBars());
+        assertThat(visibilities[1]).isEqualTo(0);
+    }
+
+    @Test
+    public void getBarVisibilities_immersiveStatusForAppAndMatchingApp_hidesStatusBar() {
+        Settings.Global.putString(
+                mContext.getContentResolver(),
+                CarSettings.Global.SYSTEM_BAR_VISIBILITY_OVERRIDE,
+                "immersive.status=" + PACKAGE_NAME);
+        BarControlPolicy.reloadFromSetting(mContext);
+
+        int[] visibilities = BarControlPolicy.getBarVisibilities(PACKAGE_NAME);
+
+        assertThat(visibilities[0]).isEqualTo(navigationBars());
+        assertThat(visibilities[1]).isEqualTo(statusBars());
+    }
+
+    @Test
+    public void getBarVisibilities_immersiveStatusForAppAndNonMatchingApp_showsSystemBars() {
+        Settings.Global.putString(
+                mContext.getContentResolver(),
+                CarSettings.Global.SYSTEM_BAR_VISIBILITY_OVERRIDE,
+                "immersive.status=" + PACKAGE_NAME);
+        BarControlPolicy.reloadFromSetting(mContext);
+
+        int[] visibilities = BarControlPolicy.getBarVisibilities("sample2.app");
+
+        assertThat(visibilities[0]).isEqualTo(statusBars() | navigationBars());
+        assertThat(visibilities[1]).isEqualTo(0);
+    }
+
+    @Test
+    public void getBarVisibilities_immersiveStatusForAppsAndNonApp_showsSystemBars() {
+        Settings.Global.putString(
+                mContext.getContentResolver(),
+                CarSettings.Global.SYSTEM_BAR_VISIBILITY_OVERRIDE,
+                "immersive.status=apps");
+        BarControlPolicy.reloadFromSetting(mContext);
+
+        int[] visibilities = BarControlPolicy.getBarVisibilities(PACKAGE_NAME);
+
+        assertThat(visibilities[0]).isEqualTo(statusBars() | navigationBars());
+        assertThat(visibilities[1]).isEqualTo(0);
+    }
+
+    @Test
+    public void getBarVisibilities_immersiveFullForAppAndMatchingApp_hidesSystemBars() {
+        Settings.Global.putString(
+                mContext.getContentResolver(),
+                CarSettings.Global.SYSTEM_BAR_VISIBILITY_OVERRIDE,
+                "immersive.full=" + PACKAGE_NAME);
+        BarControlPolicy.reloadFromSetting(mContext);
+
+        int[] visibilities = BarControlPolicy.getBarVisibilities(PACKAGE_NAME);
+
+        assertThat(visibilities[0]).isEqualTo(0);
+        assertThat(visibilities[1]).isEqualTo(statusBars() | navigationBars());
+    }
+
+    @Test
+    public void getBarVisibilities_immersiveFullForAppAndNonMatchingApp_showsSystemBars() {
+        Settings.Global.putString(
+                mContext.getContentResolver(),
+                CarSettings.Global.SYSTEM_BAR_VISIBILITY_OVERRIDE,
+                "immersive.full=" + PACKAGE_NAME);
+        BarControlPolicy.reloadFromSetting(mContext);
+
+        int[] visibilities = BarControlPolicy.getBarVisibilities("sample2.app");
+
+        assertThat(visibilities[0]).isEqualTo(statusBars() | navigationBars());
+        assertThat(visibilities[1]).isEqualTo(0);
+    }
+
+    @Test
+    public void getBarVisibilities_immersiveFullForAppsAndNonApp_showsSystemBars() {
+        Settings.Global.putString(
+                mContext.getContentResolver(),
+                CarSettings.Global.SYSTEM_BAR_VISIBILITY_OVERRIDE,
+                "immersive.full=apps");
+        BarControlPolicy.reloadFromSetting(mContext);
+
+        int[] visibilities = BarControlPolicy.getBarVisibilities(PACKAGE_NAME);
+
+        assertThat(visibilities[0]).isEqualTo(statusBars() | navigationBars());
+        assertThat(visibilities[1]).isEqualTo(0);
+    }
+}
diff --git a/packages/CarSystemUI/tests/src/com/android/systemui/wm/DisplaySystemBarsControllerTest.java b/packages/CarSystemUI/tests/src/com/android/systemui/wm/DisplaySystemBarsControllerTest.java
new file mode 100644
index 0000000..29cc8ee
--- /dev/null
+++ b/packages/CarSystemUI/tests/src/com/android/systemui/wm/DisplaySystemBarsControllerTest.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.wm;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.verify;
+
+import android.car.settings.CarSettings;
+import android.os.Handler;
+import android.os.RemoteException;
+import android.provider.Settings;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+import android.view.IWindowManager;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.TransactionPool;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@RunWith(AndroidTestingRunner.class)
[email protected]
+@SmallTest
+public class DisplaySystemBarsControllerTest extends SysuiTestCase {
+
+    private DisplaySystemBarsController mController;
+
+    private static final int DISPLAY_ID = 1;
+
+    @Mock
+    private SystemWindows mSystemWindows;
+    @Mock
+    private IWindowManager mIWindowManager;
+    @Mock
+    private DisplayController mDisplayController;
+    @Mock
+    private Handler mHandler;
+    @Mock
+    private TransactionPool mTransactionPool;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        mSystemWindows.mContext = mContext;
+        mSystemWindows.mWmService = mIWindowManager;
+
+        mController = new DisplaySystemBarsController(
+                mSystemWindows,
+                mDisplayController,
+                mHandler,
+                mTransactionPool
+        );
+    }
+
+    @Test
+    public void onDisplayAdded_setsDisplayWindowInsetsControllerOnWMService()
+            throws RemoteException {
+        mController.onDisplayAdded(DISPLAY_ID);
+
+        verify(mIWindowManager).setDisplayWindowInsetsController(
+                eq(DISPLAY_ID), any(DisplaySystemBarsController.PerDisplay.class));
+    }
+
+    @Test
+    public void onDisplayAdded_loadsBarControlPolicyFilters() {
+        String text = "sample text";
+        Settings.Global.putString(
+                mContext.getContentResolver(),
+                CarSettings.Global.SYSTEM_BAR_VISIBILITY_OVERRIDE,
+                text
+        );
+
+        mController.onDisplayAdded(DISPLAY_ID);
+
+        assertThat(BarControlPolicy.sSettingValue).isEqualTo(text);
+    }
+
+    @Test
+    public void onDisplayRemoved_unsetsDisplayWindowInsetsControllerInWMService()
+            throws RemoteException {
+        mController.onDisplayAdded(DISPLAY_ID);
+
+        mController.onDisplayRemoved(DISPLAY_ID);
+
+        verify(mIWindowManager).setDisplayWindowInsetsController(
+                DISPLAY_ID, /* displayWindowInsetsController= */ null);
+    }
+}
diff --git a/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java b/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java
index f42bf19..11d1b0a 100644
--- a/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java
+++ b/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java
@@ -483,6 +483,13 @@
     }
 
     @Override
+    protected void onDocIdDeleted(String docId) {
+        Uri uri = DocumentsContract.buildDocumentUri(AUTHORITY, docId);
+        getContext().revokeUriPermission(uri, ~0);
+    }
+
+
+    @Override
     public Cursor queryRoots(String[] projection) throws FileNotFoundException {
         final MatrixCursor result = new MatrixCursor(resolveRootProjection(projection));
         synchronized (mRootsLock) {
diff --git a/packages/SettingsLib/res/drawable/ic_show_x_wifi_signal_0.xml b/packages/SettingsLib/res/drawable/ic_show_x_wifi_signal_0.xml
new file mode 100644
index 0000000..16e9190
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/ic_show_x_wifi_signal_0.xml
@@ -0,0 +1,31 @@
+<!--
+    Copyright (C) 2020 The Android Open Source Project
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="25.50"
+    android:viewportHeight="25.50">
+    <group
+        android:translateX="0.77"
+        android:translateY="0.23" >
+        <path
+            android:pathData="M14,12h6.54l3.12,-3.89c0.39,-0.48 0.29,-1.19 -0.22,-1.54C21.67,5.36 17.55,3 12,3C6.44,3 2.33,5.36 0.56,6.57C0.05,6.92 -0.05,7.63 0.33,8.11L11.16,21.6c0.42,0.53 1.23,0.53 1.66,0L14,20.13V12z"
+            android:fillColor="#FFFFFF"/>
+        <path
+            android:pathData="M22.71,15.67l-1.83,1.83l1.83,1.83c0.38,0.38 0.38,1 0,1.38v0c-0.38,0.38 -1,0.39 -1.38,0l-1.83,-1.83l-1.83,1.83c-0.38,0.38 -1,0.38 -1.38,0l-0.01,-0.01c-0.38,-0.38 -0.38,-1 0,-1.38l1.83,-1.83l-1.82,-1.82c-0.38,-0.38 -0.38,-1 0,-1.38l0.01,-0.01c0.38,-0.38 1,-0.38 1.38,0l1.82,1.82l1.82,-1.82c0.38,-0.38 1,-0.38 1.38,0l0,0C23.09,14.67 23.09,15.29 22.71,15.67z"
+            android:fillColor="#FFFFFF"/>
+    </group>
+</vector>
diff --git a/packages/SettingsLib/res/drawable/ic_show_x_wifi_signal_1.xml b/packages/SettingsLib/res/drawable/ic_show_x_wifi_signal_1.xml
new file mode 100644
index 0000000..4c338c9
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/ic_show_x_wifi_signal_1.xml
@@ -0,0 +1,27 @@
+<!--
+    Copyright (C) 2020 The Android Open Source Project
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="24.0"
+    android:viewportHeight="24.0">
+    <path
+        android:fillColor="@android:color/white"
+        android:pathData="M22,16.41L20.59,15l-2.09,2.09L16.41,15L15,16.41l2.09,2.09L15,20.59L16.41,22l2.09,-2.08L20.59,22L22,20.59l-2.08,-2.09L22,16.41z"/>
+    <path
+        android:fillColor="@android:color/white"
+        android:pathData="M12,2.01C7.25,2.01 2.97,4.09 0,7.4L7.582,16.625C7.582,16.627 7.58,16.629 7.58,16.631L11.99,22L12,22L13,20.789L13,17.641L13,13.119C12.68,13.039 12.34,13 12,13C10.601,13 9.351,13.64 8.531,14.639L2.699,7.539C5.269,5.279 8.58,4.01 12,4.01C15.42,4.01 18.731,5.279 21.301,7.539L16.811,13L19.4,13L24,7.4C21.03,4.09 16.75,2.01 12,2.01z"/>
+</vector>
diff --git a/packages/SettingsLib/res/drawable/ic_show_x_wifi_signal_2.xml b/packages/SettingsLib/res/drawable/ic_show_x_wifi_signal_2.xml
new file mode 100644
index 0000000..79037db
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/ic_show_x_wifi_signal_2.xml
@@ -0,0 +1,27 @@
+<!--
+    Copyright (C) 2020 The Android Open Source Project
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="24.0"
+    android:viewportHeight="24.0">
+    <path
+        android:fillColor="@android:color/white"
+        android:pathData="M22,16.41L20.59,15l-2.09,2.09L16.41,15L15,16.41l2.09,2.09L15,20.59L16.41,22l2.09,-2.08L20.59,22L22,20.59l-2.08,-2.09L22,16.41z"/>
+    <path
+        android:fillColor="@android:color/white"
+        android:pathData="M12,2C7.25,2 2.97,4.081 0,7.391L12,22L13,20.779L13,17.631L13,13L16.801,13L18,13L19.391,13L24,7.391C21.03,4.081 16.75,2 12,2zM12,4C14.747,4 17.423,4.819 19.701,6.313C20.259,6.678 20.795,7.085 21.301,7.529L17.389,12.287C16.029,10.868 14.119,9.99 12,9.99C9.88,9.99 7.969,10.869 6.609,12.289L2.699,7.529C5.269,5.269 8.58,4 12,4z"/>
+</vector>
diff --git a/packages/SettingsLib/res/drawable/ic_show_x_wifi_signal_3.xml b/packages/SettingsLib/res/drawable/ic_show_x_wifi_signal_3.xml
new file mode 100644
index 0000000..21ad128
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/ic_show_x_wifi_signal_3.xml
@@ -0,0 +1,27 @@
+<!--
+    Copyright (C) 2020 The Android Open Source Project
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="24.0"
+    android:viewportHeight="24.0">
+    <path
+        android:fillColor="@android:color/white"
+        android:pathData="M22,16.41L20.59,15l-2.09,2.09L16.41,15L15,16.41l2.09,2.09L15,20.59L16.41,22l2.09,-2.08L20.59,22L22,20.59l-2.08,-2.09L22,16.41z"/>
+    <path
+        android:fillColor="@android:color/white"
+        android:pathData="M12,2C7.25,2 2.97,4.081 0,7.391L3.301,11.41L12,22L13,20.779L13,17.631L13,13L16.801,13L19.391,13L20.699,11.41C20.699,11.409 20.698,11.409 20.697,11.408L24,7.391C21.03,4.081 16.75,2 12,2zM12,4C15.42,4 18.731,5.269 21.301,7.529L19.35,9.9C17.43,8.1 14.86,6.99 12,6.99C9.14,6.99 6.57,8.1 4.65,9.9C4.65,9.901 4.649,9.902 4.648,9.902L2.699,7.529C5.269,5.269 8.58,4 12,4z"/>
+</vector>
diff --git a/packages/SettingsLib/res/drawable/ic_show_x_wifi_signal_4.xml b/packages/SettingsLib/res/drawable/ic_show_x_wifi_signal_4.xml
new file mode 100644
index 0000000..2ec5ba3
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/ic_show_x_wifi_signal_4.xml
@@ -0,0 +1,27 @@
+<!--
+    Copyright (C) 2020 The Android Open Source Project
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="24.0"
+    android:viewportHeight="24.0">
+    <path
+        android:fillColor="@android:color/white"
+        android:pathData="M12,2C7.25,2 2.97,4.08 0,7.39L12,22l1,-1.22V13h6.39L24,7.39C21.03,4.08 16.75,2 12,2z"/>
+    <path
+        android:fillColor="@android:color/white"
+        android:pathData="M22,16.41L20.59,15l-2.09,2.09L16.41,15L15,16.41l2.09,2.09L15,20.59L16.41,22l2.09,-2.08L20.59,22L22,20.59l-2.08,-2.09L22,16.41z"/>
+</vector>
diff --git a/packages/SettingsLib/src/com/android/settingslib/Utils.java b/packages/SettingsLib/src/com/android/settingslib/Utils.java
index a43412e..b280806 100644
--- a/packages/SettingsLib/src/com/android/settingslib/Utils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/Utils.java
@@ -49,11 +49,19 @@
     private static String sSharedSystemSharedLibPackageName;
 
     static final int[] WIFI_PIE = {
-            com.android.internal.R.drawable.ic_wifi_signal_0,
-            com.android.internal.R.drawable.ic_wifi_signal_1,
-            com.android.internal.R.drawable.ic_wifi_signal_2,
-            com.android.internal.R.drawable.ic_wifi_signal_3,
-            com.android.internal.R.drawable.ic_wifi_signal_4
+        com.android.internal.R.drawable.ic_wifi_signal_0,
+        com.android.internal.R.drawable.ic_wifi_signal_1,
+        com.android.internal.R.drawable.ic_wifi_signal_2,
+        com.android.internal.R.drawable.ic_wifi_signal_3,
+        com.android.internal.R.drawable.ic_wifi_signal_4
+    };
+
+    static final int[] SHOW_X_WIFI_PIE = {
+        R.drawable.ic_show_x_wifi_signal_0,
+        R.drawable.ic_show_x_wifi_signal_1,
+        R.drawable.ic_show_x_wifi_signal_2,
+        R.drawable.ic_show_x_wifi_signal_3,
+        R.drawable.ic_show_x_wifi_signal_4
     };
 
     public static void updateLocationEnabled(Context context, boolean enabled, int userId,
@@ -353,10 +361,22 @@
      * @throws IllegalArgumentException if an invalid RSSI level is given.
      */
     public static int getWifiIconResource(int level) {
+        return getWifiIconResource(false /* showX */, level);
+    }
+
+    /**
+     * Returns the Wifi icon resource for a given RSSI level.
+     *
+     * @param showX True if a connected Wi-Fi network has the problem which should show Pie+x
+     *              signal icon to users.
+     * @param level The number of bars to show (0-4)
+     * @throws IllegalArgumentException if an invalid RSSI level is given.
+     */
+    public static int getWifiIconResource(boolean showX, int level) {
         if (level < 0 || level >= WIFI_PIE.length) {
             throw new IllegalArgumentException("No Wifi icon found for level: " + level);
         }
-        return WIFI_PIE[level];
+        return showX ? SHOW_X_WIFI_PIE[level] : WIFI_PIE[level];
     }
 
     public static int getDefaultStorageManagerDaysToRetain(Resources resources) {
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java
index 9d06c84..72a6074 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java
@@ -465,7 +465,16 @@
             synchronized (mMediaDevicesLock) {
                 mMediaDevices.clear();
                 mMediaDevices.addAll(devices);
-                mMediaDevices.addAll(buildDisconnectedBluetoothDevice());
+                // Add disconnected bluetooth devices only when phone output device is available.
+                for (MediaDevice device : devices) {
+                    final int type = device.getDeviceType();
+                    if (type == MediaDevice.MediaDeviceType.TYPE_USB_C_AUDIO_DEVICE
+                            || type == MediaDevice.MediaDeviceType.TYPE_3POINT5_MM_AUDIO_DEVICE
+                            || type == MediaDevice.MediaDeviceType.TYPE_PHONE_DEVICE) {
+                        mMediaDevices.addAll(buildDisconnectedBluetoothDevice());
+                        break;
+                    }
+                }
             }
 
             final MediaDevice infoMediaDevice = mInfoMediaManager.getCurrentConnectedDevice();
diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiEntryPreference.java b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiEntryPreference.java
index a53bc9f..aad0d3a 100644
--- a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiEntryPreference.java
+++ b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiEntryPreference.java
@@ -64,6 +64,7 @@
     private final IconInjector mIconInjector;
     private WifiEntry mWifiEntry;
     private int mLevel = -1;
+    private boolean mShowX; // Shows the Wi-Fi signl icon of Pie+x when it's true.
     private CharSequence mContentDescription;
     private OnButtonClickListener mOnButtonClickListener;
 
@@ -136,9 +137,11 @@
     public void refresh() {
         setTitle(mWifiEntry.getTitle());
         final int level = mWifiEntry.getLevel();
-        if (level != mLevel) {
+        final boolean showX = mWifiEntry.shouldShowXLevelIcon();
+        if (level != mLevel || showX != mShowX) {
             mLevel = level;
-            updateIcon(mLevel);
+            mShowX = showX;
+            updateIcon(mShowX, mLevel);
             notifyChanged();
         }
 
@@ -184,13 +187,13 @@
     }
 
 
-    private void updateIcon(int level) {
+    private void updateIcon(boolean showX, int level) {
         if (level == -1) {
             setIcon(null);
             return;
         }
 
-        final Drawable drawable = mIconInjector.getIcon(level);
+        final Drawable drawable = mIconInjector.getIcon(showX, level);
         if (drawable != null) {
             drawable.setTintList(Utils.getColorAttr(getContext(),
                     android.R.attr.colorControlNormal));
@@ -260,8 +263,8 @@
             mContext = context;
         }
 
-        public Drawable getIcon(int level) {
-            return mContext.getDrawable(Utils.getWifiIconResource(level));
+        public Drawable getIcon(boolean showX, int level) {
+            return mContext.getDrawable(Utils.getWifiIconResource(showX, level));
         }
     }
 
diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiStatusTracker.java b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiStatusTracker.java
index bc58bfc..c57d4ad 100644
--- a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiStatusTracker.java
+++ b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiStatusTracker.java
@@ -13,6 +13,7 @@
 import static android.net.NetworkCapabilities.NET_CAPABILITY_CAPTIVE_PORTAL;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_PARTIAL_CONNECTIVITY;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED;
+import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
 
 import android.content.Context;
 import android.content.Intent;
@@ -250,6 +251,10 @@
                     statusLabel = mContext.getString(R.string.wifi_status_no_internet);
                 }
                 return;
+            } else if (!isDefaultNetwork && mDefaultNetworkCapabilities != null
+                    && mDefaultNetworkCapabilities.hasTransport(TRANSPORT_CELLULAR)) {
+                statusLabel = mContext.getString(R.string.wifi_connected_low_quality);
+                return;
             }
         }
 
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/LocalMediaManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/LocalMediaManagerTest.java
index a654fd4..8e850b2 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/LocalMediaManagerTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/LocalMediaManagerTest.java
@@ -585,6 +585,7 @@
         when(device1.getId()).thenReturn(TEST_DEVICE_ID_1);
         when(device2.getId()).thenReturn(TEST_DEVICE_ID_2);
         when(device3.getId()).thenReturn(TEST_DEVICE_ID_3);
+        when(device1.getDeviceType()).thenReturn(MediaDevice.MediaDeviceType.TYPE_PHONE_DEVICE);
         when(mLocalMediaManager.mPhoneDevice.getId()).thenReturn("test_phone_id");
 
         assertThat(mLocalMediaManager.mMediaDevices).hasSize(2);
@@ -683,6 +684,7 @@
         when(device1.getId()).thenReturn(TEST_DEVICE_ID_1);
         when(device2.getId()).thenReturn(TEST_DEVICE_ID_2);
         when(device3.getId()).thenReturn(TEST_DEVICE_ID_3);
+        when(device1.getDeviceType()).thenReturn(MediaDevice.MediaDeviceType.TYPE_PHONE_DEVICE);
         when(mLocalMediaManager.mPhoneDevice.getId()).thenReturn("test_phone_id");
 
         assertThat(mLocalMediaManager.mMediaDevices).hasSize(2);
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/wifi/WifiEntryPreferenceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/wifi/WifiEntryPreferenceTest.java
index 46e699d..c21830b 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/wifi/WifiEntryPreferenceTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/wifi/WifiEntryPreferenceTest.java
@@ -62,6 +62,17 @@
     @Mock
     private Drawable mMockDrawable4;
 
+    @Mock
+    private Drawable mMockShowXDrawable0;
+    @Mock
+    private Drawable mMockShowXDrawable1;
+    @Mock
+    private Drawable mMockShowXDrawable2;
+    @Mock
+    private Drawable mMockShowXDrawable3;
+    @Mock
+    private Drawable mMockShowXDrawable4;
+
     private static final String MOCK_TITLE = "title";
     private static final String MOCK_SUMMARY = "summary";
     private static final String FAKE_URI_STRING = "fakeuri";
@@ -75,11 +86,22 @@
         when(mMockWifiEntry.getTitle()).thenReturn(MOCK_TITLE);
         when(mMockWifiEntry.getSummary(false /* concise */)).thenReturn(MOCK_SUMMARY);
 
-        when(mMockIconInjector.getIcon(0)).thenReturn(mMockDrawable0);
-        when(mMockIconInjector.getIcon(1)).thenReturn(mMockDrawable1);
-        when(mMockIconInjector.getIcon(2)).thenReturn(mMockDrawable2);
-        when(mMockIconInjector.getIcon(3)).thenReturn(mMockDrawable3);
-        when(mMockIconInjector.getIcon(4)).thenReturn(mMockDrawable4);
+        when(mMockIconInjector.getIcon(false /* showX */, 0)).thenReturn(mMockDrawable0);
+        when(mMockIconInjector.getIcon(false /* showX */, 1)).thenReturn(mMockDrawable1);
+        when(mMockIconInjector.getIcon(false /* showX */, 2)).thenReturn(mMockDrawable2);
+        when(mMockIconInjector.getIcon(false /* showX */, 3)).thenReturn(mMockDrawable3);
+        when(mMockIconInjector.getIcon(false /* showX */, 4)).thenReturn(mMockDrawable4);
+
+        when(mMockIconInjector.getIcon(true /* showX */, 0))
+                .thenReturn(mMockShowXDrawable0);
+        when(mMockIconInjector.getIcon(true /* showX */, 1))
+                .thenReturn(mMockShowXDrawable1);
+        when(mMockIconInjector.getIcon(true /* showX */, 2))
+                .thenReturn(mMockShowXDrawable2);
+        when(mMockIconInjector.getIcon(true /* showX */, 3))
+                .thenReturn(mMockShowXDrawable3);
+        when(mMockIconInjector.getIcon(true /* showX */, 4))
+                .thenReturn(mMockShowXDrawable4);
     }
 
     @Test
@@ -155,6 +177,36 @@
     }
 
     @Test
+    public void levelChanged_showXWifiRefresh_shouldUpdateLevelIcon() {
+        final List<Drawable> iconList = new ArrayList<>();
+        when(mMockWifiEntry.shouldShowXLevelIcon()).thenReturn(true);
+        final WifiEntryPreference pref =
+                new WifiEntryPreference(mContext, mMockWifiEntry, mMockIconInjector);
+
+        when(mMockWifiEntry.getLevel()).thenReturn(0);
+        pref.refresh();
+        iconList.add(pref.getIcon());
+        when(mMockWifiEntry.getLevel()).thenReturn(1);
+        pref.refresh();
+        iconList.add(pref.getIcon());
+        when(mMockWifiEntry.getLevel()).thenReturn(2);
+        pref.refresh();
+        iconList.add(pref.getIcon());
+        when(mMockWifiEntry.getLevel()).thenReturn(3);
+        pref.refresh();
+        iconList.add(pref.getIcon());
+        when(mMockWifiEntry.getLevel()).thenReturn(4);
+        pref.refresh();
+        iconList.add(pref.getIcon());
+        when(mMockWifiEntry.getLevel()).thenReturn(-1);
+        pref.refresh();
+        iconList.add(pref.getIcon());
+
+        assertThat(iconList).containsExactly(mMockShowXDrawable0, mMockShowXDrawable1,
+                mMockShowXDrawable2, mMockShowXDrawable3, mMockShowXDrawable4, null);
+    }
+
+    @Test
     public void notNull_whenGetHelpUriString_shouldSetImageButtonVisible() {
         when(mMockWifiEntry.getHelpUriString()).thenReturn(FAKE_URI_STRING);
         final WifiEntryPreference pref =
diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
index d05e6e1..3055104 100644
--- a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
+++ b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
@@ -166,6 +166,9 @@
         Settings.Secure.PEOPLE_STRIP,
         Settings.Secure.MEDIA_CONTROLS_RESUME,
         Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE,
-        Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS
+        Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS,
+        Settings.Secure.ADAPTIVE_CONNECTIVITY_ENABLED,
+        Settings.Secure.ASSIST_HANDLES_LEARNING_TIME_ELAPSED_MILLIS,
+        Settings.Secure.ASSIST_HANDLES_LEARNING_EVENT_COUNT
     };
 }
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
index fa810bd..f1846db 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
@@ -251,5 +251,9 @@
         VALIDATORS.put(
                 Secure.ACCESSIBILITY_BUTTON_TARGETS,
                 ACCESSIBILITY_SHORTCUT_TARGET_LIST_VALIDATOR);
+        VALIDATORS.put(Secure.ADAPTIVE_CONNECTIVITY_ENABLED, BOOLEAN_VALIDATOR);
+        VALIDATORS.put(
+                Secure.ASSIST_HANDLES_LEARNING_TIME_ELAPSED_MILLIS, NONE_NEGATIVE_LONG_VALIDATOR);
+        VALIDATORS.put(Secure.ASSIST_HANDLES_LEARNING_EVENT_COUNT, NON_NEGATIVE_INTEGER_VALIDATOR);
     }
 }
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
index 779293f..89a55e4 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
@@ -1875,6 +1875,15 @@
                 SecureSettingsProto.Assist.GESTURE_SETUP_COMPLETE);
         p.end(assistToken);
 
+        final long assistHandlesToken = p.start(SecureSettingsProto.ASSIST_HANDLES);
+        dumpSetting(s, p,
+                Settings.Secure.ASSIST_HANDLES_LEARNING_TIME_ELAPSED_MILLIS,
+                SecureSettingsProto.AssistHandles.LEARNING_TIME_ELAPSED_MILLIS);
+        dumpSetting(s, p,
+                Settings.Secure.ASSIST_HANDLES_LEARNING_EVENT_COUNT,
+                SecureSettingsProto.AssistHandles.LEARNING_EVENT_COUNT);
+        p.end(assistHandlesToken);
+
         final long autofillToken = p.start(SecureSettingsProto.AUTOFILL);
         dumpSetting(s, p,
                 Settings.Secure.AUTOFILL_SERVICE,
@@ -1976,6 +1985,9 @@
         dumpSetting(s, p,
                 Settings.Secure.CONNECTIVITY_RELEASE_PENDING_INTENT_DELAY_MS,
                 SecureSettingsProto.CONNECTIVITY_RELEASE_PENDING_INTENT_DELAY_MS);
+        dumpSetting(s, p,
+                Settings.Secure.ADAPTIVE_CONNECTIVITY_ENABLED,
+                SecureSettingsProto.ADAPTIVE_CONNECTIVITY_ENABLED);
 
         final long controlsToken = p.start(SecureSettingsProto.CONTROLS);
         dumpSetting(s, p,
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/WriteFallbackSettingsFilesJobService.java b/packages/SettingsProvider/src/com/android/providers/settings/WriteFallbackSettingsFilesJobService.java
index 6e5b8890..66aa7ba 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/WriteFallbackSettingsFilesJobService.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/WriteFallbackSettingsFilesJobService.java
@@ -35,19 +35,17 @@
 public class WriteFallbackSettingsFilesJobService extends JobService {
     @Override
     public boolean onStartJob(final JobParameters params) {
-        switch (params.getJobId()) {
-            case WRITE_FALLBACK_SETTINGS_FILES_JOB_ID:
-                final List<String> settingsFiles = new ArrayList<>();
-                settingsFiles.add(params.getExtras().getString(TABLE_GLOBAL, ""));
-                settingsFiles.add(params.getExtras().getString(TABLE_SYSTEM, ""));
-                settingsFiles.add(params.getExtras().getString(TABLE_SECURE, ""));
-                settingsFiles.add(params.getExtras().getString(TABLE_SSAID, ""));
-                settingsFiles.add(params.getExtras().getString(TABLE_CONFIG, ""));
-                SettingsProvider.writeFallBackSettingsFiles(settingsFiles);
-                return true;
-            default:
-                return false;
+        if (params.getJobId() != WRITE_FALLBACK_SETTINGS_FILES_JOB_ID) {
+            return false;
         }
+        final List<String> settingsFiles = new ArrayList<>();
+        settingsFiles.add(params.getExtras().getString(TABLE_GLOBAL, ""));
+        settingsFiles.add(params.getExtras().getString(TABLE_SYSTEM, ""));
+        settingsFiles.add(params.getExtras().getString(TABLE_SECURE, ""));
+        settingsFiles.add(params.getExtras().getString(TABLE_SSAID, ""));
+        settingsFiles.add(params.getExtras().getString(TABLE_CONFIG, ""));
+        SettingsProvider.writeFallBackSettingsFiles(settingsFiles);
+        return false;
     }
 
     @Override
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index a7ef5e6..6e74184 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -113,6 +113,7 @@
     <uses-permission android:name="android.permission.SET_ORIENTATION" />
     <uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
     <uses-permission android:name="android.permission.MONITOR_INPUT" />
+    <uses-permission android:name="android.permission.INPUT_CONSUMER" />
 
     <!-- DreamManager -->
     <uses-permission android:name="android.permission.READ_DREAM_STATE" />
@@ -239,6 +240,9 @@
 
     <!-- Listen app op changes -->
     <uses-permission android:name="android.permission.WATCH_APPOPS" />
+    <uses-permission android:name="android.permission.OBSERVE_GRANT_REVOKE_PERMISSIONS" />
+    <!-- For handling silent audio recordings -->
+    <uses-permission android:name="android.permission.MODIFY_AUDIO_ROUTING" />
 
     <!-- to read and change hvac values in a car -->
     <uses-permission android:name="android.car.permission.CONTROL_CAR_CLIMATE" />
@@ -266,6 +270,7 @@
     <!-- Permission to make accessibility service access Bubbles -->
     <uses-permission android:name="android.permission.ADD_TRUSTED_DISPLAY" />
 
+
     <protected-broadcast android:name="com.android.settingslib.action.REGISTER_SLICE_RECEIVER" />
     <protected-broadcast android:name="com.android.settingslib.action.UNREGISTER_SLICE_RECEIVER" />
     <protected-broadcast android:name="com.android.settings.flashlight.action.FLASHLIGHT_CHANGED" />
@@ -395,19 +400,15 @@
 
         <!-- Springboard for launching the share and edit activity. This needs to be in the main
              system ui process since we need to notify the status bar to dismiss the keyguard -->
-        <receiver android:name=".screenshot.GlobalScreenshot$ActionProxyReceiver"
-            android:exported="false" />
-
-        <!-- Callback for dismissing screenshot notification after a share target is picked -->
-        <receiver android:name=".screenshot.GlobalScreenshot$TargetChosenReceiver"
+        <receiver android:name=".screenshot.ActionProxyReceiver"
             android:exported="false" />
 
         <!-- Callback for deleting screenshot notification -->
-        <receiver android:name=".screenshot.GlobalScreenshot$DeleteScreenshotReceiver"
+        <receiver android:name=".screenshot.DeleteScreenshotReceiver"
             android:exported="false" />
 
         <!-- Callback for invoking a smart action from the screenshot notification. -->
-        <receiver android:name=".screenshot.GlobalScreenshot$SmartActionsReceiver"
+        <receiver android:name=".screenshot.SmartActionsReceiver"
                   android:exported="false"/>
 
         <!-- started from UsbDeviceSettingsManager -->
@@ -720,10 +721,9 @@
         <service android:name=".controls.controller.AuxiliaryPersistenceWrapper$DeletionJobService"
                  android:permission="android.permission.BIND_JOB_SERVICE"/>
 
-        <!-- started from ControlsFavoritingActivity -->
+        <!-- started from ControlsRequestReceiver -->
         <activity
             android:name=".controls.management.ControlsRequestDialog"
-            android:exported="true"
             android:theme="@style/Theme.ControlsRequestDialog"
             android:finishOnCloseSystemDialogs="true"
             android:showForAllUsers="true"
diff --git a/packages/SystemUI/res-keyguard/drawable/kg_emergency_button_background.xml b/packages/SystemUI/res-keyguard/drawable/kg_emergency_button_background.xml
new file mode 100644
index 0000000..cc2089f
--- /dev/null
+++ b/packages/SystemUI/res-keyguard/drawable/kg_emergency_button_background.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2020 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+<ripple xmlns:android="http://schemas.android.com/apk/res/android"
+    android:color="?attr/wallpaperTextColorSecondary">
+    <item android:id="@android:id/background">
+        <shape
+            android:color="@android:color/transparent">
+            <stroke android:width="1dp" android:color="?attr/wallpaperTextColorSecondary"/>
+            <corners android:radius="24dp"/>
+        </shape>
+    </item>
+    <item android:id="@android:id/mask">
+        <shape android:shape="rectangle">
+            <solid android:color="?attr/wallpaperTextColorSecondary"/>
+            <corners android:radius="24dp"/>
+        </shape>
+    </item>
+</ripple>
\ No newline at end of file
diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_emergency_carrier_area.xml b/packages/SystemUI/res-keyguard/layout/keyguard_emergency_carrier_area.xml
index 3018a022..370576b 100644
--- a/packages/SystemUI/res-keyguard/layout/keyguard_emergency_carrier_area.xml
+++ b/packages/SystemUI/res-keyguard/layout/keyguard_emergency_carrier_area.xml
@@ -33,6 +33,7 @@
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
         style="@style/Keyguard.TextView"
+        android:layout_marginBottom="8dp"
         android:singleLine="true"
         android:ellipsize="marquee"
         android:visibility="gone"
@@ -42,11 +43,9 @@
     <com.android.keyguard.EmergencyButton
         android:id="@+id/emergency_call_button"
         android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:layout_weight="1"
-        android:layout_marginTop="@dimen/eca_overlap"
+        android:layout_height="32dp"
+        android:layout_marginBottom="12dp"
         android:text="@*android:string/lockscreen_emergency_call"
-        style="@style/Keyguard.TextView.EmergencyButton"
-        android:textAllCaps="@bool/kg_use_all_caps" />
+        style="@style/Keyguard.TextView.EmergencyButton" />
 
 </com.android.keyguard.EmergencyCarrierArea>
diff --git a/packages/SystemUI/res-keyguard/values/styles.xml b/packages/SystemUI/res-keyguard/values/styles.xml
index 5f2a946..401f3e3 100644
--- a/packages/SystemUI/res-keyguard/values/styles.xml
+++ b/packages/SystemUI/res-keyguard/values/styles.xml
@@ -23,10 +23,13 @@
         <item name="android:textColor">?attr/wallpaperTextColorSecondary</item>
         <item name="android:textSize">@dimen/kg_status_line_font_size</item>
     </style>
-    <style name="Keyguard.TextView.EmergencyButton" parent="@android:style/DeviceDefault.ButtonBar">
+    <style name="Keyguard.TextView.EmergencyButton" parent="Theme.SystemUI">
         <item name="android:textColor">?attr/wallpaperTextColorSecondary</item>
-        <item name="android:textSize">@dimen/kg_status_line_font_size</item>
-        <item name="android:background">@null</item>
+        <item name="android:textSize">14dp</item>
+        <item name="android:background">@drawable/kg_emergency_button_background</item>
+        <item name="android:fontFamily">@*android:string/config_headlineFontFamily</item>
+        <item name="android:paddingLeft">12dp</item>
+        <item name="android:paddingRight">12dp</item>
     </style>
     <style name="Widget.TextView.NumPadKey" parent="@android:style/Widget.TextView">
         <item name="android:singleLine">true</item>
diff --git a/packages/SystemUI/res/drawable/privacy_chip_bg.xml b/packages/SystemUI/res/drawable/privacy_chip_bg.xml
new file mode 100644
index 0000000..827cf4a9
--- /dev/null
+++ b/packages/SystemUI/res/drawable/privacy_chip_bg.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2020 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<shape xmlns:android="http://schemas.android.com/apk/res/android">
+    <solid android:color="#242424" /> <!-- 14% of white -->
+    <padding android:paddingTop="@dimen/ongoing_appops_chip_bg_padding"
+        android:paddingBottom="@dimen/ongoing_appops_chip_bg_padding" />
+    <corners android:radius="@dimen/ongoing_appops_chip_bg_corner_radius" />
+</shape>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/controls_management.xml b/packages/SystemUI/res/layout/controls_management.xml
index ae7f44d..b9e711e 100644
--- a/packages/SystemUI/res/layout/controls_management.xml
+++ b/packages/SystemUI/res/layout/controls_management.xml
@@ -50,7 +50,7 @@
 
     <FrameLayout
         android:layout_width="match_parent"
-        android:layout_height="72dp">
+        android:layout_height="@dimen/controls_management_footer_height">
 
         <View
             android:layout_width="match_parent"
@@ -61,7 +61,8 @@
         <androidx.constraintlayout.widget.ConstraintLayout
             android:layout_width="match_parent"
             android:layout_height="match_parent"
-            android:padding="@dimen/controls_management_footer_side_margin">
+            android:paddingHorizontal="@dimen/controls_management_footer_side_margin"
+            android:paddingVertical="@dimen/controls_management_footer_top_margin" >
 
             <Button
                 android:id="@+id/other_apps"
diff --git a/packages/SystemUI/res/layout/controls_management_favorites.xml b/packages/SystemUI/res/layout/controls_management_favorites.xml
index 4850e75..0ddd0e38 100644
--- a/packages/SystemUI/res/layout/controls_management_favorites.xml
+++ b/packages/SystemUI/res/layout/controls_management_favorites.xml
@@ -36,7 +36,7 @@
         android:layout_width="wrap_content"
         android:layout_height="@dimen/controls_management_page_indicator_height"
         android:layout_gravity="center"
-        android:layout_marginTop="@dimen/controls_management_list_margin"
+        android:layout_marginTop="@dimen/controls_management_indicator_top_margin"
         android:visibility="invisible" />
 
     <androidx.viewpager2.widget.ViewPager2
diff --git a/packages/SystemUI/res/layout/controls_structure_page.xml b/packages/SystemUI/res/layout/controls_structure_page.xml
index f048d62..412ed56 100644
--- a/packages/SystemUI/res/layout/controls_structure_page.xml
+++ b/packages/SystemUI/res/layout/controls_structure_page.xml
@@ -21,4 +21,4 @@
     android:layout_width="match_parent"
     android:layout_height="match_parent"
     android:orientation="vertical"
-    android:layout_marginTop="@dimen/controls_management_zone_top_margin"/>
\ No newline at end of file
+    android:layout_marginTop="@dimen/controls_management_favorites_top_margin"/>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/global_screenshot_action_chip.xml b/packages/SystemUI/res/layout/global_screenshot_action_chip.xml
index 46396e3..4b3534b 100644
--- a/packages/SystemUI/res/layout/global_screenshot_action_chip.xml
+++ b/packages/SystemUI/res/layout/global_screenshot_action_chip.xml
@@ -32,6 +32,7 @@
         android:gravity="center">
         <ImageView
             android:id="@+id/screenshot_action_chip_icon"
+            android:tint="@*android:color/accent_device_default"
             android:layout_width="@dimen/screenshot_action_chip_icon_size"
             android:layout_height="@dimen/screenshot_action_chip_icon_size"
             android:layout_marginStart="@dimen/screenshot_action_chip_padding_start"
diff --git a/packages/SystemUI/res/layout/media_view.xml b/packages/SystemUI/res/layout/media_view.xml
index 3c641af..170f2c4 100644
--- a/packages/SystemUI/res/layout/media_view.xml
+++ b/packages/SystemUI/res/layout/media_view.xml
@@ -166,8 +166,7 @@
         android:layout_height="wrap_content"
         android:clickable="true"
         android:maxHeight="@dimen/qs_media_enabled_seekbar_height"
-        android:paddingTop="16dp"
-        android:paddingBottom="16dp"
+        android:paddingVertical="@dimen/qs_media_enabled_seekbar_vertical_padding"
         android:thumbTint="@color/media_primary_text"
         android:progressTint="@color/media_seekbar_progress"
         android:progressBackgroundTint="@color/media_disabled"
@@ -210,8 +209,98 @@
         android:layout_width="@dimen/qs_media_icon_size"
         android:layout_height="@dimen/qs_media_icon_size" />
 
-    <!-- Buttons to remove this view when no longer needed -->
-    <include
-        layout="@layout/qs_media_panel_options"
-        android:visibility="gone" />
+    <!-- Constraints are set here as they are the same regardless of host -->
+    <TextView
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginTop="@dimen/qs_media_panel_outer_padding"
+        android:layout_marginStart="@dimen/qs_media_panel_outer_padding"
+        android:layout_marginEnd="@dimen/qs_media_panel_outer_padding"
+        android:id="@+id/media_text"
+        android:fontFamily="@*android:string/config_headlineFontFamilyMedium"
+        android:textColor="@color/media_primary_text"
+        android:text="@string/controls_media_title"
+        app:layout_constraintTop_toTopOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintBottom_toTopOf="@id/remove_text"
+        app:layout_constraintVertical_chainStyle="spread_inside"/>
+
+    <TextView
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginStart="@dimen/qs_media_panel_outer_padding"
+        android:layout_marginEnd="@dimen/qs_media_panel_outer_padding"
+        android:id="@+id/remove_text"
+        android:fontFamily="@*android:string/config_headlineFontFamily"
+        android:singleLine="true"
+        android:textColor="@color/media_primary_text"
+        android:text="@string/controls_media_close_session"
+        app:layout_constraintTop_toBottomOf="@id/media_text"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintBottom_toTopOf="@id/settings"/>
+
+    <FrameLayout
+        android:id="@+id/settings"
+        android:background="@drawable/qs_media_light_source"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginStart="@dimen/qs_media_panel_outer_padding"
+        android:paddingBottom="@dimen/qs_media_panel_outer_padding"
+        android:minWidth="48dp"
+        android:minHeight="48dp"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@id/remove_text">
+
+        <TextView
+            android:layout_gravity="bottom"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:fontFamily="@*android:string/config_headlineFontFamilyMedium"
+            android:textColor="@android:color/white"
+            android:text="@string/controls_media_settings_button" />
+    </FrameLayout>
+
+    <FrameLayout
+        android:id="@+id/cancel"
+        android:background="@drawable/qs_media_light_source"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginEnd="@dimen/qs_media_panel_outer_padding"
+        android:paddingBottom="@dimen/qs_media_panel_outer_padding"
+        android:minWidth="48dp"
+        android:minHeight="48dp"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintEnd_toStartOf="@id/dismiss" >
+
+        <TextView
+            android:layout_gravity="bottom"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:fontFamily="@*android:string/config_headlineFontFamilyMedium"
+            android:textColor="@android:color/white"
+            android:text="@string/cancel" />
+    </FrameLayout>
+
+    <FrameLayout
+        android:id="@+id/dismiss"
+        android:background="@drawable/qs_media_light_source"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginEnd="@dimen/qs_media_panel_outer_padding"
+        android:paddingBottom="@dimen/qs_media_panel_outer_padding"
+        android:minWidth="48dp"
+        android:minHeight="48dp"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintEnd_toEndOf="parent">
+
+        <TextView
+            android:layout_gravity="bottom"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:fontFamily="@*android:string/config_headlineFontFamilyMedium"
+            android:textColor="@android:color/white"
+            android:text="@string/controls_media_dismiss_button"
+        />
+    </FrameLayout>
 </com.android.systemui.util.animation.TransitionLayout>
diff --git a/packages/SystemUI/res/layout/ongoing_privacy_chip.xml b/packages/SystemUI/res/layout/ongoing_privacy_chip.xml
new file mode 100644
index 0000000..3c30632
--- /dev/null
+++ b/packages/SystemUI/res/layout/ongoing_privacy_chip.xml
@@ -0,0 +1,40 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2020 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+
+<com.android.systemui.privacy.OngoingPrivacyChip
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/privacy_chip"
+    android:layout_height="match_parent"
+    android:layout_width="wrap_content"
+    android:layout_gravity="center_vertical|end"
+    android:focusable="true" >
+
+        <FrameLayout
+            android:id="@+id/background"
+            android:layout_height="@dimen/ongoing_appops_chip_height"
+            android:layout_width="wrap_content"
+            android:minWidth="48dp"
+            android:layout_gravity="center_vertical">
+                <LinearLayout
+                    android:id="@+id/icons_container"
+                    android:layout_height="match_parent"
+                    android:layout_width="wrap_content"
+                    android:gravity="center_vertical"
+                    />
+          </FrameLayout>
+</com.android.systemui.privacy.OngoingPrivacyChip>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/qs_footer_impl.xml b/packages/SystemUI/res/layout/qs_footer_impl.xml
index 5c00af5..436188a 100644
--- a/packages/SystemUI/res/layout/qs_footer_impl.xml
+++ b/packages/SystemUI/res/layout/qs_footer_impl.xml
@@ -62,7 +62,7 @@
                 android:gravity="center_vertical"
                 android:focusable="true"
                 android:singleLine="true"
-                android:ellipsize="end"
+                android:ellipsize="marquee"
                 android:textAppearance="@style/TextAppearance.QS.Status"
                 android:layout_marginEnd="4dp"
                 android:visibility="gone"/>
diff --git a/packages/SystemUI/res/layout/qs_media_panel_options.xml b/packages/SystemUI/res/layout/qs_media_panel_options.xml
deleted file mode 100644
index e72c0e8..0000000
--- a/packages/SystemUI/res/layout/qs_media_panel_options.xml
+++ /dev/null
@@ -1,61 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-  ~ Copyright (C) 2019 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
-  -->
-<LinearLayout
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    android:id="@+id/qs_media_controls_options"
-    android:layout_width="match_parent"
-    android:layout_height="match_parent"
-    android:gravity="center"
-    android:padding="16dp"
-    android:orientation="vertical">
-    <LinearLayout
-        android:layout_width="wrap_content"
-        android:layout_height="48dp"
-        android:layout_weight="1"
-        android:minWidth="48dp"
-        android:layout_gravity="start|bottom"
-        android:gravity="bottom"
-        android:id="@+id/remove"
-        android:orientation="horizontal">
-        <ImageView
-            android:layout_width="18dp"
-            android:layout_height="18dp"
-            android:id="@+id/remove_icon"
-            android:layout_marginEnd="16dp"
-            android:tint="@color/media_primary_text"
-            android:src="@drawable/ic_clear"/>
-        <TextView
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:id="@+id/remove_text"
-            android:fontFamily="@*android:string/config_headlineFontFamilyMedium"
-            android:singleLine="true"
-            android:textColor="@color/media_primary_text"
-            android:text="@string/controls_media_close_session" />
-    </LinearLayout>
-    <TextView
-        android:id="@+id/cancel"
-        android:layout_width="wrap_content"
-        android:layout_height="48dp"
-        android:layout_weight="1"
-        android:minWidth="48dp"
-        android:layout_gravity="end|bottom"
-        android:gravity="bottom"
-        android:fontFamily="@*android:string/config_headlineFontFamilyMedium"
-        android:textColor="@android:color/white"
-        android:text="@string/cancel" />
-</LinearLayout>
diff --git a/packages/SystemUI/res/layout/quick_status_bar_header_system_icons.xml b/packages/SystemUI/res/layout/quick_status_bar_header_system_icons.xml
index be86e5f..3c74801 100644
--- a/packages/SystemUI/res/layout/quick_status_bar_header_system_icons.xml
+++ b/packages/SystemUI/res/layout/quick_status_bar_header_system_icons.xml
@@ -14,7 +14,7 @@
 ** See the License for the specific language governing permissions and
 ** limitations under the License.
 -->
-<FrameLayout
+<LinearLayout
     xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:systemui="http://schemas.android.com/apk/res-auto"
     android:id="@+id/quick_status_bar_system_icons"
@@ -27,6 +27,13 @@
     android:clickable="true"
     android:paddingTop="@dimen/status_bar_padding_top" >
 
+    <LinearLayout
+        android:layout_width="0dp"
+        android:layout_height="match_parent"
+        android:layout_weight="1"
+        android:orientation="horizontal"
+        android:gravity="center_vertical|start" >
+
     <com.android.systemui.statusbar.policy.Clock
         android:id="@+id/clock"
         android:layout_width="wrap_content"
@@ -38,5 +45,23 @@
         android:singleLine="true"
         android:textAppearance="@style/TextAppearance.StatusBar.Clock"
         systemui:showDark="false" />
+    </LinearLayout>
 
-</FrameLayout>
+    <android.widget.Space
+        android:id="@+id/space"
+        android:layout_width="0dp"
+        android:layout_height="match_parent"
+        android:layout_gravity="center_vertical|center_horizontal"
+        android:visibility="gone" />
+
+    <LinearLayout
+        android:layout_width="0dp"
+        android:layout_height="match_parent"
+        android:layout_weight="1"
+        android:orientation="horizontal"
+        android:gravity="center_vertical|end" >
+
+    <include layout="@layout/ongoing_privacy_chip" />
+
+    </LinearLayout>
+</LinearLayout>
diff --git a/packages/SystemUI/res/values-h740dp-port/dimens.xml b/packages/SystemUI/res/values-h740dp-port/dimens.xml
new file mode 100644
index 0000000..966066f
--- /dev/null
+++ b/packages/SystemUI/res/values-h740dp-port/dimens.xml
@@ -0,0 +1,27 @@
+<!--
+  ~ Copyright (C) 2020 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License
+  -->
+
+<resources>
+    <dimen name="qs_tile_height">106dp</dimen>
+    <dimen name="qs_tile_margin_vertical">24dp</dimen>
+
+    <!-- The height of the qs customize header. Should be
+         (qs_panel_padding_top (48dp) +  brightness_mirror_height (48dp) + qs_tile_margin_top (18dp)) -
+         (Toolbar_minWidth (56dp) + qs_tile_margin_top_bottom (12dp))
+    -->
+    <dimen name="qs_customize_header_min_height">46dp</dimen>
+    <dimen name="qs_tile_margin_top">18dp</dimen>
+</resources>
\ No newline at end of file
diff --git a/packages/SystemUI/res/values-land/dimens.xml b/packages/SystemUI/res/values-land/dimens.xml
index 9e1b66f..b584dfe 100644
--- a/packages/SystemUI/res/values-land/dimens.xml
+++ b/packages/SystemUI/res/values-land/dimens.xml
@@ -47,4 +47,15 @@
 
     <dimen name="global_actions_power_dialog_item_height">130dp</dimen>
     <dimen name="global_actions_power_dialog_item_bottom_margin">35dp</dimen>
+
+    <dimen name="controls_management_top_padding">12dp</dimen>
+    <dimen name="controls_management_titles_margin">8dp</dimen>
+    <dimen name="controls_management_indicator_top_margin">8dp</dimen>
+    <dimen name="controls_management_list_margin">4dp</dimen>
+    <dimen name="controls_management_footer_height">56dp</dimen>
+    <dimen name="controls_management_zone_top_margin">24dp</dimen>
+
+    <!-- (footer_height -48dp)/2 -->
+    <dimen name="controls_management_footer_top_margin">4dp</dimen>
+    <dimen name="controls_management_favorites_top_margin">8dp</dimen>
 </resources>
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index 01abc77..00977384 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -510,6 +510,8 @@
         <item>com.android.systemui</item>
     </string-array>
 
+    <integer name="ongoing_appops_dialog_max_apps">5</integer>
+
     <!-- Launcher package name for overlaying icons. -->
     <string name="launcher_overlayable_package" translatable="false">com.android.launcher3</string>
 
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index eb8758c..f07627a 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -482,20 +482,21 @@
     <!-- The size of the gesture span needed to activate the "pull" notification expansion -->
     <dimen name="pull_span_min">25dp</dimen>
 
-    <dimen name="qs_tile_height">106dp</dimen>
+    <dimen name="qs_tile_height">96dp</dimen>
     <!--notification_side_paddings + notification_content_margin_start - (qs_quick_tile_size - qs_tile_background_size) / 2 -->
     <dimen name="qs_tile_layout_margin_side">18dp</dimen>
     <dimen name="qs_tile_margin_horizontal">18dp</dimen>
     <dimen name="qs_tile_margin_horizontal_two_line">2dp</dimen>
-    <dimen name="qs_tile_margin_vertical">24dp</dimen>
+    <dimen name="qs_tile_margin_vertical">2dp</dimen>
     <dimen name="qs_tile_margin_top_bottom">12dp</dimen>
     <dimen name="qs_tile_margin_top_bottom_negative">-12dp</dimen>
     <!-- The height of the qs customize header. Should be
-         (qs_panel_padding_top (48dp) +  brightness_mirror_height (48dp) + qs_tile_margin_top (18dp)) -
+         (qs_panel_padding_top (48dp) +  brightness_mirror_height (48dp) + qs_tile_margin_top (0dp)) -
          (Toolbar_minWidth (56dp) + qs_tile_margin_top_bottom (12dp))
     -->
-    <dimen name="qs_customize_header_min_height">46dp</dimen>
-    <dimen name="qs_tile_margin_top">18dp</dimen>
+    <dimen name="qs_customize_header_min_height">28dp</dimen>
+    <dimen name="qs_tile_margin_top">0dp</dimen>
+    <dimen name="qs_tile_icon_background_stroke_width">-1dp</dimen>
     <dimen name="qs_tile_background_size">44dp</dimen>
     <dimen name="qs_quick_tile_size">48dp</dimen>
     <dimen name="qs_quick_tile_padding">12dp</dimen>
@@ -1175,6 +1176,23 @@
 
     <!-- How much into a DisplayCutout's bounds we can go, on each side -->
     <dimen name="display_cutout_margin_consumption">0px</dimen>
+
+    <!-- Height of the Ongoing App Ops chip -->
+    <dimen name="ongoing_appops_chip_height">32dp</dimen>
+    <!-- Padding between background of Ongoing App Ops chip and content -->
+    <dimen name="ongoing_appops_chip_bg_padding">8dp</dimen>
+    <!-- Side padding between background of Ongoing App Ops chip and content -->
+    <dimen name="ongoing_appops_chip_side_padding">8dp</dimen>
+    <!-- Margin between icons of Ongoing App Ops chip when QQS-->
+    <dimen name="ongoing_appops_chip_icon_margin_collapsed">0dp</dimen>
+    <!-- Margin between icons of Ongoing App Ops chip when QS-->
+    <dimen name="ongoing_appops_chip_icon_margin_expanded">2dp</dimen>
+    <!-- Icon size of Ongoing App Ops chip -->
+    <dimen name="ongoing_appops_chip_icon_size">@dimen/status_bar_icon_drawing_size</dimen>
+    <!-- Radius of Ongoing App Ops chip corners -->
+    <dimen name="ongoing_appops_chip_bg_corner_radius">16dp</dimen>
+
+
     <!-- How much each bubble is elevated. -->
     <dimen name="bubble_elevation">1dp</dimen>
     <!-- How much the bubble flyout text container is elevated. -->
@@ -1278,6 +1296,8 @@
     <dimen name="qs_footer_horizontal_margin">22dp</dimen>
     <dimen name="qs_media_disabled_seekbar_height">1dp</dimen>
     <dimen name="qs_media_enabled_seekbar_height">3dp</dimen>
+    <dimen name="qs_media_enabled_seekbar_vertical_padding">15dp</dimen>
+    <dimen name="qs_media_disabled_seekbar_vertical_padding">16dp</dimen>
 
     <dimen name="magnification_border_size">5dp</dimen>
     <dimen name="magnification_frame_move_short">5dp</dimen>
@@ -1333,15 +1353,19 @@
     <dimen name="controls_management_side_padding">16dp</dimen>
     <dimen name="controls_management_titles_margin">16dp</dimen>
     <dimen name="controls_management_footer_side_margin">8dp</dimen>
+    <dimen name="controls_management_footer_top_margin">@dimen/controls_management_footer_side_margin</dimen>
     <dimen name="controls_management_list_margin">16dp</dimen>
+    <dimen name="controls_management_indicator_top_margin">@dimen/controls_management_list_margin</dimen>
     <dimen name="controls_management_apps_list_margin">64dp</dimen>
     <dimen name="controls_management_editing_list_margin">48dp</dimen>
     <dimen name="controls_management_editing_divider_margin">24dp</dimen>
     <dimen name="controls_management_apps_extra_side_margin">8dp</dimen>
     <dimen name="controls_management_zone_top_margin">32dp</dimen>
+    <dimen name="controls_management_favorites_top_margin">@dimen/controls_management_zone_top_margin</dimen>
     <dimen name="controls_management_status_side_margin">16dp</dimen>
     <dimen name="controls_management_page_indicator_height">24dp</dimen>
     <dimen name="controls_management_checkbox_size">25dp</dimen>
+    <dimen name="controls_management_footer_height">72dp</dimen>
     <dimen name="controls_title_size">24sp</dimen>
     <dimen name="controls_subtitle_size">16sp</dimen>
 
@@ -1354,7 +1378,7 @@
     <dimen name="controls_app_divider_height">2dp</dimen>
     <dimen name="controls_app_divider_side_margin">32dp</dimen>
 
-    <dimen name="controls_card_margin">2dp</dimen>
+    <dimen name="controls_card_margin">@dimen/control_base_item_margin</dimen>
     <item name="control_card_elevation" type="dimen" format="float">15</item>
 
     <dimen name="controls_dialog_padding">32dp</dimen>
diff --git a/packages/SystemUI/res/values/ids.xml b/packages/SystemUI/res/values/ids.xml
index 8212d61..b8e8db5 100644
--- a/packages/SystemUI/res/values/ids.xml
+++ b/packages/SystemUI/res/values/ids.xml
@@ -92,6 +92,8 @@
 
     <item type="id" name="requires_remeasuring"/>
 
+    <item type="id" name="secondary_home_handle" />
+
     <!-- Whether the icon is from a notification for which targetSdk < L -->
     <item type="id" name="icon_is_pre_L"/>
 
@@ -175,6 +177,9 @@
     <item type="id" name="accessibility_action_controls_move_before" />
     <item type="id" name="accessibility_action_controls_move_after" />
 
+    <item type="id" name="accessibility_action_qs_move_to_position" />
+    <item type="id" name="accessibility_action_qs_add_to_position" />
+
     <!-- Accessibility actions for PIP -->
     <item type="id" name="action_pip_resize" />
 </resources>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index db45a60..95de486 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -278,14 +278,10 @@
     <string name="screenrecord_cancel_label">Cancel</string>
     <!-- Label for notification action to share screen recording [CHAR LIMIT=35] -->
     <string name="screenrecord_share_label">Share</string>
-    <!-- Label for notification action to delete a screen recording file [CHAR LIMIT=35] -->
-    <string name="screenrecord_delete_label">Delete</string>
     <!-- A toast message shown after successfully canceling a screen recording [CHAR LIMIT=NONE] -->
     <string name="screenrecord_cancel_success">Screen recording canceled</string>
     <!-- Notification text shown after saving a screen recording to prompt the user to view it [CHAR LIMIT=100] -->
     <string name="screenrecord_save_message">Screen recording saved, tap to view</string>
-    <!-- A toast message shown after successfully deleting a screen recording [CHAR LIMIT=NONE] -->
-    <string name="screenrecord_delete_description">Screen recording deleted</string>
     <!-- A toast message shown when there is an error deleting a screen recording [CHAR LIMIT=NONE] -->
     <string name="screenrecord_delete_error">Error deleting screen recording</string>
     <!-- A toast message shown when the screen recording cannot be started due to insufficient permissions [CHAR LIMIT=NONE] -->
@@ -2271,23 +2267,26 @@
     <!-- Accessibility action for moving docked stack divider to make the bottom screen full screen [CHAR LIMIT=NONE] -->
     <string name="accessibility_action_divider_bottom_full">Bottom full screen</string>
 
-    <!-- Accessibility description of a QS tile while editing positions [CHAR LIMIT=NONE] -->
-    <string name="accessibility_qs_edit_tile_label">Position <xliff:g id="position" example="2">%1$d</xliff:g>, <xliff:g id="tile_name" example="Wi-Fi">%2$s</xliff:g>. Double tap to edit.</string>
+    <!-- Accessibility description of action to remove QS tile on click. It will read as "Double-tap to remove tile" in screen readers [CHAR LIMIT=NONE] -->
+    <string name="accessibility_qs_edit_remove_tile_action">remove tile</string>
 
-    <!-- Accessibility description of a QS tile while editing positions [CHAR LIMIT=NONE] -->
-    <string name="accessibility_qs_edit_add_tile_label"><xliff:g id="tile_name" example="Wi-Fi">%1$s</xliff:g>. Double tap to add.</string>
+    <!-- Accessibility action of action to add QS tile to end. It will read as "Double-tap to add tile to end" in screen readers [CHAR LIMIT=NONE] -->
+    <string name="accessibility_qs_edit_tile_add_action">add tile to end</string>
 
-    <!-- Accessibility description of option to move QS tile [CHAR LIMIT=NONE] -->
-    <string name="accessibility_qs_edit_move_tile">Move <xliff:g id="tile_name" example="Wi-Fi">%1$s</xliff:g></string>
+    <!-- Accessibility action for context menu to move QS tile [CHAR LIMIT=NONE] -->
+    <string name="accessibility_qs_edit_tile_start_move">Move tile</string>
 
-    <!-- Accessibility description of option to remove QS tile [CHAR LIMIT=NONE] -->
-    <string name="accessibility_qs_edit_remove_tile">Remove <xliff:g id="tile_name" example="Wi-Fi">%1$s</xliff:g></string>
+    <!-- Accessibility action for context menu to add QS tile [CHAR LIMIT=NONE] -->
+    <string name="accessibility_qs_edit_tile_start_add">Add tile</string>
 
-    <!-- Accessibility action when QS tile is to be added [CHAR LIMIT=NONE] -->
-    <string name="accessibility_qs_edit_tile_add">Add <xliff:g id="tile_name" example="Wi-Fi">%1$s</xliff:g> to position <xliff:g id="position" example="5">%2$d</xliff:g></string>
+    <!-- Accessibility description when QS tile is to be moved, indicating the destination position [CHAR LIMIT=NONE] -->
+    <string name="accessibility_qs_edit_tile_move_to_position">Move to <xliff:g id="position" example="5">%1$d</xliff:g></string>
 
-    <!-- Accessibility action when QS tile is to be moved [CHAR LIMIT=NONE] -->
-    <string name="accessibility_qs_edit_tile_move">Move <xliff:g id="tile_name" example="Wi-Fi">%1$s</xliff:g> to position <xliff:g id="position" example="5">%2$d</xliff:g></string>
+    <!-- Accessibility description when QS tile is to be added, indicating the destination position [CHAR LIMIT=NONE] -->
+    <string name="accessibility_qs_edit_tile_add_to_position">Add to position <xliff:g id="position" example="5">%1$d</xliff:g></string>
+
+    <!-- Accessibility description indicating the currently selected tile's position. Only used for tiles that are currently in use [CHAR LIMIT=NONE] -->
+    <string name="accessibility_qs_edit_position">Position <xliff:g id="position" example="5">%1$d</xliff:g></string>
 
     <!-- Accessibility label for window when QS editing is happening [CHAR LIMIT=NONE] -->
     <string name="accessibility_desc_quick_settings_edit">Quick settings editor.</string>
@@ -2404,17 +2403,26 @@
 
     <!-- Title for notification & dialog that the user's phone last shut down because it got too hot. [CHAR LIMIT=40] -->
     <string name="thermal_shutdown_title">Phone turned off due to heat</string>
-    <!-- Message body for notification that user's phone last shut down because it got too hot. [CHAR LIMIT=100] -->
-    <string name="thermal_shutdown_message">Your phone is now running normally</string>
-    <!-- Text body for dialog alerting user that their phone last shut down because it got too hot. [CHAR LIMIT=450] -->
+    <!-- Message body for notification that user's phone last shut down because it got too hot. [CHAR LIMIT=120] -->
+    <string name="thermal_shutdown_message">Your phone is now running normally.\nTap for more info</string>
+    <!-- Text body for dialog alerting user that their phone last shut down because it got too hot. [CHAR LIMIT=500] -->
     <string name="thermal_shutdown_dialog_message">Your phone was too hot, so it turned off to cool down. Your phone is now running normally.\n\nYour phone may get too hot if you:\n\t&#8226; Use resource-intensive apps (such as gaming, video, or navigation apps)\n\t&#8226; Download or upload large files\n\t&#8226; Use your phone in high temperatures</string>
+    <!-- Text help link for care instructions for overheating devices [CHAR LIMIT=40] -->
+    <string name="thermal_shutdown_dialog_help_text">See care steps</string>
+    <!-- URL for care instructions for overheating devices -->
+    <string name="thermal_shutdown_dialog_help_url" translatable="false"></string>
 
     <!-- Title for notification (and dialog) that user's phone has reached a certain temperature and may start to slow down in order to cool down. [CHAR LIMIT=30] -->
     <string name="high_temp_title">Phone is getting warm</string>
-    <!-- Message body for notification that user's phone has reached a certain temperature and may start to slow down in order to cool down. [CHAR LIMIT=100] -->
-    <string name="high_temp_notif_message">Some features limited while phone cools down</string>
-    <!-- Text body for dialog alerting user that their phone has reached a certain temperature and may start to slow down in order to cool down. [CHAR LIMIT=300] -->
+    <!-- Message body for notification that user's phone has reached a certain temperature and may start to slow down in order to cool down. [CHAR LIMIT=120] -->
+    <string name="high_temp_notif_message">Some features limited while phone cools down.\nTap for more info</string>
+    <!-- Text body for dialog alerting user that their phone has reached a certain temperature and may start to slow down in order to cool down. [CHAR LIMIT=350] -->
     <string name="high_temp_dialog_message">Your phone will automatically try to cool down. You can still use your phone, but it may run slower.\n\nOnce your phone has cooled down, it will run normally.</string>
+    <!-- Text help link for care instructions for overheating devices [CHAR LIMIT=40] -->
+    <string name="high_temp_dialog_help_text">See care steps</string>
+    <!-- URL for care instructions for overheating devices -->
+    <string name="high_temp_dialog_help_url" translatable="false"></string>
+
     <!-- Title for alarm dialog alerting user the usb adapter has reached a certain temperature that should disconnect charging cable immediately. [CHAR LIMIT=30] -->
     <string name="high_temp_alarm_title">Unplug charger</string>
     <!-- Text body for dialog alerting user the usb adapter has reached a certain temperature that should disconnect charging cable immediately. [CHAR LIMIT=300] -->
@@ -2608,6 +2616,27 @@
          app for debugging. Will not be seen by users. [CHAR LIMIT=20] -->
     <string name="heap_dump_tile_name">Dump SysUI Heap</string>
 
+    <!-- Content description for ongoing privacy chip. Use with a single app [CHAR LIMIT=NONE]-->
+    <string name="ongoing_privacy_chip_content_single_app"><xliff:g id="app" example="Example App">%1$s</xliff:g> is using your <xliff:g id="types_list" example="camera, location">%2$s</xliff:g>.</string>
+
+    <!-- Content description for ongoing privacy chip. Use with multiple apps [CHAR LIMIT=NONE]-->
+    <string name="ongoing_privacy_chip_content_multiple_apps">Applications are using your <xliff:g id="types_list" example="camera, location">%s</xliff:g>.</string>
+
+    <!-- Separator for types. Include spaces before and after if needed [CHAR LIMIT=10] -->
+    <string name="ongoing_privacy_dialog_separator">,\u0020</string>
+
+    <!-- Separator for types, before last type. Include spaces before and after if needed [CHAR LIMIT=10] -->
+    <string name="ongoing_privacy_dialog_last_separator">\u0020and\u0020</string>
+
+    <!-- Text for camera app op [CHAR LIMIT=20]-->
+    <string name="privacy_type_camera">camera</string>
+
+    <!-- Text for location app op [CHAR LIMIT=20]-->
+    <string name="privacy_type_location">location</string>
+
+    <!-- Text for microphone app op [CHAR LIMIT=20]-->
+    <string name="privacy_type_microphone">microphone</string>
+
     <!-- Text for the quick setting tile for sensor privacy [CHAR LIMIT=30] -->
     <string name="sensor_privacy_mode">Sensors off</string>
 
@@ -2791,8 +2820,10 @@
     <string name="controls_media_title">Media</string>
     <!-- Explanation for closing controls associated with a specific media session [CHAR_LIMIT=NONE] -->
     <string name="controls_media_close_session">Hide the current session.</string>
+    <!-- Explanation that controls associated with a specific media session are active [CHAR_LIMIT=NONE] -->
+    <string name="controls_media_active_session">Current session cannot be hidden.</string>
     <!-- Label for a button that will hide media controls [CHAR_LIMIT=30] -->
-    <string name="controls_media_dismiss_button">Hide</string>
+    <string name="controls_media_dismiss_button">Dismiss</string>
     <!-- Label for button to resume media playback [CHAR_LIMIT=NONE] -->
     <string name="controls_media_resume">Resume</string>
     <!-- Label for button to go to media control settings screen [CHAR_LIMIT=30] -->
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/WallpaperEngineCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/WallpaperEngineCompat.java
index 4d968f1..73dc60d 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/WallpaperEngineCompat.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/WallpaperEngineCompat.java
@@ -26,6 +26,17 @@
 
     private static final String TAG = "WallpaperEngineCompat";
 
+    /**
+     * Returns true if {@link IWallpaperEngine#scalePreview(Rect)} is available.
+     */
+    public static boolean supportsScalePreview() {
+        try {
+            return IWallpaperEngine.class.getMethod("scalePreview", Rect.class) != null;
+        } catch (NoSuchMethodException | SecurityException e) {
+            return false;
+        }
+    }
+
     private final IWallpaperEngine mWrappedEngine;
 
     public WallpaperEngineCompat(IWallpaperEngine wrappedEngine) {
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukView.java
index 4c5e7ee..5148dd7 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukView.java
@@ -448,8 +448,8 @@
                                 if (DEBUG) Log.d(LOG_TAG, "verifyPasswordAndUnlock "
                                         + " UpdateSim.onSimCheckResponse: "
                                         + " attemptsRemaining=" + result.getAttemptsRemaining());
-                                mStateMachine.reset();
                             }
+                            mStateMachine.reset();
                             mCheckSimPukThread = null;
                         }
                     });
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index 3acbfb8..60541eb 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -286,11 +286,11 @@
     private final Executor mBackgroundExecutor;
 
     /**
-     * Short delay before restarting biometric authentication after a successful try
-     * This should be slightly longer than the time between on<biometric>Authenticated
-     * (e.g. onFingerprintAuthenticated) and setKeyguardGoingAway(true).
+     * Short delay before restarting fingerprint authentication after a successful try. This should
+     * be slightly longer than the time between onFingerprintAuthenticated and
+     * setKeyguardGoingAway(true).
      */
-    private static final int BIOMETRIC_CONTINUE_DELAY_MS = 500;
+    private static final int FINGERPRINT_CONTINUE_DELAY_MS = 500;
 
     // If the HAL dies or is unable to authenticate, keyguard should retry after a short delay
     private int mHardwareFingerprintUnavailableRetryCount = 0;
@@ -598,7 +598,7 @@
         }
 
         mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_BIOMETRIC_AUTHENTICATION_CONTINUE),
-                BIOMETRIC_CONTINUE_DELAY_MS);
+                FINGERPRINT_CONTINUE_DELAY_MS);
 
         // Only authenticate fingerprint once when assistant is visible
         mAssistantVisible = false;
@@ -780,9 +780,6 @@
             }
         }
 
-        mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_BIOMETRIC_AUTHENTICATION_CONTINUE),
-                BIOMETRIC_CONTINUE_DELAY_MS);
-
         // Only authenticate face once when assistant is visible
         mAssistantVisible = false;
 
@@ -1072,6 +1069,15 @@
                 STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN);
     }
 
+    private boolean isEncryptedOrLockdown(int userId) {
+        final int strongAuth = mStrongAuthTracker.getStrongAuthForUser(userId);
+        final boolean isLockDown =
+                containsFlag(strongAuth, STRONG_AUTH_REQUIRED_AFTER_DPM_LOCK_NOW)
+                        || containsFlag(strongAuth, STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN);
+        final boolean isEncrypted = containsFlag(strongAuth, STRONG_AUTH_REQUIRED_AFTER_BOOT);
+        return isEncrypted || isLockDown;
+    }
+
     public boolean userNeedsStrongAuth() {
         return mStrongAuthTracker.getStrongAuthForUser(getCurrentUser())
                 != LockPatternUtils.StrongAuthTracker.STRONG_AUTH_NOT_REQUIRED;
@@ -1248,6 +1254,10 @@
         }
     };
 
+    // Trigger the fingerprint success path so the bouncer can be shown
+    private final FingerprintManager.FingerprintDetectionCallback mFingerprintDetectionCallback
+            = this::handleFingerprintAuthenticated;
+
     private FingerprintManager.AuthenticationCallback mFingerprintAuthenticationCallback
             = new AuthenticationCallback() {
 
@@ -2050,8 +2060,15 @@
                 mFingerprintCancelSignal.cancel();
             }
             mFingerprintCancelSignal = new CancellationSignal();
-            mFpm.authenticate(null, mFingerprintCancelSignal, 0, mFingerprintAuthenticationCallback,
-                    null, userId);
+
+            if (isEncryptedOrLockdown(userId)) {
+                mFpm.detectFingerprint(mFingerprintCancelSignal, mFingerprintDetectionCallback,
+                        userId);
+            } else {
+                mFpm.authenticate(null, mFingerprintCancelSignal, 0,
+                        mFingerprintAuthenticationCallback, null, userId);
+            }
+
             setFingerprintRunningState(BIOMETRIC_STATE_RUNNING);
         }
     }
@@ -2087,7 +2104,7 @@
 
     private boolean isUnlockWithFingerprintPossible(int userId) {
         return mFpm != null && mFpm.isHardwareDetected() && !isFingerprintDisabled(userId)
-                && mFpm.getEnrolledFingerprints(userId).size() > 0;
+                && mFpm.hasEnrolledTemplates(userId);
     }
 
     private boolean isUnlockWithFacePossible(int userId) {
diff --git a/packages/SystemUI/src/com/android/systemui/Dependency.java b/packages/SystemUI/src/com/android/systemui/Dependency.java
index d1df276..7c36713 100644
--- a/packages/SystemUI/src/com/android/systemui/Dependency.java
+++ b/packages/SystemUI/src/com/android/systemui/Dependency.java
@@ -54,6 +54,7 @@
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.power.EnhancedEstimates;
 import com.android.systemui.power.PowerUI;
+import com.android.systemui.privacy.PrivacyItemController;
 import com.android.systemui.recents.OverviewProxyService;
 import com.android.systemui.recents.Recents;
 import com.android.systemui.screenrecord.RecordingController;
@@ -315,6 +316,7 @@
     @Inject Lazy<SensorPrivacyManager> mSensorPrivacyManager;
     @Inject Lazy<AutoHideController> mAutoHideController;
     @Inject Lazy<ForegroundServiceNotificationListener> mForegroundServiceNotificationListener;
+    @Inject Lazy<PrivacyItemController> mPrivacyItemController;
     @Inject @Background Lazy<Looper> mBgLooper;
     @Inject @Background Lazy<Handler> mBgHandler;
     @Inject @Main Lazy<Looper> mMainLooper;
@@ -516,6 +518,7 @@
         mProviders.put(ForegroundServiceNotificationListener.class,
                 mForegroundServiceNotificationListener::get);
         mProviders.put(ClockManager.class, mClockManager::get);
+        mProviders.put(PrivacyItemController.class, mPrivacyItemController::get);
         mProviders.put(ActivityManagerWrapper.class, mActivityManagerWrapper::get);
         mProviders.put(DevicePolicyManagerWrapper.class, mDevicePolicyManagerWrapper::get);
         mProviders.put(PackageManagerWrapper.class, mPackageManagerWrapper::get);
diff --git a/packages/SystemUI/src/com/android/systemui/SlicePermissionActivity.java b/packages/SystemUI/src/com/android/systemui/SlicePermissionActivity.java
index 2365f12..57e6568 100644
--- a/packages/SystemUI/src/com/android/systemui/SlicePermissionActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/SlicePermissionActivity.java
@@ -52,11 +52,6 @@
 
         mUri = getIntent().getParcelableExtra(SliceProvider.EXTRA_BIND_URI);
         mCallingPkg = getIntent().getStringExtra(SliceProvider.EXTRA_PKG);
-        if (mUri == null) {
-            Log.e(TAG, SliceProvider.EXTRA_BIND_URI + " wasn't provided");
-            finish();
-            return;
-        }
 
         try {
             PackageManager pm = getPackageManager();
@@ -108,12 +103,10 @@
     }
 
     private void verifyCallingPkg() {
-        final String providerPkg = getIntent().getStringExtra("provider_pkg");
+        final String providerPkg = getIntent().getStringExtra(SliceProvider.EXTRA_PROVIDER_PKG);
         if (providerPkg == null || mProviderPkg.equals(providerPkg)) return;
         final String callingPkg = getCallingPkg();
-        EventLog.writeEvent(0x534e4554, "159145361", getUid(callingPkg), String.format(
-                "pkg %s (disguised as %s) attempted to request permission to show %s slices in %s",
-                callingPkg, providerPkg, mProviderPkg, mCallingPkg));
+        EventLog.writeEvent(0x534e4554, "159145361", getUid(callingPkg));
     }
 
     @Nullable
diff --git a/packages/SystemUI/src/com/android/systemui/appops/AppOpItem.java b/packages/SystemUI/src/com/android/systemui/appops/AppOpItem.java
index 7e5b426..93a8df4 100644
--- a/packages/SystemUI/src/com/android/systemui/appops/AppOpItem.java
+++ b/packages/SystemUI/src/com/android/systemui/appops/AppOpItem.java
@@ -25,7 +25,9 @@
     private int mUid;
     private String mPackageName;
     private long mTimeStarted;
-    private String mState;
+    private StringBuilder mState;
+    // This is only used for items with mCode == AppOpsManager.OP_RECORD_AUDIO
+    private boolean mSilenced;
 
     public AppOpItem(int code, int uid, String packageName, long timeStarted) {
         this.mCode = code;
@@ -36,9 +38,8 @@
                 .append("AppOpItem(")
                 .append("Op code=").append(code).append(", ")
                 .append("UID=").append(uid).append(", ")
-                .append("Package name=").append(packageName)
-                .append(")")
-                .toString();
+                .append("Package name=").append(packageName).append(", ")
+                .append("Paused=");
     }
 
     public int getCode() {
@@ -57,8 +58,16 @@
         return mTimeStarted;
     }
 
+    public void setSilenced(boolean silenced) {
+        mSilenced = silenced;
+    }
+
+    public boolean isSilenced() {
+        return mSilenced;
+    }
+
     @Override
     public String toString() {
-        return mState;
+        return mState.append(mSilenced).append(")").toString();
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/appops/AppOpsControllerImpl.java b/packages/SystemUI/src/com/android/systemui/appops/AppOpsControllerImpl.java
index 941de2d..2b9514f 100644
--- a/packages/SystemUI/src/com/android/systemui/appops/AppOpsControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/appops/AppOpsControllerImpl.java
@@ -16,18 +16,31 @@
 
 package com.android.systemui.appops;
 
+import static android.media.AudioManager.ACTION_MICROPHONE_MUTE_CHANGED;
+
 import android.app.AppOpsManager;
+import android.content.BroadcastReceiver;
 import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.PackageManager;
+import android.location.LocationManager;
+import android.media.AudioManager;
+import android.media.AudioRecordingConfiguration;
 import android.os.Handler;
 import android.os.Looper;
 import android.os.UserHandle;
 import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.Log;
+import android.util.SparseArray;
+
+import androidx.annotation.WorkerThread;
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.systemui.Dumpable;
+import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.dagger.qualifiers.Background;
 import com.android.systemui.dump.DumpManager;
 
@@ -47,7 +60,7 @@
  * NotificationPresenter to be displayed to the user.
  */
 @Singleton
-public class AppOpsControllerImpl implements AppOpsController,
+public class AppOpsControllerImpl extends BroadcastReceiver implements AppOpsController,
         AppOpsManager.OnOpActiveChangedInternalListener,
         AppOpsManager.OnOpNotedListener, Dumpable {
 
@@ -57,23 +70,38 @@
     private static final long NOTED_OP_TIME_DELAY_MS = 5000;
     private static final String TAG = "AppOpsControllerImpl";
     private static final boolean DEBUG = false;
-    private final Context mContext;
 
+    private final BroadcastDispatcher mDispatcher;
     private final AppOpsManager mAppOps;
+    private final AudioManager mAudioManager;
+    private final LocationManager mLocationManager;
+
+    // mLocationProviderPackages are cached and updated only occasionally
+    private static final long LOCATION_PROVIDER_UPDATE_FREQUENCY_MS = 30000;
+    private long mLastLocationProviderPackageUpdate;
+    private List<String> mLocationProviderPackages;
+
     private H mBGHandler;
     private final List<AppOpsController.Callback> mCallbacks = new ArrayList<>();
     private final ArrayMap<Integer, Set<Callback>> mCallbacksByCode = new ArrayMap<>();
+    private final PermissionFlagsCache mFlagsCache;
     private boolean mListening;
+    private boolean mMicMuted;
 
     @GuardedBy("mActiveItems")
     private final List<AppOpItem> mActiveItems = new ArrayList<>();
     @GuardedBy("mNotedItems")
     private final List<AppOpItem> mNotedItems = new ArrayList<>();
+    @GuardedBy("mActiveItems")
+    private final SparseArray<ArrayList<AudioRecordingConfiguration>> mRecordingsByUid =
+            new SparseArray<>();
 
     protected static final int[] OPS = new int[] {
             AppOpsManager.OP_CAMERA,
+            AppOpsManager.OP_PHONE_CALL_CAMERA,
             AppOpsManager.OP_SYSTEM_ALERT_WINDOW,
             AppOpsManager.OP_RECORD_AUDIO,
+            AppOpsManager.OP_PHONE_CALL_MICROPHONE,
             AppOpsManager.OP_COARSE_LOCATION,
             AppOpsManager.OP_FINE_LOCATION
     };
@@ -82,14 +110,22 @@
     public AppOpsControllerImpl(
             Context context,
             @Background Looper bgLooper,
-            DumpManager dumpManager) {
-        mContext = context;
+            DumpManager dumpManager,
+            PermissionFlagsCache cache,
+            AudioManager audioManager,
+            BroadcastDispatcher dispatcher
+    ) {
+        mDispatcher = dispatcher;
         mAppOps = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);
+        mFlagsCache = cache;
         mBGHandler = new H(bgLooper);
         final int numOps = OPS.length;
         for (int i = 0; i < numOps; i++) {
             mCallbacksByCode.put(OPS[i], new ArraySet<>());
         }
+        mAudioManager = audioManager;
+        mMicMuted = audioManager.isMicrophoneMute();
+        mLocationManager = context.getSystemService(LocationManager.class);
         dumpManager.registerDumpable(TAG, this);
     }
 
@@ -104,12 +140,22 @@
         if (listening) {
             mAppOps.startWatchingActive(OPS, this);
             mAppOps.startWatchingNoted(OPS, this);
+            mAudioManager.registerAudioRecordingCallback(mAudioRecordingCallback, mBGHandler);
+            mBGHandler.post(() -> mAudioRecordingCallback.onRecordingConfigChanged(
+                    mAudioManager.getActiveRecordingConfigurations()));
+            mDispatcher.registerReceiverWithHandler(this,
+                    new IntentFilter(ACTION_MICROPHONE_MUTE_CHANGED), mBGHandler);
+
         } else {
             mAppOps.stopWatchingActive(this);
             mAppOps.stopWatchingNoted(this);
+            mAudioManager.unregisterAudioRecordingCallback(mAudioRecordingCallback);
+
             mBGHandler.removeCallbacksAndMessages(null); // null removes all
+            mDispatcher.unregisterReceiver(this);
             synchronized (mActiveItems) {
                 mActiveItems.clear();
+                mRecordingsByUid.clear();
             }
             synchronized (mNotedItems) {
                 mNotedItems.clear();
@@ -182,9 +228,12 @@
             AppOpItem item = getAppOpItemLocked(mActiveItems, code, uid, packageName);
             if (item == null && active) {
                 item = new AppOpItem(code, uid, packageName, System.currentTimeMillis());
+                if (code == AppOpsManager.OP_RECORD_AUDIO) {
+                    item.setSilenced(isAnyRecordingPausedLocked(uid));
+                }
                 mActiveItems.add(item);
                 if (DEBUG) Log.w(TAG, "Added item: " + item.toString());
-                return true;
+                return !item.isSilenced();
             } else if (item != null && !active) {
                 mActiveItems.remove(item);
                 if (DEBUG) Log.w(TAG, "Removed item: " + item.toString());
@@ -208,7 +257,7 @@
             active = getAppOpItemLocked(mActiveItems, code, uid, packageName) != null;
         }
         if (!active) {
-            notifySuscribers(code, uid, packageName, false);
+            notifySuscribersWorker(code, uid, packageName, false);
         }
     }
 
@@ -231,10 +280,94 @@
     }
 
     /**
+     * Does the app-op code refer to a user sensitive permission for the specified user id
+     * and package. Only user sensitive permission should be shown to the user by default.
+     *
+     * @param appOpCode The code of the app-op.
+     * @param uid The uid of the user.
+     * @param packageName The name of the package.
+     *
+     * @return {@code true} iff the app-op item is user sensitive
+     */
+    private boolean isUserSensitive(int appOpCode, int uid, String packageName) {
+        String permission = AppOpsManager.opToPermission(appOpCode);
+        if (permission == null) {
+            return false;
+        }
+        int permFlags = mFlagsCache.getPermissionFlags(permission,
+                packageName, uid);
+        return (permFlags & PackageManager.FLAG_PERMISSION_USER_SENSITIVE_WHEN_GRANTED) != 0;
+    }
+
+    /**
+     * Does the app-op item refer to an operation that should be shown to the user.
+     * Only specficic ops (like SYSTEM_ALERT_WINDOW) or ops that refer to user sensitive
+     * permission should be shown to the user by default.
+     *
+     * @param item The item
+     *
+     * @return {@code true} iff the app-op item should be shown to the user
+     */
+    private boolean isUserVisible(AppOpItem item) {
+        return isUserVisible(item.getCode(), item.getUid(), item.getPackageName());
+    }
+
+    /**
+     * Checks if a package is the current location provider.
+     *
+     * <p>Data is cached to avoid too many calls into system server
+     *
+     * @param packageName The package that might be the location provider
+     *
+     * @return {@code true} iff the package is the location provider.
+     */
+    private boolean isLocationProvider(String packageName) {
+        long now = System.currentTimeMillis();
+
+        if (mLastLocationProviderPackageUpdate + LOCATION_PROVIDER_UPDATE_FREQUENCY_MS < now) {
+            mLastLocationProviderPackageUpdate = now;
+            mLocationProviderPackages = mLocationManager.getProviderPackages(
+                    LocationManager.FUSED_PROVIDER);
+        }
+
+        return mLocationProviderPackages.contains(packageName);
+    }
+
+    /**
+     * Does the app-op, uid and package name, refer to an operation that should be shown to the
+     * user. Only specficic ops (like {@link AppOpsManager.OP_SYSTEM_ALERT_WINDOW}) or
+     * ops that refer to user sensitive permission should be shown to the user by default.
+     *
+     * @param item The item
+     *
+     * @return {@code true} iff the app-op for should be shown to the user
+     */
+    private boolean isUserVisible(int appOpCode, int uid, String packageName) {
+        // currently OP_SYSTEM_ALERT_WINDOW and OP_MONITOR_HIGH_POWER_LOCATION
+        // does not correspond to a platform permission
+        // which may be user sensitive, so for now always show it to the user.
+        if (appOpCode == AppOpsManager.OP_SYSTEM_ALERT_WINDOW
+                || appOpCode == AppOpsManager.OP_MONITOR_HIGH_POWER_LOCATION
+                || appOpCode == AppOpsManager.OP_PHONE_CALL_CAMERA
+                || appOpCode == AppOpsManager.OP_PHONE_CALL_MICROPHONE) {
+            return true;
+        }
+
+        if (appOpCode == AppOpsManager.OP_CAMERA && isLocationProvider(packageName)) {
+            return true;
+        }
+
+        return isUserSensitive(appOpCode, uid, packageName);
+    }
+
+    /**
      * Returns a copy of the list containing all the active AppOps that the controller tracks.
      *
+     * Call from a worker thread as it may perform long operations.
+     *
      * @return List of active AppOps information
      */
+    @WorkerThread
     public List<AppOpItem> getActiveAppOps() {
         return getActiveAppOpsForUser(UserHandle.USER_ALL);
     }
@@ -243,10 +376,13 @@
      * Returns a copy of the list containing all the active AppOps that the controller tracks, for
      * a given user id.
      *
+     * Call from a worker thread as it may perform long operations.
+     *
      * @param userId User id to track, can be {@link UserHandle#USER_ALL}
      *
      * @return List of active AppOps information for that user id
      */
+    @WorkerThread
     public List<AppOpItem> getActiveAppOpsForUser(int userId) {
         List<AppOpItem> list = new ArrayList<>();
         synchronized (mActiveItems) {
@@ -254,7 +390,8 @@
             for (int i = 0; i < numActiveItems; i++) {
                 AppOpItem item = mActiveItems.get(i);
                 if ((userId == UserHandle.USER_ALL
-                        || UserHandle.getUserId(item.getUid()) == userId)) {
+                        || UserHandle.getUserId(item.getUid()) == userId)
+                        && isUserVisible(item) && !item.isSilenced()) {
                     list.add(item);
                 }
             }
@@ -264,7 +401,8 @@
             for (int i = 0; i < numNotedItems; i++) {
                 AppOpItem item = mNotedItems.get(i);
                 if ((userId == UserHandle.USER_ALL
-                        || UserHandle.getUserId(item.getUid()) == userId)) {
+                        || UserHandle.getUserId(item.getUid()) == userId)
+                        && isUserVisible(item)) {
                     list.add(item);
                 }
             }
@@ -272,6 +410,10 @@
         return list;
     }
 
+    private void notifySuscribers(int code, int uid, String packageName, boolean active) {
+        mBGHandler.post(() -> notifySuscribersWorker(code, uid, packageName, active));
+    }
+
     @Override
     public void onOpActiveChanged(int code, int uid, String packageName, boolean active) {
         if (DEBUG) {
@@ -289,7 +431,7 @@
         // If active is false, we only send the update if the op is not actively noted (prevent
         // early removal)
         if (!alsoNoted) {
-            mBGHandler.post(() -> notifySuscribers(code, uid, packageName, active));
+            notifySuscribers(code, uid, packageName, active);
         }
     }
 
@@ -307,12 +449,12 @@
             alsoActive = getAppOpItemLocked(mActiveItems, code, uid, packageName) != null;
         }
         if (!alsoActive) {
-            mBGHandler.post(() -> notifySuscribers(code, uid, packageName, true));
+            notifySuscribers(code, uid, packageName, true);
         }
     }
 
-    private void notifySuscribers(int code, int uid, String packageName, boolean active) {
-        if (mCallbacksByCode.containsKey(code)) {
+    private void notifySuscribersWorker(int code, int uid, String packageName, boolean active) {
+        if (mCallbacksByCode.containsKey(code) && isUserVisible(code, uid, packageName)) {
             if (DEBUG) Log.d(TAG, "Notifying of change in package " + packageName);
             for (Callback cb: mCallbacksByCode.get(code)) {
                 cb.onActiveStateChanged(code, uid, packageName, active);
@@ -337,6 +479,70 @@
 
     }
 
+    private boolean isAnyRecordingPausedLocked(int uid) {
+        if (mMicMuted) {
+            return true;
+        }
+        List<AudioRecordingConfiguration> configs = mRecordingsByUid.get(uid);
+        if (configs == null) return false;
+        int configsNum = configs.size();
+        for (int i = 0; i < configsNum; i++) {
+            AudioRecordingConfiguration config = configs.get(i);
+            if (config.isClientSilenced()) return true;
+        }
+        return false;
+    }
+
+    private void updateRecordingPausedStatus() {
+        synchronized (mActiveItems) {
+            int size = mActiveItems.size();
+            for (int i = 0; i < size; i++) {
+                AppOpItem item = mActiveItems.get(i);
+                if (item.getCode() == AppOpsManager.OP_RECORD_AUDIO) {
+                    boolean paused = isAnyRecordingPausedLocked(item.getUid());
+                    if (item.isSilenced() != paused) {
+                        item.setSilenced(paused);
+                        notifySuscribers(
+                                item.getCode(),
+                                item.getUid(),
+                                item.getPackageName(),
+                                !item.isSilenced()
+                        );
+                    }
+                }
+            }
+        }
+    }
+
+    private AudioManager.AudioRecordingCallback mAudioRecordingCallback =
+            new AudioManager.AudioRecordingCallback() {
+        @Override
+        public void onRecordingConfigChanged(List<AudioRecordingConfiguration> configs) {
+            synchronized (mActiveItems) {
+                mRecordingsByUid.clear();
+                final int recordingsCount = configs.size();
+                for (int i = 0; i < recordingsCount; i++) {
+                    AudioRecordingConfiguration recording = configs.get(i);
+
+                    ArrayList<AudioRecordingConfiguration> recordings = mRecordingsByUid.get(
+                            recording.getClientUid());
+                    if (recordings == null) {
+                        recordings = new ArrayList<>();
+                        mRecordingsByUid.put(recording.getClientUid(), recordings);
+                    }
+                    recordings.add(recording);
+                }
+            }
+            updateRecordingPausedStatus();
+        }
+    };
+
+    @Override
+    public void onReceive(Context context, Intent intent) {
+        mMicMuted = mAudioManager.isMicrophoneMute();
+        updateRecordingPausedStatus();
+    }
+
     protected class H extends Handler {
         H(Looper looper) {
             super(looper);
diff --git a/packages/SystemUI/src/com/android/systemui/appops/PermissionFlagsCache.kt b/packages/SystemUI/src/com/android/systemui/appops/PermissionFlagsCache.kt
new file mode 100644
index 0000000..9248b4f8
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/appops/PermissionFlagsCache.kt
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.systemui.appops
+
+import android.content.pm.PackageManager
+import android.os.UserHandle
+import androidx.annotation.WorkerThread
+import com.android.systemui.dagger.qualifiers.Background
+import java.util.concurrent.Executor
+import javax.inject.Inject
+import javax.inject.Singleton
+
+private data class PermissionFlagKey(
+    val permission: String,
+    val packageName: String,
+    val uid: Int
+)
+
+/**
+ * Cache for PackageManager's PermissionFlags.
+ *
+ * After a specific `{permission, package, uid}` has been requested, updates to it will be tracked,
+ * and changes to the uid will trigger new requests (in the background).
+ */
+@Singleton
+class PermissionFlagsCache @Inject constructor(
+    private val packageManager: PackageManager,
+    @Background private val executor: Executor
+) : PackageManager.OnPermissionsChangedListener {
+
+    private val permissionFlagsCache =
+            mutableMapOf<Int, MutableMap<PermissionFlagKey, Int>>()
+    private var listening = false
+
+    override fun onPermissionsChanged(uid: Int) {
+        executor.execute {
+            // Only track those that we've seen before
+            val keys = permissionFlagsCache.get(uid)
+            if (keys != null) {
+                keys.mapValuesTo(keys) {
+                    getFlags(it.key)
+                }
+            }
+        }
+    }
+
+    /**
+     * Retrieve permission flags from cache or PackageManager. There parameters will be passed
+     * directly to [PackageManager].
+     *
+     * Calls to this method should be done from a background thread.
+     */
+    @WorkerThread
+    fun getPermissionFlags(permission: String, packageName: String, uid: Int): Int {
+        if (!listening) {
+            listening = true
+            packageManager.addOnPermissionsChangeListener(this)
+        }
+        val key = PermissionFlagKey(permission, packageName, uid)
+        return permissionFlagsCache.getOrPut(uid, { mutableMapOf() }).get(key) ?: run {
+            getFlags(key).also {
+                permissionFlagsCache.get(uid)?.put(key, it)
+            }
+        }
+    }
+
+    private fun getFlags(key: PermissionFlagKey): Int {
+        return packageManager.getPermissionFlags(key.permission, key.packageName,
+                UserHandle.getUserHandleForUid(key.uid))
+    }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/assist/AssistHandleReminderExpBehavior.java b/packages/SystemUI/src/com/android/systemui/assist/AssistHandleReminderExpBehavior.java
index 8e49d58..0085710 100644
--- a/packages/SystemUI/src/com/android/systemui/assist/AssistHandleReminderExpBehavior.java
+++ b/packages/SystemUI/src/com/android/systemui/assist/AssistHandleReminderExpBehavior.java
@@ -26,6 +26,8 @@
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.pm.ResolveInfo;
+import android.database.ContentObserver;
+import android.net.Uri;
 import android.os.Handler;
 import android.provider.Settings;
 
@@ -66,8 +68,10 @@
 @Singleton
 final class AssistHandleReminderExpBehavior implements BehaviorController {
 
-    private static final String LEARNING_TIME_ELAPSED_KEY = "reminder_exp_learning_time_elapsed";
-    private static final String LEARNING_EVENT_COUNT_KEY = "reminder_exp_learning_event_count";
+    private static final Uri LEARNING_TIME_ELAPSED_URI =
+            Settings.Secure.getUriFor(Settings.Secure.ASSIST_HANDLES_LEARNING_TIME_ELAPSED_MILLIS);
+    private static final Uri LEARNING_EVENT_COUNT_URI =
+            Settings.Secure.getUriFor(Settings.Secure.ASSIST_HANDLES_LEARNING_EVENT_COUNT);
     private static final String LEARNED_HINT_LAST_SHOWN_KEY =
             "reminder_exp_learned_hint_last_shown";
     private static final long DEFAULT_LEARNING_TIME_MS = TimeUnit.DAYS.toMillis(10);
@@ -181,6 +185,7 @@
     private boolean mIsNavBarHidden;
     private boolean mIsLauncherShowing;
     private int mConsecutiveTaskSwitches;
+    @Nullable private ContentObserver mSettingObserver;
 
     /** Whether user has learned the gesture. */
     private boolean mIsLearned;
@@ -248,9 +253,22 @@
         mWakefulnessLifecycle.get().addObserver(mWakefulnessLifecycleObserver);
 
         mLearningTimeElapsed = Settings.Secure.getLong(
-                context.getContentResolver(), LEARNING_TIME_ELAPSED_KEY, /* default = */ 0);
+                context.getContentResolver(),
+                Settings.Secure.ASSIST_HANDLES_LEARNING_TIME_ELAPSED_MILLIS,
+                /* default = */ 0);
         mLearningCount = Settings.Secure.getInt(
-                context.getContentResolver(), LEARNING_EVENT_COUNT_KEY, /* default = */ 0);
+                context.getContentResolver(),
+                Settings.Secure.ASSIST_HANDLES_LEARNING_EVENT_COUNT,
+                /* default = */ 0);
+        mSettingObserver = new SettingsObserver(context, mHandler);
+        context.getContentResolver().registerContentObserver(
+                LEARNING_TIME_ELAPSED_URI,
+                /* notifyForDescendants = */ true,
+                mSettingObserver);
+        context.getContentResolver().registerContentObserver(
+                LEARNING_EVENT_COUNT_URI,
+                /* notifyForDescendants = */ true,
+                mSettingObserver);
         mLearnedHintLastShownEpochDay = Settings.Secure.getLong(
                 context.getContentResolver(), LEARNED_HINT_LAST_SHOWN_KEY, /* default = */ 0);
         mLastLearningTimestamp = mClock.currentTimeMillis();
@@ -264,8 +282,20 @@
         if (mContext != null) {
             mBroadcastDispatcher.get().unregisterReceiver(mDefaultHomeBroadcastReceiver);
             mBootCompleteCache.get().removeListener(mBootCompleteListener);
-            Settings.Secure.putLong(mContext.getContentResolver(), LEARNING_TIME_ELAPSED_KEY, 0);
-            Settings.Secure.putInt(mContext.getContentResolver(), LEARNING_EVENT_COUNT_KEY, 0);
+            mContext.getContentResolver().unregisterContentObserver(mSettingObserver);
+            mSettingObserver = null;
+            // putString to use overrideableByRestore
+            Settings.Secure.putString(
+                    mContext.getContentResolver(),
+                    Settings.Secure.ASSIST_HANDLES_LEARNING_TIME_ELAPSED_MILLIS,
+                    Long.toString(0L),
+                    /* overrideableByRestore = */ true);
+            // putString to use overrideableByRestore
+            Settings.Secure.putString(
+                    mContext.getContentResolver(),
+                    Settings.Secure.ASSIST_HANDLES_LEARNING_EVENT_COUNT,
+                    Integer.toString(0),
+                    /* overrideableByRestore = */ true);
             Settings.Secure.putLong(mContext.getContentResolver(), LEARNED_HINT_LAST_SHOWN_KEY, 0);
             mContext = null;
         }
@@ -282,8 +312,12 @@
             return;
         }
 
-        Settings.Secure.putLong(
-                mContext.getContentResolver(), LEARNING_EVENT_COUNT_KEY, ++mLearningCount);
+        // putString to use overrideableByRestore
+        Settings.Secure.putString(
+                mContext.getContentResolver(),
+                Settings.Secure.ASSIST_HANDLES_LEARNING_EVENT_COUNT,
+                Integer.toString(++mLearningCount),
+                /* overrideableByRestore = */ true);
     }
 
     @Override
@@ -460,8 +494,12 @@
         mIsLearned =
                 mLearningCount >= getLearningCount() || mLearningTimeElapsed >= getLearningTimeMs();
 
-        mHandler.post(() -> Settings.Secure.putLong(
-                mContext.getContentResolver(), LEARNING_TIME_ELAPSED_KEY, mLearningTimeElapsed));
+        // putString to use overrideableByRestore
+        mHandler.post(() -> Settings.Secure.putString(
+                mContext.getContentResolver(),
+                Settings.Secure.ASSIST_HANDLES_LEARNING_TIME_ELAPSED_MILLIS,
+                Long.toString(mLearningTimeElapsed),
+                /* overrideableByRestore = */ true));
     }
 
     private void resetConsecutiveTaskSwitches() {
@@ -589,4 +627,32 @@
                 + "="
                 + getShowWhenTaught());
     }
+
+    private final class SettingsObserver extends ContentObserver {
+
+        private final Context mContext;
+
+        SettingsObserver(Context context, Handler handler) {
+            super(handler);
+            mContext = context;
+        }
+
+        @Override
+        public void onChange(boolean selfChange, @Nullable Uri uri) {
+            if (LEARNING_TIME_ELAPSED_URI.equals(uri)) {
+                mLastLearningTimestamp = mClock.currentTimeMillis();
+                mLearningTimeElapsed = Settings.Secure.getLong(
+                        mContext.getContentResolver(),
+                        Settings.Secure.ASSIST_HANDLES_LEARNING_TIME_ELAPSED_MILLIS,
+                        /* default = */ 0);
+            } else if (LEARNING_EVENT_COUNT_URI.equals(uri)) {
+                mLearningCount = Settings.Secure.getInt(
+                        mContext.getContentResolver(),
+                        Settings.Secure.ASSIST_HANDLES_LEARNING_EVENT_COUNT,
+                        /* default = */ 0);
+            }
+
+            super.onChange(selfChange, uri);
+        }
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/assist/AssistantSessionEvent.kt b/packages/SystemUI/src/com/android/systemui/assist/AssistantSessionEvent.kt
index 8b953fa..be089b1 100644
--- a/packages/SystemUI/src/com/android/systemui/assist/AssistantSessionEvent.kt
+++ b/packages/SystemUI/src/com/android/systemui/assist/AssistantSessionEvent.kt
@@ -21,7 +21,7 @@
 
 enum class AssistantSessionEvent(private val id: Int) : UiEventLogger.UiEventEnum {
     @UiEvent(doc = "Unknown assistant session event")
-    ASSISTANT_SESSION_UNKNOWN(523),
+    ASSISTANT_SESSION_UNKNOWN(0),
 
     @UiEvent(doc = "Assistant session dismissed due to timeout")
     ASSISTANT_SESSION_TIMEOUT_DISMISS(524),
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/Bubble.java b/packages/SystemUI/src/com/android/systemui/bubbles/Bubble.java
index c6d1286..b71e3ad 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/Bubble.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/Bubble.java
@@ -286,6 +286,15 @@
     }
 
     /**
+     * Sets whether this bubble is considered visually interruptive. Normally pulled from the
+     * {@link NotificationEntry}, this method is purely for testing.
+     */
+    @VisibleForTesting
+    void setVisuallyInterruptiveForTest(boolean visuallyInterruptive) {
+        mIsVisuallyInterruptive = visuallyInterruptive;
+    }
+
+    /**
      * Starts a task to inflate & load any necessary information to display a bubble.
      *
      * @param callback the callback to notify one the bubble is ready to be displayed.
@@ -411,6 +420,7 @@
             } else if (mIntent != null && entry.getBubbleMetadata().getIntent() == null) {
                 // Was an intent bubble now it's a shortcut bubble... still unregister the listener
                 mIntent.unregisterCancelListener(mIntentCancelListener);
+                mIntentActive = false;
                 mIntent = null;
             }
             mDeleteIntent = entry.getBubbleMetadata().getDeleteIntent();
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
index e252195..5deae92 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
@@ -133,7 +133,8 @@
     @IntDef({DISMISS_USER_GESTURE, DISMISS_AGED, DISMISS_TASK_FINISHED, DISMISS_BLOCKED,
             DISMISS_NOTIF_CANCEL, DISMISS_ACCESSIBILITY_ACTION, DISMISS_NO_LONGER_BUBBLE,
             DISMISS_USER_CHANGED, DISMISS_GROUP_CANCELLED, DISMISS_INVALID_INTENT,
-            DISMISS_OVERFLOW_MAX_REACHED, DISMISS_SHORTCUT_REMOVED, DISMISS_PACKAGE_REMOVED})
+            DISMISS_OVERFLOW_MAX_REACHED, DISMISS_SHORTCUT_REMOVED, DISMISS_PACKAGE_REMOVED,
+            DISMISS_NO_BUBBLE_UP})
     @Target({FIELD, LOCAL_VARIABLE, PARAMETER})
     @interface DismissReason {}
 
@@ -150,6 +151,7 @@
     static final int DISMISS_OVERFLOW_MAX_REACHED = 11;
     static final int DISMISS_SHORTCUT_REMOVED = 12;
     static final int DISMISS_PACKAGE_REMOVED = 13;
+    static final int DISMISS_NO_BUBBLE_UP = 14;
 
     private final Context mContext;
     private final NotificationEntryManager mNotificationEntryManager;
@@ -168,6 +170,12 @@
     @Nullable private BubbleStackView mStackView;
     private BubbleIconFactory mBubbleIconFactory;
 
+    /**
+     * The relative position of the stack when we removed it and nulled it out. If the stack is
+     * re-created, it will re-appear at this position.
+     */
+    @Nullable private BubbleStackView.RelativeStackPosition mPositionFromRemovedStack;
+
     // Tracks the id of the current (foreground) user.
     private int mCurrentUserId;
     // Saves notification keys of active bubbles when users are switched.
@@ -398,7 +406,8 @@
             if (bubble.getBubbleIntent() == null) {
                 return;
             }
-            if (bubble.isIntentActive()) {
+            if (bubble.isIntentActive()
+                    || mBubbleData.hasBubbleInStackWithKey(bubble.getKey())) {
                 bubble.setPendingIntentCanceled();
                 return;
             }
@@ -718,6 +727,7 @@
                     mContext, mBubbleData, mSurfaceSynchronizer, mFloatingContentCoordinator,
                     mSysUiState, this::onAllBubblesAnimatedOut, this::onImeVisibilityChanged,
                     this::hideCurrentInputMethod);
+            mStackView.setStackStartPosition(mPositionFromRemovedStack);
             mStackView.addView(mBubbleScrim);
             if (mExpandListener != null) {
                 mStackView.setExpandListener(mExpandListener);
@@ -787,6 +797,7 @@
         try {
             mAddedToWindowManager = false;
             if (mStackView != null) {
+                mPositionFromRemovedStack = mStackView.getRelativeStackPosition();
                 mWindowManager.removeView(mStackView);
                 mStackView.removeView(mBubbleScrim);
                 mStackView = null;
@@ -1109,8 +1120,17 @@
         if (notif.getImportance() >= NotificationManager.IMPORTANCE_HIGH) {
             notif.setInterruption();
         }
-        Bubble bubble = mBubbleData.getOrCreateBubble(notif, null /* persistedBubble */);
-        inflateAndAdd(bubble, suppressFlyout, showInShade);
+        if (!notif.getRanking().visuallyInterruptive()
+                && (notif.getBubbleMetadata() != null
+                    && !notif.getBubbleMetadata().getAutoExpandBubble())
+                && mBubbleData.hasOverflowBubbleWithKey(notif.getKey())) {
+            // Update the bubble but don't promote it out of overflow
+            Bubble b = mBubbleData.getOverflowBubbleWithKey(notif.getKey());
+            b.setEntry(notif);
+        } else {
+            Bubble bubble = mBubbleData.getOrCreateBubble(notif, null /* persistedBubble */);
+            inflateAndAdd(bubble, suppressFlyout, showInShade);
+        }
     }
 
     void inflateAndAdd(Bubble bubble, boolean suppressFlyout, boolean showInShade) {
@@ -1234,8 +1254,18 @@
             rankingMap.getRanking(key, mTmpRanking);
             boolean isActiveBubble = mBubbleData.hasAnyBubbleWithKey(key);
             if (isActiveBubble && !mTmpRanking.canBubble()) {
-                mBubbleData.dismissBubbleWithKey(entry.getKey(),
-                        BubbleController.DISMISS_BLOCKED);
+                // If this entry is no longer allowed to bubble, dismiss with the BLOCKED reason.
+                // This means that the app or channel's ability to bubble has been revoked.
+                mBubbleData.dismissBubbleWithKey(
+                        key, BubbleController.DISMISS_BLOCKED);
+            } else if (isActiveBubble
+                    && !mNotificationInterruptStateProvider.shouldBubbleUp(entry)) {
+                // If this entry is allowed to bubble, but cannot currently bubble up, dismiss it.
+                // This happens when DND is enabled and configured to hide bubbles. Dismissing with
+                // the reason DISMISS_NO_BUBBLE_UP will retain the underlying notification, so that
+                // the bubble will be re-created if shouldBubbleUp returns true.
+                mBubbleData.dismissBubbleWithKey(
+                        key, BubbleController.DISMISS_NO_BUBBLE_UP);
             } else if (entry != null && mTmpRanking.isBubble() && !isActiveBubble) {
                 entry.setFlagBubble(true);
                 onEntryUpdated(entry);
@@ -1312,8 +1342,10 @@
                     mStackView.removeBubble(bubble);
                 }
 
-                // If the bubble is removed for user switching, leave the notification in place.
-                if (reason == DISMISS_USER_CHANGED) {
+                // Leave the notification in place if we're dismissing due to user switching, or
+                // because DND is suppressing the bubble. In both of those cases, we need to be able
+                // to restore the bubble from the notification later.
+                if (reason == DISMISS_USER_CHANGED || reason == DISMISS_NO_BUBBLE_UP) {
                     continue;
                 }
                 if (reason == DISMISS_NOTIF_CANCEL) {
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java
index d2dc506..85ea8bc 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java
@@ -277,7 +277,8 @@
         } else {
             // Updates an existing bubble
             bubble.setSuppressFlyout(suppressFlyout);
-            doUpdate(bubble);
+            // If there is no flyout, we probably shouldn't show the bubble at the top
+            doUpdate(bubble, !suppressFlyout /* reorder */);
         }
 
         if (bubble.shouldAutoExpand()) {
@@ -431,12 +432,12 @@
         }
     }
 
-    private void doUpdate(Bubble bubble) {
+    private void doUpdate(Bubble bubble, boolean reorder) {
         if (DEBUG_BUBBLE_DATA) {
             Log.d(TAG, "doUpdate: " + bubble);
         }
         mStateChange.updatedBubble = bubble;
-        if (!isExpanded()) {
+        if (!isExpanded() && reorder) {
             int prevPos = mBubbles.indexOf(bubble);
             mBubbles.remove(bubble);
             mBubbles.add(0, bubble);
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java
index 110a5ab..58d5776 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java
@@ -306,7 +306,6 @@
         // Set ActivityView's alpha value as zero, since there is no view content to be shown.
         setContentVisibility(false);
 
-        mActivityViewContainer.setBackgroundColor(Color.WHITE);
         mActivityViewContainer.setOutlineProvider(new ViewOutlineProvider() {
             @Override
             public void getOutline(View view, Outline outline) {
@@ -434,9 +433,11 @@
     }
 
     void applyThemeAttrs() {
-        final TypedArray ta = mContext.obtainStyledAttributes(
-                new int[] {android.R.attr.dialogCornerRadius});
+        final TypedArray ta = mContext.obtainStyledAttributes(new int[] {
+                android.R.attr.dialogCornerRadius,
+                android.R.attr.colorBackgroundFloating});
         mCornerRadius = ta.getDimensionPixelSize(0, 0);
+        mActivityViewContainer.setBackgroundColor(ta.getColor(1, Color.WHITE));
         ta.recycle();
 
         if (mActivityView != null && ScreenDecorationsUtils.supportsRoundedCornersOnWindows(
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleIconFactory.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleIconFactory.java
index 40a93e1..d017bc0 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleIconFactory.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleIconFactory.java
@@ -24,7 +24,8 @@
 import android.graphics.Bitmap;
 import android.graphics.Canvas;
 import android.graphics.Paint;
-import android.graphics.Rect;
+import android.graphics.Path;
+import android.graphics.drawable.AdaptiveIconDrawable;
 import android.graphics.drawable.BitmapDrawable;
 import android.graphics.drawable.Drawable;
 import android.graphics.drawable.Icon;
@@ -41,15 +42,15 @@
  */
 public class BubbleIconFactory extends BaseIconFactory {
 
+    private int mBadgeSize;
+
     protected BubbleIconFactory(Context context) {
         super(context, context.getResources().getConfiguration().densityDpi,
                 context.getResources().getDimensionPixelSize(R.dimen.individual_bubble_size));
-    }
-
-    int getBadgeSize() {
-        return mContext.getResources().getDimensionPixelSize(
+        mBadgeSize = mContext.getResources().getDimensionPixelSize(
                 com.android.launcher3.icons.R.dimen.profile_badge_size);
     }
+
     /**
      * Returns the drawable that the developer has provided to display in the bubble.
      */
@@ -79,25 +80,34 @@
      * will include the workprofile indicator on the badge if appropriate.
      */
     BitmapInfo getBadgeBitmap(Drawable userBadgedAppIcon, boolean isImportantConversation) {
-        Bitmap userBadgedBitmap = createIconBitmap(
-                userBadgedAppIcon, 1f, getBadgeSize());
-        ShadowGenerator shadowGenerator = new ShadowGenerator(getBadgeSize());
-        if (!isImportantConversation) {
-            Canvas c = new Canvas();
-            c.setBitmap(userBadgedBitmap);
-            shadowGenerator.recreateIcon(Bitmap.createBitmap(userBadgedBitmap), c);
-            return createIconBitmap(userBadgedBitmap);
-        } else {
-            float ringStrokeWidth = mContext.getResources().getDimensionPixelSize(
+        ShadowGenerator shadowGenerator = new ShadowGenerator(mBadgeSize);
+        Bitmap userBadgedBitmap = createIconBitmap(userBadgedAppIcon, 1f, mBadgeSize);
+
+        if (userBadgedAppIcon instanceof AdaptiveIconDrawable) {
+            userBadgedBitmap = Bitmap.createScaledBitmap(
+                    getCircleBitmap((AdaptiveIconDrawable) userBadgedAppIcon, /* size */
+                            userBadgedAppIcon.getIntrinsicWidth()),
+                    mBadgeSize, mBadgeSize, /* filter */ true);
+        }
+
+        if (isImportantConversation) {
+            final float ringStrokeWidth = mContext.getResources().getDimensionPixelSize(
                     com.android.internal.R.dimen.importance_ring_stroke_width);
-            int importantConversationColor = mContext.getResources().getColor(
+            final int importantConversationColor = mContext.getResources().getColor(
                     com.android.settingslib.R.color.important_conversation, null);
             Bitmap badgeAndRing = Bitmap.createBitmap(userBadgedBitmap.getWidth(),
                     userBadgedBitmap.getHeight(), userBadgedBitmap.getConfig());
             Canvas c = new Canvas(badgeAndRing);
-            Rect dest = new Rect((int) ringStrokeWidth, (int) ringStrokeWidth,
-                    c.getHeight() - (int) ringStrokeWidth, c.getWidth() - (int) ringStrokeWidth);
-            c.drawBitmap(userBadgedBitmap, null, dest, null);
+
+            final int bitmapTop = (int) ringStrokeWidth;
+            final int bitmapLeft = (int) ringStrokeWidth;
+            final int bitmapWidth = c.getWidth() - 2 * (int) ringStrokeWidth;
+            final int bitmapHeight = c.getHeight() - 2 * (int) ringStrokeWidth;
+
+            Bitmap scaledBitmap = Bitmap.createScaledBitmap(userBadgedBitmap, bitmapWidth,
+                    bitmapHeight, /* filter */ true);
+            c.drawBitmap(scaledBitmap, bitmapTop, bitmapLeft, /* paint */null);
+
             Paint ringPaint = new Paint();
             ringPaint.setStyle(Paint.Style.STROKE);
             ringPaint.setColor(importantConversationColor);
@@ -105,11 +115,48 @@
             ringPaint.setStrokeWidth(ringStrokeWidth);
             c.drawCircle(c.getWidth() / 2, c.getHeight() / 2, c.getWidth() / 2 - ringStrokeWidth,
                     ringPaint);
+
             shadowGenerator.recreateIcon(Bitmap.createBitmap(badgeAndRing), c);
             return createIconBitmap(badgeAndRing);
+        } else {
+            Canvas c = new Canvas();
+            c.setBitmap(userBadgedBitmap);
+            shadowGenerator.recreateIcon(Bitmap.createBitmap(userBadgedBitmap), c);
+            return createIconBitmap(userBadgedBitmap);
         }
     }
 
+    public Bitmap getCircleBitmap(AdaptiveIconDrawable icon, int size) {
+        Drawable foreground = icon.getForeground();
+        Drawable background = icon.getBackground();
+        Bitmap bitmap = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888);
+        Canvas canvas = new Canvas();
+        canvas.setBitmap(bitmap);
+
+        // Clip canvas to circle.
+        Path circlePath = new Path();
+        circlePath.addCircle(/* x */ size / 2f,
+                /* y */ size / 2f,
+                /* radius */ size / 2f,
+                Path.Direction.CW);
+        canvas.clipPath(circlePath);
+
+        // Draw background.
+        background.setBounds(0, 0, size, size);
+        background.draw(canvas);
+
+        // Draw foreground. The foreground and background drawables are derived from adaptive icons
+        // Some icon shapes fill more space than others, so adaptive icons are normalized to about
+        // the same size. This size is smaller than the original bounds, so we estimate
+        // the difference in this offset.
+        int offset = size / 5;
+        foreground.setBounds(-offset, -offset, size + offset, size + offset);
+        foreground.draw(canvas);
+
+        canvas.setBitmap(null);
+        return bitmap;
+    }
+
     /**
      * Returns a {@link BitmapInfo} for the entire bubble icon including the badge.
      */
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleLoggerImpl.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleLoggerImpl.java
index c1dd8c3..48a9b917 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleLoggerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleLoggerImpl.java
@@ -30,10 +30,6 @@
      * @param e UI event
      */
     public void log(Bubble b, UiEventEnum e) {
-        if (b.getInstanceId() == null) {
-            // Added from persistence -- TODO log this with specific event?
-            return;
-        }
         logWithInstanceId(e, b.getAppUid(), b.getPackageName(), b.getInstanceId());
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
index 749b537..f2d8732 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
@@ -258,13 +258,8 @@
 
     /** Layout change listener that moves the stack to the nearest valid position on rotation. */
     private OnLayoutChangeListener mOrientationChangedListener;
-    /** Whether the stack was on the left side of the screen prior to rotation. */
-    private boolean mWasOnLeftBeforeRotation = false;
-    /**
-     * How far down the screen the stack was before rotation, in terms of percentage of the way down
-     * the allowable region. Defaults to -1 if not set.
-     */
-    private float mVerticalPosPercentBeforeRotation = -1;
+
+    @Nullable private RelativeStackPosition mRelativeStackPositionBeforeRotation;
 
     private int mMaxBubbles;
     private int mBubbleSize;
@@ -967,9 +962,10 @@
                         mExpandedViewContainer.setTranslationY(getExpandedViewY());
                         mExpandedViewContainer.setAlpha(1f);
                     }
-                    if (mVerticalPosPercentBeforeRotation >= 0) {
-                        mStackAnimationController.moveStackToSimilarPositionAfterRotation(
-                                mWasOnLeftBeforeRotation, mVerticalPosPercentBeforeRotation);
+                    if (mRelativeStackPositionBeforeRotation != null) {
+                        mStackAnimationController.setStackPosition(
+                                mRelativeStackPositionBeforeRotation);
+                        mRelativeStackPositionBeforeRotation = null;
                     }
                     removeOnLayoutChangeListener(mOrientationChangedListener);
                 };
@@ -1231,13 +1227,7 @@
                 com.android.internal.R.dimen.status_bar_height);
         mBubblePaddingTop = res.getDimensionPixelSize(R.dimen.bubble_padding_top);
 
-        final RectF allowablePos = mStackAnimationController.getAllowableStackPositionRegion();
-        mWasOnLeftBeforeRotation = mStackAnimationController.isStackOnLeftSide();
-        mVerticalPosPercentBeforeRotation =
-                (mStackAnimationController.getStackPosition().y - allowablePos.top)
-                        / (allowablePos.bottom - allowablePos.top);
-        mVerticalPosPercentBeforeRotation =
-                Math.max(0f, Math.min(1f, mVerticalPosPercentBeforeRotation));
+        mRelativeStackPositionBeforeRotation = mStackAnimationController.getRelativeStackPosition();
         addOnLayoutChangeListener(mOrientationChangedListener);
         hideFlyoutImmediate();
 
@@ -1506,7 +1496,7 @@
         if (getBubbleCount() == 0 && mShouldShowUserEducation) {
             // Override the default stack position if we're showing user education.
             mStackAnimationController.setStackPosition(
-                    mStackAnimationController.getDefaultStartPosition());
+                    mStackAnimationController.getStartPosition());
         }
 
         if (getBubbleCount() == 0) {
@@ -1586,6 +1576,11 @@
             Log.d(TAG, "setSelectedBubble: " + bubbleToSelect);
         }
 
+        if (bubbleToSelect == null) {
+            mBubbleData.setShowingOverflow(false);
+            return;
+        }
+
         // Ignore this new bubble only if it is the exact same bubble object. Otherwise, we'll want
         // to re-render it even if it has the same key (equals() returns true). If the currently
         // expanded bubble is removed and instantly re-added, we'll get back a new Bubble instance
@@ -1594,10 +1589,11 @@
         if (mExpandedBubble == bubbleToSelect) {
             return;
         }
-        if (bubbleToSelect == null || bubbleToSelect.getKey() != BubbleOverflow.KEY) {
-            mBubbleData.setShowingOverflow(false);
-        } else {
+
+        if (bubbleToSelect.getKey() == BubbleOverflow.KEY) {
             mBubbleData.setShowingOverflow(true);
+        } else {
+            mBubbleData.setShowingOverflow(false);
         }
 
         if (mIsExpanded && mIsExpansionAnimating) {
@@ -1715,7 +1711,7 @@
             // Post so we have height of mUserEducationView
             mUserEducationView.post(() -> {
                 final int viewHeight = mUserEducationView.getHeight();
-                PointF stackPosition = mStackAnimationController.getDefaultStartPosition();
+                PointF stackPosition = mStackAnimationController.getStartPosition();
                 final float translationY = stackPosition.y + (mBubbleSize / 2) - (viewHeight / 2);
                 mUserEducationView.setTranslationY(translationY);
                 mUserEducationView.animate()
@@ -2871,10 +2867,18 @@
                 .floatValue();
     }
 
+    public void setStackStartPosition(RelativeStackPosition position) {
+        mStackAnimationController.setStackStartPosition(position);
+    }
+
     public PointF getStackPosition() {
         return mStackAnimationController.getStackPosition();
     }
 
+    public RelativeStackPosition getRelativeStackPosition() {
+        return mStackAnimationController.getRelativeStackPosition();
+    }
+
     /**
      * Logs the bubble UI event.
      *
@@ -2938,4 +2942,47 @@
         }
         return bubbles;
     }
+
+    /**
+     * Representation of stack position that uses relative properties rather than absolute
+     * coordinates. This is used to maintain similar stack positions across configuration changes.
+     */
+    public static class RelativeStackPosition {
+        /** Whether to place the stack at the leftmost allowed position. */
+        private boolean mOnLeft;
+
+        /**
+         * How far down the vertically allowed region to place the stack. For example, if the stack
+         * allowed region is between y = 100 and y = 1100 and this is 0.2f, we'll place the stack at
+         * 100 + (0.2f * 1000) = 300.
+         */
+        private float mVerticalOffsetPercent;
+
+        public RelativeStackPosition(boolean onLeft, float verticalOffsetPercent) {
+            mOnLeft = onLeft;
+            mVerticalOffsetPercent = clampVerticalOffsetPercent(verticalOffsetPercent);
+        }
+
+        /** Constructs a relative position given a region and a point in that region. */
+        public RelativeStackPosition(PointF position, RectF region) {
+            mOnLeft = position.x < region.width() / 2;
+            mVerticalOffsetPercent =
+                    clampVerticalOffsetPercent((position.y - region.top) / region.height());
+        }
+
+        /** Ensures that the offset percent is between 0f and 1f. */
+        private float clampVerticalOffsetPercent(float offsetPercent) {
+            return Math.max(0f, Math.min(1f, offsetPercent));
+        }
+
+        /**
+         * Given an allowable stack position region, returns the point within that region
+         * represented by this relative position.
+         */
+        public PointF getAbsolutePositionInRegion(RectF region) {
+            return new PointF(
+                    mOnLeft ? region.left : region.right,
+                    region.top + mVerticalOffsetPercent * region.height());
+        }
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/animation/StackAnimationController.java b/packages/SystemUI/src/com/android/systemui/bubbles/animation/StackAnimationController.java
index b378469..e835ea2 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/animation/StackAnimationController.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/animation/StackAnimationController.java
@@ -35,6 +35,7 @@
 import androidx.dynamicanimation.animation.SpringForce;
 
 import com.android.systemui.R;
+import com.android.systemui.bubbles.BubbleStackView;
 import com.android.systemui.util.FloatingContentCoordinator;
 import com.android.systemui.util.animation.PhysicsAnimator;
 import com.android.systemui.util.magnetictarget.MagnetizedObject;
@@ -125,6 +126,9 @@
      */
     private Rect mAnimatingToBounds = new Rect();
 
+    /** Initial starting location for the stack. */
+    @Nullable private BubbleStackView.RelativeStackPosition mStackStartPosition;
+
     /** Whether or not the stack's start position has been set. */
     private boolean mStackMovedToStartPosition = false;
 
@@ -431,21 +435,6 @@
         return stackPos;
     }
 
-    /**
-     * Moves the stack in response to rotation. We keep it in the most similar position by keeping
-     * it on the same side, and positioning it the same percentage of the way down the screen
-     * (taking status bar/nav bar into account by using the allowable region's height).
-     */
-    public void moveStackToSimilarPositionAfterRotation(boolean wasOnLeft, float verticalPercent) {
-        final RectF allowablePos = getAllowableStackPositionRegion();
-        final float allowableRegionHeight = allowablePos.bottom - allowablePos.top;
-
-        final float x = wasOnLeft ? allowablePos.left : allowablePos.right;
-        final float y = (allowableRegionHeight * verticalPercent) + allowablePos.top;
-
-        setStackPosition(new PointF(x, y));
-    }
-
     /** Description of current animation controller state. */
     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
         pw.println("StackAnimationController state:");
@@ -815,7 +804,7 @@
         } else {
             // When all children are removed ensure stack position is sane
             setStackPosition(mRestingStackPosition == null
-                    ? getDefaultStartPosition()
+                    ? getStartPosition()
                     : mRestingStackPosition);
 
             // Remove the stack from the coordinator since we don't have any bubbles and aren't
@@ -868,7 +857,7 @@
         mLayout.setVisibility(View.INVISIBLE);
         mLayout.post(() -> {
             setStackPosition(mRestingStackPosition == null
-                    ? getDefaultStartPosition()
+                    ? getStartPosition()
                     : mRestingStackPosition);
             mStackMovedToStartPosition = true;
             mLayout.setVisibility(View.VISIBLE);
@@ -938,15 +927,47 @@
         }
     }
 
-    /** Returns the default stack position, which is on the top left. */
-    public PointF getDefaultStartPosition() {
-        boolean isRtl = mLayout != null
-                && mLayout.getResources().getConfiguration().getLayoutDirection()
-                == View.LAYOUT_DIRECTION_RTL;
-        return new PointF(isRtl
-                        ? getAllowableStackPositionRegion().right
-                        : getAllowableStackPositionRegion().left,
-                getAllowableStackPositionRegion().top + mStackStartingVerticalOffset);
+    public void setStackPosition(BubbleStackView.RelativeStackPosition position) {
+        setStackPosition(position.getAbsolutePositionInRegion(getAllowableStackPositionRegion()));
+    }
+
+    public BubbleStackView.RelativeStackPosition getRelativeStackPosition() {
+        return new BubbleStackView.RelativeStackPosition(
+                mStackPosition, getAllowableStackPositionRegion());
+    }
+
+    /**
+     * Sets the starting position for the stack, where it will be located when the first bubble is
+     * added.
+     */
+    public void setStackStartPosition(BubbleStackView.RelativeStackPosition position) {
+        mStackStartPosition = position;
+    }
+
+    /**
+     * Returns the starting stack position. If {@link #setStackStartPosition} was called, this will
+     * return that position - otherwise, a reasonable default will be returned.
+     */
+    @Nullable public PointF getStartPosition() {
+        if (mLayout == null) {
+            return null;
+        }
+
+        if (mStackStartPosition == null) {
+            // Start on the left if we're in LTR, right otherwise.
+            final boolean startOnLeft =
+                    mLayout.getResources().getConfiguration().getLayoutDirection()
+                            != View.LAYOUT_DIRECTION_RTL;
+
+            final float startingVerticalOffset = mLayout.getResources().getDimensionPixelOffset(
+                    R.dimen.bubble_stack_starting_offset_y);
+
+            mStackStartPosition = new BubbleStackView.RelativeStackPosition(
+                    startOnLeft,
+                    startingVerticalOffset / getAllowableStackPositionRegion().height());
+        }
+
+        return mStackStartPosition.getAbsolutePositionInRegion(getAllowableStackPositionRegion());
     }
 
     private boolean isStackPositionSet() {
diff --git a/packages/SystemUI/src/com/android/systemui/controls/CustomIconCache.kt b/packages/SystemUI/src/com/android/systemui/controls/CustomIconCache.kt
new file mode 100644
index 0000000..cca0f16
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/controls/CustomIconCache.kt
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.controls
+
+import android.content.ComponentName
+import android.graphics.drawable.Icon
+import androidx.annotation.GuardedBy
+import javax.inject.Inject
+import javax.inject.Singleton
+
+/**
+ * Icon cache for custom icons sent with controls.
+ *
+ * It assumes that only one component can be current at the time, to minimize the number of icons
+ * stored at a given time.
+ */
+@Singleton
+class CustomIconCache @Inject constructor() {
+
+    private var currentComponent: ComponentName? = null
+    @GuardedBy("cache")
+    private val cache: MutableMap<String, Icon> = LinkedHashMap()
+
+    /**
+     * Store an icon in the cache.
+     *
+     * If the icons currently stored do not correspond to the component to be stored, the cache is
+     * cleared first.
+     */
+    fun store(component: ComponentName, controlId: String, icon: Icon?) {
+        if (component != currentComponent) {
+            clear()
+            currentComponent = component
+        }
+        synchronized(cache) {
+            if (icon != null) {
+                cache.put(controlId, icon)
+            } else {
+                cache.remove(controlId)
+            }
+        }
+    }
+
+    /**
+     * Retrieves a custom icon stored in the cache.
+     *
+     * It will return null if the component requested is not the one whose icons are stored, or if
+     * there is no icon cached for that id.
+     */
+    fun retrieve(component: ComponentName, controlId: String): Icon? {
+        if (component != currentComponent) return null
+        return synchronized(cache) {
+            cache.get(controlId)
+        }
+    }
+
+    private fun clear() {
+        synchronized(cache) {
+            cache.clear()
+        }
+    }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsFavoritePersistenceWrapper.kt b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsFavoritePersistenceWrapper.kt
index 1bda841..d930c98 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsFavoritePersistenceWrapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsFavoritePersistenceWrapper.kt
@@ -87,6 +87,10 @@
      * @param list a list of favorite controls. The list will be stored in the same order.
      */
     fun storeFavorites(structures: List<StructureInfo>) {
+        if (structures.isEmpty() && !file.exists()) {
+            // Do not create a new file to store nothing
+            return
+        }
         executor.execute {
             Log.d(TAG, "Saving data to file: $file")
             val atomicFile = AtomicFile(file)
diff --git a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsProviderLifecycleManager.kt b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsProviderLifecycleManager.kt
index ec8bfc6..977e46a 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsProviderLifecycleManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsProviderLifecycleManager.kt
@@ -76,7 +76,8 @@
         private const val LOAD_TIMEOUT_SECONDS = 20L // seconds
         private const val MAX_BIND_RETRIES = 5
         private const val DEBUG = true
-        private val BIND_FLAGS = Context.BIND_AUTO_CREATE or Context.BIND_FOREGROUND_SERVICE
+        private val BIND_FLAGS = Context.BIND_AUTO_CREATE or Context.BIND_FOREGROUND_SERVICE or
+            Context.BIND_NOT_PERCEPTIBLE
     }
 
     private val intent = Intent().apply {
diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/ControlAdapter.kt b/packages/SystemUI/src/com/android/systemui/controls/management/ControlAdapter.kt
index c683a87..40662536 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/management/ControlAdapter.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlAdapter.kt
@@ -72,8 +72,13 @@
             TYPE_CONTROL -> {
                 ControlHolder(
                     layoutInflater.inflate(R.layout.controls_base_item, parent, false).apply {
-                        layoutParams.apply {
+                        (layoutParams as ViewGroup.MarginLayoutParams).apply {
                             width = ViewGroup.LayoutParams.MATCH_PARENT
+                            // Reset margins as they will be set through the decoration
+                            topMargin = 0
+                            bottomMargin = 0
+                            leftMargin = 0
+                            rightMargin = 0
                         }
                         elevation = [email protected]
                         background = parent.context.getDrawable(
@@ -258,6 +263,7 @@
         val context = itemView.context
         val fg = context.getResources().getColorStateList(ri.foreground, context.getTheme())
 
+        icon.imageTintList = null
         ci.customIcon?.let {
             icon.setImageIcon(it)
         } ?: run {
@@ -386,7 +392,7 @@
         val type = parent.adapter?.getItemViewType(position)
         if (type == ControlAdapter.TYPE_CONTROL) {
             outRect.apply {
-                top = topMargin
+                top = topMargin * 2 // Use double margin, as we are not setting bottom
                 left = sideMargins
                 right = sideMargins
                 bottom = 0
diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsEditingActivity.kt b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsEditingActivity.kt
index ff40a8a..f68388d 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsEditingActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsEditingActivity.kt
@@ -29,6 +29,7 @@
 import androidx.recyclerview.widget.RecyclerView
 import com.android.systemui.R
 import com.android.systemui.broadcast.BroadcastDispatcher
+import com.android.systemui.controls.CustomIconCache
 import com.android.systemui.controls.controller.ControlsControllerImpl
 import com.android.systemui.controls.controller.StructureInfo
 import com.android.systemui.globalactions.GlobalActionsComponent
@@ -42,7 +43,8 @@
 class ControlsEditingActivity @Inject constructor(
     private val controller: ControlsControllerImpl,
     broadcastDispatcher: BroadcastDispatcher,
-    private val globalActionsComponent: GlobalActionsComponent
+    private val globalActionsComponent: GlobalActionsComponent,
+    private val customIconCache: CustomIconCache
 ) : LifecycleActivity() {
 
     companion object {
@@ -170,7 +172,7 @@
 
     private fun setUpList() {
         val controls = controller.getFavoritesForStructure(component, structure)
-        model = FavoritesModel(component, controls, favoritesModelCallback)
+        model = FavoritesModel(customIconCache, component, controls, favoritesModelCallback)
         val elevation = resources.getFloat(R.dimen.control_card_elevation)
         val recyclerView = requireViewById<RecyclerView>(R.id.list)
         recyclerView.alpha = 0.0f
diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsModel.kt b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsModel.kt
index 4ef64a5..ad0e7a5 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsModel.kt
@@ -114,11 +114,27 @@
     val controlStatus: ControlStatus
 ) : ElementWrapper(), ControlInterface by controlStatus
 
+private fun nullIconGetter(_a: ComponentName, _b: String): Icon? = null
+
 data class ControlInfoWrapper(
     override val component: ComponentName,
     val controlInfo: ControlInfo,
     override var favorite: Boolean
 ) : ElementWrapper(), ControlInterface {
+
+    var customIconGetter: (ComponentName, String) -> Icon? = ::nullIconGetter
+        private set
+
+    // Separate constructor so the getter is not used in auto-generated methods
+    constructor(
+        component: ComponentName,
+        controlInfo: ControlInfo,
+        favorite: Boolean,
+        customIconGetter: (ComponentName, String) -> Icon?
+    ): this(component, controlInfo, favorite) {
+        this.customIconGetter = customIconGetter
+    }
+
     override val controlId: String
         get() = controlInfo.controlId
     override val title: CharSequence
@@ -128,8 +144,7 @@
     override val deviceType: Int
         get() = controlInfo.deviceType
     override val customIcon: Icon?
-        // Will need to address to support for edit activity
-        get() = null
+        get() = customIconGetter(component, controlId)
 }
 
 data class DividerWrapper(
diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/FavoritesModel.kt b/packages/SystemUI/src/com/android/systemui/controls/management/FavoritesModel.kt
index 5242501..f9ce636 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/management/FavoritesModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/management/FavoritesModel.kt
@@ -21,6 +21,7 @@
 import androidx.recyclerview.widget.ItemTouchHelper
 import androidx.recyclerview.widget.RecyclerView
 import com.android.systemui.controls.ControlInterface
+import com.android.systemui.controls.CustomIconCache
 import com.android.systemui.controls.controller.ControlInfo
 import java.util.Collections
 
@@ -35,6 +36,7 @@
  * @property favoritesModelCallback callback to notify on first change and empty favorites
  */
 class FavoritesModel(
+    private val customIconCache: CustomIconCache,
     private val componentName: ComponentName,
     favorites: List<ControlInfo>,
     private val favoritesModelCallback: FavoritesModelCallback
@@ -83,7 +85,7 @@
         }
 
     override val elements: List<ElementWrapper> = favorites.map {
-        ControlInfoWrapper(componentName, it, true)
+        ControlInfoWrapper(componentName, it, true, customIconCache::retrieve)
     } + DividerWrapper()
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlActionCoordinatorImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlActionCoordinatorImpl.kt
index 22d6b6b..e15380b 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlActionCoordinatorImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlActionCoordinatorImpl.kt
@@ -92,7 +92,7 @@
     override fun setValue(cvh: ControlViewHolder, templateId: String, newValue: Float) {
         bouncerOrRun(Action(cvh.cws.ci.controlId, {
             cvh.action(FloatAction(templateId, newValue))
-        }, true /* blockable */))
+        }, false /* blockable */))
     }
 
     override fun longPress(cvh: ControlViewHolder) {
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt
index 1eb7e21..5a52597 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt
@@ -44,6 +44,7 @@
 import android.widget.TextView
 import com.android.systemui.R
 import com.android.systemui.controls.ControlsServiceInfo
+import com.android.systemui.controls.CustomIconCache
 import com.android.systemui.controls.controller.ControlInfo
 import com.android.systemui.controls.controller.ControlsController
 import com.android.systemui.controls.controller.StructureInfo
@@ -75,7 +76,8 @@
     @Main val sharedPreferences: SharedPreferences,
     val controlActionCoordinator: ControlActionCoordinator,
     private val activityStarter: ActivityStarter,
-    private val shadeController: ShadeController
+    private val shadeController: ShadeController,
+    private val iconCache: CustomIconCache
 ) : ControlsUiController {
 
     companion object {
@@ -102,6 +104,7 @@
     private var hidden = true
     private lateinit var dismissGlobalActions: Runnable
     private val popupThemedContext = ContextThemeWrapper(context, R.style.Control_ListPopupWindow)
+    private var retainCache = false
 
     private val collator = Collator.getInstance(context.resources.configuration.locales[0])
     private val localeComparator = compareBy<SelectionItem, CharSequence>(collator) {
@@ -147,6 +150,7 @@
         this.parent = parent
         this.dismissGlobalActions = dismissGlobalActions
         hidden = false
+        retainCache = false
 
         allStructures = controlsController.get().getFavorites()
         selectedStructure = loadPreference(allStructures)
@@ -233,6 +237,8 @@
         }
         putIntentExtras(i, si)
         startActivity(context, i)
+
+        retainCache = true
     }
 
     private fun putIntentExtras(intent: Intent, si: StructureInfo) {
@@ -495,13 +501,14 @@
 
         controlsListingController.get().removeCallback(listingCallback)
 
-        RenderInfo.clearCache()
+        if (!retainCache) RenderInfo.clearCache()
     }
 
     override fun onRefreshState(componentName: ComponentName, controls: List<Control>) {
         controls.forEach { c ->
             controlsById.get(ControlKey(componentName, c.getControlId()))?.let {
                 Log.d(ControlsUiController.TAG, "onRefreshState() for id: " + c.getControlId())
+                iconCache.store(componentName, c.controlId, c.customIcon)
                 val cws = ControlWithState(componentName, it.ci, c)
                 val key = ControlKey(componentName, c.getControlId())
                 controlsById.put(key, cws)
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/DefaultBroadcastReceiverBinder.java b/packages/SystemUI/src/com/android/systemui/dagger/DefaultBroadcastReceiverBinder.java
index 56d0fa2..6e8d63b 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/DefaultBroadcastReceiverBinder.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/DefaultBroadcastReceiverBinder.java
@@ -18,7 +18,9 @@
 
 import android.content.BroadcastReceiver;
 
-import com.android.systemui.screenshot.GlobalScreenshot.ActionProxyReceiver;
+import com.android.systemui.screenshot.ActionProxyReceiver;
+import com.android.systemui.screenshot.DeleteScreenshotReceiver;
+import com.android.systemui.screenshot.SmartActionsReceiver;
 
 import dagger.Binds;
 import dagger.Module;
@@ -30,10 +32,31 @@
  */
 @Module
 public abstract class DefaultBroadcastReceiverBinder {
-    /** */
+    /**
+     *
+     */
     @Binds
     @IntoMap
     @ClassKey(ActionProxyReceiver.class)
     public abstract BroadcastReceiver bindActionProxyReceiver(
             ActionProxyReceiver broadcastReceiver);
+
+    /**
+     *
+     */
+    @Binds
+    @IntoMap
+    @ClassKey(DeleteScreenshotReceiver.class)
+    public abstract BroadcastReceiver bindDeleteScreenshotReceiver(
+            DeleteScreenshotReceiver broadcastReceiver);
+
+    /**
+     *
+     */
+    @Binds
+    @IntoMap
+    @ClassKey(SmartActionsReceiver.class)
+    public abstract BroadcastReceiver bindSmartActionsReceiver(
+            SmartActionsReceiver broadcastReceiver);
+
 }
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemServicesModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemServicesModule.java
index f683a63..8472b1b 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemServicesModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemServicesModule.java
@@ -42,6 +42,7 @@
 import android.hardware.display.DisplayManager;
 import android.media.AudioManager;
 import android.media.MediaRouter2Manager;
+import android.media.session.MediaSessionManager;
 import android.net.ConnectivityManager;
 import android.net.NetworkScoreManager;
 import android.net.wifi.WifiManager;
@@ -219,6 +220,11 @@
     }
 
     @Provides
+    static MediaSessionManager provideMediaSessionManager(Context context) {
+        return context.getSystemService(MediaSessionManager.class);
+    }
+
+    @Provides
     @Singleton
     static NetworkScoreManager provideNetworkScoreManager(Context context) {
         return context.getSystemService(NetworkScoreManager.class);
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeMachine.java b/packages/SystemUI/src/com/android/systemui/doze/DozeMachine.java
index ae7d82a..253a35c 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeMachine.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeMachine.java
@@ -430,8 +430,12 @@
         /** Give the Part a chance to clean itself up. */
         default void destroy() {}
 
-        /** Alerts that the screenstate is being changed. */
-        default void onScreenState(int state) {}
+        /**
+         *  Alerts that the screenstate is being changed.
+         *  Note: This may be called from within a call to transitionTo, so local DozeState may not
+         *  be accurate nor match with the new displayState.
+         */
+        default void onScreenState(int displayState) {}
     }
 
     /** A wrapper interface for {@link android.service.dreams.DreamService} */
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeScreenBrightness.java b/packages/SystemUI/src/com/android/systemui/doze/DozeScreenBrightness.java
index 64cfb4b..a11997b 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeScreenBrightness.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeScreenBrightness.java
@@ -69,7 +69,6 @@
      * --ei brightness_bucket 1}
      */
     private int mDebugBrightnessBucket = -1;
-    private DozeMachine.State mState;
 
     @VisibleForTesting
     public DozeScreenBrightness(Context context, DozeMachine.Service service,
@@ -109,7 +108,6 @@
 
     @Override
     public void transitionTo(DozeMachine.State oldState, DozeMachine.State newState) {
-        mState = newState;
         switch (newState) {
             case INITIALIZED:
             case DOZE:
@@ -127,10 +125,7 @@
 
     @Override
     public void onScreenState(int state) {
-        if (!mScreenOff
-                && (mState == DozeMachine.State.DOZE_AOD
-                     || mState == DozeMachine.State.DOZE_AOD_DOCKED)
-                && (state == Display.STATE_DOZE || state == Display.STATE_DOZE_SUSPEND)) {
+        if (state == Display.STATE_DOZE || state == Display.STATE_DOZE_SUSPEND) {
             setLightSensorEnabled(true);
         } else {
             setLightSensorEnabled(false);
diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java
index 2702bc4..ff25439a 100644
--- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java
@@ -149,6 +149,7 @@
 import java.util.concurrent.Executor;
 
 import javax.inject.Inject;
+import javax.inject.Provider;
 
 /**
  * Helper to show the global actions dialog.  Each item is an {@link Action} that may show depending
@@ -402,7 +403,7 @@
                         if (mDialog != null) {
                             if (!mDialog.isShowingControls() && shouldShowControls()) {
                                 mDialog.showControls(mControlsUiControllerOptional.get());
-                            } else if (shouldShowLockMessage()) {
+                            } else if (shouldShowLockMessage(mDialog)) {
                                 mDialog.showLockMessage();
                             }
                         }
@@ -699,19 +700,17 @@
         mPowerAdapter = new MyPowerOptionsAdapter();
 
         mDepthController.setShowingHomeControls(true);
-        GlobalActionsPanelPlugin.PanelViewController walletViewController =
-                getWalletViewController();
         ControlsUiController uiController = null;
         if (mControlsUiControllerOptional.isPresent() && shouldShowControls()) {
             uiController = mControlsUiControllerOptional.get();
         }
         ActionsDialog dialog = new ActionsDialog(mContext, mAdapter, mOverflowAdapter,
-                walletViewController, mDepthController, mSysuiColorExtractor,
+                this::getWalletViewController, mDepthController, mSysuiColorExtractor,
                 mStatusBarService, mNotificationShadeWindowController,
                 controlsAvailable(), uiController,
                 mSysUiState, this::onRotate, mKeyguardShowing, mPowerAdapter);
 
-        if (shouldShowLockMessage()) {
+        if (shouldShowLockMessage(dialog)) {
             dialog.showLockMessage();
         }
         dialog.setCanceledOnTouchOutside(false); // Handled by the custom class.
@@ -2144,7 +2143,8 @@
         private MultiListLayout mGlobalActionsLayout;
         private Drawable mBackgroundDrawable;
         private final SysuiColorExtractor mColorExtractor;
-        private final GlobalActionsPanelPlugin.PanelViewController mWalletViewController;
+        private final Provider<GlobalActionsPanelPlugin.PanelViewController> mWalletFactory;
+        @Nullable private GlobalActionsPanelPlugin.PanelViewController mWalletViewController;
         private boolean mKeyguardShowing;
         private boolean mShowing;
         private float mScrimAlpha;
@@ -2164,7 +2164,7 @@
         private TextView mLockMessage;
 
         ActionsDialog(Context context, MyAdapter adapter, MyOverflowAdapter overflowAdapter,
-                GlobalActionsPanelPlugin.PanelViewController walletViewController,
+                Provider<GlobalActionsPanelPlugin.PanelViewController> walletFactory,
                 NotificationShadeDepthController depthController,
                 SysuiColorExtractor sysuiColorExtractor, IStatusBarService statusBarService,
                 NotificationShadeWindowController notificationShadeWindowController,
@@ -2185,6 +2185,7 @@
             mSysUiState = sysuiState;
             mOnRotateCallback = onRotateCallback;
             mKeyguardShowing = keyguardShowing;
+            mWalletFactory = walletFactory;
 
             // Window initialization
             Window window = getWindow();
@@ -2207,7 +2208,6 @@
             window.getAttributes().setFitInsetsTypes(0 /* types */);
             setTitle(R.string.global_actions);
 
-            mWalletViewController = walletViewController;
             initializeLayout();
         }
 
@@ -2220,8 +2220,13 @@
             mControlsUiController.show(mControlsView, this::dismissForControlsActivity);
         }
 
+        private boolean isWalletViewAvailable() {
+            return mWalletViewController != null && mWalletViewController.getPanelContent() != null;
+        }
+
         private void initializeWalletView() {
-            if (mWalletViewController == null || mWalletViewController.getPanelContent() == null) {
+            mWalletViewController = mWalletFactory.get();
+            if (!isWalletViewAvailable()) {
                 return;
             }
 
@@ -2527,6 +2532,8 @@
         private void dismissWallet() {
             if (mWalletViewController != null) {
                 mWalletViewController.onDismissed();
+                // The wallet controller should not be re-used after being dismissed.
+                mWalletViewController = null;
             }
         }
 
@@ -2668,18 +2675,12 @@
                 && !mControlsServiceInfos.isEmpty();
     }
 
-    private boolean walletViewAvailable() {
-        GlobalActionsPanelPlugin.PanelViewController walletViewController =
-                getWalletViewController();
-        return walletViewController != null && walletViewController.getPanelContent() != null;
-    }
-
-    private boolean shouldShowLockMessage() {
+    private boolean shouldShowLockMessage(ActionsDialog dialog) {
         boolean isLockedAfterBoot = mLockPatternUtils.getStrongAuthForUser(getCurrentUser().id)
                 == STRONG_AUTH_REQUIRED_AFTER_BOOT;
         return !mKeyguardStateController.isUnlocked()
                 && (!mShowLockScreenCardsAndControls || isLockedAfterBoot)
-                && (controlsAvailable() || walletViewAvailable());
+                && (controlsAvailable() || dialog.isWalletViewAvailable());
     }
 
     private void onPowerMenuLockScreenSettingsChanged() {
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaBrowserFactory.java b/packages/SystemUI/src/com/android/systemui/media/MediaBrowserFactory.java
new file mode 100644
index 0000000..aca033e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaBrowserFactory.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.media;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.media.browse.MediaBrowser;
+import android.os.Bundle;
+
+import javax.inject.Inject;
+
+/**
+ * Testable wrapper around {@link MediaBrowser} constructor
+ */
+public class MediaBrowserFactory {
+    private final Context mContext;
+
+    @Inject
+    public MediaBrowserFactory(Context context) {
+        mContext = context;
+    }
+
+    /**
+     * Creates a new MediaBrowser
+     *
+     * @param serviceComponent
+     * @param callback
+     * @param rootHints
+     * @return
+     */
+    public MediaBrowser create(ComponentName serviceComponent,
+            MediaBrowser.ConnectionCallback callback, Bundle rootHints) {
+        return new MediaBrowser(mContext, serviceComponent, callback, rootHints);
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt b/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt
index e12b7dd..d8d9bd7 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt
@@ -11,6 +11,7 @@
 import android.view.View
 import android.view.ViewGroup
 import android.widget.LinearLayout
+import androidx.annotation.VisibleForTesting
 import com.android.systemui.R
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.plugins.ActivityStarter
@@ -22,6 +23,7 @@
 import com.android.systemui.util.animation.UniqueObjectHostView
 import com.android.systemui.util.animation.requiresRemeasuring
 import com.android.systemui.util.concurrency.DelayableExecutor
+import java.util.TreeMap
 import javax.inject.Inject
 import javax.inject.Provider
 import javax.inject.Singleton
@@ -41,7 +43,7 @@
     private val mediaHostStatesManager: MediaHostStatesManager,
     private val activityStarter: ActivityStarter,
     @Main executor: DelayableExecutor,
-    mediaManager: MediaDataFilter,
+    private val mediaManager: MediaDataManager,
     configurationController: ConfigurationController,
     falsingManager: FalsingManager
 ) {
@@ -103,13 +105,12 @@
     private val mediaCarousel: MediaScrollView
     private val mediaCarouselScrollHandler: MediaCarouselScrollHandler
     val mediaFrame: ViewGroup
-    val mediaPlayers: MutableMap<String, MediaControlPanel> = mutableMapOf()
     private lateinit var settingsButton: View
-    private val mediaData: MutableMap<String, MediaData> = mutableMapOf()
     private val mediaContent: ViewGroup
     private val pageIndicator: PageIndicator
     private val visualStabilityCallback: VisualStabilityManager.Callback
     private var needsReordering: Boolean = false
+    private var keysNeedRemoval = mutableSetOf<String>()
     private var isRtl: Boolean = false
         set(value) {
             if (value != field) {
@@ -123,7 +124,7 @@
         set(value) {
             if (field != value) {
                 field = value
-                for (player in mediaPlayers.values) {
+                for (player in MediaPlayerData.players()) {
                     player.setListening(field)
                 }
             }
@@ -135,6 +136,7 @@
         }
 
         override fun onOverlayChanged() {
+            recreatePlayers()
             inflateSettingsButton()
         }
 
@@ -150,7 +152,7 @@
         pageIndicator = mediaFrame.requireViewById(R.id.media_page_indicator)
         mediaCarouselScrollHandler = MediaCarouselScrollHandler(mediaCarousel, pageIndicator,
                 executor, mediaManager::onSwipeToDismiss, this::updatePageIndicatorLocation,
-                falsingManager)
+                this::closeGuts, falsingManager)
         isRtl = context.resources.configuration.layoutDirection == View.LAYOUT_DIRECTION_RTL
         inflateSettingsButton()
         mediaContent = mediaCarousel.requireViewById(R.id.media_carousel)
@@ -160,6 +162,10 @@
                 needsReordering = false
                 reorderAllPlayers()
             }
+
+            keysNeedRemoval.forEach { removePlayer(it) }
+            keysNeedRemoval.clear()
+
             // Let's reset our scroll position
             mediaCarouselScrollHandler.scrollToStart()
         }
@@ -167,20 +173,23 @@
                 true /* persistent */)
         mediaManager.addListener(object : MediaDataManager.Listener {
             override fun onMediaDataLoaded(key: String, oldKey: String?, data: MediaData) {
-                oldKey?.let { mediaData.remove(it) }
-                if (!data.active && !Utils.useMediaResumption(context)) {
-                    // This view is inactive, let's remove this! This happens e.g when dismissing /
-                    // timing out a view. We still have the data around because resumption could
-                    // be on, but we should save the resources and release this.
-                    onMediaDataRemoved(key)
+                addOrUpdatePlayer(key, oldKey, data)
+                val canRemove = data.isPlaying?.let { !it } ?: data.isClearable && !data.active
+                if (canRemove && !Utils.useMediaResumption(context)) {
+                    // This view isn't playing, let's remove this! This happens e.g when
+                    // dismissing/timing out a view. We still have the data around because
+                    // resumption could be on, but we should save the resources and release this.
+                    if (visualStabilityManager.isReorderingAllowed) {
+                        onMediaDataRemoved(key)
+                    } else {
+                        keysNeedRemoval.add(key)
+                    }
                 } else {
-                    mediaData.put(key, data)
-                    addOrUpdatePlayer(key, oldKey, data)
+                    keysNeedRemoval.remove(key)
                 }
             }
 
             override fun onMediaDataRemoved(key: String) {
-                mediaData.remove(key)
                 removePlayer(key)
             }
         })
@@ -223,53 +232,36 @@
     }
 
     private fun reorderAllPlayers() {
-        for (mediaPlayer in mediaPlayers.values) {
-            val view = mediaPlayer.view?.player
-            if (mediaPlayer.isPlaying && mediaContent.indexOfChild(view) != 0) {
-                mediaContent.removeView(view)
-                mediaContent.addView(view, 0)
+        mediaContent.removeAllViews()
+        for (mediaPlayer in MediaPlayerData.players()) {
+            mediaPlayer.view?.let {
+                mediaContent.addView(it.player)
             }
         }
         mediaCarouselScrollHandler.onPlayersChanged()
     }
 
     private fun addOrUpdatePlayer(key: String, oldKey: String?, data: MediaData) {
-        // If the key was changed, update entry
-        val oldData = mediaPlayers[oldKey]
-        if (oldData != null) {
-            val oldData = mediaPlayers.remove(oldKey)
-            mediaPlayers.put(key, oldData!!)?.let {
-                Log.wtf(TAG, "new key $key already exists when migrating from $oldKey")
-            }
-        }
-        var existingPlayer = mediaPlayers[key]
+        val existingPlayer = MediaPlayerData.getMediaPlayer(key, oldKey)
         if (existingPlayer == null) {
-            existingPlayer = mediaControlPanelFactory.get()
-            existingPlayer.attach(PlayerViewHolder.create(LayoutInflater.from(context),
-                    mediaContent))
-            existingPlayer.mediaViewController.sizeChangedListener = this::updateCarouselDimensions
-            mediaPlayers[key] = existingPlayer
+            var newPlayer = mediaControlPanelFactory.get()
+            newPlayer.attach(PlayerViewHolder.create(LayoutInflater.from(context), mediaContent))
+            newPlayer.mediaViewController.sizeChangedListener = this::updateCarouselDimensions
             val lp = LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
                     ViewGroup.LayoutParams.WRAP_CONTENT)
-            existingPlayer.view?.player?.setLayoutParams(lp)
-            existingPlayer.bind(data)
-            existingPlayer.setListening(currentlyExpanded)
-            updatePlayerToState(existingPlayer, noAnimation = true)
-            if (existingPlayer.isPlaying) {
-                mediaContent.addView(existingPlayer.view?.player, 0)
-            } else {
-                mediaContent.addView(existingPlayer.view?.player)
-            }
+            newPlayer.view?.player?.setLayoutParams(lp)
+            newPlayer.bind(data, key)
+            newPlayer.setListening(currentlyExpanded)
+            MediaPlayerData.addMediaPlayer(key, data, newPlayer)
+            updatePlayerToState(newPlayer, noAnimation = true)
+            reorderAllPlayers()
         } else {
-            existingPlayer.bind(data)
-            if (existingPlayer.isPlaying &&
-                    mediaContent.indexOfChild(existingPlayer.view?.player) != 0) {
-                if (visualStabilityManager.isReorderingAllowed) {
-                    mediaContent.removeView(existingPlayer.view?.player)
-                    mediaContent.addView(existingPlayer.view?.player, 0)
-                } else {
-                    needsReordering = true
-                }
+            existingPlayer.bind(data, key)
+            MediaPlayerData.addMediaPlayer(key, data, existingPlayer)
+            if (visualStabilityManager.isReorderingAllowed) {
+                reorderAllPlayers()
+            } else {
+                needsReordering = true
             }
         }
         updatePageIndicator()
@@ -277,29 +269,30 @@
         mediaCarousel.requiresRemeasuring = true
         // Check postcondition: mediaContent should have the same number of children as there are
         // elements in mediaPlayers.
-        if (mediaPlayers.size != mediaContent.childCount) {
+        if (MediaPlayerData.players().size != mediaContent.childCount) {
             Log.wtf(TAG, "Size of players list and number of views in carousel are out of sync")
         }
     }
 
-    private fun removePlayer(key: String) {
-        val removed = mediaPlayers.remove(key)
+    private fun removePlayer(key: String, dismissMediaData: Boolean = true) {
+        val removed = MediaPlayerData.removeMediaPlayer(key)
         removed?.apply {
             mediaCarouselScrollHandler.onPrePlayerRemoved(removed)
             mediaContent.removeView(removed.view?.player)
             removed.onDestroy()
             mediaCarouselScrollHandler.onPlayersChanged()
             updatePageIndicator()
+
+            if (dismissMediaData) {
+                // Inform the media manager of a potentially late dismissal
+                mediaManager.dismissMediaData(key, 0L)
+            }
         }
     }
 
     private fun recreatePlayers() {
-        // Note that this will scramble the order of players. Actively playing sessions will, at
-        // least, still be put in the front. If we want to maintain order, then more work is
-        // needed.
-        mediaData.forEach {
-            key, data ->
-            removePlayer(key)
+        MediaPlayerData.mediaData().forEach { (key, data) ->
+            removePlayer(key, dismissMediaData = false)
             addOrUpdatePlayer(key = key, oldKey = null, data = data)
         }
     }
@@ -337,7 +330,7 @@
             currentStartLocation = startLocation
             currentEndLocation = endLocation
             currentTransitionProgress = progress
-            for (mediaPlayer in mediaPlayers.values) {
+            for (mediaPlayer in MediaPlayerData.players()) {
                 updatePlayerToState(mediaPlayer, immediately)
             }
             maybeResetSettingsCog()
@@ -386,7 +379,7 @@
     private fun updateCarouselDimensions() {
         var width = 0
         var height = 0
-        for (mediaPlayer in mediaPlayers.values) {
+        for (mediaPlayer in MediaPlayerData.players()) {
             val controller = mediaPlayer.mediaViewController
             // When transitioning the view to gone, the view gets smaller, but the translation
             // Doesn't, let's add the translation
@@ -448,7 +441,7 @@
             this.desiredLocation = desiredLocation
             this.desiredHostState = it
             currentlyExpanded = it.expansion > 0
-            for (mediaPlayer in mediaPlayers.values) {
+            for (mediaPlayer in MediaPlayerData.players()) {
                 if (animate) {
                     mediaPlayer.mediaViewController.animatePendingStateChange(
                             duration = duration,
@@ -469,6 +462,12 @@
         }
     }
 
+    fun closeGuts() {
+        MediaPlayerData.players().forEach {
+            it.closeGuts(true)
+        }
+    }
+
     /**
      * Update the size of the carousel, remeasuring it if necessary.
      */
@@ -491,3 +490,49 @@
         }
     }
 }
+
+@VisibleForTesting
+internal object MediaPlayerData {
+    private data class MediaSortKey(
+        val data: MediaData,
+        val updateTime: Long = 0
+    )
+
+    private val comparator =
+        compareByDescending<MediaSortKey> { it.data.isPlaying }
+        .thenByDescending { it.data.isLocalSession }
+        .thenByDescending { !it.data.resumption }
+        .thenByDescending { it.updateTime }
+
+    private val mediaPlayers = TreeMap<MediaSortKey, MediaControlPanel>(comparator)
+    private val mediaData: MutableMap<String, MediaSortKey> = mutableMapOf()
+
+    fun addMediaPlayer(key: String, data: MediaData, player: MediaControlPanel) {
+        removeMediaPlayer(key)
+        val sortKey = MediaSortKey(data, System.currentTimeMillis())
+        mediaData.put(key, sortKey)
+        mediaPlayers.put(sortKey, player)
+    }
+
+    fun getMediaPlayer(key: String, oldKey: String?): MediaControlPanel? {
+        // If the key was changed, update entry
+        oldKey?.let {
+            if (it != key) {
+                mediaData.remove(it)?.let { sortKey -> mediaData.put(key, sortKey) }
+            }
+        }
+        return mediaData.get(key)?.let { mediaPlayers.get(it) }
+    }
+
+    fun removeMediaPlayer(key: String) = mediaData.remove(key)?.let { mediaPlayers.remove(it) }
+
+    fun mediaData() = mediaData.entries.map { e -> Pair(e.key, e.value.data) }
+
+    fun players() = mediaPlayers.values
+
+    @VisibleForTesting
+    fun clear() {
+        mediaData.clear()
+        mediaPlayers.clear()
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaCarouselScrollHandler.kt b/packages/SystemUI/src/com/android/systemui/media/MediaCarouselScrollHandler.kt
index 3096908..77cac50 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaCarouselScrollHandler.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaCarouselScrollHandler.kt
@@ -56,6 +56,7 @@
     private val mainExecutor: DelayableExecutor,
     private val dismissCallback: () -> Unit,
     private var translationChangedListener: () -> Unit,
+    private val closeGuts: () -> Unit,
     private val falsingManager: FalsingManager
 ) {
     /**
@@ -452,6 +453,7 @@
         val nowScrolledIn = scrollIntoCurrentMedia != 0
         if (newIndex != activeMediaIndex || wasScrolledIn != nowScrolledIn) {
             activeMediaIndex = newIndex
+            closeGuts()
             updatePlayerVisibilities()
         }
         val relativeLocation = activeMediaIndex.toFloat() + if (playerWidthPlusPadding > 0)
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java b/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java
index 3fc162e..d853e3d 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java
@@ -16,6 +16,8 @@
 
 package com.android.systemui.media;
 
+import static android.provider.Settings.ACTION_MEDIA_CONTROLS_SETTINGS;
+
 import android.app.PendingIntent;
 import android.content.Context;
 import android.content.Intent;
@@ -45,6 +47,7 @@
 import com.android.systemui.R;
 import com.android.systemui.dagger.qualifiers.Background;
 import com.android.systemui.plugins.ActivityStarter;
+import com.android.systemui.statusbar.phone.KeyguardDismissUtil;
 import com.android.systemui.util.animation.TransitionLayout;
 
 import java.util.List;
@@ -52,6 +55,8 @@
 
 import javax.inject.Inject;
 
+import dagger.Lazy;
+
 /**
  * A view controller used for Media Playback.
  */
@@ -59,6 +64,8 @@
     private static final String TAG = "MediaControlPanel";
     private static final float DISABLED_ALPHA = 0.38f;
 
+    private static final Intent SETTINGS_INTENT = new Intent(ACTION_MEDIA_CONTROLS_SETTINGS);
+
     // Button IDs for QS controls
     static final int[] ACTION_IDS = {
             R.id.action0,
@@ -75,9 +82,12 @@
 
     private Context mContext;
     private PlayerViewHolder mViewHolder;
+    private String mKey;
     private MediaViewController mMediaViewController;
     private MediaSession.Token mToken;
     private MediaController mController;
+    private KeyguardDismissUtil mKeyguardDismissUtil;
+    private Lazy<MediaDataManager> mMediaDataManagerLazy;
     private int mBackgroundColor;
     private int mAlbumArtSize;
     private int mAlbumArtRadius;
@@ -93,12 +103,15 @@
     @Inject
     public MediaControlPanel(Context context, @Background Executor backgroundExecutor,
             ActivityStarter activityStarter, MediaViewController mediaViewController,
-            SeekBarViewModel seekBarViewModel) {
+            SeekBarViewModel seekBarViewModel, Lazy<MediaDataManager> lazyMediaDataManager,
+            KeyguardDismissUtil keyguardDismissUtil) {
         mContext = context;
         mBackgroundExecutor = backgroundExecutor;
         mActivityStarter = activityStarter;
         mSeekBarViewModel = seekBarViewModel;
         mMediaViewController = mediaViewController;
+        mMediaDataManagerLazy = lazyMediaDataManager;
+        mKeyguardDismissUtil = keyguardDismissUtil;
         loadDimens();
 
         mViewOutlineProvider = new ViewOutlineProvider() {
@@ -174,15 +187,31 @@
         mSeekBarViewModel.getProgress().observeForever(mSeekBarObserver);
         mSeekBarViewModel.attachTouchHandlers(vh.getSeekBar());
         mMediaViewController.attach(player);
+
+        mViewHolder.getPlayer().setOnLongClickListener(v -> {
+            if (!mMediaViewController.isGutsVisible()) {
+                mMediaViewController.openGuts();
+                return true;
+            } else {
+                return false;
+            }
+        });
+        mViewHolder.getCancel().setOnClickListener(v -> {
+            closeGuts();
+        });
+        mViewHolder.getSettings().setOnClickListener(v -> {
+            mActivityStarter.startActivity(SETTINGS_INTENT, true /* dismissShade */);
+        });
     }
 
     /**
      * Bind this view based on the data given
      */
-    public void bind(@NonNull MediaData data) {
+    public void bind(@NonNull MediaData data, String key) {
         if (mViewHolder == null) {
             return;
         }
+        mKey = key;
         MediaSession.Token token = data.getToken();
         mBackgroundColor = data.getBackgroundColor();
         if (mToken == null || !mToken.equals(token)) {
@@ -205,6 +234,7 @@
         PendingIntent clickIntent = data.getClickIntent();
         if (clickIntent != null) {
             mViewHolder.getPlayer().setOnClickListener(v -> {
+                if (mMediaViewController.isGutsVisible()) return;
                 mActivityStarter.postStartActivityDismissingKeyguard(clickIntent);
             });
         }
@@ -329,14 +359,46 @@
         final MediaController controller = getController();
         mBackgroundExecutor.execute(() -> mSeekBarViewModel.updateController(controller));
 
-        // Set up long press menu
-        // TODO: b/156036025 bring back media guts
+        // Guts label
+        boolean isDismissible = data.isClearable();
+        mViewHolder.getSettingsText().setText(isDismissible
+                ? R.string.controls_media_close_session
+                : R.string.controls_media_active_session);
+
+        // Dismiss
+        mViewHolder.getDismissLabel().setAlpha(isDismissible ? 1 : DISABLED_ALPHA);
+        mViewHolder.getDismiss().setEnabled(isDismissible);
+        mViewHolder.getDismiss().setOnClickListener(v -> {
+            if (mKey != null) {
+                closeGuts();
+                mKeyguardDismissUtil.executeWhenUnlocked(() -> {
+                    mMediaDataManagerLazy.get().dismissMediaData(mKey,
+                            MediaViewController.GUTS_ANIMATION_DURATION + 100);
+                    return true;
+                }, /* requiresShadeOpen */ true);
+            } else {
+                Log.w(TAG, "Dismiss media with null notification. Token uid="
+                        + data.getToken().getUid());
+            }
+        });
 
         // TODO: We don't need to refresh this state constantly, only if the state actually changed
         // to something which might impact the measurement
         mMediaViewController.refreshState();
     }
 
+    /**
+     * Close the guts for this player.
+     * @param immediate {@code true} if it should be closed without animation
+     */
+    public void closeGuts(boolean immediate) {
+        mMediaViewController.closeGuts(immediate);
+    }
+
+    private void closeGuts() {
+        closeGuts(false);
+    }
+
     @UiThread
     private Drawable scaleDrawable(Icon icon) {
         if (icon == null) {
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaData.kt b/packages/SystemUI/src/com/android/systemui/media/MediaData.kt
index dafc52a..40a879a 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaData.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaData.kt
@@ -82,6 +82,10 @@
      */
     var resumeAction: Runnable?,
     /**
+     * Local or remote playback
+     */
+    var isLocalSession: Boolean = true,
+    /**
      * Indicates that this player is a resumption player (ie. It only shows a play actions which
      * will start the app and start playing).
      */
@@ -90,7 +94,17 @@
      * Notification key for cancelling a media player after a timeout (when not using resumption.)
      */
     val notificationKey: String? = null,
-    var hasCheckedForResume: Boolean = false
+    var hasCheckedForResume: Boolean = false,
+
+    /**
+     * If apps do not report PlaybackState, set as null to imply 'undetermined'
+     */
+    val isPlaying: Boolean? = null,
+
+    /**
+     * Set from the notification and used as fallback when PlaybackState cannot be determined
+     */
+    val isClearable: Boolean = true
 )
 
 /** State of a media action. */
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaDataCombineLatest.kt b/packages/SystemUI/src/com/android/systemui/media/MediaDataCombineLatest.kt
index e8f0e06..aa3699e 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaDataCombineLatest.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaDataCombineLatest.kt
@@ -17,57 +17,48 @@
 package com.android.systemui.media
 
 import javax.inject.Inject
-import javax.inject.Singleton
 
 /**
- * Combines updates from [MediaDataManager] with [MediaDeviceManager].
+ * Combines [MediaDataManager.Listener] events with [MediaDeviceManager.Listener] events.
  */
-@Singleton
-class MediaDataCombineLatest @Inject constructor(
-    private val dataSource: MediaDataManager,
-    private val deviceSource: MediaDeviceManager
-) {
+class MediaDataCombineLatest @Inject constructor() : MediaDataManager.Listener,
+        MediaDeviceManager.Listener {
+
     private val listeners: MutableSet<MediaDataManager.Listener> = mutableSetOf()
     private val entries: MutableMap<String, Pair<MediaData?, MediaDeviceData?>> = mutableMapOf()
 
-    init {
-        dataSource.addListener(object : MediaDataManager.Listener {
-            override fun onMediaDataLoaded(key: String, oldKey: String?, data: MediaData) {
-                if (oldKey != null && !oldKey.equals(key)) {
-                    val s = entries[oldKey]?.second
-                    entries[key] = data to entries[oldKey]?.second
-                    entries.remove(oldKey)
-                } else {
-                    entries[key] = data to entries[key]?.second
-                }
-                update(key, oldKey)
-            }
-            override fun onMediaDataRemoved(key: String) {
-                remove(key)
-            }
-        })
-        deviceSource.addListener(object : MediaDeviceManager.Listener {
-            override fun onMediaDeviceChanged(key: String, data: MediaDeviceData?) {
-                entries[key] = entries[key]?.first to data
-                update(key, key)
-            }
-            override fun onKeyRemoved(key: String) {
-                remove(key)
-            }
-        })
+    override fun onMediaDataLoaded(key: String, oldKey: String?, data: MediaData) {
+        if (oldKey != null && oldKey != key && entries.contains(oldKey)) {
+            entries[key] = data to entries.remove(oldKey)?.second
+            update(key, oldKey)
+        } else {
+            entries[key] = data to entries[key]?.second
+            update(key, key)
+        }
     }
 
-    /**
-     * Get a map of all non-null data entries
-     */
-    fun getData(): Map<String, MediaData> {
-        return entries.filter {
-            (key, pair) -> pair.first != null && pair.second != null
-        }.mapValues {
-            (key, pair) -> pair.first!!.copy(device = pair.second)
+    override fun onMediaDataRemoved(key: String) {
+        remove(key)
+    }
+
+    override fun onMediaDeviceChanged(
+        key: String,
+        oldKey: String?,
+        data: MediaDeviceData?
+    ) {
+        if (oldKey != null && oldKey != key && entries.contains(oldKey)) {
+            entries[key] = entries.remove(oldKey)?.first to data
+            update(key, oldKey)
+        } else {
+            entries[key] = entries[key]?.first to data
+            update(key, key)
         }
     }
 
+    override fun onKeyRemoved(key: String) {
+        remove(key)
+    }
+
     /**
      * Add a listener for [MediaData] changes that has been combined with latest [MediaDeviceData].
      */
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaDataFilter.kt b/packages/SystemUI/src/com/android/systemui/media/MediaDataFilter.kt
index 662831e..1f580a9 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaDataFilter.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaDataFilter.kt
@@ -24,32 +24,32 @@
 import com.android.systemui.statusbar.NotificationLockscreenUserManager
 import java.util.concurrent.Executor
 import javax.inject.Inject
-import javax.inject.Singleton
 
 private const val TAG = "MediaDataFilter"
+private const val DEBUG = true
 
 /**
  * Filters data updates from [MediaDataCombineLatest] based on the current user ID, and handles user
  * switches (removing entries for the previous user, adding back entries for the current user)
  *
- * This is added downstream of [MediaDataManager] since we may still need to handle callbacks from
- * background users (e.g. timeouts) that UI classes should ignore.
- * Instead, UI classes should listen to this so they can stay in sync with the current user.
+ * This is added at the end of the pipeline since we may still need to handle callbacks from
+ * background users (e.g. timeouts).
  */
-@Singleton
 class MediaDataFilter @Inject constructor(
-    private val dataSource: MediaDataCombineLatest,
     private val broadcastDispatcher: BroadcastDispatcher,
     private val mediaResumeListener: MediaResumeListener,
-    private val mediaDataManager: MediaDataManager,
     private val lockscreenUserManager: NotificationLockscreenUserManager,
     @Main private val executor: Executor
 ) : MediaDataManager.Listener {
     private val userTracker: CurrentUserTracker
-    private val listeners: MutableSet<MediaDataManager.Listener> = mutableSetOf()
+    private val _listeners: MutableSet<MediaDataManager.Listener> = mutableSetOf()
+    internal val listeners: Set<MediaDataManager.Listener>
+        get() = _listeners.toSet()
+    internal lateinit var mediaDataManager: MediaDataManager
 
-    // The filtered mediaEntries, which will be a subset of all mediaEntries in MediaDataManager
-    private val mediaEntries: LinkedHashMap<String, MediaData> = LinkedHashMap()
+    private val allEntries: LinkedHashMap<String, MediaData> = LinkedHashMap()
+    // The filtered userEntries, which will be a subset of all userEntries in MediaDataManager
+    private val userEntries: LinkedHashMap<String, MediaData> = LinkedHashMap()
 
     init {
         userTracker = object : CurrentUserTracker(broadcastDispatcher) {
@@ -59,31 +59,34 @@
             }
         }
         userTracker.startTracking()
-        dataSource.addListener(this)
     }
 
     override fun onMediaDataLoaded(key: String, oldKey: String?, data: MediaData) {
+        if (oldKey != null && oldKey != key) {
+            allEntries.remove(oldKey)
+        }
+        allEntries.put(key, data)
+
         if (!lockscreenUserManager.isCurrentProfile(data.userId)) {
             return
         }
 
-        if (oldKey != null) {
-            mediaEntries.remove(oldKey)
+        if (oldKey != null && oldKey != key) {
+            userEntries.remove(oldKey)
         }
-        mediaEntries.put(key, data)
+        userEntries.put(key, data)
 
         // Notify listeners
-        val listenersCopy = listeners.toSet()
-        listenersCopy.forEach {
+        listeners.forEach {
             it.onMediaDataLoaded(key, oldKey, data)
         }
     }
 
     override fun onMediaDataRemoved(key: String) {
-        mediaEntries.remove(key)?.let {
+        allEntries.remove(key)
+        userEntries.remove(key)?.let {
             // Only notify listeners if something actually changed
-            val listenersCopy = listeners.toSet()
-            listenersCopy.forEach {
+            listeners.forEach {
                 it.onMediaDataRemoved(key)
             }
         }
@@ -92,22 +95,22 @@
     @VisibleForTesting
     internal fun handleUserSwitched(id: Int) {
         // If the user changes, remove all current MediaData objects and inform listeners
-        val listenersCopy = listeners.toSet()
-        val keyCopy = mediaEntries.keys.toMutableList()
+        val listenersCopy = listeners
+        val keyCopy = userEntries.keys.toMutableList()
         // Clear the list first, to make sure callbacks from listeners if we have any entries
         // are up to date
-        mediaEntries.clear()
+        userEntries.clear()
         keyCopy.forEach {
-            Log.d(TAG, "Removing $it after user change")
+            if (DEBUG) Log.d(TAG, "Removing $it after user change")
             listenersCopy.forEach { listener ->
                 listener.onMediaDataRemoved(it)
             }
         }
 
-        dataSource.getData().forEach { (key, data) ->
+        allEntries.forEach { (key, data) ->
             if (lockscreenUserManager.isCurrentProfile(data.userId)) {
-                Log.d(TAG, "Re-adding $key after user change")
-                mediaEntries.put(key, data)
+                if (DEBUG) Log.d(TAG, "Re-adding $key after user change")
+                userEntries.put(key, data)
                 listenersCopy.forEach { listener ->
                     listener.onMediaDataLoaded(key, null, data)
                 }
@@ -119,7 +122,8 @@
      * Invoked when the user has dismissed the media carousel
      */
     fun onSwipeToDismiss() {
-        val mediaKeys = mediaEntries.keys.toSet()
+        if (DEBUG) Log.d(TAG, "Media carousel swiped away")
+        val mediaKeys = userEntries.keys.toSet()
         mediaKeys.forEach {
             mediaDataManager.setTimedOut(it, timedOut = true)
         }
@@ -128,26 +132,20 @@
     /**
      * Are there any media notifications active?
      */
-    fun hasActiveMedia() = mediaEntries.any { it.value.active }
+    fun hasActiveMedia() = userEntries.any { it.value.active }
 
     /**
      * Are there any media entries we should display?
-     * If resumption is enabled, this will include inactive players
-     * If resumption is disabled, we only want to show active players
      */
-    fun hasAnyMedia() = if (mediaResumeListener.isResumptionEnabled()) {
-        mediaEntries.isNotEmpty()
-    } else {
-        hasActiveMedia()
-    }
+    fun hasAnyMedia() = userEntries.isNotEmpty()
 
     /**
      * Add a listener for filtered [MediaData] changes
      */
-    fun addListener(listener: MediaDataManager.Listener) = listeners.add(listener)
+    fun addListener(listener: MediaDataManager.Listener) = _listeners.add(listener)
 
     /**
      * Remove a listener that was registered with addListener
      */
-    fun removeListener(listener: MediaDataManager.Listener) = listeners.remove(listener)
-}
\ No newline at end of file
+    fun removeListener(listener: MediaDataManager.Listener) = _listeners.remove(listener)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt b/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt
index 299ae5b..bb11efe 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt
@@ -31,6 +31,7 @@
 import android.graphics.drawable.Icon
 import android.media.MediaDescription
 import android.media.MediaMetadata
+import android.media.session.MediaController
 import android.media.session.MediaSession
 import android.net.Uri
 import android.os.UserHandle
@@ -44,10 +45,12 @@
 import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.dump.DumpManager
+import com.android.systemui.statusbar.NotificationMediaManager.isPlayingState
 import com.android.systemui.statusbar.notification.MediaNotificationProcessor
 import com.android.systemui.statusbar.notification.row.HybridGroupManager
 import com.android.systemui.util.Assert
 import com.android.systemui.util.Utils
+import com.android.systemui.util.concurrency.DelayableExecutor
 import java.io.FileDescriptor
 import java.io.IOException
 import java.io.PrintWriter
@@ -63,6 +66,7 @@
 )
 
 private const val TAG = "MediaDataManager"
+private const val DEBUG = true
 private const val DEFAULT_LUMINOSITY = 0.25f
 private const val LUMINOSITY_THRESHOLD = 0.05f
 private const val SATURATION_MULTIPLIER = 0.8f
@@ -89,31 +93,47 @@
 class MediaDataManager(
     private val context: Context,
     @Background private val backgroundExecutor: Executor,
-    @Main private val foregroundExecutor: Executor,
+    @Main private val foregroundExecutor: DelayableExecutor,
     private val mediaControllerFactory: MediaControllerFactory,
     private val broadcastDispatcher: BroadcastDispatcher,
     dumpManager: DumpManager,
     mediaTimeoutListener: MediaTimeoutListener,
     mediaResumeListener: MediaResumeListener,
+    mediaSessionBasedFilter: MediaSessionBasedFilter,
+    mediaDeviceManager: MediaDeviceManager,
+    mediaDataCombineLatest: MediaDataCombineLatest,
+    private val mediaDataFilter: MediaDataFilter,
     private var useMediaResumption: Boolean,
     private val useQsMediaPlayer: Boolean
 ) : Dumpable {
 
-    private val listeners: MutableSet<Listener> = mutableSetOf()
+    // Internal listeners are part of the internal pipeline. External listeners (those registered
+    // with [MediaDeviceManager.addListener]) receive events after they have propagated through
+    // the internal pipeline.
+    // Another way to think of the distinction between internal and external listeners is the
+    // following. Internal listeners are listeners that MediaDataManager depends on, and external
+    // listeners are listeners that depend on MediaDataManager.
+    // TODO(b/159539991#comment5): Move internal listeners to separate package.
+    private val internalListeners: MutableSet<Listener> = mutableSetOf()
     private val mediaEntries: LinkedHashMap<String, MediaData> = LinkedHashMap()
 
     @Inject
     constructor(
         context: Context,
         @Background backgroundExecutor: Executor,
-        @Main foregroundExecutor: Executor,
+        @Main foregroundExecutor: DelayableExecutor,
         mediaControllerFactory: MediaControllerFactory,
         dumpManager: DumpManager,
         broadcastDispatcher: BroadcastDispatcher,
         mediaTimeoutListener: MediaTimeoutListener,
-        mediaResumeListener: MediaResumeListener
+        mediaResumeListener: MediaResumeListener,
+        mediaSessionBasedFilter: MediaSessionBasedFilter,
+        mediaDeviceManager: MediaDeviceManager,
+        mediaDataCombineLatest: MediaDataCombineLatest,
+        mediaDataFilter: MediaDataFilter
     ) : this(context, backgroundExecutor, foregroundExecutor, mediaControllerFactory,
             broadcastDispatcher, dumpManager, mediaTimeoutListener, mediaResumeListener,
+            mediaSessionBasedFilter, mediaDeviceManager, mediaDataCombineLatest, mediaDataFilter,
             Utils.useMediaResumption(context), Utils.useQsMediaPlayer(context))
 
     private val appChangeReceiver = object : BroadcastReceiver() {
@@ -136,12 +156,26 @@
 
     init {
         dumpManager.registerDumpable(TAG, this)
+
+        // Initialize the internal processing pipeline. The listeners at the front of the pipeline
+        // are set as internal listeners so that they receive events. From there, events are
+        // propagated through the pipeline. The end of the pipeline is currently mediaDataFilter,
+        // so it is responsible for dispatching events to external listeners. To achieve this,
+        // external listeners that are registered with [MediaDataManager.addListener] are actually
+        // registered as listeners to mediaDataFilter.
+        addInternalListener(mediaTimeoutListener)
+        addInternalListener(mediaResumeListener)
+        addInternalListener(mediaSessionBasedFilter)
+        mediaSessionBasedFilter.addListener(mediaDeviceManager)
+        mediaSessionBasedFilter.addListener(mediaDataCombineLatest)
+        mediaDeviceManager.addListener(mediaDataCombineLatest)
+        mediaDataCombineLatest.addListener(mediaDataFilter)
+
+        // Set up links back into the pipeline for listeners that need to send events upstream.
         mediaTimeoutListener.timeoutCallback = { token: String, timedOut: Boolean ->
             setTimedOut(token, timedOut) }
-        addListener(mediaTimeoutListener)
-
         mediaResumeListener.setManager(this)
-        addListener(mediaResumeListener)
+        mediaDataFilter.mediaDataManager = this
 
         val suspendFilter = IntentFilter(Intent.ACTION_PACKAGES_SUSPENDED)
         broadcastDispatcher.registerReceiver(appChangeReceiver, suspendFilter, null, UserHandle.ALL)
@@ -179,13 +213,9 @@
 
     private fun removeAllForPackage(packageName: String) {
         Assert.isMainThread()
-        val listenersCopy = listeners.toSet()
         val toRemove = mediaEntries.filter { it.value.packageName == packageName }
         toRemove.forEach {
-            mediaEntries.remove(it.key)
-            listenersCopy.forEach { listener ->
-                listener.onMediaDataRemoved(it.key)
-            }
+            removeEntry(it.key)
         }
     }
 
@@ -245,15 +275,48 @@
     /**
      * Add a listener for changes in this class
      */
-    fun addListener(listener: Listener) = listeners.add(listener)
+    fun addListener(listener: Listener) {
+        // mediaDataFilter is the current end of the internal pipeline. Register external
+        // listeners as listeners to it.
+        mediaDataFilter.addListener(listener)
+    }
 
     /**
      * Remove a listener for changes in this class
      */
-    fun removeListener(listener: Listener) = listeners.remove(listener)
+    fun removeListener(listener: Listener) {
+        // Since mediaDataFilter is the current end of the internal pipelie, external listeners
+        // have been registered to it. So, they need to be removed from it too.
+        mediaDataFilter.removeListener(listener)
+    }
 
     /**
-     * Called whenever the player has been paused or stopped for a while.
+     * Add a listener for internal events.
+     */
+    private fun addInternalListener(listener: Listener) = internalListeners.add(listener)
+
+    /**
+     * Notify internal listeners of loaded event.
+     *
+     * External listeners registered with [addListener] will be notified after the event propagates
+     * through the internal listener pipeline.
+     */
+    private fun notifyMediaDataLoaded(key: String, oldKey: String?, info: MediaData) {
+        internalListeners.forEach { it.onMediaDataLoaded(key, oldKey, info) }
+    }
+
+    /**
+     * Notify internal listeners of removed event.
+     *
+     * External listeners registered with [addListener] will be notified after the event propagates
+     * through the internal listener pipeline.
+     */
+    private fun notifyMediaDataRemoved(key: String) {
+        internalListeners.forEach { it.onMediaDataRemoved(key) }
+    }
+
+    /**
+     * Called whenever the player has been paused or stopped for a while, or swiped from QQS.
      * This will make the player not active anymore, hiding it from QQS and Keyguard.
      * @see MediaData.active
      */
@@ -263,10 +326,30 @@
                 return
             }
             it.active = !timedOut
+            if (DEBUG) Log.d(TAG, "Updating $token timedOut: $timedOut")
             onMediaDataLoaded(token, token, it)
         }
     }
 
+    private fun removeEntry(key: String) {
+        mediaEntries.remove(key)
+        notifyMediaDataRemoved(key)
+    }
+
+    fun dismissMediaData(key: String, delay: Long) {
+        backgroundExecutor.execute {
+            mediaEntries[key]?.let { mediaData ->
+                if (mediaData.isLocalSession) {
+                    mediaData.token?.let {
+                        val mediaController = mediaControllerFactory.create(it)
+                        mediaController.transportControls.stop()
+                    }
+                }
+            }
+        }
+        foregroundExecutor.executeDelayed({ removeEntry(key) }, delay)
+    }
+
     private fun loadMediaDataInBgForResumption(
         userId: Int,
         desc: MediaDescription,
@@ -283,7 +366,9 @@
             return
         }
 
-        Log.d(TAG, "adding track for $userId from browser: $desc")
+        if (DEBUG) {
+            Log.d(TAG, "adding track for $userId from browser: $desc")
+        }
 
         // Album art
         var artworkBitmap = desc.iconBitmap
@@ -314,20 +399,16 @@
     ) {
         val token = sbn.notification.extras.getParcelable(Notification.EXTRA_MEDIA_SESSION)
                 as MediaSession.Token?
-        val metadata = mediaControllerFactory.create(token).metadata
-
-        if (metadata == null) {
-            // TODO: handle this better, removing media notification
-            return
-        }
+        val mediaController = mediaControllerFactory.create(token)
+        val metadata = mediaController.metadata
 
         // Foreground and Background colors computed from album art
         val notif: Notification = sbn.notification
-        var artworkBitmap = metadata.getBitmap(MediaMetadata.METADATA_KEY_ART)
+        var artworkBitmap = metadata?.getBitmap(MediaMetadata.METADATA_KEY_ART)
         if (artworkBitmap == null) {
-            artworkBitmap = metadata.getBitmap(MediaMetadata.METADATA_KEY_ALBUM_ART)
+            artworkBitmap = metadata?.getBitmap(MediaMetadata.METADATA_KEY_ALBUM_ART)
         }
-        if (artworkBitmap == null) {
+        if (artworkBitmap == null && metadata != null) {
             artworkBitmap = loadBitmapFromUri(metadata)
         }
         val artWorkIcon = if (artworkBitmap == null) {
@@ -363,16 +444,16 @@
         val smallIconDrawable: Drawable = sbn.notification.smallIcon.loadDrawable(context)
 
         // Song name
-        var song: CharSequence? = metadata.getString(MediaMetadata.METADATA_KEY_DISPLAY_TITLE)
+        var song: CharSequence? = metadata?.getString(MediaMetadata.METADATA_KEY_DISPLAY_TITLE)
         if (song == null) {
-            song = metadata.getString(MediaMetadata.METADATA_KEY_TITLE)
+            song = metadata?.getString(MediaMetadata.METADATA_KEY_TITLE)
         }
         if (song == null) {
             song = HybridGroupManager.resolveTitle(notif)
         }
 
         // Artist name
-        var artist: CharSequence? = metadata.getString(MediaMetadata.METADATA_KEY_ARTIST)
+        var artist: CharSequence? = metadata?.getString(MediaMetadata.METADATA_KEY_ARTIST)
         if (artist == null) {
             artist = HybridGroupManager.resolveText(notif)
         }
@@ -388,7 +469,7 @@
         if (actions != null) {
             for ((index, action) in actions.withIndex()) {
                 if (action.getIcon() == null) {
-                    Log.i(TAG, "No icon for action $index ${action.title}")
+                    if (DEBUG) Log.i(TAG, "No icon for action $index ${action.title}")
                     actionsToShowCollapsed.remove(index)
                     continue
                 }
@@ -411,6 +492,10 @@
             }
         }
 
+        val isLocalSession = mediaController.playbackInfo?.playbackType ==
+            MediaController.PlaybackInfo.PLAYBACK_TYPE_LOCAL ?: true
+        val isPlaying = mediaController.playbackState?.let { isPlayingState(it.state) } ?: null
+
         foregroundExecutor.execute {
             val resumeAction: Runnable? = mediaEntries[key]?.resumeAction
             val hasCheckedForResume = mediaEntries[key]?.hasCheckedForResume == true
@@ -418,8 +503,9 @@
             onMediaDataLoaded(key, oldKey, MediaData(sbn.normalizedUserId, true, bgColor, app,
                     smallIconDrawable, artist, song, artWorkIcon, actionIcons,
                     actionsToShowCollapsed, sbn.packageName, token, notif.contentIntent, null,
-                    active, resumeAction = resumeAction, notificationKey = key,
-                    hasCheckedForResume = hasCheckedForResume))
+                    active, resumeAction = resumeAction, isLocalSession = isLocalSession,
+                    notificationKey = key, hasCheckedForResume = hasCheckedForResume,
+                    isPlaying = isPlaying, isClearable = sbn.isClearable()))
         }
     }
 
@@ -432,7 +518,7 @@
             if (!TextUtils.isEmpty(uriString)) {
                 val albumArt = loadBitmapFromUri(Uri.parse(uriString))
                 if (albumArt != null) {
-                    Log.d(TAG, "loaded art from $uri")
+                    if (DEBUG) Log.d(TAG, "loaded art from $uri")
                     return albumArt
                 }
             }
@@ -463,7 +549,10 @@
                 decoder, info, source -> decoder.isMutableRequired = true
             }
         } catch (e: IOException) {
-            e.printStackTrace()
+            Log.e(TAG, "Unable to load bitmap", e)
+            null
+        } catch (e: RuntimeException) {
+            Log.e(TAG, "Unable to load bitmap", e)
             null
         }
     }
@@ -508,10 +597,7 @@
         if (mediaEntries.containsKey(key)) {
             // Otherwise this was removed already
             mediaEntries.put(key, data)
-            val listenersCopy = listeners.toSet()
-            listenersCopy.forEach {
-                it.onMediaDataLoaded(key, oldKey, data)
-            }
+            notifyMediaDataLoaded(key, oldKey, data)
         }
     }
 
@@ -519,7 +605,7 @@
         Assert.isMainThread()
         val removed = mediaEntries.remove(key)
         if (useMediaResumption && removed?.resumeAction != null) {
-            Log.d(TAG, "Not removing $key because resumable")
+            if (DEBUG) Log.d(TAG, "Not removing $key because resumable")
             // Move to resume key (aka package name) if that key doesn't already exist.
             val resumeAction = getResumeMediaAction(removed.resumeAction!!)
             val updated = removed.copy(token = null, actions = listOf(resumeAction),
@@ -527,31 +613,21 @@
             val pkg = removed?.packageName
             val migrate = mediaEntries.put(pkg, updated) == null
             // Notify listeners of "new" controls when migrating or removed and update when not
-            val listenersCopy = listeners.toSet()
             if (migrate) {
-                listenersCopy.forEach {
-                    it.onMediaDataLoaded(pkg, key, updated)
-                }
+                notifyMediaDataLoaded(pkg, key, updated)
             } else {
                 // Since packageName is used for the key of the resumption controls, it is
                 // possible that another notification has already been reused for the resumption
                 // controls of this package. In this case, rather than renaming this player as
                 // packageName, just remove it and then send a update to the existing resumption
                 // controls.
-                listenersCopy.forEach {
-                    it.onMediaDataRemoved(key)
-                }
-                listenersCopy.forEach {
-                    it.onMediaDataLoaded(pkg, pkg, updated)
-                }
+                notifyMediaDataRemoved(key)
+                notifyMediaDataLoaded(pkg, pkg, updated)
             }
             return
         }
         if (removed != null) {
-            val listenersCopy = listeners.toSet()
-            listenersCopy.forEach {
-                it.onMediaDataRemoved(key)
-            }
+            notifyMediaDataRemoved(key)
         }
     }
 
@@ -564,17 +640,31 @@
 
         if (!useMediaResumption) {
             // Remove any existing resume controls
-            val listenersCopy = listeners.toSet()
             val filtered = mediaEntries.filter { !it.value.active }
             filtered.forEach {
                 mediaEntries.remove(it.key)
-                listenersCopy.forEach { listener ->
-                    listener.onMediaDataRemoved(it.key)
-                }
+                notifyMediaDataRemoved(it.key)
             }
         }
     }
 
+    /**
+     * Invoked when the user has dismissed the media carousel
+     */
+    fun onSwipeToDismiss() = mediaDataFilter.onSwipeToDismiss()
+
+    /**
+     * Are there any media notifications active?
+     */
+    fun hasActiveMedia() = mediaDataFilter.hasActiveMedia()
+
+    /**
+     * Are there any media entries we should display?
+     * If resumption is enabled, this will include inactive players
+     * If resumption is disabled, we only want to show active players
+     */
+    fun hasAnyMedia() = mediaDataFilter.hasAnyMedia()
+
     interface Listener {
 
         /**
@@ -594,7 +684,8 @@
 
     override fun dump(fd: FileDescriptor, pw: PrintWriter, args: Array<out String>) {
         pw.apply {
-            println("listeners: $listeners")
+            println("internalListeners: $internalListeners")
+            println("externalListeners: ${mediaDataFilter.listeners}")
             println("mediaEntries: $mediaEntries")
             println("useMediaResumption: $useMediaResumption")
         }
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaDeviceManager.kt b/packages/SystemUI/src/com/android/systemui/media/MediaDeviceManager.kt
index 7ae2dc5..a993d00 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaDeviceManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaDeviceManager.kt
@@ -16,37 +16,40 @@
 
 package com.android.systemui.media
 
-import android.content.Context
 import android.media.MediaRouter2Manager
 import android.media.session.MediaController
+import androidx.annotation.AnyThread
+import androidx.annotation.MainThread
+import androidx.annotation.WorkerThread
 import com.android.settingslib.media.LocalMediaManager
 import com.android.settingslib.media.MediaDevice
-import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.Dumpable
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.dump.DumpManager
 import java.io.FileDescriptor
 import java.io.PrintWriter
 import java.util.concurrent.Executor
 import javax.inject.Inject
-import javax.inject.Singleton
+
+private const val PLAYBACK_TYPE_UNKNOWN = 0
 
 /**
  * Provides information about the route (ie. device) where playback is occurring.
  */
-@Singleton
 class MediaDeviceManager @Inject constructor(
-    private val context: Context,
+    private val controllerFactory: MediaControllerFactory,
     private val localMediaManagerFactory: LocalMediaManagerFactory,
     private val mr2manager: MediaRouter2Manager,
     @Main private val fgExecutor: Executor,
-    private val mediaDataManager: MediaDataManager,
-    private val dumpManager: DumpManager
+    @Background private val bgExecutor: Executor,
+    dumpManager: DumpManager
 ) : MediaDataManager.Listener, Dumpable {
+
     private val listeners: MutableSet<Listener> = mutableSetOf()
-    private val entries: MutableMap<String, Token> = mutableMapOf()
+    private val entries: MutableMap<String, Entry> = mutableMapOf()
 
     init {
-        mediaDataManager.addListener(this)
         dumpManager.registerDumpable(javaClass.name, this)
     }
 
@@ -69,9 +72,10 @@
         if (entry == null || entry?.token != data.token) {
             entry?.stop()
             val controller = data.token?.let {
-                MediaController(context, it)
+                controllerFactory.create(it)
             }
-            entry = Token(key, controller, localMediaManagerFactory.create(data.packageName))
+            entry = Entry(key, oldKey, controller,
+                    localMediaManagerFactory.create(data.packageName))
             entries[key] = entry
             entry.start()
         }
@@ -98,66 +102,98 @@
         }
     }
 
-    private fun processDevice(key: String, device: MediaDevice?) {
+    @MainThread
+    private fun processDevice(key: String, oldKey: String?, device: MediaDevice?) {
         val enabled = device != null
         val data = MediaDeviceData(enabled, device?.iconWithoutBackground, device?.name)
         listeners.forEach {
-            it.onMediaDeviceChanged(key, data)
+            it.onMediaDeviceChanged(key, oldKey, data)
         }
     }
 
     interface Listener {
         /** Called when the route has changed for a given notification. */
-        fun onMediaDeviceChanged(key: String, data: MediaDeviceData?)
+        fun onMediaDeviceChanged(key: String, oldKey: String?, data: MediaDeviceData?)
         /** Called when the notification was removed. */
         fun onKeyRemoved(key: String)
     }
 
-    private inner class Token(
+    private inner class Entry(
         val key: String,
+        val oldKey: String?,
         val controller: MediaController?,
         val localMediaManager: LocalMediaManager
-    ) : LocalMediaManager.DeviceCallback {
+    ) : LocalMediaManager.DeviceCallback, MediaController.Callback() {
+
         val token
             get() = controller?.sessionToken
         private var started = false
+        private var playbackType = PLAYBACK_TYPE_UNKNOWN
         private var current: MediaDevice? = null
             set(value) {
                 if (!started || value != field) {
                     field = value
-                    processDevice(key, value)
+                    fgExecutor.execute {
+                        processDevice(key, oldKey, value)
+                    }
                 }
             }
-        fun start() {
+
+        @AnyThread
+        fun start() = bgExecutor.execute {
             localMediaManager.registerCallback(this)
             localMediaManager.startScan()
+            playbackType = controller?.playbackInfo?.playbackType ?: PLAYBACK_TYPE_UNKNOWN
+            controller?.registerCallback(this)
             updateCurrent()
             started = true
         }
-        fun stop() {
+
+        @AnyThread
+        fun stop() = bgExecutor.execute {
             started = false
+            controller?.unregisterCallback(this)
             localMediaManager.stopScan()
             localMediaManager.unregisterCallback(this)
         }
+
         fun dump(fd: FileDescriptor, pw: PrintWriter, args: Array<String>) {
-            val route = controller?.let {
+            val routingSession = controller?.let {
                 mr2manager.getRoutingSessionForMediaController(it)
             }
+            val selectedRoutes = routingSession?.let {
+                mr2manager.getSelectedRoutes(it)
+            }
             with(pw) {
                 println("    current device is ${current?.name}")
                 val type = controller?.playbackInfo?.playbackType
-                println("    PlaybackType=$type (1 for local, 2 for remote)")
-                println("    route=$route")
+                println("    PlaybackType=$type (1 for local, 2 for remote) cached=$playbackType")
+                println("    routingSession=$routingSession")
+                println("    selectedRoutes=$selectedRoutes")
             }
         }
-        override fun onDeviceListUpdate(devices: List<MediaDevice>?) = fgExecutor.execute {
+
+        @WorkerThread
+        override fun onAudioInfoChanged(info: MediaController.PlaybackInfo?) {
+            val newPlaybackType = info?.playbackType ?: PLAYBACK_TYPE_UNKNOWN
+            if (newPlaybackType == playbackType) {
+                return
+            }
+            playbackType = newPlaybackType
             updateCurrent()
         }
+
+        override fun onDeviceListUpdate(devices: List<MediaDevice>?) = bgExecutor.execute {
+            updateCurrent()
+        }
+
         override fun onSelectedDeviceStateChanged(device: MediaDevice, state: Int) {
-            fgExecutor.execute {
+            bgExecutor.execute {
                 updateCurrent()
             }
         }
+
+        @WorkerThread
         private fun updateCurrent() {
             val device = localMediaManager.getCurrentConnectedDevice()
             controller?.let {
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaHierarchyManager.kt b/packages/SystemUI/src/com/android/systemui/media/MediaHierarchyManager.kt
index fc33391..b31390c 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaHierarchyManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaHierarchyManager.kt
@@ -293,6 +293,13 @@
         return viewHost
     }
 
+    /**
+     * Close the guts in all players in [MediaCarouselController].
+     */
+    fun closeGuts() {
+        mediaCarouselController.closeGuts()
+    }
+
     private fun createUniqueObjectHost(): UniqueObjectHostView {
         val viewHost = UniqueObjectHostView(context)
         viewHost.addOnAttachStateChangeListener(object : View.OnAttachStateChangeListener {
@@ -382,6 +389,14 @@
         if (isCurrentlyInGuidedTransformation()) {
             return false
         }
+        // This is an invalid transition, and can happen when using the camera gesture from the
+        // lock screen. Disallow.
+        if (previousLocation == LOCATION_LOCKSCREEN &&
+            desiredLocation == LOCATION_QQS &&
+            statusbarState == StatusBarState.SHADE) {
+            return false
+        }
+
         if (currentLocation == LOCATION_QQS &&
                 previousLocation == LOCATION_LOCKSCREEN &&
                 (statusBarStateController.leaveOpenOnKeyguardHide() ||
@@ -597,8 +612,8 @@
             // When collapsing on the lockscreen, we want to remain in QS
             return LOCATION_QS
         }
-        if (location != LOCATION_LOCKSCREEN && desiredLocation == LOCATION_LOCKSCREEN
-                && !fullyAwake) {
+        if (location != LOCATION_LOCKSCREEN && desiredLocation == LOCATION_LOCKSCREEN &&
+                !fullyAwake) {
             // When unlocking from dozing / while waking up, the media shouldn't be transitioning
             // in an animated way. Let's keep it in the lockscreen until we're fully awake and
             // reattach it without an animation
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaHost.kt b/packages/SystemUI/src/com/android/systemui/media/MediaHost.kt
index 3598719..ce184aa 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaHost.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaHost.kt
@@ -14,7 +14,7 @@
 class MediaHost @Inject constructor(
     private val state: MediaHostStateHolder,
     private val mediaHierarchyManager: MediaHierarchyManager,
-    private val mediaDataFilter: MediaDataFilter,
+    private val mediaDataManager: MediaDataManager,
     private val mediaHostStatesManager: MediaHostStatesManager
 ) : MediaHostState by state {
     lateinit var hostView: UniqueObjectHostView
@@ -79,12 +79,12 @@
                 // be a delay until the views and the controllers are initialized, leaving us
                 // with either a blank view or the controllers not yet initialized and the
                 // measuring wrong
-                mediaDataFilter.addListener(listener)
+                mediaDataManager.addListener(listener)
                 updateViewVisibility()
             }
 
             override fun onViewDetachedFromWindow(v: View?) {
-                mediaDataFilter.removeListener(listener)
+                mediaDataManager.removeListener(listener)
             }
         })
 
@@ -113,9 +113,9 @@
 
     private fun updateViewVisibility() {
         visible = if (showsOnlyActiveMedia) {
-            mediaDataFilter.hasActiveMedia()
+            mediaDataManager.hasActiveMedia()
         } else {
-            mediaDataFilter.hasAnyMedia()
+            mediaDataManager.hasAnyMedia()
         }
         val newVisibility = if (visible) View.VISIBLE else View.GONE
         if (newVisibility != hostView.visibility) {
@@ -289,4 +289,4 @@
      * Get a copy of this view state, deepcopying all appropriate members
      */
     fun copy(): MediaHostState
-}
\ No newline at end of file
+}
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaResumeListener.kt b/packages/SystemUI/src/com/android/systemui/media/MediaResumeListener.kt
index 4ec746f..936db87 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaResumeListener.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaResumeListener.kt
@@ -28,6 +28,7 @@
 import android.provider.Settings
 import android.service.media.MediaBrowserService
 import android.util.Log
+import com.android.internal.annotations.VisibleForTesting
 import com.android.systemui.broadcast.BroadcastDispatcher
 import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.tuner.TunerService
@@ -47,7 +48,8 @@
     private val context: Context,
     private val broadcastDispatcher: BroadcastDispatcher,
     @Background private val backgroundExecutor: Executor,
-    private val tunerService: TunerService
+    private val tunerService: TunerService,
+    private val mediaBrowserFactory: ResumeMediaBrowserFactory
 ) : MediaDataManager.Listener {
 
     private var useMediaResumption: Boolean = Utils.useMediaResumption(context)
@@ -58,7 +60,8 @@
     private var mediaBrowser: ResumeMediaBrowser? = null
     private var currentUserId: Int = context.userId
 
-    private val userChangeReceiver = object : BroadcastReceiver() {
+    @VisibleForTesting
+    val userChangeReceiver = object : BroadcastReceiver() {
         override fun onReceive(context: Context, intent: Intent) {
             if (Intent.ACTION_USER_UNLOCKED == intent.action) {
                 loadMediaResumptionControls()
@@ -116,8 +119,6 @@
         }, Settings.Secure.MEDIA_CONTROLS_RESUME)
     }
 
-    fun isResumptionEnabled() = useMediaResumption
-
     private fun loadSavedComponents() {
         // Make sure list is empty (if we switched users)
         resumeComponents.clear()
@@ -144,7 +145,7 @@
         }
 
         resumeComponents.forEach {
-            val browser = ResumeMediaBrowser(context, mediaBrowserCallback, it)
+            val browser = mediaBrowserFactory.create(mediaBrowserCallback, it)
             browser.findRecentMedia()
         }
     }
@@ -183,14 +184,10 @@
     private fun tryUpdateResumptionList(key: String, componentName: ComponentName) {
         Log.d(TAG, "Testing if we can connect to $componentName")
         mediaBrowser?.disconnect()
-        mediaBrowser = ResumeMediaBrowser(context,
+        mediaBrowser = mediaBrowserFactory.create(
                 object : ResumeMediaBrowser.Callback() {
                     override fun onConnected() {
-                        Log.d(TAG, "yes we can resume with $componentName")
-                        mediaDataManager.setResumeAction(key, getResumeAction(componentName))
-                        updateResumptionList(componentName)
-                        mediaBrowser?.disconnect()
-                        mediaBrowser = null
+                        Log.d(TAG, "Connected to $componentName")
                     }
 
                     override fun onError() {
@@ -199,6 +196,19 @@
                         mediaBrowser?.disconnect()
                         mediaBrowser = null
                     }
+
+                    override fun addTrack(
+                        desc: MediaDescription,
+                        component: ComponentName,
+                        browser: ResumeMediaBrowser
+                    ) {
+                        // Since this is a test, just save the component for later
+                        Log.d(TAG, "Can get resumable media from $componentName")
+                        mediaDataManager.setResumeAction(key, getResumeAction(componentName))
+                        updateResumptionList(componentName)
+                        mediaBrowser?.disconnect()
+                        mediaBrowser = null
+                    }
                 },
                 componentName)
         mediaBrowser?.testConnection()
@@ -235,7 +245,7 @@
     private fun getResumeAction(componentName: ComponentName): Runnable {
         return Runnable {
             mediaBrowser?.disconnect()
-            mediaBrowser = ResumeMediaBrowser(context,
+            mediaBrowser = mediaBrowserFactory.create(
                 object : ResumeMediaBrowser.Callback() {
                     override fun onConnected() {
                         if (mediaBrowser?.token == null) {
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaSessionBasedFilter.kt b/packages/SystemUI/src/com/android/systemui/media/MediaSessionBasedFilter.kt
new file mode 100644
index 0000000..f695622
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaSessionBasedFilter.kt
@@ -0,0 +1,171 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.media
+
+import android.content.ComponentName
+import android.content.Context
+import android.media.session.MediaController
+import android.media.session.MediaController.PlaybackInfo
+import android.media.session.MediaSession
+import android.media.session.MediaSessionManager
+import android.util.Log
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.statusbar.phone.NotificationListenerWithPlugins
+import java.util.concurrent.Executor
+import javax.inject.Inject
+
+private const val TAG = "MediaSessionBasedFilter"
+
+/**
+ * Filters media loaded events for local media sessions while an app is casting.
+ *
+ * When an app is casting there can be one remote media sessions and potentially more local media
+ * sessions. In this situation, there should only be a media object for the remote session. To
+ * achieve this, update events for the local session need to be filtered.
+ */
+class MediaSessionBasedFilter @Inject constructor(
+    context: Context,
+    private val sessionManager: MediaSessionManager,
+    @Main private val foregroundExecutor: Executor,
+    @Background private val backgroundExecutor: Executor
+) : MediaDataManager.Listener {
+
+    private val listeners: MutableSet<MediaDataManager.Listener> = mutableSetOf()
+
+    // Keep track of MediaControllers for a given package to check if an app is casting and it
+    // filter loaded events for local sessions.
+    private val packageControllers: LinkedHashMap<String, MutableList<MediaController>> =
+            LinkedHashMap()
+
+    // Keep track of the key used for the session tokens. This information is used to know when to
+    // dispatch a removed event so that a media object for a local session will be removed.
+    private val keyedTokens: MutableMap<String, MutableSet<MediaSession.Token>> = mutableMapOf()
+
+    // Keep track of which media session tokens have associated notifications.
+    private val tokensWithNotifications: MutableSet<MediaSession.Token> = mutableSetOf()
+
+    private val sessionListener = object : MediaSessionManager.OnActiveSessionsChangedListener {
+        override fun onActiveSessionsChanged(controllers: List<MediaController>) {
+            handleControllersChanged(controllers)
+        }
+    }
+
+    init {
+        backgroundExecutor.execute {
+            val name = ComponentName(context, NotificationListenerWithPlugins::class.java)
+            sessionManager.addOnActiveSessionsChangedListener(sessionListener, name)
+            handleControllersChanged(sessionManager.getActiveSessions(name))
+        }
+    }
+
+    /**
+     * Add a listener for filtered [MediaData] changes
+     */
+    fun addListener(listener: MediaDataManager.Listener) = listeners.add(listener)
+
+    /**
+     * Remove a listener that was registered with addListener
+     */
+    fun removeListener(listener: MediaDataManager.Listener) = listeners.remove(listener)
+
+    /**
+     * May filter loaded events by not passing them along to listeners.
+     *
+     * If an app has only one session with playback type PLAYBACK_TYPE_REMOTE, then assuming that
+     * the app is casting. Sometimes apps will send redundant updates to a local session with
+     * playback type PLAYBACK_TYPE_LOCAL. These updates should be filtered to improve the usability
+     * of the media controls.
+     */
+    override fun onMediaDataLoaded(key: String, oldKey: String?, info: MediaData) {
+        backgroundExecutor.execute {
+            info.token?.let {
+                tokensWithNotifications.add(it)
+            }
+            val isMigration = oldKey != null && key != oldKey
+            if (isMigration) {
+                keyedTokens.remove(oldKey)?.let { removed -> keyedTokens.put(key, removed) }
+            }
+            if (info.token != null) {
+                keyedTokens.get(key)?.let {
+                    tokens ->
+                    tokens.add(info.token)
+                } ?: run {
+                    val tokens = mutableSetOf(info.token)
+                    keyedTokens.put(key, tokens)
+                }
+            }
+            // Determine if an app is casting by checking if it has a session with playback type
+            // PLAYBACK_TYPE_REMOTE.
+            val remoteControllers = packageControllers.get(info.packageName)?.filter {
+                it.playbackInfo?.playbackType == PlaybackInfo.PLAYBACK_TYPE_REMOTE
+            }
+            // Limiting search to only apps with a single remote session.
+            val remote = if (remoteControllers?.size == 1) remoteControllers.firstOrNull() else null
+            if (isMigration || remote == null || remote.sessionToken == info.token ||
+                    !tokensWithNotifications.contains(remote.sessionToken)) {
+                // Not filtering in this case. Passing the event along to listeners.
+                dispatchMediaDataLoaded(key, oldKey, info)
+            } else {
+                // Filtering this event because the app is casting and the loaded events is for a
+                // local session.
+                Log.d(TAG, "filtering key=$key local=${info.token} remote=${remote?.sessionToken}")
+                // If the local session uses a different notification key, then lets go a step
+                // farther and dismiss the media data so that media controls for the local session
+                // don't hang around while casting.
+                if (!keyedTokens.get(key)!!.contains(remote.sessionToken)) {
+                    dispatchMediaDataRemoved(key)
+                }
+            }
+        }
+    }
+
+    override fun onMediaDataRemoved(key: String) {
+        // Queue on background thread to ensure ordering of loaded and removed events is maintained.
+        backgroundExecutor.execute {
+            keyedTokens.remove(key)
+            dispatchMediaDataRemoved(key)
+        }
+    }
+
+    private fun dispatchMediaDataLoaded(key: String, oldKey: String?, info: MediaData) {
+        foregroundExecutor.execute {
+            listeners.toSet().forEach { it.onMediaDataLoaded(key, oldKey, info) }
+        }
+    }
+
+    private fun dispatchMediaDataRemoved(key: String) {
+        foregroundExecutor.execute {
+            listeners.toSet().forEach { it.onMediaDataRemoved(key) }
+        }
+    }
+
+    private fun handleControllersChanged(controllers: List<MediaController>) {
+        packageControllers.clear()
+        controllers.forEach {
+            controller ->
+            packageControllers.get(controller.packageName)?.let {
+                tokens ->
+                tokens.add(controller)
+            } ?: run {
+                val tokens = mutableListOf(controller)
+                packageControllers.put(controller.packageName, tokens)
+            }
+        }
+        tokensWithNotifications.retainAll(controllers.map { it.sessionToken })
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaTimeoutListener.kt b/packages/SystemUI/src/com/android/systemui/media/MediaTimeoutListener.kt
index 8662aac..63a361d 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaTimeoutListener.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaTimeoutListener.kt
@@ -46,7 +46,7 @@
     /**
      * Callback representing that a media object is now expired:
      * @param token Media session unique identifier
-     * @param pauseTimeuot True when expired for {@code PAUSED_MEDIA_TIMEOUT}
+     * @param pauseTimeout True when expired for {@code PAUSED_MEDIA_TIMEOUT}
      */
     lateinit var timeoutCallback: (String, Boolean) -> Unit
 
@@ -54,32 +54,34 @@
         if (mediaListeners.containsKey(key)) {
             return
         }
-        // Having an old key means that we're migrating from/to resumption. We should invalidate
-        // the old listener and create a new one.
+        // Having an old key means that we're migrating from/to resumption. We should update
+        // the old listener to make sure that events will be dispatched to the new location.
         val migrating = oldKey != null && key != oldKey
-        var wasPlaying = false
         if (migrating) {
-            if (mediaListeners.containsKey(oldKey)) {
-                val oldListener = mediaListeners.remove(oldKey)
-                wasPlaying = oldListener?.playing ?: false
-                oldListener?.destroy()
+            val reusedListener = mediaListeners.remove(oldKey)
+            if (reusedListener != null) {
+                val wasPlaying = reusedListener.playing ?: false
                 if (DEBUG) Log.d(TAG, "migrating key $oldKey to $key, for resumption")
+                reusedListener.mediaData = data
+                reusedListener.key = key
+                mediaListeners[key] = reusedListener
+                if (wasPlaying != reusedListener.playing) {
+                    // If a player becomes active because of a migration, we'll need to broadcast
+                    // its state. Doing it now would lead to reentrant callbacks, so let's wait
+                    // until we're done.
+                    mainExecutor.execute {
+                        if (mediaListeners[key]?.playing == true) {
+                            if (DEBUG) Log.d(TAG, "deliver delayed playback state for $key")
+                            timeoutCallback.invoke(key, false /* timedOut */)
+                        }
+                    }
+                }
+                return
             } else {
                 Log.w(TAG, "Old key $oldKey for player $key doesn't exist. Continuing...")
             }
         }
         mediaListeners[key] = PlaybackStateListener(key, data)
-
-        // If a player becomes active because of a migration, we'll need to broadcast its state.
-        // Doing it now would lead to reentrant callbacks, so let's wait until we're done.
-        if (migrating && mediaListeners[key]?.playing != wasPlaying) {
-            mainExecutor.execute {
-                if (mediaListeners[key]?.playing == true) {
-                    if (DEBUG) Log.d(TAG, "deliver delayed playback state for $key")
-                    timeoutCallback.invoke(key, false /* timedOut */)
-                }
-            }
-        }
     }
 
     override fun onMediaDataRemoved(key: String) {
@@ -91,30 +93,39 @@
     }
 
     private inner class PlaybackStateListener(
-        private val key: String,
+        var key: String,
         data: MediaData
     ) : MediaController.Callback() {
 
         var timedOut = false
         var playing: Boolean? = null
 
+        var mediaData: MediaData = data
+            set(value) {
+                mediaController?.unregisterCallback(this)
+                field = value
+                mediaController = if (field.token != null) {
+                    mediaControllerFactory.create(field.token)
+                } else {
+                    null
+                }
+                mediaController?.registerCallback(this)
+                // Let's register the cancellations, but not dispatch events now.
+                // Timeouts didn't happen yet and reentrant events are troublesome.
+                processState(mediaController?.playbackState, dispatchEvents = false)
+            }
+
         // Resume controls may have null token
-        private val mediaController = if (data.token != null) {
-            mediaControllerFactory.create(data.token)
-        } else {
-            null
-        }
+        private var mediaController: MediaController? = null
         private var cancellation: Runnable? = null
 
         init {
-            mediaController?.registerCallback(this)
-            // Let's register the cancellations, but not dispatch events now.
-            // Timeouts didn't happen yet and reentrant events are troublesome.
-            processState(mediaController?.playbackState, dispatchEvents = false)
+            mediaData = data
         }
 
         fun destroy() {
             mediaController?.unregisterCallback(this)
+            cancellation?.run()
         }
 
         override fun onPlaybackStateChanged(state: PlaybackState?) {
@@ -147,9 +158,8 @@
                         Log.v(TAG, "Execute timeout for $key")
                     }
                     timedOut = true
-                    if (dispatchEvents) {
-                        timeoutCallback(key, timedOut)
-                    }
+                    // this event is async, so it's safe even when `dispatchEvents` is false
+                    timeoutCallback(key, timedOut)
                 }, PAUSED_MEDIA_TIMEOUT)
             } else {
                 expireMediaTimeout(key, "playback started - $state, $key")
@@ -171,4 +181,4 @@
             cancellation = null
         }
     }
-}
\ No newline at end of file
+}
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaViewController.kt b/packages/SystemUI/src/com/android/systemui/media/MediaViewController.kt
index 38817d7..92eeed4 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaViewController.kt
@@ -37,6 +37,11 @@
     private val mediaHostStatesManager: MediaHostStatesManager
 ) {
 
+    companion object {
+        @JvmField
+        val GUTS_ANIMATION_DURATION = 500L
+    }
+
     /**
      * A listener when the current dimensions of the player change
      */
@@ -169,6 +174,12 @@
      */
     val expandedLayout = ConstraintSet()
 
+    /**
+     * Whether the guts are visible for the associated player.
+     */
+    var isGutsVisible = false
+        private set
+
     init {
         collapsedLayout.load(context, R.xml.media_collapsed)
         expandedLayout.load(context, R.xml.media_expanded)
@@ -189,6 +200,37 @@
         configurationController.removeCallback(configurationListener)
     }
 
+    /**
+     * Show guts with an animated transition.
+     */
+    fun openGuts() {
+        if (isGutsVisible) return
+        isGutsVisible = true
+        animatePendingStateChange(GUTS_ANIMATION_DURATION, 0L)
+        setCurrentState(currentStartLocation,
+                currentEndLocation,
+                currentTransitionProgress,
+                applyImmediately = false)
+    }
+
+    /**
+     * Close the guts for the associated player.
+     *
+     * @param immediate if `false`, it will animate the transition.
+     */
+    @JvmOverloads
+    fun closeGuts(immediate: Boolean = false) {
+        if (!isGutsVisible) return
+        isGutsVisible = false
+        if (!immediate) {
+            animatePendingStateChange(GUTS_ANIMATION_DURATION, 0L)
+        }
+        setCurrentState(currentStartLocation,
+                currentEndLocation,
+                currentTransitionProgress,
+                applyImmediately = immediate)
+    }
+
     private fun ensureAllMeasurements() {
         val mediaStates = mediaHostStatesManager.mediaHostStates
         for (entry in mediaStates) {
@@ -203,6 +245,24 @@
             if (expansion > 0) expandedLayout else collapsedLayout
 
     /**
+     * Set the views to be showing/hidden based on the [isGutsVisible] for a given
+     * [TransitionViewState].
+     */
+    private fun setGutsViewState(viewState: TransitionViewState) {
+        PlayerViewHolder.controlsIds.forEach { id ->
+            viewState.widgetStates.get(id)?.let { state ->
+                // Make sure to use the unmodified state if guts are not visible
+                state.alpha = if (isGutsVisible) 0f else state.alpha
+                state.gone = if (isGutsVisible) true else state.gone
+            }
+        }
+        PlayerViewHolder.gutsIds.forEach { id ->
+            viewState.widgetStates.get(id)?.alpha = if (isGutsVisible) 1f else 0f
+            viewState.widgetStates.get(id)?.gone = !isGutsVisible
+        }
+    }
+
+    /**
      * Obtain a new viewState for a given media state. This usually returns a cached state, but if
      * it's not available, it will recreate one by measuring, which may be expensive.
      */
@@ -211,7 +271,7 @@
             return null
         }
         // Only a subset of the state is relevant to get a valid viewState. Let's get the cachekey
-        var cacheKey = getKey(state, tmpKey)
+        var cacheKey = getKey(state, isGutsVisible, tmpKey)
         val viewState = viewStates[cacheKey]
         if (viewState != null) {
             // we already have cached this measurement, let's continue
@@ -228,6 +288,7 @@
                         constraintSetForExpansion(state.expansion),
                         TransitionViewState())
 
+                setGutsViewState(result)
                 // We don't want to cache interpolated or null states as this could quickly fill up
                 // our cache. We only cache the start and the end states since the interpolation
                 // is cheap
@@ -252,11 +313,12 @@
         return result
     }
 
-    private fun getKey(state: MediaHostState, result: CacheKey): CacheKey {
+    private fun getKey(state: MediaHostState, guts: Boolean, result: CacheKey): CacheKey {
         result.apply {
             heightMeasureSpec = state.measurementInput?.heightMeasureSpec ?: 0
             widthMeasureSpec = state.measurementInput?.widthMeasureSpec ?: 0
             expansion = state.expansion
+            gutsVisible = guts
         }
         return result
     }
@@ -432,5 +494,6 @@
 private data class CacheKey(
     var widthMeasureSpec: Int = -1,
     var heightMeasureSpec: Int = -1,
-    var expansion: Float = 0.0f
+    var expansion: Float = 0.0f,
+    var gutsVisible: Boolean = false
 )
diff --git a/packages/SystemUI/src/com/android/systemui/media/PlayerViewHolder.kt b/packages/SystemUI/src/com/android/systemui/media/PlayerViewHolder.kt
index 600fdc2..16327bd 100644
--- a/packages/SystemUI/src/com/android/systemui/media/PlayerViewHolder.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/PlayerViewHolder.kt
@@ -59,6 +59,13 @@
     val action3 = itemView.requireViewById<ImageButton>(R.id.action3)
     val action4 = itemView.requireViewById<ImageButton>(R.id.action4)
 
+    // Settings screen
+    val settingsText = itemView.requireViewById<TextView>(R.id.remove_text)
+    val cancel = itemView.requireViewById<View>(R.id.cancel)
+    val dismiss = itemView.requireViewById<ViewGroup>(R.id.dismiss)
+    val dismissLabel = dismiss.getChildAt(0)
+    val settings = itemView.requireViewById<View>(R.id.settings)
+
     init {
         (player.background as IlluminationDrawable).let {
             it.registerLightSource(seamless)
@@ -67,6 +74,9 @@
             it.registerLightSource(action2)
             it.registerLightSource(action3)
             it.registerLightSource(action4)
+            it.registerLightSource(cancel)
+            it.registerLightSource(dismiss)
+            it.registerLightSource(settings)
         }
     }
 
@@ -83,9 +93,6 @@
         }
     }
 
-    // Settings screen
-    val options = itemView.requireViewById<View>(R.id.qs_media_controls_options)
-
     companion object {
         /**
          * Creates a PlayerViewHolder.
@@ -105,5 +112,29 @@
                 progressTimes.layoutDirection = View.LAYOUT_DIRECTION_LTR
             }
         }
+
+        val controlsIds = setOf(
+                R.id.icon,
+                R.id.app_name,
+                R.id.album_art,
+                R.id.header_title,
+                R.id.header_artist,
+                R.id.media_seamless,
+                R.id.notification_media_progress_time,
+                R.id.media_progress_bar,
+                R.id.action0,
+                R.id.action1,
+                R.id.action2,
+                R.id.action3,
+                R.id.action4,
+                R.id.icon
+        )
+        val gutsIds = setOf(
+                R.id.media_text,
+                R.id.remove_text,
+                R.id.cancel,
+                R.id.dismiss,
+                R.id.settings
+        )
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/media/ResumeMediaBrowser.java b/packages/SystemUI/src/com/android/systemui/media/ResumeMediaBrowser.java
index 68b6785..a4d4436 100644
--- a/packages/SystemUI/src/com/android/systemui/media/ResumeMediaBrowser.java
+++ b/packages/SystemUI/src/com/android/systemui/media/ResumeMediaBrowser.java
@@ -30,6 +30,8 @@
 import android.text.TextUtils;
 import android.util.Log;
 
+import com.android.internal.annotations.VisibleForTesting;
+
 import java.util.List;
 
 /**
@@ -46,6 +48,7 @@
     private static final String TAG = "ResumeMediaBrowser";
     private final Context mContext;
     private final Callback mCallback;
+    private MediaBrowserFactory mBrowserFactory;
     private MediaBrowser mMediaBrowser;
     private ComponentName mComponentName;
 
@@ -55,10 +58,12 @@
      * @param callback used to report media items found
      * @param componentName Component name of the MediaBrowserService this browser will connect to
      */
-    public ResumeMediaBrowser(Context context, Callback callback, ComponentName componentName) {
+    public ResumeMediaBrowser(Context context, Callback callback, ComponentName componentName,
+            MediaBrowserFactory browserFactory) {
         mContext = context;
         mCallback = callback;
         mComponentName = componentName;
+        mBrowserFactory = browserFactory;
     }
 
     /**
@@ -74,7 +79,7 @@
         disconnect();
         Bundle rootHints = new Bundle();
         rootHints.putBoolean(MediaBrowserService.BrowserRoot.EXTRA_RECENT, true);
-        mMediaBrowser = new MediaBrowser(mContext,
+        mMediaBrowser = mBrowserFactory.create(
                 mComponentName,
                 mConnectionCallback,
                 rootHints);
@@ -88,17 +93,19 @@
                 List<MediaBrowser.MediaItem> children) {
             if (children.size() == 0) {
                 Log.d(TAG, "No children found for " + mComponentName);
-                return;
-            }
-            // We ask apps to return a playable item as the first child when sending
-            // a request with EXTRA_RECENT; if they don't, no resume controls
-            MediaBrowser.MediaItem child = children.get(0);
-            MediaDescription desc = child.getDescription();
-            if (child.isPlayable() && mMediaBrowser != null) {
-                mCallback.addTrack(desc, mMediaBrowser.getServiceComponent(),
-                        ResumeMediaBrowser.this);
+                mCallback.onError();
             } else {
-                Log.d(TAG, "Child found but not playable for " + mComponentName);
+                // We ask apps to return a playable item as the first child when sending
+                // a request with EXTRA_RECENT; if they don't, no resume controls
+                MediaBrowser.MediaItem child = children.get(0);
+                MediaDescription desc = child.getDescription();
+                if (child.isPlayable() && mMediaBrowser != null) {
+                    mCallback.addTrack(desc, mMediaBrowser.getServiceComponent(),
+                            ResumeMediaBrowser.this);
+                } else {
+                    Log.d(TAG, "Child found but not playable for " + mComponentName);
+                    mCallback.onError();
+                }
             }
             disconnect();
         }
@@ -131,7 +138,7 @@
             Log.d(TAG, "Service connected for " + mComponentName);
             if (mMediaBrowser != null && mMediaBrowser.isConnected()) {
                 String root = mMediaBrowser.getRoot();
-                if (!TextUtils.isEmpty(root)) {
+                if (!TextUtils.isEmpty(root) && mMediaBrowser != null) {
                     mCallback.onConnected();
                     mMediaBrowser.subscribe(root, mSubscriptionCallback);
                     return;
@@ -182,7 +189,7 @@
         disconnect();
         Bundle rootHints = new Bundle();
         rootHints.putBoolean(MediaBrowserService.BrowserRoot.EXTRA_RECENT, true);
-        mMediaBrowser = new MediaBrowser(mContext, mComponentName,
+        mMediaBrowser = mBrowserFactory.create(mComponentName,
                 new MediaBrowser.ConnectionCallback() {
                     @Override
                     public void onConnected() {
@@ -192,7 +199,7 @@
                             return;
                         }
                         MediaSession.Token token = mMediaBrowser.getSessionToken();
-                        MediaController controller = new MediaController(mContext, token);
+                        MediaController controller = createMediaController(token);
                         controller.getTransportControls();
                         controller.getTransportControls().prepare();
                         controller.getTransportControls().play();
@@ -212,6 +219,11 @@
         mMediaBrowser.connect();
     }
 
+    @VisibleForTesting
+    protected MediaController createMediaController(MediaSession.Token token) {
+        return new MediaController(mContext, token);
+    }
+
     /**
      * Get the media session token
      * @return the token, or null if the MediaBrowser is null or disconnected
@@ -235,42 +247,19 @@
 
     /**
      * Used to test if SystemUI is allowed to connect to the given component as a MediaBrowser.
-     * ResumeMediaBrowser.Callback#onError or ResumeMediaBrowser.Callback#onConnected will be called
-     * depending on whether it was successful.
+     * If it can connect, ResumeMediaBrowser.Callback#onConnected will be called. If valid media is
+     * found, then ResumeMediaBrowser.Callback#addTrack will also be called. This allows for more
+     * detailed logging if the service has issues. If it cannot connect, or cannot find valid media,
+     * then ResumeMediaBrowser.Callback#onError will be called.
      * ResumeMediaBrowser#disconnect should be called after this to ensure the connection is closed.
      */
     public void testConnection() {
         disconnect();
-        final MediaBrowser.ConnectionCallback connectionCallback =
-                new MediaBrowser.ConnectionCallback() {
-                    @Override
-                    public void onConnected() {
-                        Log.d(TAG, "connected");
-                        if (mMediaBrowser == null || !mMediaBrowser.isConnected()
-                                || TextUtils.isEmpty(mMediaBrowser.getRoot())) {
-                            mCallback.onError();
-                        } else {
-                            mCallback.onConnected();
-                        }
-                    }
-
-                    @Override
-                    public void onConnectionSuspended() {
-                        Log.d(TAG, "suspended");
-                        mCallback.onError();
-                    }
-
-                    @Override
-                    public void onConnectionFailed() {
-                        Log.d(TAG, "failed");
-                        mCallback.onError();
-                    }
-                };
         Bundle rootHints = new Bundle();
         rootHints.putBoolean(MediaBrowserService.BrowserRoot.EXTRA_RECENT, true);
-        mMediaBrowser = new MediaBrowser(mContext,
+        mMediaBrowser = mBrowserFactory.create(
                 mComponentName,
-                connectionCallback,
+                mConnectionCallback,
                 rootHints);
         mMediaBrowser.connect();
     }
diff --git a/packages/SystemUI/src/com/android/systemui/media/ResumeMediaBrowserFactory.java b/packages/SystemUI/src/com/android/systemui/media/ResumeMediaBrowserFactory.java
new file mode 100644
index 0000000..2261aa5
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/media/ResumeMediaBrowserFactory.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.media;
+
+import android.content.ComponentName;
+import android.content.Context;
+
+import javax.inject.Inject;
+
+/**
+ * Testable wrapper around {@link ResumeMediaBrowser} constructor
+ */
+public class ResumeMediaBrowserFactory {
+    private final Context mContext;
+    private final MediaBrowserFactory mBrowserFactory;
+
+    @Inject
+    public ResumeMediaBrowserFactory(Context context, MediaBrowserFactory browserFactory) {
+        mContext = context;
+        mBrowserFactory = browserFactory;
+    }
+
+    /**
+     * Creates a new ResumeMediaBrowser.
+     *
+     * @param callback will be called on connection or error, and addTrack when media item found
+     * @param componentName component to browse
+     * @return
+     */
+    public ResumeMediaBrowser create(ResumeMediaBrowser.Callback callback,
+            ComponentName componentName) {
+        return new ResumeMediaBrowser(mContext, callback, componentName, mBrowserFactory);
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/media/SeekBarObserver.kt b/packages/SystemUI/src/com/android/systemui/media/SeekBarObserver.kt
index c2631c9..d789501 100644
--- a/packages/SystemUI/src/com/android/systemui/media/SeekBarObserver.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/SeekBarObserver.kt
@@ -28,10 +28,14 @@
  */
 class SeekBarObserver(private val holder: PlayerViewHolder) : Observer<SeekBarViewModel.Progress> {
 
-    val seekBarDefaultMaxHeight = holder.seekBar.context.resources
+    val seekBarEnabledMaxHeight = holder.seekBar.context.resources
         .getDimensionPixelSize(R.dimen.qs_media_enabled_seekbar_height)
     val seekBarDisabledHeight = holder.seekBar.context.resources
         .getDimensionPixelSize(R.dimen.qs_media_disabled_seekbar_height)
+    val seekBarEnabledVerticalPadding = holder.seekBar.context.resources
+            .getDimensionPixelSize(R.dimen.qs_media_enabled_seekbar_vertical_padding)
+    val seekBarDisabledVerticalPadding = holder.seekBar.context.resources
+            .getDimensionPixelSize(R.dimen.qs_media_disabled_seekbar_vertical_padding)
 
     /** Updates seek bar views when the data model changes. */
     @UiThread
@@ -39,6 +43,7 @@
         if (!data.enabled) {
             if (holder.seekBar.maxHeight != seekBarDisabledHeight) {
                 holder.seekBar.maxHeight = seekBarDisabledHeight
+                setVerticalPadding(seekBarDisabledVerticalPadding)
             }
             holder.seekBar.setEnabled(false)
             holder.seekBar.getThumb().setAlpha(0)
@@ -51,14 +56,9 @@
         holder.seekBar.getThumb().setAlpha(if (data.seekAvailable) 255 else 0)
         holder.seekBar.setEnabled(data.seekAvailable)
 
-        if (holder.seekBar.maxHeight != seekBarDefaultMaxHeight) {
-            holder.seekBar.maxHeight = seekBarDefaultMaxHeight
-        }
-
-        data.elapsedTime?.let {
-            holder.seekBar.setProgress(it)
-            holder.elapsedTimeView.setText(DateUtils.formatElapsedTime(
-                    it / DateUtils.SECOND_IN_MILLIS))
+        if (holder.seekBar.maxHeight != seekBarEnabledMaxHeight) {
+            holder.seekBar.maxHeight = seekBarEnabledMaxHeight
+            setVerticalPadding(seekBarEnabledVerticalPadding)
         }
 
         data.duration?.let {
@@ -66,5 +66,18 @@
             holder.totalTimeView.setText(DateUtils.formatElapsedTime(
                     it / DateUtils.SECOND_IN_MILLIS))
         }
+
+        data.elapsedTime?.let {
+            holder.seekBar.setProgress(it)
+            holder.elapsedTimeView.setText(DateUtils.formatElapsedTime(
+                    it / DateUtils.SECOND_IN_MILLIS))
+        }
+    }
+
+    @UiThread
+    fun setVerticalPadding(padding: Int) {
+        val leftPadding = holder.seekBar.paddingLeft
+        val rightPadding = holder.seekBar.paddingRight
+        holder.seekBar.setPadding(leftPadding, padding, rightPadding, padding)
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/media/SeekBarViewModel.kt b/packages/SystemUI/src/com/android/systemui/media/SeekBarViewModel.kt
index 1dca3f1..9e326aa 100644
--- a/packages/SystemUI/src/com/android/systemui/media/SeekBarViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/SeekBarViewModel.kt
@@ -71,7 +71,7 @@
 
 /** ViewModel for seek bar in QS media player. */
 class SeekBarViewModel @Inject constructor(@Background private val bgExecutor: RepeatableExecutor) {
-    private var _data = Progress(false, false, null, null)
+    private var _data = Progress(false, false, null, 0)
         set(value) {
             field = value
             _progress.postValue(value)
@@ -186,10 +186,10 @@
         val mediaMetadata = controller?.metadata
         val seekAvailable = ((playbackState?.actions ?: 0L) and PlaybackState.ACTION_SEEK_TO) != 0L
         val position = playbackState?.position?.toInt()
-        val duration = mediaMetadata?.getLong(MediaMetadata.METADATA_KEY_DURATION)?.toInt()
+        val duration = mediaMetadata?.getLong(MediaMetadata.METADATA_KEY_DURATION)?.toInt() ?: 0
         val enabled = if (playbackState == null ||
                 playbackState?.getState() == PlaybackState.STATE_NONE ||
-                (duration != null && duration <= 0)) false else true
+                (duration <= 0)) false else true
         _data = Progress(enabled, seekAvailable, position, duration)
         checkIfPollingNeeded()
     }
@@ -408,6 +408,6 @@
         val enabled: Boolean,
         val seekAvailable: Boolean,
         val elapsedTime: Int?,
-        val duration: Int?
+        val duration: Int
     )
 }
diff --git a/packages/SystemUI/src/com/android/systemui/pip/PipAnimationController.java b/packages/SystemUI/src/com/android/systemui/pip/PipAnimationController.java
index ead1786..7201931 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/PipAnimationController.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/PipAnimationController.java
@@ -21,7 +21,6 @@
 import android.animation.RectEvaluator;
 import android.animation.ValueAnimator;
 import android.annotation.IntDef;
-import android.content.Context;
 import android.graphics.Rect;
 import android.view.SurfaceControl;
 
@@ -56,13 +55,15 @@
     public static final int TRANSITION_DIRECTION_TO_PIP = 2;
     public static final int TRANSITION_DIRECTION_TO_FULLSCREEN = 3;
     public static final int TRANSITION_DIRECTION_TO_SPLIT_SCREEN = 4;
+    public static final int TRANSITION_DIRECTION_REMOVE_STACK = 5;
 
     @IntDef(prefix = { "TRANSITION_DIRECTION_" }, value = {
             TRANSITION_DIRECTION_NONE,
             TRANSITION_DIRECTION_SAME,
             TRANSITION_DIRECTION_TO_PIP,
             TRANSITION_DIRECTION_TO_FULLSCREEN,
-            TRANSITION_DIRECTION_TO_SPLIT_SCREEN
+            TRANSITION_DIRECTION_TO_SPLIT_SCREEN,
+            TRANSITION_DIRECTION_REMOVE_STACK
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface TransitionDirection {}
@@ -88,7 +89,7 @@
             });
 
     @Inject
-    PipAnimationController(Context context, PipSurfaceTransactionHelper helper) {
+    PipAnimationController(PipSurfaceTransactionHelper helper) {
         mSurfaceTransactionHelper = helper;
     }
 
@@ -338,6 +339,10 @@
 
                 @Override
                 void onStartTransaction(SurfaceControl leash, SurfaceControl.Transaction tx) {
+                    if (getTransitionDirection() == TRANSITION_DIRECTION_REMOVE_STACK) {
+                        // while removing the pip stack, no extra work needs to be done here.
+                        return;
+                    }
                     getSurfaceTransactionHelper()
                             .resetScale(tx, leash, getDestinationBounds())
                             .crop(tx, leash, getDestinationBounds())
diff --git a/packages/SystemUI/src/com/android/systemui/pip/PipBoundsHandler.java b/packages/SystemUI/src/com/android/systemui/pip/PipBoundsHandler.java
index 8bbd15b..583953c 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/PipBoundsHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/PipBoundsHandler.java
@@ -177,7 +177,7 @@
         }
         if (isValidPictureInPictureAspectRatio(mAspectRatio)) {
             transformBoundsToAspectRatio(normalBounds, mAspectRatio,
-                    false /* useCurrentMinEdgeSize */);
+                    false /* useCurrentMinEdgeSize */, false /* useCurrentSize */);
         }
         displayInfo.copyFrom(mDisplayInfo);
     }
@@ -278,7 +278,9 @@
             destinationBounds = new Rect(bounds);
         }
         if (isValidPictureInPictureAspectRatio(aspectRatio)) {
-            transformBoundsToAspectRatio(destinationBounds, aspectRatio, useCurrentMinEdgeSize);
+            boolean useCurrentSize = bounds == null && mReentrySize != null;
+            transformBoundsToAspectRatio(destinationBounds, aspectRatio, useCurrentMinEdgeSize,
+                    useCurrentSize);
         }
         mAspectRatio = aspectRatio;
         return destinationBounds;
@@ -384,7 +386,8 @@
      * @param stackBounds
      */
     public void transformBoundsToAspectRatio(Rect stackBounds) {
-        transformBoundsToAspectRatio(stackBounds, mAspectRatio, true);
+        transformBoundsToAspectRatio(stackBounds, mAspectRatio, true /* useCurrentMinEdgeSize */,
+                true /* useCurrentSize */);
     }
 
     /**
@@ -392,18 +395,16 @@
      * specified aspect ratio.
      */
     private void transformBoundsToAspectRatio(Rect stackBounds, float aspectRatio,
-            boolean useCurrentMinEdgeSize) {
+            boolean useCurrentMinEdgeSize, boolean useCurrentSize) {
         // Save the snap fraction and adjust the size based on the new aspect ratio.
         final float snapFraction = mSnapAlgorithm.getSnapFraction(stackBounds,
                 getMovementBounds(stackBounds));
-        final int minEdgeSize;
+        final int minEdgeSize = useCurrentMinEdgeSize ? mCurrentMinSize : mDefaultMinSize;
         final Size size;
-        if (useCurrentMinEdgeSize) {
-            minEdgeSize = mCurrentMinSize;
+        if (useCurrentMinEdgeSize || useCurrentSize) {
             size = mSnapAlgorithm.getSizeForAspectRatio(
                     new Size(stackBounds.width(), stackBounds.height()), aspectRatio, minEdgeSize);
         } else {
-            minEdgeSize = mDefaultMinSize;
             size = mSnapAlgorithm.getSizeForAspectRatio(aspectRatio, minEdgeSize,
                     mDisplayInfo.logicalWidth, mDisplayInfo.logicalHeight);
         }
diff --git a/packages/SystemUI/src/com/android/systemui/pip/PipTaskOrganizer.java b/packages/SystemUI/src/com/android/systemui/pip/PipTaskOrganizer.java
index 0141dee..54df53d 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/PipTaskOrganizer.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/PipTaskOrganizer.java
@@ -24,6 +24,7 @@
 import static com.android.systemui.pip.PipAnimationController.ANIM_TYPE_ALPHA;
 import static com.android.systemui.pip.PipAnimationController.ANIM_TYPE_BOUNDS;
 import static com.android.systemui.pip.PipAnimationController.TRANSITION_DIRECTION_NONE;
+import static com.android.systemui.pip.PipAnimationController.TRANSITION_DIRECTION_REMOVE_STACK;
 import static com.android.systemui.pip.PipAnimationController.TRANSITION_DIRECTION_SAME;
 import static com.android.systemui.pip.PipAnimationController.TRANSITION_DIRECTION_TO_FULLSCREEN;
 import static com.android.systemui.pip.PipAnimationController.TRANSITION_DIRECTION_TO_PIP;
@@ -39,7 +40,6 @@
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.pm.ActivityInfo;
-import android.content.res.Configuration;
 import android.graphics.Rect;
 import android.os.Handler;
 import android.os.IBinder;
@@ -94,15 +94,46 @@
     private static final int MSG_FINISH_RESIZE = 4;
     private static final int MSG_RESIZE_USER = 5;
 
+    // Not a complete set of states but serves what we want right now.
+    private enum State {
+        UNDEFINED(0),
+        TASK_APPEARED(1),
+        ENTERING_PIP(2),
+        EXITING_PIP(3);
+
+        private final int mStateValue;
+
+        State(int value) {
+            mStateValue = value;
+        }
+
+        private boolean isInPip() {
+            return mStateValue >= TASK_APPEARED.mStateValue
+                    && mStateValue != EXITING_PIP.mStateValue;
+        }
+
+        /**
+         * Resize request can be initiated in other component, ignore if we are no longer in PIP,
+         * still waiting for animation or we're exiting from it.
+         *
+         * @return {@code true} if the resize request should be blocked/ignored.
+         */
+        private boolean shouldBlockResizeRequest() {
+            return mStateValue < ENTERING_PIP.mStateValue
+                    || mStateValue == EXITING_PIP.mStateValue;
+        }
+    }
+
     private final Handler mMainHandler;
     private final Handler mUpdateHandler;
     private final PipBoundsHandler mPipBoundsHandler;
     private final PipAnimationController mPipAnimationController;
+    private final PipUiEventLogger mPipUiEventLoggerLogger;
     private final List<PipTransitionCallback> mPipTransitionCallbacks = new ArrayList<>();
     private final Rect mLastReportedBounds = new Rect();
     private final int mEnterExitAnimationDuration;
     private final PipSurfaceTransactionHelper mSurfaceTransactionHelper;
-    private final Map<IBinder, Configuration> mInitialState = new HashMap<>();
+    private final Map<IBinder, PipWindowConfigurationCompact> mCompactState = new HashMap<>();
     private final Divider mSplitDivider;
 
     // These callbacks are called on the update thread
@@ -187,8 +218,7 @@
     private ActivityManager.RunningTaskInfo mTaskInfo;
     private WindowContainerToken mToken;
     private SurfaceControl mLeash;
-    private boolean mInPip;
-    private boolean mExitingPip;
+    private State mState = State.UNDEFINED;
     private @PipAnimationController.AnimationType int mOneShotAnimationType = ANIM_TYPE_BOUNDS;
     private PipSurfaceTransactionHelper.SurfaceControlTransactionFactory
             mSurfaceControlTransactionFactory;
@@ -200,12 +230,15 @@
      */
     private boolean mShouldDeferEnteringPip;
 
+    private @ActivityInfo.ScreenOrientation int mRequestedOrientation;
+
     @Inject
     public PipTaskOrganizer(Context context, @NonNull PipBoundsHandler boundsHandler,
             @NonNull PipSurfaceTransactionHelper surfaceTransactionHelper,
             @Nullable Divider divider,
             @NonNull DisplayController displayController,
-            @NonNull PipAnimationController pipAnimationController) {
+            @NonNull PipAnimationController pipAnimationController,
+            @NonNull PipUiEventLogger pipUiEventLogger) {
         mMainHandler = new Handler(Looper.getMainLooper());
         mUpdateHandler = new Handler(PipUpdateThread.get().getLooper(), mUpdateCallbacks);
         mPipBoundsHandler = boundsHandler;
@@ -213,6 +246,7 @@
                 .getInteger(R.integer.config_pipResizeAnimationDuration);
         mSurfaceTransactionHelper = surfaceTransactionHelper;
         mPipAnimationController = pipAnimationController;
+        mPipUiEventLoggerLogger = pipUiEventLogger;
         mSurfaceControlTransactionFactory = SurfaceControl.Transaction::new;
         mSplitDivider = divider;
         displayController.addDisplayWindowListener(this);
@@ -236,11 +270,11 @@
     }
 
     public boolean isInPip() {
-        return mInPip;
+        return mState.isInPip();
     }
 
     public boolean isDeferringEnterPipAnimation() {
-        return mInPip && mShouldDeferEnteringPip;
+        return mState.isInPip() && mShouldDeferEnteringPip;
     }
 
     /**
@@ -269,21 +303,31 @@
      * @param animationDurationMs duration in millisecond for the exiting PiP transition
      */
     public void exitPip(int animationDurationMs) {
-        if (!mInPip || mExitingPip || mToken == null) {
+        if (!mState.isInPip() || mToken == null) {
             Log.wtf(TAG, "Not allowed to exitPip in current state"
-                    + " mInPip=" + mInPip + " mExitingPip=" + mExitingPip + " mToken=" + mToken);
+                    + " mState=" + mState + " mToken=" + mToken);
             return;
         }
 
-        final Configuration initialConfig = mInitialState.remove(mToken.asBinder());
-        final boolean orientationDiffers = initialConfig.windowConfiguration.getRotation()
+        final PipWindowConfigurationCompact config = mCompactState.remove(mToken.asBinder());
+        if (config == null) {
+            Log.wtf(TAG, "Token not in record, this should not happen mToken=" + mToken);
+            return;
+        }
+
+        mPipUiEventLoggerLogger.log(
+                PipUiEventLogger.PipUiEventEnum.PICTURE_IN_PICTURE_EXPAND_TO_FULLSCREEN);
+        config.syncWithScreenOrientation(mRequestedOrientation,
+                mPipBoundsHandler.getDisplayRotation());
+        final boolean orientationDiffers = config.getRotation()
                 != mPipBoundsHandler.getDisplayRotation();
         final WindowContainerTransaction wct = new WindowContainerTransaction();
-        final Rect destinationBounds = initialConfig.windowConfiguration.getBounds();
+        final Rect destinationBounds = config.getBounds();
         final int direction = syncWithSplitScreenBounds(destinationBounds)
                 ? TRANSITION_DIRECTION_TO_SPLIT_SCREEN
                 : TRANSITION_DIRECTION_TO_FULLSCREEN;
         if (orientationDiffers) {
+            mState = State.EXITING_PIP;
             // Send started callback though animation is ignored.
             sendOnPipTransitionStarted(direction);
             // Don't bother doing an animation if the display rotation differs or if it's in
@@ -292,7 +336,6 @@
             WindowOrganizer.applyTransaction(wct);
             // Send finished callback though animation is ignored.
             sendOnPipTransitionFinished(direction);
-            mInPip = false;
         } else {
             final SurfaceControl.Transaction tx =
                     mSurfaceControlTransactionFactory.getTransaction();
@@ -311,11 +354,10 @@
                     scheduleAnimateResizePip(mLastReportedBounds, destinationBounds,
                             null /* sourceHintRect */, direction, animationDurationMs,
                             null /* updateBoundsCallback */);
-                    mInPip = false;
+                    mState = State.EXITING_PIP;
                 }
             });
         }
-        mExitingPip = true;
     }
 
     private void applyWindowingModeChangeOnExit(WindowContainerTransaction wct, int direction) {
@@ -332,26 +374,35 @@
      * Removes PiP immediately.
      */
     public void removePip() {
-        if (!mInPip || mExitingPip ||  mToken == null) {
+        if (!mState.isInPip() ||  mToken == null) {
             Log.wtf(TAG, "Not allowed to removePip in current state"
-                    + " mInPip=" + mInPip + " mExitingPip=" + mExitingPip + " mToken=" + mToken);
+                    + " mState=" + mState + " mToken=" + mToken);
             return;
         }
-        getUpdateHandler().post(() -> {
-            try {
-                // Reset the task bounds first to ensure the activity configuration is reset as well
-                final WindowContainerTransaction wct = new WindowContainerTransaction();
-                wct.setBounds(mToken, null);
-                WindowOrganizer.applyTransaction(wct);
 
-                ActivityTaskManager.getService().removeStacksInWindowingModes(
-                        new int[]{ WINDOWING_MODE_PINNED });
-            } catch (RemoteException e) {
-                Log.e(TAG, "Failed to remove PiP", e);
-            }
-        });
-        mInitialState.remove(mToken.asBinder());
-        mExitingPip = true;
+        // removePipImmediately is expected when the following animation finishes.
+        mUpdateHandler.post(() -> mPipAnimationController
+                .getAnimator(mLeash, mLastReportedBounds, 1f, 0f)
+                .setTransitionDirection(TRANSITION_DIRECTION_REMOVE_STACK)
+                .setPipAnimationCallback(mPipAnimationCallback)
+                .setDuration(mEnterExitAnimationDuration)
+                .start());
+        mCompactState.remove(mToken.asBinder());
+        mState = State.EXITING_PIP;
+    }
+
+    private void removePipImmediately() {
+        try {
+            // Reset the task bounds first to ensure the activity configuration is reset as well
+            final WindowContainerTransaction wct = new WindowContainerTransaction();
+            wct.setBounds(mToken, null);
+            WindowOrganizer.applyTransaction(wct);
+
+            ActivityTaskManager.getService().removeStacksInWindowingModes(
+                    new int[]{ WINDOWING_MODE_PINNED });
+        } catch (RemoteException e) {
+            Log.e(TAG, "Failed to remove PiP", e);
+        }
     }
 
     @Override
@@ -359,11 +410,15 @@
         Objects.requireNonNull(info, "Requires RunningTaskInfo");
         mTaskInfo = info;
         mToken = mTaskInfo.token;
-        mInPip = true;
-        mExitingPip = false;
+        mState = State.TASK_APPEARED;
         mLeash = leash;
-        mInitialState.put(mToken.asBinder(), new Configuration(mTaskInfo.configuration));
+        mCompactState.put(mToken.asBinder(),
+                new PipWindowConfigurationCompact(mTaskInfo.configuration.windowConfiguration));
         mPictureInPictureParams = mTaskInfo.pictureInPictureParams;
+        mRequestedOrientation = info.requestedOrientation;
+
+        mPipUiEventLoggerLogger.setTaskInfo(mTaskInfo);
+        mPipUiEventLoggerLogger.log(PipUiEventLogger.PipUiEventEnum.PICTURE_IN_PICTURE_ENTER);
 
         if (mShouldDeferEnteringPip) {
             if (DEBUG) Log.d(TAG, "Defer entering PiP animation, fixed rotation is ongoing");
@@ -387,6 +442,7 @@
             scheduleAnimateResizePip(currentBounds, destinationBounds, sourceHintRect,
                     TRANSITION_DIRECTION_TO_PIP, mEnterExitAnimationDuration,
                     null /* updateBoundsCallback */);
+            mState = State.ENTERING_PIP;
         } else if (mOneShotAnimationType == ANIM_TYPE_ALPHA) {
             enterPipWithAlphaAnimation(destinationBounds, mEnterExitAnimationDuration);
             mOneShotAnimationType = ANIM_TYPE_BOUNDS;
@@ -432,6 +488,9 @@
                         .setPipAnimationCallback(mPipAnimationCallback)
                         .setDuration(durationMs)
                         .start());
+                // mState is set right after the animation is kicked off to block any resize
+                // requests such as offsetPip that may have been called prior to the transition.
+                mState = State.ENTERING_PIP;
             }
         });
     }
@@ -484,7 +543,7 @@
      */
     @Override
     public void onTaskVanished(ActivityManager.RunningTaskInfo info) {
-        if (!mInPip) {
+        if (!mState.isInPip()) {
             return;
         }
         final WindowContainerToken token = info.token;
@@ -495,13 +554,15 @@
         }
         mShouldDeferEnteringPip = false;
         mPictureInPictureParams = null;
-        mInPip = false;
-        mExitingPip = false;
+        mState = State.UNDEFINED;
+        mPipUiEventLoggerLogger.setTaskInfo(null);
     }
 
     @Override
     public void onTaskInfoChanged(ActivityManager.RunningTaskInfo info) {
         Objects.requireNonNull(mToken, "onTaskInfoChanged requires valid existing mToken");
+        mRequestedOrientation = info.requestedOrientation;
+        // check PictureInPictureParams for aspect ratio change.
         final PictureInPictureParams newParams = info.pictureInPictureParams;
         if (newParams == null || !applyPictureInPictureParams(newParams)) {
             Log.d(TAG, "Ignored onTaskInfoChanged with PiP param: " + newParams);
@@ -528,7 +589,7 @@
 
     @Override
     public void onFixedRotationFinished(int displayId) {
-        if (mShouldDeferEnteringPip && mInPip) {
+        if (mShouldDeferEnteringPip && mState.isInPip()) {
             final Rect destinationBounds = mPipBoundsHandler.getDestinationBounds(
                     mTaskInfo.topActivity, getAspectRatioOrDefault(mPictureInPictureParams),
                     null /* bounds */, getMinimalSize(mTaskInfo.topActivityInfo));
@@ -539,8 +600,6 @@
     }
 
     /**
-     * TODO(b/152809058): consolidate the display info handling logic in SysUI
-     *
      * @param destinationBoundsOut the current destination bounds will be populated to this param
      */
     @SuppressWarnings("unchecked")
@@ -551,7 +610,7 @@
                 mPipAnimationController.getCurrentAnimator();
         if (animator == null || !animator.isRunning()
                 || animator.getTransitionDirection() != TRANSITION_DIRECTION_TO_PIP) {
-            if (mInPip && fromRotation) {
+            if (mState.isInPip() && fromRotation) {
                 // If we are rotating while there is a current animation, immediately cancel the
                 // animation (remove the listeners so we don't trigger the normal finish resize
                 // call that should only happen on the update thread)
@@ -612,7 +671,7 @@
      * {@link PictureInPictureParams} would affect the bounds.
      */
     private boolean applyPictureInPictureParams(@NonNull PictureInPictureParams params) {
-        final boolean changed = (mPictureInPictureParams == null) ? true : !Objects.equals(
+        final boolean changed = (mPictureInPictureParams == null) || !Objects.equals(
                 mPictureInPictureParams.getAspectRatioRational(), params.getAspectRatioRational());
         if (changed) {
             mPictureInPictureParams = params;
@@ -637,10 +696,10 @@
     private void scheduleAnimateResizePip(Rect currentBounds, Rect destinationBounds,
             Rect sourceHintRect, @PipAnimationController.TransitionDirection int direction,
             int durationMs, Consumer<Rect> updateBoundsCallback) {
-        if (!mInPip) {
+        if (!mState.isInPip()) {
             // TODO: tend to use shouldBlockResizeRequest here as well but need to consider
             // the fact that when in exitPip, scheduleAnimateResizePip is executed in the window
-            // container transaction callback and we want to set the mExitingPip immediately.
+            // container transaction callback and we want to set the mState immediately.
             return;
         }
 
@@ -697,7 +756,7 @@
     private void scheduleFinishResizePip(Rect destinationBounds,
             @PipAnimationController.TransitionDirection int direction,
             Consumer<Rect> updateBoundsCallback) {
-        if (shouldBlockResizeRequest()) {
+        if (mState.shouldBlockResizeRequest()) {
             return;
         }
 
@@ -716,7 +775,7 @@
         mSurfaceTransactionHelper
                 .crop(tx, mLeash, destinationBounds)
                 .resetScale(tx, mLeash, destinationBounds)
-                .round(tx, mLeash, mInPip);
+                .round(tx, mLeash, mState.isInPip());
         return tx;
     }
 
@@ -725,7 +784,7 @@
      */
     public void scheduleOffsetPip(Rect originalBounds, int offset, int duration,
             Consumer<Rect> updateBoundsCallback) {
-        if (shouldBlockResizeRequest()) {
+        if (mState.shouldBlockResizeRequest()) {
             return;
         }
         if (mShouldDeferEnteringPip) {
@@ -770,7 +829,7 @@
         final SurfaceControl.Transaction tx = mSurfaceControlTransactionFactory.getTransaction();
         mSurfaceTransactionHelper
                 .crop(tx, mLeash, destinationBounds)
-                .round(tx, mLeash, mInPip);
+                .round(tx, mLeash, mState.isInPip());
         tx.apply();
     }
 
@@ -803,7 +862,10 @@
                     + "directly");
         }
         mLastReportedBounds.set(destinationBounds);
-        if (isInPipDirection(direction) && type == ANIM_TYPE_ALPHA) {
+        if (direction == TRANSITION_DIRECTION_REMOVE_STACK) {
+            removePipImmediately();
+            return;
+        } else if (isInPipDirection(direction) && type == ANIM_TYPE_ALPHA) {
             return;
         }
 
@@ -899,16 +961,6 @@
     }
 
     /**
-     * Resize request can be initiated in other component, ignore if we are no longer in PIP
-     * or we're exiting from it.
-     *
-     * @return {@code true} if the resize request should be blocked/ignored.
-     */
-    private boolean shouldBlockResizeRequest() {
-        return !mInPip || mExitingPip;
-    }
-
-    /**
      * Sync with {@link #mSplitDivider} on destination bounds if PiP is going to split screen.
      *
      * @param destinationBoundsOut contain the updated destination bounds if applicable
@@ -936,14 +988,14 @@
         pw.println(innerPrefix + "mToken=" + mToken
                 + " binder=" + (mToken != null ? mToken.asBinder() : null));
         pw.println(innerPrefix + "mLeash=" + mLeash);
-        pw.println(innerPrefix + "mInPip=" + mInPip);
+        pw.println(innerPrefix + "mState=" + mState);
         pw.println(innerPrefix + "mOneShotAnimationType=" + mOneShotAnimationType);
         pw.println(innerPrefix + "mPictureInPictureParams=" + mPictureInPictureParams);
         pw.println(innerPrefix + "mLastReportedBounds=" + mLastReportedBounds);
         pw.println(innerPrefix + "mInitialState:");
-        for (Map.Entry<IBinder, Configuration> e : mInitialState.entrySet()) {
+        for (Map.Entry<IBinder, PipWindowConfigurationCompact> e : mCompactState.entrySet()) {
             pw.println(innerPrefix + "  binder=" + e.getKey()
-                    + " winConfig=" + e.getValue().windowConfiguration);
+                    + " config=" + e.getValue());
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/pip/PipUiEventLogger.java b/packages/SystemUI/src/com/android/systemui/pip/PipUiEventLogger.java
new file mode 100644
index 0000000..9702035
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/pip/PipUiEventLogger.java
@@ -0,0 +1,120 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.pip;
+
+import android.app.TaskInfo;
+import android.content.pm.PackageManager;
+
+import com.android.internal.logging.UiEvent;
+import com.android.internal.logging.UiEventLogger;
+
+import javax.inject.Inject;
+import javax.inject.Singleton;
+
+
+/**
+ * Helper class that ends PiP log to UiEvent, see also go/uievent
+ */
+@Singleton
+public class PipUiEventLogger {
+
+    private static final int INVALID_PACKAGE_UID = -1;
+
+    private final UiEventLogger mUiEventLogger;
+    private final PackageManager mPackageManager;
+
+    private String mPackageName;
+    private int mPackageUid = INVALID_PACKAGE_UID;
+
+    @Inject
+    public PipUiEventLogger(UiEventLogger uiEventLogger, PackageManager packageManager) {
+        mUiEventLogger = uiEventLogger;
+        mPackageManager = packageManager;
+    }
+
+    public void setTaskInfo(TaskInfo taskInfo) {
+        if (taskInfo == null) {
+            mPackageName = null;
+            mPackageUid = INVALID_PACKAGE_UID;
+        } else {
+            mPackageName = taskInfo.topActivity.getPackageName();
+            mPackageUid = getUid(mPackageName, taskInfo.userId);
+        }
+    }
+
+    /**
+     * Sends log via UiEvent, reference go/uievent for how to debug locally
+     */
+    public void log(PipUiEventEnum event) {
+        if (mPackageName == null || mPackageUid == INVALID_PACKAGE_UID) {
+            return;
+        }
+        mUiEventLogger.log(event, mPackageUid, mPackageName);
+    }
+
+    private int getUid(String packageName, int userId) {
+        int uid = INVALID_PACKAGE_UID;
+        try {
+            uid = mPackageManager.getApplicationInfoAsUser(
+                    packageName, 0 /* ApplicationInfoFlags */, userId).uid;
+        } catch (PackageManager.NameNotFoundException e) {
+            // do nothing.
+        }
+        return uid;
+    }
+
+    /**
+     * Enums for logging the PiP events to UiEvent
+     */
+    public enum PipUiEventEnum implements UiEventLogger.UiEventEnum {
+        @UiEvent(doc = "Activity enters picture-in-picture mode")
+        PICTURE_IN_PICTURE_ENTER(603),
+
+        @UiEvent(doc = "Expands from picture-in-picture to fullscreen")
+        PICTURE_IN_PICTURE_EXPAND_TO_FULLSCREEN(604),
+
+        @UiEvent(doc = "Removes picture-in-picture by tap close button")
+        PICTURE_IN_PICTURE_TAP_TO_REMOVE(605),
+
+        @UiEvent(doc = "Removes picture-in-picture by drag to dismiss area")
+        PICTURE_IN_PICTURE_DRAG_TO_REMOVE(606),
+
+        @UiEvent(doc = "Shows picture-in-picture menu")
+        PICTURE_IN_PICTURE_SHOW_MENU(607),
+
+        @UiEvent(doc = "Hides picture-in-picture menu")
+        PICTURE_IN_PICTURE_HIDE_MENU(608),
+
+        @UiEvent(doc = "Changes the aspect ratio of picture-in-picture window. This is inherited"
+                + " from previous Tron-based logging and currently not in use.")
+        PICTURE_IN_PICTURE_CHANGE_ASPECT_RATIO(609),
+
+        @UiEvent(doc = "User resize of the picture-in-picture window")
+        PICTURE_IN_PICTURE_RESIZE(610);
+
+        private final int mId;
+
+        PipUiEventEnum(int id) {
+            mId = id;
+        }
+
+        @Override
+        public int getId() {
+            return mId;
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/pip/PipWindowConfigurationCompact.java b/packages/SystemUI/src/com/android/systemui/pip/PipWindowConfigurationCompact.java
new file mode 100644
index 0000000..ba104d6
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/pip/PipWindowConfigurationCompact.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.pip;
+
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
+import static android.view.Surface.ROTATION_0;
+import static android.view.Surface.ROTATION_180;
+import static android.view.Surface.ROTATION_270;
+import static android.view.Surface.ROTATION_90;
+
+import android.app.WindowConfiguration;
+import android.content.pm.ActivityInfo;
+import android.graphics.Rect;
+import android.view.Surface;
+
+/**
+ * Compact {@link WindowConfiguration} for PiP usage and supports operations such as rotate.
+ */
+class PipWindowConfigurationCompact {
+    private @Surface.Rotation int mRotation;
+    private Rect mBounds;
+
+    PipWindowConfigurationCompact(WindowConfiguration windowConfiguration) {
+        mRotation = windowConfiguration.getRotation();
+        mBounds = windowConfiguration.getBounds();
+    }
+
+    @Surface.Rotation int getRotation() {
+        return mRotation;
+    }
+
+    Rect getBounds() {
+        return mBounds;
+    }
+
+    void syncWithScreenOrientation(@ActivityInfo.ScreenOrientation int screenOrientation,
+            @Surface.Rotation int displayRotation) {
+        if (mBounds.top != 0 || mBounds.left != 0) {
+            // Supports fullscreen bounds like (0, 0, width, height) only now.
+            return;
+        }
+        boolean rotateNeeded = false;
+        if (ActivityInfo.isFixedOrientationPortrait(screenOrientation)
+                && (mRotation == ROTATION_90 || mRotation == ROTATION_270)) {
+            mRotation = ROTATION_0;
+            rotateNeeded = true;
+        } else if (ActivityInfo.isFixedOrientationLandscape(screenOrientation)
+                && (mRotation == ROTATION_0 || mRotation == ROTATION_180)) {
+            mRotation = ROTATION_90;
+            rotateNeeded = true;
+        } else if (screenOrientation == SCREEN_ORIENTATION_UNSPECIFIED
+                && mRotation != displayRotation) {
+            mRotation = displayRotation;
+            rotateNeeded = true;
+        }
+        if (rotateNeeded) {
+            mBounds.set(0, 0, mBounds.height(), mBounds.width());
+        }
+    }
+
+    @Override
+    public String toString() {
+        return "PipWindowConfigurationCompact(rotation=" + mRotation
+                + " bounds=" + mBounds + ")";
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java
index 7d35416..9d9c5a6 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java
@@ -46,6 +46,7 @@
 import com.android.systemui.pip.PipBoundsHandler;
 import com.android.systemui.pip.PipSnapAlgorithm;
 import com.android.systemui.pip.PipTaskOrganizer;
+import com.android.systemui.pip.PipUiEventLogger;
 import com.android.systemui.shared.recents.IPinnedStackAnimationListener;
 import com.android.systemui.shared.system.ActivityManagerWrapper;
 import com.android.systemui.shared.system.InputConsumerController;
@@ -229,7 +230,10 @@
 
         @Override
         public void onAspectRatioChanged(float aspectRatio) {
-            mHandler.post(() -> mPipBoundsHandler.onAspectRatioChanged(aspectRatio));
+            mHandler.post(() -> {
+                mPipBoundsHandler.onAspectRatioChanged(aspectRatio);
+                mTouchHandler.onAspectRatioChanged();
+            });
         }
     }
 
@@ -241,7 +245,8 @@
             PipBoundsHandler pipBoundsHandler,
             PipSnapAlgorithm pipSnapAlgorithm,
             PipTaskOrganizer pipTaskOrganizer,
-            SysUiState sysUiState) {
+            SysUiState sysUiState,
+            PipUiEventLogger pipUiEventLogger) {
         mContext = context;
         mActivityManager = ActivityManager.getService();
 
@@ -262,7 +267,8 @@
                 mInputConsumerController);
         mTouchHandler = new PipTouchHandler(context, mActivityManager,
                 mMenuController, mInputConsumerController, mPipBoundsHandler, mPipTaskOrganizer,
-                floatingContentCoordinator, deviceConfig, pipSnapAlgorithm, sysUiState);
+                floatingContentCoordinator, deviceConfig, pipSnapAlgorithm, sysUiState,
+                pipUiEventLogger);
         mAppOpsListener = new PipAppOpsListener(context, mActivityManager,
                 mTouchHandler.getMotionHelper());
         displayController.addDisplayChangingController(mRotationController);
@@ -355,17 +361,8 @@
     @Override
     public void onPipTransitionStarted(ComponentName activity, int direction) {
         if (isOutPipDirection(direction)) {
-            // On phones, the expansion animation that happens on pip tap before restoring
-            // to fullscreen makes it so that the bounds received here are the expanded
-            // bounds. We want to restore to the unexpanded bounds when re-entering pip,
-            // so we save the bounds before expansion (normal) instead of the current
-            // bounds.
-            mReentryBounds.set(mTouchHandler.getNormalBounds());
-            // Apply the snap fraction of the current bounds to the normal bounds.
-            final Rect bounds = mPipTaskOrganizer.getLastReportedBounds();
-            float snapFraction = mPipBoundsHandler.getSnapFraction(bounds);
-            mPipBoundsHandler.applySnapFraction(mReentryBounds, snapFraction);
-            // Save reentry bounds (normal non-expand bounds with current position applied).
+            // Exiting PIP, save the reentry bounds to restore to when re-entering.
+            updateReentryBounds();
             mPipBoundsHandler.onSaveReentryBounds(activity, mReentryBounds);
         }
         // Disable touches while the animation is running
@@ -379,6 +376,18 @@
         }
     }
 
+    /**
+     * Update the bounds used to save the re-entry size and snap fraction when exiting PIP.
+     */
+    public void updateReentryBounds() {
+        final Rect reentryBounds = mTouchHandler.getUserResizeBounds();
+        // Apply the snap fraction of the current bounds to the normal bounds.
+        final Rect bounds = mPipTaskOrganizer.getLastReportedBounds();
+        float snapFraction = mPipBoundsHandler.getSnapFraction(bounds);
+        mPipBoundsHandler.applySnapFraction(reentryBounds, snapFraction);
+        mReentryBounds.set(reentryBounds);
+    }
+
     @Override
     public void onPipTransitionFinished(ComponentName activity, int direction) {
         onPipTransitionFinishedOrCanceled(direction);
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivity.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivity.java
index 2a83aa0..586399c 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivity.java
@@ -562,8 +562,10 @@
 
                     // TODO: Check if the action drawable has changed before we reload it
                     action.getIcon().loadDrawableAsync(this, d -> {
-                        d.setTint(Color.WHITE);
-                        actionView.setImageDrawable(d);
+                        if (d != null) {
+                            d.setTint(Color.WHITE);
+                            actionView.setImageDrawable(d);
+                        }
                     }, mHandler);
                     actionView.setContentDescription(action.getContentDescription());
                     if (action.isEnabled()) {
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMotionHelper.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMotionHelper.java
index 8a2e4ae..9f0b1de 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMotionHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMotionHelper.java
@@ -26,9 +26,10 @@
 import android.util.Log;
 import android.view.Choreographer;
 
+import androidx.dynamicanimation.animation.AnimationHandler;
+import androidx.dynamicanimation.animation.AnimationHandler.FrameCallbackScheduler;
 import androidx.dynamicanimation.animation.SpringForce;
 
-import com.android.internal.graphics.SfVsyncFrameCallbackProvider;
 import com.android.systemui.pip.PipSnapAlgorithm;
 import com.android.systemui.pip.PipTaskOrganizer;
 import com.android.systemui.util.FloatingContentCoordinator;
@@ -74,9 +75,6 @@
     /** The region that all of PIP must stay within. */
     private final Rect mFloatingAllowedArea = new Rect();
 
-    private final SfVsyncFrameCallbackProvider mSfVsyncFrameProvider =
-            new SfVsyncFrameCallbackProvider();
-
     /**
      * Temporary bounds used when PIP is being dragged or animated. These bounds are applied to PIP
      * using {@link PipTaskOrganizer#scheduleUserResizePip}, so that we can animate shrinking into
@@ -94,8 +92,13 @@
     /** Coordinator instance for resolving conflicts with other floating content. */
     private FloatingContentCoordinator mFloatingContentCoordinator;
 
-    /** Callback that re-sizes PIP to the animated bounds. */
-    private final Choreographer.FrameCallback mResizePipVsyncCallback;
+    private ThreadLocal<AnimationHandler> mSfAnimationHandlerThreadLocal =
+            ThreadLocal.withInitial(() -> {
+                FrameCallbackScheduler scheduler = runnable ->
+                        Choreographer.getSfInstance().postFrameCallback(t -> runnable.run());
+                AnimationHandler handler = new AnimationHandler(scheduler);
+                return handler;
+            });
 
     /**
      * PhysicsAnimator instance for animating {@link #mTemporaryBounds} using physics animations.
@@ -171,16 +174,15 @@
         mSnapAlgorithm = snapAlgorithm;
         mFloatingContentCoordinator = floatingContentCoordinator;
         mPipTaskOrganizer.registerPipTransitionCallback(mPipTransitionCallback);
+        mTemporaryBoundsPhysicsAnimator.setCustomAnimationHandler(
+                mSfAnimationHandlerThreadLocal.get());
 
-        mResizePipVsyncCallback = l -> {
+        mResizePipUpdateListener = (target, values) -> {
             if (!mTemporaryBounds.isEmpty()) {
                 mPipTaskOrganizer.scheduleUserResizePip(
                         mBounds, mTemporaryBounds, null);
             }
         };
-
-        mResizePipUpdateListener = (target, values) ->
-                mSfVsyncFrameProvider.postFrameCallback(mResizePipVsyncCallback);
     }
 
     @NonNull
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipResizeGestureHandler.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipResizeGestureHandler.java
index 1ca53f9..badd883 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipResizeGestureHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipResizeGestureHandler.java
@@ -53,12 +53,12 @@
 import com.android.systemui.model.SysUiState;
 import com.android.systemui.pip.PipBoundsHandler;
 import com.android.systemui.pip.PipTaskOrganizer;
+import com.android.systemui.pip.PipUiEventLogger;
 import com.android.systemui.util.DeviceConfigProxy;
 
 import java.io.PrintWriter;
 import java.util.concurrent.Executor;
 import java.util.function.Function;
-import java.util.function.Supplier;
 
 /**
  * Helper on top of PipTouchHandler that handles inputs OUTSIDE of the PIP window, which is used to
@@ -80,6 +80,7 @@
     private final Context mContext;
     private final PipBoundsHandler mPipBoundsHandler;
     private final PipMotionHelper mMotionHelper;
+    private final PipMenuActivityController mMenuController;
     private final int mDisplayId;
     private final Executor mMainExecutor;
     private final SysUiState mSysUiState;
@@ -89,6 +90,7 @@
     private final Point mMaxSize = new Point();
     private final Point mMinSize = new Point();
     private final Rect mLastResizeBounds = new Rect();
+    private final Rect mUserResizeBounds = new Rect();
     private final Rect mLastDownBounds = new Rect();
     private final Rect mDragCornerSize = new Rect();
     private final Rect mTmpTopLeftCorner = new Rect();
@@ -110,22 +112,26 @@
     private InputMonitor mInputMonitor;
     private InputEventReceiver mInputEventReceiver;
     private PipTaskOrganizer mPipTaskOrganizer;
+    private PipUiEventLogger mPipUiEventLogger;
 
     private int mCtrlType;
 
     public PipResizeGestureHandler(Context context, PipBoundsHandler pipBoundsHandler,
             PipMotionHelper motionHelper, DeviceConfigProxy deviceConfig,
-            PipTaskOrganizer pipTaskOrganizer, Function<Rect, Rect> movementBoundsSupplier,
-            Runnable updateMovementBoundsRunnable, SysUiState sysUiState) {
+            PipTaskOrganizer pipTaskOrganizer, PipMenuActivityController pipMenuController,
+            Function<Rect, Rect> movementBoundsSupplier, Runnable updateMovementBoundsRunnable,
+            SysUiState sysUiState, PipUiEventLogger pipUiEventLogger) {
         mContext = context;
         mDisplayId = context.getDisplayId();
         mMainExecutor = context.getMainExecutor();
         mPipBoundsHandler = pipBoundsHandler;
+        mMenuController = pipMenuController;
         mMotionHelper = motionHelper;
         mPipTaskOrganizer = pipTaskOrganizer;
         mMovementBoundsSupplier = movementBoundsSupplier;
         mUpdateMovementBoundsRunnable = updateMovementBoundsRunnable;
         mSysUiState = sysUiState;
+        mPipUiEventLogger = pipUiEventLogger;
 
         context.getDisplay().getRealSize(mMaxSize);
         reloadResources();
@@ -182,6 +188,7 @@
 
     void onActivityUnpinned() {
         mIsAttached = false;
+        mUserResizeBounds.setEmpty();
         updateIsEnabled();
     }
 
@@ -317,6 +324,10 @@
                         mInputMonitor.pilferPointers();
                     }
                     if (mThresholdCrossed) {
+                        if (mMenuController.isMenuActivityVisible()) {
+                            mMenuController.hideMenuWithoutResize();
+                            mMenuController.hideMenu();
+                        }
                         final Rect currentPipBounds = mMotionHelper.getBounds();
                         mLastResizeBounds.set(TaskResizingAlgorithm.resizeDrag(x, y,
                                 mDownPoint.x, mDownPoint.y, currentPipBounds, mCtrlType, mMinSize.x,
@@ -330,6 +341,7 @@
                 case MotionEvent.ACTION_UP:
                 case MotionEvent.ACTION_CANCEL:
                     if (!mLastResizeBounds.isEmpty()) {
+                        mUserResizeBounds.set(mLastResizeBounds);
                         mPipTaskOrganizer.scheduleFinishResizePip(mLastResizeBounds,
                                 (Rect bounds) -> {
                                     new Handler(Looper.getMainLooper()).post(() -> {
@@ -338,6 +350,8 @@
                                         resetState();
                                     });
                                 });
+                        mPipUiEventLogger.log(
+                                PipUiEventLogger.PipUiEventEnum.PICTURE_IN_PICTURE_RESIZE);
                     } else {
                         resetState();
                     }
@@ -352,6 +366,18 @@
         mThresholdCrossed = false;
     }
 
+    void setUserResizeBounds(Rect bounds) {
+        mUserResizeBounds.set(bounds);
+    }
+
+    void invalidateUserResizeBounds() {
+        mUserResizeBounds.setEmpty();
+    }
+
+    Rect getUserResizeBounds() {
+        return mUserResizeBounds;
+    }
+
     void updateMaxSize(int maxX, int maxY) {
         mMaxSize.set(maxX, maxY);
     }
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java
index a8130a1..11e609b 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java
@@ -34,7 +34,6 @@
 import android.os.Handler;
 import android.os.RemoteException;
 import android.util.Log;
-import android.util.Pair;
 import android.util.Size;
 import android.view.Gravity;
 import android.view.IPinnedStackController;
@@ -55,13 +54,13 @@
 import androidx.dynamicanimation.animation.SpringForce;
 
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.os.logging.MetricsLoggerWrapper;
 import com.android.systemui.R;
 import com.android.systemui.model.SysUiState;
 import com.android.systemui.pip.PipAnimationController;
 import com.android.systemui.pip.PipBoundsHandler;
 import com.android.systemui.pip.PipSnapAlgorithm;
 import com.android.systemui.pip.PipTaskOrganizer;
+import com.android.systemui.pip.PipUiEventLogger;
 import com.android.systemui.shared.system.InputConsumerController;
 import com.android.systemui.util.DeviceConfigProxy;
 import com.android.systemui.util.DismissCircleView;
@@ -94,6 +93,8 @@
     private final WindowManager mWindowManager;
     private final IActivityManager mActivityManager;
     private final PipBoundsHandler mPipBoundsHandler;
+    private final PipUiEventLogger mPipUiEventLogger;
+
     private PipResizeGestureHandler mPipResizeGestureHandler;
     private IPinnedStackController mPinnedStackController;
 
@@ -132,9 +133,6 @@
 
     // The current movement bounds
     private Rect mMovementBounds = new Rect();
-    // The current resized bounds, changed by user resize.
-    // This is used during expand/un-expand to save/restore the user's resized size.
-    @VisibleForTesting Rect mResizedBounds = new Rect();
 
     // The reference inset bounds, used to determine the dismiss fraction
     private Rect mInsetBounds = new Rect();
@@ -198,11 +196,7 @@
 
         @Override
         public void onPipDismiss() {
-            Pair<ComponentName, Integer> topPipActivity = PipUtils.getTopPipActivity(mContext,
-                    mActivityManager);
-            if (topPipActivity.first != null) {
-                MetricsLoggerWrapper.logPictureInPictureDismissByTap(mContext, topPipActivity);
-            }
+            mPipUiEventLogger.log(PipUiEventLogger.PipUiEventEnum.PICTURE_IN_PICTURE_TAP_TO_REMOVE);
             mTouchState.removeDoubleTapTimeoutCallback();
             mMotionHelper.dismissPip();
         }
@@ -223,7 +217,8 @@
             FloatingContentCoordinator floatingContentCoordinator,
             DeviceConfigProxy deviceConfig,
             PipSnapAlgorithm pipSnapAlgorithm,
-            SysUiState sysUiState) {
+            SysUiState sysUiState,
+            PipUiEventLogger pipUiEventLogger) {
         // Initialize the Pip input consumer
         mContext = context;
         mActivityManager = activityManager;
@@ -237,8 +232,8 @@
                 mSnapAlgorithm, floatingContentCoordinator);
         mPipResizeGestureHandler =
                 new PipResizeGestureHandler(context, pipBoundsHandler, mMotionHelper,
-                        deviceConfig, pipTaskOrganizer, this::getMovementBounds,
-                        this::updateMovementBounds, sysUiState);
+                        deviceConfig, pipTaskOrganizer, menuController, this::getMovementBounds,
+                        this::updateMovementBounds, sysUiState, pipUiEventLogger);
         mTouchState = new PipTouchState(ViewConfiguration.get(context), mHandler,
                 () -> mMenuController.showMenuWithDelay(MENU_STATE_FULL, mMotionHelper.getBounds(),
                         true /* allowMenuTimeout */, willResizeMenu(), shouldShowResizeHandle()),
@@ -259,6 +254,8 @@
                 pipTaskOrganizer, pipSnapAlgorithm, this::onAccessibilityShowMenu,
                 this::updateMovementBounds, mHandler);
 
+        mPipUiEventLogger = pipUiEventLogger;
+
         mTargetView = new DismissCircleView(context);
         mTargetViewContainer = new FrameLayout(context);
         mTargetViewContainer.setBackgroundDrawable(
@@ -303,11 +300,8 @@
                     hideDismissTarget();
                 });
 
-                Pair<ComponentName, Integer> topPipActivity = PipUtils.getTopPipActivity(mContext,
-                        mActivityManager);
-                if (topPipActivity.first != null) {
-                    MetricsLoggerWrapper.logPictureInPictureDismissByDrag(mContext, topPipActivity);
-                }
+                mPipUiEventLogger.log(
+                        PipUiEventLogger.PipUiEventEnum.PICTURE_IN_PICTURE_DRAG_TO_REMOVE);
             }
         });
 
@@ -379,7 +373,6 @@
 
             mFloatingContentCoordinator.onContentRemoved(mMotionHelper);
         }
-        mResizedBounds.setEmpty();
         mPipResizeGestureHandler.onActivityUnpinned();
     }
 
@@ -389,9 +382,8 @@
         mMotionHelper.synchronizePinnedStackBounds();
         updateMovementBounds();
         if (direction == TRANSITION_DIRECTION_TO_PIP) {
-            // updates mResizedBounds only if it's an entering PiP animation
-            // mResized should be otherwise updated in setMenuState.
-            mResizedBounds.set(mMotionHelper.getBounds());
+            // Set the initial bounds as the user resize bounds.
+            mPipResizeGestureHandler.setUserResizeBounds(mMotionHelper.getBounds());
         }
 
         if (mShowPipMenuOnAnimationEnd) {
@@ -430,8 +422,21 @@
         }
     }
 
+    /**
+     * Responds to IPinnedStackListener on resetting aspect ratio for the pinned window.
+     */
+    public void onAspectRatioChanged() {
+        mPipResizeGestureHandler.invalidateUserResizeBounds();
+    }
+
     public void onMovementBoundsChanged(Rect insetBounds, Rect normalBounds, Rect curBounds,
             boolean fromImeAdjustment, boolean fromShelfAdjustment, int displayRotation) {
+        // Set the user resized bounds equal to the new normal bounds in case they were
+        // invalidated (e.g. by an aspect ratio change).
+        if (mPipResizeGestureHandler.getUserResizeBounds().isEmpty()) {
+            mPipResizeGestureHandler.setUserResizeBounds(normalBounds);
+        }
+
         final int bottomOffset = mIsImeShowing ? mImeHeight : 0;
         final boolean fromDisplayRotationChanged = (mDisplayRotation != displayRotation);
         if (fromDisplayRotationChanged) {
@@ -804,9 +809,7 @@
             // Save the current snap fraction and if we do not drag or move the PiP, then
             // we store back to this snap fraction.  Otherwise, we'll reset the snap
             // fraction and snap to the closest edge.
-            // Also save the current resized bounds so when the menu disappears, we can restore it.
             if (resize) {
-                mResizedBounds.set(mMotionHelper.getBounds());
                 Rect expandedBounds = new Rect(mExpandedBounds);
                 mSavedSnapFraction = mMotionHelper.animateToExpandedState(expandedBounds,
                         mMovementBounds, mExpandedMovementBounds, callback);
@@ -835,7 +838,7 @@
                 }
 
                 if (mDeferResizeToNormalBoundsUntilRotation == -1) {
-                    Rect restoreBounds = new Rect(mResizedBounds);
+                    Rect restoreBounds = new Rect(getUserResizeBounds());
                     Rect restoredMovementBounds = new Rect();
                     mSnapAlgorithm.getMovementBounds(restoreBounds, mInsetBounds,
                             restoredMovementBounds, mIsImeShowing ? mImeHeight : 0);
@@ -852,8 +855,10 @@
         // If pip menu has dismissed, we should register the A11y ActionReplacingConnection for pip
         // as well, or it can't handle a11y focus and pip menu can't perform any action.
         onRegistrationChanged(menuState == MENU_STATE_NONE);
-        if (menuState != MENU_STATE_CLOSE) {
-            MetricsLoggerWrapper.logPictureInPictureMenuVisible(mContext, menuState == MENU_STATE_FULL);
+        if (menuState == MENU_STATE_NONE) {
+            mPipUiEventLogger.log(PipUiEventLogger.PipUiEventEnum.PICTURE_IN_PICTURE_HIDE_MENU);
+        } else if (menuState == MENU_STATE_FULL) {
+            mPipUiEventLogger.log(PipUiEventLogger.PipUiEventEnum.PICTURE_IN_PICTURE_SHOW_MENU);
         }
     }
 
@@ -886,6 +891,10 @@
         return mNormalBounds;
     }
 
+    Rect getUserResizeBounds() {
+        return mPipResizeGestureHandler.getUserResizeBounds();
+    }
+
     /**
      * Gesture controlling normal movement of the PIP.
      */
diff --git a/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java b/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java
index 10b04c0..6abbbbe 100644
--- a/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java
+++ b/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java
@@ -24,6 +24,7 @@
 import android.content.BroadcastReceiver;
 import android.content.ContentResolver;
 import android.content.Context;
+import android.content.DialogInterface;
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.media.AudioAttributes;
@@ -376,13 +377,15 @@
             return;
         }
         mHighTempWarning = true;
+        final String message = mContext.getString(R.string.high_temp_notif_message);
         final Notification.Builder nb =
                 new Notification.Builder(mContext, NotificationChannels.ALERTS)
                         .setSmallIcon(R.drawable.ic_device_thermostat_24)
                         .setWhen(0)
                         .setShowWhen(false)
                         .setContentTitle(mContext.getString(R.string.high_temp_title))
-                        .setContentText(mContext.getString(R.string.high_temp_notif_message))
+                        .setContentText(message)
+                        .setStyle(new Notification.BigTextStyle().bigText(message))
                         .setVisibility(Notification.VISIBILITY_PUBLIC)
                         .setContentIntent(pendingBroadcast(ACTION_CLICKED_TEMP_WARNING))
                         .setDeleteIntent(pendingBroadcast(ACTION_DISMISSED_TEMP_WARNING))
@@ -402,6 +405,23 @@
         d.setPositiveButton(com.android.internal.R.string.ok, null);
         d.setShowForAllUsers(true);
         d.setOnDismissListener(dialog -> mHighTempDialog = null);
+        final String url = mContext.getString(R.string.high_temp_dialog_help_url);
+        if (!url.isEmpty()) {
+            d.setNeutralButton(R.string.high_temp_dialog_help_text,
+                    new DialogInterface.OnClickListener() {
+                        @Override
+                        public void onClick(DialogInterface dialog, int which) {
+                            final Intent helpIntent =
+                                    new Intent(Intent.ACTION_VIEW)
+                                            .setData(Uri.parse(url))
+                                            .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+                            Dependency.get(ActivityStarter.class).startActivity(helpIntent,
+                                    true /* dismissShade */, resultCode -> {
+                                        mHighTempDialog = null;
+                                    });
+                        }
+                    });
+        }
         d.show();
         mHighTempDialog = d;
     }
@@ -420,19 +440,38 @@
         d.setPositiveButton(com.android.internal.R.string.ok, null);
         d.setShowForAllUsers(true);
         d.setOnDismissListener(dialog -> mThermalShutdownDialog = null);
+        final String url = mContext.getString(R.string.thermal_shutdown_dialog_help_url);
+        if (!url.isEmpty()) {
+            d.setNeutralButton(R.string.thermal_shutdown_dialog_help_text,
+                    new DialogInterface.OnClickListener() {
+                        @Override
+                        public void onClick(DialogInterface dialog, int which) {
+                            final Intent helpIntent =
+                                    new Intent(Intent.ACTION_VIEW)
+                                            .setData(Uri.parse(url))
+                                            .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+                            Dependency.get(ActivityStarter.class).startActivity(helpIntent,
+                                    true /* dismissShade */, resultCode -> {
+                                        mThermalShutdownDialog = null;
+                                    });
+                        }
+                    });
+        }
         d.show();
         mThermalShutdownDialog = d;
     }
 
     @Override
     public void showThermalShutdownWarning() {
+        final String message = mContext.getString(R.string.thermal_shutdown_message);
         final Notification.Builder nb =
                 new Notification.Builder(mContext, NotificationChannels.ALERTS)
                         .setSmallIcon(R.drawable.ic_device_thermostat_24)
                         .setWhen(0)
                         .setShowWhen(false)
                         .setContentTitle(mContext.getString(R.string.thermal_shutdown_title))
-                        .setContentText(mContext.getString(R.string.thermal_shutdown_message))
+                        .setContentText(message)
+                        .setStyle(new Notification.BigTextStyle().bigText(message))
                         .setVisibility(Notification.VISIBILITY_PUBLIC)
                         .setContentIntent(pendingBroadcast(ACTION_CLICKED_THERMAL_SHUTDOWN_WARNING))
                         .setDeleteIntent(
diff --git a/packages/SystemUI/src/com/android/systemui/privacy/OngoingPrivacyChip.kt b/packages/SystemUI/src/com/android/systemui/privacy/OngoingPrivacyChip.kt
new file mode 100644
index 0000000..48769cd
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/privacy/OngoingPrivacyChip.kt
@@ -0,0 +1,110 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+package com.android.systemui.privacy
+
+import android.content.Context
+import android.util.AttributeSet
+import android.view.Gravity
+import android.view.ViewGroup
+import android.widget.FrameLayout
+import android.widget.ImageView
+import android.widget.LinearLayout
+import com.android.systemui.R
+
+class OngoingPrivacyChip @JvmOverloads constructor(
+    context: Context,
+    attrs: AttributeSet? = null,
+    defStyleAttrs: Int = 0,
+    defStyleRes: Int = 0
+) : FrameLayout(context, attrs, defStyleAttrs, defStyleRes) {
+
+    private val iconMarginExpanded = context.resources.getDimensionPixelSize(
+                    R.dimen.ongoing_appops_chip_icon_margin_expanded)
+    private val iconMarginCollapsed = context.resources.getDimensionPixelSize(
+                    R.dimen.ongoing_appops_chip_icon_margin_collapsed)
+    private val iconSize =
+            context.resources.getDimensionPixelSize(R.dimen.ongoing_appops_chip_icon_size)
+    private val iconColor = context.resources.getColor(
+            R.color.status_bar_clock_color, context.theme)
+    private val sidePadding =
+            context.resources.getDimensionPixelSize(R.dimen.ongoing_appops_chip_side_padding)
+    private val backgroundDrawable = context.getDrawable(R.drawable.privacy_chip_bg)
+    private lateinit var iconsContainer: LinearLayout
+    private lateinit var back: FrameLayout
+    var expanded = false
+        set(value) {
+            if (value != field) {
+                field = value
+                updateView()
+            }
+        }
+
+    var builder = PrivacyChipBuilder(context, emptyList<PrivacyItem>())
+    var privacyList = emptyList<PrivacyItem>()
+        set(value) {
+            field = value
+            builder = PrivacyChipBuilder(context, value)
+            updateView()
+        }
+
+    override fun onFinishInflate() {
+        super.onFinishInflate()
+
+        back = requireViewById(R.id.background)
+        iconsContainer = requireViewById(R.id.icons_container)
+    }
+
+    // Should only be called if the builder icons or app changed
+    private fun updateView() {
+        back.background = if (expanded) backgroundDrawable else null
+        val padding = if (expanded) sidePadding else 0
+        back.setPaddingRelative(padding, 0, padding, 0)
+        fun setIcons(chipBuilder: PrivacyChipBuilder, iconsContainer: ViewGroup) {
+            iconsContainer.removeAllViews()
+            chipBuilder.generateIcons().forEachIndexed { i, it ->
+                it.mutate()
+                it.setTint(iconColor)
+                val image = ImageView(context).apply {
+                    setImageDrawable(it)
+                    scaleType = ImageView.ScaleType.CENTER_INSIDE
+                }
+                iconsContainer.addView(image, iconSize, iconSize)
+                if (i != 0) {
+                    val lp = image.layoutParams as MarginLayoutParams
+                    lp.marginStart = if (expanded) iconMarginExpanded else iconMarginCollapsed
+                    image.layoutParams = lp
+                }
+            }
+        }
+
+        if (!privacyList.isEmpty()) {
+            generateContentDescription()
+            setIcons(builder, iconsContainer)
+            val lp = iconsContainer.layoutParams as FrameLayout.LayoutParams
+            lp.gravity = Gravity.CENTER_VERTICAL or
+                    (if (expanded) Gravity.CENTER_HORIZONTAL else Gravity.END)
+            iconsContainer.layoutParams = lp
+        } else {
+            iconsContainer.removeAllViews()
+        }
+        requestLayout()
+    }
+
+    private fun generateContentDescription() {
+        val typesText = builder.joinTypes()
+        contentDescription = context.getString(
+                R.string.ongoing_privacy_chip_content_multiple_apps, typesText)
+    }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyChipBuilder.kt b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyChipBuilder.kt
new file mode 100644
index 0000000..1d2e747
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyChipBuilder.kt
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+package com.android.systemui.privacy
+
+import android.content.Context
+import com.android.systemui.R
+
+class PrivacyChipBuilder(private val context: Context, itemsList: List<PrivacyItem>) {
+
+    val appsAndTypes: List<Pair<PrivacyApplication, List<PrivacyType>>>
+    val types: List<PrivacyType>
+    private val separator = context.getString(R.string.ongoing_privacy_dialog_separator)
+    private val lastSeparator = context.getString(R.string.ongoing_privacy_dialog_last_separator)
+
+    init {
+        appsAndTypes = itemsList.groupBy({ it.application }, { it.privacyType })
+                .toList()
+                .sortedWith(compareBy({ -it.second.size }, // Sort by number of AppOps
+                        { it.second.min() })) // Sort by "smallest" AppOpp (Location is largest)
+        types = itemsList.map { it.privacyType }.distinct().sorted()
+    }
+
+    fun generateIcons() = types.map { it.getIcon(context) }
+
+    private fun <T> List<T>.joinWithAnd(): StringBuilder {
+        return subList(0, size - 1).joinTo(StringBuilder(), separator = separator).apply {
+            append(lastSeparator)
+            append([email protected]())
+        }
+    }
+
+    fun joinTypes(): String {
+        return when (types.size) {
+            0 -> ""
+            1 -> types[0].getName(context)
+            else -> types.map { it.getName(context) }.joinWithAnd().toString()
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyChipEvent.kt b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyChipEvent.kt
new file mode 100644
index 0000000..1f24fde
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyChipEvent.kt
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.privacy
+
+import com.android.internal.logging.UiEvent
+import com.android.internal.logging.UiEventLogger
+
+enum class PrivacyChipEvent(private val _id: Int) : UiEventLogger.UiEventEnum {
+    @UiEvent(doc = "Privacy chip is viewed by the user. Logged at most once per time QS is visible")
+    ONGOING_INDICATORS_CHIP_VIEW(601),
+
+    @UiEvent(doc = "Privacy chip is clicked")
+    ONGOING_INDICATORS_CHIP_CLICK(602);
+
+    override fun getId() = _id
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyItem.kt b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyItem.kt
new file mode 100644
index 0000000..3da1363
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyItem.kt
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+package com.android.systemui.privacy
+
+import android.content.Context
+import com.android.systemui.R
+
+typealias Privacy = PrivacyType
+
+enum class PrivacyType(val nameId: Int, val iconId: Int) {
+    // This is uses the icons used by the corresponding permission groups in the AndroidManifest
+    TYPE_CAMERA(R.string.privacy_type_camera,
+            com.android.internal.R.drawable.perm_group_camera),
+    TYPE_MICROPHONE(R.string.privacy_type_microphone,
+            com.android.internal.R.drawable.perm_group_microphone),
+    TYPE_LOCATION(R.string.privacy_type_location,
+            com.android.internal.R.drawable.perm_group_location);
+
+    fun getName(context: Context) = context.resources.getString(nameId)
+
+    fun getIcon(context: Context) = context.resources.getDrawable(iconId, context.theme)
+}
+
+data class PrivacyItem(val privacyType: PrivacyType, val application: PrivacyApplication)
+
+data class PrivacyApplication(val packageName: String, val uid: Int)
diff --git a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyItemController.kt b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyItemController.kt
new file mode 100644
index 0000000..59118bf
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyItemController.kt
@@ -0,0 +1,337 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.privacy
+
+import android.app.ActivityManager
+import android.app.AppOpsManager
+import android.content.BroadcastReceiver
+import android.content.Context
+import android.content.Intent
+import android.content.IntentFilter
+import android.os.UserHandle
+import android.os.UserManager
+import android.provider.DeviceConfig
+import com.android.internal.annotations.VisibleForTesting
+import com.android.internal.config.sysui.SystemUiDeviceConfigFlags
+import com.android.systemui.Dumpable
+import com.android.systemui.appops.AppOpItem
+import com.android.systemui.appops.AppOpsController
+import com.android.systemui.broadcast.BroadcastDispatcher
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.dump.DumpManager
+import com.android.systemui.util.DeviceConfigProxy
+import com.android.systemui.util.concurrency.DelayableExecutor
+import java.io.FileDescriptor
+import java.io.PrintWriter
+import java.lang.ref.WeakReference
+import java.util.concurrent.Executor
+import javax.inject.Inject
+import javax.inject.Singleton
+
+@Singleton
+class PrivacyItemController @Inject constructor(
+    private val appOpsController: AppOpsController,
+    @Main uiExecutor: DelayableExecutor,
+    @Background private val bgExecutor: Executor,
+    private val broadcastDispatcher: BroadcastDispatcher,
+    private val deviceConfigProxy: DeviceConfigProxy,
+    private val userManager: UserManager,
+    dumpManager: DumpManager
+) : Dumpable {
+
+    @VisibleForTesting
+    internal companion object {
+        val OPS_MIC_CAMERA = intArrayOf(AppOpsManager.OP_CAMERA,
+                AppOpsManager.OP_PHONE_CALL_CAMERA, AppOpsManager.OP_RECORD_AUDIO,
+                AppOpsManager.OP_PHONE_CALL_MICROPHONE)
+        val OPS_LOCATION = intArrayOf(
+                AppOpsManager.OP_COARSE_LOCATION,
+                AppOpsManager.OP_FINE_LOCATION)
+        val OPS = OPS_MIC_CAMERA + OPS_LOCATION
+        val intentFilter = IntentFilter().apply {
+            addAction(Intent.ACTION_USER_SWITCHED)
+            addAction(Intent.ACTION_MANAGED_PROFILE_AVAILABLE)
+            addAction(Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE)
+        }
+        const val TAG = "PrivacyItemController"
+        private const val ALL_INDICATORS =
+                SystemUiDeviceConfigFlags.PROPERTY_PERMISSIONS_HUB_ENABLED
+        private const val MIC_CAMERA = SystemUiDeviceConfigFlags.PROPERTY_MIC_CAMERA_ENABLED
+    }
+
+    @VisibleForTesting
+    internal var privacyList = emptyList<PrivacyItem>()
+        @Synchronized get() = field.toList() // Returns a shallow copy of the list
+        @Synchronized set
+
+    fun isAllIndicatorsEnabled(): Boolean {
+        return deviceConfigProxy.getBoolean(DeviceConfig.NAMESPACE_PRIVACY,
+                ALL_INDICATORS, false)
+    }
+
+    private fun isMicCameraEnabled(): Boolean {
+        return deviceConfigProxy.getBoolean(DeviceConfig.NAMESPACE_PRIVACY,
+                MIC_CAMERA, false)
+    }
+
+    private var currentUserIds = emptyList<Int>()
+    private var listening = false
+    private val callbacks = mutableListOf<WeakReference<Callback>>()
+    private val internalUiExecutor = MyExecutor(WeakReference(this), uiExecutor)
+
+    private val notifyChanges = Runnable {
+        val list = privacyList
+        callbacks.forEach { it.get()?.onPrivacyItemsChanged(list) }
+    }
+
+    private val updateListAndNotifyChanges = Runnable {
+        updatePrivacyList()
+        uiExecutor.execute(notifyChanges)
+    }
+
+    var allIndicatorsAvailable = isAllIndicatorsEnabled()
+        private set
+    var micCameraAvailable = isMicCameraEnabled()
+        private set
+
+    private val devicePropertiesChangedListener =
+            object : DeviceConfig.OnPropertiesChangedListener {
+        override fun onPropertiesChanged(properties: DeviceConfig.Properties) {
+                if (DeviceConfig.NAMESPACE_PRIVACY.equals(properties.getNamespace()) &&
+                        (properties.keyset.contains(ALL_INDICATORS) ||
+                                properties.keyset.contains(MIC_CAMERA))) {
+
+                    // Running on the ui executor so can iterate on callbacks
+                    if (properties.keyset.contains(ALL_INDICATORS)) {
+                        allIndicatorsAvailable = properties.getBoolean(ALL_INDICATORS, false)
+                        callbacks.forEach { it.get()?.onFlagAllChanged(allIndicatorsAvailable) }
+                    }
+
+                    if (properties.keyset.contains(MIC_CAMERA)) {
+                        micCameraAvailable = properties.getBoolean(MIC_CAMERA, false)
+                        callbacks.forEach { it.get()?.onFlagMicCameraChanged(micCameraAvailable) }
+                    }
+                    internalUiExecutor.updateListeningState()
+                }
+            }
+        }
+
+    private val cb = object : AppOpsController.Callback {
+        override fun onActiveStateChanged(
+            code: Int,
+            uid: Int,
+            packageName: String,
+            active: Boolean
+        ) {
+            // Check if we care about this code right now
+            if (!allIndicatorsAvailable && code in OPS_LOCATION) {
+                return
+            }
+            val userId = UserHandle.getUserId(uid)
+            if (userId in currentUserIds) {
+                update(false)
+            }
+        }
+    }
+
+    @VisibleForTesting
+    internal var userSwitcherReceiver = Receiver()
+        set(value) {
+            unregisterReceiver()
+            field = value
+            if (listening) registerReceiver()
+        }
+
+    init {
+        deviceConfigProxy.addOnPropertiesChangedListener(
+                DeviceConfig.NAMESPACE_PRIVACY,
+                uiExecutor,
+                devicePropertiesChangedListener)
+        dumpManager.registerDumpable(TAG, this)
+    }
+
+    private fun unregisterReceiver() {
+        broadcastDispatcher.unregisterReceiver(userSwitcherReceiver)
+    }
+
+    private fun registerReceiver() {
+        broadcastDispatcher.registerReceiver(userSwitcherReceiver, intentFilter,
+                null /* handler */, UserHandle.ALL)
+    }
+
+    private fun update(updateUsers: Boolean) {
+        bgExecutor.execute {
+            if (updateUsers) {
+                val currentUser = ActivityManager.getCurrentUser()
+                currentUserIds = userManager.getProfiles(currentUser).map { it.id }
+            }
+            updateListAndNotifyChanges.run()
+        }
+    }
+
+    /**
+     * Updates listening status based on whether there are callbacks and the indicators are enabled.
+     *
+     * Always listen to all OPS so we don't have to figure out what we should be listening to. We
+     * still have to filter anyway. Updates are filtered in the callback.
+     *
+     * This is only called from private (add/remove)Callback and from the config listener, all in
+     * main thread.
+     */
+    private fun setListeningState() {
+        val listen = !callbacks.isEmpty() and (allIndicatorsAvailable || micCameraAvailable)
+        if (listening == listen) return
+        listening = listen
+        if (listening) {
+            appOpsController.addCallback(OPS, cb)
+            registerReceiver()
+            update(true)
+        } else {
+            appOpsController.removeCallback(OPS, cb)
+            unregisterReceiver()
+            // Make sure that we remove all indicators and notify listeners if we are not
+            // listening anymore due to indicators being disabled
+            update(false)
+        }
+    }
+
+    private fun addCallback(callback: WeakReference<Callback>) {
+        callbacks.add(callback)
+        if (callbacks.isNotEmpty() && !listening) {
+            internalUiExecutor.updateListeningState()
+        }
+        // Notify this callback if we didn't set to listening
+        else if (listening) {
+            internalUiExecutor.execute(NotifyChangesToCallback(callback.get(), privacyList))
+        }
+    }
+
+    private fun removeCallback(callback: WeakReference<Callback>) {
+        // Removes also if the callback is null
+        callbacks.removeIf { it.get()?.equals(callback.get()) ?: true }
+        if (callbacks.isEmpty()) {
+            internalUiExecutor.updateListeningState()
+        }
+    }
+
+    fun addCallback(callback: Callback) {
+        internalUiExecutor.addCallback(callback)
+    }
+
+    fun removeCallback(callback: Callback) {
+        internalUiExecutor.removeCallback(callback)
+    }
+
+    private fun updatePrivacyList() {
+        if (!listening) {
+            privacyList = emptyList()
+            return
+        }
+        val list = currentUserIds.flatMap { appOpsController.getActiveAppOpsForUser(it) }
+                .mapNotNull { toPrivacyItem(it) }.distinct()
+        privacyList = list
+    }
+
+    private fun toPrivacyItem(appOpItem: AppOpItem): PrivacyItem? {
+        val type: PrivacyType = when (appOpItem.code) {
+            AppOpsManager.OP_PHONE_CALL_CAMERA,
+            AppOpsManager.OP_CAMERA -> PrivacyType.TYPE_CAMERA
+            AppOpsManager.OP_COARSE_LOCATION,
+            AppOpsManager.OP_FINE_LOCATION -> PrivacyType.TYPE_LOCATION
+            AppOpsManager.OP_PHONE_CALL_MICROPHONE,
+            AppOpsManager.OP_RECORD_AUDIO -> PrivacyType.TYPE_MICROPHONE
+            else -> return null
+        }
+        if (type == PrivacyType.TYPE_LOCATION && !allIndicatorsAvailable) return null
+        val app = PrivacyApplication(appOpItem.packageName, appOpItem.uid)
+        return PrivacyItem(type, app)
+    }
+
+    // Used by containing class to get notified of changes
+    interface Callback {
+        fun onPrivacyItemsChanged(privacyItems: List<PrivacyItem>)
+
+        @JvmDefault
+        fun onFlagAllChanged(flag: Boolean) {}
+
+        @JvmDefault
+        fun onFlagMicCameraChanged(flag: Boolean) {}
+    }
+
+    internal inner class Receiver : BroadcastReceiver() {
+        override fun onReceive(context: Context, intent: Intent) {
+            if (intentFilter.hasAction(intent.action)) {
+                update(true)
+            }
+        }
+    }
+
+    private class NotifyChangesToCallback(
+        private val callback: Callback?,
+        private val list: List<PrivacyItem>
+    ) : Runnable {
+        override fun run() {
+            callback?.onPrivacyItemsChanged(list)
+        }
+    }
+
+    override fun dump(fd: FileDescriptor, pw: PrintWriter, args: Array<out String>) {
+        pw.println("PrivacyItemController state:")
+        pw.println("  Listening: $listening")
+        pw.println("  Current user ids: $currentUserIds")
+        pw.println("  Privacy Items:")
+        privacyList.forEach {
+            pw.print("    ")
+            pw.println(it.toString())
+        }
+        pw.println("  Callbacks:")
+        callbacks.forEach {
+            it.get()?.let {
+                pw.print("    ")
+                pw.println(it.toString())
+            }
+        }
+    }
+
+    private class MyExecutor(
+        private val outerClass: WeakReference<PrivacyItemController>,
+        private val delegate: DelayableExecutor
+    ) : Executor {
+
+        private var listeningCanceller: Runnable? = null
+
+        override fun execute(command: Runnable) {
+            delegate.execute(command)
+        }
+
+        fun updateListeningState() {
+            listeningCanceller?.run()
+            listeningCanceller = delegate.executeDelayed({
+                outerClass.get()?.setListeningState()
+            }, 0L)
+        }
+
+        fun addCallback(callback: Callback) {
+            outerClass.get()?.addCallback(WeakReference(callback))
+        }
+
+        fun removeCallback(callback: Callback) {
+            outerClass.get()?.removeCallback(WeakReference(callback))
+        }
+    }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java b/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java
index 3eed8ad..44803ae 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java
@@ -177,6 +177,16 @@
     }
 
     @Override
+    public void endFakeDrag() {
+        try {
+            super.endFakeDrag();
+        } catch (NullPointerException e) {
+            // Not sure what's going on. Let's log it
+            Log.e(TAG, "endFakeDrag called without velocityTracker", e);
+        }
+    }
+
+    @Override
     public void computeScroll() {
         if (!mScroller.isFinished() && mScroller.computeScrollOffset()) {
             if (!isFakeDragging()) {
@@ -185,7 +195,9 @@
             fakeDragBy(getScrollX() - mScroller.getCurrX());
         } else if (isFakeDragging()) {
             endFakeDrag();
-            mBounceAnimatorSet.start();
+            if (mBounceAnimatorSet != null) {
+                mBounceAnimatorSet.start();
+            }
             setOffscreenPageLimit(1);
         }
         super.computeScroll();
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFooterImpl.java b/packages/SystemUI/src/com/android/systemui/qs/QSFooterImpl.java
index c4bb4e8..6e4ab9a 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSFooterImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSFooterImpl.java
@@ -78,6 +78,8 @@
     private SettingsButton mSettingsButton;
     protected View mSettingsContainer;
     private PageIndicator mPageIndicator;
+    private TextView mBuildText;
+    private boolean mShouldShowBuildText;
 
     private boolean mQsDisabled;
     private QSPanel mQsPanel;
@@ -147,6 +149,7 @@
 
         mActionsContainer = findViewById(R.id.qs_footer_actions_container);
         mEditContainer = findViewById(R.id.qs_footer_actions_edit_container);
+        mBuildText = findViewById(R.id.build);
 
         // RenderThread is doing more harm than good when touching the header (to expand quick
         // settings), so disable it for this view
@@ -162,16 +165,19 @@
     }
 
     private void setBuildText() {
-        TextView v = findViewById(R.id.build);
-        if (v == null) return;
+        if (mBuildText == null) return;
         if (DevelopmentSettingsEnabler.isDevelopmentSettingsEnabled(mContext)) {
-            v.setText(mContext.getString(
+            mBuildText.setText(mContext.getString(
                     com.android.internal.R.string.bugreport_status,
                     Build.VERSION.RELEASE_OR_CODENAME,
                     Build.ID));
-            v.setVisibility(View.VISIBLE);
+            // Set as selected for marquee before its made visible, then it won't be announced when
+            // it's made visible.
+            mBuildText.setSelected(true);
+            mShouldShowBuildText = true;
         } else {
-            v.setVisibility(View.GONE);
+            mShouldShowBuildText = false;
+            mBuildText.setSelected(false);
         }
     }
 
@@ -321,6 +327,8 @@
         mMultiUserSwitch.setVisibility(showUserSwitcher() ? View.VISIBLE : View.INVISIBLE);
         mEditContainer.setVisibility(isDemo || !mExpanded ? View.INVISIBLE : View.VISIBLE);
         mSettingsButton.setVisibility(isDemo || !mExpanded ? View.INVISIBLE : View.VISIBLE);
+
+        mBuildText.setVisibility(mExpanded && mShouldShowBuildText ? View.VISIBLE : View.GONE);
     }
 
     private boolean showUserSwitcher() {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
index b07b1a9..58c723c 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
@@ -31,6 +31,7 @@
 import android.graphics.Rect;
 import android.media.AudioManager;
 import android.os.Handler;
+import android.os.Looper;
 import android.provider.AlarmClock;
 import android.provider.Settings;
 import android.service.notification.ZenModeConfig;
@@ -46,7 +47,9 @@
 import android.view.WindowInsets;
 import android.widget.FrameLayout;
 import android.widget.ImageView;
+import android.widget.LinearLayout;
 import android.widget.RelativeLayout;
+import android.widget.Space;
 import android.widget.TextView;
 
 import androidx.annotation.NonNull;
@@ -55,6 +58,7 @@
 import androidx.lifecycle.LifecycleOwner;
 import androidx.lifecycle.LifecycleRegistry;
 
+import com.android.internal.logging.UiEventLogger;
 import com.android.settingslib.Utils;
 import com.android.systemui.BatteryMeterView;
 import com.android.systemui.DualToneHandler;
@@ -63,6 +67,11 @@
 import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.plugins.DarkIconDispatcher;
 import com.android.systemui.plugins.DarkIconDispatcher.DarkReceiver;
+import com.android.systemui.privacy.OngoingPrivacyChip;
+import com.android.systemui.privacy.PrivacyChipBuilder;
+import com.android.systemui.privacy.PrivacyChipEvent;
+import com.android.systemui.privacy.PrivacyItem;
+import com.android.systemui.privacy.PrivacyItemController;
 import com.android.systemui.qs.QSDetail.Callback;
 import com.android.systemui.qs.carrier.QSCarrierGroup;
 import com.android.systemui.statusbar.CommandQueue;
@@ -101,7 +110,6 @@
     private static final int TOOLTIP_NOT_YET_SHOWN_COUNT = 0;
     public static final int MAX_TOOLTIP_SHOWN_COUNT = 2;
 
-    private final Handler mHandler = new Handler();
     private final NextAlarmController mAlarmController;
     private final ZenModeController mZenController;
     private final StatusBarIconController mStatusBarIconController;
@@ -140,9 +148,15 @@
     private View mRingerContainer;
     private Clock mClockView;
     private DateView mDateView;
+    private OngoingPrivacyChip mPrivacyChip;
+    private Space mSpace;
     private BatteryMeterView mBatteryRemainingIcon;
     private RingerModeTracker mRingerModeTracker;
+    private boolean mAllIndicatorsEnabled;
+    private boolean mMicCameraIndicatorsEnabled;
 
+    private PrivacyItemController mPrivacyItemController;
+    private final UiEventLogger mUiEventLogger;
     // Used for RingerModeTracker
     private final LifecycleRegistry mLifecycle = new LifecycleRegistry(this);
 
@@ -156,22 +170,56 @@
     private int mCutOutPaddingRight;
     private float mExpandedHeaderAlpha = 1.0f;
     private float mKeyguardExpansionFraction;
+    private boolean mPrivacyChipLogged = false;
+
+    private PrivacyItemController.Callback mPICCallback = new PrivacyItemController.Callback() {
+        @Override
+        public void onPrivacyItemsChanged(List<PrivacyItem> privacyItems) {
+            mPrivacyChip.setPrivacyList(privacyItems);
+            setChipVisibility(!privacyItems.isEmpty());
+        }
+
+        @Override
+        public void onFlagAllChanged(boolean flag) {
+            if (mAllIndicatorsEnabled != flag) {
+                mAllIndicatorsEnabled = flag;
+                update();
+            }
+        }
+
+        @Override
+        public void onFlagMicCameraChanged(boolean flag) {
+            if (mMicCameraIndicatorsEnabled != flag) {
+                mMicCameraIndicatorsEnabled = flag;
+                update();
+            }
+        }
+
+        private void update() {
+            StatusIconContainer iconContainer = requireViewById(R.id.statusIcons);
+            iconContainer.setIgnoredSlots(getIgnoredIconSlots());
+            setChipVisibility(!mPrivacyChip.getPrivacyList().isEmpty());
+        }
+    };
 
     @Inject
     public QuickStatusBarHeader(@Named(VIEW_CONTEXT) Context context, AttributeSet attrs,
             NextAlarmController nextAlarmController, ZenModeController zenModeController,
             StatusBarIconController statusBarIconController,
-            ActivityStarter activityStarter,
-            CommandQueue commandQueue, RingerModeTracker ringerModeTracker) {
+            ActivityStarter activityStarter, PrivacyItemController privacyItemController,
+            CommandQueue commandQueue, RingerModeTracker ringerModeTracker,
+            UiEventLogger uiEventLogger) {
         super(context, attrs);
         mAlarmController = nextAlarmController;
         mZenController = zenModeController;
         mStatusBarIconController = statusBarIconController;
         mActivityStarter = activityStarter;
+        mPrivacyItemController = privacyItemController;
         mDualToneHandler = new DualToneHandler(
                 new ContextThemeWrapper(context, R.style.QSHeaderTheme));
         mCommandQueue = commandQueue;
         mRingerModeTracker = ringerModeTracker;
+        mUiEventLogger = uiEventLogger;
     }
 
     @Override
@@ -198,8 +246,11 @@
         mRingerModeTextView = findViewById(R.id.ringer_mode_text);
         mRingerContainer = findViewById(R.id.ringer_container);
         mRingerContainer.setOnClickListener(this::onClick);
+        mPrivacyChip = findViewById(R.id.privacy_chip);
+        mPrivacyChip.setOnClickListener(this::onClick);
         mCarrierGroup = findViewById(R.id.carrier_group);
 
+
         updateResources();
 
         Rect tintArea = new Rect(0, 0, 0, 0);
@@ -219,6 +270,7 @@
         mClockView = findViewById(R.id.clock);
         mClockView.setOnClickListener(this);
         mDateView = findViewById(R.id.date);
+        mSpace = findViewById(R.id.space);
 
         // Tint for the battery icons are handled in setupHost()
         mBatteryRemainingIcon = findViewById(R.id.batteryRemainingIcon);
@@ -229,6 +281,9 @@
         mBatteryRemainingIcon.setPercentShowMode(BatteryMeterView.MODE_ESTIMATE);
         mRingerModeTextView.setSelected(true);
         mNextAlarmTextView.setSelected(true);
+
+        mAllIndicatorsEnabled = mPrivacyItemController.getAllIndicatorsAvailable();
+        mMicCameraIndicatorsEnabled = mPrivacyItemController.getMicCameraAvailable();
     }
 
     public QuickQSPanel getHeaderQsPanel() {
@@ -237,10 +292,16 @@
 
     private List<String> getIgnoredIconSlots() {
         ArrayList<String> ignored = new ArrayList<>();
-        ignored.add(mContext.getResources().getString(
-                com.android.internal.R.string.status_bar_camera));
-        ignored.add(mContext.getResources().getString(
-                com.android.internal.R.string.status_bar_microphone));
+        if (getChipEnabled()) {
+            ignored.add(mContext.getResources().getString(
+                    com.android.internal.R.string.status_bar_camera));
+            ignored.add(mContext.getResources().getString(
+                    com.android.internal.R.string.status_bar_microphone));
+            if (mAllIndicatorsEnabled) {
+                ignored.add(mContext.getResources().getString(
+                        com.android.internal.R.string.status_bar_location));
+            }
+        }
 
         return ignored;
     }
@@ -256,6 +317,20 @@
         }
     }
 
+    private void setChipVisibility(boolean chipVisible) {
+        if (chipVisible && getChipEnabled()) {
+            mPrivacyChip.setVisibility(View.VISIBLE);
+            // Makes sure that the chip is logged as viewed at most once each time QS is opened
+            // mListening makes sure that the callback didn't return after the user closed QS
+            if (!mPrivacyChipLogged && mListening) {
+                mPrivacyChipLogged = true;
+                mUiEventLogger.log(PrivacyChipEvent.ONGOING_INDICATORS_CHIP_VIEW);
+            }
+        } else {
+            mPrivacyChip.setVisibility(View.GONE);
+        }
+    }
+
     private boolean updateRingerStatus() {
         boolean isOriginalVisible = mRingerModeTextView.getVisibility() == View.VISIBLE;
         CharSequence originalRingerText = mRingerModeTextView.getText();
@@ -363,6 +438,7 @@
 
         updateStatusIconAlphaAnimator();
         updateHeaderTextContainerAlphaAnimator();
+        updatePrivacyChipAlphaAnimator();
     }
 
     private void updateStatusIconAlphaAnimator() {
@@ -377,6 +453,12 @@
                 .build();
     }
 
+    private void updatePrivacyChipAlphaAnimator() {
+        mPrivacyChipAlphaAnimator = new TouchAnimator.Builder()
+                .addFloat(mPrivacyChip, "alpha", 1, 0, 1)
+                .build();
+    }
+
     public void setExpanded(boolean expanded) {
         if (mExpanded == expanded) return;
         mExpanded = expanded;
@@ -415,6 +497,10 @@
                 mHeaderTextContainerView.setVisibility(INVISIBLE);
             }
         }
+        if (mPrivacyChipAlphaAnimator != null) {
+            mPrivacyChip.setExpanded(expansionFraction > 0.5);
+            mPrivacyChipAlphaAnimator.setPosition(keyguardExpansionFraction);
+        }
         if (expansionFraction < 1 && expansionFraction > 0.99) {
             if (mHeaderQsPanel.switchTileLayout()) {
                 updateResources();
@@ -453,6 +539,31 @@
         Pair<Integer, Integer> padding =
                 StatusBarWindowView.paddingNeededForCutoutAndRoundedCorner(
                         cutout, cornerCutoutPadding, -1);
+        if (padding == null) {
+            mSystemIconsView.setPaddingRelative(
+                    getResources().getDimensionPixelSize(R.dimen.status_bar_padding_start), 0,
+                    getResources().getDimensionPixelSize(R.dimen.status_bar_padding_end), 0);
+        } else {
+            mSystemIconsView.setPadding(padding.first, 0, padding.second, 0);
+
+        }
+        LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) mSpace.getLayoutParams();
+        boolean cornerCutout = cornerCutoutPadding != null
+                && (cornerCutoutPadding.first == 0 || cornerCutoutPadding.second == 0);
+        if (cutout != null) {
+            Rect topCutout = cutout.getBoundingRectTop();
+            if (topCutout.isEmpty() || cornerCutout) {
+                mHasTopCutout = false;
+                lp.width = 0;
+                mSpace.setVisibility(View.GONE);
+            } else {
+                mHasTopCutout = true;
+                lp.width = topCutout.width();
+                mSpace.setVisibility(View.VISIBLE);
+            }
+        }
+        mSpace.setLayoutParams(lp);
+        setChipVisibility(mPrivacyChip.getVisibility() == View.VISIBLE);
         mCutOutPaddingLeft = padding.first;
         mCutOutPaddingRight = padding.second;
         mWaterfallTopInset = cutout == null ? 0 : cutout.getWaterfallInsets().top;
@@ -513,10 +624,16 @@
             mZenController.addCallback(this);
             mAlarmController.addCallback(this);
             mLifecycle.setCurrentState(Lifecycle.State.RESUMED);
+            // Get the most up to date info
+            mAllIndicatorsEnabled = mPrivacyItemController.getAllIndicatorsAvailable();
+            mMicCameraIndicatorsEnabled = mPrivacyItemController.getMicCameraAvailable();
+            mPrivacyItemController.addCallback(mPICCallback);
         } else {
             mZenController.removeCallback(this);
             mAlarmController.removeCallback(this);
             mLifecycle.setCurrentState(Lifecycle.State.CREATED);
+            mPrivacyItemController.removeCallback(mPICCallback);
+            mPrivacyChipLogged = false;
         }
     }
 
@@ -534,6 +651,17 @@
                 mActivityStarter.postStartActivityDismissingKeyguard(new Intent(
                         AlarmClock.ACTION_SHOW_ALARMS), 0);
             }
+        } else if (v == mPrivacyChip) {
+            // Makes sure that the builder is grabbed as soon as the chip is pressed
+            PrivacyChipBuilder builder = mPrivacyChip.getBuilder();
+            if (builder.getAppsAndTypes().size() == 0) return;
+            Handler mUiHandler = new Handler(Looper.getMainLooper());
+            mUiEventLogger.log(PrivacyChipEvent.ONGOING_INDICATORS_CHIP_CLICK);
+            mUiHandler.post(() -> {
+                mActivityStarter.postStartActivityDismissingKeyguard(
+                        new Intent(Intent.ACTION_REVIEW_ONGOING_PERMISSION_USAGE), 0);
+                mHost.collapsePanels();
+            });
         } else if (v == mRingerContainer && mRingerContainer.isVisibleToUser()) {
             mActivityStarter.postStartActivityDismissingKeyguard(new Intent(
                     Settings.ACTION_SOUND_SETTINGS), 0);
@@ -640,4 +768,8 @@
             updateHeaderTextContainerAlphaAnimator();
         }
     }
+
+    private boolean getChipEnabled() {
+        return mMicCameraIndicatorsEnabled || mAllIndicatorsEnabled;
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java b/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java
index e738cec..bffeb3e 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java
@@ -14,11 +14,8 @@
 
 package com.android.systemui.qs.customize;
 
-import android.app.AlertDialog;
-import android.app.AlertDialog.Builder;
 import android.content.ComponentName;
 import android.content.Context;
-import android.content.DialogInterface;
 import android.content.res.Resources;
 import android.graphics.Canvas;
 import android.graphics.drawable.Drawable;
@@ -28,10 +25,11 @@
 import android.view.View.OnClickListener;
 import android.view.View.OnLayoutChangeListener;
 import android.view.ViewGroup;
-import android.view.accessibility.AccessibilityManager;
 import android.widget.FrameLayout;
 import android.widget.TextView;
 
+import androidx.annotation.NonNull;
+import androidx.core.view.AccessibilityDelegateCompat;
 import androidx.core.view.ViewCompat;
 import androidx.recyclerview.widget.GridLayoutManager.SpanSizeLookup;
 import androidx.recyclerview.widget.ItemTouchHelper;
@@ -49,7 +47,6 @@
 import com.android.systemui.qs.customize.TileQueryHelper.TileStateListener;
 import com.android.systemui.qs.external.CustomTile;
 import com.android.systemui.qs.tileimpl.QSIconViewImpl;
-import com.android.systemui.statusbar.phone.SystemUIDialog;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -78,10 +75,10 @@
     private final List<TileInfo> mTiles = new ArrayList<>();
     private final ItemTouchHelper mItemTouchHelper;
     private final ItemDecoration mDecoration;
-    private final AccessibilityManager mAccessibilityManager;
     private final int mMinNumTiles;
     private int mEditIndex;
     private int mTileDividerIndex;
+    private int mFocusIndex;
     private boolean mNeedsFocus;
     private List<String> mCurrentSpecs;
     private List<TileInfo> mOtherTiles;
@@ -90,17 +87,28 @@
     private Holder mCurrentDrag;
     private int mAccessibilityAction = ACTION_NONE;
     private int mAccessibilityFromIndex;
-    private CharSequence mAccessibilityFromLabel;
     private QSTileHost mHost;
     private final UiEventLogger mUiEventLogger;
+    private final AccessibilityDelegateCompat mAccessibilityDelegate;
+    private RecyclerView mRecyclerView;
 
     public TileAdapter(Context context, UiEventLogger uiEventLogger) {
         mContext = context;
         mUiEventLogger = uiEventLogger;
-        mAccessibilityManager = context.getSystemService(AccessibilityManager.class);
         mItemTouchHelper = new ItemTouchHelper(mCallbacks);
         mDecoration = new TileItemDecoration(context);
         mMinNumTiles = context.getResources().getInteger(R.integer.quick_settings_min_num_tiles);
+        mAccessibilityDelegate = new TileAdapterDelegate();
+    }
+
+    @Override
+    public void onAttachedToRecyclerView(@NonNull RecyclerView recyclerView) {
+        mRecyclerView = recyclerView;
+    }
+
+    @Override
+    public void onDetachedFromRecyclerView(@NonNull RecyclerView recyclerView) {
+        mRecyclerView = null;
     }
 
     public void setHost(QSTileHost host) {
@@ -130,7 +138,6 @@
             // Remove blank tile from last spot
             mTiles.remove(--mEditIndex);
             // Update the tile divider position
-            mTileDividerIndex--;
             notifyDataSetChanged();
         }
         mAccessibilityAction = ACTION_NONE;
@@ -241,14 +248,12 @@
     }
 
     private void setSelectableForHeaders(View view) {
-        if (mAccessibilityManager.isTouchExplorationEnabled()) {
-            final boolean selectable = mAccessibilityAction == ACTION_NONE;
-            view.setFocusable(selectable);
-            view.setImportantForAccessibility(selectable
-                    ? View.IMPORTANT_FOR_ACCESSIBILITY_YES
-                    : View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS);
-            view.setFocusableInTouchMode(selectable);
-        }
+        final boolean selectable = mAccessibilityAction == ACTION_NONE;
+        view.setFocusable(selectable);
+        view.setImportantForAccessibility(selectable
+                ? View.IMPORTANT_FOR_ACCESSIBILITY_YES
+                : View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS);
+        view.setFocusableInTouchMode(selectable);
     }
 
     @Override
@@ -285,12 +290,11 @@
             holder.mTileView.setVisibility(View.VISIBLE);
             holder.mTileView.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES);
             holder.mTileView.setContentDescription(mContext.getString(
-                    R.string.accessibility_qs_edit_tile_add, mAccessibilityFromLabel,
-                    position));
+                    R.string.accessibility_qs_edit_tile_add_to_position, position));
             holder.mTileView.setOnClickListener(new OnClickListener() {
                 @Override
                 public void onClick(View v) {
-                    selectPosition(holder.getAdapterPosition(), v);
+                    selectPosition(holder.getLayoutPosition());
                 }
             });
             focusOnHolder(holder);
@@ -299,54 +303,49 @@
 
         TileInfo info = mTiles.get(position);
 
-        if (position > mEditIndex) {
+        final boolean selectable = 0 < position && position < mEditIndex;
+        if (selectable && mAccessibilityAction == ACTION_ADD) {
             info.state.contentDescription = mContext.getString(
-                    R.string.accessibility_qs_edit_add_tile_label, info.state.label);
-        } else if (mAccessibilityAction == ACTION_ADD) {
+                    R.string.accessibility_qs_edit_tile_add_to_position, position);
+        } else if (selectable && mAccessibilityAction == ACTION_MOVE) {
             info.state.contentDescription = mContext.getString(
-                    R.string.accessibility_qs_edit_tile_add, mAccessibilityFromLabel, position);
-        } else if (mAccessibilityAction == ACTION_MOVE) {
-            info.state.contentDescription = mContext.getString(
-                    R.string.accessibility_qs_edit_tile_move, mAccessibilityFromLabel, position);
+                    R.string.accessibility_qs_edit_tile_move_to_position, position);
         } else {
-            info.state.contentDescription = mContext.getString(
-                    R.string.accessibility_qs_edit_tile_label, position, info.state.label);
+            info.state.contentDescription = info.state.label;
         }
+        info.state.expandedAccessibilityClassName = "";
+
         holder.mTileView.handleStateChanged(info.state);
         holder.mTileView.setShowAppLabel(position > mEditIndex && !info.isSystem);
+        holder.mTileView.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES);
+        holder.mTileView.setClickable(true);
+        holder.mTileView.setOnClickListener(null);
+        holder.mTileView.setFocusable(true);
+        holder.mTileView.setFocusableInTouchMode(true);
 
-        if (mAccessibilityManager.isTouchExplorationEnabled()) {
-            final boolean selectable = mAccessibilityAction == ACTION_NONE || position < mEditIndex;
+        if (mAccessibilityAction != ACTION_NONE) {
             holder.mTileView.setClickable(selectable);
             holder.mTileView.setFocusable(selectable);
+            holder.mTileView.setFocusableInTouchMode(selectable);
             holder.mTileView.setImportantForAccessibility(selectable
                     ? View.IMPORTANT_FOR_ACCESSIBILITY_YES
                     : View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS);
-            holder.mTileView.setFocusableInTouchMode(selectable);
             if (selectable) {
                 holder.mTileView.setOnClickListener(new OnClickListener() {
                     @Override
                     public void onClick(View v) {
-                        int position = holder.getAdapterPosition();
+                        int position = holder.getLayoutPosition();
                         if (position == RecyclerView.NO_POSITION) return;
                         if (mAccessibilityAction != ACTION_NONE) {
-                            selectPosition(position, v);
-                        } else {
-                            if (position < mEditIndex && canRemoveTiles()) {
-                                showAccessibilityDialog(position, v);
-                            } else if (position < mEditIndex && !canRemoveTiles()) {
-                                startAccessibleMove(position);
-                            } else {
-                                startAccessibleAdd(position);
-                            }
+                            selectPosition(position);
                         }
                     }
                 });
-                if (position == mAccessibilityFromIndex) {
-                    focusOnHolder(holder);
-                }
             }
         }
+        if (position == mFocusIndex) {
+            focusOnHolder(holder);
+        }
     }
 
     private void focusOnHolder(Holder holder) {
@@ -360,9 +359,13 @@
                         int oldLeft, int oldTop, int oldRight, int oldBottom) {
                     holder.mTileView.removeOnLayoutChangeListener(this);
                     holder.mTileView.requestFocus();
+                    if (mAccessibilityAction == ACTION_NONE) {
+                        holder.mTileView.clearFocus();
+                    }
                 }
             });
             mNeedsFocus = false;
+            mFocusIndex = RecyclerView.NO_POSITION;
         }
     }
 
@@ -370,72 +373,77 @@
         return mCurrentSpecs.size() > mMinNumTiles;
     }
 
-    private void selectPosition(int position, View v) {
+    private void selectPosition(int position) {
         if (mAccessibilityAction == ACTION_ADD) {
             // Remove the placeholder.
             mTiles.remove(mEditIndex--);
-            notifyItemRemoved(mEditIndex);
         }
         mAccessibilityAction = ACTION_NONE;
-        move(mAccessibilityFromIndex, position, v);
+        move(mAccessibilityFromIndex, position, false);
+        mFocusIndex = position;
+        mNeedsFocus = true;
         notifyDataSetChanged();
     }
 
-    private void showAccessibilityDialog(final int position, final View v) {
-        final TileInfo info = mTiles.get(position);
-        CharSequence[] options = new CharSequence[] {
-                mContext.getString(R.string.accessibility_qs_edit_move_tile, info.state.label),
-                mContext.getString(R.string.accessibility_qs_edit_remove_tile, info.state.label),
-        };
-        AlertDialog dialog = new Builder(mContext)
-                .setItems(options, new DialogInterface.OnClickListener() {
-                    @Override
-                    public void onClick(DialogInterface dialog, int which) {
-                        if (which == 0) {
-                            startAccessibleMove(position);
-                        } else {
-                            move(position, info.isSystem ? mEditIndex : mTileDividerIndex, v);
-                            notifyItemChanged(mTileDividerIndex);
-                            notifyDataSetChanged();
-                        }
-                    }
-                }).setNegativeButton(android.R.string.cancel, null)
-                .create();
-        SystemUIDialog.setShowForAllUsers(dialog, true);
-        SystemUIDialog.applyFlags(dialog);
-        dialog.show();
-    }
-
     private void startAccessibleAdd(int position) {
         mAccessibilityFromIndex = position;
-        mAccessibilityFromLabel = mTiles.get(position).state.label;
         mAccessibilityAction = ACTION_ADD;
         // Add placeholder for last slot.
         mTiles.add(mEditIndex++, null);
         // Update the tile divider position
         mTileDividerIndex++;
+        mFocusIndex = mEditIndex - 1;
         mNeedsFocus = true;
+        if (mRecyclerView != null) {
+            mRecyclerView.post(() -> mRecyclerView.smoothScrollToPosition(mFocusIndex));
+        }
         notifyDataSetChanged();
     }
 
     private void startAccessibleMove(int position) {
         mAccessibilityFromIndex = position;
-        mAccessibilityFromLabel = mTiles.get(position).state.label;
         mAccessibilityAction = ACTION_MOVE;
+        mFocusIndex = position;
         mNeedsFocus = true;
         notifyDataSetChanged();
     }
 
+    private boolean canRemoveFromPosition(int position) {
+        return canRemoveTiles() && isCurrentTile(position);
+    }
+
+    private boolean isCurrentTile(int position) {
+        return position < mEditIndex;
+    }
+
+    private boolean canAddFromPosition(int position) {
+        return position > mEditIndex;
+    }
+
+    private void addFromPosition(int position) {
+        if (!canAddFromPosition(position)) return;
+        move(position, mEditIndex);
+    }
+
+    private void removeFromPosition(int position) {
+        if (!canRemoveFromPosition(position)) return;
+        TileInfo info = mTiles.get(position);
+        move(position, info.isSystem ? mEditIndex : mTileDividerIndex);
+    }
+
     public SpanSizeLookup getSizeLookup() {
         return mSizeLookup;
     }
 
-    private boolean move(int from, int to, View v) {
+    private boolean move(int from, int to) {
+        return move(from, to, true);
+    }
+
+    private boolean move(int from, int to, boolean notify) {
         if (to == from) {
             return true;
         }
-        CharSequence fromLabel = mTiles.get(from).state.label;
-        move(from, to, mTiles);
+        move(from, to, mTiles, notify);
         updateDividerLocations();
         if (to >= mEditIndex) {
             mUiEventLogger.log(QSEditEvent.QS_EDIT_REMOVE, 0, strip(mTiles.get(to)));
@@ -477,9 +485,11 @@
         return spec;
     }
 
-    private <T> void move(int from, int to, List<T> list) {
+    private <T> void move(int from, int to, List<T> list, boolean notify) {
         list.add(to, list.remove(from));
-        notifyItemMoved(from, to);
+        if (notify) {
+            notifyItemMoved(from, to);
+        }
     }
 
     public class Holder extends ViewHolder {
@@ -491,6 +501,8 @@
                 mTileView = (CustomizeTileView) ((FrameLayout) itemView).getChildAt(0);
                 mTileView.setBackground(null);
                 mTileView.getIcon().disableAnimation();
+                mTileView.setTag(this);
+                ViewCompat.setAccessibilityDelegate(mTileView, mAccessibilityDelegate);
             }
         }
 
@@ -527,6 +539,46 @@
                     .setDuration(DRAG_LENGTH)
                     .alpha(.6f);
         }
+
+        boolean canRemove() {
+            return canRemoveFromPosition(getLayoutPosition());
+        }
+
+        boolean canAdd() {
+            return canAddFromPosition(getLayoutPosition());
+        }
+
+        void toggleState() {
+            if (canAdd()) {
+                add();
+            } else {
+                remove();
+            }
+        }
+
+        private void add() {
+            addFromPosition(getLayoutPosition());
+        }
+
+        private void remove() {
+            removeFromPosition(getLayoutPosition());
+        }
+
+        boolean isCurrentTile() {
+            return TileAdapter.this.isCurrentTile(getLayoutPosition());
+        }
+
+        void startAccessibleAdd() {
+            TileAdapter.this.startAccessibleAdd(getLayoutPosition());
+        }
+
+        void startAccessibleMove() {
+            TileAdapter.this.startAccessibleMove(getLayoutPosition());
+        }
+
+        boolean canTakeAccessibleAction() {
+            return mAccessibilityAction == ACTION_NONE;
+        }
     }
 
     private final SpanSizeLookup mSizeLookup = new SpanSizeLookup() {
@@ -648,7 +700,7 @@
                     to == 0 || to == RecyclerView.NO_POSITION) {
                 return false;
             }
-            return move(from, to, target.itemView);
+            return move(from, to);
         }
 
         @Override
diff --git a/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapterDelegate.java b/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapterDelegate.java
new file mode 100644
index 0000000..1e426ad
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapterDelegate.java
@@ -0,0 +1,152 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.customize;
+
+import android.os.Bundle;
+import android.view.View;
+import android.view.accessibility.AccessibilityNodeInfo;
+
+import androidx.core.view.AccessibilityDelegateCompat;
+import androidx.core.view.accessibility.AccessibilityNodeInfoCompat;
+
+import com.android.systemui.R;
+
+import java.util.List;
+
+/**
+ * Accessibility delegate for {@link TileAdapter} views.
+ *
+ * This delegate will populate the accessibility info with the proper actions that can be taken for
+ * the different tiles:
+ * <ul>
+ *   <li>Add to end if the tile is not a current tile (by double tap).</li>
+ *   <li>Add to a given position (by context menu). This will let the user select a position.</li>
+ *   <li>Remove, if the tile is a current tile (by double tap).</li>
+ *   <li>Move to a given position (by context menu). This will let the user select a position.</li>
+ * </ul>
+ *
+ * This only handles generating the associated actions. The logic for selecting positions is handled
+ * by {@link TileAdapter}.
+ *
+ * In order for the delegate to work properly, the asociated {@link TileAdapter.Holder} should be
+ * passed along with the view using {@link View#setTag}.
+ */
+class TileAdapterDelegate extends AccessibilityDelegateCompat {
+
+    private static final int MOVE_TO_POSITION_ID = R.id.accessibility_action_qs_move_to_position;
+    private static final int ADD_TO_POSITION_ID = R.id.accessibility_action_qs_add_to_position;
+
+    private TileAdapter.Holder getHolder(View view) {
+        return (TileAdapter.Holder) view.getTag();
+    }
+
+    @Override
+    public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfoCompat info) {
+        super.onInitializeAccessibilityNodeInfo(host, info);
+        TileAdapter.Holder holder = getHolder(host);
+        info.setCollectionItemInfo(null);
+        info.setStateDescription("");
+        if (holder == null || !holder.canTakeAccessibleAction()) {
+            // If there's not a holder (not a regular Tile) or an action cannot be taken
+            // because we are in the middle of an accessibility action, don't create a special node.
+            return;
+        }
+
+        addClickAction(host, info, holder);
+        maybeAddActionAddToPosition(host, info, holder);
+        maybeAddActionMoveToPosition(host, info, holder);
+
+        if (holder.isCurrentTile()) {
+            info.setStateDescription(host.getContext().getString(
+                    R.string.accessibility_qs_edit_position, holder.getLayoutPosition()));
+        }
+    }
+
+    @Override
+    public boolean performAccessibilityAction(View host, int action, Bundle args) {
+        TileAdapter.Holder holder = getHolder(host);
+
+        if (holder == null || !holder.canTakeAccessibleAction()) {
+            // If there's not a holder (not a regular Tile) or an action cannot be taken
+            // because we are in the middle of an accessibility action, perform the default action.
+            return super.performAccessibilityAction(host, action, args);
+        }
+        if (action == AccessibilityNodeInfo.ACTION_CLICK) {
+            holder.toggleState();
+            return true;
+        } else if (action == MOVE_TO_POSITION_ID) {
+            holder.startAccessibleMove();
+            return true;
+        } else if (action == ADD_TO_POSITION_ID) {
+            holder.startAccessibleAdd();
+            return true;
+        } else {
+            return super.performAccessibilityAction(host, action, args);
+        }
+    }
+
+    private void addClickAction(
+            View host, AccessibilityNodeInfoCompat info, TileAdapter.Holder holder) {
+        String clickActionString;
+        if (holder.canAdd()) {
+            clickActionString = host.getContext().getString(
+                    R.string.accessibility_qs_edit_tile_add_action);
+        } else if (holder.canRemove()) {
+            clickActionString = host.getContext().getString(
+                    R.string.accessibility_qs_edit_remove_tile_action);
+        } else {
+            // Remove the default click action if tile can't either be added or removed (for example
+            // if there's the minimum number of tiles)
+            List<AccessibilityNodeInfoCompat.AccessibilityActionCompat> listOfActions =
+                    info.getActionList(); // This is a copy
+            int numActions = listOfActions.size();
+            for (int i = 0; i < numActions; i++) {
+                if (listOfActions.get(i).getId() == AccessibilityNodeInfo.ACTION_CLICK) {
+                    info.removeAction(listOfActions.get(i));
+                }
+            }
+            return;
+        }
+
+        AccessibilityNodeInfoCompat.AccessibilityActionCompat action =
+                new AccessibilityNodeInfoCompat.AccessibilityActionCompat(
+                        AccessibilityNodeInfo.ACTION_CLICK, clickActionString);
+        info.addAction(action);
+    }
+
+    private void maybeAddActionMoveToPosition(
+            View host, AccessibilityNodeInfoCompat info, TileAdapter.Holder holder) {
+        if (holder.isCurrentTile()) {
+            AccessibilityNodeInfoCompat.AccessibilityActionCompat action =
+                    new AccessibilityNodeInfoCompat.AccessibilityActionCompat(MOVE_TO_POSITION_ID,
+                            host.getContext().getString(
+                                    R.string.accessibility_qs_edit_tile_start_move));
+            info.addAction(action);
+        }
+    }
+
+    private void maybeAddActionAddToPosition(
+            View host, AccessibilityNodeInfoCompat info, TileAdapter.Holder holder) {
+        if (holder.canAdd()) {
+            AccessibilityNodeInfoCompat.AccessibilityActionCompat action =
+                    new AccessibilityNodeInfoCompat.AccessibilityActionCompat(ADD_TO_POSITION_ID,
+                            host.getContext().getString(
+                                    R.string.accessibility_qs_edit_tile_start_add));
+            info.addAction(action);
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/BatterySaverTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/BatterySaverTile.java
index f249504..7150e43 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/BatterySaverTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/BatterySaverTile.java
@@ -111,6 +111,7 @@
                 : mPowerSave ? Tile.STATE_ACTIVE : Tile.STATE_INACTIVE;
         state.icon = mIcon;
         state.label = mContext.getString(R.string.battery_detail_switch_title);
+        state.secondaryLabel = "";
         state.contentDescription = state.label;
         state.value = mPowerSave;
         state.expandedAccessibilityClassName = Switch.class.getName();
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/CellularTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/CellularTile.java
index bc03ca6..59597ac 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/CellularTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/CellularTile.java
@@ -117,7 +117,8 @@
             return;
         }
         String carrierName = mController.getMobileDataNetworkName();
-        if (TextUtils.isEmpty(carrierName)) {
+        boolean isInService = mController.isMobileDataNetworkInService();
+        if (TextUtils.isEmpty(carrierName) || !isInService) {
             carrierName = mContext.getString(R.string.mobile_data_disable_message_default_carrier);
         }
         AlertDialog dialog = new Builder(mContext)
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/NightDisplayTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/NightDisplayTile.java
index 2f58272..acd2846 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/NightDisplayTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/NightDisplayTile.java
@@ -60,7 +60,7 @@
     private static final String PATTERN_HOUR_MINUTE = "h:mm a";
     private static final String PATTERN_HOUR_NINUTE_24 = "HH:mm";
 
-    private final ColorDisplayManager mManager;
+    private ColorDisplayManager mManager;
     private final LocationController mLocationController;
     private NightDisplayListener mListener;
     private boolean mIsListening;
@@ -105,6 +105,8 @@
             mListener.setCallback(null);
         }
 
+        mManager = getHost().getUserContext().getSystemService(ColorDisplayManager.class);
+
         // Make a new controller for the new user.
         mListener = new NightDisplayListener(mContext, newUserId, new Handler(Looper.myLooper()));
         if (mIsListening) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/UiModeNightTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/UiModeNightTile.java
index 7b83c20..f777553 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/UiModeNightTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/UiModeNightTile.java
@@ -50,16 +50,15 @@
     public static DateTimeFormatter formatter = DateTimeFormatter.ofPattern("hh:mm a");
     private final Icon mIcon = ResourceIcon.get(
             com.android.internal.R.drawable.ic_qs_ui_mode_night);
-    private final UiModeManager mUiModeManager;
+    private UiModeManager mUiModeManager;
     private final BatteryController mBatteryController;
     private final LocationController mLocationController;
-
     @Inject
     public UiModeNightTile(QSHost host, ConfigurationController configurationController,
             BatteryController batteryController, LocationController locationController) {
         super(host);
         mBatteryController = batteryController;
-        mUiModeManager = mContext.getSystemService(UiModeManager.class);
+        mUiModeManager = host.getUserContext().getSystemService(UiModeManager.class);
         mLocationController = locationController;
         configurationController.observe(getLifecycle(), this);
         batteryController.observe(getLifecycle(), this);
diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java
index 476ec79..8ec3db5 100644
--- a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java
+++ b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java
@@ -21,7 +21,6 @@
 import android.app.NotificationManager;
 import android.app.PendingIntent;
 import android.app.Service;
-import android.content.ContentResolver;
 import android.content.Context;
 import android.content.Intent;
 import android.content.res.Resources;
@@ -42,6 +41,7 @@
 import com.android.systemui.R;
 import com.android.systemui.dagger.qualifiers.LongRunning;
 import com.android.systemui.settings.CurrentUserContextTracker;
+import com.android.systemui.statusbar.phone.KeyguardDismissUtil;
 
 import java.io.IOException;
 import java.util.concurrent.Executor;
@@ -69,10 +69,9 @@
     private static final String ACTION_STOP_NOTIF =
             "com.android.systemui.screenrecord.STOP_FROM_NOTIF";
     private static final String ACTION_SHARE = "com.android.systemui.screenrecord.SHARE";
-    private static final String ACTION_DELETE = "com.android.systemui.screenrecord.DELETE";
 
     private final RecordingController mController;
-
+    private final KeyguardDismissUtil mKeyguardDismissUtil;
     private ScreenRecordingAudioSource mAudioSource;
     private boolean mShowTaps;
     private boolean mOriginalShowTaps;
@@ -85,12 +84,13 @@
     @Inject
     public RecordingService(RecordingController controller, @LongRunning Executor executor,
             UiEventLogger uiEventLogger, NotificationManager notificationManager,
-            CurrentUserContextTracker userContextTracker) {
+            CurrentUserContextTracker userContextTracker, KeyguardDismissUtil keyguardDismissUtil) {
         mController = controller;
         mLongExecutor = executor;
         mUiEventLogger = uiEventLogger;
         mNotificationManager = notificationManager;
         mUserContextTracker = userContextTracker;
+        mKeyguardDismissUtil = keyguardDismissUtil;
     }
 
     /**
@@ -170,33 +170,17 @@
                 Intent shareIntent = new Intent(Intent.ACTION_SEND)
                         .setType("video/mp4")
                         .putExtra(Intent.EXTRA_STREAM, shareUri);
-                String shareLabel = getResources().getString(R.string.screenrecord_share_label);
+                mKeyguardDismissUtil.executeWhenUnlocked(() -> {
+                    String shareLabel = getResources().getString(R.string.screenrecord_share_label);
+                    startActivity(Intent.createChooser(shareIntent, shareLabel)
+                            .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
+                    // Remove notification
+                    mNotificationManager.cancelAsUser(null, NOTIFICATION_VIEW_ID, currentUser);
+                    return false;
+                }, false);
 
                 // Close quick shade
                 sendBroadcast(new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS));
-
-                // Remove notification
-                mNotificationManager.cancelAsUser(null, NOTIFICATION_VIEW_ID, currentUser);
-
-                startActivity(Intent.createChooser(shareIntent, shareLabel)
-                        .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
-                break;
-            case ACTION_DELETE:
-                // Close quick shade
-                sendBroadcast(new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS));
-
-                ContentResolver resolver = getContentResolver();
-                Uri uri = Uri.parse(intent.getStringExtra(EXTRA_PATH));
-                resolver.delete(uri, null, null);
-
-                Toast.makeText(
-                        this,
-                        R.string.screenrecord_delete_description,
-                        Toast.LENGTH_LONG).show();
-
-                // Remove notification
-                mNotificationManager.cancelAsUser(null, NOTIFICATION_VIEW_ID, currentUser);
-                Log.d(TAG, "Deleted recording " + uri);
                 break;
         }
         return Service.START_STICKY;
@@ -307,16 +291,6 @@
                         PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE))
                 .build();
 
-        Notification.Action deleteAction = new Notification.Action.Builder(
-                Icon.createWithResource(this, R.drawable.ic_screenrecord),
-                getResources().getString(R.string.screenrecord_delete_label),
-                PendingIntent.getService(
-                        this,
-                        REQUEST_CODE,
-                        getDeleteIntent(this, uri.toString()),
-                        PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE))
-                .build();
-
         Bundle extras = new Bundle();
         extras.putString(Notification.EXTRA_SUBSTITUTE_APP_NAME,
                 getResources().getString(R.string.screenrecord_name));
@@ -330,7 +304,6 @@
                         viewIntent,
                         PendingIntent.FLAG_IMMUTABLE))
                 .addAction(shareAction)
-                .addAction(deleteAction)
                 .setAutoCancel(true)
                 .addExtras(extras);
 
@@ -409,11 +382,6 @@
                 .putExtra(EXTRA_PATH, path);
     }
 
-    private static Intent getDeleteIntent(Context context, String path) {
-        return new Intent(context, RecordingService.class).setAction(ACTION_DELETE)
-                .putExtra(EXTRA_PATH, path);
-    }
-
     @Override
     public void onInfo(MediaRecorder mr, int what, int extra) {
         Log.d(TAG, "Media recorder info: " + what);
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ActionProxyReceiver.java b/packages/SystemUI/src/com/android/systemui/screenshot/ActionProxyReceiver.java
new file mode 100644
index 0000000..3fd7f945
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ActionProxyReceiver.java
@@ -0,0 +1,105 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.screenshot;
+
+import static com.android.systemui.screenshot.GlobalScreenshot.ACTION_TYPE_EDIT;
+import static com.android.systemui.screenshot.GlobalScreenshot.ACTION_TYPE_SHARE;
+import static com.android.systemui.screenshot.GlobalScreenshot.EXTRA_ACTION_INTENT;
+import static com.android.systemui.screenshot.GlobalScreenshot.EXTRA_DISALLOW_ENTER_PIP;
+import static com.android.systemui.screenshot.GlobalScreenshot.EXTRA_ID;
+import static com.android.systemui.screenshot.GlobalScreenshot.EXTRA_SMART_ACTIONS_ENABLED;
+import static com.android.systemui.statusbar.phone.StatusBar.SYSTEM_DIALOG_REASON_SCREENSHOT;
+
+import android.app.ActivityOptions;
+import android.app.PendingIntent;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.util.Log;
+
+import com.android.systemui.shared.system.ActivityManagerWrapper;
+import com.android.systemui.statusbar.phone.StatusBar;
+
+import java.util.Optional;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+import javax.inject.Inject;
+
+/**
+ * Receiver to proxy the share or edit intent, used to clean up the notification and send
+ * appropriate signals to the system (ie. to dismiss the keyguard if necessary).
+ */
+public class ActionProxyReceiver extends BroadcastReceiver {
+    private static final String TAG = "ActionProxyReceiver";
+
+    private static final int CLOSE_WINDOWS_TIMEOUT_MILLIS = 3000;
+    private final StatusBar mStatusBar;
+    private final ActivityManagerWrapper mActivityManagerWrapper;
+    private final ScreenshotSmartActions mScreenshotSmartActions;
+
+    @Inject
+    public ActionProxyReceiver(Optional<StatusBar> statusBar,
+            ActivityManagerWrapper activityManagerWrapper,
+            ScreenshotSmartActions screenshotSmartActions) {
+        mStatusBar = statusBar.orElse(null);
+        mActivityManagerWrapper = activityManagerWrapper;
+        mScreenshotSmartActions = screenshotSmartActions;
+    }
+
+    @Override
+    public void onReceive(Context context, final Intent intent) {
+        Runnable startActivityRunnable = () -> {
+            try {
+                mActivityManagerWrapper.closeSystemWindows(
+                        SYSTEM_DIALOG_REASON_SCREENSHOT).get(
+                        CLOSE_WINDOWS_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
+            } catch (TimeoutException | InterruptedException | ExecutionException e) {
+                Log.e(TAG, "Unable to share screenshot", e);
+                return;
+            }
+
+            PendingIntent actionIntent = intent.getParcelableExtra(EXTRA_ACTION_INTENT);
+            ActivityOptions opts = ActivityOptions.makeBasic();
+            opts.setDisallowEnterPictureInPictureWhileLaunching(
+                    intent.getBooleanExtra(EXTRA_DISALLOW_ENTER_PIP, false));
+            try {
+                actionIntent.send(context, 0, null, null, null, null, opts.toBundle());
+            } catch (PendingIntent.CanceledException e) {
+                Log.e(TAG, "Pending intent canceled", e);
+            }
+
+        };
+
+        if (mStatusBar != null) {
+            mStatusBar.executeRunnableDismissingKeyguard(startActivityRunnable, null,
+                    true /* dismissShade */, true /* afterKeyguardGone */,
+                    true /* deferred */);
+        } else {
+            startActivityRunnable.run();
+        }
+
+        if (intent.getBooleanExtra(EXTRA_SMART_ACTIONS_ENABLED, false)) {
+            String actionType = Intent.ACTION_EDIT.equals(intent.getAction())
+                    ? ACTION_TYPE_EDIT
+                    : ACTION_TYPE_SHARE;
+            mScreenshotSmartActions.notifyScreenshotAction(
+                    context, intent.getStringExtra(EXTRA_ID), actionType, false);
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/DeleteImageInBackgroundTask.java b/packages/SystemUI/src/com/android/systemui/screenshot/DeleteImageInBackgroundTask.java
deleted file mode 100644
index 8c48655..0000000
--- a/packages/SystemUI/src/com/android/systemui/screenshot/DeleteImageInBackgroundTask.java
+++ /dev/null
@@ -1,43 +0,0 @@
-/*
- * Copyright (C) 2019 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 com.android.systemui.screenshot;
-
-import android.content.ContentResolver;
-import android.content.Context;
-import android.net.Uri;
-import android.os.AsyncTask;
-
-/**
- * An AsyncTask that deletes an image from the media store in the background.
- */
-class DeleteImageInBackgroundTask extends AsyncTask<Uri, Void, Void> {
-    private Context mContext;
-
-    DeleteImageInBackgroundTask(Context context) {
-        mContext = context;
-    }
-
-    @Override
-    protected Void doInBackground(Uri... params) {
-        if (params.length != 1) return null;
-
-        Uri screenshotUri = params[0];
-        ContentResolver resolver = mContext.getContentResolver();
-        resolver.delete(screenshotUri, null, null);
-        return null;
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/DeleteScreenshotReceiver.java b/packages/SystemUI/src/com/android/systemui/screenshot/DeleteScreenshotReceiver.java
new file mode 100644
index 0000000..9028bb5
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/DeleteScreenshotReceiver.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.screenshot;
+
+import static com.android.systemui.screenshot.GlobalScreenshot.ACTION_TYPE_DELETE;
+import static com.android.systemui.screenshot.GlobalScreenshot.EXTRA_ID;
+import static com.android.systemui.screenshot.GlobalScreenshot.EXTRA_SMART_ACTIONS_ENABLED;
+import static com.android.systemui.screenshot.GlobalScreenshot.SCREENSHOT_URI_ID;
+
+import android.content.BroadcastReceiver;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.net.Uri;
+
+import com.android.systemui.dagger.qualifiers.Background;
+
+import java.util.concurrent.Executor;
+
+import javax.inject.Inject;
+
+/**
+ * Removes the file at a provided URI.
+ */
+public class DeleteScreenshotReceiver extends BroadcastReceiver {
+
+    private final ScreenshotSmartActions mScreenshotSmartActions;
+    private final Executor mBackgroundExecutor;
+
+    @Inject
+    public DeleteScreenshotReceiver(ScreenshotSmartActions screenshotSmartActions,
+            @Background Executor backgroundExecutor) {
+        mScreenshotSmartActions = screenshotSmartActions;
+        mBackgroundExecutor = backgroundExecutor;
+    }
+
+    @Override
+    public void onReceive(Context context, Intent intent) {
+        if (!intent.hasExtra(SCREENSHOT_URI_ID)) {
+            return;
+        }
+
+        // And delete the image from the media store
+        final Uri uri = Uri.parse(intent.getStringExtra(SCREENSHOT_URI_ID));
+        mBackgroundExecutor.execute(() -> {
+            ContentResolver resolver = context.getContentResolver();
+            resolver.delete(uri, null, null);
+        });
+        if (intent.getBooleanExtra(EXTRA_SMART_ACTIONS_ENABLED, false)) {
+            mScreenshotSmartActions.notifyScreenshotAction(
+                    context, intent.getStringExtra(EXTRA_ID), ACTION_TYPE_DELETE, false);
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java b/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java
index d6e1a16..c3c947b 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java
@@ -21,8 +21,6 @@
 import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
 import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
 
-import static com.android.systemui.statusbar.phone.StatusBar.SYSTEM_DIALOG_REASON_SCREENSHOT;
-
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
 import android.animation.AnimatorSet;
@@ -30,13 +28,10 @@
 import android.annotation.Nullable;
 import android.annotation.SuppressLint;
 import android.app.ActivityManager;
-import android.app.ActivityOptions;
 import android.app.Notification;
 import android.app.PendingIntent;
-import android.content.BroadcastReceiver;
 import android.content.ComponentName;
 import android.content.Context;
-import android.content.Intent;
 import android.content.res.Configuration;
 import android.content.res.Resources;
 import android.graphics.Bitmap;
@@ -57,13 +52,11 @@
 import android.os.Handler;
 import android.os.Looper;
 import android.os.Message;
-import android.os.PowerManager;
 import android.os.RemoteException;
 import android.provider.Settings;
 import android.util.DisplayMetrics;
 import android.util.Log;
 import android.util.MathUtils;
-import android.util.Slog;
 import android.view.Display;
 import android.view.KeyEvent;
 import android.view.LayoutInflater;
@@ -88,23 +81,15 @@
 import com.android.internal.logging.UiEventLogger;
 import com.android.systemui.R;
 import com.android.systemui.dagger.qualifiers.Main;
-import com.android.systemui.shared.system.ActivityManagerWrapper;
 import com.android.systemui.shared.system.QuickStepContract;
-import com.android.systemui.statusbar.phone.StatusBar;
 
 import java.util.ArrayList;
 import java.util.List;
-import java.util.Optional;
-import java.util.concurrent.ExecutionException;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.TimeoutException;
 import java.util.function.Consumer;
 
 import javax.inject.Inject;
 import javax.inject.Singleton;
 
-import dagger.Lazy;
-
 /**
  * Class for handling device screen shots
  */
@@ -193,6 +178,7 @@
     private final UiEventLogger mUiEventLogger;
 
     private final Context mContext;
+    private final ScreenshotSmartActions mScreenshotSmartActions;
     private final WindowManager mWindowManager;
     private final WindowManager.LayoutParams mWindowLayoutParams;
     private final Display mDisplay;
@@ -214,9 +200,9 @@
     private Animator mScreenshotAnimation;
     private Runnable mOnCompleteRunnable;
     private Animator mDismissAnimation;
-    private boolean mInDarkMode = false;
-    private boolean mDirectionLTR = true;
-    private boolean mOrientationPortrait = true;
+    private boolean mInDarkMode;
+    private boolean mDirectionLTR;
+    private boolean mOrientationPortrait;
 
     private float mCornerSizeX;
     private float mDismissDeltaY;
@@ -245,15 +231,14 @@
         }
     };
 
-    /**
-     * @param context everything needs a context :(
-     */
     @Inject
     public GlobalScreenshot(
             Context context, @Main Resources resources,
+            ScreenshotSmartActions screenshotSmartActions,
             ScreenshotNotificationsController screenshotNotificationsController,
             UiEventLogger uiEventLogger) {
         mContext = context;
+        mScreenshotSmartActions = screenshotSmartActions;
         mNotificationsController = screenshotNotificationsController;
         mUiEventLogger = uiEventLogger;
 
@@ -320,6 +305,104 @@
         inoutInfo.touchableRegion.set(touchRegion);
     }
 
+    void takeScreenshotFullscreen(Consumer<Uri> finisher, Runnable onComplete) {
+        mOnCompleteRunnable = onComplete;
+
+        mDisplay.getRealMetrics(mDisplayMetrics);
+        takeScreenshotInternal(
+                finisher,
+                new Rect(0, 0, mDisplayMetrics.widthPixels, mDisplayMetrics.heightPixels));
+    }
+
+    void handleImageAsScreenshot(Bitmap screenshot, Rect screenshotScreenBounds,
+            Insets visibleInsets, int taskId, int userId, ComponentName topComponent,
+            Consumer<Uri> finisher, Runnable onComplete) {
+        // TODO: use task Id, userId, topComponent for smart handler
+
+        mOnCompleteRunnable = onComplete;
+        if (aspectRatiosMatch(screenshot, visibleInsets, screenshotScreenBounds)) {
+            saveScreenshot(screenshot, finisher, screenshotScreenBounds, visibleInsets, false);
+        } else {
+            saveScreenshot(screenshot, finisher,
+                    new Rect(0, 0, screenshot.getWidth(), screenshot.getHeight()), Insets.NONE,
+                    true);
+        }
+    }
+
+    /**
+     * Displays a screenshot selector
+     */
+    @SuppressLint("ClickableViewAccessibility")
+    void takeScreenshotPartial(final Consumer<Uri> finisher, Runnable onComplete) {
+        dismissScreenshot("new screenshot requested", true);
+        mOnCompleteRunnable = onComplete;
+
+        mWindowManager.addView(mScreenshotLayout, mWindowLayoutParams);
+        mScreenshotSelectorView.setOnTouchListener((v, event) -> {
+            ScreenshotSelectorView view = (ScreenshotSelectorView) v;
+            switch (event.getAction()) {
+                case MotionEvent.ACTION_DOWN:
+                    view.startSelection((int) event.getX(), (int) event.getY());
+                    return true;
+                case MotionEvent.ACTION_MOVE:
+                    view.updateSelection((int) event.getX(), (int) event.getY());
+                    return true;
+                case MotionEvent.ACTION_UP:
+                    view.setVisibility(View.GONE);
+                    mWindowManager.removeView(mScreenshotLayout);
+                    final Rect rect = view.getSelectionRect();
+                    if (rect != null) {
+                        if (rect.width() != 0 && rect.height() != 0) {
+                            // Need mScreenshotLayout to handle it after the view disappears
+                            mScreenshotLayout.post(() -> takeScreenshotInternal(finisher, rect));
+                        }
+                    }
+
+                    view.stopSelection();
+                    return true;
+            }
+
+            return false;
+        });
+        mScreenshotLayout.post(() -> {
+            mScreenshotSelectorView.setVisibility(View.VISIBLE);
+            mScreenshotSelectorView.requestFocus();
+        });
+    }
+
+    /**
+     * Cancels screenshot request
+     */
+    void stopScreenshot() {
+        // If the selector layer still presents on screen, we remove it and resets its state.
+        if (mScreenshotSelectorView.getSelectionRect() != null) {
+            mWindowManager.removeView(mScreenshotLayout);
+            mScreenshotSelectorView.stopSelection();
+        }
+    }
+
+    /**
+     * Clears current screenshot
+     */
+    void dismissScreenshot(String reason, boolean immediate) {
+        Log.v(TAG, "clearing screenshot: " + reason);
+        mScreenshotHandler.removeMessages(MESSAGE_CORNER_TIMEOUT);
+        mScreenshotLayout.getViewTreeObserver().removeOnComputeInternalInsetsListener(this);
+        if (!immediate) {
+            mDismissAnimation = createScreenshotDismissAnimation();
+            mDismissAnimation.addListener(new AnimatorListenerAdapter() {
+                @Override
+                public void onAnimationEnd(Animator animation) {
+                    super.onAnimationEnd(animation);
+                    clearScreenshot();
+                }
+            });
+            mDismissAnimation.start();
+        } else {
+            clearScreenshot();
+        }
+    }
+
     private void onConfigChanged(Configuration newConfig) {
         boolean needsUpdate = false;
         // dark mode
@@ -408,15 +491,12 @@
             }
             return mScreenshotLayout.onApplyWindowInsets(insets);
         });
-        mScreenshotLayout.setOnKeyListener(new View.OnKeyListener() {
-            @Override
-            public boolean onKey(View v, int keyCode, KeyEvent event) {
-                if (keyCode == KeyEvent.KEYCODE_BACK) {
-                    dismissScreenshot("back pressed", true);
-                    return true;
-                }
-                return false;
+        mScreenshotLayout.setOnKeyListener((v, keyCode, event) -> {
+            if (keyCode == KeyEvent.KEYCODE_BACK) {
+                dismissScreenshot("back pressed", false);
+                return true;
             }
+            return false;
         });
         // Get focus so that the key events go to the layout.
         mScreenshotLayout.setFocusableInTouchMode(true);
@@ -471,61 +551,27 @@
     }
 
     /**
-     * Updates the window focusability.  If the window is already showing, then it updates the
-     * window immediately, otherwise the layout params will be applied when the window is next
-     * shown.
-     */
-    private void setWindowFocusable(boolean focusable) {
-        if (focusable) {
-            mWindowLayoutParams.flags &= ~FLAG_NOT_FOCUSABLE;
-        } else {
-            mWindowLayoutParams.flags |= FLAG_NOT_FOCUSABLE;
-        }
-        if (mScreenshotLayout.isAttachedToWindow()) {
-            mWindowManager.updateViewLayout(mScreenshotLayout, mWindowLayoutParams);
-        }
-    }
-
-    /**
-     * Creates a new worker thread and saves the screenshot to the media store.
-     */
-    private void saveScreenshotInWorkerThread(
-            Consumer<Uri> finisher, @Nullable ActionsReadyListener actionsReadyListener) {
-        SaveImageInBackgroundData data = new SaveImageInBackgroundData();
-        data.image = mScreenBitmap;
-        data.finisher = finisher;
-        data.mActionsReadyListener = actionsReadyListener;
-
-        if (mSaveInBgTask != null) {
-            // just log success/failure for the pre-existing screenshot
-            mSaveInBgTask.setActionsReadyListener(new ActionsReadyListener() {
-                @Override
-                void onActionsReady(SavedImageData imageData) {
-                    logSuccessOnActionsReady(imageData);
-                }
-            });
-        }
-
-        mSaveInBgTask = new SaveImageInBackgroundTask(mContext, data);
-        mSaveInBgTask.execute();
-    }
-
-    /**
      * Takes a screenshot of the current display and shows an animation.
      */
-    private void takeScreenshot(Consumer<Uri> finisher, Rect crop) {
+    private void takeScreenshotInternal(Consumer<Uri> finisher, Rect crop) {
         // copy the input Rect, since SurfaceControl.screenshot can mutate it
         Rect screenRect = new Rect(crop);
         int rot = mDisplay.getRotation();
         int width = crop.width();
         int height = crop.height();
-        takeScreenshot(SurfaceControl.screenshot(crop, width, height, rot), finisher, screenRect,
+        saveScreenshot(SurfaceControl.screenshot(crop, width, height, rot), finisher, screenRect,
                 Insets.NONE, true);
     }
 
-    private void takeScreenshot(Bitmap screenshot, Consumer<Uri> finisher, Rect screenRect,
+    private void saveScreenshot(Bitmap screenshot, Consumer<Uri> finisher, Rect screenRect,
             Insets screenInsets, boolean showFlash) {
-        dismissScreenshot("new screenshot requested", true);
+        if (mScreenshotLayout.isAttachedToWindow()) {
+            // if we didn't already dismiss for another reason
+            if (mDismissAnimation == null || !mDismissAnimation.isRunning()) {
+                mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_REENTERED);
+            }
+            dismissScreenshot("new screenshot requested", true);
+        }
 
         mScreenBitmap = screenshot;
 
@@ -561,85 +607,6 @@
         startAnimation(finisher, screenRect, screenInsets, showFlash);
     }
 
-    void takeScreenshot(Consumer<Uri> finisher, Runnable onComplete) {
-        mOnCompleteRunnable = onComplete;
-
-        mDisplay.getRealMetrics(mDisplayMetrics);
-        takeScreenshot(
-                finisher,
-                new Rect(0, 0, mDisplayMetrics.widthPixels, mDisplayMetrics.heightPixels));
-    }
-
-    void handleImageAsScreenshot(Bitmap screenshot, Rect screenshotScreenBounds,
-            Insets visibleInsets, int taskId, int userId, ComponentName topComponent,
-            Consumer<Uri> finisher, Runnable onComplete) {
-        // TODO: use task Id, userId, topComponent for smart handler
-
-        mOnCompleteRunnable = onComplete;
-        if (aspectRatiosMatch(screenshot, visibleInsets, screenshotScreenBounds)) {
-            takeScreenshot(screenshot, finisher, screenshotScreenBounds, visibleInsets, false);
-        } else {
-            takeScreenshot(screenshot, finisher,
-                    new Rect(0, 0, screenshot.getWidth(), screenshot.getHeight()), Insets.NONE,
-                    true);
-        }
-    }
-
-    /**
-     * Displays a screenshot selector
-     */
-    @SuppressLint("ClickableViewAccessibility")
-    void takeScreenshotPartial(final Consumer<Uri> finisher, Runnable onComplete) {
-        dismissScreenshot("new screenshot requested", true);
-        mOnCompleteRunnable = onComplete;
-
-        mWindowManager.addView(mScreenshotLayout, mWindowLayoutParams);
-        mScreenshotSelectorView.setOnTouchListener(new View.OnTouchListener() {
-            @Override
-            public boolean onTouch(View v, MotionEvent event) {
-                ScreenshotSelectorView view = (ScreenshotSelectorView) v;
-                switch (event.getAction()) {
-                    case MotionEvent.ACTION_DOWN:
-                        view.startSelection((int) event.getX(), (int) event.getY());
-                        return true;
-                    case MotionEvent.ACTION_MOVE:
-                        view.updateSelection((int) event.getX(), (int) event.getY());
-                        return true;
-                    case MotionEvent.ACTION_UP:
-                        view.setVisibility(View.GONE);
-                        mWindowManager.removeView(mScreenshotLayout);
-                        final Rect rect = view.getSelectionRect();
-                        if (rect != null) {
-                            if (rect.width() != 0 && rect.height() != 0) {
-                                // Need mScreenshotLayout to handle it after the view disappears
-                                mScreenshotLayout.post(() -> takeScreenshot(finisher, rect));
-                            }
-                        }
-
-                        view.stopSelection();
-                        return true;
-                }
-
-                return false;
-            }
-        });
-        mScreenshotLayout.post(() -> {
-            mScreenshotSelectorView.setVisibility(View.VISIBLE);
-            mScreenshotSelectorView.requestFocus();
-        });
-    }
-
-    /**
-     * Cancels screenshot request
-     */
-    void stopScreenshot() {
-        // If the selector layer still presents on screen, we remove it and resets its state.
-        if (mScreenshotSelectorView.getSelectionRect() != null) {
-            mWindowManager.removeView(mScreenshotLayout);
-            mScreenshotSelectorView.stopSelection();
-        }
-    }
-
     /**
      * Save the bitmap but don't show the normal screenshot UI.. just a toast (or notification on
      * failure).
@@ -670,55 +637,70 @@
         });
     }
 
-    private boolean isUserSetupComplete() {
-        return Settings.Secure.getInt(mContext.getContentResolver(),
-                SETTINGS_SECURE_USER_SETUP_COMPLETE, 0) == 1;
+    /**
+     * Starts the animation after taking the screenshot
+     */
+    private void startAnimation(final Consumer<Uri> finisher, Rect screenRect, Insets screenInsets,
+            boolean showFlash) {
+        mScreenshotHandler.post(() -> {
+            if (!mScreenshotLayout.isAttachedToWindow()) {
+                mWindowManager.addView(mScreenshotLayout, mWindowLayoutParams);
+            }
+            mScreenshotAnimatedView.setImageDrawable(
+                    createScreenDrawable(mScreenBitmap, screenInsets));
+            setAnimatedViewSize(screenRect.width(), screenRect.height());
+            // Show when the animation starts
+            mScreenshotAnimatedView.setVisibility(View.GONE);
+
+            mScreenshotPreview.setImageDrawable(createScreenDrawable(mScreenBitmap, screenInsets));
+            // make static preview invisible (from gone) so we can query its location on screen
+            mScreenshotPreview.setVisibility(View.INVISIBLE);
+
+            mScreenshotHandler.post(() -> {
+                mScreenshotLayout.getViewTreeObserver().addOnComputeInternalInsetsListener(this);
+
+                mScreenshotAnimation =
+                        createScreenshotDropInAnimation(screenRect, showFlash);
+
+                saveScreenshotInWorkerThread(finisher, new ActionsReadyListener() {
+                    @Override
+                    void onActionsReady(SavedImageData imageData) {
+                        showUiOnActionsReady(imageData);
+                    }
+                });
+
+                // Play the shutter sound to notify that we've taken a screenshot
+                mCameraSound.play(MediaActionSound.SHUTTER_CLICK);
+
+                mScreenshotPreview.setLayerType(View.LAYER_TYPE_HARDWARE, null);
+                mScreenshotPreview.buildLayer();
+                mScreenshotAnimation.start();
+            });
+        });
     }
 
     /**
-     * Clears current screenshot
+     * Creates a new worker thread and saves the screenshot to the media store.
      */
-    void dismissScreenshot(String reason, boolean immediate) {
-        Log.v(TAG, "clearing screenshot: " + reason);
-        mScreenshotHandler.removeMessages(MESSAGE_CORNER_TIMEOUT);
-        mScreenshotLayout.getViewTreeObserver().removeOnComputeInternalInsetsListener(this);
-        if (!immediate) {
-            mDismissAnimation = createScreenshotDismissAnimation();
-            mDismissAnimation.addListener(new AnimatorListenerAdapter() {
+    private void saveScreenshotInWorkerThread(
+            Consumer<Uri> finisher, @Nullable ActionsReadyListener actionsReadyListener) {
+        SaveImageInBackgroundData data = new SaveImageInBackgroundData();
+        data.image = mScreenBitmap;
+        data.finisher = finisher;
+        data.mActionsReadyListener = actionsReadyListener;
+
+        if (mSaveInBgTask != null) {
+            // just log success/failure for the pre-existing screenshot
+            mSaveInBgTask.setActionsReadyListener(new ActionsReadyListener() {
                 @Override
-                public void onAnimationEnd(Animator animation) {
-                    super.onAnimationEnd(animation);
-                    clearScreenshot();
+                void onActionsReady(SavedImageData imageData) {
+                    logSuccessOnActionsReady(imageData);
                 }
             });
-            mDismissAnimation.start();
-        } else {
-            clearScreenshot();
-        }
-    }
-
-    private void clearScreenshot() {
-        if (mScreenshotLayout.isAttachedToWindow()) {
-            mWindowManager.removeView(mScreenshotLayout);
         }
 
-        // Clear any references to the bitmap
-        mScreenshotPreview.setImageDrawable(null);
-        mScreenshotAnimatedView.setImageDrawable(null);
-        mScreenshotAnimatedView.setVisibility(View.GONE);
-        mActionsContainerBackground.setVisibility(View.GONE);
-        mActionsContainer.setVisibility(View.GONE);
-        mBackgroundProtection.setAlpha(0f);
-        mDismissButton.setVisibility(View.GONE);
-        mScreenshotPreview.setVisibility(View.GONE);
-        mScreenshotPreview.setLayerType(View.LAYER_TYPE_NONE, null);
-        mScreenshotPreview.setContentDescription(
-                mContext.getResources().getString(R.string.screenshot_preview_description));
-        mScreenshotLayout.setAlpha(1);
-        mDismissButton.setTranslationY(0);
-        mActionsContainer.setTranslationY(0);
-        mActionsContainerBackground.setTranslationY(0);
-        mScreenshotPreview.setTranslationY(0);
+        mSaveInBgTask = new SaveImageInBackgroundTask(mContext, mScreenshotSmartActions, data);
+        mSaveInBgTask.execute();
     }
 
     /**
@@ -768,56 +750,6 @@
         }
     }
 
-    /**
-     * Starts the animation after taking the screenshot
-     */
-    private void startAnimation(final Consumer<Uri> finisher, Rect screenRect, Insets screenInsets,
-            boolean showFlash) {
-
-        // If power save is on, show a toast so there is some visual indication that a
-        // screenshot has been taken.
-        PowerManager powerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
-        if (powerManager.isPowerSaveMode()) {
-            Toast.makeText(mContext, R.string.screenshot_saved_title, Toast.LENGTH_SHORT).show();
-        }
-
-        mScreenshotHandler.post(() -> {
-            if (!mScreenshotLayout.isAttachedToWindow()) {
-                mWindowManager.addView(mScreenshotLayout, mWindowLayoutParams);
-            }
-            mScreenshotAnimatedView.setImageDrawable(
-                    createScreenDrawable(mScreenBitmap, screenInsets));
-            setAnimatedViewSize(screenRect.width(), screenRect.height());
-            // Show when the animation starts
-            mScreenshotAnimatedView.setVisibility(View.GONE);
-
-            mScreenshotPreview.setImageDrawable(createScreenDrawable(mScreenBitmap, screenInsets));
-            // make static preview invisible (from gone) so we can query its location on screen
-            mScreenshotPreview.setVisibility(View.INVISIBLE);
-
-            mScreenshotHandler.post(() -> {
-                mScreenshotLayout.getViewTreeObserver().addOnComputeInternalInsetsListener(this);
-
-                mScreenshotAnimation =
-                        createScreenshotDropInAnimation(screenRect, showFlash);
-
-                saveScreenshotInWorkerThread(finisher, new ActionsReadyListener() {
-                    @Override
-                    void onActionsReady(SavedImageData imageData) {
-                        showUiOnActionsReady(imageData);
-                    }
-                });
-
-                // Play the shutter sound to notify that we've taken a screenshot
-                mCameraSound.play(MediaActionSound.SHUTTER_CLICK);
-
-                mScreenshotPreview.setLayerType(View.LAYER_TYPE_HARDWARE, null);
-                mScreenshotPreview.buildLayer();
-                mScreenshotAnimation.start();
-            });
-        });
-    }
-
     private AnimatorSet createScreenshotDropInAnimation(Rect bounds, boolean showFlash) {
         Rect previewBounds = new Rect();
         mScreenshotPreview.getBoundsOnScreen(previewBounds);
@@ -1070,6 +1002,31 @@
         return animSet;
     }
 
+    private void clearScreenshot() {
+        if (mScreenshotLayout.isAttachedToWindow()) {
+            mWindowManager.removeView(mScreenshotLayout);
+        }
+
+        // Clear any references to the bitmap
+        mScreenshotPreview.setImageDrawable(null);
+        mScreenshotAnimatedView.setImageDrawable(null);
+        mScreenshotAnimatedView.setVisibility(View.GONE);
+        mActionsContainerBackground.setVisibility(View.GONE);
+        mActionsContainer.setVisibility(View.GONE);
+        mBackgroundProtection.setAlpha(0f);
+        mDismissButton.setVisibility(View.GONE);
+        mScreenshotPreview.setVisibility(View.GONE);
+        mScreenshotPreview.setLayerType(View.LAYER_TYPE_NONE, null);
+        mScreenshotPreview.setContentDescription(
+                mContext.getResources().getString(R.string.screenshot_preview_description));
+        mScreenshotPreview.setOnClickListener(null);
+        mScreenshotLayout.setAlpha(1);
+        mDismissButton.setTranslationY(0);
+        mActionsContainer.setTranslationY(0);
+        mActionsContainerBackground.setTranslationY(0);
+        mScreenshotPreview.setTranslationY(0);
+    }
+
     private void setAnimatedViewSize(int width, int height) {
         ViewGroup.LayoutParams layoutParams = mScreenshotAnimatedView.getLayoutParams();
         layoutParams.width = width;
@@ -1077,6 +1034,27 @@
         mScreenshotAnimatedView.setLayoutParams(layoutParams);
     }
 
+    /**
+     * Updates the window focusability.  If the window is already showing, then it updates the
+     * window immediately, otherwise the layout params will be applied when the window is next
+     * shown.
+     */
+    private void setWindowFocusable(boolean focusable) {
+        if (focusable) {
+            mWindowLayoutParams.flags &= ~FLAG_NOT_FOCUSABLE;
+        } else {
+            mWindowLayoutParams.flags |= FLAG_NOT_FOCUSABLE;
+        }
+        if (mScreenshotLayout.isAttachedToWindow()) {
+            mWindowManager.updateViewLayout(mScreenshotLayout, mWindowLayoutParams);
+        }
+    }
+
+    private boolean isUserSetupComplete() {
+        return Settings.Secure.getInt(mContext.getContentResolver(),
+                SETTINGS_SECURE_USER_SETUP_COMPLETE, 0) == 1;
+    }
+
     /** Does the aspect ratio of the bitmap with insets removed match the bounds. */
     private boolean aspectRatiosMatch(Bitmap bitmap, Insets bitmapInsets, Rect screenBounds) {
         int insettedWidth = bitmap.getWidth() - bitmapInsets.left - bitmapInsets.right;
@@ -1128,125 +1106,10 @@
         if (insets.left < 0 || insets.top < 0 || insets.right < 0 || insets.bottom < 0) {
             // Are any of the insets negative, meaning the bitmap is smaller than the bounds so need
             // to fill in the background of the drawable.
-            return new LayerDrawable(new Drawable[] {
+            return new LayerDrawable(new Drawable[]{
                     new ColorDrawable(Color.BLACK), insetDrawable});
         } else {
             return insetDrawable;
         }
     }
-
-    /**
-     * Receiver to proxy the share or edit intent, used to clean up the notification and send
-     * appropriate signals to the system (ie. to dismiss the keyguard if necessary).
-     */
-    public static class ActionProxyReceiver extends BroadcastReceiver {
-        static final int CLOSE_WINDOWS_TIMEOUT_MILLIS = 3000;
-        private final StatusBar mStatusBar;
-
-        @Inject
-        public ActionProxyReceiver(Optional<Lazy<StatusBar>> statusBarLazy) {
-            Lazy<StatusBar> statusBar = statusBarLazy.orElse(null);
-            mStatusBar = statusBar != null ? statusBar.get() : null;
-        }
-
-        @Override
-        public void onReceive(Context context, final Intent intent) {
-            Runnable startActivityRunnable = () -> {
-                try {
-                    ActivityManagerWrapper.getInstance().closeSystemWindows(
-                            SYSTEM_DIALOG_REASON_SCREENSHOT).get(
-                            CLOSE_WINDOWS_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
-                } catch (TimeoutException | InterruptedException | ExecutionException e) {
-                    Slog.e(TAG, "Unable to share screenshot", e);
-                    return;
-                }
-
-                PendingIntent actionIntent = intent.getParcelableExtra(EXTRA_ACTION_INTENT);
-                if (intent.getBooleanExtra(EXTRA_CANCEL_NOTIFICATION, false)) {
-                    ScreenshotNotificationsController.cancelScreenshotNotification(context);
-                }
-                ActivityOptions opts = ActivityOptions.makeBasic();
-                opts.setDisallowEnterPictureInPictureWhileLaunching(
-                        intent.getBooleanExtra(EXTRA_DISALLOW_ENTER_PIP, false));
-                try {
-                    actionIntent.send(context, 0, null, null, null, null, opts.toBundle());
-                } catch (PendingIntent.CanceledException e) {
-                    Log.e(TAG, "Pending intent canceled", e);
-                }
-
-            };
-
-            if (mStatusBar != null) {
-                mStatusBar.executeRunnableDismissingKeyguard(startActivityRunnable, null,
-                        true /* dismissShade */, true /* afterKeyguardGone */,
-                        true /* deferred */);
-            } else {
-                startActivityRunnable.run();
-            }
-
-            if (intent.getBooleanExtra(EXTRA_SMART_ACTIONS_ENABLED, false)) {
-                String actionType = Intent.ACTION_EDIT.equals(intent.getAction())
-                        ? ACTION_TYPE_EDIT
-                        : ACTION_TYPE_SHARE;
-                ScreenshotSmartActions.notifyScreenshotAction(
-                        context, intent.getStringExtra(EXTRA_ID), actionType, false);
-            }
-        }
-    }
-
-    /**
-     * Removes the notification for a screenshot after a share target is chosen.
-     */
-    public static class TargetChosenReceiver extends BroadcastReceiver {
-        @Override
-        public void onReceive(Context context, Intent intent) {
-            // Clear the notification only after the user has chosen a share action
-            ScreenshotNotificationsController.cancelScreenshotNotification(context);
-        }
-    }
-
-    /**
-     * Removes the last screenshot.
-     */
-    public static class DeleteScreenshotReceiver extends BroadcastReceiver {
-        @Override
-        public void onReceive(Context context, Intent intent) {
-            if (!intent.hasExtra(SCREENSHOT_URI_ID)) {
-                return;
-            }
-
-            // Clear the notification when the image is deleted
-            ScreenshotNotificationsController.cancelScreenshotNotification(context);
-
-            // And delete the image from the media store
-            final Uri uri = Uri.parse(intent.getStringExtra(SCREENSHOT_URI_ID));
-            new DeleteImageInBackgroundTask(context).execute(uri);
-            if (intent.getBooleanExtra(EXTRA_SMART_ACTIONS_ENABLED, false)) {
-                ScreenshotSmartActions.notifyScreenshotAction(
-                        context, intent.getStringExtra(EXTRA_ID), ACTION_TYPE_DELETE, false);
-            }
-        }
-    }
-
-    /**
-     * Executes the smart action tapped by the user in the notification.
-     */
-    public static class SmartActionsReceiver extends BroadcastReceiver {
-        @Override
-        public void onReceive(Context context, Intent intent) {
-            PendingIntent pendingIntent = intent.getParcelableExtra(EXTRA_ACTION_INTENT);
-            String actionType = intent.getStringExtra(EXTRA_ACTION_TYPE);
-            Slog.d(TAG, "Executing smart action [" + actionType + "]:" + pendingIntent.getIntent());
-            ActivityOptions opts = ActivityOptions.makeBasic();
-
-            try {
-                pendingIntent.send(context, 0, null, null, null, null, opts.toBundle());
-            } catch (PendingIntent.CanceledException e) {
-                Log.e(TAG, "Pending intent canceled", e);
-            }
-
-            ScreenshotSmartActions.notifyScreenshotAction(
-                    context, intent.getStringExtra(EXTRA_ID), actionType, true);
-        }
-    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java b/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java
index 468b9b1..df1d789 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java
@@ -81,6 +81,7 @@
     private static final String SCREENSHOT_SHARE_SUBJECT_TEMPLATE = "Screenshot (%s)";
 
     private final Context mContext;
+    private final ScreenshotSmartActions mScreenshotSmartActions;
     private final GlobalScreenshot.SaveImageInBackgroundData mParams;
     private final GlobalScreenshot.SavedImageData mImageData;
     private final String mImageFileName;
@@ -90,8 +91,10 @@
     private final boolean mSmartActionsEnabled;
     private final Random mRandom = new Random();
 
-    SaveImageInBackgroundTask(Context context, GlobalScreenshot.SaveImageInBackgroundData data) {
+    SaveImageInBackgroundTask(Context context, ScreenshotSmartActions screenshotSmartActions,
+            GlobalScreenshot.SaveImageInBackgroundData data) {
         mContext = context;
+        mScreenshotSmartActions = screenshotSmartActions;
         mImageData = new GlobalScreenshot.SavedImageData();
 
         // Prepare all the output metadata
@@ -141,7 +144,7 @@
             final Uri uri = resolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values);
 
             CompletableFuture<List<Notification.Action>> smartActionsFuture =
-                    ScreenshotSmartActions.getSmartActionsFuture(
+                    mScreenshotSmartActions.getSmartActionsFuture(
                             mScreenshotId, uri, image, mSmartActionsProvider,
                             mSmartActionsEnabled, getUserHandle(mContext));
 
@@ -199,7 +202,7 @@
                         SystemUiDeviceConfigFlags.SCREENSHOT_NOTIFICATION_SMART_ACTIONS_TIMEOUT_MS,
                         1000);
                 smartActions.addAll(buildSmartActions(
-                        ScreenshotSmartActions.getSmartActions(
+                        mScreenshotSmartActions.getSmartActions(
                                 mScreenshotId, smartActionsFuture, timeoutMs,
                                 mSmartActionsProvider),
                         mContext));
@@ -274,11 +277,8 @@
         // by setting the (otherwise unused) request code to the current user id.
         int requestCode = context.getUserId();
 
-        PendingIntent chooserAction = PendingIntent.getBroadcast(context, requestCode,
-                new Intent(context, GlobalScreenshot.TargetChosenReceiver.class),
-                PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_ONE_SHOT);
         Intent sharingChooserIntent =
-                Intent.createChooser(sharingIntent, null, chooserAction.getIntentSender())
+                Intent.createChooser(sharingIntent, null)
                         .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_NEW_TASK)
                         .addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
 
@@ -288,7 +288,7 @@
 
         // Create a share action for the notification
         PendingIntent shareAction = PendingIntent.getBroadcastAsUser(context, requestCode,
-                new Intent(context, GlobalScreenshot.ActionProxyReceiver.class)
+                new Intent(context, ActionProxyReceiver.class)
                         .putExtra(GlobalScreenshot.EXTRA_ACTION_INTENT, pendingIntent)
                         .putExtra(GlobalScreenshot.EXTRA_DISALLOW_ENTER_PIP, true)
                         .putExtra(GlobalScreenshot.EXTRA_ID, mScreenshotId)
@@ -333,10 +333,8 @@
 
         // Create a edit action
         PendingIntent editAction = PendingIntent.getBroadcastAsUser(context, requestCode,
-                new Intent(context, GlobalScreenshot.ActionProxyReceiver.class)
+                new Intent(context, ActionProxyReceiver.class)
                         .putExtra(GlobalScreenshot.EXTRA_ACTION_INTENT, pendingIntent)
-                        .putExtra(GlobalScreenshot.EXTRA_CANCEL_NOTIFICATION,
-                                editIntent.getComponent() != null)
                         .putExtra(GlobalScreenshot.EXTRA_ID, mScreenshotId)
                         .putExtra(GlobalScreenshot.EXTRA_SMART_ACTIONS_ENABLED,
                                 mSmartActionsEnabled)
@@ -358,7 +356,7 @@
 
         // Create a delete action for the notification
         PendingIntent deleteAction = PendingIntent.getBroadcast(context, requestCode,
-                new Intent(context, GlobalScreenshot.DeleteScreenshotReceiver.class)
+                new Intent(context, DeleteScreenshotReceiver.class)
                         .putExtra(GlobalScreenshot.SCREENSHOT_URI_ID, uri.toString())
                         .putExtra(GlobalScreenshot.EXTRA_ID, mScreenshotId)
                         .putExtra(GlobalScreenshot.EXTRA_SMART_ACTIONS_ENABLED,
@@ -398,7 +396,7 @@
             String actionType = extras.getString(
                     ScreenshotNotificationSmartActionsProvider.ACTION_TYPE,
                     ScreenshotNotificationSmartActionsProvider.DEFAULT_ACTION_TYPE);
-            Intent intent = new Intent(context, GlobalScreenshot.SmartActionsReceiver.class)
+            Intent intent = new Intent(context, SmartActionsReceiver.class)
                     .putExtra(GlobalScreenshot.EXTRA_ACTION_INTENT, action.actionIntent)
                     .addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
             addIntentExtras(mScreenshotId, intent, actionType, mSmartActionsEnabled);
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotActionChip.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotActionChip.java
index b5209bb..a488702 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotActionChip.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotActionChip.java
@@ -65,10 +65,10 @@
     }
 
     void setIcon(Icon icon, boolean tint) {
-        if (tint) {
-            icon.setTint(mIconColor);
-        }
         mIcon.setImageIcon(icon);
+        if (!tint) {
+            mIcon.setImageTintList(null);
+        }
     }
 
     void setText(CharSequence text) {
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotEvent.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotEvent.java
index 20fa991..8535d57 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotEvent.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotEvent.java
@@ -56,7 +56,9 @@
     @UiEvent(doc = "screenshot interaction timed out")
     SCREENSHOT_INTERACTION_TIMEOUT(310),
     @UiEvent(doc = "screenshot explicitly dismissed")
-    SCREENSHOT_EXPLICIT_DISMISSAL(311);
+    SCREENSHOT_EXPLICIT_DISMISSAL(311),
+    @UiEvent(doc = "screenshot reentered for new screenshot")
+    SCREENSHOT_REENTERED(640);
 
     private final int mId;
 
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotSmartActions.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotSmartActions.java
index 442b373..633cdd6 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotSmartActions.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotSmartActions.java
@@ -39,14 +39,21 @@
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.TimeoutException;
 
+import javax.inject.Inject;
+import javax.inject.Singleton;
+
 /**
  * Collects the static functions for retrieving and acting on smart actions.
  */
+@Singleton
 public class ScreenshotSmartActions {
     private static final String TAG = "ScreenshotSmartActions";
 
+    @Inject
+    public ScreenshotSmartActions() {}
+
     @VisibleForTesting
-    static CompletableFuture<List<Notification.Action>> getSmartActionsFuture(
+    CompletableFuture<List<Notification.Action>> getSmartActionsFuture(
             String screenshotId, Uri screenshotUri, Bitmap image,
             ScreenshotNotificationSmartActionsProvider smartActionsProvider,
             boolean smartActionsEnabled, UserHandle userHandle) {
@@ -86,7 +93,7 @@
     }
 
     @VisibleForTesting
-    static List<Notification.Action> getSmartActions(String screenshotId,
+    List<Notification.Action> getSmartActions(String screenshotId,
             CompletableFuture<List<Notification.Action>> smartActionsFuture, int timeoutMs,
             ScreenshotNotificationSmartActionsProvider smartActionsProvider) {
         long startTimeMs = SystemClock.uptimeMillis();
@@ -116,7 +123,7 @@
         }
     }
 
-    static void notifyScreenshotOp(String screenshotId,
+    void notifyScreenshotOp(String screenshotId,
             ScreenshotNotificationSmartActionsProvider smartActionsProvider,
             ScreenshotNotificationSmartActionsProvider.ScreenshotOp op,
             ScreenshotNotificationSmartActionsProvider.ScreenshotOpStatus status, long durationMs) {
@@ -127,7 +134,7 @@
         }
     }
 
-    static void notifyScreenshotAction(Context context, String screenshotId, String action,
+    void notifyScreenshotAction(Context context, String screenshotId, String action,
             boolean isSmartAction) {
         try {
             ScreenshotNotificationSmartActionsProvider provider =
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/SmartActionsReceiver.java b/packages/SystemUI/src/com/android/systemui/screenshot/SmartActionsReceiver.java
new file mode 100644
index 0000000..217235b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/SmartActionsReceiver.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.screenshot;
+
+import static com.android.systemui.screenshot.GlobalScreenshot.EXTRA_ACTION_INTENT;
+import static com.android.systemui.screenshot.GlobalScreenshot.EXTRA_ACTION_TYPE;
+import static com.android.systemui.screenshot.GlobalScreenshot.EXTRA_ID;
+
+import android.app.ActivityOptions;
+import android.app.PendingIntent;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.util.Log;
+import android.util.Slog;
+
+import javax.inject.Inject;
+
+
+/**
+ * Executes the smart action tapped by the user in the notification.
+ */
+public class SmartActionsReceiver extends BroadcastReceiver {
+    private static final String TAG = "SmartActionsReceiver";
+
+    private final ScreenshotSmartActions mScreenshotSmartActions;
+
+    @Inject
+    SmartActionsReceiver(ScreenshotSmartActions screenshotSmartActions) {
+        mScreenshotSmartActions = screenshotSmartActions;
+    }
+
+    @Override
+    public void onReceive(Context context, Intent intent) {
+        PendingIntent pendingIntent = intent.getParcelableExtra(EXTRA_ACTION_INTENT);
+        String actionType = intent.getStringExtra(EXTRA_ACTION_TYPE);
+        Slog.d(TAG, "Executing smart action [" + actionType + "]:" + pendingIntent.getIntent());
+        ActivityOptions opts = ActivityOptions.makeBasic();
+
+        try {
+            pendingIntent.send(context, 0, null, null, null, null, opts.toBundle());
+        } catch (PendingIntent.CanceledException e) {
+            Log.e(TAG, "Pending intent canceled", e);
+        }
+
+        mScreenshotSmartActions.notifyScreenshotAction(
+                context, intent.getStringExtra(EXTRA_ID), actionType, true);
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java
index 9f8a9bb..a043f0f 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java
@@ -61,7 +61,7 @@
         @Override
         public void onReceive(Context context, Intent intent) {
             if (ACTION_CLOSE_SYSTEM_DIALOGS.equals(intent.getAction()) && mScreenshot != null) {
-                mScreenshot.dismissScreenshot("close system dialogs", true);
+                mScreenshot.dismissScreenshot("close system dialogs", false);
             }
         }
     };
@@ -102,7 +102,7 @@
 
             switch (msg.what) {
                 case WindowManager.TAKE_SCREENSHOT_FULLSCREEN:
-                    mScreenshot.takeScreenshot(uriConsumer, onComplete);
+                    mScreenshot.takeScreenshotFullscreen(uriConsumer, onComplete);
                     break;
                 case WindowManager.TAKE_SCREENSHOT_SELECTED_REGION:
                     mScreenshot.takeScreenshotPartial(uriConsumer, onComplete);
diff --git a/packages/SystemUI/src/com/android/systemui/settings/CurrentUserContentResolverProvider.kt b/packages/SystemUI/src/com/android/systemui/settings/CurrentUserContentResolverProvider.kt
new file mode 100644
index 0000000..9d05843
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/settings/CurrentUserContentResolverProvider.kt
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.settings
+
+import android.content.ContentResolver
+
+interface CurrentUserContentResolverProvider {
+
+    val currentUserContentResolver: ContentResolver
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/settings/CurrentUserContextTracker.kt b/packages/SystemUI/src/com/android/systemui/settings/CurrentUserContextTracker.kt
index 825a7f3..d7c4caaa 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/CurrentUserContextTracker.kt
+++ b/packages/SystemUI/src/com/android/systemui/settings/CurrentUserContextTracker.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.settings
 
+import android.content.ContentResolver
 import android.content.Context
 import android.os.UserHandle
 import androidx.annotation.VisibleForTesting
@@ -31,7 +32,7 @@
 class CurrentUserContextTracker internal constructor(
     private val sysuiContext: Context,
     broadcastDispatcher: BroadcastDispatcher
-) {
+) : CurrentUserContentResolverProvider {
     private val userTracker: CurrentUserTracker
     private var initialized = false
 
@@ -44,6 +45,9 @@
             return _curUserContext!!
         }
 
+    override val currentUserContentResolver: ContentResolver
+        get() = currentUserContext.contentResolver
+
     init {
         userTracker = object : CurrentUserTracker(broadcastDispatcher) {
             override fun onUserSwitched(newUserId: Int) {
@@ -54,8 +58,8 @@
 
     fun initialize() {
         initialized = true
-        _curUserContext = makeUserContext(userTracker.currentUserId)
         userTracker.startTracking()
+        _curUserContext = makeUserContext(userTracker.currentUserId)
     }
 
     @VisibleForTesting
diff --git a/packages/SystemUI/src/com/android/systemui/settings/dagger/SettingsModule.java b/packages/SystemUI/src/com/android/systemui/settings/dagger/SettingsModule.java
index 2c5c3ce..eb5bd5c 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/dagger/SettingsModule.java
+++ b/packages/SystemUI/src/com/android/systemui/settings/dagger/SettingsModule.java
@@ -19,10 +19,12 @@
 import android.content.Context;
 
 import com.android.systemui.broadcast.BroadcastDispatcher;
+import com.android.systemui.settings.CurrentUserContentResolverProvider;
 import com.android.systemui.settings.CurrentUserContextTracker;
 
 import javax.inject.Singleton;
 
+import dagger.Binds;
 import dagger.Module;
 import dagger.Provides;
 
@@ -30,7 +32,7 @@
  * Dagger Module for classes found within the com.android.systemui.settings package.
  */
 @Module
-public interface SettingsModule {
+public abstract class SettingsModule {
 
     /**
      * Provides and initializes a CurrentUserContextTracker
@@ -45,4 +47,9 @@
         tracker.initialize();
         return tracker;
     }
+
+    @Binds
+    @Singleton
+    abstract CurrentUserContentResolverProvider bindCurrentUserContentResolverTracker(
+            CurrentUserContextTracker tracker);
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
index a144453..7e1dc66 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
@@ -140,7 +140,7 @@
      * Creates a new KeyguardIndicationController and registers callbacks.
      */
     @Inject
-    KeyguardIndicationController(Context context,
+    public KeyguardIndicationController(Context context,
             WakeLock.Builder wakeLockBuilder,
             KeyguardStateController keyguardStateController,
             StatusBarStateController statusBarStateController,
@@ -523,8 +523,7 @@
                 });
     }
 
-    @VisibleForTesting
-    String computePowerIndication() {
+    protected String computePowerIndication() {
         if (mPowerCharged) {
             return mContext.getResources().getString(R.string.keyguard_charged);
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java
index 739d30c..38e1de3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java
@@ -21,6 +21,7 @@
 import static com.android.systemui.statusbar.phone.StatusBar.SHOW_LOCKSCREEN_MEDIA_ARTWORK;
 
 import android.annotation.MainThread;
+import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.Notification;
 import android.content.Context;
@@ -39,6 +40,7 @@
 import android.os.UserHandle;
 import android.provider.DeviceConfig;
 import android.provider.DeviceConfig.Properties;
+import android.service.notification.NotificationListenerService;
 import android.util.ArraySet;
 import android.util.Log;
 import android.view.View;
@@ -51,12 +53,14 @@
 import com.android.systemui.Interpolators;
 import com.android.systemui.colorextraction.SysuiColorExtractor;
 import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.media.MediaData;
 import com.android.systemui.media.MediaDataManager;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.statusbar.dagger.StatusBarModule;
 import com.android.systemui.statusbar.notification.NotificationEntryListener;
 import com.android.systemui.statusbar.notification.NotificationEntryManager;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener;
 import com.android.systemui.statusbar.phone.BiometricUnlockController;
 import com.android.systemui.statusbar.phone.KeyguardBypassController;
 import com.android.systemui.statusbar.phone.LockscreenWallpaper;
@@ -234,8 +238,35 @@
                     NotificationVisibility visibility,
                     boolean removedByUser,
                     int reason) {
-                onNotificationRemoved(entry.getKey());
-                mediaDataManager.onNotificationRemoved(entry.getKey());
+                removeEntry(entry);
+            }
+        });
+
+        // Pending entries are never inflated, and will never generate a call to onEntryRemoved().
+        // This can happen when notifications are added and canceled before inflation. Add this
+        // separate listener for cleanup, since media inflation occurs onPendingEntryAdded().
+        notificationEntryManager.addCollectionListener(new NotifCollectionListener() {
+            @Override
+            public void onEntryCleanUp(@NonNull NotificationEntry entry) {
+                removeEntry(entry);
+            }
+        });
+
+        mMediaDataManager.addListener(new MediaDataManager.Listener() {
+            @Override
+            public void onMediaDataLoaded(@NonNull String key,
+                    @Nullable String oldKey, @NonNull MediaData data) {
+            }
+
+            @Override
+            public void onMediaDataRemoved(@NonNull String key) {
+                NotificationEntry entry = mEntryManager.getPendingOrActiveNotif(key);
+                if (entry != null) {
+                    // TODO(b/160713608): "removing" this notification won't happen and
+                    //  won't send the 'deleteIntent' if the notification is ongoing.
+                    mEntryManager.performRemoveNotification(entry.getSbn(),
+                            NotificationListenerService.REASON_CANCEL);
+                }
             }
         });
 
@@ -248,6 +279,11 @@
                 mPropertiesChangedListener);
     }
 
+    private void removeEntry(NotificationEntry entry) {
+        onNotificationRemoved(entry.getKey());
+        mMediaDataManager.onNotificationRemoved(entry.getKey());
+    }
+
     /**
      * Check if a state should be considered actively playing
      * @param state a PlaybackState
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java
index d04389d..9fd729f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java
@@ -574,6 +574,7 @@
         NotificationEntry entry = mPendingNotifications.get(key);
         if (entry != null) {
             entry.setSbn(notification);
+            entry.setRanking(ranking);
         } else {
             entry = new NotificationEntry(
                     notification,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
index 423f85f..bd65ef0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
@@ -282,7 +282,7 @@
                     + " doesn't match existing key " + mKey);
         }
 
-        mRanking = ranking;
+        mRanking = ranking.withAudiblyAlertedInfo(mRanking);
     }
 
     /*
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinator.java
index e9cbf32..943ace9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinator.java
@@ -32,8 +32,6 @@
  */
 @Singleton
 public class RankingCoordinator implements Coordinator {
-    private static final String TAG = "RankingNotificationCoordinator";
-
     private final StatusBarStateController mStatusBarStateController;
 
     @Inject
@@ -46,7 +44,7 @@
         mStatusBarStateController.addCallback(mStatusBarStateCallback);
 
         pipeline.addPreGroupFilter(mSuspendedFilter);
-        pipeline.addPreGroupFilter(mDozingFilter);
+        pipeline.addPreGroupFilter(mDndVisualEffectsFilter);
     }
 
     /**
@@ -61,10 +59,10 @@
         }
     };
 
-    private final NotifFilter mDozingFilter = new NotifFilter("IsDozingFilter") {
+    private final NotifFilter mDndVisualEffectsFilter = new NotifFilter(
+            "DndSuppressingVisualEffects") {
         @Override
         public boolean shouldFilterOut(NotificationEntry entry, long now) {
-            // Dozing + DND Settings from Ranking object
             if (mStatusBarStateController.isDozing() && entry.shouldSuppressAmbient()) {
                 return true;
             }
@@ -77,7 +75,7 @@
             new StatusBarStateController.StateListener() {
                 @Override
                 public void onDozingChanged(boolean isDozing) {
-                    mDozingFilter.invalidateList();
+                    mDndVisualEffectsFilter.invalidateList();
                 }
             };
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
index 94e12e8..70dd80e7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
@@ -504,7 +504,7 @@
 
     /**
      * Returns whether this row is considered non-blockable (i.e. it's a non-blockable system notif
-     * or is in a whitelist).
+     * or is in an allowList).
      */
     public boolean getIsNonblockable() {
         boolean isNonblockable = Dependency.get(NotificationBlockingHelperManager.class)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java
index 582e3e5..a7d83b3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java
@@ -37,6 +37,8 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.widget.ImageMessageConsumer;
 import com.android.systemui.dagger.qualifiers.Background;
+import com.android.systemui.media.MediaDataManagerKt;
+import com.android.systemui.media.MediaFeatureFlag;
 import com.android.systemui.statusbar.InflationTask;
 import com.android.systemui.statusbar.NotificationRemoteInputManager;
 import com.android.systemui.statusbar.SmartReplyController;
@@ -71,6 +73,7 @@
     public static final String TAG = "NotifContentInflater";
 
     private boolean mInflateSynchronously = false;
+    private final boolean mIsMediaInQS;
     private final NotificationRemoteInputManager mRemoteInputManager;
     private final NotifRemoteViewCache mRemoteViewCache;
     private final Lazy<SmartReplyConstants> mSmartReplyConstants;
@@ -85,12 +88,14 @@
             Lazy<SmartReplyConstants> smartReplyConstants,
             Lazy<SmartReplyController> smartReplyController,
             ConversationNotificationProcessor conversationProcessor,
+            MediaFeatureFlag mediaFeatureFlag,
             @Background Executor bgExecutor) {
         mRemoteViewCache = remoteViewCache;
         mRemoteInputManager = remoteInputManager;
         mSmartReplyConstants = smartReplyConstants;
         mSmartReplyController = smartReplyController;
         mConversationProcessor = conversationProcessor;
+        mIsMediaInQS = mediaFeatureFlag.getEnabled();
         mBgExecutor = bgExecutor;
     }
 
@@ -135,7 +140,8 @@
                 bindParams.usesIncreasedHeight,
                 bindParams.usesIncreasedHeadsUpHeight,
                 callback,
-                mRemoteInputManager.getRemoteViewsOnClickHandler());
+                mRemoteInputManager.getRemoteViewsOnClickHandler(),
+                mIsMediaInQS);
         if (mInflateSynchronously) {
             task.onPostExecute(task.doInBackground());
         } else {
@@ -711,6 +717,7 @@
         private RemoteViews.OnClickHandler mRemoteViewClickHandler;
         private CancellationSignal mCancellationSignal;
         private final ConversationNotificationProcessor mConversationProcessor;
+        private final boolean mIsMediaInQS;
 
         private AsyncInflationTask(
                 Executor bgExecutor,
@@ -726,7 +733,8 @@
                 boolean usesIncreasedHeight,
                 boolean usesIncreasedHeadsUpHeight,
                 InflationCallback callback,
-                RemoteViews.OnClickHandler remoteViewClickHandler) {
+                RemoteViews.OnClickHandler remoteViewClickHandler,
+                boolean isMediaFlagEnabled) {
             mEntry = entry;
             mRow = row;
             mSmartReplyConstants = smartReplyConstants;
@@ -742,6 +750,7 @@
             mRemoteViewClickHandler = remoteViewClickHandler;
             mCallback = callback;
             mConversationProcessor = conversationProcessor;
+            mIsMediaInQS = isMediaFlagEnabled;
             entry.setInflationTask(this);
         }
 
@@ -765,7 +774,8 @@
                     packageContext = new RtlEnabledContext(packageContext);
                 }
                 Notification notification = sbn.getNotification();
-                if (notification.isMediaNotification()) {
+                if (notification.isMediaNotification() && !(mIsMediaInQS
+                        && MediaDataManagerKt.isMediaNotification(sbn))) {
                     MediaNotificationProcessor processor = new MediaNotificationProcessor(mContext,
                             packageContext);
                     processor.processNotification(notification, recoveredBuilder);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfo.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfo.java
index f543db7..25a0ea5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfo.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfo.java
@@ -42,6 +42,8 @@
 import android.content.pm.PackageManager;
 import android.content.pm.ShortcutInfo;
 import android.content.pm.ShortcutManager;
+import android.content.res.TypedArray;
+import android.graphics.drawable.Drawable;
 import android.os.Handler;
 import android.os.RemoteException;
 import android.os.UserHandle;
@@ -539,12 +541,21 @@
                 && Settings.Global.getInt(mContext.getContentResolver(),
                         NOTIFICATION_BUBBLES, 0) == 1;
 
+        Drawable person =  mIconFactory.getBaseIconDrawable(mShortcutInfo);
+        if (person == null) {
+            person = mContext.getDrawable(R.drawable.ic_person).mutate();
+            TypedArray ta = mContext.obtainStyledAttributes(new int[]{android.R.attr.colorAccent});
+            int colorAccent = ta.getColor(0, 0);
+            ta.recycle();
+            person.setTint(colorAccent);
+        }
+
         PriorityOnboardingDialogController controller = mBuilderProvider.get()
                 .setContext(mUserContext)
                 .setView(onboardingView)
                 .setIgnoresDnd(ignoreDnd)
                 .setShowsAsBubble(showAsBubble)
-                .setIcon(mIconFactory.getBaseIconDrawable(mShortcutInfo))
+                .setIcon(person)
                 .setBadge(mIconFactory.getAppBadge(
                         mPackageName, UserHandle.getUserId(mSbn.getUid())))
                 .setOnSettingsClick(mOnConversationSettingsClickListener)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.kt
index 56238d0..4699ace 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.kt
@@ -41,8 +41,8 @@
 import com.android.systemui.statusbar.notification.stack.StackScrollAlgorithm.SectionProvider
 import com.android.systemui.statusbar.policy.ConfigurationController
 import com.android.systemui.util.children
-import com.android.systemui.util.takeUntil
 import com.android.systemui.util.foldToSparseArray
+import com.android.systemui.util.takeUntil
 import javax.inject.Inject
 
 /**
@@ -166,6 +166,9 @@
         peopleHubSubscription?.unsubscribe()
         peopleHubSubscription = null
         peopleHeaderView = reinflateView(peopleHeaderView, layoutInflater, R.layout.people_strip)
+                .apply {
+                    setOnHeaderClickListener(View.OnClickListener { onPeopleHeaderClick() })
+                }
         if (ENABLE_SNOOZED_CONVERSATION_HUB) {
             peopleHubSubscription = peopleHubViewAdapter.bindView(peopleHubViewBoundary)
         }
@@ -519,6 +522,15 @@
                 Intent.FLAG_ACTIVITY_SINGLE_TOP)
     }
 
+    private fun onPeopleHeaderClick() {
+        val intent = Intent(Settings.ACTION_CONVERSATION_SETTINGS)
+        activityStarter.startActivity(
+                intent,
+                true,
+                true,
+                Intent.FLAG_ACTIVITY_SINGLE_TOP)
+    }
+
     private fun onClearGentleNotifsClick(v: View) {
         onClearSilentNotifsClickListener?.onClick(v)
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/PeopleHubView.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/PeopleHubView.kt
index 8f77a1d..b13e7fb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/PeopleHubView.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/PeopleHubView.kt
@@ -93,6 +93,8 @@
         }
     }
 
+    fun setOnHeaderClickListener(listener: OnClickListener) = label.setOnClickListener(listener)
+
     private inner class PersonDataListenerImpl(val avatarView: ImageView) :
             DataListener<PersonViewModel?> {
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
index 541c784..7447335 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
@@ -29,6 +29,7 @@
 import com.android.systemui.statusbar.EmptyShadeView;
 import com.android.systemui.statusbar.NotificationShelf;
 import com.android.systemui.statusbar.notification.NotificationUtils;
+import com.android.systemui.statusbar.notification.row.ActivatableNotificationView;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 import com.android.systemui.statusbar.notification.row.ExpandableView;
 import com.android.systemui.statusbar.notification.row.FooterView;
@@ -687,15 +688,27 @@
             AmbientState ambientState) {
         int childCount = algorithmState.visibleChildren.size();
         float childrenOnTop = 0.0f;
+
+        int topHunIndex = -1;
+        for (int i = 0; i < childCount; i++) {
+            ExpandableView child = algorithmState.visibleChildren.get(i);
+            if (child instanceof ActivatableNotificationView
+                    && (child.isAboveShelf() || child.showingPulsing())) {
+                topHunIndex = i;
+                break;
+            }
+        }
+
         for (int i = childCount - 1; i >= 0; i--) {
             childrenOnTop = updateChildZValue(i, childrenOnTop,
-                    algorithmState, ambientState);
+                    algorithmState, ambientState, i == topHunIndex);
         }
     }
 
     protected float updateChildZValue(int i, float childrenOnTop,
             StackScrollAlgorithmState algorithmState,
-            AmbientState ambientState) {
+            AmbientState ambientState,
+            boolean shouldElevateHun) {
         ExpandableView child = algorithmState.visibleChildren.get(i);
         ExpandableViewState childViewState = child.getViewState();
         int zDistanceBetweenElements = ambientState.getZDistanceBetweenElements();
@@ -713,8 +726,7 @@
             }
             childViewState.zTranslation = baseZ
                     + childrenOnTop * zDistanceBetweenElements;
-        } else if (child == ambientState.getTrackedHeadsUpRow()
-                || (i == 0 && (child.isAboveShelf() || child.showingPulsing()))) {
+        } else if (shouldElevateHun) {
             // In case this is a new view that has never been measured before, we don't want to
             // elevate if we are currently expanded more then the notification
             int shelfHeight = ambientState.getShelf() == null ? 0 :
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/EdgeBackGestureHandler.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/EdgeBackGestureHandler.java
index 304fe00..00a932cb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/EdgeBackGestureHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/EdgeBackGestureHandler.java
@@ -231,7 +231,6 @@
             }
         }
 
-        Dependency.get(ProtoTracer.class).add(this);
         mLongPressTimeout = Math.min(MAX_LONG_PRESS_TIMEOUT,
                 ViewConfiguration.getLongPressTimeout());
 
@@ -286,6 +285,7 @@
      */
     public void onNavBarAttached() {
         mIsAttached = true;
+        Dependency.get(ProtoTracer.class).add(this);
         mOverviewProxyService.addCallback(mQuickSwitchListener);
         updateIsEnabled();
         startTracking();
@@ -296,6 +296,7 @@
      */
     public void onNavBarDetached() {
         mIsAttached = false;
+        Dependency.get(ProtoTracer.class).remove(this);
         mOverviewProxyService.removeCallback(mQuickSwitchListener);
         updateIsEnabled();
         stopTracking();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/FloatingRotationButton.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/FloatingRotationButton.java
index 16b5a23..78742f1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/FloatingRotationButton.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/FloatingRotationButton.java
@@ -33,6 +33,8 @@
 import com.android.systemui.statusbar.policy.KeyButtonDrawable;
 import com.android.systemui.statusbar.policy.KeyButtonView;
 
+import java.util.function.Consumer;
+
 /** Containing logic for the rotation button on the physical left bottom corner of the screen. */
 public class FloatingRotationButton implements RotationButton {
 
@@ -48,6 +50,7 @@
     private boolean mCanShow = true;
 
     private RotationButtonController mRotationButtonController;
+    private Consumer<Boolean> mVisibilityChangedCallback;
 
     FloatingRotationButton(Context context) {
         mContext = context;
@@ -68,6 +71,11 @@
     }
 
     @Override
+    public void setVisibilityChangedCallback(Consumer<Boolean> visibilityChangedCallback) {
+        mVisibilityChangedCallback = visibilityChangedCallback;
+    }
+
+    @Override
     public View getCurrentView() {
         return mKeyButtonView;
     }
@@ -107,6 +115,16 @@
             mKeyButtonDrawable.resetAnimation();
             mKeyButtonDrawable.startAnimation();
         }
+        mKeyButtonView.addOnLayoutChangeListener(new View.OnLayoutChangeListener() {
+            @Override
+            public void onLayoutChange(View view, int i, int i1, int i2, int i3, int i4, int i5,
+                    int i6, int i7) {
+                if (mIsShowing && mVisibilityChangedCallback != null) {
+                    mVisibilityChangedCallback.accept(true);
+                }
+                mKeyButtonView.removeOnLayoutChangeListener(this);
+            }
+        });
         return true;
     }
 
@@ -117,6 +135,9 @@
         }
         mWindowManager.removeViewImmediate(mKeyButtonView);
         mIsShowing = false;
+        if (mVisibilityChangedCallback != null) {
+            mVisibilityChangedCallback.accept(false);
+        }
         return true;
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java
index 27daf86..e60293c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java
@@ -192,6 +192,7 @@
     private int mLayoutDirection;
 
     private boolean mForceNavBarHandleOpaque;
+    private boolean mIsCurrentUserSetup;
 
     /** @see android.view.WindowInsetsController#setSystemBarsAppearance(int) */
     private @Appearance int mAppearance;
@@ -311,6 +312,10 @@
 
         @Override
         public void onNavBarButtonAlphaChanged(float alpha, boolean animate) {
+            if (!mIsCurrentUserSetup) {
+                // If the current user is not yet setup, then don't update any button alphas
+                return;
+            }
             ButtonDispatcher buttonDispatcher = null;
             boolean forceVisible = false;
             if (QuickStepContract.isSwipeUpMode(mNavBarMode)) {
@@ -349,14 +354,6 @@
                 }
             };
 
-    private final ContextButtonListener mRotationButtonListener = (button, visible) -> {
-        if (visible) {
-            // If the button will actually become visible and the navbar is about to hide,
-            // tell the statusbar to keep it around for longer
-            mAutoHideController.touchAutoHide();
-        }
-    };
-
     private final Runnable mAutoDim = () -> getBarTransitions().setAutoDim(true);
 
     private final ContentObserver mAssistContentObserver = new ContentObserver(
@@ -383,6 +380,14 @@
         }
     };
 
+    private final DeviceProvisionedController.DeviceProvisionedListener mUserSetupListener =
+            new DeviceProvisionedController.DeviceProvisionedListener() {
+        @Override
+        public void onUserSetupChanged() {
+            mIsCurrentUserSetup = mDeviceProvisionedController.isCurrentUserSetup();
+        }
+    };
+
     @Inject
     public NavigationBarFragment(AccessibilityManagerWrapper accessibilityManagerWrapper,
             DeviceProvisionedController deviceProvisionedController, MetricsLogger metricsLogger,
@@ -450,6 +455,9 @@
                 /* defaultValue = */ true);
         DeviceConfig.addOnPropertiesChangedListener(
                 DeviceConfig.NAMESPACE_SYSTEMUI, mHandler::post, mOnPropertiesChangedListener);
+
+        mIsCurrentUserSetup = mDeviceProvisionedController.isCurrentUserSetup();
+        mDeviceProvisionedController.addCallback(mUserSetupListener);
     }
 
     @Override
@@ -458,6 +466,7 @@
         mNavigationModeController.removeListener(this);
         mAccessibilityManagerWrapper.removeCallback(mAccessibilityListener);
         mContentResolver.unregisterContentObserver(mAssistContentObserver);
+        mDeviceProvisionedController.removeCallback(mUserSetupListener);
 
         DeviceConfig.removeOnPropertiesChangedListener(mOnPropertiesChangedListener);
     }
@@ -504,8 +513,6 @@
 
         // Currently there is no accelerometer sensor on non-default display.
         if (mIsOnDefaultDisplay) {
-            mNavigationBarView.getRotateSuggestionButton().setListener(mRotationButtonListener);
-
             final RotationButtonController rotationButtonController =
                     mNavigationBarView.getRotationButtonController();
             rotationButtonController.addRotationCallback(mRotationWatcher);
@@ -591,6 +598,7 @@
                 .registerDisplayListener(this, new Handler(Looper.getMainLooper()));
 
         mOrientationHandle = new QuickswitchOrientedNavHandle(getContext());
+        mOrientationHandle.setId(R.id.secondary_home_handle);
 
         getBarTransitions().addDarkIntensityListener(mOrientationHandleIntensityListener);
         mOrientationParams = new WindowManager.LayoutParams(0, 0,
@@ -1297,6 +1305,7 @@
         if (mAutoHideController != null) {
             mAutoHideController.setNavigationBar(mAutoHideUiElement);
         }
+        mNavigationBarView.setAutoHideController(autoHideController);
     }
 
     private boolean isTransientShown() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
index 1eab427..5bb3c83 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
@@ -113,12 +113,9 @@
     int mNavigationIconHints = 0;
     private int mNavBarMode;
 
-    private Rect mHomeButtonBounds = new Rect();
-    private Rect mBackButtonBounds = new Rect();
-    private Rect mRecentsButtonBounds = new Rect();
-    private Rect mRotationButtonBounds = new Rect();
-    private final Region mActiveRegion = new Region();
-    private int[] mTmpPosition = new int[2];
+    private final Region mTmpRegion = new Region();
+    private final int[] mTmpPosition = new int[2];
+    private Rect mTmpBounds = new Rect();
 
     private KeyButtonDrawable mBackIcon;
     private KeyButtonDrawable mHomeDefaultIcon;
@@ -130,6 +127,7 @@
     private boolean mDeadZoneConsuming = false;
     private final NavigationBarTransitions mBarTransitions;
     private final OverviewProxyService mOverviewProxyService;
+    private AutoHideController mAutoHideController;
 
     // performs manual animation in sync with layout transitions
     private final NavTransitionListener mTransitionListener = new NavTransitionListener();
@@ -255,24 +253,23 @@
     private final OnComputeInternalInsetsListener mOnComputeInternalInsetsListener = info -> {
         // When the nav bar is in 2-button or 3-button mode, or when IME is visible in fully
         // gestural mode, the entire nav bar should be touchable.
-        if (!mEdgeBackGestureHandler.isHandlingGestures() || mImeVisible) {
+        if (!mEdgeBackGestureHandler.isHandlingGestures()) {
             info.setTouchableInsets(InternalInsetsInfo.TOUCHABLE_INSETS_FRAME);
             return;
         }
 
         info.setTouchableInsets(InternalInsetsInfo.TOUCHABLE_INSETS_REGION);
-        ButtonDispatcher imeSwitchButton = getImeSwitchButton();
-        if (imeSwitchButton.getVisibility() == VISIBLE) {
-            // If the IME is not up, but the ime switch button is visible, then make sure that
-            // button is touchable
-            int[] loc = new int[2];
-            View buttonView = imeSwitchButton.getCurrentView();
-            buttonView.getLocationInWindow(loc);
-            info.touchableRegion.set(loc[0], loc[1], loc[0] + buttonView.getWidth(),
-                    loc[1] + buttonView.getHeight());
-            return;
+        info.touchableRegion.set(getButtonLocations(false /* includeFloatingRotationButton */,
+                false /* inScreen */));
+    };
+
+    private final Consumer<Boolean> mRotationButtonListener = (visible) -> {
+        if (visible) {
+            // If the button will actually become visible and the navbar is about to hide,
+            // tell the statusbar to keep it around for longer
+            mAutoHideController.touchAutoHide();
         }
-        info.touchableRegion.setEmpty();
+        notifyActiveTouchRegions();
     };
 
     public NavigationBarView(Context context, AttributeSet attrs) {
@@ -305,7 +302,8 @@
         mFloatingRotationButton = new FloatingRotationButton(context);
         mRotationButtonController = new RotationButtonController(context,
                 R.style.RotateButtonCCWStart90,
-                isGesturalMode ? mFloatingRotationButton : rotateSuggestionButton);
+                isGesturalMode ? mFloatingRotationButton : rotateSuggestionButton,
+                mRotationButtonListener);
 
         mConfiguration = new Configuration();
         mTmpLastConfiguration = new Configuration();
@@ -353,6 +351,10 @@
                 });
     }
 
+    public void setAutoHideController(AutoHideController autoHideController) {
+        mAutoHideController = autoHideController;
+    }
+
     public NavigationBarTransitions getBarTransitions() {
         return mBarTransitions;
     }
@@ -709,6 +711,7 @@
         getHomeButton().setVisibility(disableHome       ? View.INVISIBLE : View.VISIBLE);
         getRecentsButton().setVisibility(disableRecent  ? View.INVISIBLE : View.VISIBLE);
         getHomeHandle().setVisibility(disableHomeHandle ? View.INVISIBLE : View.VISIBLE);
+        notifyActiveTouchRegions();
     }
 
     @VisibleForTesting
@@ -927,42 +930,52 @@
     protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
         super.onLayout(changed, left, top, right, bottom);
 
-        mActiveRegion.setEmpty();
-        updateButtonLocation(getBackButton(), mBackButtonBounds, true);
-        updateButtonLocation(getHomeButton(), mHomeButtonBounds, false);
-        updateButtonLocation(getRecentsButton(), mRecentsButtonBounds, false);
-        updateButtonLocation(getRotateSuggestionButton(), mRotationButtonBounds, true);
-        // TODO: Handle button visibility changes
-        mOverviewProxyService.onActiveNavBarRegionChanges(mActiveRegion);
+        notifyActiveTouchRegions();
         mRecentsOnboarding.setNavBarHeight(getMeasuredHeight());
     }
 
-    private void updateButtonLocation(ButtonDispatcher button, Rect buttonBounds,
-            boolean isActive) {
+    /**
+     * Notifies the overview service of the active touch regions.
+     */
+    public void notifyActiveTouchRegions() {
+        mOverviewProxyService.onActiveNavBarRegionChanges(
+                getButtonLocations(true /* includeFloatingRotationButton */, true /* inScreen */));
+    }
+
+    private Region getButtonLocations(boolean includeFloatingRotationButton,
+            boolean inScreenSpace) {
+        mTmpRegion.setEmpty();
+        updateButtonLocation(getBackButton(), inScreenSpace);
+        updateButtonLocation(getHomeButton(), inScreenSpace);
+        updateButtonLocation(getRecentsButton(), inScreenSpace);
+        updateButtonLocation(getImeSwitchButton(), inScreenSpace);
+        updateButtonLocation(getAccessibilityButton(), inScreenSpace);
+        if (includeFloatingRotationButton && mFloatingRotationButton.isVisible()) {
+            updateButtonLocation(mFloatingRotationButton.getCurrentView(), inScreenSpace);
+        } else {
+            updateButtonLocation(getRotateSuggestionButton(), inScreenSpace);
+        }
+        return mTmpRegion;
+    }
+
+    private void updateButtonLocation(ButtonDispatcher button, boolean inScreenSpace) {
         View view = button.getCurrentView();
-        if (view == null) {
-            buttonBounds.setEmpty();
+        if (view == null || !button.isVisible()) {
             return;
         }
-        // Temporarily reset the translation back to origin to get the position in window
-        final float posX = view.getTranslationX();
-        final float posY = view.getTranslationY();
-        view.setTranslationX(0);
-        view.setTranslationY(0);
+        updateButtonLocation(view, inScreenSpace);
+    }
 
-        if (isActive) {
-            view.getLocationOnScreen(mTmpPosition);
-            buttonBounds.set(mTmpPosition[0], mTmpPosition[1],
-                    mTmpPosition[0] + view.getMeasuredWidth(),
-                    mTmpPosition[1] + view.getMeasuredHeight());
-            mActiveRegion.op(buttonBounds, Op.UNION);
+    private void updateButtonLocation(View view, boolean inScreenSpace) {
+        if (inScreenSpace) {
+            view.getBoundsOnScreen(mTmpBounds);
+        } else {
+            view.getLocationInWindow(mTmpPosition);
+            mTmpBounds.set(mTmpPosition[0], mTmpPosition[1],
+                    mTmpPosition[0] + view.getWidth(),
+                    mTmpPosition[1] + view.getHeight());
         }
-        view.getLocationInWindow(mTmpPosition);
-        buttonBounds.set(mTmpPosition[0], mTmpPosition[1],
-                mTmpPosition[0] + view.getMeasuredWidth(),
-                mTmpPosition[1] + view.getMeasuredHeight());
-        view.setTranslationX(posX);
-        view.setTranslationY(posY);
+        mTmpRegion.op(mTmpBounds, Op.UNION);
     }
 
     private void updateOrientationViews() {
@@ -1223,6 +1236,7 @@
         dumpButton(pw, "rcnt", getRecentsButton());
         dumpButton(pw, "rota", getRotateSuggestionButton());
         dumpButton(pw, "a11y", getAccessibilityButton());
+        dumpButton(pw, "ime", getImeSwitchButton());
 
         pw.println("    }");
         pw.println("    mScreenOn: " + mScreenOn);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
index 64202d2..799e16c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
@@ -2612,6 +2612,7 @@
         super.onClosingFinished();
         resetHorizontalPanelPosition();
         setClosingWithAlphaFadeout(false);
+        mMediaHierarchyManager.closeGuts();
     }
 
     private void setClosingWithAlphaFadeout(boolean closing) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
index 5bb8fab..4e71a7e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
@@ -47,6 +47,9 @@
 import com.android.systemui.dagger.qualifiers.DisplayId;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.dagger.qualifiers.UiBackground;
+import com.android.systemui.privacy.PrivacyItem;
+import com.android.systemui.privacy.PrivacyItemController;
+import com.android.systemui.privacy.PrivacyType;
 import com.android.systemui.qs.tiles.DndTile;
 import com.android.systemui.qs.tiles.RotationLockTile;
 import com.android.systemui.screenrecord.RecordingController;
@@ -70,6 +73,9 @@
 import com.android.systemui.util.RingerModeTracker;
 import com.android.systemui.util.time.DateFormatUtil;
 
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.util.List;
 import java.util.Locale;
 import java.util.concurrent.Executor;
 
@@ -87,13 +93,13 @@
                 ZenModeController.Callback,
                 DeviceProvisionedListener,
                 KeyguardStateController.Callback,
+                PrivacyItemController.Callback,
                 LocationController.LocationChangeCallback,
                 RecordingController.RecordingStateChangeCallback {
     private static final String TAG = "PhoneStatusBarPolicy";
     private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
 
-    static final int LOCATION_STATUS_ICON_ID =
-            com.android.internal.R.drawable.perm_group_location;
+    static final int LOCATION_STATUS_ICON_ID = PrivacyType.TYPE_LOCATION.getIconId();
 
     private final String mSlotCast;
     private final String mSlotHotspot;
@@ -107,6 +113,8 @@
     private final String mSlotHeadset;
     private final String mSlotDataSaver;
     private final String mSlotLocation;
+    private final String mSlotMicrophone;
+    private final String mSlotCamera;
     private final String mSlotSensorsOff;
     private final String mSlotScreenRecord;
     private final int mDisplayId;
@@ -132,6 +140,7 @@
     private final DeviceProvisionedController mProvisionedController;
     private final KeyguardStateController mKeyguardStateController;
     private final LocationController mLocationController;
+    private final PrivacyItemController mPrivacyItemController;
     private final Executor mUiBgExecutor;
     private final SensorPrivacyController mSensorPrivacyController;
     private final RecordingController mRecordingController;
@@ -162,7 +171,8 @@
             RecordingController recordingController,
             @Nullable TelecomManager telecomManager, @DisplayId int displayId,
             @Main SharedPreferences sharedPreferences, DateFormatUtil dateFormatUtil,
-            RingerModeTracker ringerModeTracker) {
+            RingerModeTracker ringerModeTracker,
+            PrivacyItemController privacyItemController) {
         mIconController = iconController;
         mCommandQueue = commandQueue;
         mBroadcastDispatcher = broadcastDispatcher;
@@ -181,6 +191,7 @@
         mProvisionedController = deviceProvisionedController;
         mKeyguardStateController = keyguardStateController;
         mLocationController = locationController;
+        mPrivacyItemController = privacyItemController;
         mSensorPrivacyController = sensorPrivacyController;
         mRecordingController = recordingController;
         mUiBgExecutor = uiBgExecutor;
@@ -200,6 +211,8 @@
         mSlotHeadset = resources.getString(com.android.internal.R.string.status_bar_headset);
         mSlotDataSaver = resources.getString(com.android.internal.R.string.status_bar_data_saver);
         mSlotLocation = resources.getString(com.android.internal.R.string.status_bar_location);
+        mSlotMicrophone = resources.getString(com.android.internal.R.string.status_bar_microphone);
+        mSlotCamera = resources.getString(com.android.internal.R.string.status_bar_camera);
         mSlotSensorsOff = resources.getString(com.android.internal.R.string.status_bar_sensors_off);
         mSlotScreenRecord = resources.getString(
                 com.android.internal.R.string.status_bar_screen_record);
@@ -271,6 +284,22 @@
                 mResources.getString(R.string.accessibility_data_saver_on));
         mIconController.setIconVisibility(mSlotDataSaver, false);
 
+
+        // privacy items
+        String microphoneString = mResources.getString(PrivacyType.TYPE_MICROPHONE.getNameId());
+        String microphoneDesc = mResources.getString(
+                R.string.ongoing_privacy_chip_content_multiple_apps, microphoneString);
+        mIconController.setIcon(mSlotMicrophone, PrivacyType.TYPE_MICROPHONE.getIconId(),
+                microphoneDesc);
+        mIconController.setIconVisibility(mSlotMicrophone, false);
+
+        String cameraString = mResources.getString(PrivacyType.TYPE_CAMERA.getNameId());
+        String cameraDesc = mResources.getString(
+                R.string.ongoing_privacy_chip_content_multiple_apps, cameraString);
+        mIconController.setIcon(mSlotCamera, PrivacyType.TYPE_CAMERA.getIconId(),
+                cameraDesc);
+        mIconController.setIconVisibility(mSlotCamera, false);
+
         mIconController.setIcon(mSlotLocation, LOCATION_STATUS_ICON_ID,
                 mResources.getString(R.string.accessibility_location_active));
         mIconController.setIconVisibility(mSlotLocation, false);
@@ -294,6 +323,7 @@
         mNextAlarmController.addCallback(mNextAlarmCallback);
         mDataSaver.addCallback(this);
         mKeyguardStateController.addCallback(this);
+        mPrivacyItemController.addCallback(this);
         mSensorPrivacyController.addCallback(mSensorPrivacyListener);
         mLocationController.addCallback(this);
         mRecordingController.addCallback(this);
@@ -609,13 +639,52 @@
         mIconController.setIconVisibility(mSlotDataSaver, isDataSaving);
     }
 
+    @Override  // PrivacyItemController.Callback
+    public void onPrivacyItemsChanged(List<PrivacyItem> privacyItems) {
+        updatePrivacyItems(privacyItems);
+    }
+
+    private void updatePrivacyItems(List<PrivacyItem> items) {
+        boolean showCamera = false;
+        boolean showMicrophone = false;
+        boolean showLocation = false;
+        for (PrivacyItem item : items) {
+            if (item == null /* b/124234367 */) {
+                if (DEBUG) {
+                    Log.e(TAG, "updatePrivacyItems - null item found");
+                    StringWriter out = new StringWriter();
+                    mPrivacyItemController.dump(null, new PrintWriter(out), null);
+                    Log.e(TAG, out.toString());
+                }
+                continue;
+            }
+            switch (item.getPrivacyType()) {
+                case TYPE_CAMERA:
+                    showCamera = true;
+                    break;
+                case TYPE_LOCATION:
+                    showLocation = true;
+                    break;
+                case TYPE_MICROPHONE:
+                    showMicrophone = true;
+                    break;
+            }
+        }
+
+        mIconController.setIconVisibility(mSlotCamera, showCamera);
+        mIconController.setIconVisibility(mSlotMicrophone, showMicrophone);
+        if (mPrivacyItemController.getAllIndicatorsAvailable()) {
+            mIconController.setIconVisibility(mSlotLocation, showLocation);
+        }
+    }
+
     @Override
     public void onLocationActiveChanged(boolean active) {
-        updateLocation();
+        if (!mPrivacyItemController.getAllIndicatorsAvailable()) updateLocationFromController();
     }
 
     // Updates the status view based on the current state of location requests.
-    private void updateLocation() {
+    private void updateLocationFromController() {
         if (mLocationController.isLocationActive()) {
             mIconController.setIconVisibility(mSlotLocation, true);
         } else {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/RotationButton.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/RotationButton.java
index 2580c0e..281207b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/RotationButton.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/RotationButton.java
@@ -20,9 +20,12 @@
 
 import com.android.systemui.statusbar.policy.KeyButtonDrawable;
 
+import java.util.function.Consumer;
+
 /** Interface of a rotation button that interacts {@link RotationButtonController}. */
 interface RotationButton {
     void setRotationButtonController(RotationButtonController rotationButtonController);
+    void setVisibilityChangedCallback(Consumer<Boolean> visibilityChangedCallback);
     View getCurrentView();
     boolean show();
     boolean hide();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/RotationButtonController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/RotationButtonController.java
index 59b10e4..dbf5aa7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/RotationButtonController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/RotationButtonController.java
@@ -117,7 +117,8 @@
         return (disable2Flags & StatusBarManager.DISABLE2_ROTATE_SUGGESTIONS) != 0;
     }
 
-    RotationButtonController(Context context, @StyleRes int style, RotationButton rotationButton) {
+    RotationButtonController(Context context, @StyleRes int style, RotationButton rotationButton,
+            Consumer<Boolean> visibilityChangedCallback) {
         mContext = context;
         mRotationButton = rotationButton;
         mRotationButton.setRotationButtonController(this);
@@ -131,6 +132,7 @@
         mTaskStackListener = new TaskStackListenerImpl();
         mRotationButton.setOnClickListener(this::onRotateSuggestionClick);
         mRotationButton.setOnHoverListener(this::onRotateSuggestionHover);
+        mRotationButton.setVisibilityChangedCallback(visibilityChangedCallback);
     }
 
     void registerListeners() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/RotationContextButton.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/RotationContextButton.java
index bd96752..687c223 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/RotationContextButton.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/RotationContextButton.java
@@ -26,6 +26,8 @@
 
 import com.android.systemui.statusbar.policy.KeyButtonDrawable;
 
+import java.util.function.Consumer;
+
 /** Containing logic for the rotation button in nav bar. */
 public class RotationContextButton extends ContextualButton implements
         NavigationModeController.ModeChangedListener, RotationButton {
@@ -44,6 +46,18 @@
     }
 
     @Override
+    public void setVisibilityChangedCallback(Consumer<Boolean> visibilityChangedCallback) {
+        setListener(new ContextButtonListener() {
+            @Override
+            public void onVisibilityChanged(ContextualButton button, boolean visible) {
+                if (visibilityChangedCallback != null) {
+                    visibilityChangedCallback.accept(visible);
+                }
+            }
+        });
+    }
+
+    @Override
     public void setVisibility(int visibility) {
         super.setVisibility(visibility);
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryController.java
index 673549a..e5a4679 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryController.java
@@ -79,6 +79,13 @@
     default void setReverseState(boolean isReverse) {}
 
     /**
+     * Returns {@code true} if extreme battery saver is on.
+     */
+    default boolean isExtremeSaverOn() {
+        return false;
+    }
+
+    /**
      * A listener that will be notified whenever a change in battery level or power save mode has
      * occurred.
      */
@@ -92,6 +99,9 @@
 
         default void onReverseChanged(boolean isReverse, int level, String name) {
         }
+
+        default void onExtremeBatterySaverChanged(boolean isExtreme) {
+        }
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/Clock.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/Clock.java
index d43dd23..120a0e3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/Clock.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/Clock.java
@@ -81,6 +81,7 @@
     private boolean mClockVisibleByUser = true;
 
     private boolean mAttached;
+    private boolean mScreenReceiverRegistered;
     private Calendar mCalendar;
     private String mClockFormatString;
     private SimpleDateFormat mClockFormat;
@@ -212,6 +213,14 @@
     @Override
     protected void onDetachedFromWindow() {
         super.onDetachedFromWindow();
+        if (mScreenReceiverRegistered) {
+            mScreenReceiverRegistered = false;
+            mBroadcastDispatcher.unregisterReceiver(mScreenReceiver);
+            if (mSecondsHandler != null) {
+                mSecondsHandler.removeCallbacks(mSecondTick);
+                mSecondsHandler = null;
+            }
+        }
         if (mAttached) {
             mBroadcastDispatcher.unregisterReceiver(mIntentReceiver);
             mAttached = false;
@@ -362,12 +371,14 @@
                     mSecondsHandler.postAtTime(mSecondTick,
                             SystemClock.uptimeMillis() / 1000 * 1000 + 1000);
                 }
+                mScreenReceiverRegistered = true;
                 IntentFilter filter = new IntentFilter(Intent.ACTION_SCREEN_OFF);
                 filter.addAction(Intent.ACTION_SCREEN_ON);
                 mBroadcastDispatcher.registerReceiver(mScreenReceiver, filter);
             }
         } else {
             if (mSecondsHandler != null) {
+                mScreenReceiverRegistered = false;
                 mBroadcastDispatcher.unregisterReceiver(mScreenReceiver);
                 mSecondsHandler.removeCallbacks(mSecondTick);
                 mSecondsHandler = null;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java
index 07de388..82ad00a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java
@@ -20,6 +20,7 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.app.Notification;
 import android.content.Context;
 import android.content.res.Resources;
 import android.database.ContentObserver;
@@ -106,9 +107,9 @@
 
     public void updateNotification(@NonNull String key, boolean alert) {
         super.updateNotification(key, alert);
-        AlertEntry alertEntry = getHeadsUpEntry(key);
-        if (alert && alertEntry != null) {
-            setEntryPinned((HeadsUpEntry) alertEntry, shouldHeadsUpBecomePinned(alertEntry.mEntry));
+        HeadsUpEntry headsUpEntry = getHeadsUpEntry(key);
+        if (alert && headsUpEntry != null) {
+            setEntryPinned(headsUpEntry, shouldHeadsUpBecomePinned(headsUpEntry.mEntry));
         }
     }
 
@@ -359,6 +360,11 @@
         return false;
     }
 
+    private static boolean isOngoingCallNotif(NotificationEntry entry) {
+        return entry.getSbn().isOngoing() && Notification.CATEGORY_CALL.equals(
+                entry.getSbn().getNotification().category);
+    }
+
     /**
      * This represents a notification and how long it is in a heads up mode. It also manages its
      * lifecycle automatically when created.
@@ -391,6 +397,15 @@
                 return 1;
             }
 
+            boolean selfCall = isOngoingCallNotif(mEntry);
+            boolean otherCall = isOngoingCallNotif(headsUpEntry.mEntry);
+
+            if (selfCall && !otherCall) {
+                return -1;
+            } else if (!selfCall && otherCall) {
+                return 1;
+            }
+
             if (remoteInputActive && !headsUpEntry.remoteInputActive) {
                 return -1;
             } else if (!remoteInputActive && headsUpEntry.remoteInputActive) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonDrawable.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonDrawable.java
index 23d03a4..e1ff9a2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonDrawable.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonDrawable.java
@@ -172,6 +172,24 @@
     }
 
     @Override
+    public boolean setVisible(boolean visible, boolean restart) {
+        boolean changed = super.setVisible(visible, restart);
+        if (changed) {
+            // End any existing animations when the visibility changes
+            jumpToCurrentState();
+        }
+        return changed;
+    }
+
+    @Override
+    public void jumpToCurrentState() {
+        super.jumpToCurrentState();
+        if (mAnimatedDrawable != null) {
+            mAnimatedDrawable.jumpToCurrentState();
+        }
+    }
+
+    @Override
     public void setAlpha(int alpha) {
         mState.mAlpha = alpha;
         mIconPaint.setAlpha(alpha);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonRipple.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonRipple.java
index 2d8784d..9e1485d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonRipple.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonRipple.java
@@ -225,6 +225,16 @@
     }
 
     @Override
+    public boolean setVisible(boolean visible, boolean restart) {
+        boolean changed = super.setVisible(visible, restart);
+        if (changed) {
+            // End any existing animations when the visibility changes
+            jumpToCurrentState();
+        }
+        return changed;
+    }
+
+    @Override
     public void jumpToCurrentState() {
         endAnimations("jumpToCurrentState", false /* cancel */);
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileSignalController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileSignalController.java
index cf83603..eb2d9bc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileSignalController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileSignalController.java
@@ -418,6 +418,10 @@
         return (mServiceState != null && mServiceState.isEmergencyOnly());
     }
 
+    public boolean isInService() {
+        return Utils.isInService(mServiceState);
+    }
+
     private boolean isRoaming() {
         // During a carrier change, roaming indications need to be supressed.
         if (isCarrierNetworkChangeActive()) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkController.java
index 95a9772..1dbb228f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkController.java
@@ -37,6 +37,7 @@
     DataUsageController getMobileDataController();
     DataSaverController getDataSaverController();
     String getMobileDataNetworkName();
+    boolean isMobileDataNetworkInService();
     int getNumberSubscriptions();
 
     boolean hasVoiceCallingFeature();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java
index 32c4aec..df00a4f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java
@@ -446,6 +446,12 @@
     }
 
     @Override
+    public boolean isMobileDataNetworkInService() {
+        MobileSignalController controller = getDataController();
+        return controller != null && controller.isInService();
+    }
+
+    @Override
     public int getNumberSubscriptions() {
         return mMobileSignalControllers.size();
     }
diff --git a/packages/SystemUI/src/com/android/systemui/util/animation/PhysicsAnimator.kt b/packages/SystemUI/src/com/android/systemui/util/animation/PhysicsAnimator.kt
index 016f4de..2a5424c 100644
--- a/packages/SystemUI/src/com/android/systemui/util/animation/PhysicsAnimator.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/animation/PhysicsAnimator.kt
@@ -20,6 +20,7 @@
 import android.util.ArrayMap
 import android.util.Log
 import android.view.View
+import androidx.dynamicanimation.animation.AnimationHandler
 import androidx.dynamicanimation.animation.DynamicAnimation
 import androidx.dynamicanimation.animation.FlingAnimation
 import androidx.dynamicanimation.animation.FloatPropertyCompat
@@ -124,6 +125,12 @@
     private var defaultFling: FlingConfig = globalDefaultFling
 
     /**
+     * AnimationHandler to use if it need custom AnimationHandler, if this is null, it will use
+     * the default AnimationHandler in the DynamicAnimation.
+     */
+    private var customAnimationHandler: AnimationHandler? = null
+
+    /**
      * Internal listeners that respond to DynamicAnimations updating and ending, and dispatch to
      * the listeners provided via [addUpdateListener] and [addEndListener]. This allows us to add
      * just one permanent update and end listener to the DynamicAnimations.
@@ -447,6 +454,14 @@
         this.defaultFling = defaultFling
     }
 
+    /**
+     * Set the custom AnimationHandler for all aniatmion in this animator. Set this with null for
+     * restoring to default AnimationHandler.
+     */
+    fun setCustomAnimationHandler(handler: AnimationHandler) {
+        this.customAnimationHandler = handler
+    }
+
     /** Starts the animations! */
     fun start() {
         startAction()
@@ -501,10 +516,13 @@
                     // springs) on this property before flinging.
                     cancel(animatedProperty)
 
+                    // Apply the custom animation handler if it not null
+                    val flingAnim = getFlingAnimation(animatedProperty, target)
+                    flingAnim.animationHandler =
+                            customAnimationHandler ?: flingAnim.animationHandler
+
                     // Apply the configuration and start the animation.
-                    getFlingAnimation(animatedProperty, target)
-                            .also { flingConfig.applyToAnimation(it) }
-                            .start()
+                    flingAnim.also { flingConfig.applyToAnimation(it) }.start()
                 }
             }
 
@@ -516,6 +534,21 @@
                 if (flingConfig == null) {
                     // Apply the configuration and start the animation.
                     val springAnim = getSpringAnimation(animatedProperty, target)
+
+                    // If customAnimationHander is exist and has not been set to the animation,
+                    // it should set here.
+                    if (customAnimationHandler != null &&
+                            springAnim.animationHandler != customAnimationHandler) {
+                        // Cancel the animation before set animation handler
+                        if (springAnim.isRunning) {
+                            cancel(animatedProperty)
+                        }
+                        // Apply the custom animation handler if it not null
+                        springAnim.animationHandler =
+                                customAnimationHandler ?: springAnim.animationHandler
+                    }
+
+                    // Apply the configuration and start the animation.
                     springConfig.applyToAnimation(springAnim)
                     animationStartActions.add(springAnim::start)
                 } else {
@@ -570,10 +603,13 @@
                                     }
                                 }
 
+                                // Apply the custom animation handler if it not null
+                                val springAnim = getSpringAnimation(animatedProperty, target)
+                                springAnim.animationHandler =
+                                        customAnimationHandler ?: springAnim.animationHandler
+
                                 // Apply the configuration and start the spring animation.
-                                getSpringAnimation(animatedProperty, target)
-                                        .also { springConfig.applyToAnimation(it) }
-                                        .start()
+                                springAnim.also { springConfig.applyToAnimation(it) }.start()
                             }
                         }
                     })
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
index 3455ff4..6fe11ed 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
@@ -935,6 +935,7 @@
     protected void onStateChangedH(State state) {
         if (D.BUG) Log.d(TAG, "onStateChangedH() state: " + state.toString());
         if (mState != null && state != null
+                && mState.ringerModeInternal != -1
                 && mState.ringerModeInternal != state.ringerModeInternal
                 && state.ringerModeInternal == AudioManager.RINGER_MODE_VIBRATE) {
             mController.vibrate(VibrationEffect.get(VibrationEffect.EFFECT_HEAVY_CLICK));
diff --git a/packages/SystemUI/src/com/android/systemui/wm/DisplayImeController.java b/packages/SystemUI/src/com/android/systemui/wm/DisplayImeController.java
index 926f653..dd4ea57 100644
--- a/packages/SystemUI/src/com/android/systemui/wm/DisplayImeController.java
+++ b/packages/SystemUI/src/com/android/systemui/wm/DisplayImeController.java
@@ -228,12 +228,22 @@
                         mHandler.post(() -> {
                             final Point lastSurfacePosition = mImeSourceControl != null
                                     ? mImeSourceControl.getSurfacePosition() : null;
+                            final boolean positionChanged =
+                                    !activeControl.getSurfacePosition().equals(lastSurfacePosition);
+                            final boolean leashChanged =
+                                    !haveSameLeash(mImeSourceControl, activeControl);
                             mImeSourceControl = activeControl;
-                            if (!activeControl.getSurfacePosition().equals(lastSurfacePosition)
-                                    && mAnimation != null) {
-                                startAnimation(mImeShowing, true /* forceRestart */);
-                            } else if (!mImeShowing) {
-                                removeImeSurface();
+                            if (mAnimation != null) {
+                                if (positionChanged) {
+                                    startAnimation(mImeShowing, true /* forceRestart */);
+                                }
+                            } else {
+                                if (leashChanged) {
+                                    applyVisibilityToLeash();
+                                }
+                                if (!mImeShowing) {
+                                    removeImeSurface();
+                                }
                             }
                         });
                     }
@@ -241,6 +251,20 @@
             }
         }
 
+        private void applyVisibilityToLeash() {
+            SurfaceControl leash = mImeSourceControl.getLeash();
+            if (leash != null) {
+                SurfaceControl.Transaction t = mTransactionPool.acquire();
+                if (mImeShowing) {
+                    t.show(leash);
+                } else {
+                    t.hide(leash);
+                }
+                t.apply();
+                mTransactionPool.release(t);
+            }
+        }
+
         @Override
         public void showInsets(int types, boolean fromIme) {
             if ((types & WindowInsets.Type.ime()) == 0) {
@@ -259,6 +283,11 @@
             mHandler.post(() -> startAnimation(false /* show */, false /* forceRestart */));
         }
 
+        @Override
+        public void topFocusedWindowChanged(String packageName) {
+            // no-op
+        }
+
         /**
          * Sends the local visibility state back to window manager. Needed for legacy adjustForIme.
          */
@@ -487,4 +516,20 @@
         return IInputMethodManager.Stub.asInterface(
                 ServiceManager.getService(Context.INPUT_METHOD_SERVICE));
     }
+
+    private static boolean haveSameLeash(InsetsSourceControl a, InsetsSourceControl b) {
+        if (a == b) {
+            return true;
+        }
+        if (a == null || b == null) {
+            return false;
+        }
+        if (a.getLeash() == b.getLeash()) {
+            return true;
+        }
+        if (a.getLeash() == null || b.getLeash() == null) {
+            return false;
+        }
+        return a.getLeash().isSameSurface(b.getLeash());
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
index fabf093..5a07752 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
@@ -174,6 +174,8 @@
         when(mFaceManager.isHardwareDetected()).thenReturn(true);
         when(mFaceManager.hasEnrolledTemplates()).thenReturn(true);
         when(mFaceManager.hasEnrolledTemplates(anyInt())).thenReturn(true);
+        when(mFingerprintManager.isHardwareDetected()).thenReturn(true);
+        when(mFingerprintManager.hasEnrolledTemplates(anyInt())).thenReturn(true);
         when(mUserManager.isUserUnlocked(anyInt())).thenReturn(true);
         when(mUserManager.isPrimaryUser()).thenReturn(true);
         when(mStrongAuthTracker.getStub()).thenReturn(mock(IStrongAuthTracker.Stub.class));
@@ -419,6 +421,43 @@
     }
 
     @Test
+    public void testTriesToAuthenticateFingerprint_whenKeyguard() {
+        mKeyguardUpdateMonitor.dispatchStartedGoingToSleep(0 /* why */);
+        mTestableLooper.processAllMessages();
+
+        verify(mFingerprintManager).authenticate(any(), any(), anyInt(), any(), any(), anyInt());
+        verify(mFingerprintManager, never()).detectFingerprint(any(), any(), anyInt());
+    }
+
+    @Test
+    public void testFingerprintDoesNotAuth_whenEncrypted() {
+        testFingerprintWhenStrongAuth(
+                KeyguardUpdateMonitor.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_BOOT);
+    }
+
+    @Test
+    public void testFingerprintDoesNotAuth_whenDpmLocked() {
+        testFingerprintWhenStrongAuth(
+                KeyguardUpdateMonitor.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_DPM_LOCK_NOW);
+    }
+
+    @Test
+    public void testFingerprintDoesNotAuth_whenUserLockdown() {
+        testFingerprintWhenStrongAuth(
+                KeyguardUpdateMonitor.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN);
+    }
+
+    private void testFingerprintWhenStrongAuth(int strongAuth) {
+        when(mStrongAuthTracker.getStrongAuthForUser(anyInt())).thenReturn(strongAuth);
+        mKeyguardUpdateMonitor.dispatchStartedGoingToSleep(0 /* why */);
+        mTestableLooper.processAllMessages();
+
+        verify(mFingerprintManager, never())
+                .authenticate(any(), any(), anyInt(), any(), any(), anyInt());
+        verify(mFingerprintManager).detectFingerprint(any(), any(), anyInt());
+    }
+
+    @Test
     public void testTriesToAuthenticate_whenBouncer() {
         mKeyguardUpdateMonitor.sendKeyguardBouncerChanged(true);
         mTestableLooper.processAllMessages();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/appops/AppOpsControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/appops/AppOpsControllerTest.java
index e0049d1..ade3290 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/appops/AppOpsControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/appops/AppOpsControllerTest.java
@@ -26,11 +26,19 @@
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.anyLong;
 import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.inOrder;
+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;
 
 import android.app.AppOpsManager;
+import android.content.pm.PackageManager;
+import android.media.AudioManager;
+import android.media.AudioRecordingConfiguration;
 import android.os.Looper;
 import android.os.UserHandle;
 import android.testing.AndroidTestingRunner;
@@ -39,14 +47,17 @@
 import androidx.test.filters.SmallTest;
 
 import com.android.systemui.SysuiTestCase;
+import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.dump.DumpManager;
 
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.InOrder;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
+import java.util.Collections;
 import java.util.List;
 
 @SmallTest
@@ -56,6 +67,7 @@
     private static final String TEST_PACKAGE_NAME = "test";
     private static final int TEST_UID = UserHandle.getUid(0, 0);
     private static final int TEST_UID_OTHER = UserHandle.getUid(1, 0);
+    private static final int TEST_UID_NON_USER_SENSITIVE = UserHandle.getUid(2, 0);
 
     @Mock
     private AppOpsManager mAppOpsManager;
@@ -65,6 +77,18 @@
     private AppOpsControllerImpl.H mMockHandler;
     @Mock
     private DumpManager mDumpManager;
+    @Mock
+    private PermissionFlagsCache mFlagsCache;
+    @Mock
+    private PackageManager mPackageManager;
+    @Mock(stubOnly = true)
+    private AudioManager mAudioManager;
+    @Mock()
+    private BroadcastDispatcher mDispatcher;
+    @Mock(stubOnly = true)
+    private AudioManager.AudioRecordingCallback mRecordingCallback;
+    @Mock(stubOnly = true)
+    private AudioRecordingConfiguration mPausedMockRecording;
 
     private AppOpsControllerImpl mController;
     private TestableLooper mTestableLooper;
@@ -76,20 +100,46 @@
 
         getContext().addMockSystemService(AppOpsManager.class, mAppOpsManager);
 
-        mController =
-                new AppOpsControllerImpl(mContext, mTestableLooper.getLooper(), mDumpManager);
+        // All permissions of TEST_UID and TEST_UID_OTHER are user sensitive. None of
+        // TEST_UID_NON_USER_SENSITIVE are user sensitive.
+        getContext().setMockPackageManager(mPackageManager);
+        when(mFlagsCache.getPermissionFlags(anyString(), anyString(), eq(TEST_UID))).thenReturn(
+                PackageManager.FLAG_PERMISSION_USER_SENSITIVE_WHEN_GRANTED);
+        when(mFlagsCache.getPermissionFlags(anyString(), anyString(), eq(TEST_UID_OTHER)))
+                .thenReturn(PackageManager.FLAG_PERMISSION_USER_SENSITIVE_WHEN_GRANTED);
+        when(mFlagsCache.getPermissionFlags(anyString(), anyString(),
+                eq(TEST_UID_NON_USER_SENSITIVE))).thenReturn(0);
+
+        doAnswer((invocation) -> mRecordingCallback = invocation.getArgument(0))
+                .when(mAudioManager).registerAudioRecordingCallback(any(), any());
+        when(mPausedMockRecording.getClientUid()).thenReturn(TEST_UID);
+        when(mPausedMockRecording.isClientSilenced()).thenReturn(true);
+
+        when(mAudioManager.getActiveRecordingConfigurations())
+                .thenReturn(List.of(mPausedMockRecording));
+
+        mController = new AppOpsControllerImpl(
+                mContext,
+                mTestableLooper.getLooper(),
+                mDumpManager,
+                mFlagsCache,
+                mAudioManager,
+                mDispatcher
+        );
     }
 
     @Test
     public void testOnlyListenForFewOps() {
         mController.setListening(true);
         verify(mAppOpsManager, times(1)).startWatchingActive(AppOpsControllerImpl.OPS, mController);
+        verify(mDispatcher, times(1)).registerReceiverWithHandler(eq(mController), any(), any());
     }
 
     @Test
     public void testStopListening() {
         mController.setListening(false);
         verify(mAppOpsManager, times(1)).stopWatchingActive(mController);
+        verify(mDispatcher, times(1)).unregisterReceiver(mController);
     }
 
     @Test
@@ -173,6 +223,26 @@
     }
 
     @Test
+    public void nonUserSensitiveOpsAreIgnored() {
+        mController.onOpActiveChanged(AppOpsManager.OP_RECORD_AUDIO,
+                TEST_UID_NON_USER_SENSITIVE, TEST_PACKAGE_NAME, true);
+        assertEquals(0, mController.getActiveAppOpsForUser(
+                UserHandle.getUserId(TEST_UID_NON_USER_SENSITIVE)).size());
+    }
+
+    @Test
+    public void nonUserSensitiveOpsNotNotified() {
+        mController.addCallback(new int[]{AppOpsManager.OP_RECORD_AUDIO}, mCallback);
+        mController.onOpActiveChanged(AppOpsManager.OP_RECORD_AUDIO,
+                TEST_UID_NON_USER_SENSITIVE, TEST_PACKAGE_NAME, true);
+
+        mTestableLooper.processAllMessages();
+
+        verify(mCallback, never())
+                .onActiveStateChanged(anyInt(), anyInt(), anyString(), anyBoolean());
+    }
+
+    @Test
     public void opNotedScheduledForRemoval() {
         mController.setBGHandler(mMockHandler);
         mController.onOpNoted(AppOpsManager.OP_FINE_LOCATION, TEST_UID, TEST_PACKAGE_NAME,
@@ -321,6 +391,89 @@
                 AppOpsManager.OP_FINE_LOCATION, TEST_UID, TEST_PACKAGE_NAME, true);
     }
 
+    @Test
+    public void testPausedRecordingIsRetrievedOnCreation() {
+        mController.addCallback(new int[]{AppOpsManager.OP_RECORD_AUDIO}, mCallback);
+        mTestableLooper.processAllMessages();
+
+        mController.onOpActiveChanged(
+                AppOpsManager.OP_RECORD_AUDIO, TEST_UID, TEST_PACKAGE_NAME, true);
+        mTestableLooper.processAllMessages();
+
+        verify(mCallback, never())
+                .onActiveStateChanged(anyInt(), anyInt(), anyString(), anyBoolean());
+    }
+
+    @Test
+    public void testPausedRecordingFilteredOut() {
+        mController.addCallback(new int[]{AppOpsManager.OP_RECORD_AUDIO}, mCallback);
+        mTestableLooper.processAllMessages();
+
+        mController.onOpActiveChanged(
+                AppOpsManager.OP_RECORD_AUDIO, TEST_UID, TEST_PACKAGE_NAME, true);
+        mTestableLooper.processAllMessages();
+
+        assertTrue(mController.getActiveAppOps().isEmpty());
+    }
+
+    @Test
+    public void testOnlyRecordAudioPaused() {
+        mController.addCallback(new int[]{
+                AppOpsManager.OP_RECORD_AUDIO,
+                AppOpsManager.OP_CAMERA
+        }, mCallback);
+        mTestableLooper.processAllMessages();
+
+        mController.onOpActiveChanged(
+                AppOpsManager.OP_CAMERA, TEST_UID, TEST_PACKAGE_NAME, true);
+        mTestableLooper.processAllMessages();
+
+        verify(mCallback).onActiveStateChanged(
+                AppOpsManager.OP_CAMERA, TEST_UID, TEST_PACKAGE_NAME, true);
+        List<AppOpItem> list = mController.getActiveAppOps();
+
+        assertEquals(1, list.size());
+        assertEquals(AppOpsManager.OP_CAMERA, list.get(0).getCode());
+    }
+
+    @Test
+    public void testUnpausedRecordingSentActive() {
+        mController.addCallback(new int[]{AppOpsManager.OP_RECORD_AUDIO}, mCallback);
+        mTestableLooper.processAllMessages();
+        mController.onOpActiveChanged(
+                AppOpsManager.OP_RECORD_AUDIO, TEST_UID, TEST_PACKAGE_NAME, true);
+
+        mTestableLooper.processAllMessages();
+        mRecordingCallback.onRecordingConfigChanged(Collections.emptyList());
+
+        mTestableLooper.processAllMessages();
+
+        verify(mCallback).onActiveStateChanged(
+                AppOpsManager.OP_RECORD_AUDIO, TEST_UID, TEST_PACKAGE_NAME, true);
+    }
+
+    @Test
+    public void testAudioPausedSentInactive() {
+        mController.addCallback(new int[]{AppOpsManager.OP_RECORD_AUDIO}, mCallback);
+        mTestableLooper.processAllMessages();
+        mController.onOpActiveChanged(
+                AppOpsManager.OP_RECORD_AUDIO, TEST_UID_OTHER, TEST_PACKAGE_NAME, true);
+        mTestableLooper.processAllMessages();
+
+        AudioRecordingConfiguration mockARC = mock(AudioRecordingConfiguration.class);
+        when(mockARC.getClientUid()).thenReturn(TEST_UID_OTHER);
+        when(mockARC.isClientSilenced()).thenReturn(true);
+
+        mRecordingCallback.onRecordingConfigChanged(List.of(mockARC));
+        mTestableLooper.processAllMessages();
+
+        InOrder inOrder = inOrder(mCallback);
+        inOrder.verify(mCallback).onActiveStateChanged(
+                AppOpsManager.OP_RECORD_AUDIO, TEST_UID_OTHER, TEST_PACKAGE_NAME, true);
+        inOrder.verify(mCallback).onActiveStateChanged(
+                AppOpsManager.OP_RECORD_AUDIO, TEST_UID_OTHER, TEST_PACKAGE_NAME, false);
+    }
+
     private class TestHandler extends AppOpsControllerImpl.H {
         TestHandler(Looper looper) {
             mController.super(looper);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/appops/PermissionFlagsCacheTest.kt b/packages/SystemUI/tests/src/com/android/systemui/appops/PermissionFlagsCacheTest.kt
new file mode 100644
index 0000000..0fb0ce0
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/appops/PermissionFlagsCacheTest.kt
@@ -0,0 +1,145 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.systemui.appops
+
+import android.content.pm.PackageManager
+import android.os.UserHandle
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.time.FakeSystemClock
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertNotEquals
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.any
+import org.mockito.ArgumentMatchers.anyString
+import org.mockito.Mock
+import org.mockito.Mockito.`when`
+import org.mockito.Mockito.never
+import org.mockito.Mockito.times
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class PermissionFlagsCacheTest : SysuiTestCase() {
+
+    companion object {
+        const val TEST_PERMISSION = "test_permission"
+        const val TEST_PACKAGE = "test_package"
+        const val TEST_UID1 = 1000
+        const val TEST_UID2 = UserHandle.PER_USER_RANGE + 1000
+    }
+
+    @Mock
+    private lateinit var packageManager: PackageManager
+
+    private lateinit var executor: FakeExecutor
+    private lateinit var flagsCache: PermissionFlagsCache
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+
+        executor = FakeExecutor(FakeSystemClock())
+
+        flagsCache = PermissionFlagsCache(packageManager, executor)
+        executor.runAllReady()
+    }
+
+    @Test
+    fun testNotListeningByDefault() {
+        verify(packageManager, never()).addOnPermissionsChangeListener(any())
+    }
+
+    @Test
+    fun testGetCorrectFlags() {
+        `when`(packageManager.getPermissionFlags(anyString(), anyString(), any())).thenReturn(0)
+        `when`(packageManager.getPermissionFlags(
+                TEST_PERMISSION,
+                TEST_PACKAGE,
+                UserHandle.getUserHandleForUid(TEST_UID1))
+        ).thenReturn(1)
+
+        assertEquals(1, flagsCache.getPermissionFlags(TEST_PERMISSION, TEST_PACKAGE, TEST_UID1))
+        assertNotEquals(1, flagsCache.getPermissionFlags(TEST_PERMISSION, TEST_PACKAGE, TEST_UID2))
+    }
+
+    @Test
+    fun testFlagIsCached() {
+        flagsCache.getPermissionFlags(TEST_PERMISSION, TEST_PACKAGE, TEST_UID1)
+
+        flagsCache.getPermissionFlags(TEST_PERMISSION, TEST_PACKAGE, TEST_UID1)
+
+        verify(packageManager, times(1)).getPermissionFlags(
+                TEST_PERMISSION,
+                TEST_PACKAGE,
+                UserHandle.getUserHandleForUid(TEST_UID1)
+        )
+    }
+
+    @Test
+    fun testListeningAfterFirstRequest() {
+        flagsCache.getPermissionFlags(TEST_PERMISSION, TEST_PACKAGE, TEST_UID1)
+
+        verify(packageManager).addOnPermissionsChangeListener(any())
+    }
+
+    @Test
+    fun testListeningOnlyOnce() {
+        flagsCache.getPermissionFlags(TEST_PERMISSION, TEST_PACKAGE, TEST_UID1)
+
+        flagsCache.getPermissionFlags(TEST_PERMISSION, TEST_PACKAGE, TEST_UID2)
+
+        verify(packageManager, times(1)).addOnPermissionsChangeListener(any())
+    }
+
+    @Test
+    fun testUpdateFlag() {
+        assertEquals(0, flagsCache.getPermissionFlags(TEST_PERMISSION, TEST_PACKAGE, TEST_UID1))
+
+        `when`(packageManager.getPermissionFlags(
+                TEST_PERMISSION,
+                TEST_PACKAGE,
+                UserHandle.getUserHandleForUid(TEST_UID1))
+        ).thenReturn(1)
+
+        flagsCache.onPermissionsChanged(TEST_UID1)
+
+        executor.runAllReady()
+
+        assertEquals(1, flagsCache.getPermissionFlags(TEST_PERMISSION, TEST_PACKAGE, TEST_UID1))
+    }
+
+    @Test
+    fun testUpdateFlag_notUpdatedIfUidHasNotBeenRequestedBefore() {
+        flagsCache.getPermissionFlags(TEST_PERMISSION, TEST_PACKAGE, TEST_UID1)
+
+        flagsCache.onPermissionsChanged(TEST_UID2)
+
+        executor.runAllReady()
+
+        verify(packageManager, never()).getPermissionFlags(
+                TEST_PERMISSION,
+                TEST_PACKAGE,
+                UserHandle.getUserHandleForUid(TEST_UID2)
+        )
+    }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java
index 15828b4..1ad8856 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java
@@ -999,6 +999,29 @@
         verify(mNotificationGroupManager, times(1)).onEntryRemoved(groupSummary.getEntry());
     }
 
+
+    /**
+     * Verifies that when a non visually interruptive update occurs for a bubble in the overflow,
+     * the that bubble does not get promoted from the overflow.
+     */
+    @Test
+    public void test_notVisuallyInterruptive_updateOverflowBubble_notAdded() {
+        // Setup
+        mBubbleController.updateBubble(mRow.getEntry());
+        mBubbleController.updateBubble(mRow2.getEntry());
+        assertTrue(mBubbleController.hasBubbles());
+
+        // Overflow it
+        mBubbleData.dismissBubbleWithKey(mRow.getEntry().getKey(),
+                BubbleController.DISMISS_USER_GESTURE);
+        assertThat(mBubbleData.hasBubbleInStackWithKey(mRow.getEntry().getKey())).isFalse();
+        assertThat(mBubbleData.hasOverflowBubbleWithKey(mRow.getEntry().getKey())).isTrue();
+
+        // Test
+        mBubbleController.updateBubble(mRow.getEntry());
+        assertThat(mBubbleData.hasBubbleInStackWithKey(mRow.getEntry().getKey())).isFalse();
+    }
+
     /**
      * Sets the bubble metadata flags for this entry. These ]flags are normally set by
      * NotificationManagerService when the notification is sent, however, these tests do not
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleDataTest.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleDataTest.java
index 12221bc..0a07a73 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleDataTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleDataTest.java
@@ -514,6 +514,26 @@
     }
 
     /**
+     * Verifies that when a non visually interruptive update occurs, that the selection does not
+     * change.
+     */
+    @Test
+    public void test_notVisuallyInterruptive_updateBubble_selectionDoesntChange() {
+        // Setup
+        sendUpdatedEntryAtTime(mEntryA1, 1000);
+        sendUpdatedEntryAtTime(mEntryB1, 2000);
+        sendUpdatedEntryAtTime(mEntryB2, 3000);
+        sendUpdatedEntryAtTime(mEntryA2, 4000); // [A2, B2, B1, A1]
+        mBubbleData.setListener(mListener);
+
+        assertThat(mBubbleData.getSelectedBubble()).isEqualTo(mBubbleA2);
+
+        // Test
+        sendUpdatedEntryAtTime(mEntryB1, 5000, false /* isVisuallyInterruptive */);
+        assertThat(mBubbleData.getSelectedBubble()).isEqualTo(mBubbleA2);
+    }
+
+    /**
      * Verifies that a request to expand the stack has no effect if there are no bubbles.
      */
     @Test
@@ -885,9 +905,15 @@
     }
 
     private void sendUpdatedEntryAtTime(NotificationEntry entry, long postTime) {
+        sendUpdatedEntryAtTime(entry, postTime, true /* visuallyInterruptive */);
+    }
+
+    private void sendUpdatedEntryAtTime(NotificationEntry entry, long postTime,
+            boolean visuallyInterruptive) {
         setPostTime(entry, postTime);
         // BubbleController calls this:
         Bubble b = mBubbleData.getOrCreateBubble(entry, null /* persistedBubble */);
+        b.setVisuallyInterruptiveForTest(visuallyInterruptive);
         // And then this
         mBubbleData.notificationEntryUpdated(b, false /* suppressFlyout*/,
                 true /* showInShade */);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/CustomIconCacheTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/CustomIconCacheTest.kt
new file mode 100644
index 0000000..4d0f2ed
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/CustomIconCacheTest.kt
@@ -0,0 +1,101 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.controls
+
+import android.content.ComponentName
+import android.graphics.drawable.Icon
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import org.junit.Assert.assertNull
+import org.junit.Assert.assertTrue
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class CustomIconCacheTest : SysuiTestCase() {
+
+    companion object {
+        private val TEST_COMPONENT1 = ComponentName.unflattenFromString("pkg/.cls1")!!
+        private val TEST_COMPONENT2 = ComponentName.unflattenFromString("pkg/.cls2")!!
+        private const val CONTROL_ID_1 = "TEST_CONTROL_1"
+        private const val CONTROL_ID_2 = "TEST_CONTROL_2"
+    }
+
+    @Mock(stubOnly = true)
+    private lateinit var icon1: Icon
+    @Mock(stubOnly = true)
+    private lateinit var icon2: Icon
+    private lateinit var customIconCache: CustomIconCache
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+
+        customIconCache = CustomIconCache()
+    }
+
+    @Test
+    fun testIconStoredCorrectly() {
+        customIconCache.store(TEST_COMPONENT1, CONTROL_ID_1, icon1)
+
+        assertTrue(icon1 === customIconCache.retrieve(TEST_COMPONENT1, CONTROL_ID_1))
+    }
+
+    @Test
+    fun testIconNotStoredReturnsNull() {
+        customIconCache.store(TEST_COMPONENT1, CONTROL_ID_1, icon1)
+
+        assertNull(customIconCache.retrieve(TEST_COMPONENT1, CONTROL_ID_2))
+    }
+
+    @Test
+    fun testWrongComponentReturnsNull() {
+        customIconCache.store(TEST_COMPONENT1, CONTROL_ID_1, icon1)
+
+        assertNull(customIconCache.retrieve(TEST_COMPONENT2, CONTROL_ID_1))
+    }
+
+    @Test
+    fun testChangeComponentOldComponentIsRemoved() {
+        customIconCache.store(TEST_COMPONENT1, CONTROL_ID_1, icon1)
+        customIconCache.store(TEST_COMPONENT2, CONTROL_ID_2, icon2)
+
+        assertNull(customIconCache.retrieve(TEST_COMPONENT1, CONTROL_ID_1))
+        assertNull(customIconCache.retrieve(TEST_COMPONENT1, CONTROL_ID_2))
+    }
+
+    @Test
+    fun testChangeComponentCorrectIconRetrieved() {
+        customIconCache.store(TEST_COMPONENT1, CONTROL_ID_1, icon1)
+        customIconCache.store(TEST_COMPONENT2, CONTROL_ID_1, icon2)
+
+        assertTrue(icon2 === customIconCache.retrieve(TEST_COMPONENT2, CONTROL_ID_1))
+    }
+
+    @Test
+    fun testStoreNull() {
+        customIconCache.store(TEST_COMPONENT1, CONTROL_ID_1, icon1)
+        customIconCache.store(TEST_COMPONENT1, CONTROL_ID_1, null)
+
+        assertNull(customIconCache.retrieve(TEST_COMPONENT1, CONTROL_ID_1))
+    }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsFavoritePersistenceWrapperTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsFavoritePersistenceWrapperTest.kt
index 861c620..690b9a7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsFavoritePersistenceWrapperTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsFavoritePersistenceWrapperTest.kt
@@ -25,6 +25,7 @@
 import com.android.systemui.util.time.FakeSystemClock
 import org.junit.After
 import org.junit.Assert.assertEquals
+import org.junit.Assert.assertFalse
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -78,4 +79,15 @@
 
         assertEquals(list, wrapper.readFavorites())
     }
+
+    @Test
+    fun testSaveEmptyOnNonExistingFile() {
+        if (file.exists()) {
+            file.delete()
+        }
+
+        wrapper.storeFavorites(emptyList())
+
+        assertFalse(file.exists())
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/management/FavoritesModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/management/FavoritesModelTest.kt
index ce33a8d..f0003ed 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/management/FavoritesModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/management/FavoritesModelTest.kt
@@ -22,6 +22,7 @@
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.controls.ControlInterface
+import com.android.systemui.controls.CustomIconCache
 import com.android.systemui.controls.controller.ControlInfo
 import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.eq
@@ -57,6 +58,8 @@
     private lateinit var callback: FavoritesModel.FavoritesModelCallback
     @Mock
     private lateinit var adapter: RecyclerView.Adapter<*>
+    @Mock
+    private lateinit var customIconCache: CustomIconCache
     private lateinit var model: FavoritesModel
     private lateinit var dividerWrapper: DividerWrapper
 
@@ -64,7 +67,7 @@
     fun setUp() {
         MockitoAnnotations.initMocks(this)
 
-        model = FavoritesModel(TEST_COMPONENT, INITIAL_FAVORITES, callback)
+        model = FavoritesModel(customIconCache, TEST_COMPONENT, INITIAL_FAVORITES, callback)
         model.attachAdapter(adapter)
         dividerWrapper = model.elements.first { it is DividerWrapper } as DividerWrapper
     }
@@ -89,7 +92,7 @@
     @Test
     fun testInitialElements() {
         val expected = INITIAL_FAVORITES.map {
-            ControlInfoWrapper(TEST_COMPONENT, it, true)
+            ControlInfoWrapper(TEST_COMPONENT, it, true, customIconCache::retrieve)
         } + DividerWrapper()
         assertEquals(expected, model.elements)
     }
@@ -287,5 +290,13 @@
         verify(callback).onFirstChange()
     }
 
+    @Test
+    fun testCacheCalledWhenGettingCustomIcon() {
+        val wrapper = model.elements[0] as ControlInfoWrapper
+        wrapper.customIcon
+
+        verify(customIconCache).retrieve(TEST_COMPONENT, wrapper.controlId)
+    }
+
     private fun getDividerPosition(): Int = model.elements.indexOf(dividerWrapper)
 }
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenBrightnessTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenBrightnessTest.java
index 3ef6027..c82bb9b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenBrightnessTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenBrightnessTest.java
@@ -103,8 +103,6 @@
 
     @Test
     public void testAod_usesLightSensor() {
-        mScreen.transitionTo(UNINITIALIZED, INITIALIZED);
-        mScreen.transitionTo(INITIALIZED, DOZE_AOD);
         mScreen.onScreenState(Display.STATE_DOZE);
 
         mSensor.sendSensorEvent(3);
@@ -114,8 +112,7 @@
 
     @Test
     public void testAod_usesDebugValue() throws Exception {
-        mScreen.transitionTo(UNINITIALIZED, INITIALIZED);
-        mScreen.transitionTo(INITIALIZED, DOZE_AOD);
+        mScreen.onScreenState(Display.STATE_DOZE);
 
         Intent intent = new Intent(DozeScreenBrightness.ACTION_AOD_BRIGHTNESS);
         intent.putExtra(DozeScreenBrightness.BRIGHTNESS_BUCKET, 1);
@@ -166,14 +163,13 @@
     }
 
     @Test
-    public void testDozingAfterPulsing_pausesLightSensor() throws Exception {
+    public void testScreenOffAfterPulsing_pausesLightSensor() throws Exception {
         mScreen.transitionTo(UNINITIALIZED, INITIALIZED);
         mScreen.transitionTo(INITIALIZED, DOZE);
         mScreen.transitionTo(DOZE, DOZE_REQUEST_PULSE);
         mScreen.transitionTo(DOZE_REQUEST_PULSE, DOZE_PULSING);
         mScreen.transitionTo(DOZE_PULSING, DOZE_PULSE_DONE);
         mScreen.transitionTo(DOZE_PULSE_DONE, DOZE);
-        mScreen.onScreenState(Display.STATE_DOZE);
 
         mSensor.sendSensorEvent(1);
 
@@ -181,6 +177,17 @@
     }
 
     @Test
+    public void testOnScreenStateSetBeforeTransition_stillRegistersSensor() {
+        mScreen.transitionTo(UNINITIALIZED, INITIALIZED);
+        mScreen.onScreenState(Display.STATE_DOZE);
+        mScreen.transitionTo(INITIALIZED, DOZE_AOD);
+
+        mSensor.sendSensorEvent(1);
+
+        assertEquals(1, mServiceFake.screenBrightness);
+    }
+
+    @Test
     public void testNullSensor() throws Exception {
         mScreen = new DozeScreenBrightness(mContext, mServiceFake, mSensorManager,
                 null /* sensor */, mBroadcastDispatcher, mDozeHost, null /* handler */,
@@ -191,12 +198,15 @@
         mScreen.transitionTo(INITIALIZED, DOZE_AOD);
         mScreen.transitionTo(DOZE_AOD, DOZE_AOD_PAUSING);
         mScreen.transitionTo(DOZE_AOD_PAUSING, DOZE_AOD_PAUSED);
+        mScreen.onScreenState(Display.STATE_DOZE);
+        mScreen.onScreenState(Display.STATE_OFF);
     }
 
     @Test
     public void testNoBrightnessDeliveredAfterFinish() throws Exception {
         mScreen.transitionTo(UNINITIALIZED, INITIALIZED);
         mScreen.transitionTo(INITIALIZED, DOZE_AOD);
+        mScreen.onScreenState(Display.STATE_DOZE);
         mScreen.transitionTo(DOZE_AOD, FINISH);
 
         mSensor.sendSensorEvent(1);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt
index c63781c..ad70361 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.media
 
+import android.content.Intent
 import android.content.res.ColorStateList
 import android.graphics.Color
 import android.graphics.drawable.GradientDrawable
@@ -23,6 +24,7 @@
 import android.media.MediaMetadata
 import android.media.session.MediaSession
 import android.media.session.PlaybackState
+import android.provider.Settings.ACTION_MEDIA_CONTROLS_SETTINGS
 import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper
 import android.view.View
@@ -35,24 +37,29 @@
 import androidx.constraintlayout.widget.ConstraintSet
 import androidx.lifecycle.LiveData
 import androidx.test.filters.SmallTest
-import com.android.systemui.R
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.statusbar.phone.KeyguardDismissUtil
 import com.android.systemui.util.animation.TransitionLayout
 import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.mockito.eq
 import com.android.systemui.util.time.FakeSystemClock
 import com.google.common.truth.Truth.assertThat
+import dagger.Lazy
 import org.junit.After
 import org.junit.Before
 import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.mockito.ArgumentCaptor
+import org.mockito.ArgumentMatchers.anyLong
 import org.mockito.Mock
+import org.mockito.Mockito.anyBoolean
 import org.mockito.Mockito.mock
+import org.mockito.Mockito.never
 import org.mockito.Mockito.verify
-import org.mockito.Mockito.`when` as whenever
 import org.mockito.junit.MockitoJUnit
+import org.mockito.Mockito.`when` as whenever
 
 private const val KEY = "TEST_KEY"
 private const val APP = "APP"
@@ -81,6 +88,8 @@
     @Mock private lateinit var seekBarViewModel: SeekBarViewModel
     @Mock private lateinit var seekBarData: LiveData<SeekBarViewModel.Progress>
     @Mock private lateinit var mediaViewController: MediaViewController
+    @Mock private lateinit var keyguardDismissUtil: KeyguardDismissUtil
+    @Mock private lateinit var mediaDataManager: MediaDataManager
     @Mock private lateinit var expandedSet: ConstraintSet
     @Mock private lateinit var collapsedSet: ConstraintSet
     private lateinit var appIcon: ImageView
@@ -100,6 +109,11 @@
     private lateinit var action2: ImageButton
     private lateinit var action3: ImageButton
     private lateinit var action4: ImageButton
+    private lateinit var settingsText: TextView
+    private lateinit var settings: View
+    private lateinit var cancel: View
+    private lateinit var dismiss: FrameLayout
+    private lateinit var dismissLabel: View
 
     private lateinit var session: MediaSession
     private val device = MediaDeviceData(true, null, DEVICE_NAME)
@@ -114,7 +128,7 @@
         whenever(mediaViewController.collapsedLayout).thenReturn(collapsedSet)
 
         player = MediaControlPanel(context, bgExecutor, activityStarter, mediaViewController,
-                seekBarViewModel)
+                seekBarViewModel, Lazy { mediaDataManager }, keyguardDismissUtil)
         whenever(seekBarViewModel.progress).thenReturn(seekBarData)
 
         // Mock out a view holder for the player to attach to.
@@ -156,6 +170,16 @@
         whenever(holder.action3).thenReturn(action3)
         action4 = ImageButton(context)
         whenever(holder.action4).thenReturn(action4)
+        settingsText = TextView(context)
+        whenever(holder.settingsText).thenReturn(settingsText)
+        settings = View(context)
+        whenever(holder.settings).thenReturn(settings)
+        cancel = View(context)
+        whenever(holder.cancel).thenReturn(cancel)
+        dismiss = FrameLayout(context)
+        whenever(holder.dismiss).thenReturn(dismiss)
+        dismissLabel = View(context)
+        whenever(holder.dismissLabel).thenReturn(dismissLabel)
 
         // Create media session
         val metadataBuilder = MediaMetadata.Builder().apply {
@@ -183,7 +207,7 @@
     fun bindWhenUnattached() {
         val state = MediaData(USER_ID, true, BG_COLOR, APP, null, ARTIST, TITLE, null, emptyList(),
                 emptyList(), PACKAGE, null, null, device, true, null)
-        player.bind(state)
+        player.bind(state, PACKAGE)
         assertThat(player.isPlaying()).isFalse()
     }
 
@@ -192,7 +216,7 @@
         player.attach(holder)
         val state = MediaData(USER_ID, true, BG_COLOR, APP, null, ARTIST, TITLE, null, emptyList(),
                 emptyList(), PACKAGE, session.getSessionToken(), null, device, true, null)
-        player.bind(state)
+        player.bind(state, PACKAGE)
         assertThat(appName.getText()).isEqualTo(APP)
         assertThat(titleText.getText()).isEqualTo(TITLE)
         assertThat(artistText.getText()).isEqualTo(ARTIST)
@@ -203,7 +227,7 @@
         player.attach(holder)
         val state = MediaData(USER_ID, true, BG_COLOR, APP, null, ARTIST, TITLE, null, emptyList(),
                 emptyList(), PACKAGE, session.getSessionToken(), null, device, true, null)
-        player.bind(state)
+        player.bind(state, PACKAGE)
         val list = ArgumentCaptor.forClass(ColorStateList::class.java)
         verify(view).setBackgroundTintList(list.capture())
         assertThat(list.value).isEqualTo(ColorStateList.valueOf(BG_COLOR))
@@ -214,7 +238,7 @@
         player.attach(holder)
         val state = MediaData(USER_ID, true, BG_COLOR, APP, null, ARTIST, TITLE, null, emptyList(),
                 emptyList(), PACKAGE, session.getSessionToken(), null, device, true, null)
-        player.bind(state)
+        player.bind(state, PACKAGE)
         assertThat(seamlessText.getText()).isEqualTo(DEVICE_NAME)
         assertThat(seamless.isEnabled()).isTrue()
     }
@@ -226,7 +250,7 @@
         player.attach(holder)
         val state = MediaData(USER_ID, true, BG_COLOR, APP, null, ARTIST, TITLE, null, emptyList(),
                 emptyList(), PACKAGE, session.getSessionToken(), null, disabledDevice, true, null)
-        player.bind(state)
+        player.bind(state, PACKAGE)
         verify(expandedSet).setVisibility(seamless.id, View.GONE)
         verify(expandedSet).setVisibility(seamlessFallback.id, View.VISIBLE)
         verify(collapsedSet).setVisibility(seamless.id, View.GONE)
@@ -238,7 +262,7 @@
         player.attach(holder)
         val state = MediaData(USER_ID, true, BG_COLOR, APP, null, ARTIST, TITLE, null, emptyList(),
                 emptyList(), PACKAGE, session.getSessionToken(), null, null, true, null)
-        player.bind(state)
+        player.bind(state, PACKAGE)
         assertThat(seamless.isEnabled()).isTrue()
         assertThat(seamlessText.getText()).isEqualTo(context.getResources().getString(
                 com.android.internal.R.string.ext_media_seamless_action))
@@ -250,8 +274,83 @@
         val state = MediaData(USER_ID, true, BG_COLOR, APP, null, ARTIST, TITLE, null, emptyList(),
                 emptyList(), PACKAGE, session.getSessionToken(), null, device, true, null,
                 resumption = true)
-        player.bind(state)
+        player.bind(state, PACKAGE)
         assertThat(seamlessText.getText()).isEqualTo(DEVICE_NAME)
         assertThat(seamless.isEnabled()).isFalse()
     }
+
+    @Test
+    fun longClick_gutsClosed() {
+        player.attach(holder)
+        whenever(mediaViewController.isGutsVisible).thenReturn(false)
+
+        val captor = ArgumentCaptor.forClass(View.OnLongClickListener::class.java)
+        verify(holder.player).setOnLongClickListener(captor.capture())
+
+        captor.value.onLongClick(holder.player)
+        verify(mediaViewController).openGuts()
+    }
+
+    @Test
+    fun longClick_gutsOpen() {
+        player.attach(holder)
+        whenever(mediaViewController.isGutsVisible).thenReturn(true)
+
+        val captor = ArgumentCaptor.forClass(View.OnLongClickListener::class.java)
+        verify(holder.player).setOnLongClickListener(captor.capture())
+
+        captor.value.onLongClick(holder.player)
+        verify(mediaViewController, never()).openGuts()
+    }
+
+    @Test
+    fun cancelButtonClick_animation() {
+        player.attach(holder)
+
+        cancel.callOnClick()
+
+        verify(mediaViewController).closeGuts(false)
+    }
+
+    @Test
+    fun settingsButtonClick() {
+        player.attach(holder)
+
+        settings.callOnClick()
+
+        val captor = ArgumentCaptor.forClass(Intent::class.java)
+        verify(activityStarter).startActivity(captor.capture(), eq(true))
+
+        assertThat(captor.value.action).isEqualTo(ACTION_MEDIA_CONTROLS_SETTINGS)
+    }
+
+    @Test
+    fun dismissButtonClick() {
+        val mediaKey = "key for dismissal"
+        player.attach(holder)
+        val state = MediaData(USER_ID, true, BG_COLOR, APP, null, ARTIST, TITLE, null, emptyList(),
+                emptyList(), PACKAGE, session.getSessionToken(), null, null, true, null,
+                notificationKey = KEY)
+        player.bind(state, mediaKey)
+
+        assertThat(dismiss.isEnabled).isEqualTo(true)
+        dismiss.callOnClick()
+        val captor = ArgumentCaptor.forClass(ActivityStarter.OnDismissAction::class.java)
+        verify(keyguardDismissUtil).executeWhenUnlocked(captor.capture(), anyBoolean())
+
+        captor.value.onDismiss()
+        verify(mediaDataManager).dismissMediaData(eq(mediaKey), anyLong())
+    }
+
+    @Test
+    fun dismissButtonDisabled() {
+        val mediaKey = "key for dismissal"
+        player.attach(holder)
+        val state = MediaData(USER_ID, true, BG_COLOR, APP, null, ARTIST, TITLE, null, emptyList(),
+                emptyList(), PACKAGE, session.getSessionToken(), null, null, true, null,
+                isClearable = false, notificationKey = KEY)
+        player.bind(state, mediaKey)
+
+        assertThat(dismiss.isEnabled).isEqualTo(false)
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataCombineLatestTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataCombineLatestTest.java
index dbc5596..609b847 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataCombineLatestTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataCombineLatestTest.java
@@ -20,8 +20,8 @@
 
 import static org.mockito.Mockito.any;
 import static org.mockito.Mockito.eq;
-import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
 import static org.mockito.Mockito.verify;
 
 import android.graphics.Color;
@@ -33,20 +33,25 @@
 import com.android.systemui.SysuiTestCase;
 
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.ArgumentCaptor;
 import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
 
 import java.util.ArrayList;
-import java.util.Map;
 
 @SmallTest
 @RunWith(AndroidTestingRunner.class)
 @TestableLooper.RunWithLooper
 public class MediaDataCombineLatestTest extends SysuiTestCase {
 
+    @Rule public MockitoRule mockito = MockitoJUnit.rule();
+
     private static final String KEY = "TEST_KEY";
+    private static final String OLD_KEY = "TEST_KEY_OLD";
     private static final String APP = "APP";
     private static final String PACKAGE = "PKG";
     private static final int BG_COLOR = Color.RED;
@@ -57,39 +62,26 @@
 
     private MediaDataCombineLatest mManager;
 
-    @Mock private MediaDataManager mDataSource;
-    @Mock private MediaDeviceManager mDeviceSource;
     @Mock private MediaDataManager.Listener mListener;
 
-    private MediaDataManager.Listener mDataListener;
-    private MediaDeviceManager.Listener mDeviceListener;
-
     private MediaData mMediaData;
     private MediaDeviceData mDeviceData;
 
     @Before
     public void setUp() {
-        mDataSource = mock(MediaDataManager.class);
-        mDeviceSource = mock(MediaDeviceManager.class);
-        mListener = mock(MediaDataManager.Listener.class);
-
-        mManager = new MediaDataCombineLatest(mDataSource, mDeviceSource);
-
-        mDataListener = captureDataListener();
-        mDeviceListener = captureDeviceListener();
-
+        mManager = new MediaDataCombineLatest();
         mManager.addListener(mListener);
 
         mMediaData = new MediaData(USER_ID, true, BG_COLOR, APP, null, ARTIST, TITLE, null,
-                new ArrayList<>(), new ArrayList<>(), PACKAGE, null, null, null, true, null, false,
-                KEY, false);
+                new ArrayList<>(), new ArrayList<>(), PACKAGE, null, null, null, true, null, true,
+                false, KEY, false, false, false);
         mDeviceData = new MediaDeviceData(true, null, DEVICE_NAME);
     }
 
     @Test
     public void eventNotEmittedWithoutDevice() {
         // WHEN data source emits an event without device data
-        mDataListener.onMediaDataLoaded(KEY, null, mMediaData);
+        mManager.onMediaDataLoaded(KEY, null, mMediaData);
         // THEN an event isn't emitted
         verify(mListener, never()).onMediaDataLoaded(eq(KEY), any(), any());
     }
@@ -97,7 +89,7 @@
     @Test
     public void eventNotEmittedWithoutMedia() {
         // WHEN device source emits an event without media data
-        mDeviceListener.onMediaDeviceChanged(KEY, mDeviceData);
+        mManager.onMediaDeviceChanged(KEY, null, mDeviceData);
         // THEN an event isn't emitted
         verify(mListener, never()).onMediaDataLoaded(eq(KEY), any(), any());
     }
@@ -105,9 +97,9 @@
     @Test
     public void emitEventAfterDeviceFirst() {
         // GIVEN that a device event has already been received
-        mDeviceListener.onMediaDeviceChanged(KEY, mDeviceData);
+        mManager.onMediaDeviceChanged(KEY, null, mDeviceData);
         // WHEN media event is received
-        mDataListener.onMediaDataLoaded(KEY, null, mMediaData);
+        mManager.onMediaDataLoaded(KEY, null, mMediaData);
         // THEN the listener receives a combined event
         ArgumentCaptor<MediaData> captor = ArgumentCaptor.forClass(MediaData.class);
         verify(mListener).onMediaDataLoaded(eq(KEY), any(), captor.capture());
@@ -117,9 +109,9 @@
     @Test
     public void emitEventAfterMediaFirst() {
         // GIVEN that media event has already been received
-        mDataListener.onMediaDataLoaded(KEY, null, mMediaData);
+        mManager.onMediaDataLoaded(KEY, null, mMediaData);
         // WHEN device event is received
-        mDeviceListener.onMediaDeviceChanged(KEY, mDeviceData);
+        mManager.onMediaDeviceChanged(KEY, null, mDeviceData);
         // THEN the listener receives a combined event
         ArgumentCaptor<MediaData> captor = ArgumentCaptor.forClass(MediaData.class);
         verify(mListener).onMediaDataLoaded(eq(KEY), any(), captor.capture());
@@ -127,62 +119,94 @@
     }
 
     @Test
+    public void migrateKeyMediaFirst() {
+        // GIVEN that media and device info has already been received
+        mManager.onMediaDataLoaded(OLD_KEY, null, mMediaData);
+        mManager.onMediaDeviceChanged(OLD_KEY, null, mDeviceData);
+        reset(mListener);
+        // WHEN a key migration event is received
+        mManager.onMediaDataLoaded(KEY, OLD_KEY, mMediaData);
+        // THEN the listener receives a combined event
+        ArgumentCaptor<MediaData> captor = ArgumentCaptor.forClass(MediaData.class);
+        verify(mListener).onMediaDataLoaded(eq(KEY), eq(OLD_KEY), captor.capture());
+        assertThat(captor.getValue().getDevice()).isNotNull();
+    }
+
+    @Test
+    public void migrateKeyDeviceFirst() {
+        // GIVEN that media and device info has already been received
+        mManager.onMediaDataLoaded(OLD_KEY, null, mMediaData);
+        mManager.onMediaDeviceChanged(OLD_KEY, null, mDeviceData);
+        reset(mListener);
+        // WHEN a key migration event is received
+        mManager.onMediaDeviceChanged(KEY, OLD_KEY, mDeviceData);
+        // THEN the listener receives a combined event
+        ArgumentCaptor<MediaData> captor = ArgumentCaptor.forClass(MediaData.class);
+        verify(mListener).onMediaDataLoaded(eq(KEY), eq(OLD_KEY), captor.capture());
+        assertThat(captor.getValue().getDevice()).isNotNull();
+    }
+
+    @Test
+    public void migrateKeyMediaAfter() {
+        // GIVEN that media and device info has already been received
+        mManager.onMediaDataLoaded(OLD_KEY, null, mMediaData);
+        mManager.onMediaDeviceChanged(OLD_KEY, null, mDeviceData);
+        mManager.onMediaDeviceChanged(KEY, OLD_KEY, mDeviceData);
+        reset(mListener);
+        // WHEN a second key migration event is received for media
+        mManager.onMediaDataLoaded(KEY, OLD_KEY, mMediaData);
+        // THEN the key has already been migrated
+        ArgumentCaptor<MediaData> captor = ArgumentCaptor.forClass(MediaData.class);
+        verify(mListener).onMediaDataLoaded(eq(KEY), eq(KEY), captor.capture());
+        assertThat(captor.getValue().getDevice()).isNotNull();
+    }
+
+    @Test
+    public void migrateKeyDeviceAfter() {
+        // GIVEN that media and device info has already been received
+        mManager.onMediaDataLoaded(OLD_KEY, null, mMediaData);
+        mManager.onMediaDeviceChanged(OLD_KEY, null, mDeviceData);
+        mManager.onMediaDataLoaded(KEY, OLD_KEY, mMediaData);
+        reset(mListener);
+        // WHEN a second key migration event is received for the device
+        mManager.onMediaDeviceChanged(KEY, OLD_KEY, mDeviceData);
+        // THEN the key has already be migrated
+        ArgumentCaptor<MediaData> captor = ArgumentCaptor.forClass(MediaData.class);
+        verify(mListener).onMediaDataLoaded(eq(KEY), eq(KEY), captor.capture());
+        assertThat(captor.getValue().getDevice()).isNotNull();
+    }
+
+    @Test
     public void mediaDataRemoved() {
         // WHEN media data is removed without first receiving device or data
-        mDataListener.onMediaDataRemoved(KEY);
+        mManager.onMediaDataRemoved(KEY);
         // THEN a removed event isn't emitted
         verify(mListener, never()).onMediaDataRemoved(eq(KEY));
     }
 
     @Test
     public void mediaDataRemovedAfterMediaEvent() {
-        mDataListener.onMediaDataLoaded(KEY, null, mMediaData);
-        mDataListener.onMediaDataRemoved(KEY);
+        mManager.onMediaDataLoaded(KEY, null, mMediaData);
+        mManager.onMediaDataRemoved(KEY);
         verify(mListener).onMediaDataRemoved(eq(KEY));
     }
 
     @Test
     public void mediaDataRemovedAfterDeviceEvent() {
-        mDeviceListener.onMediaDeviceChanged(KEY, mDeviceData);
-        mDataListener.onMediaDataRemoved(KEY);
+        mManager.onMediaDeviceChanged(KEY, null, mDeviceData);
+        mManager.onMediaDataRemoved(KEY);
         verify(mListener).onMediaDataRemoved(eq(KEY));
     }
 
     @Test
     public void mediaDataKeyUpdated() {
         // GIVEN that device and media events have already been received
-        mDataListener.onMediaDataLoaded(KEY, null, mMediaData);
-        mDeviceListener.onMediaDeviceChanged(KEY, mDeviceData);
+        mManager.onMediaDataLoaded(KEY, null, mMediaData);
+        mManager.onMediaDeviceChanged(KEY, null, mDeviceData);
         // WHEN the key is changed
-        mDataListener.onMediaDataLoaded("NEW_KEY", KEY, mMediaData);
+        mManager.onMediaDataLoaded("NEW_KEY", KEY, mMediaData);
         // THEN the listener gets a load event with the correct keys
         ArgumentCaptor<MediaData> captor = ArgumentCaptor.forClass(MediaData.class);
         verify(mListener).onMediaDataLoaded(eq("NEW_KEY"), any(), captor.capture());
     }
-
-    @Test
-    public void getDataIncludesDevice() {
-        // GIVEN that device and media events have been received
-        mDeviceListener.onMediaDeviceChanged(KEY, mDeviceData);
-        mDataListener.onMediaDataLoaded(KEY, null, mMediaData);
-
-        // THEN the result of getData includes device info
-        Map<String, MediaData> results = mManager.getData();
-        assertThat(results.get(KEY)).isNotNull();
-        assertThat(results.get(KEY).getDevice()).isEqualTo(mDeviceData);
-    }
-
-    private MediaDataManager.Listener captureDataListener() {
-        ArgumentCaptor<MediaDataManager.Listener> captor = ArgumentCaptor.forClass(
-                MediaDataManager.Listener.class);
-        verify(mDataSource).addListener(captor.capture());
-        return captor.getValue();
-    }
-
-    private MediaDeviceManager.Listener captureDeviceListener() {
-        ArgumentCaptor<MediaDeviceManager.Listener> captor = ArgumentCaptor.forClass(
-                MediaDeviceManager.Listener.class);
-        verify(mDeviceSource).addListener(captor.capture());
-        return captor.getValue();
-    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataFilterTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataFilterTest.kt
index afb64a7..36b6527 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataFilterTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataFilterTest.kt
@@ -32,6 +32,7 @@
 import org.mockito.Mockito
 import org.mockito.Mockito.`when`
 import org.mockito.Mockito.never
+import org.mockito.Mockito.reset
 import org.mockito.Mockito.verify
 import org.mockito.MockitoAnnotations
 import java.util.concurrent.Executor
@@ -56,8 +57,6 @@
 class MediaDataFilterTest : SysuiTestCase() {
 
     @Mock
-    private lateinit var combineLatest: MediaDataCombineLatest
-    @Mock
     private lateinit var listener: MediaDataManager.Listener
     @Mock
     private lateinit var broadcastDispatcher: BroadcastDispatcher
@@ -78,8 +77,9 @@
     @Before
     fun setup() {
         MockitoAnnotations.initMocks(this)
-        mediaDataFilter = MediaDataFilter(combineLatest, broadcastDispatcher, mediaResumeListener,
-            mediaDataManager, lockscreenUserManager, executor)
+        mediaDataFilter = MediaDataFilter(broadcastDispatcher, mediaResumeListener,
+                lockscreenUserManager, executor)
+        mediaDataFilter.mediaDataManager = mediaDataManager
         mediaDataFilter.addListener(listener)
 
         // Start all tests as main user
@@ -152,8 +152,9 @@
     @Test
     fun testOnUserSwitched_addsNewUserControls() {
         // GIVEN that we had some media for both users
-        val dataMap = mapOf(KEY to dataMain, KEY_ALT to dataGuest)
-        `when`(combineLatest.getData()).thenReturn(dataMap)
+        mediaDataFilter.onMediaDataLoaded(KEY, null, dataMain)
+        mediaDataFilter.onMediaDataLoaded(KEY_ALT, null, dataGuest)
+        reset(listener)
 
         // and we switch to guest user
         setUser(USER_GUEST)
@@ -213,4 +214,4 @@
 
         verify(mediaDataManager).setTimedOut(eq(KEY), eq(true))
     }
-}
\ No newline at end of file
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataManagerTest.kt
index 59c2d0e..b47ee29 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataManagerTest.kt
@@ -15,6 +15,7 @@
 import com.android.systemui.dump.DumpManager
 import com.android.systemui.statusbar.SbnBuilder
 import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.mockito.capture
 import com.android.systemui.util.mockito.eq
 import com.android.systemui.util.time.FakeSystemClock
 import com.google.common.truth.Truth.assertThat
@@ -23,9 +24,13 @@
 import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.Captor
 import org.mockito.Mock
 import org.mockito.Mockito
 import org.mockito.Mockito.mock
+import org.mockito.Mockito.never
+import org.mockito.Mockito.reset
 import org.mockito.Mockito.verify
 import org.mockito.junit.MockitoJUnit
 import org.mockito.Mockito.`when` as whenever
@@ -47,6 +52,7 @@
 @RunWith(AndroidTestingRunner::class)
 class MediaDataManagerTest : SysuiTestCase() {
 
+    @JvmField @Rule val mockito = MockitoJUnit.rule()
     @Mock lateinit var mediaControllerFactory: MediaControllerFactory
     @Mock lateinit var controller: MediaController
     lateinit var session: MediaSession
@@ -57,19 +63,36 @@
     @Mock lateinit var broadcastDispatcher: BroadcastDispatcher
     @Mock lateinit var mediaTimeoutListener: MediaTimeoutListener
     @Mock lateinit var mediaResumeListener: MediaResumeListener
+    @Mock lateinit var mediaSessionBasedFilter: MediaSessionBasedFilter
+    @Mock lateinit var mediaDeviceManager: MediaDeviceManager
+    @Mock lateinit var mediaDataCombineLatest: MediaDataCombineLatest
+    @Mock lateinit var mediaDataFilter: MediaDataFilter
+    @Mock lateinit var listener: MediaDataManager.Listener
     @Mock lateinit var pendingIntent: PendingIntent
-    @JvmField @Rule val mockito = MockitoJUnit.rule()
     lateinit var mediaDataManager: MediaDataManager
     lateinit var mediaNotification: StatusBarNotification
+    @Captor lateinit var mediaDataCaptor: ArgumentCaptor<MediaData>
 
     @Before
     fun setup() {
         foregroundExecutor = FakeExecutor(FakeSystemClock())
         backgroundExecutor = FakeExecutor(FakeSystemClock())
-        mediaDataManager = MediaDataManager(context, backgroundExecutor, foregroundExecutor,
-                mediaControllerFactory, broadcastDispatcher, dumpManager,
-                mediaTimeoutListener, mediaResumeListener, useMediaResumption = true,
-                useQsMediaPlayer = true)
+        mediaDataManager = MediaDataManager(
+            context = context,
+            backgroundExecutor = backgroundExecutor,
+            foregroundExecutor = foregroundExecutor,
+            mediaControllerFactory = mediaControllerFactory,
+            broadcastDispatcher = broadcastDispatcher,
+            dumpManager = dumpManager,
+            mediaTimeoutListener = mediaTimeoutListener,
+            mediaResumeListener = mediaResumeListener,
+            mediaSessionBasedFilter = mediaSessionBasedFilter,
+            mediaDeviceManager = mediaDeviceManager,
+            mediaDataCombineLatest = mediaDataCombineLatest,
+            mediaDataFilter = mediaDataFilter,
+            useMediaResumption = true,
+            useQsMediaPlayer = true
+        )
         session = MediaSession(context, "MediaDataManagerTestSession")
         mediaNotification = SbnBuilder().run {
             setPkg(PACKAGE_NAME)
@@ -84,6 +107,12 @@
             putString(MediaMetadata.METADATA_KEY_TITLE, SESSION_TITLE)
         }
         whenever(mediaControllerFactory.create(eq(session.sessionToken))).thenReturn(controller)
+
+        // This is an ugly hack for now. The mediaSessionBasedFilter is one of the internal
+        // listeners in the internal processing pipeline. It receives events, but ince it is a
+        // mock, it doesn't pass those events along the chain to the external listeners. So, just
+        // treat mediaSessionBasedFilter as a listener for testing.
+        listener = mediaSessionBasedFilter
     }
 
     @After
@@ -113,8 +142,6 @@
 
     @Test
     fun testOnMetaDataLoaded_callsListener() {
-        val listener = mock(MediaDataManager.Listener::class.java)
-        mediaDataManager.addListener(listener)
         mediaDataManager.onNotificationAdded(KEY, mediaNotification)
         mediaDataManager.onMediaDataLoaded(KEY, oldKey = null, data = mock(MediaData::class.java))
         verify(listener).onMediaDataLoaded(eq(KEY), eq(null), anyObject())
@@ -122,84 +149,74 @@
 
     @Test
     fun testOnMetaDataLoaded_conservesActiveFlag() {
-        val listener = TestListener()
         whenever(mediaControllerFactory.create(anyObject())).thenReturn(controller)
         whenever(controller.metadata).thenReturn(metadataBuilder.build())
         mediaDataManager.addListener(listener)
         mediaDataManager.onNotificationAdded(KEY, mediaNotification)
         assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
         assertThat(foregroundExecutor.runAllReady()).isEqualTo(1)
-        assertThat(listener.data!!.active).isTrue()
+        verify(listener).onMediaDataLoaded(eq(KEY), eq(null), capture(mediaDataCaptor))
+        assertThat(mediaDataCaptor.value!!.active).isTrue()
     }
 
     @Test
     fun testOnNotificationRemoved_callsListener() {
-        val listener = mock(MediaDataManager.Listener::class.java)
-        mediaDataManager.addListener(listener)
         mediaDataManager.onNotificationAdded(KEY, mediaNotification)
         mediaDataManager.onMediaDataLoaded(KEY, oldKey = null, data = mock(MediaData::class.java))
         mediaDataManager.onNotificationRemoved(KEY)
-
         verify(listener).onMediaDataRemoved(eq(KEY))
     }
 
     @Test
     fun testOnNotificationRemoved_withResumption() {
         // GIVEN that the manager has a notification with a resume action
-        val listener = TestListener()
-        mediaDataManager.addListener(listener)
         whenever(controller.metadata).thenReturn(metadataBuilder.build())
         mediaDataManager.onNotificationAdded(KEY, mediaNotification)
         assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
         assertThat(foregroundExecutor.runAllReady()).isEqualTo(1)
-        val data = listener.data!!
+        verify(listener).onMediaDataLoaded(eq(KEY), eq(null), capture(mediaDataCaptor))
+        val data = mediaDataCaptor.value
         assertThat(data.resumption).isFalse()
         mediaDataManager.onMediaDataLoaded(KEY, null, data.copy(resumeAction = Runnable {}))
         // WHEN the notification is removed
         mediaDataManager.onNotificationRemoved(KEY)
         // THEN the media data indicates that it is for resumption
-        assertThat(listener.data!!.resumption).isTrue()
-        // AND the new key is the package name
-        assertThat(listener.key!!).isEqualTo(PACKAGE_NAME)
-        assertThat(listener.oldKey!!).isEqualTo(KEY)
-        assertThat(listener.removedKey).isNull()
+        verify(listener).onMediaDataLoaded(eq(PACKAGE_NAME), eq(KEY), capture(mediaDataCaptor))
+        assertThat(mediaDataCaptor.value.resumption).isTrue()
     }
 
     @Test
     fun testOnNotificationRemoved_twoWithResumption() {
         // GIVEN that the manager has two notifications with resume actions
-        val listener = TestListener()
-        mediaDataManager.addListener(listener)
         whenever(controller.metadata).thenReturn(metadataBuilder.build())
         mediaDataManager.onNotificationAdded(KEY, mediaNotification)
         mediaDataManager.onNotificationAdded(KEY_2, mediaNotification)
         assertThat(backgroundExecutor.runAllReady()).isEqualTo(2)
         assertThat(foregroundExecutor.runAllReady()).isEqualTo(2)
-        val data = listener.data!!
+        verify(listener).onMediaDataLoaded(eq(KEY), eq(null), capture(mediaDataCaptor))
+        val data = mediaDataCaptor.value
         assertThat(data.resumption).isFalse()
         val resumableData = data.copy(resumeAction = Runnable {})
         mediaDataManager.onMediaDataLoaded(KEY, null, resumableData)
         mediaDataManager.onMediaDataLoaded(KEY_2, null, resumableData)
+        reset(listener)
         // WHEN the first is removed
         mediaDataManager.onNotificationRemoved(KEY)
         // THEN the data is for resumption and the key is migrated to the package name
-        assertThat(listener.data!!.resumption).isTrue()
-        assertThat(listener.key!!).isEqualTo(PACKAGE_NAME)
-        assertThat(listener.oldKey!!).isEqualTo(KEY)
-        assertThat(listener.removedKey).isNull()
+        verify(listener).onMediaDataLoaded(eq(PACKAGE_NAME), eq(KEY), capture(mediaDataCaptor))
+        assertThat(mediaDataCaptor.value.resumption).isTrue()
+        verify(listener, never()).onMediaDataRemoved(eq(KEY))
         // WHEN the second is removed
         mediaDataManager.onNotificationRemoved(KEY_2)
         // THEN the data is for resumption and the second key is removed
-        assertThat(listener.data!!.resumption).isTrue()
-        assertThat(listener.key!!).isEqualTo(PACKAGE_NAME)
-        assertThat(listener.oldKey!!).isEqualTo(PACKAGE_NAME)
-        assertThat(listener.removedKey!!).isEqualTo(KEY_2)
+        verify(listener).onMediaDataLoaded(eq(PACKAGE_NAME), eq(PACKAGE_NAME),
+                capture(mediaDataCaptor))
+        assertThat(mediaDataCaptor.value.resumption).isTrue()
+        verify(listener).onMediaDataRemoved(eq(KEY_2))
     }
 
     @Test
     fun testAddResumptionControls() {
-        val listener = TestListener()
-        mediaDataManager.addListener(listener)
         // WHEN resumption controls are added`
         val desc = MediaDescription.Builder().run {
             setTitle(SESSION_TITLE)
@@ -210,32 +227,23 @@
         assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
         assertThat(foregroundExecutor.runAllReady()).isEqualTo(1)
         // THEN the media data indicates that it is for resumption
-        val data = listener.data!!
+        verify(listener).onMediaDataLoaded(eq(PACKAGE_NAME), eq(null), capture(mediaDataCaptor))
+        val data = mediaDataCaptor.value
         assertThat(data.resumption).isTrue()
         assertThat(data.song).isEqualTo(SESSION_TITLE)
         assertThat(data.app).isEqualTo(APP_NAME)
         assertThat(data.actions).hasSize(1)
     }
 
-    /**
-     * Simple implementation of [MediaDataManager.Listener] for the test.
-     *
-     * Giving up on trying to get a mock Listener and ArgumentCaptor to work.
-     */
-    private class TestListener : MediaDataManager.Listener {
-        var data: MediaData? = null
-        var key: String? = null
-        var oldKey: String? = null
-        var removedKey: String? = null
+    @Test
+    fun testDismissMedia_listenerCalled() {
+        mediaDataManager.onNotificationAdded(KEY, mediaNotification)
+        mediaDataManager.onMediaDataLoaded(KEY, oldKey = null, data = mock(MediaData::class.java))
+        mediaDataManager.dismissMediaData(KEY, 0L)
 
-        override fun onMediaDataLoaded(key: String, oldKey: String?, data: MediaData) {
-            this.key = key
-            this.oldKey = oldKey
-            this.data = data
-        }
+        foregroundExecutor.advanceClockToLast()
+        foregroundExecutor.runAllReady()
 
-        override fun onMediaDataRemoved(key: String) {
-            removedKey = key
-        }
+        verify(listener).onMediaDataRemoved(eq(KEY))
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDeviceManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDeviceManagerTest.kt
index fc22eeb..ab3b208 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDeviceManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDeviceManagerTest.kt
@@ -16,13 +16,12 @@
 
 package com.android.systemui.media
 
-import android.app.Notification
 import android.graphics.drawable.Drawable
-import android.media.MediaMetadata
 import android.media.MediaRouter2Manager
 import android.media.RoutingSessionInfo
+import android.media.session.MediaController
+import android.media.session.MediaController.PlaybackInfo
 import android.media.session.MediaSession
-import android.media.session.PlaybackState
 import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper
 import androidx.test.filters.SmallTest
@@ -55,7 +54,6 @@
 private const val KEY_OLD = "TEST_KEY_OLD"
 private const val PACKAGE = "PKG"
 private const val SESSION_KEY = "SESSION_KEY"
-private const val SESSION_ARTIST = "SESSION_ARTIST"
 private const val SESSION_TITLE = "SESSION_TITLE"
 private const val DEVICE_NAME = "DEVICE_NAME"
 private const val USER_ID = 0
@@ -68,28 +66,29 @@
 public class MediaDeviceManagerTest : SysuiTestCase() {
 
     private lateinit var manager: MediaDeviceManager
-    @Mock private lateinit var mediaDataManager: MediaDataManager
+    @Mock private lateinit var controllerFactory: MediaControllerFactory
     @Mock private lateinit var lmmFactory: LocalMediaManagerFactory
     @Mock private lateinit var lmm: LocalMediaManager
     @Mock private lateinit var mr2: MediaRouter2Manager
-    private lateinit var fakeExecutor: FakeExecutor
+    private lateinit var fakeFgExecutor: FakeExecutor
+    private lateinit var fakeBgExecutor: FakeExecutor
     @Mock private lateinit var dumpster: DumpManager
     @Mock private lateinit var listener: MediaDeviceManager.Listener
     @Mock private lateinit var device: MediaDevice
     @Mock private lateinit var icon: Drawable
     @Mock private lateinit var route: RoutingSessionInfo
+    @Mock private lateinit var controller: MediaController
+    @Mock private lateinit var playbackInfo: PlaybackInfo
     private lateinit var session: MediaSession
-    private lateinit var metadataBuilder: MediaMetadata.Builder
-    private lateinit var playbackBuilder: PlaybackState.Builder
-    private lateinit var notifBuilder: Notification.Builder
     private lateinit var mediaData: MediaData
     @JvmField @Rule val mockito = MockitoJUnit.rule()
 
     @Before
     fun setUp() {
-        fakeExecutor = FakeExecutor(FakeSystemClock())
-        manager = MediaDeviceManager(context, lmmFactory, mr2, fakeExecutor, mediaDataManager,
-                dumpster)
+        fakeFgExecutor = FakeExecutor(FakeSystemClock())
+        fakeBgExecutor = FakeExecutor(FakeSystemClock())
+        manager = MediaDeviceManager(controllerFactory, lmmFactory, mr2, fakeFgExecutor,
+                fakeBgExecutor, dumpster)
         manager.addListener(listener)
 
         // Configure mocks.
@@ -100,28 +99,13 @@
         whenever(mr2.getRoutingSessionForMediaController(any())).thenReturn(route)
 
         // Create a media sesssion and notification for testing.
-        metadataBuilder = MediaMetadata.Builder().apply {
-            putString(MediaMetadata.METADATA_KEY_ARTIST, SESSION_ARTIST)
-            putString(MediaMetadata.METADATA_KEY_TITLE, SESSION_TITLE)
-        }
-        playbackBuilder = PlaybackState.Builder().apply {
-            setState(PlaybackState.STATE_PAUSED, 6000L, 1f)
-            setActions(PlaybackState.ACTION_PLAY)
-        }
-        session = MediaSession(context, SESSION_KEY).apply {
-            setMetadata(metadataBuilder.build())
-            setPlaybackState(playbackBuilder.build())
-        }
-        session.setActive(true)
-        notifBuilder = Notification.Builder(context, "NONE").apply {
-            setContentTitle(SESSION_TITLE)
-            setContentText(SESSION_ARTIST)
-            setSmallIcon(android.R.drawable.ic_media_pause)
-            setStyle(Notification.MediaStyle().setMediaSession(session.getSessionToken()))
-        }
+        session = MediaSession(context, SESSION_KEY)
+
         mediaData = MediaData(USER_ID, true, 0, PACKAGE, null, null, SESSION_TITLE, null,
             emptyList(), emptyList(), PACKAGE, session.sessionToken, clickIntent = null,
             device = null, active = true, resumeAction = null)
+        whenever(controllerFactory.create(session.sessionToken))
+                .thenReturn(controller)
     }
 
     @After
@@ -144,13 +128,15 @@
     fun loadAndRemoveMediaData() {
         manager.onMediaDataLoaded(KEY, null, mediaData)
         manager.onMediaDataRemoved(KEY)
+        fakeBgExecutor.runAllReady()
         verify(lmm).unregisterCallback(any())
     }
 
     @Test
     fun loadMediaDataWithNullToken() {
         manager.onMediaDataLoaded(KEY, null, mediaData.copy(token = null))
-        fakeExecutor.runAllReady()
+        fakeBgExecutor.runAllReady()
+        fakeFgExecutor.runAllReady()
         val data = captureDeviceData(KEY)
         assertThat(data.enabled).isTrue()
         assertThat(data.name).isEqualTo(DEVICE_NAME)
@@ -163,10 +149,12 @@
         reset(listener)
         // WHEN data is loaded with a new key
         manager.onMediaDataLoaded(KEY, KEY_OLD, mediaData)
+        fakeBgExecutor.runAllReady()
+        fakeFgExecutor.runAllReady()
         // THEN the listener for the old key should removed.
         verify(lmm).unregisterCallback(any())
         // AND a new device event emitted
-        val data = captureDeviceData(KEY)
+        val data = captureDeviceData(KEY, KEY_OLD)
         assertThat(data.enabled).isTrue()
         assertThat(data.name).isEqualTo(DEVICE_NAME)
     }
@@ -179,26 +167,32 @@
         // WHEN the new key is the same as the old key
         manager.onMediaDataLoaded(KEY, KEY, mediaData)
         // THEN no event should be emitted
-        verify(listener, never()).onMediaDeviceChanged(eq(KEY), any())
+        verify(listener, never()).onMediaDeviceChanged(eq(KEY), eq(null), any())
     }
 
     @Test
     fun unknownOldKey() {
-        manager.onMediaDataLoaded(KEY, "unknown", mediaData)
-        verify(listener).onMediaDeviceChanged(eq(KEY), any())
+        val oldKey = "unknown"
+        manager.onMediaDataLoaded(KEY, oldKey, mediaData)
+        fakeBgExecutor.runAllReady()
+        fakeFgExecutor.runAllReady()
+        verify(listener).onMediaDeviceChanged(eq(KEY), eq(oldKey), any())
     }
 
     @Test
     fun updateToSessionTokenWithNullRoute() {
         // GIVEN that media data has been loaded with a null token
         manager.onMediaDataLoaded(KEY, null, mediaData.copy(token = null))
+        fakeBgExecutor.runAllReady()
+        fakeFgExecutor.runAllReady()
+        reset(listener)
         // WHEN media data is loaded with a different token
         // AND that token results in a null route
-        reset(listener)
         whenever(mr2.getRoutingSessionForMediaController(any())).thenReturn(null)
         manager.onMediaDataLoaded(KEY, null, mediaData)
+        fakeBgExecutor.runAllReady()
+        fakeFgExecutor.runAllReady()
         // THEN the device should be disabled
-        fakeExecutor.runAllReady()
         val data = captureDeviceData(KEY)
         assertThat(data.enabled).isFalse()
         assertThat(data.name).isNull()
@@ -209,7 +203,8 @@
     fun deviceEventOnAddNotification() {
         // WHEN a notification is added
         manager.onMediaDataLoaded(KEY, null, mediaData)
-        val deviceCallback = captureCallback()
+        fakeBgExecutor.runAllReady()
+        fakeFgExecutor.runAllReady()
         // THEN the update is dispatched to the listener
         val data = captureDeviceData(KEY)
         assertThat(data.enabled).isTrue()
@@ -223,16 +218,18 @@
         manager.removeListener(listener)
         // THEN it doesn't receive device events
         manager.onMediaDataLoaded(KEY, null, mediaData)
-        verify(listener, never()).onMediaDeviceChanged(eq(KEY), any())
+        verify(listener, never()).onMediaDeviceChanged(eq(KEY), eq(null), any())
     }
 
     @Test
     fun deviceListUpdate() {
         manager.onMediaDataLoaded(KEY, null, mediaData)
+        fakeBgExecutor.runAllReady()
         val deviceCallback = captureCallback()
         // WHEN the device list changes
         deviceCallback.onDeviceListUpdate(mutableListOf(device))
-        assertThat(fakeExecutor.runAllReady()).isEqualTo(1)
+        assertThat(fakeBgExecutor.runAllReady()).isEqualTo(1)
+        assertThat(fakeFgExecutor.runAllReady()).isEqualTo(1)
         // THEN the update is dispatched to the listener
         val data = captureDeviceData(KEY)
         assertThat(data.enabled).isTrue()
@@ -243,10 +240,12 @@
     @Test
     fun selectedDeviceStateChanged() {
         manager.onMediaDataLoaded(KEY, null, mediaData)
+        fakeBgExecutor.runAllReady()
         val deviceCallback = captureCallback()
         // WHEN the selected device changes state
         deviceCallback.onSelectedDeviceStateChanged(device, 1)
-        assertThat(fakeExecutor.runAllReady()).isEqualTo(1)
+        assertThat(fakeBgExecutor.runAllReady()).isEqualTo(1)
+        assertThat(fakeFgExecutor.runAllReady()).isEqualTo(1)
         // THEN the update is dispatched to the listener
         val data = captureDeviceData(KEY)
         assertThat(data.enabled).isTrue()
@@ -269,6 +268,8 @@
         whenever(mr2.getRoutingSessionForMediaController(any())).thenReturn(null)
         // WHEN a notification is added
         manager.onMediaDataLoaded(KEY, null, mediaData)
+        fakeBgExecutor.runAllReady()
+        fakeFgExecutor.runAllReady()
         // THEN the device is disabled
         val data = captureDeviceData(KEY)
         assertThat(data.enabled).isFalse()
@@ -280,13 +281,16 @@
     fun deviceDisabledWhenMR2ReturnsNullRouteInfoOnDeviceChanged() {
         // GIVEN a notif is added
         manager.onMediaDataLoaded(KEY, null, mediaData)
+        fakeBgExecutor.runAllReady()
+        fakeFgExecutor.runAllReady()
         reset(listener)
         // AND MR2Manager returns null for routing session
         whenever(mr2.getRoutingSessionForMediaController(any())).thenReturn(null)
         // WHEN the selected device changes state
         val deviceCallback = captureCallback()
         deviceCallback.onSelectedDeviceStateChanged(device, 1)
-        fakeExecutor.runAllReady()
+        fakeBgExecutor.runAllReady()
+        fakeFgExecutor.runAllReady()
         // THEN the device is disabled
         val data = captureDeviceData(KEY)
         assertThat(data.enabled).isFalse()
@@ -298,13 +302,16 @@
     fun deviceDisabledWhenMR2ReturnsNullRouteInfoOnDeviceListUpdate() {
         // GIVEN a notif is added
         manager.onMediaDataLoaded(KEY, null, mediaData)
+        fakeBgExecutor.runAllReady()
+        fakeFgExecutor.runAllReady()
         reset(listener)
         // GIVEN that MR2Manager returns null for routing session
         whenever(mr2.getRoutingSessionForMediaController(any())).thenReturn(null)
         // WHEN the selected device changes state
         val deviceCallback = captureCallback()
         deviceCallback.onDeviceListUpdate(mutableListOf(device))
-        fakeExecutor.runAllReady()
+        fakeBgExecutor.runAllReady()
+        fakeFgExecutor.runAllReady()
         // THEN the device is disabled
         val data = captureDeviceData(KEY)
         assertThat(data.enabled).isFalse()
@@ -312,15 +319,50 @@
         assertThat(data.icon).isNull()
     }
 
+    @Test
+    fun audioInfoChanged() {
+        whenever(playbackInfo.getPlaybackType()).thenReturn(PlaybackInfo.PLAYBACK_TYPE_LOCAL)
+        whenever(controller.getPlaybackInfo()).thenReturn(playbackInfo)
+        // GIVEN a controller with local playback type
+        manager.onMediaDataLoaded(KEY, null, mediaData)
+        fakeBgExecutor.runAllReady()
+        fakeFgExecutor.runAllReady()
+        reset(mr2)
+        // WHEN onAudioInfoChanged fires with remote playback type
+        whenever(playbackInfo.getPlaybackType()).thenReturn(PlaybackInfo.PLAYBACK_TYPE_REMOTE)
+        val captor = ArgumentCaptor.forClass(MediaController.Callback::class.java)
+        verify(controller).registerCallback(captor.capture())
+        captor.value.onAudioInfoChanged(playbackInfo)
+        // THEN the route is checked
+        verify(mr2).getRoutingSessionForMediaController(eq(controller))
+    }
+
+    @Test
+    fun audioInfoHasntChanged() {
+        whenever(playbackInfo.getPlaybackType()).thenReturn(PlaybackInfo.PLAYBACK_TYPE_REMOTE)
+        whenever(controller.getPlaybackInfo()).thenReturn(playbackInfo)
+        // GIVEN a controller with remote playback type
+        manager.onMediaDataLoaded(KEY, null, mediaData)
+        fakeBgExecutor.runAllReady()
+        fakeFgExecutor.runAllReady()
+        reset(mr2)
+        // WHEN onAudioInfoChanged fires with remote playback type
+        val captor = ArgumentCaptor.forClass(MediaController.Callback::class.java)
+        verify(controller).registerCallback(captor.capture())
+        captor.value.onAudioInfoChanged(playbackInfo)
+        // THEN the route is not checked
+        verify(mr2, never()).getRoutingSessionForMediaController(eq(controller))
+    }
+
     fun captureCallback(): LocalMediaManager.DeviceCallback {
         val captor = ArgumentCaptor.forClass(LocalMediaManager.DeviceCallback::class.java)
         verify(lmm).registerCallback(captor.capture())
         return captor.getValue()
     }
 
-    fun captureDeviceData(key: String): MediaDeviceData {
+    fun captureDeviceData(key: String, oldKey: String? = null): MediaDeviceData {
         val captor = ArgumentCaptor.forClass(MediaDeviceData::class.java)
-        verify(listener).onMediaDeviceChanged(eq(key), captor.capture())
+        verify(listener).onMediaDeviceChanged(eq(key), eq(oldKey), captor.capture())
         return captor.getValue()
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaHierarchyManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaHierarchyManagerTest.kt
index 91c5ff8..d86dfa5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaHierarchyManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaHierarchyManagerTest.kt
@@ -142,4 +142,11 @@
         verify(mediaCarouselController).onDesiredLocationChanged(ArgumentMatchers.anyInt(),
                 any(MediaHostState::class.java), anyBoolean(), anyLong(), anyLong())
     }
+
+    @Test
+    fun testCloseGutsRelayToCarousel() {
+        mediaHiearchyManager.closeGuts()
+
+        verify(mediaCarouselController).closeGuts()
+    }
 }
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaPlayerDataTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaPlayerDataTest.kt
new file mode 100644
index 0000000..00b003d
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaPlayerDataTest.kt
@@ -0,0 +1,124 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.media
+
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mockito.mock
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+public class MediaPlayerDataTest : SysuiTestCase() {
+
+    companion object {
+        val LOCAL = true
+        val RESUMPTION = true
+        val PLAYING = true
+        val UNDETERMINED = null
+    }
+
+    @Before
+    fun setup() {
+        MediaPlayerData.clear()
+    }
+
+    @Test
+    fun addPlayingThenRemote() {
+        val playerIsPlaying = mock(MediaControlPanel::class.java)
+        val dataIsPlaying = createMediaData("app1", PLAYING, LOCAL, !RESUMPTION)
+
+        val playerIsRemote = mock(MediaControlPanel::class.java)
+        val dataIsRemote = createMediaData("app2", PLAYING, !LOCAL, !RESUMPTION)
+
+        MediaPlayerData.addMediaPlayer("2", dataIsRemote, playerIsRemote)
+        MediaPlayerData.addMediaPlayer("1", dataIsPlaying, playerIsPlaying)
+
+        val players = MediaPlayerData.players()
+        assertThat(players).hasSize(2)
+        assertThat(players).containsExactly(playerIsPlaying, playerIsRemote).inOrder()
+    }
+
+    @Test
+    fun switchPlayersPlaying() {
+        val playerIsPlaying1 = mock(MediaControlPanel::class.java)
+        var dataIsPlaying1 = createMediaData("app1", PLAYING, LOCAL, !RESUMPTION)
+
+        val playerIsPlaying2 = mock(MediaControlPanel::class.java)
+        var dataIsPlaying2 = createMediaData("app2", !PLAYING, LOCAL, !RESUMPTION)
+
+        MediaPlayerData.addMediaPlayer("1", dataIsPlaying1, playerIsPlaying1)
+        MediaPlayerData.addMediaPlayer("2", dataIsPlaying2, playerIsPlaying2)
+
+        dataIsPlaying1 = createMediaData("app1", !PLAYING, LOCAL, !RESUMPTION)
+        dataIsPlaying2 = createMediaData("app2", PLAYING, LOCAL, !RESUMPTION)
+
+        MediaPlayerData.addMediaPlayer("1", dataIsPlaying1, playerIsPlaying1)
+        MediaPlayerData.addMediaPlayer("2", dataIsPlaying2, playerIsPlaying2)
+
+        val players = MediaPlayerData.players()
+        assertThat(players).hasSize(2)
+        assertThat(players).containsExactly(playerIsPlaying2, playerIsPlaying1).inOrder()
+    }
+
+    @Test
+    fun fullOrderTest() {
+        val playerIsPlaying = mock(MediaControlPanel::class.java)
+        val dataIsPlaying = createMediaData("app1", PLAYING, LOCAL, !RESUMPTION)
+
+        val playerIsPlayingAndRemote = mock(MediaControlPanel::class.java)
+        val dataIsPlayingAndRemote = createMediaData("app2", PLAYING, !LOCAL, !RESUMPTION)
+
+        val playerIsStoppedAndLocal = mock(MediaControlPanel::class.java)
+        val dataIsStoppedAndLocal = createMediaData("app3", !PLAYING, LOCAL, !RESUMPTION)
+
+        val playerIsStoppedAndRemote = mock(MediaControlPanel::class.java)
+        val dataIsStoppedAndRemote = createMediaData("app4", !PLAYING, !LOCAL, !RESUMPTION)
+
+        val playerCanResume = mock(MediaControlPanel::class.java)
+        val dataCanResume = createMediaData("app5", !PLAYING, LOCAL, RESUMPTION)
+
+        val playerUndetermined = mock(MediaControlPanel::class.java)
+        val dataUndetermined = createMediaData("app6", UNDETERMINED, LOCAL, RESUMPTION)
+
+        MediaPlayerData.addMediaPlayer("3", dataIsStoppedAndLocal, playerIsStoppedAndLocal)
+        MediaPlayerData.addMediaPlayer("5", dataIsStoppedAndRemote, playerIsStoppedAndRemote)
+        MediaPlayerData.addMediaPlayer("4", dataCanResume, playerCanResume)
+        MediaPlayerData.addMediaPlayer("1", dataIsPlaying, playerIsPlaying)
+        MediaPlayerData.addMediaPlayer("2", dataIsPlayingAndRemote, playerIsPlayingAndRemote)
+        MediaPlayerData.addMediaPlayer("6", dataUndetermined, playerUndetermined)
+
+        val players = MediaPlayerData.players()
+        assertThat(players).hasSize(6)
+        assertThat(players).containsExactly(playerIsPlaying, playerIsPlayingAndRemote,
+            playerIsStoppedAndLocal, playerCanResume, playerIsStoppedAndRemote,
+            playerUndetermined).inOrder()
+    }
+
+    private fun createMediaData(
+        app: String,
+        isPlaying: Boolean?,
+        isLocalSession: Boolean,
+        resumption: Boolean
+    ) =
+        MediaData(0, false, 0, app, null, null, null, null, emptyList(), emptyList<Int>(), "",
+            null, null, null, true, null, isLocalSession, resumption, null, false, isPlaying)
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaResumeListenerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaResumeListenerTest.kt
new file mode 100644
index 0000000..5d81de6
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaResumeListenerTest.kt
@@ -0,0 +1,258 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.media
+
+import android.app.PendingIntent
+import android.content.ComponentName
+import android.content.Context
+import android.content.Intent
+import android.content.SharedPreferences
+import android.content.pm.PackageManager
+import android.content.pm.ResolveInfo
+import android.content.pm.ServiceInfo
+import android.graphics.Color
+import android.media.MediaDescription
+import android.media.session.MediaSession
+import android.provider.Settings
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.broadcast.BroadcastDispatcher
+import com.android.systemui.tuner.TunerService
+import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.time.FakeSystemClock
+import org.junit.After
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.Captor
+import org.mockito.Mock
+import org.mockito.Mockito
+import org.mockito.Mockito.mock
+import org.mockito.Mockito.never
+import org.mockito.Mockito.times
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when` as whenever
+import org.mockito.MockitoAnnotations
+
+private const val KEY = "TEST_KEY"
+private const val OLD_KEY = "RESUME_KEY"
+private const val APP = "APP"
+private const val BG_COLOR = Color.RED
+private const val PACKAGE_NAME = "PKG"
+private const val CLASS_NAME = "CLASS"
+private const val ARTIST = "ARTIST"
+private const val TITLE = "TITLE"
+private const val USER_ID = 0
+private const val MEDIA_PREFERENCES = "media_control_prefs"
+private const val RESUME_COMPONENTS = "package1/class1:package2/class2:package3/class3"
+
+private fun <T> capture(argumentCaptor: ArgumentCaptor<T>): T = argumentCaptor.capture()
+private fun <T> eq(value: T): T = Mockito.eq(value) ?: value
+private fun <T> any(): T = Mockito.any<T>()
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
[email protected]
+class MediaResumeListenerTest : SysuiTestCase() {
+
+    @Mock private lateinit var broadcastDispatcher: BroadcastDispatcher
+    @Mock private lateinit var mediaDataManager: MediaDataManager
+    @Mock private lateinit var device: MediaDeviceData
+    @Mock private lateinit var token: MediaSession.Token
+    @Mock private lateinit var tunerService: TunerService
+    @Mock private lateinit var resumeBrowserFactory: ResumeMediaBrowserFactory
+    @Mock private lateinit var resumeBrowser: ResumeMediaBrowser
+    @Mock private lateinit var sharedPrefs: SharedPreferences
+    @Mock private lateinit var sharedPrefsEditor: SharedPreferences.Editor
+    @Mock private lateinit var mockContext: Context
+    @Mock private lateinit var pendingIntent: PendingIntent
+
+    @Captor lateinit var callbackCaptor: ArgumentCaptor<ResumeMediaBrowser.Callback>
+
+    private lateinit var executor: FakeExecutor
+    private lateinit var data: MediaData
+    private lateinit var resumeListener: MediaResumeListener
+
+    private var originalQsSetting = Settings.Global.getInt(context.contentResolver,
+        Settings.Global.SHOW_MEDIA_ON_QUICK_SETTINGS, 1)
+    private var originalResumeSetting = Settings.Secure.getInt(context.contentResolver,
+        Settings.Secure.MEDIA_CONTROLS_RESUME, 0)
+
+    @Before
+    fun setup() {
+        MockitoAnnotations.initMocks(this)
+
+        Settings.Global.putInt(context.contentResolver,
+            Settings.Global.SHOW_MEDIA_ON_QUICK_SETTINGS, 1)
+        Settings.Secure.putInt(context.contentResolver,
+            Settings.Secure.MEDIA_CONTROLS_RESUME, 1)
+
+        whenever(resumeBrowserFactory.create(capture(callbackCaptor), any()))
+                .thenReturn(resumeBrowser)
+
+        // resume components are stored in sharedpreferences
+        whenever(mockContext.getSharedPreferences(eq(MEDIA_PREFERENCES), anyInt()))
+                .thenReturn(sharedPrefs)
+        whenever(sharedPrefs.getString(any(), any())).thenReturn(RESUME_COMPONENTS)
+        whenever(sharedPrefs.edit()).thenReturn(sharedPrefsEditor)
+        whenever(sharedPrefsEditor.putString(any(), any())).thenReturn(sharedPrefsEditor)
+        whenever(mockContext.packageManager).thenReturn(context.packageManager)
+        whenever(mockContext.contentResolver).thenReturn(context.contentResolver)
+
+        executor = FakeExecutor(FakeSystemClock())
+        resumeListener = MediaResumeListener(mockContext, broadcastDispatcher, executor,
+                tunerService, resumeBrowserFactory)
+        resumeListener.setManager(mediaDataManager)
+        mediaDataManager.addListener(resumeListener)
+
+        data = MediaData(
+                userId = USER_ID,
+                initialized = true,
+                backgroundColor = BG_COLOR,
+                app = APP,
+                appIcon = null,
+                artist = ARTIST,
+                song = TITLE,
+                artwork = null,
+                actions = emptyList(),
+                actionsToShowInCompact = emptyList(),
+                packageName = PACKAGE_NAME,
+                token = token,
+                clickIntent = null,
+                device = device,
+                active = true,
+                notificationKey = KEY,
+                resumeAction = null)
+    }
+
+    @After
+    fun tearDown() {
+        Settings.Global.putInt(context.contentResolver,
+            Settings.Global.SHOW_MEDIA_ON_QUICK_SETTINGS, originalQsSetting)
+        Settings.Secure.putInt(context.contentResolver,
+            Settings.Secure.MEDIA_CONTROLS_RESUME, originalResumeSetting)
+    }
+
+    @Test
+    fun testWhenNoResumption_doesNothing() {
+        Settings.Secure.putInt(context.contentResolver,
+            Settings.Secure.MEDIA_CONTROLS_RESUME, 0)
+
+        // When listener is created, we do NOT register a user change listener
+        val listener = MediaResumeListener(context, broadcastDispatcher, executor, tunerService,
+                resumeBrowserFactory)
+        listener.setManager(mediaDataManager)
+        verify(broadcastDispatcher, never()).registerReceiver(eq(listener.userChangeReceiver),
+            any(), any(), any())
+
+        // When data is loaded, we do NOT execute or update anything
+        listener.onMediaDataLoaded(KEY, OLD_KEY, data)
+        assertThat(executor.numPending()).isEqualTo(0)
+        verify(mediaDataManager, never()).setResumeAction(any(), any())
+    }
+
+    @Test
+    fun testOnLoad_checksForResume_noService() {
+        // When media data is loaded that has not been checked yet, and does not have a MBS
+        resumeListener.onMediaDataLoaded(KEY, null, data)
+
+        // Then we report back to the manager
+        verify(mediaDataManager).setResumeAction(KEY, null)
+    }
+
+    @Test
+    fun testOnLoad_checksForResume_hasService() {
+        // Set up mocks to successfully find a MBS that returns valid media
+        val pm = mock(PackageManager::class.java)
+        whenever(mockContext.packageManager).thenReturn(pm)
+        val resolveInfo = ResolveInfo()
+        val serviceInfo = ServiceInfo()
+        serviceInfo.packageName = PACKAGE_NAME
+        resolveInfo.serviceInfo = serviceInfo
+        resolveInfo.serviceInfo.name = CLASS_NAME
+        val resumeInfo = listOf(resolveInfo)
+        whenever(pm.queryIntentServices(any(), anyInt())).thenReturn(resumeInfo)
+
+        val description = MediaDescription.Builder().setTitle(TITLE).build()
+        val component = ComponentName(PACKAGE_NAME, CLASS_NAME)
+        whenever(resumeBrowser.testConnection()).thenAnswer {
+            callbackCaptor.value.addTrack(description, component, resumeBrowser)
+        }
+
+        // When media data is loaded that has not been checked yet, and does have a MBS
+        val dataCopy = data.copy(resumeAction = null, hasCheckedForResume = false)
+        resumeListener.onMediaDataLoaded(KEY, null, dataCopy)
+
+        // Then we test whether the service is valid
+        executor.runAllReady()
+        verify(resumeBrowser).testConnection()
+
+        // And since it is, we report back to the manager
+        verify(mediaDataManager).setResumeAction(eq(KEY), any())
+
+        // But we do not tell it to add new controls
+        verify(mediaDataManager, never())
+                .addResumptionControls(anyInt(), any(), any(), any(), any(), any(), any())
+
+        // Finally, make sure the resume browser disconnected
+        verify(resumeBrowser).disconnect()
+    }
+
+    @Test
+    fun testOnLoad_doesNotCheckAgain() {
+        // When a media data is loaded that has been checked already
+        var dataCopy = data.copy(hasCheckedForResume = true)
+        resumeListener.onMediaDataLoaded(KEY, null, dataCopy)
+
+        // Then we should not check it again
+        verify(resumeBrowser, never()).testConnection()
+        verify(mediaDataManager, never()).setResumeAction(KEY, null)
+    }
+
+    @Test
+    fun testOnUserUnlock_loadsTracks() {
+        // Set up mock service to successfully find valid media
+        val description = MediaDescription.Builder().setTitle(TITLE).build()
+        val component = ComponentName(PACKAGE_NAME, CLASS_NAME)
+        whenever(resumeBrowser.token).thenReturn(token)
+        whenever(resumeBrowser.appIntent).thenReturn(pendingIntent)
+        whenever(resumeBrowser.findRecentMedia()).thenAnswer {
+            callbackCaptor.value.addTrack(description, component, resumeBrowser)
+        }
+
+        // Make sure broadcast receiver is registered
+        resumeListener.setManager(mediaDataManager)
+        verify(broadcastDispatcher).registerReceiver(eq(resumeListener.userChangeReceiver),
+                any(), any(), any())
+
+        // When we get an unlock event
+        val intent = Intent(Intent.ACTION_USER_UNLOCKED)
+        resumeListener.userChangeReceiver.onReceive(context, intent)
+
+        // Then we should attempt to find recent media for each saved component
+        verify(resumeBrowser, times(3)).findRecentMedia()
+
+        // Then since the mock service found media, the manager should be informed
+        verify(mediaDataManager, times(3)).addResumptionControls(anyInt(),
+                any(), any(), any(), any(), any(), eq(PACKAGE_NAME))
+    }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaSessionBasedFilterTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaSessionBasedFilterTest.kt
new file mode 100644
index 0000000..2d90cc4
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaSessionBasedFilterTest.kt
@@ -0,0 +1,422 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.media
+
+import android.graphics.Color
+import android.media.session.MediaController
+import android.media.session.MediaController.PlaybackInfo
+import android.media.session.MediaSession
+import android.media.session.MediaSessionManager
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import androidx.test.filters.SmallTest
+
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.mockito.eq
+import com.android.systemui.util.time.FakeSystemClock
+
+import org.junit.After
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.Mock
+import org.mockito.Mockito
+import org.mockito.Mockito.any
+import org.mockito.Mockito.never
+import org.mockito.Mockito.reset
+import org.mockito.Mockito.verify
+import org.mockito.junit.MockitoJUnit
+import org.mockito.Mockito.`when` as whenever
+
+private const val PACKAGE = "PKG"
+private const val KEY = "TEST_KEY"
+private const val NOTIF_KEY = "TEST_KEY"
+private const val SESSION_ARTIST = "SESSION_ARTIST"
+private const val SESSION_TITLE = "SESSION_TITLE"
+private const val APP_NAME = "APP_NAME"
+private const val USER_ID = 0
+
+private val info = MediaData(
+    userId = USER_ID,
+    initialized = true,
+    backgroundColor = Color.DKGRAY,
+    app = APP_NAME,
+    appIcon = null,
+    artist = SESSION_ARTIST,
+    song = SESSION_TITLE,
+    artwork = null,
+    actions = emptyList(),
+    actionsToShowInCompact = emptyList(),
+    packageName = PACKAGE,
+    token = null,
+    clickIntent = null,
+    device = null,
+    active = true,
+    resumeAction = null,
+    resumption = false,
+    notificationKey = NOTIF_KEY,
+    hasCheckedForResume = false
+)
+
+private fun <T> eq(value: T): T = Mockito.eq(value) ?: value
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
[email protected]
+public class MediaSessionBasedFilterTest : SysuiTestCase() {
+
+    @JvmField @Rule val mockito = MockitoJUnit.rule()
+
+    // Unit to be tested
+    private lateinit var filter: MediaSessionBasedFilter
+
+    private lateinit var sessionListener: MediaSessionManager.OnActiveSessionsChangedListener
+    @Mock private lateinit var mediaListener: MediaDataManager.Listener
+
+    // MediaSessionBasedFilter dependencies
+    @Mock private lateinit var mediaSessionManager: MediaSessionManager
+    private lateinit var fgExecutor: FakeExecutor
+    private lateinit var bgExecutor: FakeExecutor
+
+    @Mock private lateinit var controller1: MediaController
+    @Mock private lateinit var controller2: MediaController
+    @Mock private lateinit var controller3: MediaController
+    @Mock private lateinit var controller4: MediaController
+
+    private lateinit var token1: MediaSession.Token
+    private lateinit var token2: MediaSession.Token
+    private lateinit var token3: MediaSession.Token
+    private lateinit var token4: MediaSession.Token
+
+    @Mock private lateinit var remotePlaybackInfo: PlaybackInfo
+    @Mock private lateinit var localPlaybackInfo: PlaybackInfo
+
+    private lateinit var session1: MediaSession
+    private lateinit var session2: MediaSession
+    private lateinit var session3: MediaSession
+    private lateinit var session4: MediaSession
+
+    private lateinit var mediaData1: MediaData
+    private lateinit var mediaData2: MediaData
+    private lateinit var mediaData3: MediaData
+    private lateinit var mediaData4: MediaData
+
+    @Before
+    fun setUp() {
+        fgExecutor = FakeExecutor(FakeSystemClock())
+        bgExecutor = FakeExecutor(FakeSystemClock())
+        filter = MediaSessionBasedFilter(context, mediaSessionManager, fgExecutor, bgExecutor)
+
+        // Configure mocks.
+        whenever(mediaSessionManager.getActiveSessions(any())).thenReturn(emptyList())
+
+        session1 = MediaSession(context, "MediaSessionBasedFilter1")
+        session2 = MediaSession(context, "MediaSessionBasedFilter2")
+        session3 = MediaSession(context, "MediaSessionBasedFilter3")
+        session4 = MediaSession(context, "MediaSessionBasedFilter4")
+
+        token1 = session1.sessionToken
+        token2 = session2.sessionToken
+        token3 = session3.sessionToken
+        token4 = session4.sessionToken
+
+        whenever(controller1.getSessionToken()).thenReturn(token1)
+        whenever(controller2.getSessionToken()).thenReturn(token2)
+        whenever(controller3.getSessionToken()).thenReturn(token3)
+        whenever(controller4.getSessionToken()).thenReturn(token4)
+
+        whenever(controller1.getPackageName()).thenReturn(PACKAGE)
+        whenever(controller2.getPackageName()).thenReturn(PACKAGE)
+        whenever(controller3.getPackageName()).thenReturn(PACKAGE)
+        whenever(controller4.getPackageName()).thenReturn(PACKAGE)
+
+        mediaData1 = info.copy(token = token1)
+        mediaData2 = info.copy(token = token2)
+        mediaData3 = info.copy(token = token3)
+        mediaData4 = info.copy(token = token4)
+
+        whenever(remotePlaybackInfo.getPlaybackType()).thenReturn(PlaybackInfo.PLAYBACK_TYPE_REMOTE)
+        whenever(localPlaybackInfo.getPlaybackType()).thenReturn(PlaybackInfo.PLAYBACK_TYPE_LOCAL)
+
+        whenever(controller1.getPlaybackInfo()).thenReturn(localPlaybackInfo)
+        whenever(controller2.getPlaybackInfo()).thenReturn(localPlaybackInfo)
+        whenever(controller3.getPlaybackInfo()).thenReturn(localPlaybackInfo)
+        whenever(controller4.getPlaybackInfo()).thenReturn(localPlaybackInfo)
+
+        // Capture listener
+        bgExecutor.runAllReady()
+        val listenerCaptor = ArgumentCaptor.forClass(
+                MediaSessionManager.OnActiveSessionsChangedListener::class.java)
+        verify(mediaSessionManager).addOnActiveSessionsChangedListener(
+                listenerCaptor.capture(), any())
+        sessionListener = listenerCaptor.value
+
+        filter.addListener(mediaListener)
+    }
+
+    @After
+    fun tearDown() {
+        session1.release()
+        session2.release()
+        session3.release()
+        session4.release()
+    }
+
+    @Test
+    fun noMediaSession_loadedEventNotFiltered() {
+        filter.onMediaDataLoaded(KEY, null, mediaData1)
+        bgExecutor.runAllReady()
+        fgExecutor.runAllReady()
+        verify(mediaListener).onMediaDataLoaded(eq(KEY), eq(null), eq(mediaData1))
+    }
+
+    @Test
+    fun noMediaSession_removedEventNotFiltered() {
+        filter.onMediaDataRemoved(KEY)
+        bgExecutor.runAllReady()
+        fgExecutor.runAllReady()
+        verify(mediaListener).onMediaDataRemoved(eq(KEY))
+    }
+
+    @Test
+    fun matchingMediaSession_loadedEventNotFiltered() {
+        // GIVEN an active session
+        val controllers = listOf(controller1)
+        whenever(mediaSessionManager.getActiveSessions(any())).thenReturn(controllers)
+        sessionListener.onActiveSessionsChanged(controllers)
+        // WHEN a loaded event is received that matches the session
+        filter.onMediaDataLoaded(KEY, null, mediaData1)
+        bgExecutor.runAllReady()
+        fgExecutor.runAllReady()
+        // THEN the event is not filtered
+        verify(mediaListener).onMediaDataLoaded(eq(KEY), eq(null), eq(mediaData1))
+    }
+
+    @Test
+    fun matchingMediaSession_removedEventNotFiltered() {
+        // GIVEN an active session
+        val controllers = listOf(controller1)
+        whenever(mediaSessionManager.getActiveSessions(any())).thenReturn(controllers)
+        sessionListener.onActiveSessionsChanged(controllers)
+        // WHEN a removed event is received
+        filter.onMediaDataRemoved(KEY)
+        bgExecutor.runAllReady()
+        fgExecutor.runAllReady()
+        // THEN the event is not filtered
+        verify(mediaListener).onMediaDataRemoved(eq(KEY))
+    }
+
+    @Test
+    fun remoteSession_loadedEventNotFiltered() {
+        // GIVEN a remote session
+        whenever(controller1.getPlaybackInfo()).thenReturn(remotePlaybackInfo)
+        val controllers = listOf(controller1)
+        whenever(mediaSessionManager.getActiveSessions(any())).thenReturn(controllers)
+        sessionListener.onActiveSessionsChanged(controllers)
+        // WHEN a loaded event is received that matche the session
+        filter.onMediaDataLoaded(KEY, null, mediaData1)
+        bgExecutor.runAllReady()
+        fgExecutor.runAllReady()
+        // THEN the event is not filtered
+        verify(mediaListener).onMediaDataLoaded(eq(KEY), eq(null), eq(mediaData1))
+    }
+
+    @Test
+    fun remoteAndLocalSessions_localLoadedEventFiltered() {
+        // GIVEN remote and local sessions
+        whenever(controller1.getPlaybackInfo()).thenReturn(remotePlaybackInfo)
+        val controllers = listOf(controller1, controller2)
+        whenever(mediaSessionManager.getActiveSessions(any())).thenReturn(controllers)
+        sessionListener.onActiveSessionsChanged(controllers)
+        // WHEN a loaded event is received that matches the remote session
+        filter.onMediaDataLoaded(KEY, null, mediaData1)
+        bgExecutor.runAllReady()
+        fgExecutor.runAllReady()
+        // THEN the event is not filtered
+        verify(mediaListener).onMediaDataLoaded(eq(KEY), eq(null), eq(mediaData1))
+        // WHEN a loaded event is received that matches the local session
+        filter.onMediaDataLoaded(KEY, null, mediaData2)
+        bgExecutor.runAllReady()
+        fgExecutor.runAllReady()
+        // THEN the event is filtered
+        verify(mediaListener, never()).onMediaDataLoaded(eq(KEY), eq(null), eq(mediaData2))
+    }
+
+    @Test
+    fun remoteAndLocalSessions_remoteSessionWithoutNotification() {
+        // GIVEN remote and local sessions
+        whenever(controller2.getPlaybackInfo()).thenReturn(remotePlaybackInfo)
+        val controllers = listOf(controller1, controller2)
+        whenever(mediaSessionManager.getActiveSessions(any())).thenReturn(controllers)
+        sessionListener.onActiveSessionsChanged(controllers)
+        // WHEN a loaded event is received that matches the local session
+        filter.onMediaDataLoaded(KEY, null, mediaData1)
+        bgExecutor.runAllReady()
+        fgExecutor.runAllReady()
+        // THEN the event is not filtered because there isn't a notification for the remote
+        // session.
+        verify(mediaListener).onMediaDataLoaded(eq(KEY), eq(null), eq(mediaData1))
+    }
+
+    @Test
+    fun remoteAndLocalHaveDifferentKeys_localLoadedEventFiltered() {
+        // GIVEN remote and local sessions
+        val key1 = "KEY_1"
+        val key2 = "KEY_2"
+        whenever(controller1.getPlaybackInfo()).thenReturn(remotePlaybackInfo)
+        val controllers = listOf(controller1, controller2)
+        whenever(mediaSessionManager.getActiveSessions(any())).thenReturn(controllers)
+        sessionListener.onActiveSessionsChanged(controllers)
+        // WHEN a loaded event is received that matches the remote session
+        filter.onMediaDataLoaded(key1, null, mediaData1)
+        bgExecutor.runAllReady()
+        fgExecutor.runAllReady()
+        // THEN the event is not filtered
+        verify(mediaListener).onMediaDataLoaded(eq(key1), eq(null), eq(mediaData1))
+        // WHEN a loaded event is received that matches the local session
+        filter.onMediaDataLoaded(key2, null, mediaData2)
+        bgExecutor.runAllReady()
+        fgExecutor.runAllReady()
+        // THEN the event is filtered
+        verify(mediaListener, never()).onMediaDataLoaded(eq(key2), eq(null), eq(mediaData2))
+        // AND there should be a removed event for key2
+        verify(mediaListener).onMediaDataRemoved(eq(key2))
+    }
+
+    @Test
+    fun remoteAndLocalHaveDifferentKeys_remoteSessionWithoutNotification() {
+        // GIVEN remote and local sessions
+        val key1 = "KEY_1"
+        val key2 = "KEY_2"
+        whenever(controller2.getPlaybackInfo()).thenReturn(remotePlaybackInfo)
+        val controllers = listOf(controller1, controller2)
+        whenever(mediaSessionManager.getActiveSessions(any())).thenReturn(controllers)
+        sessionListener.onActiveSessionsChanged(controllers)
+        // WHEN a loaded event is received that matches the local session
+        filter.onMediaDataLoaded(key1, null, mediaData1)
+        bgExecutor.runAllReady()
+        fgExecutor.runAllReady()
+        // THEN the event is not filtered
+        verify(mediaListener).onMediaDataLoaded(eq(key1), eq(null), eq(mediaData1))
+        // WHEN a loaded event is received that matches the remote session
+        filter.onMediaDataLoaded(key2, null, mediaData2)
+        bgExecutor.runAllReady()
+        fgExecutor.runAllReady()
+        // THEN the event is not filtered
+        verify(mediaListener).onMediaDataLoaded(eq(key2), eq(null), eq(mediaData2))
+    }
+
+    @Test
+    fun multipleRemoteSessions_loadedEventNotFiltered() {
+        // GIVEN two remote sessions
+        whenever(controller1.getPlaybackInfo()).thenReturn(remotePlaybackInfo)
+        whenever(controller2.getPlaybackInfo()).thenReturn(remotePlaybackInfo)
+        val controllers = listOf(controller1, controller2)
+        whenever(mediaSessionManager.getActiveSessions(any())).thenReturn(controllers)
+        sessionListener.onActiveSessionsChanged(controllers)
+        // WHEN a loaded event is received that matches the remote session
+        filter.onMediaDataLoaded(KEY, null, mediaData1)
+        bgExecutor.runAllReady()
+        fgExecutor.runAllReady()
+        // THEN the event is not filtered
+        verify(mediaListener).onMediaDataLoaded(eq(KEY), eq(null), eq(mediaData1))
+        // WHEN a loaded event is received that matches the local session
+        filter.onMediaDataLoaded(KEY, null, mediaData2)
+        bgExecutor.runAllReady()
+        fgExecutor.runAllReady()
+        // THEN the event is not filtered
+        verify(mediaListener).onMediaDataLoaded(eq(KEY), eq(null), eq(mediaData2))
+    }
+
+    @Test
+    fun multipleOtherSessions_loadedEventNotFiltered() {
+        // GIVEN multiple active sessions from other packages
+        val controllers = listOf(controller1, controller2, controller3, controller4)
+        whenever(controller1.getPackageName()).thenReturn("PKG_1")
+        whenever(controller2.getPackageName()).thenReturn("PKG_2")
+        whenever(controller3.getPackageName()).thenReturn("PKG_3")
+        whenever(controller4.getPackageName()).thenReturn("PKG_4")
+        whenever(mediaSessionManager.getActiveSessions(any())).thenReturn(controllers)
+        sessionListener.onActiveSessionsChanged(controllers)
+        // WHEN a loaded event is received
+        filter.onMediaDataLoaded(KEY, null, mediaData1)
+        bgExecutor.runAllReady()
+        fgExecutor.runAllReady()
+        // THEN the event is not filtered
+        verify(mediaListener).onMediaDataLoaded(eq(KEY), eq(null), eq(mediaData1))
+    }
+
+    @Test
+    fun doNotFilterDuringKeyMigration() {
+        val key1 = "KEY_1"
+        val key2 = "KEY_2"
+        // GIVEN a loaded event
+        filter.onMediaDataLoaded(key1, null, mediaData2)
+        bgExecutor.runAllReady()
+        fgExecutor.runAllReady()
+        reset(mediaListener)
+        // GIVEN remote and local sessions
+        whenever(controller1.getPlaybackInfo()).thenReturn(remotePlaybackInfo)
+        val controllers = listOf(controller1, controller2)
+        whenever(mediaSessionManager.getActiveSessions(any())).thenReturn(controllers)
+        sessionListener.onActiveSessionsChanged(controllers)
+        // WHEN a loaded event is received that matches the local session but it is a key migration
+        filter.onMediaDataLoaded(key2, key1, mediaData2)
+        bgExecutor.runAllReady()
+        fgExecutor.runAllReady()
+        // THEN the key migration event is fired
+        verify(mediaListener).onMediaDataLoaded(eq(key2), eq(key1), eq(mediaData2))
+    }
+
+    @Test
+    fun filterAfterKeyMigration() {
+        val key1 = "KEY_1"
+        val key2 = "KEY_2"
+        // GIVEN a loaded event
+        filter.onMediaDataLoaded(key1, null, mediaData1)
+        filter.onMediaDataLoaded(key1, null, mediaData2)
+        bgExecutor.runAllReady()
+        fgExecutor.runAllReady()
+        reset(mediaListener)
+        // GIVEN remote and local sessions
+        whenever(controller1.getPlaybackInfo()).thenReturn(remotePlaybackInfo)
+        val controllers = listOf(controller1, controller2)
+        whenever(mediaSessionManager.getActiveSessions(any())).thenReturn(controllers)
+        sessionListener.onActiveSessionsChanged(controllers)
+        // GIVEN that the keys have been migrated
+        filter.onMediaDataLoaded(key2, key1, mediaData1)
+        filter.onMediaDataLoaded(key2, key1, mediaData2)
+        bgExecutor.runAllReady()
+        fgExecutor.runAllReady()
+        reset(mediaListener)
+        // WHEN a loaded event is received that matches the local session
+        filter.onMediaDataLoaded(key2, null, mediaData2)
+        bgExecutor.runAllReady()
+        fgExecutor.runAllReady()
+        // THEN the key migration event is filtered
+        verify(mediaListener, never()).onMediaDataLoaded(eq(key2), eq(null), eq(mediaData2))
+        // WHEN a loaded event is received that matches the remote session
+        filter.onMediaDataLoaded(key2, null, mediaData1)
+        bgExecutor.runAllReady()
+        fgExecutor.runAllReady()
+        // THEN the key migration event is fired
+        verify(mediaListener).onMediaDataLoaded(eq(key2), eq(null), eq(mediaData1))
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaTimeoutListenerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaTimeoutListenerTest.kt
index 7a8e4f7..f397959 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaTimeoutListenerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaTimeoutListenerTest.kt
@@ -23,8 +23,9 @@
 import android.testing.AndroidTestingRunner
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
-import com.android.systemui.util.concurrency.DelayableExecutor
+import com.android.systemui.util.concurrency.FakeExecutor
 import com.android.systemui.util.mockito.capture
+import com.android.systemui.util.time.FakeSystemClock
 import com.google.common.truth.Truth.assertThat
 import org.junit.Before
 import org.junit.Rule
@@ -33,7 +34,6 @@
 import org.mockito.ArgumentCaptor
 import org.mockito.ArgumentMatchers.any
 import org.mockito.ArgumentMatchers.anyBoolean
-import org.mockito.ArgumentMatchers.anyLong
 import org.mockito.ArgumentMatchers.anyString
 import org.mockito.Captor
 import org.mockito.Mock
@@ -63,10 +63,8 @@
 
     @Mock private lateinit var mediaControllerFactory: MediaControllerFactory
     @Mock private lateinit var mediaController: MediaController
-    @Mock private lateinit var executor: DelayableExecutor
+    private lateinit var executor: FakeExecutor
     @Mock private lateinit var timeoutCallback: (String, Boolean) -> Unit
-    @Mock private lateinit var cancellationRunnable: Runnable
-    @Captor private lateinit var timeoutCaptor: ArgumentCaptor<Runnable>
     @Captor private lateinit var mediaCallbackCaptor: ArgumentCaptor<MediaController.Callback>
     @JvmField @Rule val mockito = MockitoJUnit.rule()
     private lateinit var metadataBuilder: MediaMetadata.Builder
@@ -78,7 +76,7 @@
     @Before
     fun setup() {
         `when`(mediaControllerFactory.create(any())).thenReturn(mediaController)
-        `when`(executor.executeDelayed(any(), anyLong())).thenReturn(cancellationRunnable)
+        executor = FakeExecutor(FakeSystemClock())
         mediaTimeoutListener = MediaTimeoutListener(mediaControllerFactory, executor)
         mediaTimeoutListener.timeoutCallback = timeoutCallback
 
@@ -120,7 +118,7 @@
     fun testOnMediaDataLoaded_registersTimeout_whenPaused() {
         mediaTimeoutListener.onMediaDataLoaded(KEY, null, mediaData)
         verify(mediaController).registerCallback(capture(mediaCallbackCaptor))
-        verify(executor).executeDelayed(capture(timeoutCaptor), anyLong())
+        assertThat(executor.numPending()).isEqualTo(1)
         verify(timeoutCallback, never()).invoke(anyString(), anyBoolean())
     }
 
@@ -137,6 +135,17 @@
     }
 
     @Test
+    fun testOnMediaDataRemoved_clearsTimeout() {
+        // GIVEN media that is paused
+        mediaTimeoutListener.onMediaDataLoaded(KEY, null, mediaData)
+        assertThat(executor.numPending()).isEqualTo(1)
+        // WHEN the media is removed
+        mediaTimeoutListener.onMediaDataRemoved(KEY)
+        // THEN the timeout runnable is cancelled
+        assertThat(executor.numPending()).isEqualTo(0)
+    }
+
+    @Test
     fun testOnMediaDataLoaded_migratesKeys() {
         // From not playing
         mediaTimeoutListener.onMediaDataLoaded(KEY, null, mediaData)
@@ -151,7 +160,24 @@
         verify(mediaController).registerCallback(anyObject())
 
         // Enqueues callback
-        verify(executor).execute(anyObject())
+        assertThat(executor.numPending()).isEqualTo(1)
+    }
+
+    @Test
+    fun testOnMediaDataLoaded_migratesKeys_noTimeoutExtension() {
+        // From not playing
+        mediaTimeoutListener.onMediaDataLoaded(KEY, null, mediaData)
+        clearInvocations(mediaController)
+
+        // Migrate, still not playing
+        val playingState = mock(android.media.session.PlaybackState::class.java)
+        `when`(playingState.state).thenReturn(PlaybackState.STATE_PAUSED)
+        `when`(mediaController.playbackState).thenReturn(playingState)
+        mediaTimeoutListener.onMediaDataLoaded("NEWKEY", KEY, mediaData)
+
+        // The number of queued timeout tasks remains the same. The timeout task isn't cancelled nor
+        // is another scheduled
+        assertThat(executor.numPending()).isEqualTo(1)
     }
 
     @Test
@@ -161,7 +187,7 @@
 
         mediaCallbackCaptor.value.onPlaybackStateChanged(PlaybackState.Builder()
                 .setState(PlaybackState.STATE_PAUSED, 0L, 0f).build())
-        verify(executor).executeDelayed(capture(timeoutCaptor), anyLong())
+        assertThat(executor.numPending()).isEqualTo(1)
     }
 
     @Test
@@ -171,7 +197,7 @@
 
         mediaCallbackCaptor.value.onPlaybackStateChanged(PlaybackState.Builder()
                 .setState(PlaybackState.STATE_PLAYING, 0L, 0f).build())
-        verify(cancellationRunnable).run()
+        assertThat(executor.numPending()).isEqualTo(0)
     }
 
     @Test
@@ -179,10 +205,9 @@
         // Assuming we have a pending timeout
         testOnPlaybackStateChanged_schedulesTimeout_whenPaused()
 
-        clearInvocations(cancellationRunnable)
         mediaCallbackCaptor.value.onPlaybackStateChanged(PlaybackState.Builder()
                 .setState(PlaybackState.STATE_STOPPED, 0L, 0f).build())
-        verify(cancellationRunnable, never()).run()
+        assertThat(executor.numPending()).isEqualTo(1)
     }
 
     @Test
@@ -190,7 +215,10 @@
         // Assuming we're have a pending timeout
         testOnPlaybackStateChanged_schedulesTimeout_whenPaused()
 
-        timeoutCaptor.value.run()
+        with(executor) {
+            advanceClockToNext()
+            runAllReady()
+        }
         verify(timeoutCallback).invoke(eq(KEY), eq(true))
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/ResumeMediaBrowserTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/ResumeMediaBrowserTest.kt
new file mode 100644
index 0000000..d26229e
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/ResumeMediaBrowserTest.kt
@@ -0,0 +1,287 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.media
+
+import android.content.ComponentName
+import android.content.Context
+import android.media.MediaDescription
+import android.media.browse.MediaBrowser
+import android.media.session.MediaController
+import android.media.session.MediaSession
+import android.service.media.MediaBrowserService
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.Captor
+import org.mockito.Mock
+import org.mockito.Mockito
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+import org.mockito.Mockito.`when` as whenever
+
+private const val PACKAGE_NAME = "package"
+private const val CLASS_NAME = "class"
+private const val TITLE = "song title"
+private const val MEDIA_ID = "media ID"
+private const val ROOT = "media browser root"
+
+private fun <T> capture(argumentCaptor: ArgumentCaptor<T>): T = argumentCaptor.capture()
+private fun <T> eq(value: T): T = Mockito.eq(value) ?: value
+private fun <T> any(): T = Mockito.any<T>()
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
[email protected]
+public class ResumeMediaBrowserTest : SysuiTestCase() {
+
+    private lateinit var resumeBrowser: TestableResumeMediaBrowser
+    private val component = ComponentName(PACKAGE_NAME, CLASS_NAME)
+    private val description = MediaDescription.Builder()
+            .setTitle(TITLE)
+            .setMediaId(MEDIA_ID)
+            .build()
+
+    @Mock lateinit var callback: ResumeMediaBrowser.Callback
+    @Mock lateinit var listener: MediaResumeListener
+    @Mock lateinit var service: MediaBrowserService
+    @Mock lateinit var browserFactory: MediaBrowserFactory
+    @Mock lateinit var browser: MediaBrowser
+    @Mock lateinit var token: MediaSession.Token
+    @Mock lateinit var mediaController: MediaController
+    @Mock lateinit var transportControls: MediaController.TransportControls
+
+    @Captor lateinit var connectionCallback: ArgumentCaptor<MediaBrowser.ConnectionCallback>
+    @Captor lateinit var subscriptionCallback: ArgumentCaptor<MediaBrowser.SubscriptionCallback>
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+
+        whenever(browserFactory.create(any(), capture(connectionCallback), any()))
+                .thenReturn(browser)
+
+        whenever(mediaController.transportControls).thenReturn(transportControls)
+
+        resumeBrowser = TestableResumeMediaBrowser(context, callback, component, browserFactory,
+                mediaController)
+    }
+
+    @Test
+    fun testConnection_connectionFails_callsOnError() {
+        // When testConnection cannot connect to the service
+        setupBrowserFailed()
+        resumeBrowser.testConnection()
+
+        // Then it calls onError
+        verify(callback).onError()
+    }
+
+    @Test
+    fun testConnection_connects_onConnected() {
+        // When testConnection can connect to the service
+        setupBrowserConnection()
+        resumeBrowser.testConnection()
+
+        // Then it calls onConnected
+        verify(callback).onConnected()
+    }
+
+    @Test
+    fun testConnection_noValidMedia_error() {
+        // When testConnection can connect to the service, and does not find valid media
+        setupBrowserConnectionNoResults()
+        resumeBrowser.testConnection()
+
+        // Then it calls onError
+        verify(callback).onError()
+    }
+
+    @Test
+    fun testConnection_hasValidMedia_addTrack() {
+        // When testConnection can connect to the service, and finds valid media
+        setupBrowserConnectionValidMedia()
+        resumeBrowser.testConnection()
+
+        // Then it calls addTrack
+        verify(callback).onConnected()
+        verify(callback).addTrack(eq(description), eq(component), eq(resumeBrowser))
+    }
+
+    @Test
+    fun testFindRecentMedia_connectionFails_error() {
+        // When findRecentMedia is called and we cannot connect
+        setupBrowserFailed()
+        resumeBrowser.findRecentMedia()
+
+        // Then it calls onError
+        verify(callback).onError()
+    }
+
+    @Test
+    fun testFindRecentMedia_noRoot_error() {
+        // When findRecentMedia is called and does not get a valid root
+        setupBrowserConnection()
+        whenever(browser.getRoot()).thenReturn(null)
+        resumeBrowser.findRecentMedia()
+
+        // Then it calls onError
+        verify(callback).onError()
+    }
+
+    @Test
+    fun testFindRecentMedia_connects_onConnected() {
+        // When findRecentMedia is called and we connect
+        setupBrowserConnection()
+        resumeBrowser.findRecentMedia()
+
+        // Then it calls onConnected
+        verify(callback).onConnected()
+    }
+
+    @Test
+    fun testFindRecentMedia_noChildren_error() {
+        // When findRecentMedia is called and we connect, but do not get any results
+        setupBrowserConnectionNoResults()
+        resumeBrowser.findRecentMedia()
+
+        // Then it calls onError
+        verify(callback).onError()
+    }
+
+    @Test
+    fun testFindRecentMedia_notPlayable_error() {
+        // When findRecentMedia is called and we connect, but do not get a playable child
+        setupBrowserConnectionNotPlayable()
+        resumeBrowser.findRecentMedia()
+
+        // Then it calls onError
+        verify(callback).onError()
+    }
+
+    @Test
+    fun testFindRecentMedia_hasValidMedia_addTrack() {
+        // When findRecentMedia is called and we can connect and get playable media
+        setupBrowserConnectionValidMedia()
+        resumeBrowser.findRecentMedia()
+
+        // Then it calls addTrack
+        verify(callback).addTrack(eq(description), eq(component), eq(resumeBrowser))
+    }
+
+    @Test
+    fun testRestart_connectionFails_error() {
+        // When restart is called and we cannot connect
+        setupBrowserFailed()
+        resumeBrowser.restart()
+
+        // Then it calls onError
+        verify(callback).onError()
+    }
+
+    @Test
+    fun testRestart_connects() {
+        // When restart is called and we connect successfully
+        setupBrowserConnection()
+        resumeBrowser.restart()
+
+        // Then it creates a new controller and sends play command
+        verify(transportControls).prepare()
+        verify(transportControls).play()
+
+        // Then it calls onConnected
+        verify(callback).onConnected()
+    }
+
+    /**
+     * Helper function to mock a failed connection
+     */
+    private fun setupBrowserFailed() {
+        whenever(browser.connect()).thenAnswer {
+            connectionCallback.value.onConnectionFailed()
+        }
+    }
+
+    /**
+     * Helper function to mock a successful connection only
+     */
+    private fun setupBrowserConnection() {
+        whenever(browser.connect()).thenAnswer {
+            connectionCallback.value.onConnected()
+        }
+        whenever(browser.isConnected()).thenReturn(true)
+        whenever(browser.getRoot()).thenReturn(ROOT)
+        whenever(browser.sessionToken).thenReturn(token)
+    }
+
+    /**
+     * Helper function to mock a successful connection, but no media results
+     */
+    private fun setupBrowserConnectionNoResults() {
+        setupBrowserConnection()
+        whenever(browser.subscribe(any(), capture(subscriptionCallback))).thenAnswer {
+            subscriptionCallback.value.onChildrenLoaded(ROOT, emptyList())
+        }
+    }
+
+    /**
+     * Helper function to mock a successful connection, but no playable results
+     */
+    private fun setupBrowserConnectionNotPlayable() {
+        setupBrowserConnection()
+
+        val child = MediaBrowser.MediaItem(description, 0)
+
+        whenever(browser.subscribe(any(), capture(subscriptionCallback))).thenAnswer {
+            subscriptionCallback.value.onChildrenLoaded(ROOT, listOf(child))
+        }
+    }
+
+    /**
+     * Helper function to mock a successful connection with playable media
+     */
+    private fun setupBrowserConnectionValidMedia() {
+        setupBrowserConnection()
+
+        val child = MediaBrowser.MediaItem(description, MediaBrowser.MediaItem.FLAG_PLAYABLE)
+
+        whenever(browser.serviceComponent).thenReturn(component)
+        whenever(browser.subscribe(any(), capture(subscriptionCallback))).thenAnswer {
+            subscriptionCallback.value.onChildrenLoaded(ROOT, listOf(child))
+        }
+    }
+
+    /**
+     * Override so media controller use is testable
+     */
+    private class TestableResumeMediaBrowser(
+        context: Context,
+        callback: Callback,
+        componentName: ComponentName,
+        browserFactory: MediaBrowserFactory,
+        private val fakeController: MediaController
+    ) : ResumeMediaBrowser(context, callback, componentName, browserFactory) {
+
+        override fun createMediaController(token: MediaSession.Token): MediaController {
+            return fakeController
+        }
+    }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/SeekBarObserverTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/SeekBarObserverTest.kt
index e9a0a40..7d8728e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/SeekBarObserverTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/SeekBarObserverTest.kt
@@ -69,7 +69,7 @@
     fun seekBarGone() {
         // WHEN seek bar is disabled
         val isEnabled = false
-        val data = SeekBarViewModel.Progress(isEnabled, false, null, null)
+        val data = SeekBarViewModel.Progress(isEnabled, false, null, 0)
         observer.onChanged(data)
         // THEN seek bar shows just a thin line with no text
         assertThat(seekBarView.isEnabled()).isFalse()
@@ -94,11 +94,11 @@
 
     @Test
     fun seekBarProgress() {
-        // WHEN seek bar progress is about half
+        // WHEN part of the track has been played
         val data = SeekBarViewModel.Progress(true, true, 3000, 120000)
         observer.onChanged(data)
-        // THEN seek bar is visible
-        assertThat(seekBarView.progress).isEqualTo(100)
+        // THEN seek bar shows the progress
+        assertThat(seekBarView.progress).isEqualTo(3000)
         assertThat(seekBarView.max).isEqualTo(120000)
         assertThat(elapsedTimeView.getText()).isEqualTo("00:03")
         assertThat(totalTimeView.getText()).isEqualTo("02:00")
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/SeekBarViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/SeekBarViewModelTest.kt
index c8ef9fb..b81ab74 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/SeekBarViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/SeekBarViewModelTest.kt
@@ -204,6 +204,22 @@
     }
 
     @Test
+    fun updateDurationNoMetadata() {
+        // GIVEN that the metadata is null
+        whenever(mockController.getMetadata()).thenReturn(null)
+        // AND a valid playback state (ie. media session is not destroyed)
+        val state = PlaybackState.Builder().run {
+            setState(PlaybackState.STATE_PLAYING, 200L, 1f)
+            build()
+        }
+        whenever(mockController.getPlaybackState()).thenReturn(state)
+        // WHEN the controller is updated
+        viewModel.updateController(mockController)
+        // THEN the seek bar is disabled
+        assertThat(viewModel.progress.value!!.enabled).isFalse()
+    }
+
+    @Test
     fun updateElapsedTime() {
         // GIVEN that the PlaybackState contains the current position
         val position = 200L
diff --git a/packages/SystemUI/tests/src/com/android/systemui/pip/PipAnimationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/pip/PipAnimationControllerTest.java
index 536cae4..c9c1111 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/pip/PipAnimationControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/pip/PipAnimationControllerTest.java
@@ -61,8 +61,7 @@
     @Before
     public void setUp() throws Exception {
         mPipAnimationController = new PipAnimationController(
-                mContext, new PipSurfaceTransactionHelper(mContext,
-                mock(ConfigurationController.class)));
+                new PipSurfaceTransactionHelper(mContext, mock(ConfigurationController.class)));
         mLeash = new SurfaceControl.Builder()
                 .setContainerLayer()
                 .setName("FakeLeash")
diff --git a/packages/SystemUI/tests/src/com/android/systemui/pip/PipBoundsHandlerTest.java b/packages/SystemUI/tests/src/com/android/systemui/pip/PipBoundsHandlerTest.java
index f404f04..70c2bba 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/pip/PipBoundsHandlerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/pip/PipBoundsHandlerTest.java
@@ -270,6 +270,21 @@
     }
 
     @Test
+    public void onSaveReentryBounds_restoreLastSize() {
+        final Rect oldSize = mPipBoundsHandler.getDestinationBounds(mTestComponentName1,
+                DEFAULT_ASPECT_RATIO, EMPTY_CURRENT_BOUNDS, EMPTY_MINIMAL_SIZE);
+
+        oldSize.scale(1.25f);
+        mPipBoundsHandler.onSaveReentryBounds(mTestComponentName1, oldSize);
+
+        final Rect newSize = mPipBoundsHandler.getDestinationBounds(mTestComponentName1,
+                DEFAULT_ASPECT_RATIO, EMPTY_CURRENT_BOUNDS, EMPTY_MINIMAL_SIZE);
+
+        assertEquals(oldSize.width(), newSize.width());
+        assertEquals(oldSize.height(), newSize.height());
+    }
+
+    @Test
     public void onResetReentryBounds_useDefaultBounds() {
         final Rect defaultBounds = mPipBoundsHandler.getDestinationBounds(mTestComponentName1,
                 DEFAULT_ASPECT_RATIO, EMPTY_CURRENT_BOUNDS, EMPTY_MINIMAL_SIZE);
@@ -299,6 +314,22 @@
         assertBoundsInclusionWithMargin("restoreLastPosition", newBounds, actualBounds);
     }
 
+    @Test
+    public void onSaveReentryBounds_componentMismatch_restoreLastSize() {
+        final Rect oldSize = mPipBoundsHandler.getDestinationBounds(mTestComponentName1,
+                DEFAULT_ASPECT_RATIO, EMPTY_CURRENT_BOUNDS, EMPTY_MINIMAL_SIZE);
+
+        oldSize.scale(1.25f);
+        mPipBoundsHandler.onSaveReentryBounds(mTestComponentName1, oldSize);
+
+        mPipBoundsHandler.onResetReentryBounds(mTestComponentName2);
+        final Rect newSize = mPipBoundsHandler.getDestinationBounds(mTestComponentName1,
+                DEFAULT_ASPECT_RATIO, EMPTY_CURRENT_BOUNDS, EMPTY_MINIMAL_SIZE);
+
+        assertEquals(oldSize.width(), newSize.width());
+        assertEquals(oldSize.height(), newSize.height());
+    }
+
     private void assertBoundsInclusionWithMargin(String from, Rect expected, Rect actual) {
         final Rect expectedWithMargin = new Rect(expected);
         expectedWithMargin.inset(-ROUNDING_ERROR_MARGIN, -ROUNDING_ERROR_MARGIN);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/pip/phone/PipTouchHandlerTest.java b/packages/SystemUI/tests/src/com/android/systemui/pip/phone/PipTouchHandlerTest.java
index 96bb521..9f67722 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/pip/phone/PipTouchHandlerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/pip/phone/PipTouchHandlerTest.java
@@ -37,6 +37,7 @@
 import com.android.systemui.pip.PipBoundsHandler;
 import com.android.systemui.pip.PipSnapAlgorithm;
 import com.android.systemui.pip.PipTaskOrganizer;
+import com.android.systemui.pip.PipUiEventLogger;
 import com.android.systemui.shared.system.InputConsumerController;
 import com.android.systemui.util.DeviceConfigProxy;
 import com.android.systemui.util.FloatingContentCoordinator;
@@ -85,6 +86,9 @@
     @Mock
     private SysUiState mSysUiState;
 
+    @Mock
+    private PipUiEventLogger mPipUiEventLogger;
+
     private PipSnapAlgorithm mPipSnapAlgorithm;
     private PipMotionHelper mMotionHelper;
     private PipResizeGestureHandler mPipResizeGestureHandler;
@@ -104,7 +108,7 @@
         mPipTouchHandler = new PipTouchHandler(mContext, mActivityManager,
                 mPipMenuActivityController, mInputConsumerController, mPipBoundsHandler,
                 mPipTaskOrganizer, mFloatingContentCoordinator, mDeviceConfigProxy,
-                mPipSnapAlgorithm, mSysUiState);
+                mPipSnapAlgorithm, mSysUiState, mPipUiEventLogger);
         mMotionHelper = Mockito.spy(mPipTouchHandler.getMotionHelper());
         mPipResizeGestureHandler = Mockito.spy(mPipTouchHandler.getPipResizeGestureHandler());
         mPipTouchHandler.setPipMotionHelper(mMotionHelper);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyChipBuilderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyChipBuilderTest.kt
new file mode 100644
index 0000000..dcee5a716
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyChipBuilderTest.kt
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.privacy
+
+import androidx.test.filters.SmallTest
+import androidx.test.runner.AndroidJUnit4
+import com.android.systemui.SysuiTestCase
+import org.junit.Assert.assertEquals
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+@SmallTest
+class PrivacyChipBuilderTest : SysuiTestCase() {
+
+    companion object {
+        val TEST_UID = 1
+    }
+
+    @Test
+    fun testGenerateAppsList() {
+        val bar2 = PrivacyItem(Privacy.TYPE_CAMERA, PrivacyApplication(
+                "Bar", TEST_UID))
+        val bar3 = PrivacyItem(Privacy.TYPE_LOCATION, PrivacyApplication(
+                "Bar", TEST_UID))
+        val foo0 = PrivacyItem(Privacy.TYPE_MICROPHONE, PrivacyApplication(
+                "Foo", TEST_UID))
+        val baz1 = PrivacyItem(Privacy.TYPE_CAMERA, PrivacyApplication(
+                "Baz", TEST_UID))
+
+        val items = listOf(bar2, foo0, baz1, bar3)
+
+        val textBuilder = PrivacyChipBuilder(context, items)
+
+        val list = textBuilder.appsAndTypes
+        assertEquals(3, list.size)
+        val appsList = list.map { it.first }
+        val typesList = list.map { it.second }
+        // List is sorted by number of types and then by types
+        assertEquals(listOf("Bar", "Baz", "Foo"), appsList.map { it.packageName })
+        assertEquals(listOf(Privacy.TYPE_CAMERA, Privacy.TYPE_LOCATION), typesList[0])
+        assertEquals(listOf(Privacy.TYPE_CAMERA), typesList[1])
+        assertEquals(listOf(Privacy.TYPE_MICROPHONE), typesList[2])
+    }
+
+    @Test
+    fun testOrder() {
+        // We want location to always go last, so it will go in the "+ other apps"
+        val appCamera = PrivacyItem(PrivacyType.TYPE_CAMERA,
+                PrivacyApplication("Camera", TEST_UID))
+        val appMicrophone =
+                PrivacyItem(PrivacyType.TYPE_MICROPHONE,
+                        PrivacyApplication("Microphone", TEST_UID))
+        val appLocation =
+                PrivacyItem(PrivacyType.TYPE_LOCATION,
+                        PrivacyApplication("Location", TEST_UID))
+
+        val items = listOf(appLocation, appMicrophone, appCamera)
+        val textBuilder = PrivacyChipBuilder(context, items)
+        val appList = textBuilder.appsAndTypes.map { it.first }.map { it.packageName }
+        assertEquals(listOf("Camera", "Microphone", "Location"), appList)
+    }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyItemControllerFlagsTest.kt b/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyItemControllerFlagsTest.kt
new file mode 100644
index 0000000..4ba29e6
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyItemControllerFlagsTest.kt
@@ -0,0 +1,219 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.privacy
+
+import android.os.UserManager
+import android.provider.DeviceConfig
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.internal.config.sysui.SystemUiDeviceConfigFlags
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.appops.AppOpsController
+import com.android.systemui.broadcast.BroadcastDispatcher
+import com.android.systemui.dump.DumpManager
+import com.android.systemui.util.DeviceConfigProxy
+import com.android.systemui.util.DeviceConfigProxyFake
+import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.time.FakeSystemClock
+import org.junit.Assert.assertFalse
+import org.junit.Assert.assertTrue
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.Mock
+import org.mockito.Mockito
+import org.mockito.Mockito.anyBoolean
+import org.mockito.Mockito.atLeastOnce
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+class PrivacyItemControllerFlagsTest : SysuiTestCase() {
+    companion object {
+        fun <T> capture(argumentCaptor: ArgumentCaptor<T>): T = argumentCaptor.capture()
+        fun <T> eq(value: T): T = Mockito.eq(value) ?: value
+        fun <T> any(): T = Mockito.any<T>()
+
+        private const val ALL_INDICATORS =
+                SystemUiDeviceConfigFlags.PROPERTY_PERMISSIONS_HUB_ENABLED
+        private const val MIC_CAMERA = SystemUiDeviceConfigFlags.PROPERTY_MIC_CAMERA_ENABLED
+    }
+
+    @Mock
+    private lateinit var appOpsController: AppOpsController
+    @Mock
+    private lateinit var callback: PrivacyItemController.Callback
+    @Mock
+    private lateinit var userManager: UserManager
+    @Mock
+    private lateinit var broadcastDispatcher: BroadcastDispatcher
+    @Mock
+    private lateinit var dumpManager: DumpManager
+
+    private lateinit var privacyItemController: PrivacyItemController
+    private lateinit var executor: FakeExecutor
+    private lateinit var deviceConfigProxy: DeviceConfigProxy
+
+    fun PrivacyItemController(): PrivacyItemController {
+        return PrivacyItemController(
+                appOpsController,
+                executor,
+                executor,
+                broadcastDispatcher,
+                deviceConfigProxy,
+                userManager,
+                dumpManager
+        )
+    }
+
+    @Before
+    fun setup() {
+        MockitoAnnotations.initMocks(this)
+        executor = FakeExecutor(FakeSystemClock())
+        deviceConfigProxy = DeviceConfigProxyFake()
+
+        privacyItemController = PrivacyItemController()
+        privacyItemController.addCallback(callback)
+
+        executor.runAllReady()
+    }
+
+    @Test
+    fun testNotListeningByDefault() {
+        assertFalse(privacyItemController.allIndicatorsAvailable)
+        assertFalse(privacyItemController.micCameraAvailable)
+
+        verify(appOpsController, never()).addCallback(any(), any())
+    }
+
+    @Test
+    fun testMicCameraChanged() {
+        changeMicCamera(true)
+        executor.runAllReady()
+
+        verify(callback).onFlagMicCameraChanged(true)
+        verify(callback, never()).onFlagAllChanged(anyBoolean())
+
+        assertTrue(privacyItemController.micCameraAvailable)
+        assertFalse(privacyItemController.allIndicatorsAvailable)
+    }
+
+    @Test
+    fun testAllChanged() {
+        changeAll(true)
+        executor.runAllReady()
+
+        verify(callback).onFlagAllChanged(true)
+        verify(callback, never()).onFlagMicCameraChanged(anyBoolean())
+
+        assertTrue(privacyItemController.allIndicatorsAvailable)
+        assertFalse(privacyItemController.micCameraAvailable)
+    }
+
+    @Test
+    fun testBothChanged() {
+        changeAll(true)
+        changeMicCamera(true)
+        executor.runAllReady()
+
+        verify(callback, atLeastOnce()).onFlagAllChanged(true)
+        verify(callback, atLeastOnce()).onFlagMicCameraChanged(true)
+
+        assertTrue(privacyItemController.allIndicatorsAvailable)
+        assertTrue(privacyItemController.micCameraAvailable)
+    }
+
+    @Test
+    fun testAll_listeningToAll() {
+        changeAll(true)
+        executor.runAllReady()
+
+        verify(appOpsController).addCallback(eq(PrivacyItemController.OPS), any())
+    }
+
+    @Test
+    fun testMicCamera_listening() {
+        changeMicCamera(true)
+        executor.runAllReady()
+
+        verify(appOpsController).addCallback(eq(PrivacyItemController.OPS), any())
+    }
+
+    @Test
+    fun testAll_listening() {
+        changeAll(true)
+        executor.runAllReady()
+
+        verify(appOpsController).addCallback(eq(PrivacyItemController.OPS), any())
+    }
+
+    @Test
+    fun testAllFalse_notListening() {
+        changeAll(true)
+        executor.runAllReady()
+        changeAll(false)
+        executor.runAllReady()
+
+        verify(appOpsController).removeCallback(any(), any())
+    }
+
+    @Test
+    fun testSomeListening_stillListening() {
+        changeAll(true)
+        changeMicCamera(true)
+        executor.runAllReady()
+        changeAll(false)
+        executor.runAllReady()
+
+        verify(appOpsController, never()).removeCallback(any(), any())
+    }
+
+    @Test
+    fun testAllDeleted_stopListening() {
+        changeAll(true)
+        executor.runAllReady()
+        changeAll(null)
+        executor.runAllReady()
+
+        verify(appOpsController).removeCallback(any(), any())
+    }
+
+    @Test
+    fun testMicDeleted_stopListening() {
+        changeMicCamera(true)
+        executor.runAllReady()
+        changeMicCamera(null)
+        executor.runAllReady()
+
+        verify(appOpsController).removeCallback(any(), any())
+    }
+
+    private fun changeMicCamera(value: Boolean?) = changeProperty(MIC_CAMERA, value)
+    private fun changeAll(value: Boolean?) = changeProperty(ALL_INDICATORS, value)
+
+    private fun changeProperty(name: String, value: Boolean?) {
+        deviceConfigProxy.setProperty(
+                DeviceConfig.NAMESPACE_PRIVACY,
+                name,
+                value?.toString(),
+                false
+        )
+    }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyItemControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyItemControllerTest.kt
new file mode 100644
index 0000000..5c5df26
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyItemControllerTest.kt
@@ -0,0 +1,329 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.privacy
+
+import android.app.ActivityManager
+import android.app.AppOpsManager
+import android.content.Intent
+import android.content.pm.UserInfo
+import android.os.UserHandle
+import android.os.UserManager
+import android.provider.DeviceConfig
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper.RunWithLooper
+import androidx.test.filters.SmallTest
+import com.android.internal.config.sysui.SystemUiDeviceConfigFlags
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.appops.AppOpItem
+import com.android.systemui.appops.AppOpsController
+import com.android.systemui.broadcast.BroadcastDispatcher
+import com.android.systemui.dump.DumpManager
+import com.android.systemui.util.DeviceConfigProxy
+import com.android.systemui.util.DeviceConfigProxyFake
+import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.time.FakeSystemClock
+import org.hamcrest.Matchers.hasItem
+import org.hamcrest.Matchers.not
+import org.hamcrest.Matchers.nullValue
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertThat
+import org.junit.Assert.assertTrue
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.ArgumentMatchers.anyList
+import org.mockito.Captor
+import org.mockito.Mock
+import org.mockito.Mockito
+import org.mockito.Mockito.atLeastOnce
+import org.mockito.Mockito.doReturn
+import org.mockito.Mockito.mock
+import org.mockito.Mockito.never
+import org.mockito.Mockito.reset
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.verifyNoMoreInteractions
+import org.mockito.MockitoAnnotations
+
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+@RunWithLooper
+class PrivacyItemControllerTest : SysuiTestCase() {
+
+    companion object {
+        val CURRENT_USER_ID = ActivityManager.getCurrentUser()
+        val TEST_UID = CURRENT_USER_ID * UserHandle.PER_USER_RANGE
+        const val TEST_PACKAGE_NAME = "test"
+
+        private const val ALL_INDICATORS =
+                SystemUiDeviceConfigFlags.PROPERTY_PERMISSIONS_HUB_ENABLED
+        private const val MIC_CAMERA = SystemUiDeviceConfigFlags.PROPERTY_MIC_CAMERA_ENABLED
+        fun <T> capture(argumentCaptor: ArgumentCaptor<T>): T = argumentCaptor.capture()
+        fun <T> eq(value: T): T = Mockito.eq(value) ?: value
+        fun <T> any(): T = Mockito.any<T>()
+    }
+
+    @Mock
+    private lateinit var appOpsController: AppOpsController
+    @Mock
+    private lateinit var callback: PrivacyItemController.Callback
+    @Mock
+    private lateinit var userManager: UserManager
+    @Mock
+    private lateinit var broadcastDispatcher: BroadcastDispatcher
+    @Mock
+    private lateinit var dumpManager: DumpManager
+    @Captor
+    private lateinit var argCaptor: ArgumentCaptor<List<PrivacyItem>>
+    @Captor
+    private lateinit var argCaptorCallback: ArgumentCaptor<AppOpsController.Callback>
+
+    private lateinit var privacyItemController: PrivacyItemController
+    private lateinit var executor: FakeExecutor
+    private lateinit var deviceConfigProxy: DeviceConfigProxy
+
+    fun PrivacyItemController(): PrivacyItemController {
+        return PrivacyItemController(
+                appOpsController,
+                executor,
+                executor,
+                broadcastDispatcher,
+                deviceConfigProxy,
+                userManager,
+                dumpManager
+        )
+    }
+
+    @Before
+    fun setup() {
+        MockitoAnnotations.initMocks(this)
+        executor = FakeExecutor(FakeSystemClock())
+        deviceConfigProxy = DeviceConfigProxyFake()
+
+        changeAll(true)
+
+        doReturn(listOf(object : UserInfo() {
+            init {
+                id = CURRENT_USER_ID
+            }
+        })).`when`(userManager).getProfiles(anyInt())
+
+        privacyItemController = PrivacyItemController()
+    }
+
+    @Test
+    fun testSetListeningTrueByAddingCallback() {
+        privacyItemController.addCallback(callback)
+        executor.runAllReady()
+        verify(appOpsController).addCallback(eq(PrivacyItemController.OPS),
+                any())
+        verify(callback).onPrivacyItemsChanged(anyList())
+    }
+
+    @Test
+    fun testSetListeningFalseByRemovingLastCallback() {
+        privacyItemController.addCallback(callback)
+        executor.runAllReady()
+        verify(appOpsController, never()).removeCallback(any(),
+                any())
+        privacyItemController.removeCallback(callback)
+        executor.runAllReady()
+        verify(appOpsController).removeCallback(eq(PrivacyItemController.OPS),
+                any())
+        verify(callback).onPrivacyItemsChanged(emptyList())
+    }
+
+    @Test
+    fun testDistinctItems() {
+        doReturn(listOf(AppOpItem(AppOpsManager.OP_CAMERA, TEST_UID, "", 0),
+                AppOpItem(AppOpsManager.OP_CAMERA, TEST_UID, "", 1)))
+                .`when`(appOpsController).getActiveAppOpsForUser(anyInt())
+
+        privacyItemController.addCallback(callback)
+        executor.runAllReady()
+        verify(callback).onPrivacyItemsChanged(capture(argCaptor))
+        assertEquals(1, argCaptor.value.size)
+    }
+
+    @Test
+    fun testRegisterReceiver_allUsers() {
+        privacyItemController.addCallback(callback)
+        executor.runAllReady()
+        verify(broadcastDispatcher, atLeastOnce()).registerReceiver(
+                eq(privacyItemController.userSwitcherReceiver), any(), eq(null), eq(UserHandle.ALL))
+        verify(broadcastDispatcher, never())
+                .unregisterReceiver(eq(privacyItemController.userSwitcherReceiver))
+    }
+
+    @Test
+    fun testReceiver_ACTION_USER_FOREGROUND() {
+        privacyItemController.userSwitcherReceiver.onReceive(context,
+                Intent(Intent.ACTION_USER_SWITCHED))
+        executor.runAllReady()
+        verify(userManager).getProfiles(anyInt())
+    }
+
+    @Test
+    fun testReceiver_ACTION_MANAGED_PROFILE_ADDED() {
+        privacyItemController.userSwitcherReceiver.onReceive(context,
+                Intent(Intent.ACTION_MANAGED_PROFILE_AVAILABLE))
+        executor.runAllReady()
+        verify(userManager).getProfiles(anyInt())
+    }
+
+    @Test
+    fun testReceiver_ACTION_MANAGED_PROFILE_REMOVED() {
+        privacyItemController.userSwitcherReceiver.onReceive(context,
+                Intent(Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE))
+        executor.runAllReady()
+        verify(userManager).getProfiles(anyInt())
+    }
+
+    @Test
+    fun testAddMultipleCallbacks() {
+        val otherCallback = mock(PrivacyItemController.Callback::class.java)
+        privacyItemController.addCallback(callback)
+        executor.runAllReady()
+        verify(callback).onPrivacyItemsChanged(anyList())
+
+        privacyItemController.addCallback(otherCallback)
+        executor.runAllReady()
+        verify(otherCallback).onPrivacyItemsChanged(anyList())
+        // Adding a callback should not unnecessarily call previous ones
+        verifyNoMoreInteractions(callback)
+    }
+
+    @Test
+    fun testMultipleCallbacksAreUpdated() {
+        doReturn(emptyList<AppOpItem>()).`when`(appOpsController).getActiveAppOpsForUser(anyInt())
+
+        val otherCallback = mock(PrivacyItemController.Callback::class.java)
+        privacyItemController.addCallback(callback)
+        privacyItemController.addCallback(otherCallback)
+        executor.runAllReady()
+        reset(callback)
+        reset(otherCallback)
+
+        verify(appOpsController).addCallback(any(), capture(argCaptorCallback))
+        argCaptorCallback.value.onActiveStateChanged(0, TEST_UID, "", true)
+        executor.runAllReady()
+        verify(callback).onPrivacyItemsChanged(anyList())
+        verify(otherCallback).onPrivacyItemsChanged(anyList())
+    }
+
+    @Test
+    fun testRemoveCallback() {
+        doReturn(emptyList<AppOpItem>()).`when`(appOpsController).getActiveAppOpsForUser(anyInt())
+        val otherCallback = mock(PrivacyItemController.Callback::class.java)
+        privacyItemController.addCallback(callback)
+        privacyItemController.addCallback(otherCallback)
+        executor.runAllReady()
+        executor.runAllReady()
+        reset(callback)
+        reset(otherCallback)
+
+        verify(appOpsController).addCallback(any(), capture(argCaptorCallback))
+        privacyItemController.removeCallback(callback)
+        argCaptorCallback.value.onActiveStateChanged(0, TEST_UID, "", true)
+        executor.runAllReady()
+        verify(callback, never()).onPrivacyItemsChanged(anyList())
+        verify(otherCallback).onPrivacyItemsChanged(anyList())
+    }
+
+    @Test
+    fun testListShouldNotHaveNull() {
+        doReturn(listOf(AppOpItem(AppOpsManager.OP_ACTIVATE_VPN, TEST_UID, "", 0),
+                        AppOpItem(AppOpsManager.OP_COARSE_LOCATION, TEST_UID, "", 0)))
+                .`when`(appOpsController).getActiveAppOpsForUser(anyInt())
+        privacyItemController.addCallback(callback)
+        executor.runAllReady()
+        executor.runAllReady()
+
+        verify(callback).onPrivacyItemsChanged(capture(argCaptor))
+        assertEquals(1, argCaptor.value.size)
+        assertThat(argCaptor.value, not(hasItem(nullValue())))
+    }
+
+    @Test
+    fun testListShouldBeCopy() {
+        val list = listOf(PrivacyItem(PrivacyType.TYPE_CAMERA,
+                PrivacyApplication("", TEST_UID)))
+        privacyItemController.privacyList = list
+        val privacyList = privacyItemController.privacyList
+        assertEquals(list, privacyList)
+        assertTrue(list !== privacyList)
+    }
+
+    @Test
+    fun testNotListeningWhenIndicatorsDisabled() {
+        changeAll(false)
+        privacyItemController.addCallback(callback)
+        executor.runAllReady()
+        verify(appOpsController, never()).addCallback(eq(PrivacyItemController.OPS),
+                any())
+    }
+
+    @Test
+    fun testNotSendingLocationWhenOnlyMicCamera() {
+        changeAll(false)
+        changeMicCamera(true)
+        executor.runAllReady()
+
+        doReturn(listOf(AppOpItem(AppOpsManager.OP_CAMERA, TEST_UID, "", 0),
+                AppOpItem(AppOpsManager.OP_COARSE_LOCATION, TEST_UID, "", 0)))
+                .`when`(appOpsController).getActiveAppOpsForUser(anyInt())
+
+        privacyItemController.addCallback(callback)
+        executor.runAllReady()
+
+        verify(callback).onPrivacyItemsChanged(capture(argCaptor))
+
+        assertEquals(1, argCaptor.value.size)
+        assertEquals(PrivacyType.TYPE_CAMERA, argCaptor.value[0].privacyType)
+    }
+
+    @Test
+    fun testNotUpdated_LocationChangeWhenOnlyMicCamera() {
+        doReturn(listOf(AppOpItem(AppOpsManager.OP_COARSE_LOCATION, TEST_UID, "", 0)))
+                .`when`(appOpsController).getActiveAppOpsForUser(anyInt())
+
+        privacyItemController.addCallback(callback)
+        changeAll(false)
+        changeMicCamera(true)
+        executor.runAllReady()
+        reset(callback) // Clean callback
+
+        verify(appOpsController).addCallback(any(), capture(argCaptorCallback))
+        argCaptorCallback.value.onActiveStateChanged(
+                AppOpsManager.OP_FINE_LOCATION, TEST_UID, TEST_PACKAGE_NAME, true)
+
+        verify(callback, never()).onPrivacyItemsChanged(any())
+    }
+
+    private fun changeMicCamera(value: Boolean?) = changeProperty(MIC_CAMERA, value)
+    private fun changeAll(value: Boolean?) = changeProperty(ALL_INDICATORS, value)
+
+    private fun changeProperty(name: String, value: Boolean?) {
+        deviceConfigProxy.setProperty(
+                DeviceConfig.NAMESPACE_PRIVACY,
+                name,
+                value?.toString(),
+                false
+        )
+    }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/customize/TileAdapterDelegateTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/customize/TileAdapterDelegateTest.java
new file mode 100644
index 0000000..a5dead0
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/customize/TileAdapterDelegateTest.java
@@ -0,0 +1,265 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.customize;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.fail;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.os.Bundle;
+import android.testing.AndroidTestingRunner;
+import android.view.View;
+import android.view.accessibility.AccessibilityNodeInfo;
+
+import androidx.core.view.accessibility.AccessibilityNodeInfoCompat;
+import androidx.test.filters.SmallTest;
+
+import com.android.systemui.R;
+import com.android.systemui.SysuiTestCase;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+public class TileAdapterDelegateTest extends SysuiTestCase {
+
+    private static final int MOVE_TO_POSITION_ID = R.id.accessibility_action_qs_move_to_position;
+    private static final int ADD_TO_POSITION_ID = R.id.accessibility_action_qs_add_to_position;
+    private static final int POSITION_STRING_ID = R.string.accessibility_qs_edit_position;
+
+    @Mock
+    private TileAdapter.Holder mHolder;
+
+    private AccessibilityNodeInfoCompat mInfo;
+    private TileAdapterDelegate mDelegate;
+    private View mView;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+
+        mView = new View(mContext);
+        mDelegate = new TileAdapterDelegate();
+        mInfo = AccessibilityNodeInfoCompat.obtain();
+    }
+
+    @Test
+    public void testInfoNoSpecialActionsWhenNoHolder() {
+        mDelegate.onInitializeAccessibilityNodeInfo(mView, mInfo);
+        for (AccessibilityNodeInfoCompat.AccessibilityActionCompat action : mInfo.getActionList()) {
+            if (action.getId() == MOVE_TO_POSITION_ID || action.getId() == ADD_TO_POSITION_ID
+                    || action.getId() == AccessibilityNodeInfo.ACTION_CLICK) {
+                fail("It should not have special action " + action.getId());
+            }
+        }
+    }
+
+    @Test
+    public void testInfoNoSpecialActionsWhenCannotStartAccessibleAction() {
+        mView.setTag(mHolder);
+        when(mHolder.canTakeAccessibleAction()).thenReturn(false);
+        mDelegate.onInitializeAccessibilityNodeInfo(mView, mInfo);
+        for (AccessibilityNodeInfoCompat.AccessibilityActionCompat action : mInfo.getActionList()) {
+            if (action.getId() == MOVE_TO_POSITION_ID || action.getId() == ADD_TO_POSITION_ID
+                    || action.getId() == AccessibilityNodeInfo.ACTION_CLICK) {
+                fail("It should not have special action " + action.getId());
+            }
+        }
+    }
+
+    @Test
+    public void testNoCollectionItemInfo() {
+        mInfo.setCollectionItemInfo(
+                AccessibilityNodeInfoCompat.CollectionItemInfoCompat.obtain(0, 1, 0, 1, false));
+
+        mDelegate.onInitializeAccessibilityNodeInfo(mView, mInfo);
+        assertThat(mInfo.getCollectionItemInfo()).isNull();
+    }
+
+    @Test
+    public void testStateDescriptionHasPositionForCurrentTile() {
+        mView.setTag(mHolder);
+        int position = 3;
+        when(mHolder.getLayoutPosition()).thenReturn(position);
+        when(mHolder.isCurrentTile()).thenReturn(true);
+        when(mHolder.canTakeAccessibleAction()).thenReturn(true);
+
+        String expectedString = mContext.getString(POSITION_STRING_ID, position);
+
+        mDelegate.onInitializeAccessibilityNodeInfo(mView, mInfo);
+        assertThat(mInfo.getStateDescription()).isEqualTo(expectedString);
+    }
+
+    @Test
+    public void testStateDescriptionEmptyForNotCurrentTile() {
+        mView.setTag(mHolder);
+        int position = 3;
+        when(mHolder.getLayoutPosition()).thenReturn(position);
+        when(mHolder.isCurrentTile()).thenReturn(false);
+        when(mHolder.canTakeAccessibleAction()).thenReturn(true);
+
+        mDelegate.onInitializeAccessibilityNodeInfo(mView, mInfo);
+        assertThat(mInfo.getStateDescription()).isEqualTo("");
+    }
+
+    @Test
+    public void testClickAddAction() {
+        mView.setTag(mHolder);
+        when(mHolder.canTakeAccessibleAction()).thenReturn(true);
+        when(mHolder.canAdd()).thenReturn(true);
+        when(mHolder.canRemove()).thenReturn(false);
+
+        mDelegate.onInitializeAccessibilityNodeInfo(mView, mInfo);
+
+        String expectedString = mContext.getString(R.string.accessibility_qs_edit_tile_add_action);
+        AccessibilityNodeInfoCompat.AccessibilityActionCompat action =
+                getActionForId(mInfo, AccessibilityNodeInfo.ACTION_CLICK);
+        assertThat(action.getLabel().toString()).contains(expectedString);
+    }
+
+    @Test
+    public void testClickRemoveAction() {
+        mView.setTag(mHolder);
+        when(mHolder.canTakeAccessibleAction()).thenReturn(true);
+        when(mHolder.canAdd()).thenReturn(false);
+        when(mHolder.canRemove()).thenReturn(true);
+
+        mDelegate.onInitializeAccessibilityNodeInfo(mView, mInfo);
+
+        String expectedString = mContext.getString(
+                R.string.accessibility_qs_edit_remove_tile_action);
+        AccessibilityNodeInfoCompat.AccessibilityActionCompat action =
+                getActionForId(mInfo, AccessibilityNodeInfo.ACTION_CLICK);
+        assertThat(action.getLabel().toString()).contains(expectedString);
+    }
+
+    @Test
+    public void testNoClickAction() {
+        mView.setTag(mHolder);
+        when(mHolder.canTakeAccessibleAction()).thenReturn(true);
+        when(mHolder.canAdd()).thenReturn(false);
+        when(mHolder.canRemove()).thenReturn(false);
+        mInfo.addAction(AccessibilityNodeInfo.ACTION_CLICK);
+
+        mDelegate.onInitializeAccessibilityNodeInfo(mView, mInfo);
+
+        AccessibilityNodeInfoCompat.AccessibilityActionCompat action =
+                getActionForId(mInfo, AccessibilityNodeInfo.ACTION_CLICK);
+        assertThat(action).isNull();
+    }
+
+    @Test
+    public void testAddToPositionAction() {
+        mView.setTag(mHolder);
+        when(mHolder.canTakeAccessibleAction()).thenReturn(true);
+        when(mHolder.canAdd()).thenReturn(true);
+
+        mDelegate.onInitializeAccessibilityNodeInfo(mView, mInfo);
+        assertThat(getActionForId(mInfo, ADD_TO_POSITION_ID)).isNotNull();
+    }
+
+    @Test
+    public void testNoAddToPositionAction() {
+        mView.setTag(mHolder);
+        when(mHolder.canTakeAccessibleAction()).thenReturn(true);
+        when(mHolder.canAdd()).thenReturn(false);
+
+        mDelegate.onInitializeAccessibilityNodeInfo(mView, mInfo);
+        assertThat(getActionForId(mInfo, ADD_TO_POSITION_ID)).isNull();
+    }
+
+    @Test
+    public void testMoveToPositionAction() {
+        mView.setTag(mHolder);
+        when(mHolder.canTakeAccessibleAction()).thenReturn(true);
+        when(mHolder.isCurrentTile()).thenReturn(true);
+
+        mDelegate.onInitializeAccessibilityNodeInfo(mView, mInfo);
+        assertThat(getActionForId(mInfo, MOVE_TO_POSITION_ID)).isNotNull();
+    }
+
+    @Test
+    public void testNoMoveToPositionAction() {
+        mView.setTag(mHolder);
+        when(mHolder.canTakeAccessibleAction()).thenReturn(true);
+        when(mHolder.isCurrentTile()).thenReturn(false);
+
+        mDelegate.onInitializeAccessibilityNodeInfo(mView, mInfo);
+        assertThat(getActionForId(mInfo, MOVE_TO_POSITION_ID)).isNull();
+    }
+
+    @Test
+    public void testNoInteractionsWhenCannotTakeAccessibleAction() {
+        mView.setTag(mHolder);
+        when(mHolder.canTakeAccessibleAction()).thenReturn(false);
+
+        mDelegate.performAccessibilityAction(mView, AccessibilityNodeInfo.ACTION_CLICK, null);
+        mDelegate.performAccessibilityAction(mView, MOVE_TO_POSITION_ID, new Bundle());
+        mDelegate.performAccessibilityAction(mView, ADD_TO_POSITION_ID, new Bundle());
+
+        verify(mHolder, never()).toggleState();
+        verify(mHolder, never()).startAccessibleAdd();
+        verify(mHolder, never()).startAccessibleMove();
+    }
+
+    @Test
+    public void testClickActionTogglesState() {
+        mView.setTag(mHolder);
+        when(mHolder.canTakeAccessibleAction()).thenReturn(true);
+
+        mDelegate.performAccessibilityAction(mView, AccessibilityNodeInfo.ACTION_CLICK, null);
+
+        verify(mHolder).toggleState();
+    }
+
+    @Test
+    public void testAddToPositionActionStartsAccessibleAdd() {
+        mView.setTag(mHolder);
+        when(mHolder.canTakeAccessibleAction()).thenReturn(true);
+
+        mDelegate.performAccessibilityAction(mView, ADD_TO_POSITION_ID, null);
+
+        verify(mHolder).startAccessibleAdd();
+    }
+
+    @Test
+    public void testMoveToPositionActionStartsAccessibleMove() {
+        mView.setTag(mHolder);
+        when(mHolder.canTakeAccessibleAction()).thenReturn(true);
+
+        mDelegate.performAccessibilityAction(mView, MOVE_TO_POSITION_ID, null);
+
+        verify(mHolder).startAccessibleMove();
+    }
+
+    private AccessibilityNodeInfoCompat.AccessibilityActionCompat getActionForId(
+            AccessibilityNodeInfoCompat info, int action) {
+        for (AccessibilityNodeInfoCompat.AccessibilityActionCompat a : info.getActionList()) {
+            if (a.getId() == action) {
+                return a;
+            }
+        }
+        return null;
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingServiceTest.java b/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingServiceTest.java
index e98b6b6..4c9e141 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingServiceTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingServiceTest.java
@@ -32,7 +32,9 @@
 
 import com.android.internal.logging.UiEventLogger;
 import com.android.systemui.SysuiTestCase;
+import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.settings.CurrentUserContextTracker;
+import com.android.systemui.statusbar.phone.KeyguardDismissUtil;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -61,6 +63,12 @@
     private Executor mExecutor;
     @Mock
     private CurrentUserContextTracker mUserContextTracker;
+    private KeyguardDismissUtil mKeyguardDismissUtil = new KeyguardDismissUtil() {
+        public void executeWhenUnlocked(ActivityStarter.OnDismissAction action,
+                boolean requiresShadeOpen) {
+            action.onDismiss();
+        }
+    };
 
     private RecordingService mRecordingService;
 
@@ -68,7 +76,7 @@
     public void setUp() throws Exception {
         MockitoAnnotations.initMocks(this);
         mRecordingService = Mockito.spy(new RecordingService(mController, mExecutor, mUiEventLogger,
-                mNotificationManager, mUserContextTracker));
+                mNotificationManager, mUserContextTracker, mKeyguardDismissUtil));
 
         // Return actual context info
         doReturn(mContext).when(mRecordingService).getApplicationContext();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ActionProxyReceiverTest.java b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ActionProxyReceiverTest.java
new file mode 100644
index 0000000..4aaafbd
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ActionProxyReceiverTest.java
@@ -0,0 +1,153 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.screenshot;
+
+import static com.android.systemui.screenshot.GlobalScreenshot.ACTION_TYPE_SHARE;
+import static com.android.systemui.screenshot.GlobalScreenshot.EXTRA_ID;
+import static com.android.systemui.screenshot.GlobalScreenshot.EXTRA_SMART_ACTIONS_ENABLED;
+import static com.android.systemui.statusbar.phone.StatusBar.SYSTEM_DIALOG_REASON_SCREENSHOT;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.ArgumentMatchers.isNull;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.PendingIntent;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+import android.testing.AndroidTestingRunner;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.shared.system.ActivityManagerWrapper;
+import com.android.systemui.statusbar.phone.StatusBar;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.mockito.stubbing.Answer;
+
+import java.util.Optional;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+@RunWith(AndroidTestingRunner.class)
+@SmallTest
+public class ActionProxyReceiverTest extends SysuiTestCase {
+
+    @Mock
+    private StatusBar mMockStatusBar;
+    @Mock
+    private ActivityManagerWrapper mMockActivityManagerWrapper;
+    @Mock
+    private Future mMockFuture;
+    @Mock
+    private ScreenshotSmartActions mMockScreenshotSmartActions;
+    @Mock
+    private PendingIntent mMockPendingIntent;
+
+    private Intent mIntent;
+
+    @Before
+    public void setup() throws InterruptedException, ExecutionException, TimeoutException {
+        MockitoAnnotations.initMocks(this);
+        mIntent = new Intent(mContext, ActionProxyReceiver.class)
+                .putExtra(GlobalScreenshot.EXTRA_ACTION_INTENT, mMockPendingIntent);
+
+        when(mMockActivityManagerWrapper.closeSystemWindows(anyString())).thenReturn(mMockFuture);
+        when(mMockFuture.get(anyLong(), any(TimeUnit.class))).thenReturn(null);
+    }
+
+    @Test
+    public void testPendingIntentSentWithoutStatusBar() throws PendingIntent.CanceledException {
+        ActionProxyReceiver actionProxyReceiver = constructActionProxyReceiver(false);
+
+        actionProxyReceiver.onReceive(mContext, mIntent);
+
+        verify(mMockActivityManagerWrapper).closeSystemWindows(SYSTEM_DIALOG_REASON_SCREENSHOT);
+        verify(mMockStatusBar, never()).executeRunnableDismissingKeyguard(
+                any(Runnable.class), any(Runnable.class), anyBoolean(), anyBoolean(), anyBoolean());
+        verify(mMockPendingIntent).send(
+                eq(mContext), anyInt(), isNull(), isNull(), isNull(), isNull(), any(Bundle.class));
+    }
+
+    @Test
+    public void testPendingIntentSentWithStatusBar() throws PendingIntent.CanceledException {
+        ActionProxyReceiver actionProxyReceiver = constructActionProxyReceiver(true);
+        // ensure that the pending intent call is passed through
+        doAnswer((Answer<Object>) invocation -> {
+            ((Runnable) invocation.getArgument(0)).run();
+            return null;
+        }).when(mMockStatusBar).executeRunnableDismissingKeyguard(
+                any(Runnable.class), isNull(), anyBoolean(), anyBoolean(), anyBoolean());
+
+        actionProxyReceiver.onReceive(mContext, mIntent);
+
+        verify(mMockActivityManagerWrapper).closeSystemWindows(SYSTEM_DIALOG_REASON_SCREENSHOT);
+        verify(mMockStatusBar).executeRunnableDismissingKeyguard(
+                any(Runnable.class), isNull(), eq(true), eq(true), eq(true));
+        verify(mMockPendingIntent).send(
+                eq(mContext), anyInt(), isNull(), isNull(), isNull(), isNull(), any(Bundle.class));
+    }
+
+    @Test
+    public void testSmartActionsNotNotifiedByDefault() {
+        ActionProxyReceiver actionProxyReceiver = constructActionProxyReceiver(true);
+
+        actionProxyReceiver.onReceive(mContext, mIntent);
+
+        verify(mMockScreenshotSmartActions, never())
+                .notifyScreenshotAction(any(Context.class), anyString(), anyString(), anyBoolean());
+    }
+
+    @Test
+    public void testSmartActionsNotifiedIfEnabled() {
+        ActionProxyReceiver actionProxyReceiver = constructActionProxyReceiver(true);
+        mIntent.putExtra(EXTRA_SMART_ACTIONS_ENABLED, true);
+        String testId = "testID";
+        mIntent.putExtra(EXTRA_ID, testId);
+
+        actionProxyReceiver.onReceive(mContext, mIntent);
+
+        verify(mMockScreenshotSmartActions).notifyScreenshotAction(
+                mContext, testId, ACTION_TYPE_SHARE, false);
+    }
+
+    private ActionProxyReceiver constructActionProxyReceiver(boolean withStatusBar) {
+        if (withStatusBar) {
+            return new ActionProxyReceiver(
+                    Optional.of(mMockStatusBar), mMockActivityManagerWrapper,
+                    mMockScreenshotSmartActions);
+        } else {
+            return new ActionProxyReceiver(
+                    Optional.empty(), mMockActivityManagerWrapper, mMockScreenshotSmartActions);
+        }
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/DeleteScreenshotReceiverTest.java b/packages/SystemUI/tests/src/com/android/systemui/screenshot/DeleteScreenshotReceiverTest.java
new file mode 100644
index 0000000..b924913
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/DeleteScreenshotReceiverTest.java
@@ -0,0 +1,145 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.screenshot;
+
+import static com.android.systemui.screenshot.GlobalScreenshot.ACTION_TYPE_DELETE;
+import static com.android.systemui.screenshot.GlobalScreenshot.EXTRA_ID;
+import static com.android.systemui.screenshot.GlobalScreenshot.EXTRA_SMART_ACTIONS_ENABLED;
+import static com.android.systemui.screenshot.GlobalScreenshot.SCREENSHOT_URI_ID;
+
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertNotNull;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+
+import android.content.ContentResolver;
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.Intent;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.Environment;
+import android.provider.MediaStore;
+import android.testing.AndroidTestingRunner;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.util.concurrency.FakeExecutor;
+import com.android.systemui.util.time.FakeSystemClock;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.io.File;
+import java.util.concurrent.Executor;
+
+@RunWith(AndroidTestingRunner.class)
+@SmallTest
+public class DeleteScreenshotReceiverTest extends SysuiTestCase {
+
+    @Mock
+    private ScreenshotSmartActions mMockScreenshotSmartActions;
+    @Mock
+    private Executor mMockExecutor;
+
+    private DeleteScreenshotReceiver mDeleteScreenshotReceiver;
+    private FakeExecutor mFakeExecutor = new FakeExecutor(new FakeSystemClock());
+
+    @Before
+    public void setup() {
+        MockitoAnnotations.initMocks(this);
+        mDeleteScreenshotReceiver =
+                new DeleteScreenshotReceiver(mMockScreenshotSmartActions, mMockExecutor);
+    }
+
+    @Test
+    public void testNoUriProvided() {
+        Intent intent = new Intent(mContext, DeleteScreenshotReceiver.class);
+
+        mDeleteScreenshotReceiver.onReceive(mContext, intent);
+
+        verify(mMockExecutor, never()).execute(any(Runnable.class));
+        verify(mMockScreenshotSmartActions, never()).notifyScreenshotAction(
+                any(Context.class), any(String.class), any(String.class), anyBoolean());
+    }
+
+    @Test
+    public void testFileDeleted() {
+        DeleteScreenshotReceiver deleteScreenshotReceiver =
+                new DeleteScreenshotReceiver(mMockScreenshotSmartActions, mFakeExecutor);
+        ContentResolver contentResolver = mContext.getContentResolver();
+        final Uri testUri = contentResolver.insert(
+                MediaStore.Images.Media.EXTERNAL_CONTENT_URI, getFakeContentValues());
+        assertNotNull(testUri);
+
+        try {
+            Cursor cursor =
+                    contentResolver.query(testUri, null, null, null, null);
+            assertEquals(1, cursor.getCount());
+            Intent intent = new Intent(mContext, DeleteScreenshotReceiver.class)
+                    .putExtra(SCREENSHOT_URI_ID, testUri.toString());
+
+            deleteScreenshotReceiver.onReceive(mContext, intent);
+            int runCount = mFakeExecutor.runAllReady();
+
+            assertEquals(1, runCount);
+            cursor =
+                    contentResolver.query(testUri, null, null, null, null);
+            assertEquals(0, cursor.getCount());
+        } finally {
+            contentResolver.delete(testUri, null, null);
+        }
+
+        // ensure smart actions not called by default
+        verify(mMockScreenshotSmartActions, never()).notifyScreenshotAction(
+                any(Context.class), any(String.class), any(String.class), anyBoolean());
+    }
+
+    @Test
+    public void testNotifyScreenshotAction() {
+        Intent intent = new Intent(mContext, DeleteScreenshotReceiver.class);
+        String uriString = "testUri";
+        String testId = "testID";
+        intent.putExtra(SCREENSHOT_URI_ID, uriString);
+        intent.putExtra(EXTRA_ID, testId);
+        intent.putExtra(EXTRA_SMART_ACTIONS_ENABLED, true);
+
+        mDeleteScreenshotReceiver.onReceive(mContext, intent);
+
+        verify(mMockExecutor).execute(any(Runnable.class));
+        verify(mMockScreenshotSmartActions).notifyScreenshotAction(
+                mContext, testId, ACTION_TYPE_DELETE, false);
+    }
+
+    private static ContentValues getFakeContentValues() {
+        final ContentValues values = new ContentValues();
+        values.put(MediaStore.MediaColumns.RELATIVE_PATH, Environment.DIRECTORY_PICTURES
+                + File.separator + Environment.DIRECTORY_SCREENSHOTS);
+        values.put(MediaStore.MediaColumns.DISPLAY_NAME, "test_screenshot");
+        values.put(MediaStore.MediaColumns.MIME_TYPE, "image/png");
+        values.put(MediaStore.MediaColumns.DATE_ADDED, 0);
+        values.put(MediaStore.MediaColumns.DATE_MODIFIED, 0);
+        return values;
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotNotificationSmartActionsTest.java b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotNotificationSmartActionsTest.java
index d3b3399..184329ec 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotNotificationSmartActionsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotNotificationSmartActionsTest.java
@@ -61,12 +61,14 @@
  */
 public class ScreenshotNotificationSmartActionsTest extends SysuiTestCase {
     private ScreenshotNotificationSmartActionsProvider mSmartActionsProvider;
+    private ScreenshotSmartActions mScreenshotSmartActions;
     private Handler mHandler;
 
     @Before
     public void setup() {
         mSmartActionsProvider = mock(
                 ScreenshotNotificationSmartActionsProvider.class);
+        mScreenshotSmartActions = new ScreenshotSmartActions();
         mHandler = mock(Handler.class);
     }
 
@@ -82,7 +84,7 @@
         when(smartActionsProvider.getActions(any(), any(), any(), any(), any()))
             .thenThrow(RuntimeException.class);
         CompletableFuture<List<Notification.Action>> smartActionsFuture =
-                ScreenshotSmartActions.getSmartActionsFuture(
+                mScreenshotSmartActions.getSmartActionsFuture(
                         "", Uri.parse("content://authority/data"), bitmap, smartActionsProvider,
                         true, UserHandle.getUserHandleForUid(UserHandle.myUserId()));
         assertNotNull(smartActionsFuture);
@@ -100,7 +102,7 @@
         int timeoutMs = 1000;
         when(smartActionsFuture.get(timeoutMs, TimeUnit.MILLISECONDS)).thenThrow(
                 RuntimeException.class);
-        List<Notification.Action> actions = ScreenshotSmartActions.getSmartActions(
+        List<Notification.Action> actions = mScreenshotSmartActions.getSmartActions(
                 "", smartActionsFuture, timeoutMs, mSmartActionsProvider);
         assertEquals(Collections.emptyList(), actions);
     }
@@ -111,7 +113,7 @@
             throws Exception {
         doThrow(RuntimeException.class).when(mSmartActionsProvider).notifyOp(any(), any(), any(),
                 anyLong());
-        ScreenshotSmartActions.notifyScreenshotOp(null, mSmartActionsProvider, null, null, -1);
+        mScreenshotSmartActions.notifyScreenshotOp(null, mSmartActionsProvider, null, null, -1);
     }
 
     // Tests for a non-hardware bitmap, ScreenshotNotificationSmartActionsProvider is never invoked
@@ -122,7 +124,7 @@
         Bitmap bitmap = mock(Bitmap.class);
         when(bitmap.getConfig()).thenReturn(Bitmap.Config.RGB_565);
         CompletableFuture<List<Notification.Action>> smartActionsFuture =
-                ScreenshotSmartActions.getSmartActionsFuture(
+                mScreenshotSmartActions.getSmartActionsFuture(
                         "", Uri.parse("content://autority/data"), bitmap, mSmartActionsProvider,
                         true, UserHandle.getUserHandleForUid(UserHandle.myUserId()));
         verify(mSmartActionsProvider, never()).getActions(any(), any(), any(), any(), any());
@@ -136,7 +138,7 @@
     public void testScreenshotNotificationSmartActionsProviderInvokedOnce() {
         Bitmap bitmap = mock(Bitmap.class);
         when(bitmap.getConfig()).thenReturn(Bitmap.Config.HARDWARE);
-        ScreenshotSmartActions.getSmartActionsFuture(
+        mScreenshotSmartActions.getSmartActionsFuture(
                 "", Uri.parse("content://autority/data"), bitmap, mSmartActionsProvider, true,
                 UserHandle.getUserHandleForUid(UserHandle.myUserId()));
         verify(mSmartActionsProvider, times(1)).getActions(any(), any(), any(), any(), any());
@@ -152,7 +154,7 @@
                 SystemUIFactory.getInstance().createScreenshotNotificationSmartActionsProvider(
                         mContext, null, mHandler);
         CompletableFuture<List<Notification.Action>> smartActionsFuture =
-                ScreenshotSmartActions.getSmartActionsFuture("", null, bitmap,
+                mScreenshotSmartActions.getSmartActionsFuture("", null, bitmap,
                         actionsProvider,
                         true, UserHandle.getUserHandleForUid(UserHandle.myUserId()));
         assertNotNull(smartActionsFuture);
@@ -172,7 +174,8 @@
         data.image = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888);
         data.finisher = null;
         data.mActionsReadyListener = null;
-        SaveImageInBackgroundTask task = new SaveImageInBackgroundTask(mContext, data);
+        SaveImageInBackgroundTask task =
+                new SaveImageInBackgroundTask(mContext, mScreenshotSmartActions, data);
 
         Notification.Action shareAction = task.createShareAction(mContext, mContext.getResources(),
                 Uri.parse("Screenshot_123.png"));
@@ -198,7 +201,8 @@
         data.image = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888);
         data.finisher = null;
         data.mActionsReadyListener = null;
-        SaveImageInBackgroundTask task = new SaveImageInBackgroundTask(mContext, data);
+        SaveImageInBackgroundTask task =
+                new SaveImageInBackgroundTask(mContext, mScreenshotSmartActions, data);
 
         Notification.Action editAction = task.createEditAction(mContext, mContext.getResources(),
                 Uri.parse("Screenshot_123.png"));
@@ -224,7 +228,8 @@
         data.image = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888);
         data.finisher = null;
         data.mActionsReadyListener = null;
-        SaveImageInBackgroundTask task = new SaveImageInBackgroundTask(mContext, data);
+        SaveImageInBackgroundTask task =
+                new SaveImageInBackgroundTask(mContext, mScreenshotSmartActions, data);
 
         Notification.Action deleteAction = task.createDeleteAction(mContext,
                 mContext.getResources(),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/SmartActionsReceiverTest.java b/packages/SystemUI/tests/src/com/android/systemui/screenshot/SmartActionsReceiverTest.java
new file mode 100644
index 0000000..ce6f073
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/SmartActionsReceiverTest.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.screenshot;
+
+import static com.android.systemui.screenshot.GlobalScreenshot.EXTRA_ACTION_TYPE;
+import static com.android.systemui.screenshot.GlobalScreenshot.EXTRA_ID;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.ArgumentMatchers.isNull;
+import static org.mockito.Mockito.verify;
+
+import android.app.PendingIntent;
+import android.content.Intent;
+import android.os.Bundle;
+import android.testing.AndroidTestingRunner;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.systemui.SysuiTestCase;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@RunWith(AndroidTestingRunner.class)
+@SmallTest
+public class SmartActionsReceiverTest extends SysuiTestCase {
+
+    @Mock
+    private ScreenshotSmartActions mMockScreenshotSmartActions;
+    @Mock
+    private PendingIntent mMockPendingIntent;
+
+    private SmartActionsReceiver mSmartActionsReceiver;
+    private Intent mIntent;
+
+    @Before
+    public void setup() {
+        MockitoAnnotations.initMocks(this);
+        mSmartActionsReceiver = new SmartActionsReceiver(mMockScreenshotSmartActions);
+        mIntent = new Intent(mContext, SmartActionsReceiver.class)
+                .putExtra(GlobalScreenshot.EXTRA_ACTION_INTENT, mMockPendingIntent);
+    }
+
+    @Test
+    public void testSmartActionIntent() throws PendingIntent.CanceledException {
+        String testId = "testID";
+        String testActionType = "testActionType";
+        mIntent.putExtra(EXTRA_ID, testId);
+        mIntent.putExtra(EXTRA_ACTION_TYPE, testActionType);
+
+        mSmartActionsReceiver.onReceive(mContext, mIntent);
+
+        verify(mMockPendingIntent).send(
+                eq(mContext), eq(0), isNull(), isNull(), isNull(), isNull(), any(Bundle.class));
+        verify(mMockScreenshotSmartActions).notifyScreenshotAction(
+                mContext, testId, testActionType, true);
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/AlertingNotificationManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/AlertingNotificationManagerTest.java
index 402a99d..dee6020 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/AlertingNotificationManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/AlertingNotificationManagerTest.java
@@ -108,11 +108,7 @@
         return new TestableAlertingNotificationManager();
     }
 
-    protected StatusBarNotification createNewNotification(int id) {
-        Notification.Builder n = new Notification.Builder(mContext, "")
-                .setSmallIcon(R.drawable.ic_person)
-                .setContentTitle("Title")
-                .setContentText("Text");
+    protected StatusBarNotification createNewSbn(int id, Notification.Builder n) {
         return new StatusBarNotification(
                 TEST_PACKAGE_NAME /* pkg */,
                 TEST_PACKAGE_NAME,
@@ -126,6 +122,14 @@
                 0 /* postTime */);
     }
 
+    protected StatusBarNotification createNewNotification(int id) {
+        Notification.Builder n = new Notification.Builder(mContext, "")
+                .setSmallIcon(R.drawable.ic_person)
+                .setContentTitle("Title")
+                .setContentText("Text");
+        return createNewSbn(id, n);
+    }
+
     @Before
     public void setUp() {
         mTestHandler = Handler.createAsync(Looper.myLooper());
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java
index 90423c1..f1e6bf6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java
@@ -382,6 +382,36 @@
     }
 
     @Test
+    public void testUpdatePendingNotification_rankingUpdated() {
+        // GIVEN a notification with ranking is pending
+        final Ranking originalRanking = mEntry.getRanking();
+        mEntryManager.mPendingNotifications.put(mEntry.getKey(), mEntry);
+
+        // WHEN the same notification has been updated with a new ranking
+        final int newRank = 2345;
+        doAnswer(invocationOnMock -> {
+            Ranking ranking = (Ranking)
+                    invocationOnMock.getArguments()[1];
+            ranking.populate(
+                    mEntry.getKey(),
+                    newRank, /* this changed!! */
+                    false,
+                    0,
+                    0,
+                    IMPORTANCE_DEFAULT,
+                    null, null,
+                    null, null, null, true,
+                    Ranking.USER_SENTIMENT_NEUTRAL, false, -1,
+                    false, null, null, false, false, false, null, false);
+            return true;
+        }).when(mRankingMap).getRanking(eq(mEntry.getKey()), any(Ranking.class));
+        mEntryManager.addNotification(mSbn, mRankingMap);
+
+        // THEN ranking for the entry has been updated with new ranking
+        assertEquals(newRank, mEntry.getRanking().getRank());
+    }
+
+    @Test
     public void testLifetimeExtenders_ifNotificationIsRetainedItIsntRemoved() {
         // GIVEN an entry manager with a notification
         mEntryManager.addActiveNotificationForTest(mEntry);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java
index 25da741..3718cd7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java
@@ -51,6 +51,7 @@
 import androidx.test.filters.Suppress;
 
 import com.android.systemui.SysuiTestCase;
+import com.android.systemui.media.MediaFeatureFlag;
 import com.android.systemui.statusbar.NotificationRemoteInputManager;
 import com.android.systemui.statusbar.SmartReplyController;
 import com.android.systemui.statusbar.notification.ConversationNotificationProcessor;
@@ -110,6 +111,7 @@
                 () -> smartReplyConstants,
                 () -> smartReplyController,
                 mConversationNotificationProcessor,
+                mock(MediaFeatureFlag.class),
                 mock(Executor.class));
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationEntryManagerInflationTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationEntryManagerInflationTest.java
index 787b7b7..8bb155b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationEntryManagerInflationTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationEntryManagerInflationTest.java
@@ -44,6 +44,7 @@
 import com.android.systemui.R;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.bubbles.BubbleController;
+import com.android.systemui.media.MediaFeatureFlag;
 import com.android.systemui.plugins.FalsingManager;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.shared.plugins.PluginManager;
@@ -204,6 +205,7 @@
                 () -> mock(SmartReplyConstants.class),
                 () -> mock(SmartReplyController.class),
                 mock(ConversationNotificationProcessor.class),
+                mock(MediaFeatureFlag.class),
                 mBgExecutor);
         mRowContentBindStage = new RowContentBindStage(
                 binder,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java
index b9eb4d1..0c6409b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java
@@ -46,6 +46,7 @@
 import com.android.systemui.TestableDependency;
 import com.android.systemui.bubbles.BubbleController;
 import com.android.systemui.bubbles.BubblesTestActivity;
+import com.android.systemui.media.MediaFeatureFlag;
 import com.android.systemui.plugins.FalsingManager;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.statusbar.NotificationMediaManager;
@@ -133,6 +134,7 @@
                 () -> mock(SmartReplyConstants.class),
                 () -> mock(SmartReplyController.class),
                 mock(ConversationNotificationProcessor.class),
+                mock(MediaFeatureFlag.class),
                 mock(Executor.class));
         contentBinder.setInflateSynchronously(true);
         mBindStage = new RowContentBindStage(contentBinder,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarRotationContextTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarRotationContextTest.java
index d6b38ff..1b1ea31 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarRotationContextTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarRotationContextTest.java
@@ -60,7 +60,8 @@
         final View view = new View(mContext);
         mRotationButton = mock(RotationButton.class);
         mRotationButtonController = spy(
-                new RotationButtonController(mContext, RES_UNDEF, mRotationButton));
+                new RotationButtonController(mContext, RES_UNDEF, mRotationButton,
+                        (visibility) -> {}));
         final KeyButtonDrawable kbd = mock(KeyButtonDrawable.class);
         doReturn(view).when(mRotationButton).getCurrentView();
         doReturn(true).when(mRotationButton).acceptRotationProposal();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/HeadsUpManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/HeadsUpManagerTest.java
index fc7d0ce..0e4b053 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/HeadsUpManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/HeadsUpManagerTest.java
@@ -16,12 +16,15 @@
 
 package com.android.systemui.statusbar.policy;
 
+import static com.google.common.truth.Truth.assertThat;
+
 import static junit.framework.Assert.assertFalse;
 import static junit.framework.Assert.assertTrue;
 
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.Mockito.doReturn;
 
+import android.app.Notification;
 import android.content.Context;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
@@ -30,6 +33,7 @@
 
 import com.android.systemui.statusbar.AlertingNotificationManager;
 import com.android.systemui.statusbar.AlertingNotificationManagerTest;
+import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -84,5 +88,25 @@
         assertTrue("Heads up should live long enough", mLivesPastNormalTime);
         assertFalse(mHeadsUpManager.isAlerting(mEntry.getKey()));
     }
+
+    @Test
+    public void testAlertEntryCompareTo_ongoingCallLessThanActiveRemoteInput() {
+        HeadsUpManager.HeadsUpEntry ongoingCall = mHeadsUpManager.new HeadsUpEntry();
+        ongoingCall.setEntry(new NotificationEntryBuilder()
+                .setSbn(createNewSbn(0,
+                        new Notification.Builder(mContext, "")
+                                .setCategory(Notification.CATEGORY_CALL)
+                                .setOngoing(true)))
+                .build());
+
+        HeadsUpManager.HeadsUpEntry activeRemoteInput = mHeadsUpManager.new HeadsUpEntry();
+        activeRemoteInput.setEntry(new NotificationEntryBuilder()
+                .setSbn(createNewNotification(1))
+                .build());
+        activeRemoteInput.remoteInputActive = true;
+
+        assertThat(ongoingCall.compareTo(activeRemoteInput)).isLessThan(0);
+        assertThat(activeRemoteInput.compareTo(ongoingCall)).isGreaterThan(0);
+    }
 }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerDataTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerDataTest.java
index 6fffcff..56598f7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerDataTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerDataTest.java
@@ -3,6 +3,8 @@
 import static android.telephony.AccessNetworkConstants.TRANSPORT_TYPE_WWAN;
 import static android.telephony.NetworkRegistrationInfo.DOMAIN_PS;
 
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
 import static org.mockito.Matchers.anyInt;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
@@ -10,6 +12,7 @@
 import android.net.NetworkCapabilities;
 import android.os.Looper;
 import android.telephony.NetworkRegistrationInfo;
+import android.telephony.ServiceState;
 import android.telephony.TelephonyManager;
 import android.test.suitebuilder.annotation.SmallTest;
 import android.testing.AndroidTestingRunner;
@@ -259,6 +262,25 @@
         assertDataNetworkNameEquals(newDataName);
     }
 
+    @Test
+    public void testIsDataInService_true() {
+        setupDefaultSignal();
+        assertTrue(mNetworkController.isMobileDataNetworkInService());
+    }
+
+    @Test
+    public void testIsDataInService_noSignal_false() {
+        assertFalse(mNetworkController.isMobileDataNetworkInService());
+    }
+
+    @Test
+    public void testIsDataInService_notInService_false() {
+        setupDefaultSignal();
+        setVoiceRegState(ServiceState.STATE_OUT_OF_SERVICE);
+        setDataRegState(ServiceState.STATE_OUT_OF_SERVICE);
+        assertFalse(mNetworkController.isMobileDataNetworkInService());
+    }
+
     private void testDataActivity(int direction, boolean in, boolean out) {
         updateDataActivity(direction);
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeNetworkController.java b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeNetworkController.java
index d5ba381..e7acfae 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeNetworkController.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeNetworkController.java
@@ -95,6 +95,11 @@
     }
 
     @Override
+    public boolean isMobileDataNetworkInService() {
+        return false;
+    }
+
+    @Override
     public int getNumberSubscriptions() {
         return 0;
     }
diff --git a/packages/overlays/IconShapePebbleOverlay/res/values/config.xml b/packages/overlays/IconShapePebbleOverlay/res/values/config.xml
index 2465fe0..e7eeb30 100644
--- a/packages/overlays/IconShapePebbleOverlay/res/values/config.xml
+++ b/packages/overlays/IconShapePebbleOverlay/res/values/config.xml
@@ -18,7 +18,7 @@
 -->
 <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <!-- Specifies the path that is used by AdaptiveIconDrawable class to crop launcher icons. -->
-    <string name="config_icon_mask" translatable="false">"MM55,0 C25,0 0,25 0,50 0,78 28,100 55,100 85,100 100,85 100,58 100,30 86,0 55,0 Z"</string>
+    <string name="config_icon_mask" translatable="false">"M55,0 C25,0 0,25 0,50 0,78 28,100 55,100 85,100 100,85 100,58 100,30 86,0 55,0 Z"</string>
     <!-- Flag indicating whether round icons should be parsed from the application manifest. -->
     <bool name="config_useRoundIcon">false</bool>
     <!-- Corner radius of system dialogs -->
diff --git a/proto/src/metrics_constants/metrics_constants.proto b/proto/src/metrics_constants/metrics_constants.proto
index b0e401b..3f712dd 100644
--- a/proto/src/metrics_constants/metrics_constants.proto
+++ b/proto/src/metrics_constants/metrics_constants.proto
@@ -3706,33 +3706,40 @@
     // OS: O
     BACKUP_SETTINGS = 818;
 
+    // DEPRECATED: The metrics has been migrated to UiEvent per go/uievent.
     // ACTION: Picture-in-picture was explicitly entered for an activity
     // VALUE: true if it was entered while hiding as a result of moving to
     // another task, false otherwise
-    ACTION_PICTURE_IN_PICTURE_ENTERED = 819;
+    ACTION_PICTURE_IN_PICTURE_ENTERED = 819 [deprecated=true];
 
+    // DEPRECATED: The metrics has been migrated to UiEvent per go/uievent.
     // ACTION: The activity currently in picture-in-picture was expanded back to fullscreen
     // PACKAGE: The package name of the activity that was expanded back to fullscreen
-    ACTION_PICTURE_IN_PICTURE_EXPANDED_TO_FULLSCREEN = 820;
+    ACTION_PICTURE_IN_PICTURE_EXPANDED_TO_FULLSCREEN = 820 [deprecated=true];
 
+    // DEPRECATED: The metrics no longer used after migration to UiEvent per go/uievent.
     // ACTION: The activity currently in picture-in-picture was minimized
     // VALUE: True if the PiP was minimized, false otherwise
-    ACTION_PICTURE_IN_PICTURE_MINIMIZED = 821;
+    ACTION_PICTURE_IN_PICTURE_MINIMIZED = 821 [deprecated=true];
 
+    // DEPRECATED: The metrics has been migrated to UiEvent per go/uievent.
     // ACTION: Picture-in-picture was dismissed via the dismiss button
     // VALUE: 0 if dismissed by tap, 1 if dismissed by drag
-    ACTION_PICTURE_IN_PICTURE_DISMISSED = 822;
+    ACTION_PICTURE_IN_PICTURE_DISMISSED = 822 [deprecated=true];
 
-    // ACTION: The visibility of the picture-in-picture meny
+    // DEPRECATED: The metrics has been migrated to UiEvent per go/uievent.
+    // ACTION: The visibility of the picture-in-picture menu
     // VALUE: Whether or not the menu is visible
-    ACTION_PICTURE_IN_PICTURE_MENU = 823;
+    ACTION_PICTURE_IN_PICTURE_MENU = 823 [deprecated=true];
 
+    // DEPRECATED: The metrics has been migrated to UiEvent per go/uievent.
     // Enclosing category for group of PICTURE_IN_PICTURE_ASPECT_RATIO_FOO events,
     // logged when the aspect ratio changes
-    ACTION_PICTURE_IN_PICTURE_ASPECT_RATIO_CHANGED = 824;
+    ACTION_PICTURE_IN_PICTURE_ASPECT_RATIO_CHANGED = 824 [deprecated=true];
 
+    // DEPRECATED: The metrics no longer used after migration to UiEvent per go/uievent.
     // The current aspect ratio of the PiP, logged when it changes.
-    PICTURE_IN_PICTURE_ASPECT_RATIO = 825;
+    PICTURE_IN_PICTURE_ASPECT_RATIO = 825 [deprecated=true];
 
     // FIELD - length in dp of ACTION_LS_* gestures, or zero if not applicable
     // CATEGORY: GLOBAL_SYSTEM_UI
diff --git a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
index 6b852ad..8822d55c 100644
--- a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
+++ b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
@@ -90,6 +90,7 @@
 import java.util.Collections;
 import java.util.HashSet;
 import java.util.List;
+import java.util.NoSuchElementException;
 import java.util.Set;
 
 /**
@@ -147,6 +148,8 @@
 
     private boolean mRequestMultiFingerGestures;
 
+    private boolean mRequestTwoFingerPassthrough;
+
     boolean mRequestFilterKeyEvents;
 
     boolean mRetrieveInteractiveWindows;
@@ -322,8 +325,10 @@
                 & AccessibilityServiceInfo.FLAG_SERVICE_HANDLES_DOUBLE_TAP) != 0;
         mRequestMultiFingerGestures = (info.flags
                 & AccessibilityServiceInfo.FLAG_REQUEST_MULTI_FINGER_GESTURES) != 0;
-        mRequestFilterKeyEvents = (info.flags
-                & AccessibilityServiceInfo.FLAG_REQUEST_FILTER_KEY_EVENTS) != 0;
+        mRequestTwoFingerPassthrough =
+                (info.flags & AccessibilityServiceInfo.FLAG_REQUEST_2_FINGER_PASSTHROUGH) != 0;
+        mRequestFilterKeyEvents =
+                (info.flags & AccessibilityServiceInfo.FLAG_REQUEST_FILTER_KEY_EVENTS) != 0;
         mRetrieveInteractiveWindows = (info.flags
                 & AccessibilityServiceInfo.FLAG_RETRIEVE_INTERACTIVE_WINDOWS) != 0;
         mCaptureFingerprintGestures = (info.flags
@@ -1179,7 +1184,11 @@
                 /* ignore */
         }
         if (mService != null) {
-            mService.unlinkToDeath(this, 0);
+            try {
+                mService.unlinkToDeath(this, 0);
+            } catch (NoSuchElementException e) {
+                Slog.e(LOG_TAG, "Failed unregistering death link");
+            }
             mService = null;
         }
 
@@ -1768,6 +1777,10 @@
         return mRequestMultiFingerGestures;
     }
 
+    public boolean isTwoFingerPassthroughEnabled() {
+        return mRequestTwoFingerPassthrough;
+    }
+
     @Override
     public void setGestureDetectionPassthroughRegion(int displayId, Region region) {
         mSystemSupport.setGestureDetectionPassthroughRegion(displayId, region);
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java
index 020f225..a04a7c5 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java
@@ -114,6 +114,13 @@
      */
     static final int FLAG_REQUEST_MULTI_FINGER_GESTURES = 0x00000100;
 
+    /**
+     * Flag for enabling multi-finger gestures.
+     *
+     * @see #setUserAndEnabledFeatures(int, int)
+     */
+    static final int FLAG_REQUEST_2_FINGER_PASSTHROUGH = 0x00000200;
+
     static final int FEATURES_AFFECTING_MOTION_EVENTS =
             FLAG_FEATURE_INJECT_MOTION_EVENTS
                     | FLAG_FEATURE_AUTOCLICK
@@ -121,7 +128,8 @@
                     | FLAG_FEATURE_SCREEN_MAGNIFIER
                     | FLAG_FEATURE_TRIGGERED_SCREEN_MAGNIFIER
                     | FLAG_SERVICE_HANDLES_DOUBLE_TAP
-                    | FLAG_REQUEST_MULTI_FINGER_GESTURES;
+                    | FLAG_REQUEST_MULTI_FINGER_GESTURES
+                    | FLAG_REQUEST_2_FINGER_PASSTHROUGH;
 
     private final Context mContext;
 
@@ -417,6 +425,9 @@
                 if ((mEnabledFeatures & FLAG_REQUEST_MULTI_FINGER_GESTURES) != 0) {
                     explorer.setMultiFingerGesturesEnabled(true);
                 }
+                if ((mEnabledFeatures & FLAG_REQUEST_2_FINGER_PASSTHROUGH) != 0) {
+                    explorer.setTwoFingerPassthroughEnabled(true);
+                }
                 addFirstEventHandler(displayId, explorer);
                 mTouchExplorer.put(displayId, explorer);
             }
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index fcf270b4..9c10bad 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -1742,6 +1742,9 @@
                 if (userState.isMultiFingerGesturesEnabledLocked()) {
                     flags |= AccessibilityInputFilter.FLAG_REQUEST_MULTI_FINGER_GESTURES;
                 }
+                if (userState.isTwoFingerPassthroughEnabledLocked()) {
+                    flags |= AccessibilityInputFilter.FLAG_REQUEST_2_FINGER_PASSTHROUGH;
+                }
             }
             if (userState.isFilterKeyEventsEnabledLocked()) {
                 flags |= AccessibilityInputFilter.FLAG_FEATURE_FILTER_KEY_EVENTS;
@@ -2020,6 +2023,7 @@
         boolean touchExplorationEnabled = mUiAutomationManager.isTouchExplorationEnabledLocked();
         boolean serviceHandlesDoubleTapEnabled = false;
         boolean requestMultiFingerGestures = false;
+        boolean requestTwoFingerPassthrough = false;
         final int serviceCount = userState.mBoundServices.size();
         for (int i = 0; i < serviceCount; i++) {
             AccessibilityServiceConnection service = userState.mBoundServices.get(i);
@@ -2027,6 +2031,7 @@
                 touchExplorationEnabled = true;
                 serviceHandlesDoubleTapEnabled = service.isServiceHandlesDoubleTapEnabled();
                 requestMultiFingerGestures = service.isMultiFingerGesturesEnabled();
+                requestTwoFingerPassthrough = service.isTwoFingerPassthroughEnabled();
                 break;
             }
         }
@@ -2043,6 +2048,7 @@
         }
         userState.setServiceHandlesDoubleTapLocked(serviceHandlesDoubleTapEnabled);
         userState.setMultiFingerGesturesLocked(requestMultiFingerGestures);
+        userState.setTwoFingerPassthroughLocked(requestTwoFingerPassthrough);
     }
 
     private boolean readAccessibilityShortcutKeySettingLocked(AccessibilityUserState userState) {
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java
index 0845d01..299a776 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java
@@ -109,6 +109,7 @@
     private boolean mIsTouchExplorationEnabled;
     private boolean mServiceHandlesDoubleTap;
     private boolean mRequestMultiFingerGestures;
+    private boolean mRequestTwoFingerPassthrough;
     private int mUserInteractiveUiTimeout;
     private int mUserNonInteractiveUiTimeout;
     private int mNonInteractiveUiTimeout = 0;
@@ -160,6 +161,7 @@
         mIsTouchExplorationEnabled = false;
         mServiceHandlesDoubleTap = false;
         mRequestMultiFingerGestures = false;
+        mRequestTwoFingerPassthrough = false;
         mIsDisplayMagnificationEnabled = false;
         mIsAutoclickEnabled = false;
         mUserNonInteractiveUiTimeout = 0;
@@ -446,6 +448,8 @@
                 .append(String.valueOf(mServiceHandlesDoubleTap));
         pw.append(", requestMultiFingerGestures=")
                 .append(String.valueOf(mRequestMultiFingerGestures));
+        pw.append(", requestTwoFingerPassthrough=")
+                .append(String.valueOf(mRequestTwoFingerPassthrough));
         pw.append(", displayMagnificationEnabled=").append(String.valueOf(
                 mIsDisplayMagnificationEnabled));
         pw.append(", autoclickEnabled=").append(String.valueOf(mIsAutoclickEnabled));
@@ -733,6 +737,14 @@
     public void setMultiFingerGesturesLocked(boolean enabled) {
         mRequestMultiFingerGestures = enabled;
     }
+    public boolean isTwoFingerPassthroughEnabledLocked() {
+        return mRequestTwoFingerPassthrough;
+    }
+
+    public void setTwoFingerPassthroughLocked(boolean enabled) {
+        mRequestTwoFingerPassthrough = enabled;
+    }
+
 
     public int getUserInteractiveUiTimeoutLocked() {
         return mUserInteractiveUiTimeout;
diff --git a/services/accessibility/java/com/android/server/accessibility/gestures/EventDispatcher.java b/services/accessibility/java/com/android/server/accessibility/gestures/EventDispatcher.java
index 070626be..a4fec82 100644
--- a/services/accessibility/java/com/android/server/accessibility/gestures/EventDispatcher.java
+++ b/services/accessibility/java/com/android/server/accessibility/gestures/EventDispatcher.java
@@ -256,6 +256,7 @@
                 return actionMasked;
         }
     }
+
     /**
      * Sends down events to the view hierarchy for all pointers which are not already being
      * delivered i.e. pointers that are not yet injected.
@@ -285,6 +286,79 @@
     }
 
     /**
+     * Sends down events to the view hierarchy for all pointers which are not already being
+     * delivered with original down location. i.e. pointers that are not yet injected. The down time
+     * is also replaced by the original one.
+     *
+     *
+     * @param prototype The prototype from which to create the injected events.
+     * @param policyFlags The policy flags associated with the event.
+     */
+    void sendDownForAllNotInjectedPointersWithOriginalDown(MotionEvent prototype, int policyFlags) {
+        // Inject the injected pointers.
+        int pointerIdBits = 0;
+        final int pointerCount = prototype.getPointerCount();
+        final MotionEvent event = computeEventWithOriginalDown(prototype);
+        for (int i = 0; i < pointerCount; i++) {
+            final int pointerId = prototype.getPointerId(i);
+            // Do not send event for already delivered pointers.
+            if (!mState.isInjectedPointerDown(pointerId)) {
+                pointerIdBits |= (1 << pointerId);
+                final int action = computeInjectionAction(MotionEvent.ACTION_DOWN, i);
+                sendMotionEvent(
+                        event,
+                        action,
+                        mState.getLastReceivedEvent(),
+                        pointerIdBits,
+                        policyFlags);
+            }
+        }
+    }
+
+    private MotionEvent computeEventWithOriginalDown(MotionEvent prototype) {
+        final int pointerCount = prototype.getPointerCount();
+        if (pointerCount != mState.getReceivedPointerTracker().getReceivedPointerDownCount()) {
+            Slog.w(LOG_TAG, "The pointer count doesn't match the received count.");
+            return MotionEvent.obtain(prototype);
+        }
+        MotionEvent.PointerCoords[] coords = new MotionEvent.PointerCoords[pointerCount];
+        MotionEvent.PointerProperties[] properties =
+                new MotionEvent.PointerProperties[pointerCount];
+        for (int i = 0; i < pointerCount; ++i) {
+            final int pointerId = prototype.getPointerId(i);
+            final float x = mState.getReceivedPointerTracker().getReceivedPointerDownX(pointerId);
+            final float y = mState.getReceivedPointerTracker().getReceivedPointerDownY(pointerId);
+            coords[i] = new MotionEvent.PointerCoords();
+            coords[i].x = x;
+            coords[i].y = y;
+            properties[i] = new MotionEvent.PointerProperties();
+            properties[i].id = pointerId;
+            properties[i].toolType = MotionEvent.TOOL_TYPE_FINGER;
+        }
+        MotionEvent event =
+                MotionEvent.obtain(
+                        prototype.getDownTime(),
+                        // The event time is used for downTime while sending ACTION_DOWN. We adjust
+                        // it to avoid the motion velocity is too fast in the beginning after
+                        // Delegating.
+                        prototype.getDownTime(),
+                        prototype.getAction(),
+                        pointerCount,
+                        properties,
+                        coords,
+                        prototype.getMetaState(),
+                        prototype.getButtonState(),
+                        prototype.getXPrecision(),
+                        prototype.getYPrecision(),
+                        prototype.getDeviceId(),
+                        prototype.getEdgeFlags(),
+                        prototype.getSource(),
+                        prototype.getFlags());
+        return event;
+    }
+
+    /**
+     *
      * Sends up events to the view hierarchy for all pointers which are already being delivered i.e.
      * pointers that are injected.
      *
diff --git a/services/accessibility/java/com/android/server/accessibility/gestures/GestureManifold.java b/services/accessibility/java/com/android/server/accessibility/gestures/GestureManifold.java
index 5c15214..a860db3 100644
--- a/services/accessibility/java/com/android/server/accessibility/gestures/GestureManifold.java
+++ b/services/accessibility/java/com/android/server/accessibility/gestures/GestureManifold.java
@@ -94,8 +94,13 @@
     private boolean mServiceHandlesDoubleTap = false;
     // Whether multi-finger gestures are enabled.
     boolean mMultiFingerGesturesEnabled;
+    // Whether the two-finger passthrough is enabled when multi-finger gestures are enabled.
+    private boolean mTwoFingerPassthroughEnabled;
     // A list of all the multi-finger gestures, for easy adding and removal.
     private final List<GestureMatcher> mMultiFingerGestures = new ArrayList<>();
+    // A list of two-finger swipes, for easy adding and removal when turning on or off two-finger
+    // passthrough.
+    private final List<GestureMatcher> mTwoFingerSwipes = new ArrayList<>();
     // Shared state information.
     private TouchState mState;
 
@@ -105,6 +110,7 @@
         mListener = listener;
         mState = state;
         mMultiFingerGesturesEnabled = false;
+        mTwoFingerPassthroughEnabled = false;
         // Set up gestures.
         // Start with double tap.
         mGestures.add(new MultiTap(context, 2, GESTURE_DOUBLE_TAP, this));
@@ -161,14 +167,14 @@
         mMultiFingerGestures.add(
                 new MultiFingerMultiTap(mContext, 4, 3, GESTURE_4_FINGER_TRIPLE_TAP, this));
         // Two-finger swipes.
-        mMultiFingerGestures.add(
+        mTwoFingerSwipes.add(
                 new MultiFingerSwipe(context, 2, DOWN, GESTURE_2_FINGER_SWIPE_DOWN, this));
-        mMultiFingerGestures.add(
+        mTwoFingerSwipes.add(
                 new MultiFingerSwipe(context, 2, LEFT, GESTURE_2_FINGER_SWIPE_LEFT, this));
-        mMultiFingerGestures.add(
+        mTwoFingerSwipes.add(
                 new MultiFingerSwipe(context, 2, RIGHT, GESTURE_2_FINGER_SWIPE_RIGHT, this));
-        mMultiFingerGestures.add(
-                new MultiFingerSwipe(context, 2, UP, GESTURE_2_FINGER_SWIPE_UP, this));
+        mTwoFingerSwipes.add(new MultiFingerSwipe(context, 2, UP, GESTURE_2_FINGER_SWIPE_UP, this));
+        mMultiFingerGestures.addAll(mTwoFingerSwipes);
         // Three-finger swipes.
         mMultiFingerGestures.add(
                 new MultiFingerSwipe(context, 3, DOWN, GESTURE_3_FINGER_SWIPE_DOWN, this));
@@ -360,6 +366,25 @@
         }
     }
 
+    public boolean isTwoFingerPassthroughEnabled() {
+        return mTwoFingerPassthroughEnabled;
+    }
+
+    public void setTwoFingerPassthroughEnabled(boolean mode) {
+        if (mTwoFingerPassthroughEnabled != mode) {
+            mTwoFingerPassthroughEnabled = mode;
+            if (!mode) {
+                mMultiFingerGestures.addAll(mTwoFingerSwipes);
+                if (mMultiFingerGesturesEnabled) {
+                    mGestures.addAll(mTwoFingerSwipes);
+                }
+            } else {
+                mMultiFingerGestures.removeAll(mTwoFingerSwipes);
+                mGestures.removeAll(mTwoFingerSwipes);
+            }
+        }
+    }
+
     public void setServiceHandlesDoubleTap(boolean mode) {
         mServiceHandlesDoubleTap = mode;
     }
diff --git a/services/accessibility/java/com/android/server/accessibility/gestures/MultiFingerSwipe.java b/services/accessibility/java/com/android/server/accessibility/gestures/MultiFingerSwipe.java
index 4b89731..2a30539 100644
--- a/services/accessibility/java/com/android/server/accessibility/gestures/MultiFingerSwipe.java
+++ b/services/accessibility/java/com/android/server/accessibility/gestures/MultiFingerSwipe.java
@@ -294,7 +294,7 @@
                                 + Float.toString(mGestureDetectionThresholdPixels));
             }
             if (getState() == STATE_CLEAR) {
-                if (moveDelta < mTouchSlop) {
+                if (moveDelta < (mTargetFingerCount * mTouchSlop)) {
                     // This still counts as a touch not a swipe.
                     continue;
                 } else if (mStrokeBuffers[pointerIndex].size() == 0) {
diff --git a/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java b/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java
index 6074ac9..9538110 100644
--- a/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java
+++ b/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java
@@ -24,6 +24,7 @@
 import android.content.Context;
 import android.graphics.Region;
 import android.os.Handler;
+import android.util.DisplayMetrics;
 import android.util.Slog;
 import android.view.InputDevice;
 import android.view.MotionEvent;
@@ -73,12 +74,21 @@
     // The timeout after which we are no longer trying to detect a gesture.
     private static final int EXIT_GESTURE_DETECTION_TIMEOUT = 2000;
 
+    // The height of the top and bottom edges for  edge-swipes.
+    // For now this is only used to allow three-finger edge-swipes from the bottom.
+    private static final float EDGE_SWIPE_HEIGHT_CM = 0.25f;
+
+    // The calculated edge height for the top and bottom edges.
+    private final float mEdgeSwipeHeightPixels;
     // Timeout before trying to decide what the user is trying to do.
     private final int mDetermineUserIntentTimeout;
 
     // Slop between the first and second tap to be a double tap.
     private final int mDoubleTapSlop;
 
+    // Slop to move before being considered a move rather than a tap.
+    private final int mTouchSlop;
+
     // The current state of the touch explorer.
     private TouchState mState;
 
@@ -151,6 +161,9 @@
         mDispatcher = new EventDispatcher(context, mAms, super.getNext(), mState);
         mDetermineUserIntentTimeout = ViewConfiguration.getDoubleTapTimeout();
         mDoubleTapSlop = ViewConfiguration.get(context).getScaledDoubleTapSlop();
+        mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
+        DisplayMetrics metrics = mContext.getResources().getDisplayMetrics();
+        mEdgeSwipeHeightPixels = metrics.ydpi / GestureUtils.CM_PER_INCH * EDGE_SWIPE_HEIGHT_CM;
         mHandler = new Handler(context.getMainLooper());
         mExitGestureDetectionModeDelayed = new ExitGestureDetectionModeDelayed();
         mSendHoverEnterAndMoveDelayed = new SendHoverEnterAndMoveDelayed();
@@ -196,16 +209,10 @@
         if (mState.isTouchExploring()) {
             // If a touch exploration gesture is in progress send events for its end.
             sendHoverExitAndTouchExplorationGestureEndIfNeeded(policyFlags);
-        }  else if (mState.isDragging()) {
-            mDraggingPointerId = INVALID_POINTER_ID;
-            // Send exit to all pointers that we have delivered.
-            mDispatcher.sendUpForInjectedDownPointers(event, policyFlags);
-        } else if (mState.isDelegating()) {
-            // Send exit to all pointers that we have delivered.
-            mDispatcher.sendUpForInjectedDownPointers(event, policyFlags);
-        } else if (mState.isGestureDetecting()) {
-            // No state specific cleanup required.
         }
+        mDraggingPointerId = INVALID_POINTER_ID;
+        // Send exit to any pointers that we have delivered as part of delegating or dragging.
+        mDispatcher.sendUpForInjectedDownPointers(event, policyFlags);
         // Remove all pending callbacks.
         mSendHoverEnterAndMoveDelayed.cancel();
         mSendHoverExitDelayed.cancel();
@@ -533,6 +540,7 @@
             sendHoverExitAndTouchExplorationGestureEndIfNeeded(policyFlags);
         }
     }
+
     /**
      * Handles ACTION_MOVE while in the touch interacting state. This is where transitions to
      * delegating and dragging states are handled.
@@ -541,7 +549,7 @@
             MotionEvent event, MotionEvent rawEvent, int policyFlags) {
         final int pointerId = mReceivedPointerTracker.getPrimaryPointerId();
         final int pointerIndex = event.findPointerIndex(pointerId);
-        final int pointerIdBits = (1 << pointerId);
+        int pointerIdBits = (1 << pointerId);
         switch (event.getPointerCount()) {
             case 1:
                 // We have not started sending events since we try to
@@ -552,12 +560,37 @@
                 }
                 break;
             case 2:
-                if (mGestureDetector.isMultiFingerGesturesEnabled()) {
+                if (mGestureDetector.isMultiFingerGesturesEnabled()
+                        && !mGestureDetector.isTwoFingerPassthroughEnabled()) {
                     return;
                 }
                 // Make sure we don't have any pending transitions to touch exploration
                 mSendHoverEnterAndMoveDelayed.cancel();
                 mSendHoverExitDelayed.cancel();
+                if (mGestureDetector.isMultiFingerGesturesEnabled()
+                        && mGestureDetector.isTwoFingerPassthroughEnabled()) {
+                    if (pointerIndex < 0) {
+                        return;
+                    }
+                    // Require both fingers to have moved a certain amount before starting a drag.
+                    for (int index = 0; index < event.getPointerCount(); ++index) {
+                        int id = event.getPointerId(index);
+                        if (!mReceivedPointerTracker.isReceivedPointerDown(id)) {
+                            // Something is wrong with the event stream.
+                            Slog.e(LOG_TAG, "Invalid pointer id: " + id);
+                        }
+                        final float deltaX =
+                                mReceivedPointerTracker.getReceivedPointerDownX(id)
+                                        - rawEvent.getX(index);
+                        final float deltaY =
+                                mReceivedPointerTracker.getReceivedPointerDownY(id)
+                                        - rawEvent.getY(index);
+                        final double moveDelta = Math.hypot(deltaX, deltaY);
+                        if (moveDelta < (2 * mTouchSlop)) {
+                            return;
+                        }
+                    }
+                }
                 // More than one pointer so the user is not touch exploring
                 // and now we have to decide whether to delegate or drag.
                 // Remove move history before send injected non-move events
@@ -565,12 +598,20 @@
                 if (isDraggingGesture(event)) {
                     // Two pointers moving in the same direction within
                     // a given distance perform a drag.
-                    mState.startDragging();
-                    mDraggingPointerId = pointerId;
-                    adjustEventLocationForDrag(event);
+                    computeDraggingPointerIdIfNeeded(event);
+                    pointerIdBits = 1 << mDraggingPointerId;
                     event.setEdgeFlags(mReceivedPointerTracker.getLastReceivedDownEdgeFlags());
-                    mDispatcher.sendMotionEvent(
-                            event, MotionEvent.ACTION_DOWN, rawEvent, pointerIdBits, policyFlags);
+                    MotionEvent downEvent = computeDownEventForDrag(event);
+                    if (downEvent != null) {
+                        mDispatcher.sendMotionEvent(downEvent, MotionEvent.ACTION_DOWN, rawEvent,
+                                pointerIdBits, policyFlags);
+                        mDispatcher.sendMotionEvent(event, MotionEvent.ACTION_MOVE, rawEvent,
+                                pointerIdBits, policyFlags);
+                    } else {
+                        mDispatcher.sendMotionEvent(event, MotionEvent.ACTION_DOWN, rawEvent,
+                                pointerIdBits, policyFlags);
+                    }
+                    mState.startDragging();
                 } else {
                     // Two pointers moving arbitrary are delegated to the view hierarchy.
                     mState.startDelegating();
@@ -579,12 +620,31 @@
                 break;
             default:
                 if (mGestureDetector.isMultiFingerGesturesEnabled()) {
-                    return;
+                    if (mGestureDetector.isTwoFingerPassthroughEnabled()) {
+                        if (event.getPointerCount() == 3) {
+                            // If three fingers went down on the bottom edge of the screen, delegate
+                            // immediately.
+                            if (allPointersDownOnBottomEdge(event)) {
+                                if (DEBUG) {
+                                    Slog.d(LOG_TAG, "Three-finger edge swipe detected.");
+                                }
+                                mState.startDelegating();
+                                if (mState.isTouchExploring()) {
+                                    mDispatcher.sendDownForAllNotInjectedPointers(event,
+                                            policyFlags);
+                                } else {
+                                    mDispatcher.sendDownForAllNotInjectedPointersWithOriginalDown(
+                                            event, policyFlags);
+                                }
+                            }
+                        }
+                    }
+                } else {
+                    // More than two pointers are delegated to the view hierarchy.
+                    mState.startDelegating();
+                    event = MotionEvent.obtainNoHistory(event);
+                    mDispatcher.sendDownForAllNotInjectedPointers(event, policyFlags);
                 }
-                // More than two pointers are delegated to the view hierarchy.
-                mState.startDelegating();
-                event = MotionEvent.obtainNoHistory(event);
-                mDispatcher.sendDownForAllNotInjectedPointers(event, policyFlags);
                 break;
         }
     }
@@ -626,7 +686,8 @@
                         event, MotionEvent.ACTION_HOVER_MOVE, rawEvent, pointerIdBits, policyFlags);
                 break;
             case 2:
-                if (mGestureDetector.isMultiFingerGesturesEnabled()) {
+                if (mGestureDetector.isMultiFingerGesturesEnabled()
+                        && !mGestureDetector.isTwoFingerPassthroughEnabled()) {
                     return;
                 }
                 if (mSendHoverEnterAndMoveDelayed.isPending()) {
@@ -681,7 +742,8 @@
      */
     private void handleMotionEventStateDragging(
             MotionEvent event, MotionEvent rawEvent, int policyFlags) {
-        if (mGestureDetector.isMultiFingerGesturesEnabled()) {
+        if (mGestureDetector.isMultiFingerGesturesEnabled()
+                && !mGestureDetector.isTwoFingerPassthroughEnabled()) {
             // Multi-finger gestures conflict with this functionality.
             return;
         }
@@ -723,7 +785,7 @@
                     case 2: {
                         if (isDraggingGesture(event)) {
                             // If still dragging send a drag event.
-                            adjustEventLocationForDrag(event);
+                            computeDraggingPointerIdIfNeeded(event);
                             mDispatcher.sendMotionEvent(
                                     event,
                                     MotionEvent.ACTION_MOVE,
@@ -734,6 +796,7 @@
                             // The two pointers are moving either in different directions or
                             // no close enough => delegate the gesture to the view hierarchy.
                             mState.startDelegating();
+                            mDraggingPointerId = INVALID_POINTER_ID;
                             // Remove move history before send injected non-move events
                             event = MotionEvent.obtainNoHistory(event);
                             // Send an event to the end of the drag gesture.
@@ -749,6 +812,7 @@
                     } break;
                     default: {
                         mState.startDelegating();
+                        mDraggingPointerId = INVALID_POINTER_ID;
                         event = MotionEvent.obtainNoHistory(event);
                         // Send an event to the end of the drag gesture.
                         mDispatcher.sendMotionEvent(
@@ -772,17 +836,15 @@
                  }
             } break;
             case MotionEvent.ACTION_UP: {
+                final int pointerId = event.getPointerId(event.getActionIndex());
+                if (pointerId == mDraggingPointerId) {
+                    mDispatcher.sendMotionEvent(
+                            event, MotionEvent.ACTION_UP, rawEvent, pointerIdBits, policyFlags);
+                }
                 mAms.onTouchInteractionEnd();
                 // Announce the end of a new touch interaction.
                 mDispatcher.sendAccessibilityEvent(
                         AccessibilityEvent.TYPE_TOUCH_INTERACTION_END);
-                final int pointerId = event.getPointerId(event.getActionIndex());
-                if (pointerId == mDraggingPointerId) {
-                    mDraggingPointerId = INVALID_POINTER_ID;
-                    // Send an event to the end of the drag gesture.
-                    mDispatcher.sendMotionEvent(
-                            event, MotionEvent.ACTION_UP, rawEvent, pointerIdBits, policyFlags);
-                }
             } break;
         }
     }
@@ -901,21 +963,104 @@
     }
 
     /**
-     * Adjust the location of an injected event when performing a drag The new location will be in
-     * between the two fingers touching the screen.
+     * Computes {@link #mDraggingPointerId} if it is invalid. The pointer will be the finger
+     * closet to an edge of the screen.
      */
-    private void adjustEventLocationForDrag(MotionEvent event) {
-
+    private void computeDraggingPointerIdIfNeeded(MotionEvent event) {
+        if (mDraggingPointerId != INVALID_POINTER_ID) {
+            // If we have a valid pointer ID, we should be good
+            final int pointerIndex = event.findPointerIndex(mDraggingPointerId);
+            if (event.findPointerIndex(pointerIndex) >= 0) {
+                return;
+            }
+        }
+        // Use the pointer that is closest to its closest edge.
         final float firstPtrX = event.getX(0);
         final float firstPtrY = event.getY(0);
+        final int firstPtrId = event.getPointerId(0);
         final float secondPtrX = event.getX(1);
         final float secondPtrY = event.getY(1);
-        final int pointerIndex = event.findPointerIndex(mDraggingPointerId);
-        final float deltaX =
-                (pointerIndex == 0) ? (secondPtrX - firstPtrX) : (firstPtrX - secondPtrX);
-        final float deltaY =
-                (pointerIndex == 0) ? (secondPtrY - firstPtrY) : (firstPtrY - secondPtrY);
-        event.offsetLocation(deltaX / 2, deltaY / 2);
+        final int secondPtrId = event.getPointerId(1);
+        mDraggingPointerId = (getDistanceToClosestEdge(firstPtrX, firstPtrY)
+                 < getDistanceToClosestEdge(secondPtrX, secondPtrY))
+                 ? firstPtrId : secondPtrId;
+    }
+
+    private float getDistanceToClosestEdge(float x, float y) {
+        final long width = mContext.getResources().getDisplayMetrics().widthPixels;
+        final long height = mContext.getResources().getDisplayMetrics().heightPixels;
+        float distance = Float.MAX_VALUE;
+        if (x < (width - x)) {
+            distance = x;
+        } else {
+            distance = width - x;
+        }
+        if (distance > y) {
+            distance = y;
+        }
+        if (distance > (height - y)) {
+            distance = (height - y);
+        }
+        return distance;
+    }
+
+    /**
+     * Creates a down event using the down coordinates of the dragging pointer and other information
+     * from the supplied event. The supplied event's down time is adjusted to reflect the time when
+     * the dragging pointer initially went down.
+     */
+    private MotionEvent computeDownEventForDrag(MotionEvent event) {
+        // Creating a down event only  makes sense if we haven't started touch exploring yet.
+        if (mState.isTouchExploring()
+                || mDraggingPointerId == INVALID_POINTER_ID
+                || event == null) {
+            return null;
+        }
+        final float x = mReceivedPointerTracker.getReceivedPointerDownX(mDraggingPointerId);
+        final float y = mReceivedPointerTracker.getReceivedPointerDownY(mDraggingPointerId);
+        final long time = mReceivedPointerTracker.getReceivedPointerDownTime(mDraggingPointerId);
+        MotionEvent.PointerCoords[] coords = new MotionEvent.PointerCoords[1];
+        coords[0] = new MotionEvent.PointerCoords();
+        coords[0].x = x;
+        coords[0].y = y;
+        MotionEvent.PointerProperties[] properties = new MotionEvent.PointerProperties[1];
+        properties[0] = new MotionEvent.PointerProperties();
+        properties[0].id = mDraggingPointerId;
+        properties[0].toolType = MotionEvent.TOOL_TYPE_FINGER;
+        MotionEvent downEvent =
+                MotionEvent.obtain(
+                        time,
+                        time,
+                        MotionEvent.ACTION_DOWN,
+                        1,
+                        properties,
+                        coords,
+                        event.getMetaState(),
+                        event.getButtonState(),
+                        event.getXPrecision(),
+                        event.getYPrecision(),
+                        event.getDeviceId(),
+                        event.getEdgeFlags(),
+                        event.getSource(),
+                        event.getFlags());
+        event.setDownTime(time);
+        return downEvent;
+    }
+
+    private boolean allPointersDownOnBottomEdge(MotionEvent event) {
+        final long screenHeight =
+                mContext.getResources().getDisplayMetrics().heightPixels;
+        for (int i = 0; i < event.getPointerCount(); ++i) {
+            final int pointerId = event.getPointerId(i);
+            final float pointerDownY = mReceivedPointerTracker.getReceivedPointerDownY(pointerId);
+            if (pointerDownY < (screenHeight - mEdgeSwipeHeightPixels)) {
+                if (DEBUG) {
+                    Slog.d(LOG_TAG, "The pointer is not on the bottom edge" + pointerDownY);
+                }
+                return false;
+            }
+        }
+        return true;
     }
 
     public TouchState getState() {
@@ -944,6 +1089,13 @@
         mGestureDetector.setMultiFingerGesturesEnabled(enabled);
     }
 
+    /**
+     * This function turns on and off two-finger passthrough gestures such as drag and pinch when
+     * multi-finger gestures are enabled.
+     */
+    public void setTwoFingerPassthroughEnabled(boolean enabled) {
+        mGestureDetector.setTwoFingerPassthroughEnabled(enabled);
+    }
     public void setGestureDetectionPassthroughRegion(Region region) {
         mGestureDetectionPassthroughRegion = region;
     }
diff --git a/services/appprediction/java/com/android/server/appprediction/AppPredictionManagerService.java b/services/appprediction/java/com/android/server/appprediction/AppPredictionManagerService.java
index 1c4db12..59ba82e 100644
--- a/services/appprediction/java/com/android/server/appprediction/AppPredictionManagerService.java
+++ b/services/appprediction/java/com/android/server/appprediction/AppPredictionManagerService.java
@@ -34,6 +34,7 @@
 import android.content.Context;
 import android.content.pm.ParceledListSlice;
 import android.os.Binder;
+import android.os.IBinder;
 import android.os.ResultReceiver;
 import android.os.ShellCallback;
 import android.util.Slog;
@@ -108,9 +109,9 @@
 
         @Override
         public void createPredictionSession(@NonNull AppPredictionContext context,
-                @NonNull AppPredictionSessionId sessionId) {
-            runForUserLocked("createPredictionSession", sessionId,
-                    (service) -> service.onCreatePredictionSessionLocked(context, sessionId));
+                @NonNull AppPredictionSessionId sessionId, @NonNull IBinder token) {
+            runForUserLocked("createPredictionSession", sessionId, (service) ->
+                    service.onCreatePredictionSessionLocked(context, sessionId, token));
         }
 
         @Override
diff --git a/services/appprediction/java/com/android/server/appprediction/AppPredictionPerUserService.java b/services/appprediction/java/com/android/server/appprediction/AppPredictionPerUserService.java
index 7ee607c..735f420 100644
--- a/services/appprediction/java/com/android/server/appprediction/AppPredictionPerUserService.java
+++ b/services/appprediction/java/com/android/server/appprediction/AppPredictionPerUserService.java
@@ -30,6 +30,7 @@
 import android.content.pm.PackageManager.NameNotFoundException;
 import android.content.pm.ParceledListSlice;
 import android.content.pm.ServiceInfo;
+import android.os.IBinder;
 import android.os.RemoteCallbackList;
 import android.os.RemoteException;
 import android.provider.DeviceConfig;
@@ -44,8 +45,6 @@
 import com.android.server.infra.AbstractPerUserSystemService;
 import com.android.server.people.PeopleServiceInternal;
 
-import java.util.function.Consumer;
-
 /**
  * Per-user instance of {@link AppPredictionManagerService}.
  */
@@ -112,17 +111,24 @@
      */
     @GuardedBy("mLock")
     public void onCreatePredictionSessionLocked(@NonNull AppPredictionContext context,
-            @NonNull AppPredictionSessionId sessionId) {
-        if (!mSessionInfos.containsKey(sessionId)) {
-            mSessionInfos.put(sessionId, new AppPredictionSessionInfo(sessionId, context,
-                    DeviceConfig.getBoolean(NAMESPACE_SYSTEMUI,
-                            PREDICT_USING_PEOPLE_SERVICE_PREFIX + context.getUiSurface(), false),
-                    this::removeAppPredictionSessionInfo));
-        }
-        final boolean serviceExists = resolveService(sessionId, s ->
-                s.onCreatePredictionSession(context, sessionId), true);
-        if (!serviceExists) {
-            mSessionInfos.remove(sessionId);
+            @NonNull AppPredictionSessionId sessionId, @NonNull IBinder token) {
+        final boolean usesPeopleService = DeviceConfig.getBoolean(NAMESPACE_SYSTEMUI,
+                PREDICT_USING_PEOPLE_SERVICE_PREFIX + context.getUiSurface(), false);
+        final boolean serviceExists = resolveService(sessionId, false,
+                usesPeopleService, s -> s.onCreatePredictionSession(context, sessionId));
+        if (serviceExists && !mSessionInfos.containsKey(sessionId)) {
+            final AppPredictionSessionInfo sessionInfo = new AppPredictionSessionInfo(
+                    sessionId, context, usesPeopleService, token, () -> {
+                synchronized (mLock) {
+                    onDestroyPredictionSessionLocked(sessionId);
+                }
+            });
+            if (sessionInfo.linkToDeath()) {
+                mSessionInfos.put(sessionId, sessionInfo);
+            } else {
+                // destroy the session if calling process is already dead
+                onDestroyPredictionSessionLocked(sessionId);
+            }
         }
     }
 
@@ -132,7 +138,10 @@
     @GuardedBy("mLock")
     public void notifyAppTargetEventLocked(@NonNull AppPredictionSessionId sessionId,
             @NonNull AppTargetEvent event) {
-        resolveService(sessionId, s -> s.notifyAppTargetEvent(sessionId, event), false);
+        final AppPredictionSessionInfo sessionInfo = mSessionInfos.get(sessionId);
+        if (sessionInfo == null) return;
+        resolveService(sessionId, false, sessionInfo.mUsesPeopleService,
+                s -> s.notifyAppTargetEvent(sessionId, event));
     }
 
     /**
@@ -141,8 +150,10 @@
     @GuardedBy("mLock")
     public void notifyLaunchLocationShownLocked(@NonNull AppPredictionSessionId sessionId,
             @NonNull String launchLocation, @NonNull ParceledListSlice targetIds) {
-        resolveService(sessionId, s ->
-                s.notifyLaunchLocationShown(sessionId, launchLocation, targetIds), false);
+        final AppPredictionSessionInfo sessionInfo = mSessionInfos.get(sessionId);
+        if (sessionInfo == null) return;
+        resolveService(sessionId, false, sessionInfo.mUsesPeopleService,
+                s -> s.notifyLaunchLocationShown(sessionId, launchLocation, targetIds));
     }
 
     /**
@@ -151,7 +162,10 @@
     @GuardedBy("mLock")
     public void sortAppTargetsLocked(@NonNull AppPredictionSessionId sessionId,
             @NonNull ParceledListSlice targets, @NonNull IPredictionCallback callback) {
-        resolveService(sessionId, s -> s.sortAppTargets(sessionId, targets, callback), true);
+        final AppPredictionSessionInfo sessionInfo = mSessionInfos.get(sessionId);
+        if (sessionInfo == null) return;
+        resolveService(sessionId, true, sessionInfo.mUsesPeopleService,
+                s -> s.sortAppTargets(sessionId, targets, callback));
     }
 
     /**
@@ -160,10 +174,12 @@
     @GuardedBy("mLock")
     public void registerPredictionUpdatesLocked(@NonNull AppPredictionSessionId sessionId,
             @NonNull IPredictionCallback callback) {
-        final boolean serviceExists = resolveService(sessionId, s ->
-                s.registerPredictionUpdates(sessionId, callback), false);
         final AppPredictionSessionInfo sessionInfo = mSessionInfos.get(sessionId);
-        if (serviceExists && sessionInfo != null) {
+        if (sessionInfo == null) return;
+        final boolean serviceExists = resolveService(sessionId, false,
+                sessionInfo.mUsesPeopleService,
+                s -> s.registerPredictionUpdates(sessionId, callback));
+        if (serviceExists) {
             sessionInfo.addCallbackLocked(callback);
         }
     }
@@ -174,10 +190,12 @@
     @GuardedBy("mLock")
     public void unregisterPredictionUpdatesLocked(@NonNull AppPredictionSessionId sessionId,
             @NonNull IPredictionCallback callback) {
-        final boolean serviceExists = resolveService(sessionId, s ->
-                s.unregisterPredictionUpdates(sessionId, callback), false);
         final AppPredictionSessionInfo sessionInfo = mSessionInfos.get(sessionId);
-        if (serviceExists && sessionInfo != null) {
+        if (sessionInfo == null) return;
+        final boolean serviceExists = resolveService(sessionId, false,
+                sessionInfo.mUsesPeopleService,
+                s -> s.unregisterPredictionUpdates(sessionId, callback));
+        if (serviceExists) {
             sessionInfo.removeCallbackLocked(callback);
         }
     }
@@ -187,7 +205,10 @@
      */
     @GuardedBy("mLock")
     public void requestPredictionUpdateLocked(@NonNull AppPredictionSessionId sessionId) {
-        resolveService(sessionId, s -> s.requestPredictionUpdate(sessionId), true);
+        final AppPredictionSessionInfo sessionInfo = mSessionInfos.get(sessionId);
+        if (sessionInfo == null) return;
+        resolveService(sessionId, true, sessionInfo.mUsesPeopleService,
+                s -> s.requestPredictionUpdate(sessionId));
     }
 
     /**
@@ -195,12 +216,14 @@
      */
     @GuardedBy("mLock")
     public void onDestroyPredictionSessionLocked(@NonNull AppPredictionSessionId sessionId) {
-        final boolean serviceExists = resolveService(sessionId, s ->
-                s.onDestroyPredictionSession(sessionId), false);
-        final AppPredictionSessionInfo sessionInfo = mSessionInfos.get(sessionId);
-        if (serviceExists && sessionInfo != null) {
-            sessionInfo.destroy();
+        if (isDebug()) {
+            Slog.d(TAG, "onDestroyPredictionSessionLocked(): sessionId=" + sessionId);
         }
+        final AppPredictionSessionInfo sessionInfo = mSessionInfos.remove(sessionId);
+        if (sessionInfo == null) return;
+        resolveService(sessionId, false, sessionInfo.mUsesPeopleService,
+                s -> s.onDestroyPredictionSession(sessionId));
+        sessionInfo.destroy();
     }
 
     @Override
@@ -291,27 +314,18 @@
         }
 
         for (AppPredictionSessionInfo sessionInfo : mSessionInfos.values()) {
-            sessionInfo.resurrectSessionLocked(this);
-        }
-    }
-
-    private void removeAppPredictionSessionInfo(AppPredictionSessionId sessionId) {
-        if (isDebug()) {
-            Slog.d(TAG, "removeAppPredictionSessionInfo(): sessionId=" + sessionId);
-        }
-        synchronized (mLock) {
-            mSessionInfos.remove(sessionId);
+            sessionInfo.resurrectSessionLocked(this, sessionInfo.mToken);
         }
     }
 
     @GuardedBy("mLock")
     @Nullable
-    protected boolean resolveService(@NonNull final AppPredictionSessionId sessionId,
-            @NonNull final AbstractRemoteService.AsyncRequest<IPredictionService> cb,
-            boolean sendImmediately) {
-        final AppPredictionSessionInfo sessionInfo = mSessionInfos.get(sessionId);
-        if (sessionInfo == null) return false;
-        if (sessionInfo.mUsesPeopleService) {
+    protected boolean resolveService(
+            @NonNull final AppPredictionSessionId sessionId,
+            boolean sendImmediately,
+            boolean usesPeopleService,
+            @NonNull final AbstractRemoteService.AsyncRequest<IPredictionService> cb) {
+        if (usesPeopleService) {
             final IPredictionService service =
                     LocalServices.getService(PeopleServiceInternal.class);
             if (service != null) {
@@ -368,7 +382,9 @@
         private final AppPredictionContext mPredictionContext;
         private final boolean mUsesPeopleService;
         @NonNull
-        private final Consumer<AppPredictionSessionId> mRemoveSessionInfoAction;
+        final IBinder mToken;
+        @NonNull
+        final IBinder.DeathRecipient mDeathRecipient;
 
         private final RemoteCallbackList<IPredictionCallback> mCallbacks =
                 new RemoteCallbackList<IPredictionCallback>() {
@@ -388,14 +404,16 @@
                 @NonNull final AppPredictionSessionId id,
                 @NonNull final AppPredictionContext predictionContext,
                 final boolean usesPeopleService,
-                @NonNull final Consumer<AppPredictionSessionId> removeSessionInfoAction) {
+                @NonNull final IBinder token,
+                @NonNull final IBinder.DeathRecipient deathRecipient) {
             if (DEBUG) {
                 Slog.d(TAG, "Creating AppPredictionSessionInfo for session Id=" + id);
             }
             mSessionId = id;
             mPredictionContext = predictionContext;
             mUsesPeopleService = usesPeopleService;
-            mRemoveSessionInfoAction = removeSessionInfoAction;
+            mToken = token;
+            mDeathRecipient = deathRecipient;
         }
 
         void addCallbackLocked(IPredictionCallback callback) {
@@ -414,23 +432,38 @@
             mCallbacks.unregister(callback);
         }
 
+        boolean linkToDeath() {
+            try {
+                mToken.linkToDeath(mDeathRecipient, 0);
+            } catch (RemoteException e) {
+                if (DEBUG) {
+                    Slog.w(TAG, "Caller is dead before session can be started, sessionId: "
+                            + mSessionId);
+                }
+                return false;
+            }
+            return true;
+        }
+
         void destroy() {
             if (DEBUG) {
                 Slog.d(TAG, "Removing all callbacks for session Id=" + mSessionId
                         + " and " + mCallbacks.getRegisteredCallbackCount() + " callbacks.");
             }
+            if (mToken != null) {
+                mToken.unlinkToDeath(mDeathRecipient, 0);
+            }
             mCallbacks.kill();
-            mRemoveSessionInfoAction.accept(mSessionId);
         }
 
-        void resurrectSessionLocked(AppPredictionPerUserService service) {
+        void resurrectSessionLocked(AppPredictionPerUserService service, IBinder token) {
             int callbackCount = mCallbacks.getRegisteredCallbackCount();
             if (DEBUG) {
                 Slog.d(TAG, "Resurrecting remote service (" + service.getRemoteServiceLocked()
                         + ") for session Id=" + mSessionId + " and "
                         + callbackCount + " callbacks.");
             }
-            service.onCreatePredictionSessionLocked(mPredictionContext, mSessionId);
+            service.onCreatePredictionSessionLocked(mPredictionContext, mSessionId, token);
             mCallbacks.broadcast(
                     callback -> service.registerPredictionUpdatesLocked(mSessionId, callback));
         }
diff --git a/services/autofill/java/com/android/server/autofill/RemoteFillService.java b/services/autofill/java/com/android/server/autofill/RemoteFillService.java
index 5a9320f..b0755ac 100644
--- a/services/autofill/java/com/android/server/autofill/RemoteFillService.java
+++ b/services/autofill/java/com/android/server/autofill/RemoteFillService.java
@@ -161,8 +161,9 @@
 
                 @Override
                 public void onFailure(int requestId, CharSequence message) {
+                    String errorMessage = message == null ? "" : String.valueOf(message);
                     fillRequest.completeExceptionally(
-                            new RuntimeException(String.valueOf(message)));
+                            new RuntimeException(errorMessage));
                 }
             });
             return fillRequest;
diff --git a/services/backup/java/com/android/server/backup/keyvalue/KeyValueBackupTask.java b/services/backup/java/com/android/server/backup/keyvalue/KeyValueBackupTask.java
index 5e10916..a4e58a1f 100644
--- a/services/backup/java/com/android/server/backup/keyvalue/KeyValueBackupTask.java
+++ b/services/backup/java/com/android/server/backup/keyvalue/KeyValueBackupTask.java
@@ -649,16 +649,33 @@
         mReporter.onStartPackageBackup(PM_PACKAGE);
         mCurrentPackage = new PackageInfo();
         mCurrentPackage.packageName = PM_PACKAGE;
-
         try {
-            extractPmAgentData(mCurrentPackage);
+            // If we can't even extractPmAgentData(), then we treat the local state as
+            // compromised, just in case. This means that we will clear data and will
+            // start from a clean slate in the next attempt. It's not clear whether that's
+            // the right thing to do, but matches what we have historically done.
+            try {
+                extractPmAgentData(mCurrentPackage);
+            } catch (TaskException e) {
+                throw TaskException.stateCompromised(e); // force stateCompromised
+            }
+            // During sendDataToTransport, we generally trust any thrown TaskException
+            // about whether stateCompromised because those are likely transient;
+            // clearing state for those would have the potential to lead to cascading
+            // failures, as discussed in http://b/144030477.
+            // For specific status codes (e.g. TRANSPORT_NON_INCREMENTAL_BACKUP_REQUIRED),
+            // cleanUpAgentForTransportStatus() or theoretically handleTransportStatus()
+            // still have the opportunity to perform additional clean-up tasks.
             int status = sendDataToTransport(mCurrentPackage);
             cleanUpAgentForTransportStatus(status);
         } catch (AgentException | TaskException e) {
             mReporter.onExtractPmAgentDataError(e);
             cleanUpAgentForError(e);
-            // PM agent failure is task failure.
-            throw TaskException.stateCompromised(e);
+            if (e instanceof TaskException) {
+                throw (TaskException) e;
+            } else {
+                throw TaskException.stateCompromised(e); // PM agent failure is task failure.
+            }
         }
     }
 
diff --git a/services/core/java/android/os/UserManagerInternal.java b/services/core/java/android/os/UserManagerInternal.java
index fbe8c04..3898348 100644
--- a/services/core/java/android/os/UserManagerInternal.java
+++ b/services/core/java/android/os/UserManagerInternal.java
@@ -227,6 +227,14 @@
     public abstract @NonNull List<UserInfo> getUsers(boolean excludeDying);
 
     /**
+     * Internal implementation of getUsers does not check permissions.
+     * This improves performance for calls from inside system server which already have permissions
+     * checked.
+     */
+    public abstract @NonNull List<UserInfo> getUsers(boolean excludePartial, boolean excludeDying,
+            boolean excludePreCreated);
+
+    /**
      * Checks if the {@code callingUserId} and {@code targetUserId} are same or in same group
      * and that the {@code callingUserId} is not a profile and {@code targetUserId} is enabled.
      *
diff --git a/services/core/java/com/android/server/BluetoothManagerService.java b/services/core/java/com/android/server/BluetoothManagerService.java
index e919f39..0b8b352 100644
--- a/services/core/java/com/android/server/BluetoothManagerService.java
+++ b/services/core/java/com/android/server/BluetoothManagerService.java
@@ -97,6 +97,8 @@
 
     private static final String BLUETOOTH_ADMIN_PERM = android.Manifest.permission.BLUETOOTH_ADMIN;
     private static final String BLUETOOTH_PERM = android.Manifest.permission.BLUETOOTH;
+    private static final String BLUETOOTH_PRIVILEGED =
+            android.Manifest.permission.BLUETOOTH_PRIVILEGED;
 
     private static final String SECURE_SETTINGS_BLUETOOTH_ADDR_VALID = "bluetooth_addr_valid";
     private static final String SECURE_SETTINGS_BLUETOOTH_ADDRESS = "bluetooth_address";
@@ -296,6 +298,9 @@
     }
 
     public boolean onFactoryReset() {
+        mContext.enforceCallingOrSelfPermission(BLUETOOTH_PRIVILEGED,
+                "Need BLUETOOTH_PRIVILEGED permission");
+
         // Wait for stable state if bluetooth is temporary state.
         int state = getState();
         if (state == BluetoothAdapter.STATE_BLE_TURNING_ON
diff --git a/services/core/java/com/android/server/RescueParty.java b/services/core/java/com/android/server/RescueParty.java
index 829fca6..9fc8f0b 100644
--- a/services/core/java/com/android/server/RescueParty.java
+++ b/services/core/java/com/android/server/RescueParty.java
@@ -454,10 +454,14 @@
         public boolean mayObservePackage(String packageName) {
             PackageManager pm = mContext.getPackageManager();
             try {
-                // A package is a Mainline module if this is non-null
+                // A package is a module if this is non-null
                 if (pm.getModuleInfo(packageName, 0) != null) {
                     return true;
                 }
+            } catch (PackageManager.NameNotFoundException ignore) {
+            }
+
+            try {
                 ApplicationInfo info = pm.getApplicationInfo(packageName, 0);
                 return (info.flags & PERSISTENT_MASK) == PERSISTENT_MASK;
             } catch (PackageManager.NameNotFoundException e) {
diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java
index 1147db5..8403de4 100644
--- a/services/core/java/com/android/server/StorageManagerService.java
+++ b/services/core/java/com/android/server/StorageManagerService.java
@@ -4745,15 +4745,20 @@
             }
         }
 
-        public void onAppOpsChanged(int code, int uid, @Nullable String packageName, int mode) {
+        public void onAppOpsChanged(int code, int uid, @Nullable String packageName, int mode,
+                int previousMode) {
             final long token = Binder.clearCallingIdentity();
             try {
                 if (mIsFuseEnabled) {
                     // When using FUSE, we may need to kill the app if the op changes
                     switch(code) {
                         case OP_REQUEST_INSTALL_PACKAGES:
-                            // Always kill regardless of op change, to remount apps /storage
-                            killAppForOpChange(code, uid);
+                            if (previousMode == MODE_ALLOWED || mode == MODE_ALLOWED) {
+                                // If we transition to/from MODE_ALLOWED, kill the app to make
+                                // sure it has the correct view of /storage. Changing between
+                                // MODE_DEFAULT / MODE_ERRORED is a no-op
+                                killAppForOpChange(code, uid);
+                            }
                             return;
                         case OP_MANAGE_EXTERNAL_STORAGE:
                             if (mode != MODE_ALLOWED) {
diff --git a/services/core/java/com/android/server/UiModeManagerService.java b/services/core/java/com/android/server/UiModeManagerService.java
index be080e5..4ff6dac 100644
--- a/services/core/java/com/android/server/UiModeManagerService.java
+++ b/services/core/java/com/android/server/UiModeManagerService.java
@@ -134,6 +134,7 @@
     int mCurUiMode = 0;
     private int mSetUiMode = 0;
     private boolean mHoldingConfiguration = false;
+    private int mCurrentUser;
 
     private Configuration mConfiguration = new Configuration();
     boolean mSystemReady;
@@ -323,8 +324,16 @@
     @Override
     public void onSwitchUser(int userHandle) {
         super.onSwitchUser(userHandle);
+      	mCurrentUser = userHandle;
         getContext().getContentResolver().unregisterContentObserver(mSetupWizardObserver);
         verifySetupWizardCompleted();
+        synchronized (mLock) {
+            // only update if the value is actually changed
+            if (updateNightModeFromSettingsLocked(getContext(), getContext().getResources(),
+                   mCurrentUser)) {
+                updateLocked(0, 0);
+            }
+        }
     }
 
     @Override
@@ -352,11 +361,10 @@
                         new IntentFilter(Intent.ACTION_DOCK_EVENT));
                 IntentFilter batteryFilter = new IntentFilter(Intent.ACTION_BATTERY_CHANGED);
                 context.registerReceiver(mBatteryReceiver, batteryFilter);
-                IntentFilter filter = new IntentFilter();
-                filter.addAction(Intent.ACTION_USER_SWITCHED);
                 context.registerReceiver(mSettingsRestored,
                         new IntentFilter(Intent.ACTION_SETTING_RESTORED));
-                context.registerReceiver(new UserSwitchedReceiver(), filter, null, mHandler);
+                context.registerReceiver(mOnShutdown,
+                        new IntentFilter(Intent.ACTION_SHUTDOWN));
                 updateConfigurationLocked();
                 applyConfigurationExternallyLocked();
             }
@@ -403,6 +411,21 @@
         publishLocalService(UiModeManagerInternal.class, mLocalService);
     }
 
+    private final BroadcastReceiver mOnShutdown = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            if (mNightMode == MODE_NIGHT_AUTO) {
+                persistComputedNightMode(mCurrentUser);
+            }
+        }
+    };
+
+    private void persistComputedNightMode(int userId) {
+        Secure.putIntForUser(getContext().getContentResolver(),
+                Secure.UI_NIGHT_MODE_LAST_COMPUTED, mComputedNightMode ? 1 : 0,
+                userId);
+    }
+
     private final BroadcastReceiver mSettingsRestored = new BroadcastReceiver() {
         @Override
         public void onReceive(Context context, Intent intent) {
@@ -485,6 +508,9 @@
      * @return True if the new value is different from the old value. False otherwise.
      */
     private boolean updateNightModeFromSettingsLocked(Context context, Resources res, int userId) {
+        if (mCarModeEnabled || mCar) {
+            return false;
+        }
         int oldNightMode = mNightMode;
         if (mSetupWizardComplete) {
             mNightMode = Secure.getIntForUser(context.getContentResolver(),
@@ -501,6 +527,10 @@
                     Secure.getLongForUser(context.getContentResolver(),
                             Secure.DARK_THEME_CUSTOM_END_TIME,
                             DEFAULT_CUSTOM_NIGHT_END_TIME.toNanoOfDay() / 1000L, userId) * 1000);
+            if (mNightMode == MODE_NIGHT_AUTO) {
+                mComputedNightMode = Secure.getIntForUser(context.getContentResolver(),
+                        Secure.UI_NIGHT_MODE_LAST_COMPUTED, 0, userId) != 0;
+            }
         }
 
         return oldNightMode != mNightMode;
@@ -725,16 +755,30 @@
 
         @Override
         public boolean setNightModeActivated(boolean active) {
+            if (isNightModeLocked() && (getContext().checkCallingOrSelfPermission(
+                    android.Manifest.permission.MODIFY_DAY_NIGHT_MODE)
+                    != PackageManager.PERMISSION_GRANTED)) {
+                Slog.e(TAG, "Night mode locked, requires MODIFY_DAY_NIGHT_MODE permission");
+                return false;
+            }
+            final int user = Binder.getCallingUserHandle().getIdentifier();
+            if (user != mCurrentUser && getContext().checkCallingOrSelfPermission(
+                    android.Manifest.permission.INTERACT_ACROSS_USERS)
+                    != PackageManager.PERMISSION_GRANTED) {
+                Slog.e(TAG, "Target user is not current user,"
+                        + " INTERACT_ACROSS_USERS permission is required");
+                return false;
+
+            }
             synchronized (mLock) {
-                final int user = UserHandle.getCallingUserId();
                 final long ident = Binder.clearCallingIdentity();
                 try {
                     if (mNightMode == MODE_NIGHT_AUTO || mNightMode == MODE_NIGHT_CUSTOM) {
                         unregisterScreenOffEventLocked();
                         mOverrideNightModeOff = !active;
                         mOverrideNightModeOn = active;
-                        mOverrideNightModeUser = user;
-                        persistNightModeOverrides(user);
+                        mOverrideNightModeUser = mCurrentUser;
+                        persistNightModeOverrides(mCurrentUser);
                     } else if (mNightMode == UiModeManager.MODE_NIGHT_NO
                             && active) {
                         mNightMode = UiModeManager.MODE_NIGHT_YES;
@@ -744,7 +788,7 @@
                     }
                     updateConfigurationLocked();
                     applyConfigurationExternallyLocked();
-                    persistNightMode(user);
+                    persistNightMode(mCurrentUser);
                     return true;
                 } finally {
                     Binder.restoreCallingIdentity(ident);
@@ -1015,7 +1059,7 @@
 
     private void persistNightMode(int user) {
         // Only persist setting if not in car mode
-        if (mCarModeEnabled) return;
+        if (mCarModeEnabled || mCar) return;
         Secure.putIntForUser(getContext().getContentResolver(),
                 Secure.UI_NIGHT_MODE, mNightMode, user);
         Secure.putLongForUser(getContext().getContentResolver(),
@@ -1028,7 +1072,7 @@
 
     private void persistNightModeOverrides(int user) {
         // Only persist setting if not in car mode
-        if (mCarModeEnabled) return;
+        if (mCarModeEnabled || mCar) return;
         Secure.putIntForUser(getContext().getContentResolver(),
                 Secure.UI_NIGHT_MODE_OVERRIDE_ON, mOverrideNightModeOn ? 1 : 0, user);
         Secure.putIntForUser(getContext().getContentResolver(),
@@ -1079,7 +1123,7 @@
         }
 
         // Override night mode in power save mode if not in car mode
-        if (mPowerSave && !mCarModeEnabled) {
+        if (mPowerSave && !mCarModeEnabled && !mCar) {
             uiMode &= ~Configuration.UI_MODE_NIGHT_NO;
             uiMode |= Configuration.UI_MODE_NIGHT_YES;
         } else {
@@ -1577,18 +1621,4 @@
             }
         }
     }
-
-    private final class UserSwitchedReceiver extends BroadcastReceiver {
-        @Override
-        public void onReceive(Context context, Intent intent) {
-            synchronized (mLock) {
-                final int currentId = intent.getIntExtra(
-                        Intent.EXTRA_USER_HANDLE, USER_SYSTEM);
-                // only update if the value is actually changed
-                if (updateNightModeFromSettingsLocked(context, context.getResources(), currentId)) {
-                    updateLocked(0, 0);
-                }
-            }
-        }
-    }
 }
diff --git a/services/core/java/com/android/server/VibratorService.java b/services/core/java/com/android/server/VibratorService.java
index a153d41..9472a8a 100644
--- a/services/core/java/com/android/server/VibratorService.java
+++ b/services/core/java/com/android/server/VibratorService.java
@@ -1066,18 +1066,18 @@
         return attrs.isFlagSet(VibrationAttributes.FLAG_BYPASS_INTERRUPTION_POLICY);
     }
 
-    private int getAppOpMode(Vibration vib) {
+    private int getAppOpMode(int uid, String packageName, VibrationAttributes attrs) {
         int mode = mAppOps.checkAudioOpNoThrow(AppOpsManager.OP_VIBRATE,
-                vib.attrs.getAudioAttributes().getUsage(), vib.uid, vib.opPkg);
+                attrs.getAudioAttributes().getUsage(), uid, packageName);
         if (mode == AppOpsManager.MODE_ALLOWED) {
-            mode = mAppOps.startOpNoThrow(AppOpsManager.OP_VIBRATE, vib.uid, vib.opPkg);
+            mode = mAppOps.startOpNoThrow(AppOpsManager.OP_VIBRATE, uid, packageName);
         }
 
-        if (mode == AppOpsManager.MODE_IGNORED && shouldBypassDnd(vib.attrs)) {
+        if (mode == AppOpsManager.MODE_IGNORED && shouldBypassDnd(attrs)) {
             // If we're just ignoring the vibration op then this is set by DND and we should ignore
             // if we're asked to bypass. AppOps won't be able to record this operation, so make
             // sure we at least note it in the logs for debugging.
-            Slog.d(TAG, "Bypassing DND for vibration: " + vib);
+            Slog.d(TAG, "Bypassing DND for vibrate from uid " + uid);
             mode = AppOpsManager.MODE_ALLOWED;
         }
         return mode;
@@ -1099,7 +1099,7 @@
             return false;
         }
 
-        final int mode = getAppOpMode(vib);
+        final int mode = getAppOpMode(vib.uid, vib.opPkg, vib.attrs);
         if (mode != AppOpsManager.MODE_ALLOWED) {
             if (mode == AppOpsManager.MODE_ERRORED) {
                 // We might be getting calls from within system_server, so we don't actually
@@ -1774,7 +1774,7 @@
                 return SCALE_MUTE;
             }
             if (ActivityManager.checkComponentPermission(android.Manifest.permission.VIBRATE,
-                        vib.getUid(), -1 /*owningUid*/, true /*exported*/)
+                    vib.getUid(), -1 /*owningUid*/, true /*exported*/)
                     != PackageManager.PERMISSION_GRANTED) {
                 Slog.w(TAG, "pkg=" + vib.getPackage() + ", uid=" + vib.getUid()
                         + " tried to play externally controlled vibration"
@@ -1782,6 +1782,14 @@
                 return SCALE_MUTE;
             }
 
+            int mode = getAppOpMode(vib.getUid(), vib.getPackage(), vib.getVibrationAttributes());
+            if (mode != AppOpsManager.MODE_ALLOWED) {
+                if (mode == AppOpsManager.MODE_ERRORED) {
+                    Slog.w(TAG, "Would be an error: external vibrate from uid " + vib.getUid());
+                }
+                return SCALE_MUTE;
+            }
+
             final int scaleLevel;
             synchronized (mLock) {
                 if (!vib.equals(mCurrentExternalVibration)) {
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index 3f867f6..dd0e1f6 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -1836,11 +1836,13 @@
         if (DEBUG_SERVICE) Slog.v(TAG_SERVICE, "bindService: " + service
                 + " type=" + resolvedType + " conn=" + connection.asBinder()
                 + " flags=0x" + Integer.toHexString(flags));
+        final int callingPid = Binder.getCallingPid();
+        final int callingUid = Binder.getCallingUid();
         final ProcessRecord callerApp = mAm.getRecordForAppLocked(caller);
         if (callerApp == null) {
             throw new SecurityException(
                     "Unable to find app for caller " + caller
-                    + " (pid=" + Binder.getCallingPid()
+                    + " (pid=" + callingPid
                     + ") when binding service " + service);
         }
 
@@ -1880,19 +1882,19 @@
         }
 
         if ((flags & Context.BIND_SCHEDULE_LIKE_TOP_APP) != 0 && !isCallerSystem) {
-            throw new SecurityException("Non-system caller (pid=" + Binder.getCallingPid()
+            throw new SecurityException("Non-system caller (pid=" + callingPid
                     + ") set BIND_SCHEDULE_LIKE_TOP_APP when binding service " + service);
         }
 
         if ((flags & Context.BIND_ALLOW_WHITELIST_MANAGEMENT) != 0 && !isCallerSystem) {
             throw new SecurityException(
-                    "Non-system caller " + caller + " (pid=" + Binder.getCallingPid()
+                    "Non-system caller " + caller + " (pid=" + callingPid
                     + ") set BIND_ALLOW_WHITELIST_MANAGEMENT when binding service " + service);
         }
 
         if ((flags & Context.BIND_ALLOW_INSTANT) != 0 && !isCallerSystem) {
             throw new SecurityException(
-                    "Non-system caller " + caller + " (pid=" + Binder.getCallingPid()
+                    "Non-system caller " + caller + " (pid=" + callingPid
                             + ") set BIND_ALLOW_INSTANT when binding service " + service);
         }
 
@@ -1908,7 +1910,7 @@
 
         ServiceLookupResult res =
             retrieveServiceLocked(service, instanceName, resolvedType, callingPackage,
-                    Binder.getCallingPid(), Binder.getCallingUid(), userId, true,
+                    callingPid, callingUid, userId, true,
                     callerFg, isBindExternal, allowInstant);
         if (res == null) {
             return 0;
@@ -2068,7 +2070,7 @@
             if (!s.mAllowWhileInUsePermissionInFgs) {
                 s.mAllowWhileInUsePermissionInFgs =
                         shouldAllowWhileInUsePermissionInFgsLocked(callingPackage,
-                                Binder.getCallingPid(), Binder.getCallingUid(),
+                                callingPid, callingUid,
                                 service, s, false);
             }
 
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index a7ef01a..c090f33 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -2214,17 +2214,13 @@
         @Override
         protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
             try {
-                if (mActivityManagerService.mOomAdjuster.mCachedAppOptimizer.useFreezer()) {
-                    Process.enableFreezer(false);
-                }
+                mActivityManagerService.mOomAdjuster.mCachedAppOptimizer.enableFreezer(false);
 
                 if (!DumpUtils.checkDumpAndUsageStatsPermission(mActivityManagerService.mContext,
                         "meminfo", pw)) return;
                 PriorityDump.dump(mPriorityDumper, fd, pw, args);
             } finally {
-                if (mActivityManagerService.mOomAdjuster.mCachedAppOptimizer.useFreezer()) {
-                    Process.enableFreezer(true);
-                }
+                mActivityManagerService.mOomAdjuster.mCachedAppOptimizer.enableFreezer(true);
             }
         }
     }
@@ -2238,17 +2234,13 @@
         @Override
         protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
             try {
-                if (mActivityManagerService.mOomAdjuster.mCachedAppOptimizer.useFreezer()) {
-                    Process.enableFreezer(false);
-                }
+                mActivityManagerService.mOomAdjuster.mCachedAppOptimizer.enableFreezer(false);
 
                 if (!DumpUtils.checkDumpAndUsageStatsPermission(mActivityManagerService.mContext,
                         "gfxinfo", pw)) return;
                 mActivityManagerService.dumpGraphicsHardwareUsage(fd, pw, args);
             } finally {
-                if (mActivityManagerService.mOomAdjuster.mCachedAppOptimizer.useFreezer()) {
-                    Process.enableFreezer(true);
-                }
+                mActivityManagerService.mOomAdjuster.mCachedAppOptimizer.enableFreezer(true);
             }
         }
     }
@@ -2262,17 +2254,13 @@
         @Override
         protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
             try {
-                if (mActivityManagerService.mOomAdjuster.mCachedAppOptimizer.useFreezer()) {
-                    Process.enableFreezer(false);
-                }
+                mActivityManagerService.mOomAdjuster.mCachedAppOptimizer.enableFreezer(false);
 
                 if (!DumpUtils.checkDumpAndUsageStatsPermission(mActivityManagerService.mContext,
                         "dbinfo", pw)) return;
                 mActivityManagerService.dumpDbInfo(fd, pw, args);
             } finally {
-                if (mActivityManagerService.mOomAdjuster.mCachedAppOptimizer.useFreezer()) {
-                    Process.enableFreezer(true);
-                }
+                mActivityManagerService.mOomAdjuster.mCachedAppOptimizer.enableFreezer(true);
             }
         }
     }
@@ -2318,9 +2306,7 @@
         @Override
         protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
             try {
-                if (mActivityManagerService.mOomAdjuster.mCachedAppOptimizer.useFreezer()) {
-                    Process.enableFreezer(false);
-                }
+                mActivityManagerService.mOomAdjuster.mCachedAppOptimizer.enableFreezer(false);
 
                 if (!DumpUtils.checkDumpAndUsageStatsPermission(mActivityManagerService.mContext,
                         "cacheinfo", pw)) {
@@ -2329,9 +2315,7 @@
 
                 mActivityManagerService.dumpBinderCacheContents(fd, pw, args);
             } finally {
-                if (mActivityManagerService.mOomAdjuster.mCachedAppOptimizer.useFreezer()) {
-                    Process.enableFreezer(true);
-                }
+                mActivityManagerService.mOomAdjuster.mCachedAppOptimizer.enableFreezer(true);
             }
         }
     }
@@ -10528,7 +10512,7 @@
 
     private void dumpEverything(FileDescriptor fd, PrintWriter pw, String[] args, int opti,
             boolean dumpAll, String dumpPackage, boolean dumpClient, boolean dumpNormalPriority,
-            int dumpAppId) {
+            int dumpAppId, boolean dumpProxies) {
 
         ActiveServices.ServiceDumper sdumper;
 
@@ -10587,7 +10571,7 @@
             }
             sdumper.dumpWithClient();
         }
-        if (dumpPackage == null) {
+        if (dumpPackage == null && dumpProxies) {
             // Intentionally dropping the lock for this, because dumpBinderProxies() will make many
             // outgoing binder calls to retrieve interface descriptors; while that is system code,
             // there is nothing preventing an app from overriding this implementation by talking to
@@ -10996,13 +10980,14 @@
                 // dumpEverything() will take the lock when needed, and momentarily drop
                 // it for dumping client state.
                 dumpEverything(fd, pw, args, opti, dumpAll, dumpPackage, dumpClient,
-                        dumpNormalPriority, dumpAppId);
+                        dumpNormalPriority, dumpAppId, true /* dumpProxies */);
             } else {
                 // Take the lock here, so we get a consistent state for the entire dump;
-                // dumpEverything() will take the lock as well, but that is fine.
+                // dumpEverything() will take the lock as well, which is fine for everything
+                // except dumping proxies, which can take a long time; exclude them.
                 synchronized(this) {
                     dumpEverything(fd, pw, args, opti, dumpAll, dumpPackage, dumpClient,
-                            dumpNormalPriority, dumpAppId);
+                            dumpNormalPriority, dumpAppId, false /* dumpProxies */);
                 }
             }
         }
@@ -18602,14 +18587,14 @@
                     }
                 }
 
-                Process.enableFreezer(false);
+                mOomAdjuster.mCachedAppOptimizer.enableFreezer(false);
 
                 final RemoteCallback intermediateCallback = new RemoteCallback(
                         new RemoteCallback.OnResultListener() {
                         @Override
                         public void onResult(Bundle result) {
                             finishCallback.sendResult(result);
-                            Process.enableFreezer(true);
+                            mOomAdjuster.mCachedAppOptimizer.enableFreezer(true);
                         }
                     }, null);
 
@@ -20371,4 +20356,16 @@
             Binder.restoreCallingIdentity(token);
         }
     }
+
+    @Override
+    public boolean enableAppFreezer(boolean enable) {
+        int callerUid = Binder.getCallingUid();
+
+        // Only system can toggle the freezer state
+        if (callerUid == SYSTEM_UID) {
+            return mOomAdjuster.mCachedAppOptimizer.enableFreezer(enable);
+        } else {
+            throw new SecurityException("Caller uid " + callerUid + " cannot set freezer state ");
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/am/CachedAppOptimizer.java b/services/core/java/com/android/server/am/CachedAppOptimizer.java
index 43e3a04..8112bb8 100644
--- a/services/core/java/com/android/server/am/CachedAppOptimizer.java
+++ b/services/core/java/com/android/server/am/CachedAppOptimizer.java
@@ -16,14 +16,13 @@
 
 package com.android.server.am;
 
-import static android.os.Process.THREAD_PRIORITY_FOREGROUND;
-
 import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_COMPACTION;
 import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_FREEZER;
 import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM;
 
 import android.app.ActivityManager;
 import android.app.ActivityThread;
+import android.app.ApplicationExitInfo;
 import android.os.Debug;
 import android.os.Handler;
 import android.os.Message;
@@ -134,11 +133,15 @@
     static final int REPORT_UNFREEZE_MSG = 4;
 
     //TODO:change this static definition into a configurable flag.
-    static final int FREEZE_TIMEOUT_MS = 500;
+    static final int FREEZE_TIMEOUT_MS = 10000;
 
     static final int DO_FREEZE = 1;
     static final int REPORT_UNFREEZE = 2;
 
+    // Bitfield values for sync/async transactions reveived by frozen processes
+    static final int SYNC_RECEIVED_WHILE_FROZEN = 1;
+    static final int ASYNC_RECEIVED_WHILE_FROZEN = 2;
+
     /**
      * This thread must be moved to the system background cpuset.
      * If that doesn't happen, it's probably going to draw a lot of power.
@@ -210,6 +213,8 @@
     @GuardedBy("mPhenotypeFlagLock")
     private volatile boolean mUseCompaction = DEFAULT_USE_COMPACTION;
     private volatile boolean mUseFreezer = DEFAULT_USE_FREEZER;
+    @GuardedBy("this")
+    private int mFreezerDisableCount = 1; // Freezer is initially disabled, until enabled
     private final Random mRandom = new Random();
     @GuardedBy("mPhenotypeFlagLock")
     @VisibleForTesting volatile float mCompactStatsdSampleRate = DEFAULT_STATSD_SAMPLE_RATE;
@@ -256,7 +261,7 @@
             ProcessDependencies processDependencies) {
         mAm = am;
         mCachedAppOptimizerThread = new ServiceThread("CachedAppOptimizerThread",
-            THREAD_PRIORITY_FOREGROUND, true);
+            Process.THREAD_GROUP_SYSTEM, true);
         mProcStateThrottle = new HashSet<>();
         mProcessDependencies = processDependencies;
         mTestCallback = callback;
@@ -280,8 +285,6 @@
             updateProcStateThrottle();
             updateUseFreezer();
         }
-        Process.setThreadGroupAndCpuset(mCachedAppOptimizerThread.getThreadId(),
-                Process.THREAD_GROUP_SYSTEM);
     }
 
     /**
@@ -411,35 +414,119 @@
         mUseCompaction = DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
                     KEY_USE_COMPACTION, DEFAULT_USE_COMPACTION);
 
-        if (mUseCompaction) {
+        if (mUseCompaction && mCompactionHandler == null) {
             if (!mCachedAppOptimizerThread.isAlive()) {
                 mCachedAppOptimizerThread.start();
             }
 
             mCompactionHandler = new MemCompactionHandler();
+
+            Process.setThreadGroupAndCpuset(mCachedAppOptimizerThread.getThreadId(),
+                    Process.THREAD_GROUP_SYSTEM);
         }
     }
 
     /**
-     * Determines whether the freezer is correctly supported by this system
+     * Enables or disabled the app freezer.
+     * @param enable Enables the freezer if true, disables it if false.
+     * @return true if the operation completed successfully, false otherwise.
+     */
+    public synchronized boolean enableFreezer(boolean enable) {
+        if (!mUseFreezer) {
+            return false;
+        }
+
+        if (enable) {
+            mFreezerDisableCount--;
+
+            if (mFreezerDisableCount > 0) {
+                return true;
+            } else if (mFreezerDisableCount < 0) {
+                Slog.e(TAG_AM, "unbalanced call to enableFreezer, ignoring");
+                mFreezerDisableCount = 0;
+                return false;
+            }
+        } else {
+            mFreezerDisableCount++;
+
+            if (mFreezerDisableCount > 1) {
+                return true;
+            }
+        }
+
+        try {
+            enableFreezerInternal(enable);
+            return true;
+        } catch (java.lang.RuntimeException e) {
+            if (enable) {
+                mFreezerDisableCount = 0;
+            } else {
+                mFreezerDisableCount = 1;
+            }
+
+            Slog.e(TAG_AM, "Exception handling freezer state (enable: " + enable + "): "
+                    + e.toString());
+        }
+
+        return false;
+    }
+
+    /**
+     * Enable or disable the freezer. When enable == false all frozen processes are unfrozen,
+     * but aren't removed from the freezer. While in this state, processes can be added or removed
+     * by using Process.setProcessFrozen(), but they wouldn't be actually frozen until the freezer
+     * is enabled. If enable == true all processes in the freezer are frozen.
+     *
+     * @param enable Specify whether to enable (true) or disable (false) the freezer.
+     *
+     * @hide
+     */
+    private static native void enableFreezerInternal(boolean enable);
+
+    /**
+     * Informs binder that a process is about to be frozen. If freezer is enabled on a process via
+     * this method, this method will synchronously dispatch all pending transactions to the
+     * specified pid. This method will not add significant latencies when unfreezing.
+     * After freezing binder calls, binder will block all transaction to the frozen pid, and return
+     * an error to the sending process.
+     *
+     * @param pid the target pid for which binder transactions are to be frozen
+     * @param freeze specifies whether to flush transactions and then freeze (true) or unfreeze
+     * binder for the specificed pid.
+     *
+     * @throws RuntimeException in case a flush/freeze operation could not complete successfully.
+     */
+    private static native void freezeBinder(int pid, boolean freeze);
+
+    /**
+     * Retrieves binder freeze info about a process.
+     * @param pid the pid for which binder freeze info is to be retrieved.
+     *
+     * @throws RuntimeException if the operation could not complete successfully.
+     * @return a bit field reporting the binder freeze info for the process.
+     */
+    private static native int getBinderFreezeInfo(int pid);
+
+    /**
+     * Determines whether the freezer is supported by this system
      */
     public static boolean isFreezerSupported() {
         boolean supported = false;
         FileReader fr = null;
 
         try {
-            fr = new FileReader("/dev/freezer/frozen/freezer.killable");
-            int i = fr.read();
+            fr = new FileReader("/sys/fs/cgroup/freezer/cgroup.freeze");
+            char state = (char) fr.read();
 
-            if ((char) i == '1') {
+            if (state == '1' || state == '0') {
                 supported = true;
             } else {
-                Slog.w(TAG_AM, "Freezer killability is turned off, disabling freezer");
+                Slog.e(TAG_AM, "unexpected value in cgroup.freeze");
             }
         } catch (java.io.FileNotFoundException e) {
-            Slog.d(TAG_AM, "Freezer.killable not present, disabling freezer");
+            Slog.d(TAG_AM, "cgroup.freeze not present");
         } catch (Exception e) {
-            Slog.d(TAG_AM, "Unable to read freezer.killable, disabling freezer: " + e.toString());
+            Slog.d(TAG_AM, "unable to read cgroup.freeze: " + e.toString());
         }
 
         if (fr != null) {
@@ -470,13 +557,20 @@
             mUseFreezer = isFreezerSupported();
         }
 
-        if (mUseFreezer) {
+        if (mUseFreezer && mFreezeHandler == null) {
             Slog.d(TAG_AM, "Freezer enabled");
+            enableFreezer(true);
+
             if (!mCachedAppOptimizerThread.isAlive()) {
                 mCachedAppOptimizerThread.start();
             }
 
             mFreezeHandler = new FreezeHandler();
+
+            Process.setThreadGroupAndCpuset(mCachedAppOptimizerThread.getThreadId(),
+                    Process.THREAD_GROUP_SYSTEM);
+        } else {
+            enableFreezer(false);
         }
     }
 
@@ -649,6 +743,37 @@
             return;
         }
 
+        boolean processKilled = false;
+
+        try {
+            int freezeInfo = getBinderFreezeInfo(app.pid);
+
+            if ((freezeInfo & SYNC_RECEIVED_WHILE_FROZEN) != 0) {
+                Slog.d(TAG_AM, "pid " + app.pid + " " + app.processName + " "
+                        + " received sync transactions while frozen, killing");
+                app.kill("Sync transaction while in frozen state",
+                        ApplicationExitInfo.REASON_OTHER,
+                        ApplicationExitInfo.SUBREASON_INVALID_STATE, true);
+                processKilled = true;
+            }
+
+            if ((freezeInfo & ASYNC_RECEIVED_WHILE_FROZEN) != 0) {
+                Slog.d(TAG_AM, "pid " + app.pid + " " + app.processName + " "
+                        + " received async transactions while frozen");
+            }
+        } catch (Exception e) {
+            Slog.d(TAG_AM, "Unable to query binder frozen info for pid " + app.pid + " "
+                    + app.processName + ". Killing it. Exception: " + e);
+            app.kill("Unable to query binder frozen stats",
+                    ApplicationExitInfo.REASON_OTHER,
+                    ApplicationExitInfo.SUBREASON_INVALID_STATE, true);
+            processKilled = true;
+        }
+
+        if (processKilled) {
+            return;
+        }
+
         long freezeTime = app.freezeUnfreezeTime;
 
         try {
@@ -662,6 +787,17 @@
         }
 
         if (!app.frozen) {
+            try {
+                freezeBinder(app.pid, false);
+            } catch (RuntimeException e) {
+                Slog.e(TAG_AM, "Unable to unfreeze binder for " + app.pid + " " + app.processName
+                        + ". Killing it");
+                app.kill("Unable to unfreeze",
+                        ApplicationExitInfo.REASON_OTHER,
+                        ApplicationExitInfo.SUBREASON_INVALID_STATE, true);
+                return;
+            }
+
             if (DEBUG_FREEZER) {
                 Slog.d(TAG_AM, "sync unfroze " + app.pid + " " + app.processName);
             }
@@ -974,6 +1110,14 @@
                     return;
                 }
 
+                try {
+                    freezeBinder(pid, true);
+                } catch (RuntimeException e) {
+                    // TODO: it might be preferable to kill the target pid in this case
+                    Slog.e(TAG_AM, "Unable to freeze binder for " + pid + " " + name);
+                    return;
+                }
+
                 if (pid == 0 || proc.frozen) {
                     // Already frozen or not a real process, either one being
                     // launched or one being killed
diff --git a/services/core/java/com/android/server/am/CoreSettingsObserver.java b/services/core/java/com/android/server/am/CoreSettingsObserver.java
index 4e938ab..f766296 100644
--- a/services/core/java/com/android/server/am/CoreSettingsObserver.java
+++ b/services/core/java/com/android/server/am/CoreSettingsObserver.java
@@ -121,6 +121,10 @@
                 WidgetFlags.KEY_ENABLE_CURSOR_DRAG_FROM_ANYWHERE, boolean.class,
                 WidgetFlags.ENABLE_CURSOR_DRAG_FROM_ANYWHERE_DEFAULT));
         sDeviceConfigEntries.add(new DeviceConfigEntry<Integer>(
+                DeviceConfig.NAMESPACE_WIDGET, WidgetFlags.CURSOR_DRAG_MIN_ANGLE_FROM_VERTICAL,
+                WidgetFlags.KEY_CURSOR_DRAG_MIN_ANGLE_FROM_VERTICAL, int.class,
+                WidgetFlags.CURSOR_DRAG_MIN_ANGLE_FROM_VERTICAL_DEFAULT));
+        sDeviceConfigEntries.add(new DeviceConfigEntry<Integer>(
                 DeviceConfig.NAMESPACE_WIDGET, WidgetFlags.FINGER_TO_CURSOR_DISTANCE,
                 WidgetFlags.KEY_FINGER_TO_CURSOR_DISTANCE, int.class,
                 WidgetFlags.FINGER_TO_CURSOR_DISTANCE_DEFAULT));
diff --git a/services/core/java/com/android/server/am/ServiceRecord.java b/services/core/java/com/android/server/am/ServiceRecord.java
index fc17dde..1b65dba 100644
--- a/services/core/java/com/android/server/am/ServiceRecord.java
+++ b/services/core/java/com/android/server/am/ServiceRecord.java
@@ -16,6 +16,9 @@
 
 package com.android.server.am;
 
+import static android.app.PendingIntent.FLAG_IMMUTABLE;
+import static android.app.PendingIntent.FLAG_UPDATE_CURRENT;
+
 import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM;
 import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME;
 
@@ -873,7 +876,7 @@
                                 runningIntent.setData(Uri.fromParts("package",
                                         appInfo.packageName, null));
                                 PendingIntent pi = PendingIntent.getActivityAsUser(ams.mContext, 0,
-                                        runningIntent, PendingIntent.FLAG_UPDATE_CURRENT, null,
+                                        runningIntent, FLAG_UPDATE_CURRENT | FLAG_IMMUTABLE, null,
                                         UserHandle.of(userId));
                                 notiBuilder.setColor(ams.mContext.getColor(
                                         com.android.internal
@@ -912,7 +915,7 @@
                         }
                         if (localForegroundNoti.getSmallIcon() == null) {
                             // Notifications whose icon is 0 are defined to not show
-                            // a notification, silently ignoring it.  We don't want to
+                            // a notification.  We don't want to
                             // just ignore it, we want to prevent the service from
                             // being foreground.
                             throw new RuntimeException("invalid service notification: "
diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java
index 42af4da..640d99f 100644
--- a/services/core/java/com/android/server/appop/AppOpsService.java
+++ b/services/core/java/com/android/server/appop/AppOpsService.java
@@ -39,6 +39,7 @@
 import static android.app.AppOpsManager.OP_NONE;
 import static android.app.AppOpsManager.OP_PLAY_AUDIO;
 import static android.app.AppOpsManager.OP_RECORD_AUDIO;
+import static android.app.AppOpsManager.OP_RECORD_AUDIO_HOTWORD;
 import static android.app.AppOpsManager.OpEventProxyInfo;
 import static android.app.AppOpsManager.RestrictionBypass;
 import static android.app.AppOpsManager.SAMPLING_STRATEGY_BOOT_TIME_SAMPLING;
@@ -2052,6 +2053,8 @@
     public void getHistoricalOps(int uid, String packageName, String attributionTag,
             List<String> opNames, int filter, long beginTimeMillis, long endTimeMillis,
             int flags, RemoteCallback callback) {
+        PackageManager pm = mContext.getPackageManager();
+
         ensureHistoricalOpRequestIsValid(uid, packageName, attributionTag, opNames, filter,
                 beginTimeMillis, endTimeMillis, flags);
         Objects.requireNonNull(callback, "callback cannot be null");
@@ -2059,8 +2062,16 @@
         ActivityManagerInternal ami = LocalServices.getService(ActivityManagerInternal.class);
         boolean isCallerInstrumented = ami.isUidCurrentlyInstrumented(Binder.getCallingUid());
         boolean isCallerSystem = Binder.getCallingPid() == Process.myPid();
+        boolean isCallerPermissionController;
+        try {
+            isCallerPermissionController = pm.getPackageUid(
+                    mContext.getPackageManager().getPermissionControllerPackageName(), 0)
+                    == Binder.getCallingUid();
+        } catch (PackageManager.NameNotFoundException doesNotHappen) {
+            return;
+        }
 
-        if (!isCallerSystem && !isCallerInstrumented) {
+        if (!isCallerSystem && !isCallerInstrumented && !isCallerPermissionController) {
             mHandler.post(() -> callback.sendResult(new Bundle()));
             return;
         }
@@ -2188,6 +2199,7 @@
             updatePermissionRevokedCompat(uid, code, mode);
         }
 
+        int previousMode;
         synchronized (this) {
             final int defaultMode = AppOpsManager.opToDefaultMode(code);
 
@@ -2196,12 +2208,14 @@
                 if (mode == defaultMode) {
                     return;
                 }
+                previousMode = AppOpsManager.MODE_DEFAULT;
                 uidState = new UidState(uid);
                 uidState.opModes = new SparseIntArray();
                 uidState.opModes.put(code, mode);
                 mUidStates.put(uid, uidState);
                 scheduleWriteLocked();
             } else if (uidState.opModes == null) {
+                previousMode = AppOpsManager.MODE_DEFAULT;
                 if (mode != defaultMode) {
                     uidState.opModes = new SparseIntArray();
                     uidState.opModes.put(code, mode);
@@ -2211,6 +2225,7 @@
                 if (uidState.opModes.indexOfKey(code) >= 0 && uidState.opModes.get(code) == mode) {
                     return;
                 }
+                previousMode = uidState.opModes.get(code);
                 if (mode == defaultMode) {
                     uidState.opModes.delete(code);
                     if (uidState.opModes.size() <= 0) {
@@ -2225,7 +2240,7 @@
         }
 
         notifyOpChangedForAllPkgsInUid(code, uid, false, permissionPolicyCallback);
-        notifyOpChangedSync(code, uid, null, mode);
+        notifyOpChangedSync(code, uid, null, mode, previousMode);
     }
 
     /**
@@ -2404,11 +2419,12 @@
         }
     }
 
-    private void notifyOpChangedSync(int code, int uid, @NonNull String packageName, int mode) {
+    private void notifyOpChangedSync(int code, int uid, @NonNull String packageName, int mode,
+            int previousMode) {
         final StorageManagerInternal storageManagerInternal =
                 LocalServices.getService(StorageManagerInternal.class);
         if (storageManagerInternal != null) {
-            storageManagerInternal.onAppOpsChanged(code, uid, packageName, mode);
+            storageManagerInternal.onAppOpsChanged(code, uid, packageName, mode, previousMode);
         }
     }
 
@@ -2440,11 +2456,13 @@
             return;
         }
 
+        int previousMode = AppOpsManager.MODE_DEFAULT;
         synchronized (this) {
             UidState uidState = getUidStateLocked(uid, false);
             Op op = getOpLocked(code, uid, packageName, null, bypass, true);
             if (op != null) {
                 if (op.mode != mode) {
+                    previousMode = op.mode;
                     op.mode = mode;
                     if (uidState != null) {
                         uidState.evalForegroundOps(mOpModeWatchers);
@@ -2481,7 +2499,7 @@
                     this, repCbs, code, uid, packageName));
         }
 
-        notifyOpChangedSync(code, uid, packageName, mode);
+        notifyOpChangedSync(code, uid, packageName, mode, previousMode);
     }
 
     private void notifyOpChanged(ArraySet<ModeCallback> callbacks, int code,
@@ -2524,7 +2542,7 @@
     }
 
     private static ArrayList<ChangeRec> addChange(ArrayList<ChangeRec> reports,
-            int op, int uid, String packageName) {
+            int op, int uid, String packageName, int previousMode) {
         boolean duplicate = false;
         if (reports == null) {
             reports = new ArrayList<>();
@@ -2539,7 +2557,7 @@
             }
         }
         if (!duplicate) {
-            reports.add(new ChangeRec(op, uid, packageName));
+            reports.add(new ChangeRec(op, uid, packageName, previousMode));
         }
 
         return reports;
@@ -2547,7 +2565,7 @@
 
     private static HashMap<ModeCallback, ArrayList<ChangeRec>> addCallbacks(
             HashMap<ModeCallback, ArrayList<ChangeRec>> callbacks,
-            int op, int uid, String packageName, ArraySet<ModeCallback> cbs) {
+            int op, int uid, String packageName, int previousMode, ArraySet<ModeCallback> cbs) {
         if (cbs == null) {
             return callbacks;
         }
@@ -2558,7 +2576,7 @@
         for (int i=0; i<N; i++) {
             ModeCallback cb = cbs.valueAt(i);
             ArrayList<ChangeRec> reports = callbacks.get(cb);
-            ArrayList<ChangeRec> changed = addChange(reports, op, uid, packageName);
+            ArrayList<ChangeRec> changed = addChange(reports, op, uid, packageName, previousMode);
             if (changed != reports) {
                 callbacks.put(cb, changed);
             }
@@ -2570,11 +2588,13 @@
         final int op;
         final int uid;
         final String pkg;
+        final int previous_mode;
 
-        ChangeRec(int _op, int _uid, String _pkg) {
+        ChangeRec(int _op, int _uid, String _pkg, int _previous_mode) {
             op = _op;
             uid = _uid;
             pkg = _pkg;
+            previous_mode = _previous_mode;
         }
     }
 
@@ -2610,18 +2630,19 @@
                     for (int j = uidOpCount - 1; j >= 0; j--) {
                         final int code = opModes.keyAt(j);
                         if (AppOpsManager.opAllowsReset(code)) {
+                            int previousMode = opModes.valueAt(j);
                             opModes.removeAt(j);
                             if (opModes.size() <= 0) {
                                 uidState.opModes = null;
                             }
                             for (String packageName : getPackagesForUid(uidState.uid)) {
                                 callbacks = addCallbacks(callbacks, code, uidState.uid, packageName,
-                                        mOpModeWatchers.get(code));
+                                        previousMode, mOpModeWatchers.get(code));
                                 callbacks = addCallbacks(callbacks, code, uidState.uid, packageName,
-                                        mPackageModeWatchers.get(packageName));
+                                        previousMode, mPackageModeWatchers.get(packageName));
 
                                 allChanges = addChange(allChanges, code, uidState.uid,
-                                        packageName);
+                                        packageName, previousMode);
                             }
                         }
                     }
@@ -2652,16 +2673,18 @@
                         Op curOp = pkgOps.valueAt(j);
                         if (AppOpsManager.opAllowsReset(curOp.op)
                                 && curOp.mode != AppOpsManager.opToDefaultMode(curOp.op)) {
+                            int previousMode = curOp.mode;
                             curOp.mode = AppOpsManager.opToDefaultMode(curOp.op);
                             changed = true;
                             uidChanged = true;
                             final int uid = curOp.uidState.uid;
                             callbacks = addCallbacks(callbacks, curOp.op, uid, packageName,
-                                    mOpModeWatchers.get(curOp.op));
+                                    previousMode, mOpModeWatchers.get(curOp.op));
                             callbacks = addCallbacks(callbacks, curOp.op, uid, packageName,
-                                    mPackageModeWatchers.get(packageName));
+                                    previousMode, mPackageModeWatchers.get(packageName));
 
-                            allChanges = addChange(allChanges, curOp.op, uid, packageName);
+                            allChanges = addChange(allChanges, curOp.op, uid, packageName,
+                                    previousMode);
                             curOp.removeAttributionsWithNoTime();
                             if (curOp.mAttributions.isEmpty()) {
                                 pkgOps.removeAt(j);
@@ -2702,7 +2725,7 @@
             for (int i = 0; i < numChanges; i++) {
                 ChangeRec change = allChanges.get(i);
                 notifyOpChangedSync(change.op, change.uid, change.pkg,
-                        AppOpsManager.opToDefaultMode(change.op));
+                        AppOpsManager.opToDefaultMode(change.op), change.previous_mode);
             }
         }
     }
@@ -3390,7 +3413,19 @@
         verifyIncomingOp(code);
         String resolvedPackageName = resolvePackageName(uid, packageName);
         if (resolvedPackageName == null) {
-            return  AppOpsManager.MODE_IGNORED;
+            return AppOpsManager.MODE_IGNORED;
+        }
+
+        // As a special case for OP_RECORD_AUDIO_HOTWORD, which we use only for attribution
+        // purposes and not as a check, also make sure that the caller is allowed to access
+        // the data gated by OP_RECORD_AUDIO.
+        //
+        // TODO: Revert this change before Android 12.
+        if (code == OP_RECORD_AUDIO_HOTWORD) {
+            int result = checkOperation(OP_RECORD_AUDIO, uid, packageName);
+            if (result != AppOpsManager.MODE_ALLOWED) {
+                return result;
+            }
         }
 
         RestrictionBypass bypass;
diff --git a/services/core/java/com/android/server/audio/AudioDeviceBroker.java b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
index 45f95fd..074d3fe 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceBroker.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
@@ -224,12 +224,35 @@
             if (!addSpeakerphoneClient(cb, pid, on)) {
                 return false;
             }
+            if (on) {
+                // Cancel BT SCO ON request by this same client: speakerphone and BT SCO routes
+                // are mutually exclusive.
+                // See symmetrical operation for startBluetoothScoForClient_Sync().
+                mBtHelper.stopBluetoothScoForPid(pid);
+            }
             final boolean wasOn = isSpeakerphoneOn();
             updateSpeakerphoneOn(eventSource);
             return (wasOn != isSpeakerphoneOn());
         }
     }
 
+    /**
+     * Turns speakerphone off for a given pid and update speakerphone state.
+     * @param pid
+     */
+    @GuardedBy("mDeviceStateLock")
+    private void setSpeakerphoneOffForPid(int pid) {
+        SpeakerphoneClient client = getSpeakerphoneClientForPid(pid);
+        if (client == null) {
+            return;
+        }
+        client.unregisterDeathRecipient();
+        mSpeakerphoneClients.remove(client);
+        final String eventSource = new StringBuilder("setSpeakerphoneOffForPid(")
+                .append(pid).append(")").toString();
+        updateSpeakerphoneOn(eventSource);
+    }
+
     @GuardedBy("mDeviceStateLock")
     private void updateSpeakerphoneOn(String eventSource) {
         if (isSpeakerphoneOnRequested()) {
@@ -476,8 +499,8 @@
         sendIIMsgNoDelay(MSG_II_SET_HEARING_AID_VOLUME, SENDMSG_REPLACE, index, streamType);
     }
 
-    /*package*/ void postSetModeOwnerPid(int pid) {
-        sendIMsgNoDelay(MSG_I_SET_MODE_OWNER_PID, SENDMSG_REPLACE, pid);
+    /*package*/ void postSetModeOwnerPid(int pid, int mode) {
+        sendIIMsgNoDelay(MSG_I_SET_MODE_OWNER_PID, SENDMSG_REPLACE, pid, mode);
     }
 
     /*package*/ void postBluetoothA2dpDeviceConfigChange(@NonNull BluetoothDevice device) {
@@ -488,6 +511,10 @@
     /*package*/ void startBluetoothScoForClient_Sync(IBinder cb, int scoAudioMode,
                 @NonNull String eventSource) {
         synchronized (mDeviceStateLock) {
+            // Cancel speakerphone ON request by this same client: speakerphone and BT SCO routes
+            // are mutually exclusive.
+            // See symmetrical operation for setSpeakerphoneOn(true).
+            setSpeakerphoneOffForPid(Binder.getCallingPid());
             mBtHelper.startBluetoothScoForClient(cb, scoAudioMode, eventSource);
         }
     }
@@ -949,7 +976,9 @@
                         synchronized (mDeviceStateLock) {
                             if (mModeOwnerPid != msg.arg1) {
                                 mModeOwnerPid = msg.arg1;
-                                updateSpeakerphoneOn("setNewModeOwner");
+                                if (msg.arg2 != AudioSystem.MODE_RINGTONE) {
+                                    updateSpeakerphoneOn("setNewModeOwner");
+                                }
                                 if (mModeOwnerPid != 0) {
                                     mBtHelper.disconnectBluetoothSco(mModeOwnerPid);
                                 }
@@ -1379,6 +1408,16 @@
         return false;
     }
 
+    @GuardedBy("mDeviceStateLock")
+    private SpeakerphoneClient getSpeakerphoneClientForPid(int pid) {
+        for (SpeakerphoneClient cl : mSpeakerphoneClients) {
+            if (cl.getPid() == pid) {
+                return cl;
+            }
+        }
+        return null;
+    }
+
     // List of clients requesting speakerPhone ON
     @GuardedBy("mDeviceStateLock")
     private final @NonNull ArrayList<SpeakerphoneClient> mSpeakerphoneClients =
diff --git a/services/core/java/com/android/server/audio/AudioEventLogger.java b/services/core/java/com/android/server/audio/AudioEventLogger.java
index 9ebd75b..af0e978 100644
--- a/services/core/java/com/android/server/audio/AudioEventLogger.java
+++ b/services/core/java/com/android/server/audio/AudioEventLogger.java
@@ -60,7 +60,36 @@
          * @return the same instance of the event
          */
         public Event printLog(String tag) {
-            Log.i(tag, eventToString());
+            return printLog(ALOGI, tag);
+        }
+
+        public static final int ALOGI = 0;
+        public static final int ALOGE = 1;
+        public static final int ALOGW = 2;
+        public static final int ALOGV = 3;
+
+        /**
+         * Same as {@link #printLog(String)} with a log type
+         * @param type one of {@link #ALOGI}, {@link #ALOGE}, {@link #ALOGV}
+         * @param tag
+         * @return
+         */
+        public Event printLog(int type, String tag) {
+            switch (type) {
+                case ALOGI:
+                    Log.i(tag, eventToString());
+                    break;
+                case ALOGE:
+                    Log.e(tag, eventToString());
+                    break;
+                case ALOGW:
+                    Log.w(tag, eventToString());
+                    break;
+                case ALOGV:
+                default:
+                    Log.v(tag, eventToString());
+                    break;
+            }
             return this;
         }
 
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 2991339..f6d8987 100755
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -26,6 +26,10 @@
 import static android.provider.Settings.Secure.VOLUME_HUSH_OFF;
 import static android.provider.Settings.Secure.VOLUME_HUSH_VIBRATE;
 
+import static com.android.server.audio.AudioEventLogger.Event.ALOGE;
+import static com.android.server.audio.AudioEventLogger.Event.ALOGI;
+import static com.android.server.audio.AudioEventLogger.Event.ALOGW;
+
 import android.Manifest;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
@@ -284,6 +288,7 @@
     private static final int MSG_PLAYBACK_CONFIG_CHANGE = 29;
     private static final int MSG_BROADCAST_MICROPHONE_MUTE = 30;
     private static final int MSG_CHECK_MODE_FOR_UID = 31;
+    private static final int MSG_REINIT_VOLUMES = 32;
     // start of messages handled under wakelock
     //   these messages can only be queued, i.e. sent with queueMsgUnderWakeLock(),
     //   and not with sendMsg(..., ..., SENDMSG_QUEUE, ...)
@@ -673,6 +678,7 @@
 
     public AudioService(Context context, AudioSystemAdapter audioSystem,
             SystemServerAdapter systemServer) {
+        sLifecycleLogger.log(new AudioEventLogger.StringEvent("AudioService()"));
         mContext = context;
         mContentResolver = context.getContentResolver();
         mAppOps = (AppOpsManager)context.getSystemService(Context.APP_OPS_SERVICE);
@@ -892,6 +898,9 @@
                 mPrescaleAbsoluteVolume[i] = preScale[i];
             }
         }
+
+        // check on volume initialization
+        checkVolumeRangeInitialization("AudioService()");
     }
 
     public void systemReady() {
@@ -1019,11 +1028,15 @@
         if (!mSystemReady ||
                 (AudioSystem.checkAudioFlinger() != AudioSystem.AUDIO_STATUS_OK)) {
             Log.e(TAG, "Audioserver died.");
+            sLifecycleLogger.log(new AudioEventLogger.StringEvent(
+                    "onAudioServerDied() audioserver died"));
             sendMsg(mAudioHandler, MSG_AUDIO_SERVER_DIED, SENDMSG_NOOP, 0, 0,
                     null, 500);
             return;
         }
-        Log.e(TAG, "Audioserver started.");
+        Log.i(TAG, "Audioserver started.");
+        sLifecycleLogger.log(new AudioEventLogger.StringEvent(
+                "onAudioServerDied() audioserver started"));
 
         updateAudioHalPids();
 
@@ -1058,14 +1071,7 @@
         mDeviceBroker.setForceUse_Async(AudioSystem.FOR_SYSTEM, forSys, "onAudioServerDied");
 
         // Restore stream volumes
-        int numStreamTypes = AudioSystem.getNumStreamTypes();
-        for (int streamType = numStreamTypes - 1; streamType >= 0; streamType--) {
-            VolumeStreamState streamState = mStreamStates[streamType];
-            AudioSystem.initStreamVolume(
-                streamType, streamState.mIndexMin / 10, streamState.mIndexMax / 10);
-
-            streamState.applyAllVolumes();
-        }
+        onReinitVolumes("after audioserver restart");
 
         // Restore audio volume groups
         restoreVolumeGroups();
@@ -1163,6 +1169,72 @@
         setMicMuteFromSwitchInput();
     }
 
+    private void onReinitVolumes(@NonNull String caller) {
+        final int numStreamTypes = AudioSystem.getNumStreamTypes();
+        // keep track of any error during stream volume initialization
+        int status = AudioSystem.AUDIO_STATUS_OK;
+        for (int streamType = numStreamTypes - 1; streamType >= 0; streamType--) {
+            VolumeStreamState streamState = mStreamStates[streamType];
+            final int res = AudioSystem.initStreamVolume(
+                    streamType, streamState.mIndexMin / 10, streamState.mIndexMax / 10);
+            if (res != AudioSystem.AUDIO_STATUS_OK) {
+                status = res;
+                Log.e(TAG, "Failed to initStreamVolume (" + res + ") for stream " + streamType);
+                // stream volume initialization failed, no need to try the others, it will be
+                // attempted again when MSG_REINIT_VOLUMES is handled
+                break;
+            }
+            streamState.applyAllVolumes();
+        }
+
+        // did it work? check based on status
+        if (status != AudioSystem.AUDIO_STATUS_OK) {
+            sLifecycleLogger.log(new AudioEventLogger.StringEvent(
+                    caller + ": initStreamVolume failed with " + status + " will retry")
+                    .printLog(ALOGE, TAG));
+            sendMsg(mAudioHandler, MSG_REINIT_VOLUMES, SENDMSG_NOOP, 0, 0,
+                    caller /*obj*/, 2 * INDICATE_SYSTEM_READY_RETRY_DELAY_MS);
+            return;
+        }
+
+        // did it work? check based on min/max values of some basic streams
+        if (!checkVolumeRangeInitialization(caller)) {
+            return;
+        }
+
+        // success
+        sLifecycleLogger.log(new AudioEventLogger.StringEvent(
+                caller + ": initStreamVolume succeeded").printLog(ALOGI, TAG));
+    }
+
+    /**
+     * Check volume ranges were properly initialized
+     * @return true if volume ranges were successfully initialized
+     */
+    private boolean checkVolumeRangeInitialization(String caller) {
+        boolean success = true;
+        final int[] basicStreams = { AudioSystem.STREAM_ALARM, AudioSystem.STREAM_RING,
+                AudioSystem.STREAM_MUSIC, AudioSystem.STREAM_VOICE_CALL,
+                AudioSystem.STREAM_ACCESSIBILITY };
+        for (int streamType : basicStreams) {
+            final AudioAttributes aa = new AudioAttributes.Builder()
+                    .setInternalLegacyStreamType(streamType).build();
+            if (AudioSystem.getMaxVolumeIndexForAttributes(aa) < 0
+                    || AudioSystem.getMinVolumeIndexForAttributes(aa) < 0) {
+                success = false;
+                break;
+            }
+        }
+        if (!success) {
+            sLifecycleLogger.log(new AudioEventLogger.StringEvent(
+                    caller + ": initStreamVolume succeeded but invalid mix/max levels, will retry")
+                    .printLog(ALOGW, TAG));
+            sendMsg(mAudioHandler, MSG_REINIT_VOLUMES, SENDMSG_NOOP, 0, 0,
+                    caller /*obj*/, 2 * INDICATE_SYSTEM_READY_RETRY_DELAY_MS);
+        }
+        return success;
+    }
+
     private void onDispatchAudioServerStateChange(boolean state) {
         synchronized (mAudioServerStateListeners) {
             for (AsdProxy asdp : mAudioServerStateListeners.values()) {
@@ -3680,13 +3752,15 @@
         private final IBinder mCb; // To be notified of client's death
         private final int mPid;
         private final int mUid;
-        private String mPackage;
+        private final boolean mIsPrivileged;
+        private final String mPackage;
         private int mMode = AudioSystem.MODE_NORMAL; // Current mode set by this client
 
-        SetModeDeathHandler(IBinder cb, int pid, int uid, String caller) {
+        SetModeDeathHandler(IBinder cb, int pid, int uid, boolean isPrivileged, String caller) {
             mCb = cb;
             mPid = pid;
             mUid = uid;
+            mIsPrivileged = isPrivileged;
             mPackage = caller;
         }
 
@@ -3698,12 +3772,13 @@
                 if (index < 0) {
                     Log.w(TAG, "unregistered setMode() client died");
                 } else {
-                    newModeOwnerPid = setModeInt(AudioSystem.MODE_NORMAL, mCb, mPid, mUid, TAG);
+                    newModeOwnerPid = setModeInt(
+                            AudioSystem.MODE_NORMAL, mCb, mPid, mUid, mIsPrivileged, TAG);
                 }
             }
             // when entering RINGTONE, IN_CALL or IN_COMMUNICATION mode, clear all
             // SCO connections not started by the application changing the mode when pid changes
-            mDeviceBroker.postSetModeOwnerPid(newModeOwnerPid);
+            mDeviceBroker.postSetModeOwnerPid(newModeOwnerPid, AudioService.this.getMode());
         }
 
         public int getPid() {
@@ -3729,6 +3804,10 @@
         public String getPackage() {
             return mPackage;
         }
+
+        public boolean isPrivileged() {
+            return mIsPrivileged;
+        }
     }
 
     /** @see AudioManager#setMode(int) */
@@ -3780,18 +3859,19 @@
                         + " without permission or being mode owner");
                 return;
             }
-            newModeOwnerPid = setModeInt(
-                mode, cb, callingPid, Binder.getCallingUid(), callingPackage);
+            newModeOwnerPid = setModeInt(mode, cb, callingPid, Binder.getCallingUid(),
+                    hasModifyPhoneStatePermission, callingPackage);
         }
         // when entering RINGTONE, IN_CALL or IN_COMMUNICATION mode, clear all
         // SCO connections not started by the application changing the mode when pid changes
-        mDeviceBroker.postSetModeOwnerPid(newModeOwnerPid);
+        mDeviceBroker.postSetModeOwnerPid(newModeOwnerPid, getMode());
     }
 
     // setModeInt() returns a valid PID if the audio mode was successfully set to
     // any mode other than NORMAL.
     @GuardedBy("mDeviceBroker.mSetModeLock")
-    private int setModeInt(int mode, IBinder cb, int pid, int uid, String caller) {
+    private int setModeInt(
+            int mode, IBinder cb, int pid, int uid, boolean isPrivileged, String caller) {
         if (DEBUG_MODE) {
             Log.v(TAG, "setModeInt(mode=" + mode + ", pid=" + pid
                     + ", uid=" + uid + ", caller=" + caller + ")");
@@ -3843,7 +3923,7 @@
                 }
             } else {
                 if (hdlr == null) {
-                    hdlr = new SetModeDeathHandler(cb, pid, uid, caller);
+                    hdlr = new SetModeDeathHandler(cb, pid, uid, isPrivileged, caller);
                 }
                 // Register for client death notification
                 try {
@@ -3902,7 +3982,8 @@
             // change of mode may require volume to be re-applied on some devices
             updateAbsVolumeMultiModeDevices(oldMode, actualMode);
 
-            if (actualMode == AudioSystem.MODE_IN_COMMUNICATION) {
+            if (actualMode == AudioSystem.MODE_IN_COMMUNICATION
+                    && !hdlr.isPrivileged()) {
                 sendMsg(mAudioHandler,
                         MSG_CHECK_MODE_FOR_UID,
                         SENDMSG_QUEUE,
@@ -5570,7 +5651,15 @@
             mIndexMin = MIN_STREAM_VOLUME[streamType] * 10;
             mIndexMinNoPerm = mIndexMin; // may be overwritten later in updateNoPermMinIndex()
             mIndexMax = MAX_STREAM_VOLUME[streamType] * 10;
-            AudioSystem.initStreamVolume(streamType, mIndexMin / 10, mIndexMax / 10);
+            final int status = AudioSystem.initStreamVolume(
+                    streamType, mIndexMin / 10, mIndexMax / 10);
+            if (status != AudioSystem.AUDIO_STATUS_OK) {
+                sLifecycleLogger.log(new AudioEventLogger.StringEvent(
+                         "VSS() stream:" + streamType + " initStreamVolume=" + status)
+                        .printLog(ALOGE, TAG));
+                sendMsg(mAudioHandler, MSG_REINIT_VOLUMES, SENDMSG_NOOP, 0, 0,
+                        "VSS()" /*obj*/, 2 * INDICATE_SYSTEM_READY_RETRY_DELAY_MS);
+            }
 
             readSettings();
             mVolumeChanged = new Intent(AudioManager.VOLUME_CHANGED_ACTION);
@@ -6401,14 +6490,17 @@
                         if (msg.obj == null) {
                             break;
                         }
-                        // If the app corresponding to this mode death handler object is not
-                        // capturing or playing audio anymore after 3 seconds, remove it
-                        // from the stack. Otherwise, check again in 3 seconds.
+                        // If no other app is currently owning the audio mode and
+                        // the app corresponding to this mode death handler object is still in the
+                        // mode owner stack but not capturing or playing audio after 3 seconds,
+                        // remove it from the stack.
+                        // Otherwise, check again in 3 seconds.
                         SetModeDeathHandler h = (SetModeDeathHandler) msg.obj;
                         if (mSetModeDeathHandlers.indexOf(h) < 0) {
                             break;
                         }
-                        if (mRecordMonitor.isRecordingActiveForUid(h.getUid())
+                        if (getModeOwnerUid() != h.getUid()
+                                || mRecordMonitor.isRecordingActiveForUid(h.getUid())
                                 || mPlaybackMonitor.isPlaybackActiveForUid(h.getUid())) {
                             sendMsg(mAudioHandler,
                                     MSG_CHECK_MODE_FOR_UID,
@@ -6419,11 +6511,15 @@
                                     CHECK_MODE_FOR_UID_PERIOD_MS);
                             break;
                         }
-                        // For now just log the fact that an app is hogging the audio mode.
-                        // TODO(b/160260850): remove abusive app from audio mode stack.
+                        setModeInt(AudioSystem.MODE_NORMAL, h.getBinder(), h.getPid(), h.getUid(),
+                                h.isPrivileged(), "MSG_CHECK_MODE_FOR_UID");
                         mModeLogger.log(new PhoneStateEvent(h.getPackage(), h.getPid()));
                     }
                     break;
+
+                case MSG_REINIT_VOLUMES:
+                    onReinitVolumes((String) msg.obj);
+                    break;
             }
         }
     }
@@ -7354,12 +7450,16 @@
     //==========================================================================================
     // AudioService logging and dumpsys
     //==========================================================================================
+    static final int LOG_NB_EVENTS_LIFECYCLE = 20;
     static final int LOG_NB_EVENTS_PHONE_STATE = 20;
     static final int LOG_NB_EVENTS_DEVICE_CONNECTION = 30;
     static final int LOG_NB_EVENTS_FORCE_USE = 20;
     static final int LOG_NB_EVENTS_VOLUME = 40;
     static final int LOG_NB_EVENTS_DYN_POLICY = 10;
 
+    static final AudioEventLogger sLifecycleLogger = new AudioEventLogger(LOG_NB_EVENTS_LIFECYCLE,
+            "audio services lifecycle");
+
     final private AudioEventLogger mModeLogger = new AudioEventLogger(LOG_NB_EVENTS_PHONE_STATE,
             "phone state (logged after successful call to AudioSystem.setPhoneState(int, int))");
 
@@ -7436,6 +7536,7 @@
     protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
         if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return;
 
+        sLifecycleLogger.dump(pw);
         if (mAudioHandler != null) {
             pw.println("\nMessage handler (watch for unhandled messages):");
             mAudioHandler.dump(new PrintWriterPrinter(pw), "  ");
diff --git a/services/core/java/com/android/server/audio/BtHelper.java b/services/core/java/com/android/server/audio/BtHelper.java
index b4c41b2..7616557 100644
--- a/services/core/java/com/android/server/audio/BtHelper.java
+++ b/services/core/java/com/android/server/audio/BtHelper.java
@@ -432,19 +432,36 @@
         // and this must be done on behalf of system server to make sure permissions are granted.
         final long ident = Binder.clearCallingIdentity();
         if (client != null) {
-            AudioService.sDeviceLogger.log(new AudioEventLogger.StringEvent(eventSource));
-            client.requestScoState(BluetoothHeadset.STATE_AUDIO_DISCONNECTED,
-                    SCO_MODE_VIRTUAL_CALL);
-            // If a disconnection is pending, the client will be removed whne clearAllScoClients()
-            // is called form receiveBtEvent()
-            if (mScoAudioState != SCO_STATE_DEACTIVATE_REQ
-                    && mScoAudioState != SCO_STATE_DEACTIVATING) {
-                client.remove(false /*stop */, true /*unregister*/);
-            }
+            stopAndRemoveClient(client, eventSource);
         }
         Binder.restoreCallingIdentity(ident);
     }
 
+    // @GuardedBy("AudioDeviceBroker.mSetModeLock")
+    @GuardedBy("AudioDeviceBroker.mDeviceStateLock")
+    /*package*/ synchronized void stopBluetoothScoForPid(int pid) {
+        ScoClient client = getScoClientForPid(pid);
+        if (client == null) {
+            return;
+        }
+        final String eventSource = new StringBuilder("stopBluetoothScoForPid(")
+                .append(pid).append(")").toString();
+        stopAndRemoveClient(client, eventSource);
+    }
+
+    @GuardedBy("AudioDeviceBroker.mDeviceStateLock")
+    // @GuardedBy("BtHelper.this")
+    private void stopAndRemoveClient(ScoClient client, @NonNull String eventSource) {
+        AudioService.sDeviceLogger.log(new AudioEventLogger.StringEvent(eventSource));
+        client.requestScoState(BluetoothHeadset.STATE_AUDIO_DISCONNECTED,
+                SCO_MODE_VIRTUAL_CALL);
+        // If a disconnection is pending, the client will be removed when clearAllScoClients()
+        // is called form receiveBtEvent()
+        if (mScoAudioState != SCO_STATE_DEACTIVATE_REQ
+                && mScoAudioState != SCO_STATE_DEACTIVATING) {
+            client.remove(false /*stop */, true /*unregister*/);
+        }
+    }
 
     /*package*/ synchronized void setHearingAidVolume(int index, int streamType) {
         if (mHearingAid == null) {
@@ -974,6 +991,16 @@
         return null;
     }
 
+    @GuardedBy("BtHelper.this")
+    private ScoClient getScoClientForPid(int pid) {
+        for (ScoClient cl : mScoClients) {
+            if (cl.getPid() == pid) {
+                return cl;
+            }
+        }
+        return null;
+    }
+
     // @GuardedBy("AudioDeviceBroker.mSetModeLock")
     //@GuardedBy("AudioDeviceBroker.mDeviceStateLock")
     @GuardedBy("BtHelper.this")
diff --git a/services/core/java/com/android/server/biometrics/AuthService.java b/services/core/java/com/android/server/biometrics/AuthService.java
index 061972c..1312679 100644
--- a/services/core/java/com/android/server/biometrics/AuthService.java
+++ b/services/core/java/com/android/server/biometrics/AuthService.java
@@ -28,6 +28,7 @@
 import static android.hardware.biometrics.BiometricAuthenticator.TYPE_IRIS;
 import static android.hardware.biometrics.BiometricManager.Authenticators;
 
+import android.app.AppOpsManager;
 import android.content.Context;
 import android.content.pm.PackageManager;
 import android.hardware.biometrics.BiometricPrompt;
@@ -128,6 +129,11 @@
             return IIrisService.Stub.asInterface(
                     ServiceManager.getService(Context.IRIS_SERVICE));
         }
+
+        @VisibleForTesting
+        public AppOpsManager getAppOps(Context context) {
+            return context.getSystemService(AppOpsManager.class);
+        }
     }
 
     private final class AuthServiceImpl extends IAuthService.Stub {
@@ -138,6 +144,8 @@
 
             // Only allow internal clients to authenticate with a different userId.
             final int callingUserId = UserHandle.getCallingUserId();
+            final int callingUid = Binder.getCallingUid();
+            final int callingPid = Binder.getCallingPid();
             if (userId == callingUserId) {
                 checkPermission();
             } else {
@@ -146,6 +154,16 @@
                 checkInternalPermission();
             }
 
+            if (!checkAppOps(callingUid, opPackageName, "authenticate()")) {
+                Slog.e(TAG, "Denied by app ops: " + opPackageName);
+                return;
+            }
+
+            if (!Utils.isForeground(callingUid, callingPid)) {
+                Slog.e(TAG, "Caller is not foreground: " + opPackageName);
+                return;
+            }
+
             if (token == null || receiver == null || opPackageName == null || bundle == null) {
                 Slog.e(TAG, "Unable to authenticate, one or more null arguments");
                 return;
@@ -163,8 +181,6 @@
                 checkInternalPermission();
             }
 
-            final int callingUid = Binder.getCallingUid();
-            final int callingPid = Binder.getCallingPid();
             final long identity = Binder.clearCallingIdentity();
             try {
                 mBiometricService.authenticate(
@@ -392,4 +408,9 @@
                     "Must have USE_BIOMETRIC permission");
         }
     }
+
+    private boolean checkAppOps(int uid, String opPackageName, String reason) {
+        return mInjector.getAppOps(getContext()).noteOp(AppOpsManager.OP_USE_BIOMETRIC, uid,
+                opPackageName, null /* attributionTag */, reason) == AppOpsManager.MODE_ALLOWED;
+    }
 }
diff --git a/services/core/java/com/android/server/biometrics/AuthenticationClient.java b/services/core/java/com/android/server/biometrics/AuthenticationClient.java
index ef1b574..b4d74e0 100644
--- a/services/core/java/com/android/server/biometrics/AuthenticationClient.java
+++ b/services/core/java/com/android/server/biometrics/AuthenticationClient.java
@@ -16,16 +16,22 @@
 
 package com.android.server.biometrics;
 
+import android.app.ActivityManager;
+import android.app.ActivityTaskManager;
+import android.content.ComponentName;
 import android.content.Context;
+import android.content.pm.ApplicationInfo;
 import android.hardware.biometrics.BiometricAuthenticator;
 import android.hardware.biometrics.BiometricConstants;
 import android.hardware.biometrics.BiometricsProtoEnums;
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.security.KeyStore;
+import android.util.EventLog;
 import android.util.Slog;
 
 import java.util.ArrayList;
+import java.util.List;
 
 /**
  * A class to keep track of the authentication state for a given client.
@@ -148,7 +154,54 @@
                     + ", requireConfirmation: " + mRequireConfirmation
                     + ", user: " + getTargetUserId());
 
+            // Ensure authentication only succeeds if the client activity is on top or is keyguard.
+            boolean isBackgroundAuth = false;
+            if (authenticated && !Utils.isKeyguard(getContext(), getOwnerString())) {
+                try {
+                    final List<ActivityManager.RunningTaskInfo> tasks =
+                            ActivityTaskManager.getService().getTasks(1);
+                    if (tasks == null || tasks.isEmpty()) {
+                        Slog.e(TAG, "No running tasks reported");
+                        isBackgroundAuth = true;
+                    } else {
+                        final ComponentName topActivity = tasks.get(0).topActivity;
+                        if (topActivity == null) {
+                            Slog.e(TAG, "Unable to get top activity");
+                            isBackgroundAuth = true;
+                        } else {
+                            final String topPackage = topActivity.getPackageName();
+                            if (!topPackage.contentEquals(getOwnerString())) {
+                                Slog.e(TAG, "Background authentication detected, top: " + topPackage
+                                        + ", client: " + this);
+                                isBackgroundAuth = true;
+                            }
+                        }
+                    }
+                } catch (RemoteException e) {
+                    Slog.e(TAG, "Unable to get running tasks", e);
+                    isBackgroundAuth = true;
+                }
+            }
+
+            // Fail authentication if we can't confirm the client activity is on top.
+            if (isBackgroundAuth) {
+                Slog.e(TAG, "Failing possible background authentication");
+                authenticated = false;
+
+                // SafetyNet logging for exploitation attempts of b/159249069.
+                final ApplicationInfo appInfo = getContext().getApplicationInfo();
+                EventLog.writeEvent(0x534e4554, "159249069", appInfo != null ? appInfo.uid : -1,
+                        "Attempted background authentication");
+            }
+
             if (authenticated) {
+                // SafetyNet logging for b/159249069 if constraint is violated.
+                if (isBackgroundAuth) {
+                    final ApplicationInfo appInfo = getContext().getApplicationInfo();
+                    EventLog.writeEvent(0x534e4554, "159249069", appInfo != null ? appInfo.uid : -1,
+                            "Successful background authentication!");
+                }
+
                 mAlreadyDone = true;
 
                 if (listener != null) {
diff --git a/services/core/java/com/android/server/biometrics/BiometricServiceBase.java b/services/core/java/com/android/server/biometrics/BiometricServiceBase.java
index d29b5bd..f971ad1 100644
--- a/services/core/java/com/android/server/biometrics/BiometricServiceBase.java
+++ b/services/core/java/com/android/server/biometrics/BiometricServiceBase.java
@@ -724,10 +724,9 @@
         }
     }
 
-    protected void handleAuthenticated(BiometricAuthenticator.Identifier identifier,
-            ArrayList<Byte> token) {
+    protected void handleAuthenticated(boolean authenticated,
+            BiometricAuthenticator.Identifier identifier, ArrayList<Byte> token) {
         ClientMonitor client = mCurrentClient;
-        final boolean authenticated = identifier.getBiometricId() != 0;
 
         if (client != null && client.onAuthenticated(identifier, authenticated, token)) {
             removeClient(client);
@@ -1023,7 +1022,7 @@
             return false;
         }
 
-        if (requireForeground && !(isForegroundActivity(uid, pid) || isCurrentClient(
+        if (requireForeground && !(Utils.isForeground(uid, pid) || isCurrentClient(
                 opPackageName))) {
             Slog.w(getTag(), "Rejecting " + opPackageName + "; not in foreground");
             return false;
@@ -1046,29 +1045,6 @@
         return mKeyguardPackage.equals(clientPackage);
     }
 
-    private boolean isForegroundActivity(int uid, int pid) {
-        try {
-            final List<ActivityManager.RunningAppProcessInfo> procs =
-                    ActivityManager.getService().getRunningAppProcesses();
-            if (procs == null) {
-                Slog.e(getTag(), "Processes null, defaulting to true");
-                return true;
-            }
-
-            int N = procs.size();
-            for (int i = 0; i < N; i++) {
-                ActivityManager.RunningAppProcessInfo proc = procs.get(i);
-                if (proc.pid == pid && proc.uid == uid
-                        && proc.importance <= IMPORTANCE_FOREGROUND_SERVICE) {
-                    return true;
-                }
-            }
-        } catch (RemoteException e) {
-            Slog.w(getTag(), "am.getRunningAppProcesses() failed");
-        }
-        return false;
-    }
-
     /**
      * Calls the HAL to switch states to the new task. If there's already a current task,
      * it calls cancel() and sets mPendingClient to begin when the current task finishes
diff --git a/services/core/java/com/android/server/biometrics/Utils.java b/services/core/java/com/android/server/biometrics/Utils.java
index 14378da..c661f45 100644
--- a/services/core/java/com/android/server/biometrics/Utils.java
+++ b/services/core/java/com/android/server/biometrics/Utils.java
@@ -16,20 +16,38 @@
 
 package com.android.server.biometrics;
 
+import static android.Manifest.permission.USE_BIOMETRIC_INTERNAL;
+import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND_SERVICE;
 import static android.hardware.biometrics.BiometricManager.Authenticators;
 
+import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_BOOT;
+import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_DPM_LOCK_NOW;
+import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN;
+
+import android.annotation.NonNull;
+import android.app.ActivityManager;
+import android.content.ComponentName;
 import android.content.Context;
+import android.content.pm.PackageManager;
 import android.hardware.biometrics.BiometricConstants;
 import android.hardware.biometrics.BiometricManager;
 import android.hardware.biometrics.BiometricPrompt;
 import android.hardware.biometrics.BiometricPrompt.AuthenticationResultType;
 import android.os.Build;
 import android.os.Bundle;
+import android.os.RemoteException;
 import android.os.UserHandle;
 import android.provider.Settings;
 import android.util.Slog;
 
+import com.android.internal.R;
+import com.android.internal.widget.LockPatternUtils;
+
+import java.util.List;
+
 public class Utils {
+    private static final String TAG = "BiometricUtils";
+
     public static boolean isDebugEnabled(Context context, int targetUserId) {
         if (targetUserId == UserHandle.USER_NULL) {
             return false;
@@ -256,4 +274,50 @@
                 throw new IllegalArgumentException("Unsupported dismissal reason: " + reason);
         }
     }
+
+    public static boolean isForeground(int callingUid, int callingPid) {
+        try {
+            final List<ActivityManager.RunningAppProcessInfo> procs =
+                    ActivityManager.getService().getRunningAppProcesses();
+            if (procs == null) {
+                Slog.e(TAG, "No running app processes found, defaulting to true");
+                return true;
+            }
+
+            for (int i = 0; i < procs.size(); i++) {
+                ActivityManager.RunningAppProcessInfo proc = procs.get(i);
+                if (proc.pid == callingPid && proc.uid == callingUid
+                        && proc.importance <= IMPORTANCE_FOREGROUND_SERVICE) {
+                    return true;
+                }
+            }
+        } catch (RemoteException e) {
+            Slog.w(TAG, "am.getRunningAppProcesses() failed");
+        }
+        return false;
+    }
+
+    public static boolean isKeyguard(Context context, String clientPackage) {
+        final boolean hasPermission = context.checkCallingOrSelfPermission(USE_BIOMETRIC_INTERNAL)
+                == PackageManager.PERMISSION_GRANTED;
+
+        final ComponentName keyguardComponent = ComponentName.unflattenFromString(
+                context.getResources().getString(R.string.config_keyguardComponent));
+        final String keyguardPackage = keyguardComponent != null
+                ? keyguardComponent.getPackageName() : null;
+        return hasPermission && keyguardPackage != null && keyguardPackage.equals(clientPackage);
+    }
+
+    private static boolean containsFlag(int haystack, int needle) {
+        return (haystack & needle) != 0;
+    }
+
+    public static boolean isUserEncryptedOrLockdown(@NonNull LockPatternUtils lpu, int user) {
+        final int strongAuth = lpu.getStrongAuthForUser(user);
+        final boolean isEncrypted = containsFlag(strongAuth, STRONG_AUTH_REQUIRED_AFTER_BOOT);
+        final boolean isLockDown = containsFlag(strongAuth, STRONG_AUTH_REQUIRED_AFTER_DPM_LOCK_NOW)
+                || containsFlag(strongAuth, STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN);
+        Slog.d(TAG, "isEncrypted: " + isEncrypted + " isLockdown: " + isLockDown);
+        return isEncrypted || isLockDown;
+    }
 }
diff --git a/services/core/java/com/android/server/biometrics/face/FaceService.java b/services/core/java/com/android/server/biometrics/face/FaceService.java
index 72e1bbb..e5a1898 100644
--- a/services/core/java/com/android/server/biometrics/face/FaceService.java
+++ b/services/core/java/com/android/server/biometrics/face/FaceService.java
@@ -896,8 +896,9 @@
         public void onAuthenticated(final long deviceId, final int faceId, final int userId,
                 ArrayList<Byte> token) {
             mHandler.post(() -> {
-                Face face = new Face("", faceId, deviceId);
-                FaceService.super.handleAuthenticated(face, token);
+                final Face face = new Face("", faceId, deviceId);
+                final boolean authenticated = faceId != 0;
+                FaceService.super.handleAuthenticated(authenticated, face, token);
             });
         }
 
diff --git a/services/core/java/com/android/server/biometrics/fingerprint/FingerprintService.java b/services/core/java/com/android/server/biometrics/fingerprint/FingerprintService.java
index 6b7ba6a..a90fee6 100644
--- a/services/core/java/com/android/server/biometrics/fingerprint/FingerprintService.java
+++ b/services/core/java/com/android/server/biometrics/fingerprint/FingerprintService.java
@@ -56,6 +56,7 @@
 import android.os.SystemClock;
 import android.os.UserHandle;
 import android.os.UserManager;
+import android.util.EventLog;
 import android.util.Slog;
 import android.util.SparseBooleanArray;
 import android.util.SparseIntArray;
@@ -64,6 +65,7 @@
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.util.DumpUtils;
+import com.android.internal.widget.LockPatternUtils;
 import com.android.server.SystemServerInitThreadPool;
 import com.android.server.biometrics.AuthenticationClient;
 import com.android.server.biometrics.BiometricServiceBase;
@@ -72,6 +74,7 @@
 import com.android.server.biometrics.Constants;
 import com.android.server.biometrics.EnumerateClient;
 import com.android.server.biometrics.RemovalClient;
+import com.android.server.biometrics.Utils;
 
 import org.json.JSONArray;
 import org.json.JSONException;
@@ -124,6 +127,8 @@
     }
 
     private final class FingerprintAuthClient extends AuthenticationClientImpl {
+        private final boolean mDetectOnly;
+
         @Override
         protected boolean isFingerprint() {
             return true;
@@ -133,9 +138,10 @@
                 DaemonWrapper daemon, long halDeviceId, IBinder token,
                 ServiceListener listener, int targetUserId, int groupId, long opId,
                 boolean restricted, String owner, int cookie,
-                boolean requireConfirmation) {
+                boolean requireConfirmation, boolean detectOnly) {
             super(context, daemon, halDeviceId, token, listener, targetUserId, groupId, opId,
                     restricted, owner, cookie, requireConfirmation);
+            mDetectOnly = detectOnly;
         }
 
         @Override
@@ -177,6 +183,10 @@
 
             return super.handleFailedAttempt();
         }
+
+        boolean isDetectOnly() {
+            return mDetectOnly;
+        }
     }
 
     /**
@@ -234,18 +244,64 @@
         }
 
         @Override // Binder call
-        public void authenticate(final IBinder token, final long opId, final int groupId,
+        public void authenticate(final IBinder token, final long opId, final int userId,
                 final IFingerprintServiceReceiver receiver, final int flags,
                 final String opPackageName) {
-            updateActiveGroup(groupId, opPackageName);
+            // Keyguard check must be done on the caller's binder identity, since it also checks
+            // permission.
+            final boolean isKeyguard = Utils.isKeyguard(getContext(), opPackageName);
+
+            // Clear calling identity when checking LockPatternUtils for StrongAuth flags.
+            final long identity = Binder.clearCallingIdentity();
+            try {
+                if (isKeyguard && Utils.isUserEncryptedOrLockdown(mLockPatternUtils, userId)) {
+                    // If this happens, something in KeyguardUpdateMonitor is wrong.
+                    // SafetyNet for b/79776455
+                    EventLog.writeEvent(0x534e4554, "79776455");
+                    Slog.e(TAG, "Authenticate invoked when user is encrypted or lockdown");
+                    return;
+                }
+            } finally {
+                Binder.restoreCallingIdentity(identity);
+            }
+
+            updateActiveGroup(userId, opPackageName);
             final boolean restricted = isRestricted();
             final AuthenticationClientImpl client = new FingerprintAuthClient(getContext(),
                     mDaemonWrapper, mHalDeviceId, token, new ServiceListenerImpl(receiver),
-                    mCurrentUserId, groupId, opId, restricted, opPackageName,
-                    0 /* cookie */, false /* requireConfirmation */);
+                    mCurrentUserId, userId, opId, restricted, opPackageName,
+                    0 /* cookie */, false /* requireConfirmation */, false /* detectOnly */);
             authenticateInternal(client, opId, opPackageName);
         }
 
+        @Override
+        public void detectFingerprint(final IBinder token, final int userId,
+                final IFingerprintServiceReceiver receiver, final String opPackageName) {
+            checkPermission(USE_BIOMETRIC_INTERNAL);
+            if (!Utils.isKeyguard(getContext(), opPackageName)) {
+                Slog.w(TAG, "detectFingerprint called from non-sysui package: " + opPackageName);
+                return;
+            }
+
+            if (!Utils.isUserEncryptedOrLockdown(mLockPatternUtils, userId)) {
+                // If this happens, something in KeyguardUpdateMonitor is wrong. This should only
+                // ever be invoked when the user is encrypted or lockdown.
+                Slog.e(TAG, "detectFingerprint invoked when user is not encrypted or lockdown");
+                return;
+            }
+
+            Slog.d(TAG, "detectFingerprint, owner: " + opPackageName + ", user: " + userId);
+
+            updateActiveGroup(userId, opPackageName);
+            final boolean restricted = isRestricted();
+            final int operationId = 0;
+            final AuthenticationClientImpl client = new FingerprintAuthClient(getContext(),
+                    mDaemonWrapper, mHalDeviceId, token, new ServiceListenerImpl(receiver),
+                    mCurrentUserId, userId, operationId, restricted, opPackageName,
+                    0 /* cookie */, false /* requireConfirmation */, true /* detectOnly */);
+            authenticateInternal(client, operationId, opPackageName);
+        }
+
         @Override // Binder call
         public void prepareForAuthentication(IBinder token, long opId, int groupId,
                 IBiometricServiceReceiverInternal wrapperReceiver, String opPackageName,
@@ -257,7 +313,7 @@
                     mDaemonWrapper, mHalDeviceId, token,
                     new BiometricPromptServiceListenerImpl(wrapperReceiver),
                     mCurrentUserId, groupId, opId, restricted, opPackageName, cookie,
-                    false /* requireConfirmation */);
+                    false /* requireConfirmation */, false /* detectOnly */);
             authenticateInternal(client, opId, opPackageName, callingUid, callingPid,
                     callingUserId);
         }
@@ -275,6 +331,17 @@
         }
 
         @Override // Binder call
+        public void cancelFingerprintDetect(final IBinder token, final String opPackageName) {
+            checkPermission(USE_BIOMETRIC_INTERNAL);
+            if (!Utils.isKeyguard(getContext(), opPackageName)) {
+                Slog.w(TAG, "cancelFingerprintDetect called from non-sysui package: "
+                        + opPackageName);
+                return;
+            }
+            cancelAuthenticationInternal(token, opPackageName);
+        }
+
+        @Override // Binder call
         public void cancelAuthenticationFromService(final IBinder token, final String opPackageName,
                 int callingUid, int callingPid, int callingUserId, boolean fromClient) {
             checkPermission(MANAGE_BIOMETRIC);
@@ -518,7 +585,12 @@
                 BiometricAuthenticator.Identifier biometric, int userId)
                 throws RemoteException {
             if (mFingerprintServiceReceiver != null) {
-                if (biometric == null || biometric instanceof Fingerprint) {
+                final ClientMonitor client = getCurrentClient();
+                if (client instanceof FingerprintAuthClient
+                        && ((FingerprintAuthClient) client).isDetectOnly()) {
+                    mFingerprintServiceReceiver
+                            .onFingerprintDetected(deviceId, userId, isStrongBiometric());
+                } else if (biometric == null || biometric instanceof Fingerprint) {
                     mFingerprintServiceReceiver.onAuthenticationSucceeded(deviceId,
                             (Fingerprint) biometric, userId, isStrongBiometric());
                 } else {
@@ -575,6 +647,7 @@
     private final LockoutReceiver mLockoutReceiver = new LockoutReceiver();
     protected final ResetFailedAttemptsForUserRunnable mResetFailedAttemptsForCurrentUserRunnable =
             new ResetFailedAttemptsForUserRunnable();
+    private final LockPatternUtils mLockPatternUtils;
 
     /**
      * Receives callbacks from the HAL.
@@ -608,8 +681,17 @@
         public void onAuthenticated(final long deviceId, final int fingerId, final int groupId,
                 ArrayList<Byte> token) {
             mHandler.post(() -> {
-                Fingerprint fp = new Fingerprint("", groupId, fingerId, deviceId);
-                FingerprintService.super.handleAuthenticated(fp, token);
+                boolean authenticated = fingerId != 0;
+                final ClientMonitor client = getCurrentClient();
+                if (client instanceof FingerprintAuthClient) {
+                    if (((FingerprintAuthClient) client).isDetectOnly()) {
+                        Slog.w(TAG, "Detect-only. Device is encrypted or locked down");
+                        authenticated = true;
+                    }
+                }
+
+                final Fingerprint fp = new Fingerprint("", groupId, fingerId, deviceId);
+                FingerprintService.super.handleAuthenticated(authenticated, fp, token);
             });
         }
 
@@ -722,6 +804,7 @@
         mAlarmManager = context.getSystemService(AlarmManager.class);
         context.registerReceiver(mLockoutReceiver, new IntentFilter(getLockoutResetIntent()),
                 getLockoutBroadcastPermission(), null /* handler */);
+        mLockPatternUtils = new LockPatternUtils(context);
     }
 
     @Override
diff --git a/services/core/java/com/android/server/content/SyncManager.java b/services/core/java/com/android/server/content/SyncManager.java
index 6735da8..e007c75 100644
--- a/services/core/java/com/android/server/content/SyncManager.java
+++ b/services/core/java/com/android/server/content/SyncManager.java
@@ -210,6 +210,7 @@
     private static final String HANDLE_SYNC_ALARM_WAKE_LOCK = "SyncManagerHandleSyncAlarm";
     private static final String SYNC_LOOP_WAKE_LOCK = "SyncLoopWakeLock";
 
+    private static final boolean USE_WTF_FOR_ACCOUNT_ERROR = false;
 
     private static final int SYNC_OP_STATE_VALID = 0;
     // "1" used to include errors 3, 4 and 5 but now it's split up.
@@ -3446,7 +3447,7 @@
                 if (isLoggable) {
                     Slog.v(TAG, "    Dropping sync operation: account doesn't exist.");
                 }
-                Slog.wtf(TAG, "SYNC_OP_STATE_INVALID: account doesn't exist.");
+                logAccountError("SYNC_OP_STATE_INVALID: account doesn't exist.");
                 return SYNC_OP_STATE_INVALID_NO_ACCOUNT;
             }
             // Drop this sync request if it isn't syncable.
@@ -3456,14 +3457,14 @@
                     Slog.v(TAG, "    Dropping sync operation: "
                             + "isSyncable == SYNCABLE_NO_ACCOUNT_ACCESS");
                 }
-                Slog.wtf(TAG, "SYNC_OP_STATE_INVALID_NO_ACCOUNT_ACCESS");
+                logAccountError("SYNC_OP_STATE_INVALID_NO_ACCOUNT_ACCESS");
                 return SYNC_OP_STATE_INVALID_NO_ACCOUNT_ACCESS;
             }
             if (state == AuthorityInfo.NOT_SYNCABLE) {
                 if (isLoggable) {
                     Slog.v(TAG, "    Dropping sync operation: isSyncable == NOT_SYNCABLE");
                 }
-                Slog.wtf(TAG, "SYNC_OP_STATE_INVALID: NOT_SYNCABLE");
+                logAccountError("SYNC_OP_STATE_INVALID: NOT_SYNCABLE");
                 return SYNC_OP_STATE_INVALID_NOT_SYNCABLE;
             }
 
@@ -3482,12 +3483,20 @@
                 if (isLoggable) {
                     Slog.v(TAG, "    Dropping sync operation: disallowed by settings/network.");
                 }
-                Slog.wtf(TAG, "SYNC_OP_STATE_INVALID: disallowed by settings/network");
+                logAccountError("SYNC_OP_STATE_INVALID: disallowed by settings/network");
                 return SYNC_OP_STATE_INVALID_SYNC_DISABLED;
             }
             return SYNC_OP_STATE_VALID;
         }
 
+        private void logAccountError(String message) {
+            if (USE_WTF_FOR_ACCOUNT_ERROR) {
+                Slog.wtf(TAG, message);
+            } else {
+                Slog.e(TAG, message);
+            }
+        }
+
         private boolean dispatchSyncOperation(SyncOperation op) {
             if (Log.isLoggable(TAG, Log.VERBOSE)) {
                 Slog.v(TAG, "dispatchSyncOperation: we are going to sync " + op);
diff --git a/services/core/java/com/android/server/infra/AbstractMasterSystemService.java b/services/core/java/com/android/server/infra/AbstractMasterSystemService.java
index 6763585..4658d41 100644
--- a/services/core/java/com/android/server/infra/AbstractMasterSystemService.java
+++ b/services/core/java/com/android/server/infra/AbstractMasterSystemService.java
@@ -948,7 +948,7 @@
                         if (debug) {
                             Slog.d(mTag, "Eagerly recreating service for user " + userId);
                         }
-                        getServiceForUserLocked(userId);
+                        updateCachedServiceLocked(userId);
                     }
                 }
                 onServicePackageRestartedLocked(userId);
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index 2127c72..b6fbedb 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -1700,7 +1700,8 @@
 
         Intent intent = new Intent(ACTION_SHOW_INPUT_METHOD_PICKER)
                 .setPackage(mContext.getPackageName());
-        mImeSwitchPendingIntent = PendingIntent.getBroadcast(mContext, 0, intent, 0);
+        mImeSwitchPendingIntent = PendingIntent.getBroadcast(mContext, 0, intent,
+                PendingIntent.FLAG_IMMUTABLE);
 
         mShowOngoingImeSwitcherForPhones = false;
 
@@ -2528,7 +2529,8 @@
         mCurIntent.putExtra(Intent.EXTRA_CLIENT_LABEL,
                 com.android.internal.R.string.input_method_binding_label);
         mCurIntent.putExtra(Intent.EXTRA_CLIENT_INTENT, PendingIntent.getActivity(
-                mContext, 0, new Intent(Settings.ACTION_INPUT_METHOD_SETTINGS), 0));
+                mContext, 0, new Intent(Settings.ACTION_INPUT_METHOD_SETTINGS),
+                PendingIntent.FLAG_IMMUTABLE));
 
         if (bindCurrentInputMethodServiceLocked(mCurIntent, this, IME_CONNECTION_BIND_FLAGS)) {
             mLastBindTime = SystemClock.uptimeMillis();
@@ -3272,6 +3274,9 @@
 
     boolean hideCurrentInputLocked(IBinder windowToken, int flags, ResultReceiver resultReceiver,
             @SoftInputShowHideReason int reason) {
+        if (mCurClient == null || mCurClient.curSession == null) {
+            return false;
+        }
         if ((flags&InputMethodManager.HIDE_IMPLICIT_ONLY) != 0
                 && (mShowExplicitlyRequested || mShowForced)) {
             if (DEBUG) Slog.v(TAG, "Not hiding: explicit show not cancelled by non-explicit hide");
@@ -3456,7 +3461,12 @@
         // pre-rendering not supported on low-ram devices.
         cs.shouldPreRenderIme = DebugFlags.FLAG_PRE_RENDER_IME_VIEWS.value() && !mIsLowRam;
 
-        if (mCurFocusedWindow == windowToken) {
+        final boolean sameWindowFocused = mCurFocusedWindow == windowToken;
+        final boolean isTextEditor = (startInputFlags & StartInputFlags.IS_TEXT_EDITOR) != 0;
+        final boolean startInputByWinGainedFocus =
+                (startInputFlags & StartInputFlags.WINDOW_GAINED_FOCUS) != 0;
+
+        if (sameWindowFocused && isTextEditor) {
             if (DEBUG) {
                 Slog.w(TAG, "Window already focused, ignoring focus gain of: " + client
                         + " attribute=" + attribute + ", token = " + windowToken
@@ -3471,6 +3481,7 @@
                     InputBindResult.ResultCode.SUCCESS_REPORT_WINDOW_FOCUS_ONLY,
                     null, null, null, -1, null);
         }
+
         mCurFocusedWindow = windowToken;
         mCurFocusedWindowSoftInputMode = softInputMode;
         mCurFocusedWindowClient = cs;
@@ -3488,7 +3499,6 @@
                         == LayoutParams.SOFT_INPUT_ADJUST_RESIZE
                 || mRes.getConfiguration().isLayoutSizeAtLeast(
                         Configuration.SCREENLAYOUT_SIZE_LARGE);
-        final boolean isTextEditor = (startInputFlags & StartInputFlags.IS_TEXT_EDITOR) != 0;
 
         // We want to start input before showing the IME, but after closing
         // it.  We want to do this after closing it to help the IME disappear
@@ -3499,7 +3509,7 @@
         InputBindResult res = null;
         switch (softInputMode & LayoutParams.SOFT_INPUT_MASK_STATE) {
             case LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED:
-                if (!isTextEditor || !doAutoShow) {
+                if (!sameWindowFocused && (!isTextEditor || !doAutoShow)) {
                     if (LayoutParams.mayUseInputMethod(windowFlags)) {
                         // There is no focus view, and this window will
                         // be behind any soft input window, so hide the
@@ -3548,9 +3558,11 @@
                 }
                 break;
             case LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN:
-                if (DEBUG) Slog.v(TAG, "Window asks to hide input");
-                hideCurrentInputLocked(mCurFocusedWindow, 0, null,
-                        SoftInputShowHideReason.HIDE_ALWAYS_HIDDEN_STATE);
+                if (!sameWindowFocused) {
+                    if (DEBUG) Slog.v(TAG, "Window asks to hide input");
+                    hideCurrentInputLocked(mCurFocusedWindow, 0, null,
+                            SoftInputShowHideReason.HIDE_ALWAYS_HIDDEN_STATE);
+                }
                 break;
             case LayoutParams.SOFT_INPUT_STATE_VISIBLE:
                 if ((softInputMode & LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION) != 0) {
@@ -3575,13 +3587,15 @@
                 if (DEBUG) Slog.v(TAG, "Window asks to always show input");
                 if (InputMethodUtils.isSoftInputModeStateVisibleAllowed(
                         unverifiedTargetSdkVersion, startInputFlags)) {
-                    if (attribute != null) {
-                        res = startInputUncheckedLocked(cs, inputContext, missingMethods,
-                                attribute, startInputFlags, startInputReason);
-                        didStart = true;
+                    if (!sameWindowFocused) {
+                        if (attribute != null) {
+                            res = startInputUncheckedLocked(cs, inputContext, missingMethods,
+                                    attribute, startInputFlags, startInputReason);
+                            didStart = true;
+                        }
+                        showCurrentInputLocked(windowToken, InputMethodManager.SHOW_IMPLICIT, null,
+                                SoftInputShowHideReason.SHOW_STATE_ALWAYS_VISIBLE);
                     }
-                    showCurrentInputLocked(windowToken, InputMethodManager.SHOW_IMPLICIT, null,
-                            SoftInputShowHideReason.SHOW_STATE_ALWAYS_VISIBLE);
                 } else {
                     Slog.e(TAG, "SOFT_INPUT_STATE_ALWAYS_VISIBLE is ignored because"
                             + " there is no focused view that also returns true from"
@@ -3592,7 +3606,20 @@
 
         if (!didStart) {
             if (attribute != null) {
-                if (!DebugFlags.FLAG_OPTIMIZE_START_INPUT.value()
+                if (sameWindowFocused) {
+                    // On previous platforms, when Dialogs re-gained focus, the Activity behind
+                    // would briefly gain focus first, and dismiss the IME.
+                    // On R that behavior has been fixed, but unfortunately apps have come
+                    // to rely on this behavior to hide the IME when the editor no longer has focus
+                    // To maintain compatibility, we are now hiding the IME when we don't have
+                    // an editor upon refocusing a window.
+                    if (startInputByWinGainedFocus) {
+                        hideCurrentInputLocked(mCurFocusedWindow, 0, null,
+                                SoftInputShowHideReason.HIDE_SAME_WINDOW_FOCUSED_WITHOUT_EDITOR);
+                    }
+                    res = startInputUncheckedLocked(cs, inputContext, missingMethods, attribute,
+                            startInputFlags, startInputReason);
+                } else if (!DebugFlags.FLAG_OPTIMIZE_START_INPUT.value()
                         || (startInputFlags & StartInputFlags.IS_TEXT_EDITOR) != 0) {
                     res = startInputUncheckedLocked(cs, inputContext, missingMethods, attribute,
                             startInputFlags, startInputReason);
@@ -3606,6 +3633,10 @@
         return res;
     }
 
+    private boolean isImeVisible() {
+        return (mImeWindowVis & InputMethodService.IME_VISIBLE) != 0;
+    }
+
     private boolean canShowInputMethodPickerLocked(IInputMethodClient client) {
         // TODO(yukawa): multi-display support.
         final int uid = Binder.getCallingUid();
diff --git a/services/core/java/com/android/server/location/AppOpsHelper.java b/services/core/java/com/android/server/location/AppOpsHelper.java
index c598fb1..d0192cd 100644
--- a/services/core/java/com/android/server/location/AppOpsHelper.java
+++ b/services/core/java/com/android/server/location/AppOpsHelper.java
@@ -18,7 +18,9 @@
 
 import static android.app.AppOpsManager.OP_MONITOR_HIGH_POWER_LOCATION;
 import static android.app.AppOpsManager.OP_MONITOR_LOCATION;
+import static android.content.pm.PackageManager.PERMISSION_GRANTED;
 
+import static com.android.server.location.CallerIdentity.PERMISSION_NONE;
 import static com.android.server.location.LocationManagerService.D;
 import static com.android.server.location.LocationManagerService.TAG;
 
@@ -122,8 +124,18 @@
             Preconditions.checkState(mAppOps != null);
         }
 
+        if (callerIdentity.permissionLevel == PERMISSION_NONE) {
+            return false;
+        }
+
         long identity = Binder.clearCallingIdentity();
         try {
+            if (mContext.checkPermission(
+                    CallerIdentity.asPermission(callerIdentity.permissionLevel), callerIdentity.pid,
+                    callerIdentity.uid) != PERMISSION_GRANTED) {
+                return false;
+            }
+
             return mAppOps.checkOpNoThrow(
                     CallerIdentity.asAppOp(callerIdentity.permissionLevel),
                     callerIdentity.uid,
@@ -138,8 +150,24 @@
      * called right before a location is delivered, and if it returns false, the location should not
      * be delivered.
      */
-    public boolean noteLocationAccess(CallerIdentity identity) {
-        return noteOpNoThrow(CallerIdentity.asAppOp(identity.permissionLevel), identity);
+    public boolean noteLocationAccess(CallerIdentity callerIdentity) {
+        if (callerIdentity.permissionLevel == PERMISSION_NONE) {
+            return false;
+        }
+
+        long identity = Binder.clearCallingIdentity();
+        try {
+            if (mContext.checkPermission(
+                    CallerIdentity.asPermission(callerIdentity.permissionLevel), callerIdentity.pid,
+                    callerIdentity.uid) != PERMISSION_GRANTED) {
+                return false;
+            }
+        } finally {
+            Binder.restoreCallingIdentity(identity);
+        }
+
+        return noteOpNoThrow(CallerIdentity.asAppOp(callerIdentity.permissionLevel),
+                callerIdentity);
     }
 
     /**
diff --git a/services/core/java/com/android/server/location/LocationManagerService.java b/services/core/java/com/android/server/location/LocationManagerService.java
index bfcbe46..5c9350a 100644
--- a/services/core/java/com/android/server/location/LocationManagerService.java
+++ b/services/core/java/com/android/server/location/LocationManagerService.java
@@ -1586,6 +1586,7 @@
                 }
 
                 record.mRequest = locationRequest;
+                record.mReceiver.updateMonitoring(true);
                 requests.add(locationRequest);
                 if (!locationRequest.isLowPowerMode()) {
                     providerRequest.setLowPowerMode(false);
@@ -2063,10 +2064,12 @@
         }
     }
 
+    @Nullable
     @Override
-    public boolean getCurrentLocation(LocationRequest locationRequest,
-            ICancellationSignal remoteCancellationSignal, ILocationListener listener,
-            String packageName, String featureId, String listenerId) {
+    public ICancellationSignal getCurrentLocation(LocationRequest locationRequest,
+            ILocationListener listener, String packageName, String featureId, String listenerId) {
+        ICancellationSignal remoteCancellationSignal = CancellationSignal.createTransport();
+
         // side effect of validating locationRequest and packageName
         Location lastLocation = getLastLocation(locationRequest, packageName, featureId);
         if (lastLocation != null) {
@@ -2076,17 +2079,17 @@
             if (locationAgeMs < MAX_CURRENT_LOCATION_AGE_MS) {
                 try {
                     listener.onLocationChanged(lastLocation);
-                    return true;
+                    return remoteCancellationSignal;
                 } catch (RemoteException e) {
                     Log.w(TAG, e);
-                    return false;
+                    return null;
                 }
             }
 
             if (!mAppForegroundHelper.isAppForeground(Binder.getCallingUid())) {
                 if (locationAgeMs < mSettingsHelper.getBackgroundThrottleIntervalMs()) {
                     // not allowed to request new locations, so we can't return anything
-                    return false;
+                    return null;
                 }
             }
         }
@@ -2094,11 +2097,8 @@
         requestLocationUpdates(locationRequest, listener, null, packageName, featureId, listenerId);
         CancellationSignal cancellationSignal = CancellationSignal.fromTransport(
                 remoteCancellationSignal);
-        if (cancellationSignal != null) {
-            cancellationSignal.setOnCancelListener(
-                    () -> removeUpdates(listener, null));
-        }
-        return true;
+        cancellationSignal.setOnCancelListener(() -> removeUpdates(listener, null));
+        return remoteCancellationSignal;
     }
 
     @Override
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsService.java b/services/core/java/com/android/server/locksettings/LockSettingsService.java
index a45906d..b626fca 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsService.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsService.java
@@ -2625,7 +2625,6 @@
 
         final AuthenticationToken auth = mSpManager.newSyntheticPasswordAndSid(
                 getGateKeeperService(), credentialHash, credential, userId);
-        onAuthTokenKnownForUser(userId, auth);
         if (auth == null) {
             Slog.wtf(TAG, "initializeSyntheticPasswordLocked returns null auth token");
             return null;
@@ -2648,6 +2647,7 @@
         }
         fixateNewestUserKeyAuth(userId);
         setSyntheticPasswordHandleLocked(handle, userId);
+        onAuthTokenKnownForUser(userId, auth);
         return auth;
     }
 
diff --git a/services/core/java/com/android/server/media/BluetoothRouteProvider.java b/services/core/java/com/android/server/media/BluetoothRouteProvider.java
index 3a4dfaf..0b3cdae 100644
--- a/services/core/java/com/android/server/media/BluetoothRouteProvider.java
+++ b/services/core/java/com/android/server/media/BluetoothRouteProvider.java
@@ -34,6 +34,7 @@
 import android.media.AudioManager;
 import android.media.AudioSystem;
 import android.media.MediaRoute2Info;
+import android.os.UserHandle;
 import android.text.TextUtils;
 import android.util.Log;
 import android.util.Slog;
@@ -55,7 +56,6 @@
     private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
 
     private static final String HEARING_AID_ROUTE_ID_PREFIX = "HEARING_AID_";
-    private static BluetoothRouteProvider sInstance;
 
     @SuppressWarnings("WeakerAccess") /* synthetic access */
     // Maps hardware address to BluetoothRouteInfo
@@ -79,19 +79,21 @@
     private final BroadcastReceiver mBroadcastReceiver = new BluetoothBroadcastReceiver();
     private final BluetoothProfileListener mProfileListener = new BluetoothProfileListener();
 
-    static synchronized BluetoothRouteProvider getInstance(@NonNull Context context,
+    /**
+     * Create an instance of {@link BluetoothRouteProvider}.
+     * It may return {@code null} if Bluetooth is not supported on this hardware platform.
+     */
+    @Nullable
+    static BluetoothRouteProvider createInstance(@NonNull Context context,
             @NonNull BluetoothRoutesUpdatedListener listener) {
         Objects.requireNonNull(context);
         Objects.requireNonNull(listener);
 
-        if (sInstance == null) {
-            BluetoothAdapter btAdapter = BluetoothAdapter.getDefaultAdapter();
-            if (btAdapter == null) {
-                return null;
-            }
-            sInstance = new BluetoothRouteProvider(context, btAdapter, listener);
+        BluetoothAdapter btAdapter = BluetoothAdapter.getDefaultAdapter();
+        if (btAdapter == null) {
+            return null;
         }
-        return sInstance;
+        return new BluetoothRouteProvider(context, btAdapter, listener);
     }
 
     private BluetoothRouteProvider(Context context, BluetoothAdapter btAdapter,
@@ -103,7 +105,7 @@
         buildBluetoothRoutes();
     }
 
-    public void start() {
+    public void start(UserHandle user) {
         mBluetoothAdapter.getProfileProxy(mContext, mProfileListener, BluetoothProfile.A2DP);
         mBluetoothAdapter.getProfileProxy(mContext, mProfileListener, BluetoothProfile.HEARING_AID);
 
@@ -118,7 +120,8 @@
         addEventReceiver(BluetoothHearingAid.ACTION_CONNECTION_STATE_CHANGED,
                 deviceStateChangedReceiver);
 
-        mContext.registerReceiver(mBroadcastReceiver, mIntentFilter, null, null);
+        mContext.registerReceiverAsUser(mBroadcastReceiver, user,
+                mIntentFilter, null, null);
     }
 
     /**
diff --git a/services/core/java/com/android/server/media/MediaButtonReceiverHolder.java b/services/core/java/com/android/server/media/MediaButtonReceiverHolder.java
index 9dae1b4..6e655ea 100644
--- a/services/core/java/com/android/server/media/MediaButtonReceiverHolder.java
+++ b/services/core/java/com/android/server/media/MediaButtonReceiverHolder.java
@@ -259,7 +259,7 @@
             return "";
         }
         return String.join(COMPONENT_NAME_USER_ID_DELIM,
-                mComponentName.toString(),
+                mComponentName.flattenToString(),
                 String.valueOf(mUserId),
                 String.valueOf(mComponentType));
     }
diff --git a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
index 875bfdf..1114fe0 100644
--- a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
+++ b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
@@ -1176,7 +1176,8 @@
             super(Looper.getMainLooper(), null, true);
             mServiceRef = new WeakReference<>(service);
             mUserRecord = userRecord;
-            mSystemProvider = new SystemMediaRoute2Provider(service.mContext);
+            mSystemProvider = new SystemMediaRoute2Provider(service.mContext,
+                    UserHandle.of(userRecord.mUserId));
             mRouteProviders.add(mSystemProvider);
             mWatcher = new MediaRoute2ProviderWatcher(service.mContext, this,
                     this, mUserRecord.mUserId);
diff --git a/services/core/java/com/android/server/media/MediaSessionService.java b/services/core/java/com/android/server/media/MediaSessionService.java
index b02b900..87ef656 100644
--- a/services/core/java/com/android/server/media/MediaSessionService.java
+++ b/services/core/java/com/android/server/media/MediaSessionService.java
@@ -1937,7 +1937,8 @@
                 // Context#getPackageName() for getting package name that matches with the PID/UID,
                 // but it doesn't tell which package has created the MediaController, so useless.
                 return hasMediaControlPermission(controllerPid, controllerUid)
-                        || hasEnabledNotificationListener(userId, controllerPackageName);
+                        || hasEnabledNotificationListener(
+                                userId, controllerPackageName, controllerUid);
             } finally {
                 Binder.restoreCallingIdentity(token);
             }
@@ -2001,29 +2002,29 @@
             return resolvedUserId;
         }
 
-        private boolean hasEnabledNotificationListener(int resolvedUserId, String packageName)
-                throws RemoteException {
-            // You may not access another user's content as an enabled listener.
-            final int userId = UserHandle.getUserId(resolvedUserId);
-            if (resolvedUserId != userId) {
+        private boolean hasEnabledNotificationListener(int callingUserId,
+                String controllerPackageName, int controllerUid) throws RemoteException {
+            int controllerUserId = UserHandle.getUserHandleForUid(controllerUid).getIdentifier();
+            if (callingUserId != controllerUserId) {
+                // Enabled notification listener only works within the same user.
                 return false;
             }
 
             // TODO(jaewan): (Post-P) Propose NotificationManager#hasEnabledNotificationListener(
             //               String pkgName) to notification team for optimization
             final List<ComponentName> enabledNotificationListeners =
-                    mNotificationManager.getEnabledNotificationListeners(userId);
+                    mNotificationManager.getEnabledNotificationListeners(controllerUserId);
             if (enabledNotificationListeners != null) {
                 for (int i = 0; i < enabledNotificationListeners.size(); i++) {
-                    if (TextUtils.equals(packageName,
+                    if (TextUtils.equals(controllerPackageName,
                             enabledNotificationListeners.get(i).getPackageName())) {
                         return true;
                     }
                 }
             }
             if (DEBUG) {
-                Log.d(TAG, packageName + " (uid=" + resolvedUserId + ") doesn't have an enabled "
-                        + "notification listener");
+                Log.d(TAG, controllerPackageName + " (uid=" + controllerUid
+                        + ") doesn't have an enabled notification listener");
             }
             return false;
         }
diff --git a/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java b/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java
index 2c089ca..4f7af94 100644
--- a/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java
+++ b/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java
@@ -45,6 +45,7 @@
 import android.os.Looper;
 import android.os.RemoteException;
 import android.os.ServiceManager;
+import android.os.UserHandle;
 import android.text.TextUtils;
 import android.util.Log;
 import android.util.Slog;
@@ -99,7 +100,7 @@
         }
     };
 
-    SystemMediaRoute2Provider(Context context) {
+    SystemMediaRoute2Provider(Context context, UserHandle user) {
         super(sComponentName);
 
         mIsSystemRouteProvider = true;
@@ -117,7 +118,7 @@
         updateDeviceRoute(newAudioRoutes);
 
         // .getInstance returns null if there is no bt adapter available
-        mBtRouteProvider = BluetoothRouteProvider.getInstance(context, (routes) -> {
+        mBtRouteProvider = BluetoothRouteProvider.createInstance(context, (routes) -> {
             publishProviderState();
 
             boolean sessionInfoChanged;
@@ -130,11 +131,12 @@
 
         IntentFilter intentFilter = new IntentFilter(AudioManager.VOLUME_CHANGED_ACTION);
         intentFilter.addAction(AudioManager.STREAM_DEVICES_CHANGED_ACTION);
-        mContext.registerReceiver(new AudioManagerBroadcastReceiver(), intentFilter);
+        mContext.registerReceiverAsUser(new AudioManagerBroadcastReceiver(), user,
+                intentFilter, null, null);
 
         if (mBtRouteProvider != null) {
             mHandler.post(() -> {
-                mBtRouteProvider.start();
+                mBtRouteProvider.start(user);
                 notifyProviderState();
             });
         }
diff --git a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
index ffdcf7e..898037c 100644
--- a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
+++ b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
@@ -586,7 +586,7 @@
 
     private final NetworkPolicyLogger mLogger = new NetworkPolicyLogger();
 
-    /** List of apps indexed by appId and whether they have the internet permission */
+    /** List of apps indexed by uid and whether they have the internet permission */
     @GuardedBy("mUidRulesFirstLock")
     private final SparseBooleanArray mInternetPermissionMap = new SparseBooleanArray();
 
@@ -972,7 +972,7 @@
                 if (LOGV) Slog.v(TAG, "ACTION_PACKAGE_ADDED for uid=" + uid);
                 // Clear the cache for the app
                 synchronized (mUidRulesFirstLock) {
-                    mInternetPermissionMap.delete(UserHandle.getAppId(uid));
+                    mInternetPermissionMap.delete(uid);
                     updateRestrictionRulesForUidUL(uid);
                 }
             }
@@ -4194,16 +4194,14 @@
     @GuardedBy("mUidRulesFirstLock")
     private boolean hasInternetPermissionUL(int uid) {
         try {
-            final int appId = UserHandle.getAppId(uid);
-            final boolean hasPermission;
-            if (mInternetPermissionMap.indexOfKey(appId) < 0) {
-                hasPermission =
-                        mIPm.checkUidPermission(Manifest.permission.INTERNET, uid)
-                                == PackageManager.PERMISSION_GRANTED;
-                mInternetPermissionMap.put(appId, hasPermission);
-            } else {
-                hasPermission = mInternetPermissionMap.get(appId);
+            if (mInternetPermissionMap.get(uid)) {
+                return true;
             }
+            // If the cache shows that uid doesn't have internet permission,
+            // then always re-check with PackageManager just to be safe.
+            final boolean hasPermission = mIPm.checkUidPermission(Manifest.permission.INTERNET,
+                    uid) == PackageManager.PERMISSION_GRANTED;
+            mInternetPermissionMap.put(uid, hasPermission);
             return hasPermission;
         } catch (RemoteException e) {
         }
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 179118c..dcfd594 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -59,6 +59,7 @@
 import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_AWARE;
 import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_UNAWARE;
 import static android.content.pm.PackageManager.PERMISSION_GRANTED;
+import static android.media.AudioAttributes.FLAG_BYPASS_INTERRUPTION_POLICY;
 import static android.media.AudioAttributes.USAGE_NOTIFICATION_RINGTONE;
 import static android.os.IServiceManager.DUMP_FLAG_PRIORITY_CRITICAL;
 import static android.os.IServiceManager.DUMP_FLAG_PRIORITY_NORMAL;
@@ -318,7 +319,7 @@
     static final boolean DEBUG_INTERRUPTIVENESS = SystemProperties.getBoolean(
             "debug.notification.interruptiveness", false);
 
-    static final int MAX_PACKAGE_NOTIFICATIONS = 25;
+    static final int MAX_PACKAGE_NOTIFICATIONS = 50;
     static final float DEFAULT_MAX_NOTIFICATION_ENQUEUE_RATE = 5f;
 
     // message codes
@@ -1939,6 +1940,13 @@
             }
 
             @Override
+            void onConsolidatedPolicyChanged() {
+                Binder.withCleanCallingIdentity(() -> {
+                    mRankingHandler.requestSort();
+                });
+            }
+
+            @Override
             void onAutomaticRuleStatusChanged(int userId, String pkg, String id, int status) {
                 Binder.withCleanCallingIdentity(() -> {
                     Intent intent = new Intent(ACTION_AUTOMATIC_ZEN_RULE_STATUS_CHANGED);
@@ -3049,6 +3057,8 @@
                         UserHandle.getUserId(uid), REASON_PACKAGE_BANNED, null);
             }
 
+            mAppOps.setMode(AppOpsManager.OP_POST_NOTIFICATION, uid, pkg,
+                    enabled ? AppOpsManager.MODE_ALLOWED : AppOpsManager.MODE_IGNORED);
             try {
                 getContext().sendBroadcastAsUser(
                         new Intent(ACTION_APP_BLOCK_STATE_CHANGED)
@@ -6238,6 +6248,8 @@
                     for (NotificationRecord r : enqueued) {
                         if (r.mUpdateTimeMs > mWhen) {
                             // At least one enqueue was posted after the cancel, so we're invalid
+                            Slog.i(TAG, "notification cancel ignored due to newer enqueued entry"
+                                    + "key=" + r.getSbn().getKey());
                             return;
                         }
                     }
@@ -7093,8 +7105,15 @@
                     // so need to check the notification still valide for vibrate.
                     synchronized (mNotificationLock) {
                         if (mNotificationsByKey.get(record.getKey()) != null) {
+                            // Vibrator checks the appops for the op package, not the caller,
+                            // so we need to add the bypass dnd flag to be heard. it's ok to
+                            // always add this flag here because we've already checked that we can
+                            // bypass dnd
+                            AudioAttributes.Builder aab =
+                                    new AudioAttributes.Builder(record.getAudioAttributes())
+                                    .setFlags(FLAG_BYPASS_INTERRUPTION_POLICY);
                             mVibrator.vibrate(record.getSbn().getUid(), record.getSbn().getOpPkg(),
-                                    effect, "Notification (delayed)", record.getAudioAttributes());
+                                    effect, "Notification (delayed)", aab.build());
                         } else {
                             Slog.e(TAG, "No vibration for canceled notification : "
                                     + record.getKey());
@@ -7737,6 +7756,13 @@
     @VisibleForTesting
     void updateUriPermissions(@Nullable NotificationRecord newRecord,
             @Nullable NotificationRecord oldRecord, String targetPkg, int targetUserId) {
+        updateUriPermissions(newRecord, oldRecord, targetPkg, targetUserId, false);
+    }
+
+    @VisibleForTesting
+    void updateUriPermissions(@Nullable NotificationRecord newRecord,
+            @Nullable NotificationRecord oldRecord, String targetPkg, int targetUserId,
+            boolean onlyRevokeCurrentTarget) {
         final String key = (newRecord != null) ? newRecord.getKey() : oldRecord.getKey();
         if (DBG) Slog.d(TAG, key + ": updating permissions");
 
@@ -7764,7 +7790,9 @@
         }
 
         // If we have no Uris to grant, but an existing owner, go destroy it
-        if (newUris == null && permissionOwner != null) {
+        // When revoking permissions of a single listener, destroying the owner will revoke
+        // permissions of other listeners who need to keep access.
+        if (newUris == null && permissionOwner != null && !onlyRevokeCurrentTarget) {
             destroyPermissionOwner(permissionOwner, UserHandle.getUserId(oldRecord.getUid()), key);
             permissionOwner = null;
         }
@@ -7787,9 +7815,20 @@
                 final Uri uri = oldUris.valueAt(i);
                 if (newUris == null || !newUris.contains(uri)) {
                     if (DBG) Slog.d(TAG, key + ": revoking " + uri);
-                    int userId = ContentProvider.getUserIdFromUri(
-                            uri, UserHandle.getUserId(oldRecord.getUid()));
-                    revokeUriPermission(permissionOwner, uri, userId);
+                    if (onlyRevokeCurrentTarget) {
+                        // We're revoking permission from one listener only; other listeners may
+                        // still need access because the notification may still exist
+                        revokeUriPermission(permissionOwner, uri,
+                                UserHandle.getUserId(oldRecord.getUid()), targetPkg, targetUserId);
+                    } else {
+                        // This is broad to unilaterally revoke permissions to this Uri as granted
+                        // by this notification.  But this code-path can only be used when the
+                        // reason for revoking is that the notification posted again without this
+                        // Uri, not when removing an individual listener.
+                        revokeUriPermission(permissionOwner, uri,
+                                UserHandle.getUserId(oldRecord.getUid()),
+                                null, UserHandle.USER_ALL);
+                    }
                 }
             }
         }
@@ -7818,8 +7857,10 @@
         }
     }
 
-    private void revokeUriPermission(IBinder owner, Uri uri, int userId) {
+    private void revokeUriPermission(IBinder owner, Uri uri, int sourceUserId, String targetPkg,
+            int targetUserId) {
         if (uri == null || !ContentResolver.SCHEME_CONTENT.equals(uri.getScheme())) return;
+        int userId = ContentProvider.getUserIdFromUri(uri, sourceUserId);
 
         final long ident = Binder.clearCallingIdentity();
         try {
@@ -7827,7 +7868,7 @@
                     owner,
                     ContentProvider.getUriWithoutUserId(uri),
                     Intent.FLAG_GRANT_READ_URI_PERMISSION,
-                    userId);
+                    userId, targetPkg, targetUserId);
         } finally {
             Binder.restoreCallingIdentity(ident);
         }
@@ -9195,6 +9236,7 @@
             final NotificationRankingUpdate update;
             synchronized (mNotificationLock) {
                 update = makeRankingUpdateLocked(info);
+                updateUriPermissionsForActiveNotificationsLocked(info, true);
             }
             try {
                 listener.onListenerConnected(update);
@@ -9206,6 +9248,7 @@
         @Override
         @GuardedBy("mNotificationLock")
         protected void onServiceRemovedLocked(ManagedServiceInfo removed) {
+            updateUriPermissionsForActiveNotificationsLocked(removed, false);
             if (removeDisabledHints(removed)) {
                 updateListenerHintsLocked();
                 updateEffectsSuppressorLocked();
@@ -9272,8 +9315,7 @@
 
                 for (final ManagedServiceInfo info : getServices()) {
                     boolean sbnVisible = isVisibleToListener(sbn, info);
-                    boolean oldSbnVisible = oldSbn != null ? isVisibleToListener(oldSbn, info)
-                            : false;
+                    boolean oldSbnVisible = (oldSbn != null) && isVisibleToListener(oldSbn, info);
                     // This notification hasn't been and still isn't visible -> ignore.
                     if (!oldSbnVisible && !sbnVisible) {
                         continue;
@@ -9297,13 +9339,8 @@
                     // This notification became invisible -> remove the old one.
                     if (oldSbnVisible && !sbnVisible) {
                         final StatusBarNotification oldSbnLightClone = oldSbn.cloneLight();
-                        mHandler.post(new Runnable() {
-                            @Override
-                            public void run() {
-                                notifyRemoved(
-                                        info, oldSbnLightClone, update, null, REASON_USER_STOPPED);
-                            }
-                        });
+                        mHandler.post(() -> notifyRemoved(
+                                info, oldSbnLightClone, update, null, REASON_USER_STOPPED));
                         continue;
                     }
 
@@ -9313,12 +9350,7 @@
                     updateUriPermissions(r, old, info.component.getPackageName(), targetUserId);
 
                     final StatusBarNotification sbnToPost = trimCache.ForListener(info);
-                    mHandler.post(new Runnable() {
-                        @Override
-                        public void run() {
-                            notifyPosted(info, sbnToPost, update);
-                        }
-                    });
+                    mHandler.post(() -> notifyPosted(info, sbnToPost, update));
                 }
             } catch (Exception e) {
                 Slog.e(TAG, "Could not notify listeners for " + r.getKey(), e);
@@ -9326,6 +9358,46 @@
         }
 
         /**
+         * Synchronously grant or revoke permissions to Uris for all active and visible
+         * notifications to just the NotificationListenerService provided.
+         */
+        @GuardedBy("mNotificationLock")
+        private void updateUriPermissionsForActiveNotificationsLocked(
+                ManagedServiceInfo info, boolean grant) {
+            try {
+                for (final NotificationRecord r : mNotificationList) {
+                    // When granting permissions, ignore notifications which are invisible.
+                    // When revoking permissions, all notifications are invisible, so process all.
+                    if (grant && !isVisibleToListener(r.getSbn(), info)) {
+                        continue;
+                    }
+                    // If the notification is hidden, permissions are not required by the listener.
+                    if (r.isHidden() && info.targetSdkVersion < Build.VERSION_CODES.P) {
+                        continue;
+                    }
+                    // Grant or revoke access synchronously
+                    final int targetUserId = (info.userid == UserHandle.USER_ALL)
+                            ? UserHandle.USER_SYSTEM : info.userid;
+                    if (grant) {
+                        // Grant permissions by passing arguments as if the notification is new.
+                        updateUriPermissions(/* newRecord */ r, /* oldRecord */ null,
+                                info.component.getPackageName(), targetUserId);
+                    } else {
+                        // Revoke permissions by passing arguments as if the notification was
+                        // removed, but set `onlyRevokeCurrentTarget` to avoid revoking permissions
+                        // granted to *other* targets by this notification's URIs.
+                        updateUriPermissions(/* newRecord */ null, /* oldRecord */ r,
+                                info.component.getPackageName(), targetUserId,
+                                /* onlyRevokeCurrentTarget */ true);
+                    }
+                }
+            } catch (Exception e) {
+                Slog.e(TAG, "Could not " + (grant ? "grant" : "revoke") + " Uri permissions to "
+                        + info.component, e);
+            }
+        }
+
+        /**
          * asynchronously notify all listeners about a removed notification
          */
         @GuardedBy("mNotificationLock")
@@ -9360,18 +9432,11 @@
                 final NotificationStats stats = mAssistants.isServiceTokenValidLocked(info.service)
                         ? notificationStats : null;
                 final NotificationRankingUpdate update = makeRankingUpdateLocked(info);
-                mHandler.post(new Runnable() {
-                    @Override
-                    public void run() {
-                        notifyRemoved(info, sbnLight, update, stats, reason);
-                    }
-                });
+                mHandler.post(() -> notifyRemoved(info, sbnLight, update, stats, reason));
             }
 
             // Revoke access after all listeners have been updated
-            mHandler.post(() -> {
-                updateUriPermissions(null, r, null, UserHandle.USER_SYSTEM);
-            });
+            mHandler.post(() -> updateUriPermissions(null, r, null, UserHandle.USER_SYSTEM));
         }
 
         /**
diff --git a/services/core/java/com/android/server/notification/NotificationUsageStats.java b/services/core/java/com/android/server/notification/NotificationUsageStats.java
index b42fe92..9e91875 100644
--- a/services/core/java/com/android/server/notification/NotificationUsageStats.java
+++ b/services/core/java/com/android/server/notification/NotificationUsageStats.java
@@ -77,7 +77,6 @@
     private final Map<String, AggregatedStats> mStats = new HashMap<>();
     private final ArrayDeque<AggregatedStats[]> mStatsArrays = new ArrayDeque<>();
     private ArraySet<String> mStatExpiredkeys = new ArraySet<>();
-    private final SQLiteLog mSQLiteLog;
     private final Context mContext;
     private final Handler mHandler;
     private long mLastEmitTime;
@@ -85,7 +84,6 @@
     public NotificationUsageStats(Context context) {
         mContext = context;
         mLastEmitTime = SystemClock.elapsedRealtime();
-        mSQLiteLog = ENABLE_SQLITE_LOG ? new SQLiteLog(context) : null;
         mHandler = new Handler(mContext.getMainLooper()) {
             @Override
             public void handleMessage(Message msg) {
@@ -152,9 +150,6 @@
             stats.numUndecoratedRemoteViews += (notification.hasUndecoratedRemoteView() ? 1 : 0);
         }
         releaseAggregatedStatsLocked(aggregatedStatsArray);
-        if (ENABLE_SQLITE_LOG) {
-            mSQLiteLog.logPosted(notification);
-        }
     }
 
     /**
@@ -170,9 +165,6 @@
             stats.countApiUse(notification);
         }
         releaseAggregatedStatsLocked(aggregatedStatsArray);
-        if (ENABLE_SQLITE_LOG) {
-            mSQLiteLog.logPosted(notification);
-        }
     }
 
     /**
@@ -185,9 +177,6 @@
             stats.numRemovedByApp++;
         }
         releaseAggregatedStatsLocked(aggregatedStatsArray);
-        if (ENABLE_SQLITE_LOG) {
-            mSQLiteLog.logRemoved(notification);
-        }
     }
 
     /**
@@ -197,9 +186,6 @@
         MetricsLogger.histogram(mContext, "note_dismiss_longevity",
                 (int) (System.currentTimeMillis() - notification.getRankingTimeMs()) / (60 * 1000));
         notification.stats.onDismiss();
-        if (ENABLE_SQLITE_LOG) {
-            mSQLiteLog.logDismissed(notification);
-        }
     }
 
     /**
@@ -209,9 +195,6 @@
         MetricsLogger.histogram(mContext, "note_click_longevity",
                 (int) (System.currentTimeMillis() - notification.getRankingTimeMs()) / (60 * 1000));
         notification.stats.onClick();
-        if (ENABLE_SQLITE_LOG) {
-            mSQLiteLog.logClicked(notification);
-        }
     }
 
     public synchronized void registerPeopleAffinity(NotificationRecord notification, boolean valid,
@@ -328,21 +311,18 @@
                 // pass
             }
         }
-        if (ENABLE_SQLITE_LOG) {
-            try {
-                dump.put("historical", mSQLiteLog.dumpJson(filter));
-            } catch (JSONException e) {
-                // pass
-            }
-        }
         return dump;
     }
 
     public PulledStats remoteViewStats(long startMs, boolean aggregate) {
-        if (ENABLE_SQLITE_LOG) {
-            if (aggregate) {
-                return mSQLiteLog.remoteViewAggStats(startMs);
+        if (ENABLE_AGGREGATED_IN_MEMORY_STATS) {
+            PulledStats stats = new PulledStats(startMs);
+            for (AggregatedStats as : mStats.values()) {
+                if (as.numUndecoratedRemoteViews > 0) {
+                    stats.addUndecoratedPackage(as.key, as.mCreated);
+                }
             }
+            return stats;
         }
         return null;
     }
@@ -357,9 +337,6 @@
             pw.println(indent + "mStatsArrays.size(): " + mStatsArrays.size());
             pw.println(indent + "mStats.size(): " + mStats.size());
         }
-        if (ENABLE_SQLITE_LOG) {
-            mSQLiteLog.dump(pw, indent, filter);
-        }
     }
 
     public synchronized void emit() {
@@ -1046,353 +1023,4 @@
                     '}';
         }
     }
-
-    private static class SQLiteLog {
-        private static final String TAG = "NotificationSQLiteLog";
-
-        // Message types passed to the background handler.
-        private static final int MSG_POST = 1;
-        private static final int MSG_CLICK = 2;
-        private static final int MSG_REMOVE = 3;
-        private static final int MSG_DISMISS = 4;
-
-        private static final String DB_NAME = "notification_log.db";
-        private static final int DB_VERSION = 7;
-
-        /** Age in ms after which events are pruned from the DB. */
-        private static final long HORIZON_MS = 7 * 24 * 60 * 60 * 1000L;  // 1 week
-        /** Delay between pruning the DB. Used to throttle pruning. */
-        private static final long PRUNE_MIN_DELAY_MS = 6 * 60 * 60 * 1000L;  // 6 hours
-        /** Mininum number of writes between pruning the DB. Used to throttle pruning. */
-        private static final long PRUNE_MIN_WRITES = 1024;
-
-        // Table 'log'
-        private static final String TAB_LOG = "log";
-        private static final String COL_EVENT_USER_ID = "event_user_id";
-        private static final String COL_EVENT_TYPE = "event_type";
-        private static final String COL_EVENT_TIME = "event_time_ms";
-        private static final String COL_KEY = "key";
-        private static final String COL_PKG = "pkg";
-        private static final String COL_NOTIFICATION_ID = "nid";
-        private static final String COL_TAG = "tag";
-        private static final String COL_WHEN_MS = "when_ms";
-        private static final String COL_DEFAULTS = "defaults";
-        private static final String COL_FLAGS = "flags";
-        private static final String COL_IMPORTANCE_REQ = "importance_request";
-        private static final String COL_IMPORTANCE_FINAL = "importance_final";
-        private static final String COL_NOISY = "noisy";
-        private static final String COL_MUTED = "muted";
-        private static final String COL_DEMOTED = "demoted";
-        private static final String COL_CATEGORY = "category";
-        private static final String COL_ACTION_COUNT = "action_count";
-        private static final String COL_POSTTIME_MS = "posttime_ms";
-        private static final String COL_AIRTIME_MS = "airtime_ms";
-        private static final String COL_FIRST_EXPANSIONTIME_MS = "first_expansion_time_ms";
-        private static final String COL_AIRTIME_EXPANDED_MS = "expansion_airtime_ms";
-        private static final String COL_EXPAND_COUNT = "expansion_count";
-        private static final String COL_UNDECORATED = "undecorated";
-
-
-        private static final int EVENT_TYPE_POST = 1;
-        private static final int EVENT_TYPE_CLICK = 2;
-        private static final int EVENT_TYPE_REMOVE = 3;
-        private static final int EVENT_TYPE_DISMISS = 4;
-
-        private static final int IDLE_CONNECTION_TIMEOUT_MS = 30000;
-
-        private static long sLastPruneMs;
-
-        private static long sNumWrites;
-        private final SQLiteOpenHelper mHelper;
-
-        private final Handler mWriteHandler;
-        private static final long DAY_MS = 24 * 60 * 60 * 1000;
-        private static final String STATS_QUERY = "SELECT " +
-                COL_EVENT_USER_ID + ", " +
-                COL_PKG + ", " +
-                // Bucket by day by looking at 'floor((midnight - eventTimeMs) / dayMs)'
-                "CAST(((%d - " + COL_EVENT_TIME + ") / " + DAY_MS + ") AS int) " +
-                "AS day, " +
-                "COUNT(*) AS cnt, " +
-                "SUM(" + COL_MUTED + ") as muted, " +
-                "SUM(" + COL_NOISY + ") as noisy, " +
-                "SUM(" + COL_DEMOTED + ") as demoted, " +
-                "SUM(" + COL_UNDECORATED + ") as undecorated " +
-                "FROM " + TAB_LOG + " " +
-                "WHERE " +
-                COL_EVENT_TYPE + "=" + EVENT_TYPE_POST +
-                " AND " + COL_EVENT_TIME + " > %d " +
-                " GROUP BY " + COL_EVENT_USER_ID + ", day, " + COL_PKG;
-        private static final String UNDECORATED_QUERY = "SELECT " +
-                COL_PKG + ", " +
-                "MAX(" + COL_EVENT_TIME + ") as max_time " +
-                "FROM " + TAB_LOG + " " +
-                "WHERE " + COL_UNDECORATED + "> 0 " +
-                " AND " + COL_EVENT_TIME + " > %d " +
-                "GROUP BY " + COL_PKG;
-
-        public SQLiteLog(Context context) {
-            HandlerThread backgroundThread = new HandlerThread("notification-sqlite-log",
-                    android.os.Process.THREAD_PRIORITY_BACKGROUND);
-            backgroundThread.start();
-            mWriteHandler = new Handler(backgroundThread.getLooper()) {
-                @Override
-                public void handleMessage(Message msg) {
-                    NotificationRecord r = (NotificationRecord) msg.obj;
-                    long nowMs = System.currentTimeMillis();
-                    switch (msg.what) {
-                        case MSG_POST:
-                            writeEvent(r.getSbn().getPostTime(), EVENT_TYPE_POST, r);
-                            break;
-                        case MSG_CLICK:
-                            writeEvent(nowMs, EVENT_TYPE_CLICK, r);
-                            break;
-                        case MSG_REMOVE:
-                            writeEvent(nowMs, EVENT_TYPE_REMOVE, r);
-                            break;
-                        case MSG_DISMISS:
-                            writeEvent(nowMs, EVENT_TYPE_DISMISS, r);
-                            break;
-                        default:
-                            Log.wtf(TAG, "Unknown message type: " + msg.what);
-                            break;
-                    }
-                }
-            };
-            mHelper = new SQLiteOpenHelper(context, DB_NAME, null, DB_VERSION) {
-                @Override
-                public void onCreate(SQLiteDatabase db) {
-                    db.execSQL("CREATE TABLE " + TAB_LOG + " (" +
-                            "_id INTEGER PRIMARY KEY AUTOINCREMENT," +
-                            COL_EVENT_USER_ID + " INT," +
-                            COL_EVENT_TYPE + " INT," +
-                            COL_EVENT_TIME + " INT," +
-                            COL_KEY + " TEXT," +
-                            COL_PKG + " TEXT," +
-                            COL_NOTIFICATION_ID + " INT," +
-                            COL_TAG + " TEXT," +
-                            COL_WHEN_MS + " INT," +
-                            COL_DEFAULTS + " INT," +
-                            COL_FLAGS + " INT," +
-                            COL_IMPORTANCE_REQ + " INT," +
-                            COL_IMPORTANCE_FINAL + " INT," +
-                            COL_NOISY + " INT," +
-                            COL_MUTED + " INT," +
-                            COL_DEMOTED + " INT," +
-                            COL_CATEGORY + " TEXT," +
-                            COL_ACTION_COUNT + " INT," +
-                            COL_POSTTIME_MS + " INT," +
-                            COL_AIRTIME_MS + " INT," +
-                            COL_FIRST_EXPANSIONTIME_MS + " INT," +
-                            COL_AIRTIME_EXPANDED_MS + " INT," +
-                            COL_EXPAND_COUNT + " INT," +
-                            COL_UNDECORATED + " INT" +
-                            ")");
-                }
-
-                @Override
-                public void onConfigure(SQLiteDatabase db) {
-                    // Memory optimization - close idle connections after 30s of inactivity
-                    setIdleConnectionTimeout(IDLE_CONNECTION_TIMEOUT_MS);
-                }
-
-                @Override
-                public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
-                    if (oldVersion != newVersion) {
-                        db.execSQL("DROP TABLE IF EXISTS " + TAB_LOG);
-                        onCreate(db);
-                    }
-                }
-            };
-        }
-
-        public void logPosted(NotificationRecord notification) {
-            mWriteHandler.sendMessage(mWriteHandler.obtainMessage(MSG_POST, notification));
-        }
-
-        public void logClicked(NotificationRecord notification) {
-            mWriteHandler.sendMessage(mWriteHandler.obtainMessage(MSG_CLICK, notification));
-        }
-
-        public void logRemoved(NotificationRecord notification) {
-            mWriteHandler.sendMessage(mWriteHandler.obtainMessage(MSG_REMOVE, notification));
-        }
-
-        public void logDismissed(NotificationRecord notification) {
-            mWriteHandler.sendMessage(mWriteHandler.obtainMessage(MSG_DISMISS, notification));
-        }
-
-        private JSONArray jsonPostFrequencies(DumpFilter filter) throws JSONException {
-            JSONArray frequencies = new JSONArray();
-            SQLiteDatabase db = mHelper.getReadableDatabase();
-            long midnight = getMidnightMs();
-            String q = String.format(STATS_QUERY, midnight, filter.since);
-            Cursor cursor = db.rawQuery(q, null);
-            try {
-                for (cursor.moveToFirst(); !cursor.isAfterLast(); cursor.moveToNext()) {
-                    int userId = cursor.getInt(0);
-                    String pkg = cursor.getString(1);
-                    if (filter != null && !filter.matches(pkg)) continue;
-                    int day = cursor.getInt(2);
-                    int count = cursor.getInt(3);
-                    int muted = cursor.getInt(4);
-                    int noisy = cursor.getInt(5);
-                    int demoted = cursor.getInt(6);
-                    JSONObject row = new JSONObject();
-                    row.put("user_id", userId);
-                    row.put("package", pkg);
-                    row.put("day", day);
-                    row.put("count", count);
-                    row.put("noisy", noisy);
-                    row.put("muted", muted);
-                    row.put("demoted", demoted);
-                    frequencies.put(row);
-                }
-            } finally {
-                cursor.close();
-            }
-            return frequencies;
-        }
-
-        public void printPostFrequencies(PrintWriter pw, String indent, DumpFilter filter) {
-            SQLiteDatabase db = mHelper.getReadableDatabase();
-            long midnight = getMidnightMs();
-            String q = String.format(STATS_QUERY, midnight, filter.since);
-            Cursor cursor = db.rawQuery(q, null);
-            try {
-                for (cursor.moveToFirst(); !cursor.isAfterLast(); cursor.moveToNext()) {
-                    int userId = cursor.getInt(0);
-                    String pkg = cursor.getString(1);
-                    if (filter != null && !filter.matches(pkg)) continue;
-                    int day = cursor.getInt(2);
-                    int count = cursor.getInt(3);
-                    int muted = cursor.getInt(4);
-                    int noisy = cursor.getInt(5);
-                    int demoted = cursor.getInt(6);
-                    pw.println(indent + "post_frequency{user_id=" + userId + ",pkg=" + pkg +
-                            ",day=" + day + ",count=" + count + ",muted=" + muted + "/" + noisy +
-                            ",demoted=" + demoted + "}");
-                }
-            } finally {
-                cursor.close();
-            }
-        }
-
-        private long getMidnightMs() {
-            GregorianCalendar midnight = new GregorianCalendar();
-            midnight.set(midnight.get(Calendar.YEAR), midnight.get(Calendar.MONTH),
-                    midnight.get(Calendar.DATE), 23, 59, 59);
-            return midnight.getTimeInMillis();
-        }
-
-        private void writeEvent(long eventTimeMs, int eventType, NotificationRecord r) {
-            ContentValues cv = new ContentValues();
-            cv.put(COL_EVENT_USER_ID, r.getSbn().getUser().getIdentifier());
-            cv.put(COL_EVENT_TIME, eventTimeMs);
-            cv.put(COL_EVENT_TYPE, eventType);
-            putNotificationIdentifiers(r, cv);
-            if (eventType == EVENT_TYPE_POST) {
-                putNotificationDetails(r, cv);
-            } else {
-                putPosttimeVisibility(r, cv);
-            }
-            cv.put(COL_UNDECORATED, (r.hasUndecoratedRemoteView() ? 1 : 0));
-            SQLiteDatabase db = mHelper.getWritableDatabase();
-            if (db.insert(TAB_LOG, null, cv) < 0) {
-                Log.wtf(TAG, "Error while trying to insert values: " + cv);
-            }
-            sNumWrites++;
-            pruneIfNecessary(db);
-        }
-
-        private void pruneIfNecessary(SQLiteDatabase db) {
-            // Prune if we haven't in a while.
-            long nowMs = System.currentTimeMillis();
-            if (sNumWrites > PRUNE_MIN_WRITES ||
-                    nowMs - sLastPruneMs > PRUNE_MIN_DELAY_MS) {
-                sNumWrites = 0;
-                sLastPruneMs = nowMs;
-                long horizonStartMs = nowMs - HORIZON_MS;
-                try {
-                    int deletedRows = db.delete(TAB_LOG, COL_EVENT_TIME + " < ?",
-                            new String[]{String.valueOf(horizonStartMs)});
-                    Log.d(TAG, "Pruned event entries: " + deletedRows);
-                } catch (SQLiteFullException e) {
-                    Log.e(TAG, String.format("%s: %s", e.toString(), e.getMessage()));
-                }
-            }
-        }
-
-        private static void putNotificationIdentifiers(NotificationRecord r, ContentValues outCv) {
-            outCv.put(COL_KEY, r.getSbn().getKey());
-            outCv.put(COL_PKG, r.getSbn().getPackageName());
-        }
-
-        private static void putNotificationDetails(NotificationRecord r, ContentValues outCv) {
-            outCv.put(COL_NOTIFICATION_ID, r.getSbn().getId());
-            if (r.getSbn().getTag() != null) {
-                outCv.put(COL_TAG, r.getSbn().getTag());
-            }
-            outCv.put(COL_WHEN_MS, r.getSbn().getPostTime());
-            outCv.put(COL_FLAGS, r.getNotification().flags);
-            final int before = r.stats.requestedImportance;
-            final int after = r.getImportance();
-            final boolean noisy = r.stats.isNoisy;
-            outCv.put(COL_IMPORTANCE_REQ, before);
-            outCv.put(COL_IMPORTANCE_FINAL, after);
-            outCv.put(COL_DEMOTED, after < before ? 1 : 0);
-            outCv.put(COL_NOISY, noisy);
-            if (noisy && after < IMPORTANCE_HIGH) {
-                outCv.put(COL_MUTED, 1);
-            } else {
-                outCv.put(COL_MUTED, 0);
-            }
-            if (r.getNotification().category != null) {
-                outCv.put(COL_CATEGORY, r.getNotification().category);
-            }
-            outCv.put(COL_ACTION_COUNT, r.getNotification().actions != null ?
-                    r.getNotification().actions.length : 0);
-        }
-
-        private static void putPosttimeVisibility(NotificationRecord r, ContentValues outCv) {
-            outCv.put(COL_POSTTIME_MS, r.stats.getCurrentPosttimeMs());
-            outCv.put(COL_AIRTIME_MS, r.stats.getCurrentAirtimeMs());
-            outCv.put(COL_EXPAND_COUNT, r.stats.userExpansionCount);
-            outCv.put(COL_AIRTIME_EXPANDED_MS, r.stats.getCurrentAirtimeExpandedMs());
-            outCv.put(COL_FIRST_EXPANSIONTIME_MS, r.stats.posttimeToFirstVisibleExpansionMs);
-        }
-
-        public void dump(PrintWriter pw, String indent, DumpFilter filter) {
-            printPostFrequencies(pw, indent, filter);
-        }
-
-        public JSONObject dumpJson(DumpFilter filter) {
-            JSONObject dump = new JSONObject();
-            try {
-                dump.put("post_frequency", jsonPostFrequencies(filter));
-                dump.put("since", filter.since);
-                dump.put("now", System.currentTimeMillis());
-            } catch (JSONException e) {
-                // pass
-            }
-            return dump;
-        }
-
-        public PulledStats remoteViewAggStats(long startMs) {
-            PulledStats stats = new PulledStats(startMs);
-            SQLiteDatabase db = mHelper.getReadableDatabase();
-            String q = String.format(UNDECORATED_QUERY, startMs);
-            Cursor cursor = db.rawQuery(q, null);
-            try {
-                for (cursor.moveToFirst(); !cursor.isAfterLast(); cursor.moveToNext()) {
-                    String pkg = cursor.getString(0);
-                    long maxTimeMs = cursor.getLong(1);
-                    stats.addUndecoratedPackage(pkg, maxTimeMs);
-                }
-            } finally {
-                cursor.close();
-            }
-            return stats;
-        }
-    }
 }
diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
index 90d9834..33193df 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerSession.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java
@@ -267,6 +267,9 @@
     /** Uid of the creator of this session. */
     private final int mOriginalInstallerUid;
 
+    /** Package name of the app that created the installation session. */
+    private final String mOriginalInstallerPackageName;
+
     /** Uid of the owner of the installer session */
     @GuardedBy("mLock")
     private int mInstallerUid;
@@ -556,6 +559,7 @@
         mOriginalInstallerUid = installerUid;
         mInstallerUid = installerUid;
         mInstallSource = Objects.requireNonNull(installSource);
+        mOriginalInstallerPackageName = mInstallSource.installerPackageName;
         this.params = params;
         this.createdMillis = createdMillis;
         this.updatedMillis = createdMillis;
@@ -1682,11 +1686,6 @@
                 throw new IllegalArgumentException("Package is not valid", e);
             }
 
-            if (!mPackageName.equals(mInstallSource.installerPackageName)) {
-                throw new SecurityException("Can only transfer sessions that update the original "
-                        + "installer");
-            }
-
             mInstallerUid = newOwnerAppInfo.uid;
             mInstallSource = InstallSource.create(packageName, null, packageName);
         }
@@ -2173,6 +2172,15 @@
             }
         }
 
+        if (mInstallerUid != mOriginalInstallerUid) {
+            // Session has been transferred, check package name.
+            if (TextUtils.isEmpty(mPackageName) || !mPackageName.equals(
+                    mOriginalInstallerPackageName)) {
+                throw new PackageManagerException(PackageManager.INSTALL_FAILED_PACKAGE_CHANGED,
+                        "Can only transfer sessions that update the original installer");
+            }
+        }
+
         if (params.mode == SessionParams.MODE_FULL_INSTALL) {
             // Full installs must include a base package
             if (!stagedSplits.contains(null)) {
@@ -3198,6 +3206,7 @@
 
         pw.printPair("userId", userId);
         pw.printPair("mOriginalInstallerUid", mOriginalInstallerUid);
+        pw.printPair("mOriginalInstallerPackageName", mOriginalInstallerPackageName);
         pw.printPair("installerPackageName", mInstallSource.installerPackageName);
         pw.printPair("installInitiatingPackageName", mInstallSource.initiatingPackageName);
         pw.printPair("installOriginatingPackageName", mInstallSource.originatingPackageName);
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 3d63606..12ef5a1 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -2380,9 +2380,12 @@
         final int callingUserId = UserHandle.getUserId(callingUid);
 
         for (String packageName : packages) {
-            PackageSetting setting = mSettings.mPackages.get(packageName);
-            if (setting != null
-                    && !shouldFilterApplicationLocked(setting, callingUid, callingUserId)) {
+            final boolean filterApp;
+            synchronized (mLock) {
+                final PackageSetting ps = mSettings.getPackageLPr(packageName);
+                filterApp = shouldFilterApplicationLocked(ps, callingUid, callingUserId);
+            }
+            if (!filterApp) {
                 notifyInstallObserver(packageName);
             }
         }
@@ -2993,7 +2996,10 @@
             t.traceEnd();
 
             t.traceBegin("read user settings");
-            mFirstBoot = !mSettings.readLPw(mInjector.getUserManagerInternal().getUsers(false));
+            mFirstBoot = !mSettings.readLPw(mInjector.getUserManagerInternal().getUsers(
+                    /* excludePartial= */ true,
+                    /* excludeDying= */ false,
+                    /* excludePreCreated= */ false));
             t.traceEnd();
 
             // Clean up orphaned packages for which the code path doesn't exist
@@ -8933,10 +8939,10 @@
         if (providerInfo == null) {
             return null;
         }
-        if (!mSettings.isEnabledAndMatchLPr(providerInfo, flags, userId)) {
-            return null;
-        }
         synchronized (mLock) {
+            if (!mSettings.isEnabledAndMatchLPr(providerInfo, flags, userId)) {
+                return null;
+            }
             final PackageSetting ps = mSettings.mPackages.get(providerInfo.packageName);
             final ComponentName component =
                     new ComponentName(providerInfo.packageName, providerInfo.name);
@@ -9023,9 +9029,11 @@
             String targetPackage, int flags) {
         final int callingUid = Binder.getCallingUid();
         final int callingUserId = UserHandle.getUserId(callingUid);
-        final PackageSetting ps = mSettings.mPackages.get(targetPackage);
-        if (shouldFilterApplicationLocked(ps, callingUid, callingUserId)) {
-            return ParceledListSlice.emptyList();
+        synchronized (mLock) {
+            final PackageSetting ps = mSettings.getPackageLPr(targetPackage);
+            if (shouldFilterApplicationLocked(ps, callingUid, callingUserId)) {
+                return ParceledListSlice.emptyList();
+            }
         }
         return new ParceledListSlice<>(queryInstrumentationInternal(targetPackage, flags,
                 callingUserId));
@@ -14526,7 +14534,7 @@
         final PackageSetting ps;
         int appId = -1;
         long ceDataInode = -1;
-        synchronized (mSettings) {
+        synchronized (mLock) {
             ps = mSettings.getPackageLPr(packageName);
             if (ps != null) {
                 appId = ps.appId;
@@ -19388,9 +19396,11 @@
         mPermissionManager.enforceCrossUserPermission(callingUid, userId,
                 true /* requireFullPermission */, false /* checkShell */, "clear application data");
 
-        final PackageSetting ps = mSettings.getPackageLPr(packageName);
-        final boolean filterApp =
-                (ps != null && shouldFilterApplicationLocked(ps, callingUid, userId));
+        final boolean filterApp;
+        synchronized (mLock) {
+            final PackageSetting ps = mSettings.getPackageLPr(packageName);
+            filterApp = shouldFilterApplicationLocked(ps, callingUid, userId);
+        }
         if (!filterApp && mProtectedPackages.isPackageDataProtected(userId, packageName)) {
             throw new SecurityException("Cannot clear data for a protected package: "
                     + packageName);
@@ -19670,11 +19680,13 @@
         if (mContext.checkCallingOrSelfPermission(
                 android.Manifest.permission.SET_PREFERRED_APPLICATIONS)
                 != PackageManager.PERMISSION_GRANTED) {
-            if (getUidTargetSdkVersionLockedLPr(callingUid)
-                    < Build.VERSION_CODES.FROYO) {
-                Slog.w(TAG, "Ignoring addPreferredActivity() from uid "
-                        + callingUid);
-                return;
+            synchronized (mLock) {
+                if (getUidTargetSdkVersionLockedLPr(callingUid)
+                        < Build.VERSION_CODES.FROYO) {
+                    Slog.w(TAG, "Ignoring addPreferredActivity() from uid "
+                            + callingUid);
+                    return;
+                }
             }
             mContext.enforceCallingOrSelfPermission(
                     android.Manifest.permission.SET_PREFERRED_APPLICATIONS, null);
@@ -19846,8 +19858,9 @@
     /** This method takes a specific user id as well as UserHandle.USER_ALL. */
     private void clearPackagePreferredActivities(String packageName, int userId) {
         final SparseBooleanArray changedUsers = new SparseBooleanArray();
-
-        clearPackagePreferredActivitiesLPw(packageName, changedUsers, userId);
+        synchronized (mLock) {
+            clearPackagePreferredActivitiesLPw(packageName, changedUsers, userId);
+        }
         if (changedUsers.size() > 0) {
             updateDefaultHomeNotLocked(changedUsers);
             postPreferredActivityChangedBroadcast(userId);
@@ -19969,7 +19982,9 @@
         // writer
         try {
             final SparseBooleanArray changedUsers = new SparseBooleanArray();
-            clearPackagePreferredActivitiesLPw(null, changedUsers, userId);
+            synchronized (mLock) {
+                clearPackagePreferredActivitiesLPw(null, changedUsers, userId);
+            }
             if (changedUsers.size() > 0) {
                 postPreferredActivityChangedBroadcast(userId);
             }
@@ -20979,15 +20994,19 @@
         // Limit who can change which apps
         if (!UserHandle.isSameApp(callingUid, pkgSetting.appId)) {
             // Don't allow apps that don't have permission to modify other apps
-            if (!allowedByPermission
-                    || shouldFilterApplicationLocked(pkgSetting, callingUid, userId)) {
+            final boolean filterApp;
+            synchronized (mLock) {
+                filterApp = (!allowedByPermission
+                        || shouldFilterApplicationLocked(pkgSetting, callingUid, userId));
+            }
+            if (filterApp) {
                 throw new SecurityException(
                         "Attempt to change component state; "
-                        + "pid=" + Binder.getCallingPid()
-                        + ", uid=" + callingUid
-                        + (className == null
+                                + "pid=" + Binder.getCallingPid()
+                                + ", uid=" + callingUid
+                                + (className == null
                                 ? ", package=" + packageName
-                                : ", component=" + packageName + "/" + className));
+                                        : ", component=" + packageName + "/" + className));
             }
             // Don't allow changing protected packages.
             if (mProtectedPackages.isPackageStateProtected(userId, packageName)) {
@@ -25328,18 +25347,7 @@
         int mode = mInjector.getAppOpsManager().checkOpNoThrow(
                 AppOpsManager.OP_AUTO_REVOKE_PERMISSIONS_IF_UNUSED,
                 Binder.getCallingUid(), packageName);
-        if (mode == MODE_ALLOWED) {
-            return false;
-        } else if (mode == MODE_IGNORED) {
-            return true;
-        } else {
-            synchronized (mLock) {
-                boolean manifestWhitelisted =
-                        mPackages.get(packageName).getAutoRevokePermissions()
-                                == ApplicationInfo.AUTO_REVOKE_DISALLOWED;
-                return manifestWhitelisted;
-            }
-        }
+        return mode == MODE_IGNORED;
     }
 
     @Override
diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
index 39b3203..aca0797 100644
--- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
+++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
@@ -2313,7 +2313,8 @@
 
     private boolean isVendorApp(String pkg) {
         try {
-            final PackageInfo info = mInterface.getPackageInfo(pkg, 0, UserHandle.USER_SYSTEM);
+            final PackageInfo info = mInterface.getPackageInfo(
+                     pkg, PackageManager.MATCH_ANY_USER, UserHandle.USER_SYSTEM);
             return info != null && info.applicationInfo.isVendor();
         } catch (RemoteException e) {
             return false;
@@ -2322,7 +2323,8 @@
 
     private boolean isProductApp(String pkg) {
         try {
-            final PackageInfo info = mInterface.getPackageInfo(pkg, 0, UserHandle.USER_SYSTEM);
+            final PackageInfo info = mInterface.getPackageInfo(
+                    pkg, PackageManager.MATCH_ANY_USER, UserHandle.USER_SYSTEM);
             return info != null && info.applicationInfo.isProduct();
         } catch (RemoteException e) {
             return false;
@@ -2331,7 +2333,8 @@
 
     private boolean isSystemExtApp(String pkg) {
         try {
-            final PackageInfo info = mInterface.getPackageInfo(pkg, 0, UserHandle.USER_SYSTEM);
+            final PackageInfo info = mInterface.getPackageInfo(
+                    pkg, PackageManager.MATCH_ANY_USER, UserHandle.USER_SYSTEM);
             return info != null && info.applicationInfo.isSystemExt();
         } catch (RemoteException e) {
             return false;
diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java
index 0f5d978..a7164f9 100644
--- a/services/core/java/com/android/server/pm/Settings.java
+++ b/services/core/java/com/android/server/pm/Settings.java
@@ -1617,6 +1617,7 @@
                     return;
                 }
                 str = new FileInputStream(userPackagesStateFile);
+                if (DEBUG_MU) Log.i(TAG, "Reading " + userPackagesStateFile);
             }
             final XmlPullParser parser = Xml.newPullParser();
             parser.setInput(str, StandardCharsets.UTF_8.name());
@@ -2061,9 +2062,13 @@
 
             serializer.startTag(null, TAG_PACKAGE_RESTRICTIONS);
 
+            if (DEBUG_MU) Log.i(TAG, "Writing " + userPackagesStateFile);
             for (final PackageSetting pkg : mPackages.values()) {
                 final PackageUserState ustate = pkg.readUserState(userId);
-                if (DEBUG_MU) Log.i(TAG, "  pkg=" + pkg.name + ", state=" + ustate.enabled);
+                if (DEBUG_MU) {
+                    Log.i(TAG, "  pkg=" + pkg.name + ", installed=" + ustate.installed
+                            + ", state=" + ustate.enabled);
+                }
 
                 serializer.startTag(null, TAG_PACKAGE);
                 serializer.attribute(null, ATTR_NAME, pkg.name);
diff --git a/services/core/java/com/android/server/pm/StagingManager.java b/services/core/java/com/android/server/pm/StagingManager.java
index 0c96f59..b8fbb758 100644
--- a/services/core/java/com/android/server/pm/StagingManager.java
+++ b/services/core/java/com/android/server/pm/StagingManager.java
@@ -748,7 +748,6 @@
         PackageInstaller.SessionParams params = originalSession.params.copy();
         params.isStaged = false;
         params.installFlags |= PackageManager.INSTALL_STAGED;
-        // TODO(b/129744602): use the userid from the original session.
         if (preReboot) {
             params.installFlags &= ~PackageManager.INSTALL_ENABLE_ROLLBACK;
             params.installFlags |= PackageManager.INSTALL_DRY_RUN;
@@ -758,7 +757,7 @@
         try {
             int apkSessionId = mPi.createSession(
                     params, originalSession.getInstallerPackageName(),
-                    0 /* UserHandle.SYSTEM */);
+                    originalSession.userId);
             PackageInstallerSession apkSession = mPi.getSession(apkSessionId);
             apkSession.open();
             for (int i = 0, size = apkFilePaths.size(); i < size; i++) {
@@ -816,10 +815,9 @@
             if (preReboot) {
                 params.installFlags &= ~PackageManager.INSTALL_ENABLE_ROLLBACK;
             }
-            // TODO(b/129744602): use the userid from the original session.
             final int apkParentSessionId = mPi.createSession(
                     params, session.getInstallerPackageName(),
-                    0 /* UserHandle.SYSTEM */);
+                    session.userId);
             final PackageInstallerSession apkParentSession = mPi.getSession(apkParentSessionId);
             try {
                 apkParentSession.open();
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index 27924a6..f5d7d9e 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -114,7 +114,6 @@
 import com.android.server.am.UserState;
 import com.android.server.storage.DeviceStorageMonitorInternal;
 import com.android.server.utils.TimingsTraceAndSlog;
-import com.android.server.wm.ActivityTaskManagerInternal;
 
 import libcore.io.IoUtils;
 
@@ -134,6 +133,7 @@
 import java.io.PrintWriter;
 import java.nio.charset.StandardCharsets;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collections;
 import java.util.LinkedList;
 import java.util.List;
@@ -173,6 +173,7 @@
     private static final String ATTR_NEXT_SERIAL_NO = "nextSerialNumber";
     private static final String ATTR_PARTIAL = "partial";
     private static final String ATTR_PRE_CREATED = "preCreated";
+    private static final String ATTR_CONVERTED_FROM_PRE_CREATED = "convertedFromPreCreated";
     private static final String ATTR_GUEST_TO_REMOVE = "guestToRemove";
     private static final String ATTR_USER_VERSION = "version";
     private static final String ATTR_PROFILE_GROUP_ID = "profileGroupId";
@@ -406,6 +407,10 @@
 
     @GuardedBy("mUsersLock")
     private int[] mUserIds;
+
+    @GuardedBy("mUsersLock")
+    private int[] mUserIdsIncludingPreCreated;
+
     @GuardedBy("mPackagesLock")
     private int mNextSerialNumber;
     private int mUserVersion = 0;
@@ -2480,16 +2485,35 @@
     }
 
     /**
-     * Returns an array of user ids. This array is cached here for quick access, so do not modify or
-     * cache it elsewhere.
+     * Returns an array of user ids.
+     *
+     * <p>This array is cached here for quick access, so do not modify or cache it elsewhere.
+     *
      * @return the array of user ids.
      */
-    public int[] getUserIds() {
+    public @NonNull int[] getUserIds() {
         synchronized (mUsersLock) {
             return mUserIds;
         }
     }
 
+    /**
+     * Returns an array of user ids, including pre-created users.
+     *
+     * <p>This method should only used for the specific cases that need to handle pre-created users;
+     * most callers should call {@link #getUserIds()} instead.
+     *
+     * <p>This array is cached here for quick access, so do not modify or
+     * cache it elsewhere.
+     *
+     * @return the array of user ids.
+     */
+    public @NonNull int[] getUserIdsIncludingPreCreated() {
+        synchronized (mUsersLock) {
+            return mUserIdsIncludingPreCreated;
+        }
+    }
+
     @GuardedBy({"mRestrictionsLock", "mPackagesLock"})
     private void readUserListLP() {
         if (!mUserListFile.exists()) {
@@ -2873,6 +2897,9 @@
         if (userInfo.preCreated) {
             serializer.attribute(null, ATTR_PRE_CREATED, "true");
         }
+        if (userInfo.convertedFromPreCreated) {
+            serializer.attribute(null, ATTR_CONVERTED_FROM_PRE_CREATED, "true");
+        }
         if (userInfo.guestToRemove) {
             serializer.attribute(null, ATTR_GUEST_TO_REMOVE, "true");
         }
@@ -3030,6 +3057,7 @@
         int restrictedProfileParentId = UserInfo.NO_PROFILE_GROUP_ID;
         boolean partial = false;
         boolean preCreated = false;
+        boolean converted = false;
         boolean guestToRemove = false;
         boolean persistSeedData = false;
         String seedAccountName = null;
@@ -3081,6 +3109,10 @@
             if ("true".equals(valueString)) {
                 preCreated = true;
             }
+            valueString = parser.getAttributeValue(null, ATTR_CONVERTED_FROM_PRE_CREATED);
+            if ("true".equals(valueString)) {
+                converted = true;
+            }
             valueString = parser.getAttributeValue(null, ATTR_GUEST_TO_REMOVE);
             if ("true".equals(valueString)) {
                 guestToRemove = true;
@@ -3138,6 +3170,7 @@
         userInfo.lastLoggedInFingerprint = lastLoggedInFingerprint;
         userInfo.partial = partial;
         userInfo.preCreated = preCreated;
+        userInfo.convertedFromPreCreated = converted;
         userInfo.guestToRemove = guestToRemove;
         userInfo.profileGroupId = profileGroupId;
         userInfo.profileBadge = profileBadge;
@@ -3584,6 +3617,7 @@
         preCreatedUser.name = name;
         preCreatedUser.flags = newFlags;
         preCreatedUser.preCreated = false;
+        preCreatedUser.convertedFromPreCreated = true;
         preCreatedUser.creationTime = getCreationTime();
 
         synchronized (mPackagesLock) {
@@ -4327,23 +4361,43 @@
      */
     private void updateUserIds() {
         int num = 0;
+        int numIncludingPreCreated = 0;
         synchronized (mUsersLock) {
             final int userSize = mUsers.size();
             for (int i = 0; i < userSize; i++) {
-                UserInfo userInfo = mUsers.valueAt(i).info;
-                if (!userInfo.partial && !userInfo.preCreated) {
-                    num++;
+                final UserInfo userInfo = mUsers.valueAt(i).info;
+                if (!userInfo.partial) {
+                    numIncludingPreCreated++;
+                    if (!userInfo.preCreated) {
+                        num++;
+                    }
                 }
             }
+            if (DBG) {
+                Slog.d(LOG_TAG, "updateUserIds(): numberUsers= " + num
+                        + " includingPreCreated=" + numIncludingPreCreated);
+            }
             final int[] newUsers = new int[num];
+            final int[] newUsersIncludingPreCreated = new int[numIncludingPreCreated];
+
             int n = 0;
+            int nIncludingPreCreated = 0;
             for (int i = 0; i < userSize; i++) {
-                UserInfo userInfo = mUsers.valueAt(i).info;
-                if (!userInfo.partial && !userInfo.preCreated) {
-                    newUsers[n++] = mUsers.keyAt(i);
+                final UserInfo userInfo = mUsers.valueAt(i).info;
+                if (!userInfo.partial) {
+                    final int userId = mUsers.keyAt(i);
+                    newUsersIncludingPreCreated[nIncludingPreCreated++] = userId;
+                    if (!userInfo.preCreated) {
+                        newUsers[n++] = userId;
+                    }
                 }
             }
             mUserIds = newUsers;
+            mUserIdsIncludingPreCreated = newUsersIncludingPreCreated;
+            if (DBG) {
+                Slog.d(LOG_TAG, "updateUserIds(): userIds= " + Arrays.toString(mUserIds)
+                        + " includingPreCreated=" + Arrays.toString(mUserIdsIncludingPreCreated));
+            }
         }
     }
 
@@ -4626,6 +4680,7 @@
                             running ? " (running)" : "",
                             user.partial ? " (partial)" : "",
                             user.preCreated ? " (pre-created)" : "",
+                            user.convertedFromPreCreated ? " (converted)" : "",
                             current ? " (current)" : "");
                 } else {
                     // NOTE: the standard "list users" command is used by integration tests and
@@ -4710,6 +4765,9 @@
                     if (userInfo.preCreated) {
                         pw.print(" <pre-created>");
                     }
+                    if (userInfo.convertedFromPreCreated) {
+                        pw.print(" <converted>");
+                    }
                     pw.println();
                     pw.print("    Type: "); pw.println(userInfo.userType);
                     pw.print("    Flags: "); pw.print(userInfo.flags); pw.print(" (");
@@ -4791,6 +4849,13 @@
             synchronized (mUserStates) {
                 pw.println("  Started users state: " + mUserStates);
             }
+            synchronized (mUsersLock) {
+                pw.print("  Cached user IDs: ");
+                pw.println(Arrays.toString(mUserIds));
+                pw.print("  Cached user IDs (including pre-created): ");
+                pw.println(Arrays.toString(mUserIdsIncludingPreCreated));
+            }
+
         } // synchronized (mPackagesLock)
 
         // Dump some capabilities
@@ -5052,8 +5117,14 @@
 
         @Override
         public @NonNull List<UserInfo> getUsers(boolean excludeDying) {
-            return UserManagerService.this.getUsersInternal(/*excludePartial= */ true,
-                    excludeDying, /* excludePreCreated= */ true);
+            return getUsers(/*excludePartial= */ true, excludeDying, /* excludePreCreated= */ true);
+        }
+
+        @Override
+        public @NonNull List<UserInfo> getUsers(boolean excludePartial, boolean excludeDying,
+                boolean excludePreCreated) {
+            return UserManagerService.this.getUsersInternal(excludePartial, excludeDying,
+                    excludePreCreated);
         }
 
         @Override
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
index 9963cf7..66d8b59 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
@@ -2516,12 +2516,12 @@
 
         final PermissionsState permissionsState = ps.getPermissionsState();
 
-        final int[] currentUserIds = UserManagerService.getInstance().getUserIds();
+        final int[] userIds = getAllUserIds();
 
         boolean runtimePermissionsRevoked = false;
         int[] updatedUserIds = EMPTY_INT_ARRAY;
 
-        for (int userId : currentUserIds) {
+        for (int userId : userIds) {
             if (permissionsState.isMissing(userId)) {
                 Collection<String> requestedPermissions;
                 int targetSdkVersion;
@@ -2587,7 +2587,7 @@
                 // runtime and revocation of a runtime from a shared user.
                 synchronized (mLock) {
                     updatedUserIds = revokeUnusedSharedUserPermissionsLocked(
-                            ps.getSharedUser(), UserManagerService.getInstance().getUserIds());
+                            ps.getSharedUser(), userIds);
                     if (!ArrayUtils.isEmpty(updatedUserIds)) {
                         runtimePermissionsRevoked = true;
                     }
@@ -2603,13 +2603,13 @@
             final int N = pkg.getRequestedPermissions().size();
             for (int i = 0; i < N; i++) {
                 final String permName = pkg.getRequestedPermissions().get(i);
+                final String friendlyName = pkg.getPackageName() + "(" + pkg.getUid() + ")";
                 final BasePermission bp = mSettings.getPermissionLocked(permName);
                 final boolean appSupportsRuntimePermissions =
                         pkg.getTargetSdkVersion() >= Build.VERSION_CODES.M;
                 String upgradedActivityRecognitionPermission = null;
-
                 if (DEBUG_INSTALL && bp != null) {
-                    Log.i(TAG, "Package " + pkg.getPackageName()
+                    Log.i(TAG, "Package " + friendlyName
                             + " checking " + permName + ": " + bp);
                 }
 
@@ -2618,7 +2618,7 @@
                             pkg.getPackageName())) {
                         if (DEBUG_PERMISSIONS) {
                             Slog.i(TAG, "Unknown permission " + permName
-                                    + " in package " + pkg.getPackageName());
+                                    + " in package " + friendlyName);
                         }
                     }
                     continue;
@@ -2634,7 +2634,7 @@
                         newImplicitPermissions.add(permName);
 
                         if (DEBUG_PERMISSIONS) {
-                            Slog.i(TAG, permName + " is newly added for " + pkg.getPackageName());
+                            Slog.i(TAG, permName + " is newly added for " + friendlyName);
                         }
                     } else {
                         // Special case for Activity Recognition permission. Even if AR permission
@@ -2656,8 +2656,7 @@
                                 newImplicitPermissions.add(permName);
 
                                 if (DEBUG_PERMISSIONS) {
-                                    Slog.i(TAG, permName + " is newly added for "
-                                            + pkg.getPackageName());
+                                    Slog.i(TAG, permName + " is newly added for " + friendlyName);
                                 }
                                 break;
                             }
@@ -2671,7 +2670,7 @@
 //                if (/*pkg.isInstantApp()*/ false && !bp.isInstant()) {
 //                    if (DEBUG_PERMISSIONS) {
 //                        Log.i(TAG, "Denying non-ephemeral permission " + bp.getName()
-//                                + " for package " + pkg.getPackageName());
+//                                + " for package " + friendlyName);
 //                    }
 //                    continue;
 //                }
@@ -2679,7 +2678,7 @@
                 if (bp.isRuntimeOnly() && !appSupportsRuntimePermissions) {
                     if (DEBUG_PERMISSIONS) {
                         Log.i(TAG, "Denying runtime-only permission " + bp.getName()
-                                + " for package " + pkg.getPackageName());
+                                + " for package " + friendlyName);
                     }
                     continue;
                 }
@@ -2716,7 +2715,7 @@
 
                 if (DEBUG_PERMISSIONS) {
                     Slog.i(TAG, "Considering granting permission " + perm + " to package "
-                            + pkg.getPackageName());
+                            + friendlyName);
                 }
 
                 if (grant != GRANT_DENIED) {
@@ -2740,7 +2739,7 @@
                             // a runtime permission being downgraded to an install one.
                             // Also in permission review mode we keep dangerous permissions
                             // for legacy apps
-                            for (int userId : UserManagerService.getInstance().getUserIds()) {
+                            for (int userId : userIds) {
                                 if (origPermissions.getRuntimePermissionState(
                                         perm, userId) != null) {
                                     // Revoke the runtime permission and clear the flags.
@@ -2763,7 +2762,7 @@
                             boolean hardRestricted = bp.isHardRestricted();
                             boolean softRestricted = bp.isSoftRestricted();
 
-                            for (int userId : currentUserIds) {
+                            for (int userId : userIds) {
                                 // If permission policy is not ready we don't deal with restricted
                                 // permissions as the policy may whitelist some permissions. Once
                                 // the policy is initialized we would re-evaluate permissions.
@@ -2902,7 +2901,7 @@
                             boolean hardRestricted = bp.isHardRestricted();
                             boolean softRestricted = bp.isSoftRestricted();
 
-                            for (int userId : currentUserIds) {
+                            for (int userId : userIds) {
                                 // If permission policy is not ready we don't deal with restricted
                                 // permissions as the policy may whitelist some permissions. Once
                                 // the policy is initialized we would re-evaluate permissions.
@@ -3005,7 +3004,7 @@
                                     || packageOfInterest.equals(pkg.getPackageName())) {
                                 if (DEBUG_PERMISSIONS) {
                                     Slog.i(TAG, "Not granting permission " + perm
-                                            + " to package " + pkg.getPackageName()
+                                            + " to package " + friendlyName
                                             + " because it was previously installed without");
                                 }
                             }
@@ -3020,7 +3019,7 @@
                         changedInstallPermission = true;
                         if (DEBUG_PERMISSIONS) {
                             Slog.i(TAG, "Un-granting permission " + perm
-                                    + " from package " + pkg.getPackageName()
+                                    + " from package " + friendlyName
                                     + " (protectionLevel=" + bp.getProtectionLevel()
                                     + " flags=0x"
                                     + Integer.toHexString(PackageInfoUtils.appInfoFlags(pkg, ps))
@@ -3033,7 +3032,7 @@
                                 && (packageOfInterest == null
                                         || packageOfInterest.equals(pkg.getPackageName()))) {
                             Slog.i(TAG, "Not granting permission " + perm
-                                    + " to package " + pkg.getPackageName()
+                                    + " to package " + friendlyName
                                     + " (protectionLevel=" + bp.getProtectionLevel()
                                     + " flags=0x"
                                     + Integer.toHexString(PackageInfoUtils.appInfoFlags(pkg, ps))
@@ -3071,6 +3070,15 @@
     }
 
     /**
+     * Returns all relevant user ids.  This list include the current set of created user ids as well
+     * as pre-created user ids.
+     * @return user ids for created users and pre-created users
+     */
+    private int[] getAllUserIds() {
+        return UserManagerService.getInstance().getUserIdsIncludingPreCreated();
+    }
+
+    /**
      * Revoke permissions that are not implicit anymore and that have
      * {@link PackageManager#FLAG_PERMISSION_REVOKE_WHEN_REQUESTED} set.
      *
@@ -3088,7 +3096,7 @@
         boolean supportsRuntimePermissions = pkg.getTargetSdkVersion()
                 >= Build.VERSION_CODES.M;
 
-        int[] users = UserManagerService.getInstance().getUserIds();
+        int[] users = getAllUserIds();
         int numUsers = users.length;
         for (int i = 0; i < numUsers; i++) {
             int userId = users[i];
@@ -3197,7 +3205,7 @@
         if (replace && pkg.isRequestLegacyExternalStorage() && (
                 pkg.getRequestedPermissions().contains(READ_EXTERNAL_STORAGE)
                         || pkg.getRequestedPermissions().contains(WRITE_EXTERNAL_STORAGE))) {
-            return UserManagerService.getInstance().getUserIds();
+            return getAllUserIds();
         }
 
         return updatedUserIds;
@@ -3251,7 +3259,7 @@
                 if (!ps.hasInstallPermission(newPerm)) {
                     BasePermission bp = mSettings.getPermissionLocked(newPerm);
 
-                    int[] users = UserManagerService.getInstance().getUserIds();
+                    int[] users = getAllUserIds();
                     int numUsers = users.length;
                     for (int userNum = 0; userNum < numUsers; userNum++) {
                         int userId = users[userNum];
@@ -4224,6 +4232,20 @@
                                     revokePermissionFromPackageForUser(p.getPackageName(),
                                             bp.getName(), true, userId, callback));
                         }
+                    } else {
+                        mPackageManagerInt.forEachPackage(p -> {
+                            PackageSetting ps = mPackageManagerInt.getPackageSetting(
+                                    p.getPackageName());
+                            if (ps == null) {
+                                return;
+                            }
+                            PermissionsState permissionsState = ps.getPermissionsState();
+                            if (permissionsState.getInstallPermissionState(bp.getName()) != null) {
+                                permissionsState.revokeInstallPermission(bp);
+                                permissionsState.updatePermissionFlags(bp, UserHandle.USER_ALL,
+                                        MASK_PERMISSION_FLAGS_ALL, 0);
+                            }
+                        });
                     }
                     it.remove();
                 }
diff --git a/services/core/java/com/android/server/policy/PermissionPolicyService.java b/services/core/java/com/android/server/policy/PermissionPolicyService.java
index 37f088b..9dd9236 100644
--- a/services/core/java/com/android/server/policy/PermissionPolicyService.java
+++ b/services/core/java/com/android/server/policy/PermissionPolicyService.java
@@ -88,7 +88,7 @@
 public final class PermissionPolicyService extends SystemService {
     private static final String LOG_TAG = PermissionPolicyService.class.getSimpleName();
     private static final boolean DEBUG = false;
-    private static final long USER_SENSITIVE_UPDATE_DELAY_MS = 10000;
+    private static final long USER_SENSITIVE_UPDATE_DELAY_MS = 60000;
 
     private final Object mLock = new Object();
 
@@ -282,6 +282,11 @@
                 manager.updateUserSensitiveForApp(uid);
             }
         }, UserHandle.ALL, intentFilter, null, null);
+
+        PermissionControllerManager manager = new PermissionControllerManager(
+                getUserContext(getContext(), Process.myUserHandle()), FgThread.getHandler());
+        FgThread.getHandler().postDelayed(manager::updateUserSensitive,
+                USER_SENSITIVE_UPDATE_DELAY_MS);
     }
 
     /**
@@ -420,8 +425,7 @@
                 throw new IllegalStateException(e);
             }
 
-            FgThread.getHandler().postDelayed(permissionControllerManager::updateUserSensitive,
-                    USER_SENSITIVE_UPDATE_DELAY_MS);
+            permissionControllerManager.updateUserSensitive();
 
             packageManagerInternal.updateRuntimePermissionsFingerprint(userId);
         }
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index a4ed4e3..3c42e93 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -69,6 +69,7 @@
 import static android.view.WindowManagerGlobal.ADD_OKAY;
 import static android.view.WindowManagerGlobal.ADD_PERMISSION_DENIED;
 
+import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.SCREENSHOT_KEYCHORD_DELAY;
 import static com.android.server.policy.WindowManagerPolicy.WindowManagerFuncs.CAMERA_LENS_COVERED;
 import static com.android.server.policy.WindowManagerPolicy.WindowManagerFuncs.CAMERA_LENS_COVER_ABSENT;
 import static com.android.server.policy.WindowManagerPolicy.WindowManagerFuncs.CAMERA_LENS_UNCOVERED;
@@ -148,6 +149,7 @@
 import android.os.UserHandle;
 import android.os.VibrationEffect;
 import android.os.Vibrator;
+import android.provider.DeviceConfig;
 import android.provider.MediaStore;
 import android.provider.Settings;
 import android.service.dreams.DreamManagerInternal;
@@ -209,7 +211,6 @@
 import com.android.server.statusbar.StatusBarManagerInternal;
 import com.android.server.vr.VrManagerInternal;
 import com.android.server.wm.ActivityTaskManagerInternal;
-import com.android.server.wm.ActivityTaskManagerInternal.SleepToken;
 import com.android.server.wm.AppTransition;
 import com.android.server.wm.DisplayPolicy;
 import com.android.server.wm.DisplayRotation;
@@ -491,7 +492,7 @@
     private boolean mPendingKeyguardOccluded;
     private boolean mKeyguardOccludedChanged;
 
-    SleepToken mScreenOffSleepToken;
+    private ActivityTaskManagerInternal.SleepTokenAcquirer mScreenOffSleepTokenAcquirer;
     volatile boolean mKeyguardOccluded;
     Intent mHomeIntent;
     Intent mCarDockIntent;
@@ -1380,12 +1381,14 @@
     }
 
     private long getScreenshotChordLongPressDelay() {
+        long delayMs = DeviceConfig.getLong(
+                DeviceConfig.NAMESPACE_SYSTEMUI, SCREENSHOT_KEYCHORD_DELAY,
+                ViewConfiguration.get(mContext).getScreenshotChordKeyTimeout());
         if (mKeyguardDelegate.isShowing()) {
             // Double the time it takes to take a screenshot from the keyguard
-            return (long) (KEYGUARD_SCREENSHOT_CHORD_DELAY_MULTIPLIER *
-                    ViewConfiguration.get(mContext).getScreenshotChordKeyTimeout());
+            return (long) (KEYGUARD_SCREENSHOT_CHORD_DELAY_MULTIPLIER * delayMs);
         }
-        return ViewConfiguration.get(mContext).getScreenshotChordKeyTimeout();
+        return delayMs;
     }
 
     private long getRingerToggleChordDelay() {
@@ -1744,6 +1747,9 @@
                 new AccessibilityShortcutController(mContext, new Handler(), mCurrentUserId);
         mLogger = new MetricsLogger();
 
+        mScreenOffSleepTokenAcquirer = mActivityTaskManagerInternal
+                .createSleepTokenAcquirer("ScreenOff");
+
         Resources res = mContext.getResources();
         mWakeOnDpadKeyPress =
                 res.getBoolean(com.android.internal.R.bool.config_wakeOnDpadKeyPress);
@@ -4991,15 +4997,9 @@
     // TODO (multidisplay): Support multiple displays in WindowManagerPolicy.
     private void updateScreenOffSleepToken(boolean acquire) {
         if (acquire) {
-            if (mScreenOffSleepToken == null) {
-                mScreenOffSleepToken = mActivityTaskManagerInternal.acquireSleepToken(
-                        "ScreenOff", DEFAULT_DISPLAY);
-            }
+            mScreenOffSleepTokenAcquirer.acquire(DEFAULT_DISPLAY);
         } else {
-            if (mScreenOffSleepToken != null) {
-                mScreenOffSleepToken.release();
-                mScreenOffSleepToken = null;
-            }
+            mScreenOffSleepTokenAcquirer.release(DEFAULT_DISPLAY);
         }
     }
 
diff --git a/services/core/java/com/android/server/policy/WindowManagerPolicy.java b/services/core/java/com/android/server/policy/WindowManagerPolicy.java
index b3e162d..b9431a69 100644
--- a/services/core/java/com/android/server/policy/WindowManagerPolicy.java
+++ b/services/core/java/com/android/server/policy/WindowManagerPolicy.java
@@ -141,6 +141,10 @@
     @IntDef({NAV_BAR_LEFT, NAV_BAR_RIGHT, NAV_BAR_BOTTOM})
     @interface NavigationBarPosition {}
 
+    @Retention(SOURCE)
+    @IntDef({ALT_BAR_UNKNOWN, ALT_BAR_LEFT, ALT_BAR_RIGHT, ALT_BAR_BOTTOM, ALT_BAR_TOP})
+    @interface AltBarPosition {}
+
     /**
      * Pass this event to the user / app.  To be returned from
      * {@link #interceptKeyBeforeQueueing}.
diff --git a/services/core/java/com/android/server/storage/StorageSessionController.java b/services/core/java/com/android/server/storage/StorageSessionController.java
index 37df548..0d059ae 100644
--- a/services/core/java/com/android/server/storage/StorageSessionController.java
+++ b/services/core/java/com/android/server/storage/StorageSessionController.java
@@ -338,11 +338,12 @@
     }
 
     /**
-     * Returns {@code true} if {@code vol} is an emulated or public volume,
+     * Returns {@code true} if {@code vol} is an emulated or visible public volume,
      * {@code false} otherwise
      **/
     public static boolean isEmulatedOrPublic(VolumeInfo vol) {
-        return vol.type == VolumeInfo.TYPE_EMULATED || vol.type == VolumeInfo.TYPE_PUBLIC;
+        return vol.type == VolumeInfo.TYPE_EMULATED
+                || (vol.type == VolumeInfo.TYPE_PUBLIC && vol.isVisible());
     }
 
     /** Exception thrown when communication with the {@link ExternalStorageService} fails. */
diff --git a/services/core/java/com/android/server/telecom/InternalServiceRepository.java b/services/core/java/com/android/server/telecom/InternalServiceRepository.java
new file mode 100644
index 0000000..76ea5c7
--- /dev/null
+++ b/services/core/java/com/android/server/telecom/InternalServiceRepository.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.telecom;
+
+import android.content.Context;
+import android.os.Binder;
+import android.os.Process;
+
+import com.android.internal.telecom.IDeviceIdleControllerAdapter;
+import com.android.internal.telecom.IInternalServiceRetriever;
+import com.android.server.DeviceIdleInternal;
+
+/**
+ * The Telecom APK can not access services stored in LocalService directly and since it is in the
+ * SYSTEM process, it also can not use the *Manager interfaces
+ * (see {@link Context#enforceCallingPermission(String, String)}). Instead, we must wrap these local
+ * services in binder interfaces to allow Telecom access.
+ */
+public class InternalServiceRepository extends IInternalServiceRetriever.Stub {
+
+    private final IDeviceIdleControllerAdapter.Stub mDeviceIdleControllerAdapter =
+            new IDeviceIdleControllerAdapter.Stub() {
+        @Override
+        public void exemptAppTemporarilyForEvent(String packageName, long duration, int userHandle,
+                String reason) {
+            mDeviceIdleController.addPowerSaveTempWhitelistApp(Process.myUid(), packageName,
+                    duration, userHandle, true /*sync*/, reason);
+        }
+    };
+
+    private final DeviceIdleInternal mDeviceIdleController;
+
+    public InternalServiceRepository(DeviceIdleInternal deviceIdleController) {
+        mDeviceIdleController = deviceIdleController;
+    }
+
+    @Override
+    public IDeviceIdleControllerAdapter getDeviceIdleController() {
+        ensureSystemProcess();
+        return mDeviceIdleControllerAdapter;
+    }
+
+    private void ensureSystemProcess() {
+        if (Binder.getCallingUid() != Process.SYSTEM_UID) {
+            // Correctness check - this should never happen.
+            throw new SecurityException("SYSTEM ONLY API.");
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/telecom/TelecomLoaderService.java b/services/core/java/com/android/server/telecom/TelecomLoaderService.java
index a853529..52ad893 100644
--- a/services/core/java/com/android/server/telecom/TelecomLoaderService.java
+++ b/services/core/java/com/android/server/telecom/TelecomLoaderService.java
@@ -35,7 +35,10 @@
 import android.util.Slog;
 
 import com.android.internal.annotations.GuardedBy;
+import com.android.internal.telecom.ITelecomLoader;
+import com.android.internal.telecom.ITelecomService;
 import com.android.internal.telephony.SmsApplication;
+import com.android.server.DeviceIdleInternal;
 import com.android.server.LocalServices;
 import com.android.server.SystemService;
 import com.android.server.pm.UserManagerService;
@@ -53,16 +56,13 @@
         @Override
         public void onServiceConnected(ComponentName name, IBinder service) {
             // Normally, we would listen for death here, but since telecom runs in the same process
-            // as this loader (process="system") thats redundant here.
+            // as this loader (process="system") that's redundant here.
             try {
-                service.linkToDeath(new IBinder.DeathRecipient() {
-                    @Override
-                    public void binderDied() {
-                        connectToTelecom();
-                    }
-                }, 0);
+                ITelecomLoader telecomLoader = ITelecomLoader.Stub.asInterface(service);
+                ITelecomService telecomService = telecomLoader.createTelecomService(mServiceRepo);
+
                 SmsApplication.getDefaultMmsApplication(mContext, false);
-                ServiceManager.addService(Context.TELECOM_SERVICE, service);
+                ServiceManager.addService(Context.TELECOM_SERVICE, telecomService.asBinder());
 
                 synchronized (mLock) {
                     final PermissionManagerServiceInternal permissionManager =
@@ -114,6 +114,8 @@
     @GuardedBy("mLock")
     private TelecomServiceConnection mServiceConnection;
 
+    private InternalServiceRepository mServiceRepo;
+
     public TelecomLoaderService(Context context) {
         super(context);
         mContext = context;
@@ -129,6 +131,8 @@
         if (phase == PHASE_ACTIVITY_MANAGER_READY) {
             registerDefaultAppNotifier();
             registerCarrierConfigChangedReceiver();
+            // core services will have already been loaded.
+            setupServiceRepository();
             connectToTelecom();
         }
     }
@@ -154,6 +158,11 @@
         }
     }
 
+    private void setupServiceRepository() {
+        DeviceIdleInternal deviceIdleInternal = getLocalService(DeviceIdleInternal.class);
+        mServiceRepo = new InternalServiceRepository(deviceIdleInternal);
+    }
+
 
     private void registerDefaultAppProviders() {
         final PermissionManagerServiceInternal permissionManager =
diff --git a/services/core/java/com/android/server/trust/TrustManagerService.java b/services/core/java/com/android/server/trust/TrustManagerService.java
index fd3c1f9..25cd641 100644
--- a/services/core/java/com/android/server/trust/TrustManagerService.java
+++ b/services/core/java/com/android/server/trust/TrustManagerService.java
@@ -123,6 +123,8 @@
     private static final String TRUST_TIMEOUT_ALARM_TAG = "TrustManagerService.trustTimeoutForUser";
     private static final long TRUST_TIMEOUT_IN_MILLIS = 4 * 60 * 60 * 1000;
 
+    private static final String PRIV_NAMESPACE = "http://schemas.android.com/apk/prv/res/android";
+
     private final ArraySet<AgentInfo> mActiveAgents = new ArraySet<>();
     private final ArrayList<ITrustListener> mTrustListeners = new ArrayList<>();
     private final Receiver mReceiver = new Receiver();
@@ -808,8 +810,8 @@
             TypedArray sa = res
                     .obtainAttributes(attrs, com.android.internal.R.styleable.TrustAgent);
             cn = sa.getString(com.android.internal.R.styleable.TrustAgent_settingsActivity);
-            canUnlockProfile = sa.getBoolean(
-                    com.android.internal.R.styleable.TrustAgent_unlockProfile, false);
+            canUnlockProfile = attrs.getAttributeBooleanValue(
+                    PRIV_NAMESPACE, "unlockProfile", false);
             sa.recycle();
         } catch (PackageManager.NameNotFoundException e) {
             caughtException = e;
diff --git a/services/core/java/com/android/server/uri/UriGrantsManagerInternal.java b/services/core/java/com/android/server/uri/UriGrantsManagerInternal.java
index cdb6199..5772dea 100644
--- a/services/core/java/com/android/server/uri/UriGrantsManagerInternal.java
+++ b/services/core/java/com/android/server/uri/UriGrantsManagerInternal.java
@@ -75,10 +75,31 @@
     void removeUriPermissionsForPackage(
             String packageName, int userHandle, boolean persistable, boolean targetOnly);
     /**
-     * @param uri This uri must NOT contain an embedded userId.
+     * Remove any {@link UriPermission} associated with the owner whose values match the given
+     * filtering parameters.
+     *
+     * @param token An opaque owner token as returned by {@link #newUriPermissionOwner(String)}.
+     * @param uri This uri must NOT contain an embedded userId. {@code null} to apply to all Uris.
+     * @param mode The modes (as a bitmask) to revoke.
      * @param userId The userId in which the uri is to be resolved.
      */
     void revokeUriPermissionFromOwner(IBinder token, Uri uri, int mode, int userId);
+
+    /**
+     * Remove any {@link UriPermission} associated with the owner whose values match the given
+     * filtering parameters.
+     *
+     * @param token An opaque owner token as returned by {@link #newUriPermissionOwner(String)}.
+     * @param uri This uri must NOT contain an embedded userId. {@code null} to apply to all Uris.
+     * @param mode The modes (as a bitmask) to revoke.
+     * @param userId The userId in which the uri is to be resolved.
+     * @param targetPkg Calling package name to match, or {@code null} to apply to all packages.
+     * @param targetUserId Calling user to match, or {@link UserHandle#USER_ALL} to apply to all
+     *                     users.
+     */
+    void revokeUriPermissionFromOwner(IBinder token, Uri uri, int mode, int userId,
+            String targetPkg, int targetUserId);
+
     boolean checkAuthorityGrants(
             int callingUid, ProviderInfo cpi, int userId, boolean checkUser);
     void dump(PrintWriter pw, boolean dumpAll, String dumpPackage);
diff --git a/services/core/java/com/android/server/uri/UriGrantsManagerService.java b/services/core/java/com/android/server/uri/UriGrantsManagerService.java
index 7ea8544..383ba37 100644
--- a/services/core/java/com/android/server/uri/UriGrantsManagerService.java
+++ b/services/core/java/com/android/server/uri/UriGrantsManagerService.java
@@ -51,7 +51,6 @@
 import android.app.GrantedUriPermission;
 import android.app.IUriGrantsManager;
 import android.content.ClipData;
-import android.content.ComponentName;
 import android.content.ContentProvider;
 import android.content.ContentResolver;
 import android.content.Context;
@@ -88,11 +87,11 @@
 import com.android.server.SystemService;
 import com.android.server.SystemServiceManager;
 
-import libcore.io.IoUtils;
-
 import com.google.android.collect.Lists;
 import com.google.android.collect.Maps;
 
+import libcore.io.IoUtils;
+
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
 import org.xmlpull.v1.XmlSerializer;
@@ -116,7 +115,7 @@
     private static final String TAG = "UriGrantsManagerService";
     // Maximum number of persisted Uri grants a package is allowed
     private static final int MAX_PERSISTED_URI_GRANTS = 512;
-    private static final boolean ENABLE_DYNAMIC_PERMISSIONS = false;
+    private static final boolean ENABLE_DYNAMIC_PERMISSIONS = true;
 
     private final Object mLock = new Object();
     private final H mH;
@@ -1431,16 +1430,18 @@
 
         @Override
         public void revokeUriPermissionFromOwner(IBinder token, Uri uri, int mode, int userId) {
+            revokeUriPermissionFromOwner(token, uri, mode, userId, null, UserHandle.USER_ALL);
+        }
+
+        @Override
+        public void revokeUriPermissionFromOwner(IBinder token, Uri uri, int mode, int userId,
+                String targetPkg, int targetUserId) {
             final UriPermissionOwner owner = UriPermissionOwner.fromExternalToken(token);
             if (owner == null) {
                 throw new IllegalArgumentException("Unknown owner: " + token);
             }
-
-            if (uri == null) {
-                owner.removeUriPermissions(mode);
-            } else {
-                owner.removeUriPermission(new GrantUri(userId, uri, mode), mode);
-            }
+            GrantUri grantUri = uri == null ? null : new GrantUri(userId, uri, mode);
+            owner.removeUriPermission(grantUri, mode, targetPkg, targetUserId);
         }
 
         @Override
diff --git a/services/core/java/com/android/server/uri/UriPermissionOwner.java b/services/core/java/com/android/server/uri/UriPermissionOwner.java
index 2b404a4..0c26399 100644
--- a/services/core/java/com/android/server/uri/UriPermissionOwner.java
+++ b/services/core/java/com/android/server/uri/UriPermissionOwner.java
@@ -21,6 +21,7 @@
 
 import android.os.Binder;
 import android.os.IBinder;
+import android.os.UserHandle;
 import android.util.ArraySet;
 import android.util.proto.ProtoOutputStream;
 
@@ -74,30 +75,47 @@
     }
 
     void removeUriPermission(GrantUri grantUri, int mode) {
+        removeUriPermission(grantUri, mode, null, UserHandle.USER_ALL);
+    }
+
+    void removeUriPermission(GrantUri grantUri, int mode, String targetPgk, int targetUserId) {
         if ((mode & FLAG_GRANT_READ_URI_PERMISSION) != 0 && mReadPerms != null) {
             Iterator<UriPermission> it = mReadPerms.iterator();
             while (it.hasNext()) {
                 UriPermission perm = it.next();
-                if (grantUri == null || grantUri.equals(perm.uri)) {
-                    perm.removeReadOwner(this);
-                    mService.removeUriPermissionIfNeeded(perm);
-                    it.remove();
+                if (grantUri != null && !grantUri.equals(perm.uri)) {
+                    continue;
                 }
+                if (targetPgk != null && !targetPgk.equals(perm.targetPkg)) {
+                    continue;
+                }
+                if (targetUserId != UserHandle.USER_ALL && targetUserId != perm.targetUserId) {
+                    continue;
+                }
+                perm.removeReadOwner(this);
+                mService.removeUriPermissionIfNeeded(perm);
+                it.remove();
             }
             if (mReadPerms.isEmpty()) {
                 mReadPerms = null;
             }
         }
-        if ((mode & FLAG_GRANT_WRITE_URI_PERMISSION) != 0
-                && mWritePerms != null) {
+        if ((mode & FLAG_GRANT_WRITE_URI_PERMISSION) != 0 && mWritePerms != null) {
             Iterator<UriPermission> it = mWritePerms.iterator();
             while (it.hasNext()) {
                 UriPermission perm = it.next();
-                if (grantUri == null || grantUri.equals(perm.uri)) {
-                    perm.removeWriteOwner(this);
-                    mService.removeUriPermissionIfNeeded(perm);
-                    it.remove();
+                if (grantUri != null && !grantUri.equals(perm.uri)) {
+                    continue;
                 }
+                if (targetPgk != null && !targetPgk.equals(perm.targetPkg)) {
+                    continue;
+                }
+                if (targetUserId != UserHandle.USER_ALL && targetUserId != perm.targetUserId) {
+                    continue;
+                }
+                perm.removeWriteOwner(this);
+                mService.removeUriPermissionIfNeeded(perm);
+                it.remove();
             }
             if (mWritePerms.isEmpty()) {
                 mWritePerms = null;
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
index 3a30f98..8fe8853 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
@@ -1175,9 +1175,7 @@
             }
         };
 
-        private Runnable mTryToRebindRunnable = () -> {
-            tryToRebind();
-        };
+        private Runnable mTryToRebindRunnable = this::tryToRebind;
 
         WallpaperConnection(WallpaperInfo info, WallpaperData wallpaper, int clientUid) {
             mInfo = info;
@@ -1310,14 +1308,14 @@
                     // a short time in the future, specifically to allow any pending package
                     // update message on this same looper thread to be processed.
                     if (!mWallpaper.wallpaperUpdating) {
-                        mContext.getMainThreadHandler().postDelayed(() -> processDisconnect(this),
+                        mContext.getMainThreadHandler().postDelayed(mDisconnectRunnable,
                                 1000);
                     }
                 }
             }
         }
 
-        public void scheduleTimeoutLocked() {
+        private void scheduleTimeoutLocked() {
             // If we didn't reset it right away, do so after we couldn't connect to
             // it for an extended amount of time to avoid having a black wallpaper.
             final Handler fgHandler = FgThread.getHandler();
@@ -1357,11 +1355,11 @@
             }
         }
 
-        private void processDisconnect(final ServiceConnection connection) {
+        private Runnable mDisconnectRunnable = () -> {
             synchronized (mLock) {
                 // The wallpaper disappeared.  If this isn't a system-default one, track
                 // crashes and fall back to default if it continues to misbehave.
-                if (connection == mWallpaper.connection) {
+                if (this == mWallpaper.connection) {
                     final ComponentName wpService = mWallpaper.wallpaperComponent;
                     if (!mWallpaper.wallpaperUpdating
                             && mWallpaper.userId == mCurrentUserId
@@ -1389,7 +1387,7 @@
                     }
                 }
             }
-        }
+        };
 
         /**
          * Called by a live wallpaper if its colors have changed.
@@ -2801,6 +2799,13 @@
                     WallpaperConnection.DisplayConnector::disconnectLocked);
             wallpaper.connection.mService = null;
             wallpaper.connection.mDisplayConnector.clear();
+
+            FgThread.getHandler().removeCallbacks(wallpaper.connection.mResetRunnable);
+            mContext.getMainThreadHandler().removeCallbacks(
+                    wallpaper.connection.mDisconnectRunnable);
+            mContext.getMainThreadHandler().removeCallbacks(
+                    wallpaper.connection.mTryToRebindRunnable);
+
             wallpaper.connection = null;
             if (wallpaper == mLastWallpaper) mLastWallpaper = null;
         }
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index cfbc77c..1a13d0f 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -1671,7 +1671,8 @@
 
     static int getLockTaskLaunchMode(ActivityInfo aInfo, @Nullable ActivityOptions options) {
         int lockTaskLaunchMode = aInfo.lockTaskLaunchMode;
-        if (aInfo.applicationInfo.isPrivilegedApp()
+        // Non-priv apps are not allowed to use always or never, fall back to default
+        if (!aInfo.applicationInfo.isPrivilegedApp()
                 && (lockTaskLaunchMode == LOCK_TASK_LAUNCH_MODE_ALWAYS
                 || lockTaskLaunchMode == LOCK_TASK_LAUNCH_MODE_NEVER)) {
             lockTaskLaunchMode = LOCK_TASK_LAUNCH_MODE_DEFAULT;
@@ -2543,7 +2544,9 @@
 
         final ActivityStack stack = getRootTask();
         final boolean mayAdjustTop = (isState(RESUMED) || stack.mResumedActivity == null)
-                && stack.isFocusedStackOnDisplay();
+                && stack.isFocusedStackOnDisplay()
+                // Do not adjust focus task because the task will be reused to launch new activity.
+                && !task.isClearingToReuseTask();
         final boolean shouldAdjustGlobalFocus = mayAdjustTop
                 // It must be checked before {@link #makeFinishingLocked} is called, because a stack
                 // is not visible if it only contains finishing activities.
@@ -4732,6 +4735,15 @@
                 Slog.v(TAG_VISIBILITY, "Start visible activity, " + this);
             }
             setState(STARTED, "makeActiveIfNeeded");
+
+            // Update process info while making an activity from invisible to visible, to make
+            // sure the process state is updated to foreground.
+            if (app != null) {
+                app.updateProcessInfo(false /* updateServiceConnectionActivities */,
+                        true /* activityChange */, true /* updateOomAdj */,
+                        true /* addPendingTopUid */);
+            }
+
             try {
                 mAtmService.getLifecycleManager().scheduleTransaction(app.getThread(), appToken,
                         StartActivityItem.obtain());
diff --git a/services/core/java/com/android/server/wm/ActivityStack.java b/services/core/java/com/android/server/wm/ActivityStack.java
index e7e08f3..6d452c3 100644
--- a/services/core/java/com/android/server/wm/ActivityStack.java
+++ b/services/core/java/com/android/server/wm/ActivityStack.java
@@ -146,7 +146,6 @@
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.app.IVoiceInteractor;
-import com.android.internal.os.logging.MetricsLoggerWrapper;
 import com.android.internal.util.function.pooled.PooledConsumer;
 import com.android.internal.util.function.pooled.PooledFunction;
 import com.android.internal.util.function.pooled.PooledLambda;
@@ -928,12 +927,9 @@
         mCurrentUser = userId;
 
         super.switchUser(userId);
-        forAllLeafTasks((t) -> {
-            if (t.showToCurrentUser() && t != this) {
-                mChildren.remove(t);
-                mChildren.add(t);
-            }
-        }, true /* traverseTopToBottom */);
+        if (isLeafTask() && showToCurrentUser()) {
+            getParent().positionChildAt(POSITION_TOP, this, false /*includeParents*/);
+        }
     }
 
     void minimalResumeActivityLocked(ActivityRecord r) {
@@ -1704,8 +1700,9 @@
         // If the most recent activity was noHistory but was only stopped rather
         // than stopped+finished because the device went to sleep, we need to make
         // sure to finish it as we're making a new activity topmost.
-        if (shouldSleepActivities() && mLastNoHistoryActivity != null &&
-                !mLastNoHistoryActivity.finishing) {
+        if (shouldSleepActivities() && mLastNoHistoryActivity != null
+                && !mLastNoHistoryActivity.finishing
+                && mLastNoHistoryActivity != next) {
             if (DEBUG_STATES) Slog.d(TAG_STATES,
                     "no-history finish of " + mLastNoHistoryActivity + " on new resume");
             mLastNoHistoryActivity.finishIfPossible("resume-no-history", false /* oomAdj */);
@@ -1994,7 +1991,7 @@
         return mRootWindowContainer.resumeHomeActivity(prev, reason, getDisplayArea());
     }
 
-    void startActivityLocked(ActivityRecord r, ActivityRecord focusedTopActivity,
+    void startActivityLocked(ActivityRecord r, @Nullable ActivityRecord focusedTopActivity,
             boolean newTask, boolean keepCurTransition, ActivityOptions options) {
         Task rTask = r.getTask();
         final boolean allowMoveToFront = options == null || !options.getAvoidMoveToFront();
@@ -3046,8 +3043,6 @@
             getDisplayArea().positionStackAtTop(this, false /* includingParents */);
 
             mStackSupervisor.scheduleUpdatePictureInPictureModeIfNeeded(task, this);
-            MetricsLoggerWrapper.logPictureInPictureFullScreen(mAtmService.mContext,
-                    task.effectiveUid, task.realActivity.flattenToString());
         });
     }
 
@@ -3342,7 +3337,11 @@
         // Do not sleep activities in this stack if we're marked as focused and the keyguard
         // is in the process of going away.
         if (isFocusedStackOnDisplay()
-                && mStackSupervisor.getKeyguardController().isKeyguardGoingAway()) {
+                && mStackSupervisor.getKeyguardController().isKeyguardGoingAway()
+                // Avoid resuming activities on secondary displays since we don't want bubble
+                // activities to be resumed while bubble is still collapsed.
+                // TODO(b/113840485): Having keyguard going away state for secondary displays.
+                && display.isDefaultDisplay) {
             return false;
         }
 
diff --git a/services/core/java/com/android/server/wm/ActivityStackSupervisor.java b/services/core/java/com/android/server/wm/ActivityStackSupervisor.java
index 3dd82a6..7e6b7cd 100644
--- a/services/core/java/com/android/server/wm/ActivityStackSupervisor.java
+++ b/services/core/java/com/android/server/wm/ActivityStackSupervisor.java
@@ -732,6 +732,11 @@
         final ActivityStack stack = task.getStack();
 
         beginDeferResume();
+        // The LaunchActivityItem also contains process configuration, so the configuration change
+        // from WindowProcessController#setProcess can be deferred. The major reason is that if
+        // the activity has FixedRotationAdjustments, it needs to be applied with configuration.
+        // In general, this reduces a binder transaction if process configuration is changed.
+        proc.pauseConfigurationDispatch();
 
         try {
             r.startFreezingScreenLocked(proc, 0);
@@ -826,9 +831,9 @@
                 // Because we could be starting an Activity in the system process this may not go
                 // across a Binder interface which would create a new Configuration. Consequently
                 // we have to always create a new Configuration here.
-
+                final Configuration procConfig = proc.prepareConfigurationForLaunchingActivity();
                 final MergedConfiguration mergedConfiguration = new MergedConfiguration(
-                        proc.getConfiguration(), r.getMergedOverrideConfiguration());
+                        procConfig, r.getMergedOverrideConfiguration());
                 r.setLastReportedConfiguration(mergedConfiguration);
 
                 logIfTransactionTooLarge(r.intent, r.getSavedState());
@@ -862,6 +867,11 @@
                 // Schedule transaction.
                 mService.getLifecycleManager().scheduleTransaction(clientTransaction);
 
+                if (procConfig.seq > mRootWindowContainer.getConfiguration().seq) {
+                    // If the seq is increased, there should be something changed (e.g. registered
+                    // activity configuration).
+                    proc.setLastReportedConfiguration(procConfig);
+                }
                 if ((proc.mInfo.privateFlags & ApplicationInfo.PRIVATE_FLAG_CANT_SAVE_STATE) != 0
                         && mService.mHasHeavyWeightFeature) {
                     // This may be a heavy-weight process! Note that the package manager will ensure
@@ -896,6 +906,7 @@
             }
         } finally {
             endDeferResume();
+            proc.resumeConfigurationDispatch();
         }
 
         r.launchFailed = false;
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index b378621..7af237b 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -1481,9 +1481,10 @@
             // anyone interested in this piece of information.
             final ActivityStack homeStack = targetTask.getDisplayArea().getRootHomeTask();
             final boolean homeTaskVisible = homeStack != null && homeStack.shouldBeVisible(null);
+            final ActivityRecord top = targetTask.getTopNonFinishingActivity();
+            final boolean visible = top != null && top.isVisible();
             mService.getTaskChangeNotificationController().notifyActivityRestartAttempt(
-                    targetTask.getTaskInfo(), homeTaskVisible, clearedTask,
-                    targetTask.getTopNonFinishingActivity().isVisible());
+                    targetTask.getTaskInfo(), homeTaskVisible, clearedTask, visible);
         }
     }
 
@@ -1697,8 +1698,9 @@
         mRootWindowContainer.sendPowerHintForLaunchStartIfNeeded(
                 false /* forceSend */, mStartActivity);
 
-        mTargetStack.startActivityLocked(mStartActivity, topStack.getTopNonFinishingActivity(),
-                newTask, mKeepCurTransition, mOptions);
+        mTargetStack.startActivityLocked(mStartActivity,
+                topStack != null ? topStack.getTopNonFinishingActivity() : null, newTask,
+                mKeepCurTransition, mOptions);
         if (mDoResume) {
             final ActivityRecord topTaskActivity =
                     mStartActivity.getTask().topRunningActivityLocked();
@@ -2009,8 +2011,6 @@
             // of history or if it is finished immediately), thus disassociating the task. Also note
             // that mReuseTask is reset as a result of {@link Task#performClearTaskLocked}
             // launching another activity.
-            // TODO(b/36119896):  We shouldn't trigger activity launches in this path since we are
-            // already launching one.
             targetTask.performClearTaskLocked();
             targetTask.setIntent(mStartActivity);
             mAddingToTask = true;
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java b/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java
index 9125d90..d4dd35f5 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java
@@ -125,20 +125,30 @@
      * Sleep tokens cause the activity manager to put the top activity to sleep.
      * They are used by components such as dreams that may hide and block interaction
      * with underlying activities.
+     * The Acquirer provides an interface that encapsulates the underlying work, so the user does
+     * not need to handle the token by him/herself.
      */
-    public static abstract class SleepToken {
+    public interface SleepTokenAcquirer {
 
-        /** Releases the sleep token. */
-        public abstract void release();
+        /**
+         * Acquires a sleep token.
+         * @param displayId The display to apply to.
+         */
+        void acquire(int displayId);
+
+        /**
+         * Releases the sleep token.
+         * @param displayId The display to apply to.
+         */
+        void release(int displayId);
     }
 
     /**
-     * Acquires a sleep token for the specified display with the specified tag.
+     * Creates a sleep token acquirer for the specified display with the specified tag.
      *
-     * @param tag A string identifying the purpose of the token (eg. "Dream").
-     * @param displayId The display to apply the sleep token to.
+     * @param tag A string identifying the purpose (eg. "Dream").
      */
-    public abstract SleepToken acquireSleepToken(@NonNull String tag, int displayId);
+    public abstract SleepTokenAcquirer createSleepTokenAcquirer(@NonNull String tag);
 
     /**
      * Returns home activity for the specified user.
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index ffbf6dd..0542ef9 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -183,7 +183,6 @@
 import android.graphics.Bitmap;
 import android.graphics.Point;
 import android.graphics.Rect;
-import android.metrics.LogMaker;
 import android.net.Uri;
 import android.os.Binder;
 import android.os.Build;
@@ -239,12 +238,9 @@
 import com.android.internal.app.AssistUtils;
 import com.android.internal.app.IVoiceInteractor;
 import com.android.internal.app.ProcessMap;
-import com.android.internal.logging.MetricsLogger;
-import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
 import com.android.internal.notification.SystemNotificationChannels;
 import com.android.internal.os.TransferPipe;
-import com.android.internal.os.logging.MetricsLoggerWrapper;
 import com.android.internal.policy.IKeyguardDismissCallback;
 import com.android.internal.policy.KeyguardDismissCallback;
 import com.android.internal.util.ArrayUtils;
@@ -3001,13 +2997,7 @@
 
     @Override
     public void stopLockTaskModeByToken(IBinder token) {
-        synchronized (mGlobalLock) {
-            final ActivityRecord r = ActivityRecord.forTokenLocked(token);
-            if (r == null) {
-                return;
-            }
-            stopLockTaskModeInternal(r.getTask(), false /* isSystemCaller */);
-        }
+        stopLockTaskModeInternal(token, false /* isSystemCaller */);
     }
 
     /**
@@ -3049,11 +3039,19 @@
         }
     }
 
-    private void stopLockTaskModeInternal(@Nullable Task task, boolean isSystemCaller) {
+    private void stopLockTaskModeInternal(@Nullable IBinder token, boolean isSystemCaller) {
         final int callingUid = Binder.getCallingUid();
         long ident = Binder.clearCallingIdentity();
         try {
             synchronized (mGlobalLock) {
+                Task task = null;
+                if (token != null) {
+                    final ActivityRecord r = ActivityRecord.forTokenLocked(token);
+                    if (r == null) {
+                        return;
+                    }
+                    task = r.getTask();
+                }
                 getLockTaskController().stopLockTaskMode(task, isSystemCaller, callingUid);
             }
             // Launch in-call UI if a call is ongoing. This is necessary to allow stopping the lock
@@ -4109,10 +4107,6 @@
                         final ActivityStack stack = r.getRootTask();
                         stack.setPictureInPictureAspectRatio(aspectRatio);
                         stack.setPictureInPictureActions(actions);
-                        MetricsLoggerWrapper.logPictureInPictureEnter(mContext,
-                                r.info.applicationInfo.uid, r.shortComponentName,
-                                r.supportsEnterPipOnTaskSwitch);
-                        logPictureInPictureArgs(params);
                     }
                 };
 
@@ -4156,7 +4150,6 @@
                             r.pictureInPictureArgs.getAspectRatio());
                     stack.setPictureInPictureActions(r.pictureInPictureArgs.getActions());
                 }
-                logPictureInPictureArgs(params);
             }
         } finally {
             Binder.restoreCallingIdentity(origId);
@@ -4170,18 +4163,6 @@
         return 3;
     }
 
-    private void logPictureInPictureArgs(PictureInPictureParams params) {
-        if (params.hasSetActions()) {
-            MetricsLogger.histogram(mContext, "tron_varz_picture_in_picture_actions_count",
-                    params.getActions().size());
-        }
-        if (params.hasSetAspectRatio()) {
-            LogMaker lm = new LogMaker(MetricsEvent.ACTION_PICTURE_IN_PICTURE_ASPECT_RATIO_CHANGED);
-            lm.addTaggedData(MetricsEvent.PICTURE_IN_PICTURE_ASPECT_RATIO, params.getAspectRatio());
-            MetricsLogger.action(lm);
-        }
-    }
-
     /**
      * Checks the state of the system and the activity associated with the given {@param token} to
      * verify that picture-in-picture is supported for that activity.
@@ -5087,7 +5068,10 @@
         final long sleepToken = proto.start(ActivityManagerServiceDumpProcessesProto.SLEEP_STATUS);
         proto.write(ActivityManagerServiceDumpProcessesProto.SleepStatus.WAKEFULNESS,
                 PowerManagerInternal.wakefulnessToProtoEnum(wakeFullness));
-        for (ActivityTaskManagerInternal.SleepToken st : mRootWindowContainer.mSleepTokens) {
+        final int tokenSize = mRootWindowContainer.mSleepTokens.size();
+        for (int i = 0; i < tokenSize; i++) {
+            final RootWindowContainer.SleepToken st =
+                    mRootWindowContainer.mSleepTokens.valueAt(i);
             proto.write(ActivityManagerServiceDumpProcessesProto.SleepStatus.SLEEP_TOKENS,
                     st.toString());
         }
@@ -5377,15 +5361,6 @@
         return mAmInternal.isBackgroundActivityStartsEnabled();
     }
 
-    void enableScreenAfterBoot(boolean booted) {
-        writeBootProgressEnableScreen(SystemClock.uptimeMillis());
-        mWindowManager.enableScreenAfterBoot();
-
-        synchronized (mGlobalLock) {
-            updateEventDispatchingLocked(booted);
-        }
-    }
-
     static long getInputDispatchingTimeoutLocked(ActivityRecord r) {
         if (r == null || !r.hasProcess()) {
             return KEY_DISPATCHING_TIMEOUT_MS;
@@ -5521,12 +5496,35 @@
                 reason);
     }
 
-    ActivityTaskManagerInternal.SleepToken acquireSleepToken(String tag, int displayId) {
-        synchronized (mGlobalLock) {
-            final ActivityTaskManagerInternal.SleepToken token =
-                    mRootWindowContainer.createSleepToken(tag, displayId);
-            updateSleepIfNeededLocked();
-            return token;
+    final class SleepTokenAcquirerImpl implements ActivityTaskManagerInternal.SleepTokenAcquirer {
+        private final String mTag;
+        private final SparseArray<RootWindowContainer.SleepToken> mSleepTokens =
+                new SparseArray<>();
+
+        SleepTokenAcquirerImpl(@NonNull String tag) {
+            mTag = tag;
+        }
+
+        @Override
+        public void acquire(int displayId) {
+            synchronized (mGlobalLock) {
+                if (!mSleepTokens.contains(displayId)) {
+                    mSleepTokens.append(displayId,
+                            mRootWindowContainer.createSleepToken(mTag, displayId));
+                    updateSleepIfNeededLocked();
+                }
+            }
+        }
+
+        @Override
+        public void release(int displayId) {
+            synchronized (mGlobalLock) {
+                final RootWindowContainer.SleepToken token = mSleepTokens.get(displayId);
+                if (token != null) {
+                    mRootWindowContainer.removeSleepToken(token);
+                    mSleepTokens.remove(displayId);
+                }
+            }
         }
     }
 
@@ -6085,9 +6083,9 @@
 
     final class LocalService extends ActivityTaskManagerInternal {
         @Override
-        public SleepToken acquireSleepToken(String tag, int displayId) {
+        public SleepTokenAcquirer createSleepTokenAcquirer(@NonNull String tag) {
             Objects.requireNonNull(tag);
-            return ActivityTaskManagerService.this.acquireSleepToken(tag, displayId);
+            return new SleepTokenAcquirerImpl(tag);
         }
 
         @Override
@@ -6448,9 +6446,9 @@
 
         @Override
         public void enableScreenAfterBoot(boolean booted) {
+            writeBootProgressEnableScreen(SystemClock.uptimeMillis());
+            mWindowManager.enableScreenAfterBoot();
             synchronized (mGlobalLock) {
-                writeBootProgressEnableScreen(SystemClock.uptimeMillis());
-                mWindowManager.enableScreenAfterBoot();
                 updateEventDispatchingLocked(booted);
             }
         }
diff --git a/services/core/java/com/android/server/wm/DisplayArea.java b/services/core/java/com/android/server/wm/DisplayArea.java
index 2be3acc..fa2a42e 100644
--- a/services/core/java/com/android/server/wm/DisplayArea.java
+++ b/services/core/java/com/android/server/wm/DisplayArea.java
@@ -200,6 +200,9 @@
                 Comparator.comparingInt(WindowToken::getWindowLayerFromType);
 
         private final Predicate<WindowState> mGetOrientingWindow = w -> {
+            if (!w.isVisible() || !w.mLegacyPolicyVisibilityAfterAnim) {
+                return false;
+            }
             final WindowManagerPolicy policy = mWmService.mPolicy;
             if (policy.isKeyguardHostWindow(w.mAttrs)) {
                 if (mWmService.mKeyguardGoingAway) {
@@ -235,6 +238,7 @@
 
         @Override
         int getOrientation(int candidate) {
+            mLastOrientationSource = null;
             // Find a window requesting orientation.
             final WindowState win = getWindow(mGetOrientingWindow);
 
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index dfca5a9..e36b84f 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -43,14 +43,14 @@
 import static android.view.Display.REMOVE_MODE_DESTROY_CONTENT;
 import static android.view.InsetsState.ITYPE_IME;
 import static android.view.InsetsState.ITYPE_LEFT_GESTURES;
+import static android.view.InsetsState.ITYPE_NAVIGATION_BAR;
 import static android.view.InsetsState.ITYPE_RIGHT_GESTURES;
 import static android.view.Surface.ROTATION_0;
 import static android.view.Surface.ROTATION_180;
 import static android.view.Surface.ROTATION_270;
 import static android.view.Surface.ROTATION_90;
 import static android.view.View.GONE;
-import static android.view.View.SYSTEM_UI_FLAG_HIDE_NAVIGATION;
-import static android.view.View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY;
+import static android.view.WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE;
 import static android.view.WindowManager.LayoutParams.FIRST_APPLICATION_WINDOW;
 import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
 import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
@@ -596,9 +596,9 @@
     private IntArray mDisplayAccessUIDs = new IntArray();
 
     /** All tokens used to put activities on this stack to sleep (including mOffToken) */
-    final ArrayList<ActivityTaskManagerInternal.SleepToken> mAllSleepTokens = new ArrayList<>();
-    /** The token acquired by ActivityStackSupervisor to put stacks on the display to sleep */
-    ActivityTaskManagerInternal.SleepToken mOffToken;
+    final ArrayList<RootWindowContainer.SleepToken> mAllSleepTokens = new ArrayList<>();
+    /** The token acquirer to put stacks on the display to sleep */
+    private final ActivityTaskManagerInternal.SleepTokenAcquirer mOffTokenAcquirer;
 
     private boolean mSleeping;
 
@@ -934,6 +934,7 @@
         mAtmService = mWmService.mAtmService;
         mDisplay = display;
         mDisplayId = display.getDisplayId();
+        mOffTokenAcquirer = mRootWindowContainer.mDisplayOffTokenAcquirer;
         mWallpaperController = new WallpaperController(mWmService, this);
         display.getDisplayInfo(mDisplayInfo);
         display.getMetrics(mDisplayMetrics);
@@ -1495,6 +1496,15 @@
                 // intermediate orientation change, it is more stable to freeze the display.
                 return false;
             }
+            if (r.isState(RESUMED) && !r.getRootTask().mInResumeTopActivity) {
+                // If the activity is executing or has done the lifecycle callback, use normal
+                // rotation animation so the display info can be updated immediately (see
+                // updateDisplayAndOrientation). This prevents a compatibility issue such as
+                // calling setRequestedOrientation in Activity#onCreate and then get display info.
+                // If fixed rotation is applied, the display rotation will still be the old one,
+                // unless the client side gets the rotation again after the adjustments arrive.
+                return false;
+            }
         } else if (r != topRunningActivity()) {
             // If the transition has not started yet, the activity must be the top.
             return false;
@@ -1572,7 +1582,12 @@
             // the heavy operations. This also benefits that the states of multiple activities
             // are handled together.
             r.linkFixedRotationTransform(prevRotatedLaunchingApp);
-            setFixedRotationLaunchingAppUnchecked(r, rotation);
+            if (r != mFixedRotationTransitionListener.mAnimatingRecents) {
+                // Only update the record for normal activity so the display orientation can be
+                // updated when the transition is done if it becomes the top. And the case of
+                // recents can be handled when the recents animation is finished.
+                setFixedRotationLaunchingAppUnchecked(r, rotation);
+            }
             return;
         }
 
@@ -3548,7 +3563,11 @@
             return false;
         }
         return mWmService.mDisplayWindowSettings.shouldShowImeLocked(this)
-                || mWmService.mForceDesktopModeOnExternalDisplays;
+                || forceDesktopMode();
+    }
+
+    boolean forceDesktopMode() {
+        return mWmService.mForceDesktopModeOnExternalDisplays && !isDefaultDisplay && !isPrivate();
     }
 
     private void setInputMethodTarget(WindowState target, boolean targetWaitingAnim) {
@@ -4780,7 +4799,7 @@
     boolean supportsSystemDecorations() {
         return (mWmService.mDisplayWindowSettings.shouldShowSystemDecorsLocked(this)
                 || (mDisplay.getFlags() & FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS) != 0
-                || mWmService.mForceDesktopModeOnExternalDisplays)
+                || forceDesktopMode())
                 // VR virtual display will be used to run and render 2D app within a VR experience.
                 && mDisplayId != mWmService.mVr2dDisplayId
                 // Do not show system decorations on untrusted virtual display.
@@ -4977,7 +4996,7 @@
             }
 
             // Apply restriction if necessary.
-            if (needsGestureExclusionRestrictions(w, mLastDispatchedSystemUiVisibility)) {
+            if (needsGestureExclusionRestrictions(w, false /* ignoreRequest */)) {
 
                 // Processes the region along the left edge.
                 remainingLeftRight[0] = addToGlobalAndConsumeLimit(local, outExclusion, leftEdge,
@@ -4994,7 +5013,7 @@
                 outExclusion.op(middle, Op.UNION);
                 middle.recycle();
             } else {
-                boolean loggable = needsGestureExclusionRestrictions(w, 0 /* lastSysUiVis */);
+                boolean loggable = needsGestureExclusionRestrictions(w, true /* ignoreRequest */);
                 if (loggable) {
                     addToGlobalAndConsumeLimit(local, outExclusion, leftEdge,
                             Integer.MAX_VALUE, w, EXCLUSION_LEFT);
@@ -5016,17 +5035,21 @@
     }
 
     /**
-     * @return Whether gesture exclusion area should be restricted from the window depending on the
-     *         current SystemUI visibility flags.
+     * Returns whether gesture exclusion area should be restricted from the window depending on the
+     * window/activity types and the requested navigation bar visibility and the behavior.
+     *
+     * @param win The target window.
+     * @param ignoreRequest If this is {@code true}, only the window/activity types are considered.
+     * @return {@code true} if the gesture exclusion restrictions are needed.
      */
-    private static boolean needsGestureExclusionRestrictions(WindowState win, int sysUiVisibility) {
+    private static boolean needsGestureExclusionRestrictions(WindowState win,
+            boolean ignoreRequest) {
         final int type = win.mAttrs.type;
-        final int stickyHideNavFlags =
-                SYSTEM_UI_FLAG_HIDE_NAVIGATION | SYSTEM_UI_FLAG_IMMERSIVE_STICKY;
         final boolean stickyHideNav =
-                (sysUiVisibility & stickyHideNavFlags) == stickyHideNavFlags;
-        return !stickyHideNav && type != TYPE_INPUT_METHOD && type != TYPE_NOTIFICATION_SHADE
-                && win.getActivityType() != ACTIVITY_TYPE_HOME;
+                !win.getRequestedInsetsState().getSourceOrDefaultVisibility(ITYPE_NAVIGATION_BAR)
+                        && win.mAttrs.insetsFlags.behavior == BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE;
+        return (!stickyHideNav || ignoreRequest) && type != TYPE_INPUT_METHOD
+                && type != TYPE_NOTIFICATION_SHADE && win.getActivityType() != ACTIVITY_TYPE_HOME;
     }
 
     /**
@@ -5042,7 +5065,7 @@
                 && type != TYPE_APPLICATION_STARTING
                 && type != TYPE_NAVIGATION_BAR
                 && (attrs.flags & FLAG_NOT_TOUCHABLE) == 0
-                && needsGestureExclusionRestrictions(win, 0 /* sysUiVisibility */)
+                && needsGestureExclusionRestrictions(win, true /* ignoreRequest */)
                 && win.getDisplayContent().mDisplayPolicy.hasSideGestures();
     }
 
@@ -5168,11 +5191,10 @@
         final int displayId = mDisplay.getDisplayId();
         if (displayId != DEFAULT_DISPLAY) {
             final int displayState = mDisplay.getState();
-            if (displayState == Display.STATE_OFF && mOffToken == null) {
-                mOffToken = mAtmService.acquireSleepToken("Display-off", displayId);
-            } else if (displayState == Display.STATE_ON && mOffToken != null) {
-                mOffToken.release();
-                mOffToken = null;
+            if (displayState == Display.STATE_OFF) {
+                mOffTokenAcquirer.acquire(mDisplayId);
+            } else if (displayState == Display.STATE_ON) {
+                mOffTokenAcquirer.release(mDisplayId);
             }
         }
         mWmService.requestTraversal();
@@ -5424,7 +5446,8 @@
         mDisplayPolicy.release();
 
         if (!mAllSleepTokens.isEmpty()) {
-            mRootWindowContainer.mSleepTokens.removeAll(mAllSleepTokens);
+            mAllSleepTokens.forEach(token ->
+                    mRootWindowContainer.mSleepTokens.remove(token.mHashKey));
             mAllSleepTokens.clear();
             mAtmService.updateSleepIfNeededLocked();
         }
@@ -5641,6 +5664,9 @@
          */
         private ActivityRecord mAnimatingRecents;
 
+        /** Whether {@link #mAnimatingRecents} is going to be the top activity. */
+        private boolean mRecentsWillBeTop;
+
         /**
          * If the recents activity has a fixed orientation which is different from the current top
          * activity, it will be rotated before being shown so we avoid a screen rotation animation
@@ -5666,10 +5692,12 @@
          * If {@link #mAnimatingRecents} still has fixed rotation, it should be moved to top so we
          * don't clear {@link #mFixedRotationLaunchingApp} that will be handled by transition.
          */
-        void onFinishRecentsAnimation(boolean moveRecentsToBack) {
+        void onFinishRecentsAnimation() {
             final ActivityRecord animatingRecents = mAnimatingRecents;
+            final boolean recentsWillBeTop = mRecentsWillBeTop;
             mAnimatingRecents = null;
-            if (!moveRecentsToBack) {
+            mRecentsWillBeTop = false;
+            if (recentsWillBeTop) {
                 // The recents activity will be the top, such as staying at recents list or
                 // returning to home (if home and recents are the same activity).
                 return;
@@ -5692,6 +5720,10 @@
             }
         }
 
+        void notifyRecentsWillBeTop() {
+            mRecentsWillBeTop = true;
+        }
+
         /**
          * Return {@code true} if there is an ongoing animation to the "Recents" activity and this
          * activity as a fixed orientation so shouldn't be rotated.
@@ -5712,6 +5744,14 @@
             if (r == null || r == mAnimatingRecents) {
                 return;
             }
+            if (mAnimatingRecents != null && mRecentsWillBeTop) {
+                // The activity is not the recents and it should be moved to back later, so it is
+                // better to keep its current appearance for the next transition. Otherwise the
+                // display orientation may be updated too early and the layout procedures at the
+                // end of finishing recents animation is skipped. That causes flickering because
+                // the surface of closing app hasn't updated to invisible.
+                return;
+            }
             if (mFixedRotationLaunchingApp == null) {
                 // In most cases this is a no-op if the activity doesn't have fixed rotation.
                 // Otherwise it could be from finishing recents animation while the display has
@@ -5762,6 +5802,19 @@
             mRemoteInsetsController = controller;
         }
 
+        /**
+         * Notifies the remote insets controller that the top focused window has changed.
+         *
+         * @param packageName The name of the package that is open in the top focused window.
+         */
+        void topFocusedWindowChanged(String packageName) {
+            try {
+                mRemoteInsetsController.topFocusedWindowChanged(packageName);
+            } catch (RemoteException e) {
+                Slog.w(TAG, "Failed to deliver package in top focused window change", e);
+            }
+        }
+
         void notifyInsetsChanged() {
             try {
                 mRemoteInsetsController.insetsChanged(
diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java
index 7f827e4..515d1f5 100644
--- a/services/core/java/com/android/server/wm/DisplayPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayPolicy.java
@@ -29,6 +29,9 @@
 import static android.view.InsetsState.ITYPE_BOTTOM_GESTURES;
 import static android.view.InsetsState.ITYPE_BOTTOM_TAPPABLE_ELEMENT;
 import static android.view.InsetsState.ITYPE_CAPTION_BAR;
+import static android.view.InsetsState.ITYPE_CLIMATE_BAR;
+import static android.view.InsetsState.ITYPE_EXTRA_NAVIGATION_BAR;
+import static android.view.InsetsState.ITYPE_IME;
 import static android.view.InsetsState.ITYPE_LEFT_DISPLAY_CUTOUT;
 import static android.view.InsetsState.ITYPE_LEFT_GESTURES;
 import static android.view.InsetsState.ITYPE_NAVIGATION_BAR;
@@ -71,6 +74,7 @@
 import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES;
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_FORCE_DRAW_BAR_BACKGROUNDS;
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_FORCE_SHOW_STATUS_BAR;
+import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_INSET_PARENT_FRAME_BY_IME;
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_IS_SCREEN_DECOR;
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_STATUS_FORCE_SHOW_NAVIGATION;
 import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING;
@@ -102,6 +106,11 @@
 import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER;
 import static android.view.WindowManagerGlobal.ADD_OKAY;
 import static android.view.WindowManagerPolicyConstants.ACTION_HDMI_PLUGGED;
+import static android.view.WindowManagerPolicyConstants.ALT_BAR_BOTTOM;
+import static android.view.WindowManagerPolicyConstants.ALT_BAR_LEFT;
+import static android.view.WindowManagerPolicyConstants.ALT_BAR_RIGHT;
+import static android.view.WindowManagerPolicyConstants.ALT_BAR_TOP;
+import static android.view.WindowManagerPolicyConstants.ALT_BAR_UNKNOWN;
 import static android.view.WindowManagerPolicyConstants.EXTRA_HDMI_PLUGGED_STATE;
 import static android.view.WindowManagerPolicyConstants.NAV_BAR_BOTTOM;
 import static android.view.WindowManagerPolicyConstants.NAV_BAR_LEFT;
@@ -241,7 +250,8 @@
                     | View.STATUS_BAR_TRANSPARENT
                     | View.NAVIGATION_BAR_TRANSPARENT;
 
-    private static final int[] SHOW_TYPES_FOR_SWIPE = {ITYPE_NAVIGATION_BAR, ITYPE_STATUS_BAR};
+    private static final int[] SHOW_TYPES_FOR_SWIPE =
+            {ITYPE_NAVIGATION_BAR, ITYPE_STATUS_BAR, ITYPE_CLIMATE_BAR, ITYPE_EXTRA_NAVIGATION_BAR};
     private static final int[] SHOW_TYPES_FOR_PANIC = {ITYPE_NAVIGATION_BAR};
 
     private final WindowManagerService mService;
@@ -317,6 +327,27 @@
     private int[] mNavigationBarHeightForRotationInCarMode = new int[4];
     private int[] mNavigationBarWidthForRotationInCarMode = new int[4];
 
+    // Alternative status bar for when flexible insets mapping is used to place the status bar on
+    // another side of the screen.
+    private WindowState mStatusBarAlt = null;
+    @WindowManagerPolicy.AltBarPosition
+    private int mStatusBarAltPosition = ALT_BAR_UNKNOWN;
+    // Alternative navigation bar for when flexible insets mapping is used to place the navigation
+    // bar elsewhere on the screen.
+    private WindowState mNavigationBarAlt = null;
+    @WindowManagerPolicy.AltBarPosition
+    private int mNavigationBarAltPosition = ALT_BAR_UNKNOWN;
+    // Alternative climate bar for when flexible insets mapping is used to place a climate bar on
+    // the screen.
+    private WindowState mClimateBarAlt = null;
+    @WindowManagerPolicy.AltBarPosition
+    private int mClimateBarAltPosition = ALT_BAR_UNKNOWN;
+    // Alternative extra nav bar for when flexible insets mapping is used to place an extra nav bar
+    // on the screen.
+    private WindowState mExtraNavBarAlt = null;
+    @WindowManagerPolicy.AltBarPosition
+    private int mExtraNavBarAltPosition = ALT_BAR_UNKNOWN;
+
     /** See {@link #getNavigationBarFrameHeight} */
     private int[] mNavigationBarFrameHeightForRotationDefault = new int[4];
 
@@ -389,11 +420,6 @@
     private int mForcingShowNavBarLayer;
     private boolean mForceShowSystemBars;
 
-    /**
-     * Force the display of system bars regardless of other settings.
-     */
-    private boolean mForceShowSystemBarsFromExternal;
-
     private boolean mShowingDream;
     private boolean mLastShowingDream;
     private boolean mDreamingLockscreen;
@@ -439,7 +465,7 @@
                 case MSG_REQUEST_TRANSIENT_BARS:
                     synchronized (mLock) {
                         WindowState targetBar = (msg.arg1 == MSG_REQUEST_TRANSIENT_BARS_ARG_STATUS)
-                                ? mStatusBar : mNavigationBar;
+                                ? getStatusBar() : getNavigationBar();
                         if (targetBar != null) {
                             requestTransientBars(targetBar);
                         }
@@ -483,7 +509,6 @@
         final Resources r = mContext.getResources();
         mCarDockEnablesAccelerometer = r.getBoolean(R.bool.config_carDockEnablesAccelerometer);
         mDeskDockEnablesAccelerometer = r.getBoolean(R.bool.config_deskDockEnablesAccelerometer);
-        mForceShowSystemBarsFromExternal = r.getBoolean(R.bool.config_forceShowSystemBars);
 
         mAccessibilityManager = (AccessibilityManager) mContext.getSystemService(
                 Context.ACCESSIBILITY_SERVICE);
@@ -503,6 +528,7 @@
                             if (mStatusBar != null) {
                                 requestTransientBars(mStatusBar);
                             }
+                            checkAltBarSwipeForTransientBars(ALT_BAR_TOP);
                         }
                     }
 
@@ -513,6 +539,7 @@
                                     && mNavigationBarPosition == NAV_BAR_BOTTOM) {
                                 requestTransientBars(mNavigationBar);
                             }
+                            checkAltBarSwipeForTransientBars(ALT_BAR_BOTTOM);
                         }
                     }
 
@@ -529,6 +556,7 @@
                                             excludedRegion)) {
                                 requestTransientBars(mNavigationBar);
                             }
+                            checkAltBarSwipeForTransientBars(ALT_BAR_RIGHT);
                         }
                         excludedRegion.recycle();
                     }
@@ -546,6 +574,7 @@
                                             excludedRegion)) {
                                 requestTransientBars(mNavigationBar);
                             }
+                            checkAltBarSwipeForTransientBars(ALT_BAR_LEFT);
                         }
                         excludedRegion.recycle();
                     }
@@ -647,6 +676,21 @@
         mHandler.post(mGestureNavigationSettingsObserver::register);
     }
 
+    private void checkAltBarSwipeForTransientBars(@WindowManagerPolicy.AltBarPosition int pos) {
+        if (mStatusBarAlt != null && mStatusBarAltPosition == pos) {
+            requestTransientBars(mStatusBarAlt);
+        }
+        if (mNavigationBarAlt != null && mNavigationBarAltPosition == pos) {
+            requestTransientBars(mNavigationBarAlt);
+        }
+        if (mClimateBarAlt != null && mClimateBarAltPosition == pos) {
+            requestTransientBars(mClimateBarAlt);
+        }
+        if (mExtraNavBarAlt != null && mExtraNavBarAltPosition == pos) {
+            requestTransientBars(mExtraNavBarAlt);
+        }
+    }
+
     void systemReady() {
         mSystemGestures.systemReady();
         if (mService.mPointerLocationEnabled) {
@@ -701,17 +745,6 @@
         return mDockMode;
     }
 
-    /**
-     * @see WindowManagerService.setForceShowSystemBars
-     */
-    void setForceShowSystemBars(boolean forceShowSystemBars) {
-        mForceShowSystemBarsFromExternal = forceShowSystemBars;
-    }
-
-    boolean getForceShowSystemBars() {
-        return mForceShowSystemBarsFromExternal;
-    }
-
     public boolean hasNavigationBar() {
         return mHasNavigationBar;
     }
@@ -919,6 +952,20 @@
                 }
                 break;
         }
+
+        // Check if alternate bars positions were updated.
+        if (mStatusBarAlt == win) {
+            mStatusBarAltPosition = getAltBarPosition(attrs);
+        }
+        if (mNavigationBarAlt == win) {
+            mNavigationBarAltPosition = getAltBarPosition(attrs);
+        }
+        if (mClimateBarAlt == win) {
+            mClimateBarAltPosition = getAltBarPosition(attrs);
+        }
+        if (mExtraNavBarAlt == win) {
+            mExtraNavBarAltPosition = getAltBarPosition(attrs);
+        }
     }
 
     /**
@@ -958,10 +1005,9 @@
                 mContext.enforcePermission(
                         android.Manifest.permission.STATUS_BAR_SERVICE, callingPid, callingUid,
                         "DisplayPolicy");
-                if (mStatusBar != null) {
-                    if (mStatusBar.isAlive()) {
-                        return WindowManagerGlobal.ADD_MULTIPLE_SINGLETON;
-                    }
+                if ((mStatusBar != null && mStatusBar.isAlive())
+                        || (mStatusBarAlt != null && mStatusBarAlt.isAlive())) {
+                    return WindowManagerGlobal.ADD_MULTIPLE_SINGLETON;
                 }
                 break;
             case TYPE_NOTIFICATION_SHADE:
@@ -978,10 +1024,9 @@
                 mContext.enforcePermission(
                         android.Manifest.permission.STATUS_BAR_SERVICE, callingPid, callingUid,
                         "DisplayPolicy");
-                if (mNavigationBar != null) {
-                    if (mNavigationBar.isAlive()) {
-                        return WindowManagerGlobal.ADD_MULTIPLE_SINGLETON;
-                    }
+                if ((mNavigationBar != null && mNavigationBar.isAlive())
+                        || (mNavigationBarAlt != null && mNavigationBarAlt.isAlive())) {
+                    return WindowManagerGlobal.ADD_MULTIPLE_SINGLETON;
                 }
                 break;
             case TYPE_NAVIGATION_BAR_PANEL:
@@ -1013,6 +1058,33 @@
                     android.Manifest.permission.STATUS_BAR_SERVICE, callingPid, callingUid,
                     "DisplayPolicy");
             enforceSingleInsetsTypeCorrespondingToWindowType(attrs.providesInsetsTypes);
+
+            for (@InternalInsetsType int insetType : attrs.providesInsetsTypes) {
+                switch (insetType) {
+                    case ITYPE_STATUS_BAR:
+                        if ((mStatusBar != null && mStatusBar.isAlive())
+                                || (mStatusBarAlt != null && mStatusBarAlt.isAlive())) {
+                            return WindowManagerGlobal.ADD_MULTIPLE_SINGLETON;
+                        }
+                        break;
+                    case ITYPE_NAVIGATION_BAR:
+                        if ((mNavigationBar != null && mNavigationBar.isAlive())
+                                || (mNavigationBarAlt != null && mNavigationBarAlt.isAlive())) {
+                            return WindowManagerGlobal.ADD_MULTIPLE_SINGLETON;
+                        }
+                        break;
+                    case ITYPE_CLIMATE_BAR:
+                        if (mClimateBarAlt != null && mClimateBarAlt.isAlive()) {
+                            return WindowManagerGlobal.ADD_MULTIPLE_SINGLETON;
+                        }
+                        break;
+                    case ITYPE_EXTRA_NAVIGATION_BAR:
+                        if (mExtraNavBarAlt != null && mExtraNavBarAlt.isAlive()) {
+                            return WindowManagerGlobal.ADD_MULTIPLE_SINGLETON;
+                        }
+                        break;
+                }
+            }
         }
         return ADD_OKAY;
     }
@@ -1104,7 +1176,27 @@
                 break;
             default:
                 if (attrs.providesInsetsTypes != null) {
-                    for (int insetsType : attrs.providesInsetsTypes) {
+                    for (@InternalInsetsType int insetsType : attrs.providesInsetsTypes) {
+                        switch (insetsType) {
+                            case ITYPE_STATUS_BAR:
+                                mStatusBarAlt = win;
+                                mStatusBarController.setWindow(mStatusBarAlt);
+                                mStatusBarAltPosition = getAltBarPosition(attrs);
+                                break;
+                            case ITYPE_NAVIGATION_BAR:
+                                mNavigationBarAlt = win;
+                                mNavigationBarController.setWindow(mNavigationBarAlt);
+                                mNavigationBarAltPosition = getAltBarPosition(attrs);
+                                break;
+                            case ITYPE_CLIMATE_BAR:
+                                mClimateBarAlt = win;
+                                mClimateBarAltPosition = getAltBarPosition(attrs);
+                                break;
+                            case ITYPE_EXTRA_NAVIGATION_BAR:
+                                mExtraNavBarAlt = win;
+                                mExtraNavBarAltPosition = getAltBarPosition(attrs);
+                                break;
+                        }
                         mDisplayContent.setInsetProvider(insetsType, win, null);
                     }
                 }
@@ -1112,6 +1204,22 @@
         }
     }
 
+    @WindowManagerPolicy.AltBarPosition
+    private int getAltBarPosition(WindowManager.LayoutParams params) {
+        switch (params.gravity) {
+            case Gravity.LEFT:
+                return ALT_BAR_LEFT;
+            case Gravity.RIGHT:
+                return ALT_BAR_RIGHT;
+            case Gravity.BOTTOM:
+                return ALT_BAR_BOTTOM;
+            case Gravity.TOP:
+                return ALT_BAR_TOP;
+            default:
+                return ALT_BAR_UNKNOWN;
+        }
+    }
+
     TriConsumer<DisplayFrames, WindowState, Rect> getImeSourceFrameProvider() {
         return (displayFrames, windowState, inOutFrame) -> {
             if (mNavigationBar != null && navigationBarPosition(displayFrames.mDisplayWidth,
@@ -1136,6 +1244,8 @@
             switch (insetsType) {
                 case ITYPE_NAVIGATION_BAR:
                 case ITYPE_STATUS_BAR:
+                case ITYPE_CLIMATE_BAR:
+                case ITYPE_EXTRA_NAVIGATION_BAR:
                 case ITYPE_CAPTION_BAR:
                     if (++count > 1) {
                         throw new IllegalArgumentException(
@@ -1152,12 +1262,14 @@
      * @param win The window being removed.
      */
     void removeWindowLw(WindowState win) {
-        if (mStatusBar == win) {
+        if (mStatusBar == win || mStatusBarAlt == win) {
             mStatusBar = null;
+            mStatusBarAlt = null;
             mStatusBarController.setWindow(null);
             mDisplayContent.setInsetProvider(ITYPE_STATUS_BAR, null, null);
-        } else if (mNavigationBar == win) {
+        } else if (mNavigationBar == win || mNavigationBarAlt == win) {
             mNavigationBar = null;
+            mNavigationBarAlt = null;
             mNavigationBarController.setWindow(null);
             mDisplayContent.setInsetProvider(ITYPE_NAVIGATION_BAR, null, null);
         } else if (mNotificationShade == win) {
@@ -1165,6 +1277,12 @@
             if (mDisplayContent.isDefaultDisplay) {
                 mService.mPolicy.setKeyguardCandidateLw(null);
             }
+        } else if (mClimateBarAlt == win) {
+            mClimateBarAlt = null;
+            mDisplayContent.setInsetProvider(ITYPE_CLIMATE_BAR, null, null);
+        } else if (mExtraNavBarAlt == win) {
+            mExtraNavBarAlt = null;
+            mDisplayContent.setInsetProvider(ITYPE_EXTRA_NAVIGATION_BAR, null, null);
         }
         if (mLastFocusedWindow == win) {
             mLastFocusedWindow = null;
@@ -1183,7 +1301,7 @@
     }
 
     WindowState getStatusBar() {
-        return mStatusBar;
+        return mStatusBar != null ? mStatusBar : mStatusBarAlt;
     }
 
     WindowState getNotificationShade() {
@@ -1191,7 +1309,7 @@
     }
 
     WindowState getNavigationBar() {
-        return mNavigationBar;
+        return mNavigationBar != null ? mNavigationBar : mNavigationBarAlt;
     }
 
     /**
@@ -1253,6 +1371,47 @@
                     return R.anim.dock_left_enter;
                 }
             }
+        } else if (win == mStatusBarAlt || win == mNavigationBarAlt || win == mClimateBarAlt
+                || win == mExtraNavBarAlt) {
+            if (win.getAttrs().windowAnimations != 0) {
+                return ANIMATION_STYLEABLE;
+            }
+
+            int pos = (win == mStatusBarAlt) ? mStatusBarAltPosition : mNavigationBarAltPosition;
+
+            boolean isExitOrHide = transit == TRANSIT_EXIT || transit == TRANSIT_HIDE;
+            boolean isEnterOrShow = transit == TRANSIT_ENTER || transit == TRANSIT_SHOW;
+
+            switch (pos) {
+                case ALT_BAR_LEFT:
+                    if (isExitOrHide) {
+                        return R.anim.dock_left_exit;
+                    } else if (isEnterOrShow) {
+                        return R.anim.dock_left_enter;
+                    }
+                    break;
+                case ALT_BAR_RIGHT:
+                    if (isExitOrHide) {
+                        return R.anim.dock_right_exit;
+                    } else if (isEnterOrShow) {
+                        return R.anim.dock_right_enter;
+                    }
+                    break;
+                case ALT_BAR_BOTTOM:
+                    if (isExitOrHide) {
+                        return R.anim.dock_bottom_exit;
+                    } else if (isEnterOrShow) {
+                        return R.anim.dock_bottom_enter;
+                    }
+                    break;
+                case ALT_BAR_TOP:
+                    if (isExitOrHide) {
+                        return R.anim.dock_top_exit;
+                    } else if (isEnterOrShow) {
+                        return R.anim.dock_top_enter;
+                    }
+                    break;
+            }
         }
 
         if (transit == TRANSIT_PREVIEW_DONE) {
@@ -1433,7 +1592,7 @@
                             if (mInputConsumer == null) {
                                 return;
                             }
-                            showNavigationBar();
+                            showSystemBars();
                             // Any user activity always causes us to show the
                             // navigation controls, if they had been hidden.
                             // We also clear the low profile and only content
@@ -1468,13 +1627,13 @@
             }
         }
 
-        private void showNavigationBar() {
+        private void showSystemBars() {
             final InsetsSourceProvider provider = mDisplayContent.getInsetsStateController()
                     .peekSourceProvider(ITYPE_NAVIGATION_BAR);
             final InsetsControlTarget target =
                     provider != null ? provider.getControlTarget() : null;
             if (target != null) {
-                target.showInsets(Type.navigationBars(), false /* fromIme */);
+                target.showInsets(Type.systemBars(), false /* fromIme */);
             }
         }
     }
@@ -1611,7 +1770,7 @@
                 mInputConsumer = null;
                 Slog.v(TAG, INPUT_CONSUMER_NAVIGATION + " dismissed.");
             }
-        } else if (mInputConsumer == null && mStatusBar != null && canHideNavigationBar()) {
+        } else if (mInputConsumer == null && getStatusBar() != null && canHideNavigationBar()) {
             mInputConsumer = mDisplayContent.getInputMonitor().createInputConsumer(
                     mHandler.getLooper(),
                     INPUT_CONSUMER_NAVIGATION,
@@ -2086,6 +2245,13 @@
             df.set(left, top, dfu.right - right, dfu.bottom - bottom);
             if (attached == null) {
                 pf.set(df);
+                if ((pfl & PRIVATE_FLAG_INSET_PARENT_FRAME_BY_IME) != 0) {
+                    final InsetsSource source = mDisplayContent.getInsetsPolicy()
+                            .getInsetsForDispatch(win).peekSource(ITYPE_IME);
+                    if (source != null) {
+                        pf.inset(source.calculateInsets(pf, false /* ignoreVisibility */));
+                    }
+                }
                 vf.set(adjust != SOFT_INPUT_ADJUST_NOTHING
                         ? displayFrames.mCurrent : displayFrames.mDock);
             } else {
@@ -2493,6 +2659,14 @@
         }
 
         win.computeFrame(displayFrames);
+
+        // When system bars are added to the Android device through {@link #layoutStatusBar} and
+        // {@link #layoutNavigationBar}, the displayFrames are adjusted to take the system bars into
+        // account. The call below adjusts the display frames for system bars which use flexible
+        // insets mapping instead of {@link #layoutStatusbar} and {@link #layoutNavigationBar}. Note
+        // that this call is a no-op if not using flexible insets mapping.
+        adjustDisplayFramesForFlexibleInsets(win, displayFrames);
+
         // Dock windows carve out the bottom of the screen, so normal windows
         // can't appear underneath them.
         if (type == TYPE_INPUT_METHOD && win.isVisibleLw()
@@ -2505,6 +2679,40 @@
         }
     }
 
+    private void adjustDisplayFramesForFlexibleInsets(WindowState win,
+            DisplayFrames displayFrames) {
+        if (win == mStatusBarAlt) {
+            adjustDisplayFramesForWindow(win, mStatusBarAltPosition, displayFrames);
+        } else if (win == mNavigationBarAlt) {
+            adjustDisplayFramesForWindow(win, mNavigationBarAltPosition, displayFrames);
+        } else if (win == mClimateBarAlt) {
+            adjustDisplayFramesForWindow(win, mClimateBarAltPosition, displayFrames);
+        } else if (win == mExtraNavBarAlt) {
+            adjustDisplayFramesForWindow(win, mExtraNavBarAltPosition, displayFrames);
+        }
+    }
+
+    private static void adjustDisplayFramesForWindow(WindowState win,
+            @WindowManagerPolicy.AltBarPosition int position, DisplayFrames displayFrames) {
+        final Rect frame = win.getFrameLw();
+
+        // Note: This doesn't take into account display cutouts.
+        switch (position) {
+            case ALT_BAR_TOP:
+                displayFrames.mStable.top = frame.bottom;
+                break;
+            case ALT_BAR_BOTTOM:
+                displayFrames.mStable.bottom = displayFrames.mStableFullscreen.bottom = frame.top;
+                break;
+            case ALT_BAR_LEFT:
+                displayFrames.mStable.left = displayFrames.mStableFullscreen.left = frame.right;
+                break;
+            case ALT_BAR_RIGHT:
+                displayFrames.mStable.right = displayFrames.mStableFullscreen.right = frame.left;
+                break;
+        }
+    }
+
     private void layoutWallpaper(DisplayFrames displayFrames, Rect pf, Rect df, Rect cf) {
         // The wallpaper has Real Ultimate Power
         df.set(displayFrames.mUnrestricted);
@@ -2697,10 +2905,10 @@
             mDreamingLockscreen = mService.mPolicy.isKeyguardShowingAndNotOccluded();
         }
 
-        if (mStatusBar != null) {
+        if (getStatusBar() != null) {
             if (DEBUG_LAYOUT) Slog.i(TAG, "force=" + mForceStatusBar
                     + " top=" + mTopFullscreenOpaqueWindowState);
-            final boolean forceShowStatusBar = (mStatusBar.getAttrs().privateFlags
+            final boolean forceShowStatusBar = (getStatusBar().getAttrs().privateFlags
                     & PRIVATE_FLAG_FORCE_SHOW_STATUS_BAR) != 0;
             final boolean notificationShadeForcesShowingNavigation =
                     mNotificationShade != null
@@ -3186,6 +3394,16 @@
         return mNavigationBarPosition;
     }
 
+    @WindowManagerPolicy.AltBarPosition
+    int getAlternateStatusBarPosition() {
+        return mStatusBarAltPosition;
+    }
+
+    @WindowManagerPolicy.AltBarPosition
+    int getAlternateNavBarPosition() {
+        return mNavigationBarAltPosition;
+    }
+
     /**
      * A new window has been focused.
      */
@@ -3223,6 +3441,13 @@
                     (requestedState.getSourceOrDefaultVisibility(ITYPE_NAVIGATION_BAR)
                             ? Type.navigationBars() : 0)
                     | (requestedState.getSourceOrDefaultVisibility(ITYPE_STATUS_BAR)
+                            ? Type.statusBars() : 0)
+                    | (mExtraNavBarAlt != null
+                            && requestedState.getSourceOrDefaultVisibility(
+                                    ITYPE_EXTRA_NAVIGATION_BAR)
+                            ? Type.navigationBars() : 0)
+                    | (mClimateBarAlt != null
+                            && requestedState.getSourceOrDefaultVisibility(ITYPE_CLIMATE_BAR)
                             ? Type.statusBars() : 0);
 
             if (swipeTarget == mNavigationBar
@@ -3353,8 +3578,8 @@
         final boolean isFullscreen = (visibility & (View.SYSTEM_UI_FLAG_FULLSCREEN
                         | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION)) != 0
                 || (PolicyControl.getWindowFlags(win, win.mAttrs) & FLAG_FULLSCREEN) != 0
-                || (mStatusBar != null && insetsPolicy.isHidden(ITYPE_STATUS_BAR))
-                || (mNavigationBar != null && insetsPolicy.isHidden(
+                || (getStatusBar() != null && insetsPolicy.isHidden(ITYPE_STATUS_BAR))
+                || (getNavigationBar() != null && insetsPolicy.isHidden(
                         ITYPE_NAVIGATION_BAR));
         final int behavior = win.mAttrs.insetsFlags.behavior;
         final boolean isImmersive = (visibility & (View.SYSTEM_UI_FLAG_IMMERSIVE
@@ -3556,8 +3781,7 @@
         // We need to force system bars when the docked stack is visible, when the freeform stack
         // is focused but also when we are resizing for the transitions when docked stack
         // visibility changes.
-        mForceShowSystemBars = dockedStackVisible || win.inFreeformWindowingMode() || resizing
-                || mForceShowSystemBarsFromExternal;
+        mForceShowSystemBars = dockedStackVisible || win.inFreeformWindowingMode() || resizing;
         final boolean forceOpaqueStatusBar = mForceShowSystemBars && !isKeyguardShowing();
 
         // apply translucent bar vis flags
@@ -3617,7 +3841,7 @@
         final boolean hideNavBarSysui =
                 (vis & View.SYSTEM_UI_FLAG_HIDE_NAVIGATION) != 0;
 
-        final boolean transientStatusBarAllowed = mStatusBar != null
+        final boolean transientStatusBarAllowed = getStatusBar() != null
                 && (notificationShadeHasFocus || (!mForceShowSystemBars
                 && (hideStatusBarWM || (hideStatusBarSysui && immersiveSticky))));
 
@@ -3775,7 +3999,7 @@
     // TODO(b/118118435): Remove this after migration
     private boolean isImmersiveMode(int vis) {
         final int flags = View.SYSTEM_UI_FLAG_IMMERSIVE | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY;
-        return mNavigationBar != null
+        return getNavigationBar() != null
                 && (vis & View.SYSTEM_UI_FLAG_HIDE_NAVIGATION) != 0
                 && (vis & flags) != 0
                 && canHideNavigationBar();
@@ -3783,7 +4007,7 @@
 
     private boolean isImmersiveMode(WindowState win) {
         final int behavior = win.mAttrs.insetsFlags.behavior;
-        return mNavigationBar != null
+        return getNavigationBar() != null
                 && canHideNavigationBar()
                 && (behavior == BEHAVIOR_SHOW_BARS_BY_SWIPE
                         || behavior == BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE)
@@ -3863,8 +4087,8 @@
     public void takeScreenshot(int screenshotType, int source) {
         if (mScreenshotHelper != null) {
             mScreenshotHelper.takeScreenshot(screenshotType,
-                    mStatusBar != null && mStatusBar.isVisibleLw(),
-                    mNavigationBar != null && mNavigationBar.isVisibleLw(),
+                    getStatusBar() != null && getStatusBar().isVisibleLw(),
+                    getNavigationBar() != null && getNavigationBar().isVisibleLw(),
                     source, mHandler, null /* completionConsumer */);
         }
     }
@@ -3902,6 +4126,11 @@
         if (mStatusBar != null) {
             pw.print(prefix); pw.print("mStatusBar="); pw.print(mStatusBar);
         }
+        if (mStatusBarAlt != null) {
+            pw.print(prefix); pw.print("mStatusBarAlt="); pw.print(mStatusBarAlt);
+            pw.print(prefix); pw.print("mStatusBarAltPosition=");
+            pw.println(mStatusBarAltPosition);
+        }
         if (mNotificationShade != null) {
             pw.print(prefix); pw.print("mExpandedPanel="); pw.print(mNotificationShade);
         }
@@ -3913,6 +4142,21 @@
             pw.print(prefix); pw.print("mNavigationBarPosition=");
             pw.println(mNavigationBarPosition);
         }
+        if (mNavigationBarAlt != null) {
+            pw.print(prefix); pw.print("mNavigationBarAlt="); pw.println(mNavigationBarAlt);
+            pw.print(prefix); pw.print("mNavigationBarAltPosition=");
+            pw.println(mNavigationBarAltPosition);
+        }
+        if (mClimateBarAlt != null) {
+            pw.print(prefix); pw.print("mClimateBarAlt="); pw.println(mClimateBarAlt);
+            pw.print(prefix); pw.print("mClimateBarAltPosition=");
+            pw.println(mClimateBarAltPosition);
+        }
+        if (mExtraNavBarAlt != null) {
+            pw.print(prefix); pw.print("mExtraNavBarAlt="); pw.println(mExtraNavBarAlt);
+            pw.print(prefix); pw.print("mExtraNavBarAltPosition=");
+            pw.println(mExtraNavBarAltPosition);
+        }
         if (mFocusedWindow != null) {
             pw.print(prefix); pw.print("mFocusedWindow="); pw.println(mFocusedWindow);
         }
@@ -3931,8 +4175,8 @@
         }
         pw.print(prefix); pw.print("mTopIsFullscreen="); pw.println(mTopIsFullscreen);
         pw.print(prefix); pw.print("mForceStatusBar="); pw.print(mForceStatusBar);
-        pw.print(prefix); pw.print("mForceShowSystemBarsFromExternal=");
-        pw.print(mForceShowSystemBarsFromExternal);
+        pw.print(prefix); pw.print("mRemoteInsetsControllerControlsSystemBars");
+        pw.print(mDisplayContent.getInsetsPolicy().getRemoteInsetsControllerControlsSystemBars());
         pw.print(" mAllowLockscreenWhenOn="); pw.println(mAllowLockscreenWhenOn);
         mStatusBarController.dump(pw, prefix);
         mNavigationBarController.dump(pw, prefix);
diff --git a/services/core/java/com/android/server/wm/DisplayRotation.java b/services/core/java/com/android/server/wm/DisplayRotation.java
index 37ecee8..1284e00 100644
--- a/services/core/java/com/android/server/wm/DisplayRotation.java
+++ b/services/core/java/com/android/server/wm/DisplayRotation.java
@@ -330,10 +330,8 @@
         // It's also not likely to rotate a TV screen.
         final boolean isTv = mContext.getPackageManager().hasSystemFeature(
                 PackageManager.FEATURE_LEANBACK);
-        final boolean forceDesktopMode =
-                mService.mForceDesktopModeOnExternalDisplays && !isDefaultDisplay;
         mDefaultFixedToUserRotation =
-                (isCar || isTv || mService.mIsPc || forceDesktopMode)
+                (isCar || isTv || mService.mIsPc || mDisplayContent.forceDesktopMode())
                 // For debug purposes the next line turns this feature off with:
                 // $ adb shell setprop config.override_forced_orient true
                 // $ adb shell wm size reset
diff --git a/services/core/java/com/android/server/wm/DisplayWindowSettings.java b/services/core/java/com/android/server/wm/DisplayWindowSettings.java
index 470a02e..34b403b 100644
--- a/services/core/java/com/android/server/wm/DisplayWindowSettings.java
+++ b/services/core/java/com/android/server/wm/DisplayWindowSettings.java
@@ -248,7 +248,7 @@
         writeSettingsIfNeeded(entry, displayInfo);
     }
 
-    private int getWindowingModeLocked(Entry entry, int displayId) {
+    private int getWindowingModeLocked(Entry entry, DisplayContent dc) {
         int windowingMode = entry != null ? entry.mWindowingMode
                 : WindowConfiguration.WINDOWING_MODE_UNDEFINED;
         // This display used to be in freeform, but we don't support freeform anymore, so fall
@@ -259,10 +259,8 @@
         }
         // No record is present so use default windowing mode policy.
         if (windowingMode == WindowConfiguration.WINDOWING_MODE_UNDEFINED) {
-            final boolean forceDesktopMode = mService.mForceDesktopModeOnExternalDisplays
-                    && displayId != Display.DEFAULT_DISPLAY;
             windowingMode = mService.mAtmService.mSupportsFreeformWindowManagement
-                    && (mService.mIsPc || forceDesktopMode)
+                    && (mService.mIsPc || dc.forceDesktopMode())
                     ? WindowConfiguration.WINDOWING_MODE_FREEFORM
                     : WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
         }
@@ -272,7 +270,7 @@
     int getWindowingModeLocked(DisplayContent dc) {
         final DisplayInfo displayInfo = dc.getDisplayInfo();
         final Entry entry = getEntry(displayInfo);
-        return getWindowingModeLocked(entry, dc.getDisplayId());
+        return getWindowingModeLocked(entry, dc);
     }
 
     void setWindowingModeLocked(DisplayContent dc, int mode) {
@@ -382,7 +380,7 @@
         final Entry entry = getOrCreateEntry(displayInfo);
 
         // Setting windowing mode first, because it may override overscan values later.
-        dc.setWindowingMode(getWindowingModeLocked(entry, dc.getDisplayId()));
+        dc.setWindowingMode(getWindowingModeLocked(entry, dc));
 
         dc.getDisplayRotation().restoreSettings(entry.mUserRotationMode,
                 entry.mUserRotation, entry.mFixedToUserRotation);
diff --git a/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java b/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java
index e7fbc334..5ab48e15 100644
--- a/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java
+++ b/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java
@@ -138,8 +138,9 @@
                         && dcTarget.getParentWindow() == mImeTargetFromIme
                         && dcTarget.mSubLayer > mImeTargetFromIme.getWindow().mSubLayer)
                 || mImeTargetFromIme == mDisplayContent.getImeFallback()
+                || mImeTargetFromIme == mDisplayContent.mInputMethodInputTarget
                 || controlTarget == mImeTargetFromIme
-                        && (mImeTargetFromIme.getWindow() == null 
+                        && (mImeTargetFromIme.getWindow() == null
                                 || !mImeTargetFromIme.getWindow().isClosing());
     }
 
diff --git a/services/core/java/com/android/server/wm/InputMonitor.java b/services/core/java/com/android/server/wm/InputMonitor.java
index a70f98e..6041697 100644
--- a/services/core/java/com/android/server/wm/InputMonitor.java
+++ b/services/core/java/com/android/server/wm/InputMonitor.java
@@ -219,6 +219,11 @@
 
     WindowManagerPolicy.InputConsumer createInputConsumer(Looper looper, String name,
             InputEventReceiver.Factory inputEventReceiverFactory) {
+        if (!name.contentEquals(INPUT_CONSUMER_NAVIGATION)) {
+            throw new IllegalArgumentException("Illegal input consumer : " + name
+                    + ", display: " + mDisplayId);
+        }
+
         if (mInputConsumers.containsKey(name)) {
             throw new IllegalStateException("Existing input consumer found with name: " + name
                     + ", display: " + mDisplayId);
@@ -248,6 +253,11 @@
                 // stack, and we need FLAG_NOT_TOUCH_MODAL to ensure other events fall through
                 consumer.mWindowHandle.layoutParamsFlags |= FLAG_NOT_TOUCH_MODAL;
                 break;
+            case INPUT_CONSUMER_RECENTS_ANIMATION:
+                break;
+            default:
+                throw new IllegalArgumentException("Illegal input consumer : " + name
+                        + ", display: " + mDisplayId);
         }
         addInputConsumer(name, consumer);
     }
@@ -459,9 +469,6 @@
             mDisplayContent.forAllWindows(this,
                     true /* traverseTopToBottom */);
 
-            if (mAddWallpaperInputConsumerHandle) {
-                mWallpaperInputConsumer.show(mInputTransaction, 0);
-            }
             if (!mUpdateInputWindowsImmediately) {
                 mDisplayContent.getPendingTransaction().merge(mInputTransaction);
                 mDisplayContent.scheduleAnimation();
diff --git a/services/core/java/com/android/server/wm/InsetsPolicy.java b/services/core/java/com/android/server/wm/InsetsPolicy.java
index a145be0..fd67db1 100644
--- a/services/core/java/com/android/server/wm/InsetsPolicy.java
+++ b/services/core/java/com/android/server/wm/InsetsPolicy.java
@@ -45,7 +45,9 @@
 import android.view.WindowInsetsAnimation;
 import android.view.WindowInsetsAnimation.Bounds;
 import android.view.WindowInsetsAnimationControlListener;
+import android.view.WindowManager;
 
+import com.android.internal.R;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.server.DisplayThread;
 
@@ -96,12 +98,31 @@
     private BarWindow mStatusBar = new BarWindow(StatusBarManager.WINDOW_STATUS_BAR);
     private BarWindow mNavBar = new BarWindow(StatusBarManager.WINDOW_NAVIGATION_BAR);
     private boolean mAnimatingShown;
+
+    /**
+     * Let remote insets controller control system bars regardless of other settings.
+     */
+    private boolean mRemoteInsetsControllerControlsSystemBars;
     private final float[] mTmpFloat9 = new float[9];
 
     InsetsPolicy(InsetsStateController stateController, DisplayContent displayContent) {
         mStateController = stateController;
         mDisplayContent = displayContent;
         mPolicy = displayContent.getDisplayPolicy();
+        mRemoteInsetsControllerControlsSystemBars = mPolicy.getContext().getResources().getBoolean(
+                R.bool.config_remoteInsetsControllerControlsSystemBars);
+    }
+
+    boolean getRemoteInsetsControllerControlsSystemBars() {
+        return mRemoteInsetsControllerControlsSystemBars;
+    }
+
+    /**
+     * Used only for testing.
+     */
+    @VisibleForTesting
+    void setRemoteInsetsControllerControlsSystemBars(boolean controlsSystemBars) {
+        mRemoteInsetsControllerControlsSystemBars = controlsSystemBars;
     }
 
     /** Updates the target which can control system bars. */
@@ -252,6 +273,11 @@
             // Notification shade has control anyways, no reason to force anything.
             return focusedWin;
         }
+        if (remoteInsetsControllerControlsSystemBars(focusedWin)) {
+            mDisplayContent.mRemoteInsetsControlTarget.topFocusedWindowChanged(
+                    focusedWin.mAttrs.packageName);
+            return mDisplayContent.mRemoteInsetsControlTarget;
+        }
         if (forceShowsSystemBarsForWindowingMode) {
             // Status bar is forcibly shown for the windowing mode which is a steady state.
             // We don't want the client to control the status bar, and we will dispatch the real
@@ -281,6 +307,11 @@
             // Notification shade has control anyways, no reason to force anything.
             return focusedWin;
         }
+        if (remoteInsetsControllerControlsSystemBars(focusedWin)) {
+            mDisplayContent.mRemoteInsetsControlTarget.topFocusedWindowChanged(
+                    focusedWin.mAttrs.packageName);
+            return mDisplayContent.mRemoteInsetsControlTarget;
+        }
         if (forceShowsSystemBarsForWindowingMode) {
             // Navigation bar is forcibly shown for the windowing mode which is a steady state.
             // We don't want the client to control the navigation bar, and we will dispatch the real
@@ -296,6 +327,28 @@
         return focusedWin;
     }
 
+    /**
+     * Determines whether the remote insets controller should take control of system bars for all
+     * windows.
+     */
+    boolean remoteInsetsControllerControlsSystemBars(@Nullable WindowState focusedWin) {
+        if (focusedWin == null) {
+            return false;
+        }
+        if (!mRemoteInsetsControllerControlsSystemBars) {
+            return false;
+        }
+        if (mDisplayContent == null || mDisplayContent.mRemoteInsetsControlTarget == null) {
+            // No remote insets control target to take control of insets.
+            return false;
+        }
+        // If necessary, auto can control application windows when
+        // config_remoteInsetsControllerControlsSystemBars is set to true. This is useful in cases
+        // where we want to dictate system bar inset state for applications.
+        return focusedWin.getAttrs().type >= WindowManager.LayoutParams.FIRST_APPLICATION_WINDOW
+                && focusedWin.getAttrs().type <= WindowManager.LayoutParams.LAST_APPLICATION_WINDOW;
+    }
+
     private boolean forceShowsStatusBarTransiently() {
         final WindowState win = mPolicy.getStatusBar();
         return win != null && (win.mAttrs.privateFlags & PRIVATE_FLAG_FORCE_SHOW_STATUS_BAR) != 0;
@@ -317,10 +370,7 @@
         // We need to force system bars when the docked stack is visible, when the freeform stack
         // is visible but also when we are resizing for the transitions when docked stack
         // visibility changes.
-        return isDockedStackVisible
-                || isFreeformStackVisible
-                || isResizing
-                || mPolicy.getForceShowSystemBars();
+        return isDockedStackVisible || isFreeformStackVisible || isResizing;
     }
 
     @VisibleForTesting
diff --git a/services/core/java/com/android/server/wm/InsetsSourceProvider.java b/services/core/java/com/android/server/wm/InsetsSourceProvider.java
index 8e6a265..9fdfbd0 100644
--- a/services/core/java/com/android/server/wm/InsetsSourceProvider.java
+++ b/services/core/java/com/android/server/wm/InsetsSourceProvider.java
@@ -16,6 +16,8 @@
 
 package com.android.server.wm;
 
+import static android.view.InsetsState.ITYPE_CLIMATE_BAR;
+import static android.view.InsetsState.ITYPE_EXTRA_NAVIGATION_BAR;
 import static android.view.InsetsState.ITYPE_IME;
 import static android.view.InsetsState.ITYPE_NAVIGATION_BAR;
 import static android.view.InsetsState.ITYPE_STATUS_BAR;
@@ -94,7 +96,8 @@
                 new Point());
 
         final int type = source.getType();
-        if (type == ITYPE_STATUS_BAR || type == ITYPE_NAVIGATION_BAR) {
+        if (type == ITYPE_STATUS_BAR || type == ITYPE_NAVIGATION_BAR || type == ITYPE_CLIMATE_BAR
+                || type == ITYPE_EXTRA_NAVIGATION_BAR) {
             mControllable = sNewInsetsMode == NEW_INSETS_MODE_FULL;
         } else if (type == ITYPE_IME) {
             mControllable = sNewInsetsMode >= NEW_INSETS_MODE_IME;
diff --git a/services/core/java/com/android/server/wm/InsetsStateController.java b/services/core/java/com/android/server/wm/InsetsStateController.java
index 77bd4a4..68405c4 100644
--- a/services/core/java/com/android/server/wm/InsetsStateController.java
+++ b/services/core/java/com/android/server/wm/InsetsStateController.java
@@ -19,6 +19,8 @@
 import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
 import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
 import static android.view.InsetsState.ITYPE_CAPTION_BAR;
+import static android.view.InsetsState.ITYPE_CLIMATE_BAR;
+import static android.view.InsetsState.ITYPE_EXTRA_NAVIGATION_BAR;
 import static android.view.InsetsState.ITYPE_IME;
 import static android.view.InsetsState.ITYPE_INVALID;
 import static android.view.InsetsState.ITYPE_NAVIGATION_BAR;
@@ -43,6 +45,7 @@
 import android.view.InsetsState;
 import android.view.InsetsState.InternalInsetsType;
 import android.view.WindowManager;
+import android.view.WindowManager.LayoutParams.WindowType;
 
 import com.android.server.inputmethod.InputMethodManagerInternal;
 import com.android.server.protolog.common.ProtoLog;
@@ -112,7 +115,7 @@
     }
 
     InsetsState getInsetsForWindowMetrics(@NonNull WindowManager.LayoutParams attrs) {
-        final @InternalInsetsType int type = getInsetsTypeForWindowType(attrs.type);
+        final @InternalInsetsType int type = getInsetsTypeForLayoutParams(attrs);
         final WindowToken token = mDisplayContent.getWindowToken(attrs.token);
         final @WindowingMode int windowingMode = token != null
                 ? token.getWindowingMode() : WINDOWING_MODE_UNDEFINED;
@@ -132,7 +135,9 @@
         return false;
     }
 
-    private static @InternalInsetsType int getInsetsTypeForWindowType(int type) {
+    private static @InternalInsetsType
+            int getInsetsTypeForLayoutParams(WindowManager.LayoutParams attrs) {
+        @WindowType int type = attrs.type;
         switch (type) {
             case TYPE_STATUS_BAR:
                 return ITYPE_STATUS_BAR;
@@ -140,9 +145,22 @@
                 return ITYPE_NAVIGATION_BAR;
             case TYPE_INPUT_METHOD:
                 return ITYPE_IME;
-            default:
-                return ITYPE_INVALID;
         }
+
+        // If not one of the types above, check whether an internal inset mapping is specified.
+        if (attrs.providesInsetsTypes != null) {
+            for (@InternalInsetsType int insetsType : attrs.providesInsetsTypes) {
+                switch (insetsType) {
+                    case ITYPE_STATUS_BAR:
+                    case ITYPE_NAVIGATION_BAR:
+                    case ITYPE_CLIMATE_BAR:
+                    case ITYPE_EXTRA_NAVIGATION_BAR:
+                        return insetsType;
+                }
+            }
+        }
+
+        return ITYPE_INVALID;
     }
 
     /** @see #getInsetsForDispatch */
@@ -155,14 +173,15 @@
             state.removeSource(type);
 
             // Navigation bar doesn't get influenced by anything else
-            if (type == ITYPE_NAVIGATION_BAR) {
+            if (type == ITYPE_NAVIGATION_BAR || type == ITYPE_EXTRA_NAVIGATION_BAR) {
                 state.removeSource(ITYPE_IME);
                 state.removeSource(ITYPE_STATUS_BAR);
+                state.removeSource(ITYPE_CLIMATE_BAR);
                 state.removeSource(ITYPE_CAPTION_BAR);
             }
 
             // Status bar doesn't get influenced by caption bar
-            if (type == ITYPE_STATUS_BAR) {
+            if (type == ITYPE_STATUS_BAR || type == ITYPE_CLIMATE_BAR) {
                 state.removeSource(ITYPE_CAPTION_BAR);
             }
 
@@ -280,6 +299,7 @@
         }
         if (changed) {
             notifyInsetsChanged();
+            mDisplayContent.updateSystemGestureExclusion();
             mDisplayContent.getDisplayPolicy().updateSystemUiVisibilityLw();
         }
     }
@@ -331,8 +351,12 @@
             @Nullable InsetsControlTarget fakeNavControlling) {
         onControlChanged(ITYPE_STATUS_BAR, statusControlling);
         onControlChanged(ITYPE_NAVIGATION_BAR, navControlling);
+        onControlChanged(ITYPE_CLIMATE_BAR, statusControlling);
+        onControlChanged(ITYPE_EXTRA_NAVIGATION_BAR, navControlling);
         onControlFakeTargetChanged(ITYPE_STATUS_BAR, fakeStatusControlling);
         onControlFakeTargetChanged(ITYPE_NAVIGATION_BAR, fakeNavControlling);
+        onControlFakeTargetChanged(ITYPE_CLIMATE_BAR, fakeStatusControlling);
+        onControlFakeTargetChanged(ITYPE_EXTRA_NAVIGATION_BAR, fakeNavControlling);
         notifyPendingInsetsControlChanged();
     }
 
diff --git a/services/core/java/com/android/server/wm/KeyguardController.java b/services/core/java/com/android/server/wm/KeyguardController.java
index 9c535e4..8ba92b5 100644
--- a/services/core/java/com/android/server/wm/KeyguardController.java
+++ b/services/core/java/com/android/server/wm/KeyguardController.java
@@ -49,7 +49,6 @@
 
 import com.android.internal.policy.IKeyguardDismissCallback;
 import com.android.server.policy.WindowManagerPolicy;
-import com.android.server.wm.ActivityTaskManagerInternal.SleepToken;
 
 import java.io.PrintWriter;
 
@@ -73,11 +72,14 @@
     private final SparseArray<KeyguardDisplayState> mDisplayStates = new SparseArray<>();
     private final ActivityTaskManagerService mService;
     private RootWindowContainer mRootWindowContainer;
+    private final ActivityTaskManagerInternal.SleepTokenAcquirer mSleepTokenAcquirer;
+
 
     KeyguardController(ActivityTaskManagerService service,
             ActivityStackSupervisor stackSupervisor) {
         mService = service;
         mStackSupervisor = stackSupervisor;
+        mSleepTokenAcquirer = mService.new SleepTokenAcquirerImpl("keyguard");
     }
 
     void setWindowManager(WindowManagerService windowManager) {
@@ -411,17 +413,17 @@
 
     private void updateKeyguardSleepToken(int displayId) {
         final KeyguardDisplayState state = getDisplay(displayId);
-        if (isKeyguardUnoccludedOrAodShowing(displayId) && state.mSleepToken == null) {
-            state.acquiredSleepToken();
-        } else if (!isKeyguardUnoccludedOrAodShowing(displayId) && state.mSleepToken != null) {
-            state.releaseSleepToken();
+        if (isKeyguardUnoccludedOrAodShowing(displayId)) {
+            state.mSleepTokenAcquirer.acquire(displayId);
+        } else if (!isKeyguardUnoccludedOrAodShowing(displayId)) {
+            state.mSleepTokenAcquirer.release(displayId);
         }
     }
 
     private KeyguardDisplayState getDisplay(int displayId) {
         KeyguardDisplayState state = mDisplayStates.get(displayId);
         if (state == null) {
-            state = new KeyguardDisplayState(mService, displayId);
+            state = new KeyguardDisplayState(mService, displayId, mSleepTokenAcquirer);
             mDisplayStates.append(displayId, state);
         }
         return state;
@@ -442,29 +444,18 @@
         private ActivityRecord mDismissingKeyguardActivity;
         private boolean mRequestDismissKeyguard;
         private final ActivityTaskManagerService mService;
-        private SleepToken mSleepToken;
+        private final ActivityTaskManagerInternal.SleepTokenAcquirer mSleepTokenAcquirer;
 
-        KeyguardDisplayState(ActivityTaskManagerService service, int displayId) {
+        KeyguardDisplayState(ActivityTaskManagerService service, int displayId,
+                ActivityTaskManagerInternal.SleepTokenAcquirer acquirer) {
             mService = service;
             mDisplayId = displayId;
+            mSleepTokenAcquirer = acquirer;
         }
 
         void onRemoved() {
             mDismissingKeyguardActivity = null;
-            releaseSleepToken();
-        }
-
-        void acquiredSleepToken() {
-            if (mSleepToken == null) {
-                mSleepToken = mService.acquireSleepToken("keyguard", mDisplayId);
-            }
-        }
-
-        void releaseSleepToken() {
-            if (mSleepToken != null) {
-                mSleepToken.release();
-                mSleepToken = null;
-            }
+            mSleepTokenAcquirer.release(mDisplayId);
         }
 
         void visibilitiesUpdated(KeyguardController controller, DisplayContent display) {
diff --git a/services/core/java/com/android/server/wm/RecentsAnimationController.java b/services/core/java/com/android/server/wm/RecentsAnimationController.java
index a2b295a..65db23c 100644
--- a/services/core/java/com/android/server/wm/RecentsAnimationController.java
+++ b/services/core/java/com/android/server/wm/RecentsAnimationController.java
@@ -702,6 +702,11 @@
                         "cleanupAnimation(): Notify animation finished mPendingAnimations=%d "
                                 + "reorderMode=%d",
                         mPendingAnimations.size(), reorderMode);
+        if (reorderMode != REORDER_MOVE_TO_ORIGINAL_POSITION) {
+            // Notify the state at the beginning because the removeAnimation may notify the
+            // transition is finished. This is a signal that there will be a next transition.
+            mDisplayContent.mFixedRotationTransitionListener.notifyRecentsWillBeTop();
+        }
         for (int i = mPendingAnimations.size() - 1; i >= 0; i--) {
             final TaskAnimationAdapter taskAdapter = mPendingAnimations.get(i);
             if (reorderMode == REORDER_MOVE_TO_TOP || reorderMode == REORDER_KEEP_IN_PLACE) {
@@ -742,8 +747,7 @@
                         mTargetActivityRecord.token);
             }
         }
-        mDisplayContent.mFixedRotationTransitionListener.onFinishRecentsAnimation(
-                reorderMode == REORDER_MOVE_TO_ORIGINAL_POSITION /* moveRecentsToBack */);
+        mDisplayContent.mFixedRotationTransitionListener.onFinishRecentsAnimation();
 
         // Notify that the animation has ended
         if (mStatusBar != null) {
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index 39aa60b..72cd32f 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -48,6 +48,7 @@
 import static com.android.server.wm.ActivityStack.ActivityState.RESUMED;
 import static com.android.server.wm.ActivityStack.ActivityState.STOPPED;
 import static com.android.server.wm.ActivityStack.ActivityState.STOPPING;
+import static com.android.server.wm.ActivityStack.STACK_VISIBILITY_INVISIBLE;
 import static com.android.server.wm.ActivityStackSupervisor.DEFER_RESUME;
 import static com.android.server.wm.ActivityStackSupervisor.ON_TOP;
 import static com.android.server.wm.ActivityStackSupervisor.dumpHistoryList;
@@ -219,6 +220,9 @@
     // transaction from the global transaction.
     private final SurfaceControl.Transaction mDisplayTransaction;
 
+    /** The token acquirer to put stacks on the displays to sleep */
+    final ActivityTaskManagerInternal.SleepTokenAcquirer mDisplayOffTokenAcquirer;
+
     /**
      * The modes which affect which tasks are returned when calling
      * {@link RootWindowContainer#anyTaskForId(int)}.
@@ -258,7 +262,7 @@
      * They are used by components that may hide and block interaction with underlying
      * activities.
      */
-    final ArrayList<ActivityTaskManagerInternal.SleepToken> mSleepTokens = new ArrayList<>();
+    final SparseArray<SleepToken> mSleepTokens = new SparseArray<>();
 
     /** Set when a power hint has started, but not ended. */
     private boolean mPowerHintSent;
@@ -443,6 +447,7 @@
         mService = service.mAtmService;
         mStackSupervisor = mService.mStackSupervisor;
         mStackSupervisor.mRootWindowContainer = this;
+        mDisplayOffTokenAcquirer = mService.new SleepTokenAcquirerImpl("Display-off");
     }
 
     boolean updateFocusedWindowLocked(int mode, boolean updateInputWindows) {
@@ -1927,24 +1932,29 @@
     }
 
     boolean attachApplication(WindowProcessController app) throws RemoteException {
-        final String processName = app.mName;
         boolean didSomething = false;
         for (int displayNdx = getChildCount() - 1; displayNdx >= 0; --displayNdx) {
-            final DisplayContent display = getChildAt(displayNdx);
-            final ActivityStack stack = display.getFocusedStack();
-            if (stack == null) {
-                continue;
-            }
-
             mTmpRemoteException = null;
             mTmpBoolean = false; // Set to true if an activity was started.
-            final PooledFunction c = PooledLambda.obtainFunction(
-                    RootWindowContainer::startActivityForAttachedApplicationIfNeeded, this,
-                    PooledLambda.__(ActivityRecord.class), app, stack.topRunningActivity());
-            stack.forAllActivities(c);
-            c.recycle();
-            if (mTmpRemoteException != null) {
-                throw mTmpRemoteException;
+
+            final DisplayContent display = getChildAt(displayNdx);
+            for (int areaNdx = display.getTaskDisplayAreaCount() - 1; areaNdx >= 0; --areaNdx) {
+                final TaskDisplayArea taskDisplayArea = display.getTaskDisplayAreaAt(areaNdx);
+                for (int taskNdx = taskDisplayArea.getStackCount() - 1; taskNdx >= 0; --taskNdx) {
+                    final ActivityStack rootTask = taskDisplayArea.getStackAt(taskNdx);
+                    if (rootTask.getVisibility(null /*starting*/) == STACK_VISIBILITY_INVISIBLE) {
+                        break;
+                    }
+                    final PooledFunction c = PooledLambda.obtainFunction(
+                            RootWindowContainer::startActivityForAttachedApplicationIfNeeded, this,
+                            PooledLambda.__(ActivityRecord.class), app,
+                            rootTask.topRunningActivity());
+                    rootTask.forAllActivities(c);
+                    c.recycle();
+                    if (mTmpRemoteException != null) {
+                        throw mTmpRemoteException;
+                    }
+                }
             }
             didSomething |= mTmpBoolean;
         }
@@ -1962,8 +1972,8 @@
         }
 
         try {
-            if (mStackSupervisor.realStartActivityLocked(r, app, top == r /*andResume*/,
-                    true /*checkConfig*/)) {
+            if (mStackSupervisor.realStartActivityLocked(r, app,
+                    top == r && r.isFocusable() /*andResume*/, true /*checkConfig*/)) {
                 mTmpBoolean = true;
             }
         } catch (RemoteException e) {
@@ -2300,8 +2310,8 @@
         }
 
         for (int displayNdx = getChildCount() - 1; displayNdx >= 0; --displayNdx) {
-            boolean resumedOnDisplay = false;
             final DisplayContent display = getChildAt(displayNdx);
+            boolean resumedOnDisplay = false;
             for (int tdaNdx = display.getTaskDisplayAreaCount() - 1; tdaNdx >= 0; --tdaNdx) {
                 final TaskDisplayArea taskDisplayArea = display.getTaskDisplayAreaAt(tdaNdx);
                 for (int sNdx = taskDisplayArea.getStackCount() - 1; sNdx >= 0; --sNdx) {
@@ -2390,7 +2400,7 @@
                             // process the keyguard going away, which can happen before the sleep
                             // token is released. As a result, it is important we resume the
                             // activity here.
-                            resumeFocusedStacksTopActivities();
+                            stack.resumeTopActivityUncheckedLocked(null, null);
                         }
                         // The visibility update must not be called before resuming the top, so the
                         // display orientation can be updated first if needed. Otherwise there may
@@ -2635,20 +2645,29 @@
         }
     }
 
-    ActivityTaskManagerInternal.SleepToken createSleepToken(String tag, int displayId) {
+    SleepToken createSleepToken(String tag, int displayId) {
         final DisplayContent display = getDisplayContent(displayId);
         if (display == null) {
             throw new IllegalArgumentException("Invalid display: " + displayId);
         }
 
-        final SleepTokenImpl token = new SleepTokenImpl(tag, displayId);
-        mSleepTokens.add(token);
-        display.mAllSleepTokens.add(token);
+        final int tokenKey = makeSleepTokenKey(tag, displayId);
+        SleepToken token = mSleepTokens.get(tokenKey);
+        if (token == null) {
+            token = new SleepToken(tag, displayId);
+            mSleepTokens.put(tokenKey, token);
+            display.mAllSleepTokens.add(token);
+        } else {
+            throw new RuntimeException("Create the same sleep token twice: " + token);
+        }
         return token;
     }
 
-    private void removeSleepToken(SleepTokenImpl token) {
-        mSleepTokens.remove(token);
+    void removeSleepToken(SleepToken token) {
+        if (!mSleepTokens.contains(token.mHashKey)) {
+            Slog.d(TAG, "Remove non-exist sleep token: " + token + " from " + Debug.getCallers(6));
+        }
+        mSleepTokens.remove(token.mHashKey);
 
         final DisplayContent display = getDisplayContent(token.mDisplayId);
         if (display != null) {
@@ -3657,22 +3676,22 @@
         return printed;
     }
 
-    private final class SleepTokenImpl extends ActivityTaskManagerInternal.SleepToken {
+    private static int makeSleepTokenKey(String tag, int displayId) {
+        final String tokenKey = tag + displayId;
+        return tokenKey.hashCode();
+    }
+
+    static final class SleepToken {
         private final String mTag;
         private final long mAcquireTime;
         private final int mDisplayId;
+        final int mHashKey;
 
-        public SleepTokenImpl(String tag, int displayId) {
+        SleepToken(String tag, int displayId) {
             mTag = tag;
             mDisplayId = displayId;
             mAcquireTime = SystemClock.uptimeMillis();
-        }
-
-        @Override
-        public void release() {
-            synchronized (mService.mGlobalLock) {
-                removeSleepToken(this);
-            }
+            mHashKey = makeSleepTokenKey(mTag, mDisplayId);
         }
 
         @Override
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 3f197f7..db3c74f 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -1528,14 +1528,25 @@
      */
     void performClearTaskLocked() {
         mReuseTask = true;
-        performClearTask("clear-task-all");
-        mReuseTask = false;
+        mStackSupervisor.beginDeferResume();
+        try {
+            performClearTask("clear-task-all");
+        } finally {
+            mStackSupervisor.endDeferResume();
+            mReuseTask = false;
+        }
     }
 
     ActivityRecord performClearTaskForReuseLocked(ActivityRecord newR, int launchFlags) {
         mReuseTask = true;
-        final ActivityRecord result = performClearTaskLocked(newR, launchFlags);
-        mReuseTask = false;
+        mStackSupervisor.beginDeferResume();
+        final ActivityRecord result;
+        try {
+            result = performClearTaskLocked(newR, launchFlags);
+        } finally {
+            mStackSupervisor.endDeferResume();
+            mReuseTask = false;
+        }
         return result;
     }
 
@@ -2371,8 +2382,16 @@
             // For calculating screen layout, we need to use the non-decor inset screen area for the
             // calculation for compatibility reasons, i.e. screen area without system bars that
             // could never go away in Honeycomb.
-            final int compatScreenWidthDp = (int) (mTmpNonDecorBounds.width() / density);
-            final int compatScreenHeightDp = (int) (mTmpNonDecorBounds.height() / density);
+            int compatScreenWidthDp = (int) (mTmpNonDecorBounds.width() / density);
+            int compatScreenHeightDp = (int) (mTmpNonDecorBounds.height() / density);
+            // Use overrides if provided. If both overrides are provided, mTmpNonDecorBounds is
+            // undefined so it can't be used.
+            if (inOutConfig.screenWidthDp != Configuration.SCREEN_WIDTH_DP_UNDEFINED) {
+                compatScreenWidthDp = inOutConfig.screenWidthDp;
+            }
+            if (inOutConfig.screenHeightDp != Configuration.SCREEN_HEIGHT_DP_UNDEFINED) {
+                compatScreenHeightDp = inOutConfig.screenHeightDp;
+            }
             // Reducing the screen layout starting from its parent config.
             inOutConfig.screenLayout = computeScreenLayoutOverride(parentConfig.screenLayout,
                     compatScreenWidthDp, compatScreenHeightDp);
@@ -3608,6 +3627,9 @@
         info.topActivityInfo = mReuseActivitiesReport.top != null
                 ? mReuseActivitiesReport.top.info
                 : null;
+        info.requestedOrientation = mReuseActivitiesReport.base != null
+                ? mReuseActivitiesReport.base.getRequestedOrientation()
+                : SCREEN_ORIENTATION_UNSET;
     }
 
     /**
@@ -3649,6 +3671,10 @@
             return STACK_VISIBILITY_INVISIBLE;
         }
 
+        if (isTopActivityLaunchedBehind()) {
+            return STACK_VISIBILITY_VISIBLE;
+        }
+
         boolean gotSplitScreenStack = false;
         boolean gotOpaqueSplitScreenPrimary = false;
         boolean gotOpaqueSplitScreenSecondary = false;
@@ -3766,6 +3792,14 @@
                 : STACK_VISIBILITY_VISIBLE;
     }
 
+    private boolean isTopActivityLaunchedBehind() {
+        final ActivityRecord top = topRunningActivity();
+        if (top != null && top.mLaunchTaskBehind) {
+            return true;
+        }
+        return false;
+    }
+
     ActivityRecord isInTask(ActivityRecord r) {
         if (r == null) {
             return null;
diff --git a/services/core/java/com/android/server/wm/TaskOrganizerController.java b/services/core/java/com/android/server/wm/TaskOrganizerController.java
index f70bf18..e153bef 100644
--- a/services/core/java/com/android/server/wm/TaskOrganizerController.java
+++ b/services/core/java/com/android/server/wm/TaskOrganizerController.java
@@ -458,7 +458,8 @@
                 || mTmpTaskInfo.topActivityType != lastInfo.topActivityType
                 || mTmpTaskInfo.isResizeable != lastInfo.isResizeable
                 || mTmpTaskInfo.pictureInPictureParams != lastInfo.pictureInPictureParams
-                || !TaskDescription.equals(mTmpTaskInfo.taskDescription, lastInfo.taskDescription);
+                || !TaskDescription.equals(mTmpTaskInfo.taskDescription, lastInfo.taskDescription)
+                || mTmpTaskInfo.requestedOrientation != lastInfo.requestedOrientation;
         if (!changed) {
             int cfgChanges = mTmpTaskInfo.configuration.diff(lastInfo.configuration);
             final int winCfgChanges = (cfgChanges & ActivityInfo.CONFIG_WINDOW_CONFIGURATION) != 0
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index 0ade586..dd5afdd 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -2555,8 +2555,9 @@
             pw.print(prefix); pw.println("ContainerAnimator:");
             mSurfaceAnimator.dump(pw, prefix + "  ");
         }
-        if (mLastOrientationSource != null) {
+        if (mLastOrientationSource != null && this == mDisplayContent) {
             pw.println(prefix + "mLastOrientationSource=" + mLastOrientationSource);
+            pw.println(prefix + "deepestLastOrientationSource=" + getLastOrientationSource());
         }
     }
 
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index ef81c0a..b7a2eb3 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -17,6 +17,7 @@
 package com.android.server.wm;
 
 import static android.Manifest.permission.CONTROL_REMOTE_APP_TRANSITION_ANIMATIONS;
+import static android.Manifest.permission.INPUT_CONSUMER;
 import static android.Manifest.permission.INTERNAL_SYSTEM_WINDOW;
 import static android.Manifest.permission.MANAGE_ACTIVITY_STACKS;
 import static android.Manifest.permission.MANAGE_APP_TOKENS;
@@ -1476,9 +1477,14 @@
                         rootType, attrs.token, attrs.packageName)) {
                     return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
                 }
-                final IBinder binder = attrs.token != null ? attrs.token : client.asBinder();
-                token = new WindowToken(this, binder, type, false, displayContent,
-                        session.mCanAddInternalSystemWindow, isRoundedCornerOverlay);
+                if (hasParent) {
+                    // Use existing parent window token for child windows.
+                    token = parentWindow.mToken;
+                } else {
+                    final IBinder binder = attrs.token != null ? attrs.token : client.asBinder();
+                    token = new WindowToken(this, binder, type, false, displayContent,
+                            session.mCanAddInternalSystemWindow, isRoundedCornerOverlay);
+                }
             } else if (rootType >= FIRST_APPLICATION_WINDOW
                     && rootType <= LAST_APPLICATION_WINDOW) {
                 activity = token.asActivityRecord();
@@ -2163,6 +2169,10 @@
                     throw new IllegalArgumentException(
                             "Window type can not be changed after the window is added.");
                 }
+                if (!Arrays.equals(win.mAttrs.providesInsetsTypes, attrs.providesInsetsTypes)) {
+                    throw new IllegalArgumentException(
+                            "Insets types can not be changed after the window is added.");
+                }
 
                 // Odd choice but less odd than embedding in copyFrom()
                 if ((attrs.privateFlags & WindowManager.LayoutParams.PRIVATE_FLAG_PRESERVE_GEOMETRY)
@@ -5827,27 +5837,6 @@
         }
     }
 
-    @Override
-    public void setForceShowSystemBars(boolean show) {
-        boolean isAutomotive = mContext.getPackageManager().hasSystemFeature(
-                PackageManager.FEATURE_AUTOMOTIVE);
-        if (!isAutomotive) {
-            throw new UnsupportedOperationException("Force showing system bars is only supported"
-                    + "for Automotive use cases.");
-        }
-        if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.STATUS_BAR)
-                != PackageManager.PERMISSION_GRANTED) {
-            throw new SecurityException("Caller does not hold permission "
-                    + android.Manifest.permission.STATUS_BAR);
-        }
-        synchronized (mGlobalLock) {
-            final PooledConsumer c = PooledLambda.obtainConsumer(
-                    DisplayPolicy::setForceShowSystemBars, PooledLambda.__(), show);
-            mRoot.forAllDisplayPolicies(c);
-            c.recycle();
-        }
-    }
-
     public void setNavBarVirtualKeyHapticFeedbackEnabled(boolean enabled) {
         if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.STATUS_BAR)
                 != PackageManager.PERMISSION_GRANTED) {
@@ -5886,6 +5875,11 @@
     @Override
     public void createInputConsumer(IBinder token, String name, int displayId,
             InputChannel inputChannel) {
+        if (!mAtmInternal.isCallerRecents(Binder.getCallingUid())
+                && mContext.checkCallingOrSelfPermission(INPUT_CONSUMER) != PERMISSION_GRANTED) {
+            throw new SecurityException("createInputConsumer requires INPUT_CONSUMER permission");
+        }
+
         synchronized (mGlobalLock) {
             DisplayContent display = mRoot.getDisplayContent(displayId);
             if (display != null) {
@@ -5897,6 +5891,11 @@
 
     @Override
     public boolean destroyInputConsumer(String name, int displayId) {
+        if (!mAtmInternal.isCallerRecents(Binder.getCallingUid())
+                && mContext.checkCallingOrSelfPermission(INPUT_CONSUMER) != PERMISSION_GRANTED) {
+            throw new SecurityException("destroyInputConsumer requires INPUT_CONSUMER permission");
+        }
+
         synchronized (mGlobalLock) {
             DisplayContent display = mRoot.getDisplayContent(displayId);
             if (display != null) {
@@ -6188,7 +6187,7 @@
             }
             if (inputMethodControlTarget != null) {
                 pw.print("  inputMethodControlTarget in display# "); pw.print(displayId);
-                pw.print(' '); pw.println(inputMethodControlTarget.getWindow());
+                pw.print(' '); pw.println(inputMethodControlTarget);
             }
         });
         pw.print("  mInTouchMode="); pw.println(mInTouchMode);
diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java
index 930bfded..21a76ff 100644
--- a/services/core/java/com/android/server/wm/WindowOrganizerController.java
+++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java
@@ -335,7 +335,7 @@
                     }
                 }
             } else {
-                throw new RuntimeException("Reparenting leaf Tasks is not supported now.");
+                throw new RuntimeException("Reparenting leaf Tasks is not supported now. " + task);
             }
         } else {
             // Ugh, of course ActivityStack has its own special reorder logic...
diff --git a/services/core/java/com/android/server/wm/WindowProcessController.java b/services/core/java/com/android/server/wm/WindowProcessController.java
index 29cf177..df49ac7 100644
--- a/services/core/java/com/android/server/wm/WindowProcessController.java
+++ b/services/core/java/com/android/server/wm/WindowProcessController.java
@@ -186,14 +186,16 @@
 
     // Last configuration that was reported to the process.
     private final Configuration mLastReportedConfiguration = new Configuration();
-    // Configuration that is waiting to be dispatched to the process.
-    private Configuration mPendingConfiguration;
-    private final Configuration mNewOverrideConfig = new Configuration();
+    /** Whether the process configuration is waiting to be dispatched to the process. */
+    private boolean mHasPendingConfigurationChange;
     // Registered display id as a listener to override config change
     private int mDisplayId;
     private ActivityRecord mConfigActivityRecord;
     // Whether the activity config override is allowed for this process.
     private volatile boolean mIsActivityConfigOverrideAllowed = true;
+    /** Non-zero to pause dispatching process configuration change. */
+    private int mPauseConfigurationDispatchCount;
+
     /**
      * Activities that hosts some UI drawn by the current process. The activities live
      * in another process. This is used to check if the process is currently showing anything
@@ -1116,8 +1118,10 @@
         onMergedOverrideConfigurationChanged(Configuration.EMPTY);
     }
 
-    private void registerActivityConfigurationListener(ActivityRecord activityRecord) {
-        if (activityRecord == null || activityRecord.containsListener(this)) {
+    void registerActivityConfigurationListener(ActivityRecord activityRecord) {
+        if (activityRecord == null || activityRecord.containsListener(this)
+                // Check for the caller from outside of this class.
+                || !mIsActivityConfigOverrideAllowed) {
             return;
         }
         // A process can only register to one activityRecord to listen to the override configuration
@@ -1168,11 +1172,26 @@
     }
 
     @Override
+    public void onRequestedOverrideConfigurationChanged(Configuration overrideConfiguration) {
+        super.onRequestedOverrideConfigurationChanged(overrideConfiguration);
+    }
+
+    @Override
     public void onMergedOverrideConfigurationChanged(Configuration mergedOverrideConfig) {
+        super.onRequestedOverrideConfigurationChanged(mergedOverrideConfig);
+    }
+
+    @Override
+    void resolveOverrideConfiguration(Configuration newParentConfig) {
+        super.resolveOverrideConfiguration(newParentConfig);
+        final Configuration resolvedConfig = getResolvedOverrideConfiguration();
         // Make sure that we don't accidentally override the activity type.
-        mNewOverrideConfig.setTo(mergedOverrideConfig);
-        mNewOverrideConfig.windowConfiguration.setActivityType(ACTIVITY_TYPE_UNDEFINED);
-        super.onRequestedOverrideConfigurationChanged(mNewOverrideConfig);
+        resolvedConfig.windowConfiguration.setActivityType(ACTIVITY_TYPE_UNDEFINED);
+        // Activity has an independent ActivityRecord#mConfigurationSeq. If this process registers
+        // activity configuration, its config seq shouldn't go backwards by activity configuration.
+        // Otherwise if other places send wpc.getConfiguration() to client, the configuration may
+        // be ignored due to the seq is older.
+        resolvedConfig.seq = newParentConfig.seq;
     }
 
     private void updateConfiguration() {
@@ -1190,11 +1209,7 @@
         if (mListener.isCached()) {
             // This process is in a cached state. We will delay delivering the config change to the
             // process until the process is no longer cached.
-            if (mPendingConfiguration == null) {
-                mPendingConfiguration = new Configuration(config);
-            } else {
-                mPendingConfiguration.setTo(config);
-            }
+            mHasPendingConfigurationChange = true;
             return;
         }
 
@@ -1202,6 +1217,11 @@
     }
 
     private void dispatchConfigurationChange(Configuration config) {
+        if (mPauseConfigurationDispatchCount > 0) {
+            mHasPendingConfigurationChange = true;
+            return;
+        }
+        mHasPendingConfigurationChange = false;
         if (mThread == null) {
             if (Build.IS_DEBUGGABLE && mHasImeService) {
                 // TODO (b/135719017): Temporary log for debugging IME service.
@@ -1228,7 +1248,7 @@
         }
     }
 
-    private void setLastReportedConfiguration(Configuration config) {
+    void setLastReportedConfiguration(Configuration config) {
         mLastReportedConfiguration.setTo(config);
     }
 
@@ -1236,6 +1256,30 @@
         return mLastReportedConfiguration;
     }
 
+    void pauseConfigurationDispatch() {
+        mPauseConfigurationDispatchCount++;
+    }
+
+    void resumeConfigurationDispatch() {
+        mPauseConfigurationDispatchCount--;
+    }
+
+    /**
+     * This is called for sending {@link android.app.servertransaction.LaunchActivityItem}.
+     * The caller must call {@link #setLastReportedConfiguration} if the delivered configuration
+     * is newer.
+     */
+    Configuration prepareConfigurationForLaunchingActivity() {
+        final Configuration config = getConfiguration();
+        if (mHasPendingConfigurationChange) {
+            mHasPendingConfigurationChange = false;
+            // The global configuration may not change, so the client process may have the same
+            // config seq. This increment ensures that the client won't ignore the configuration.
+            config.seq = mAtm.increaseConfigurationSeqLocked();
+        }
+        return config;
+    }
+
     /** Returns the total time (in milliseconds) spent executing in both user and system code. */
     public long getCpuTime() {
         return (mListener != null) ? mListener.getCpuTime() : 0;
@@ -1327,10 +1371,8 @@
     public void onProcCachedStateChanged(boolean isCached) {
         if (!isCached) {
             synchronized (mAtm.mGlobalLockWithoutBoost) {
-                if (mPendingConfiguration != null) {
-                    final Configuration config = mPendingConfiguration;
-                    mPendingConfiguration = null;
-                    dispatchConfigurationChange(config);
+                if (mHasPendingConfigurationChange) {
+                    dispatchConfigurationChange(getConfiguration());
                 }
             }
         }
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 6a2c54e..3b8928e 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -2052,10 +2052,17 @@
         // animating... let's do something.
         final int left = mWindowFrames.mFrame.left;
         final int top = mWindowFrames.mFrame.top;
+
+        // During the transition from pip to fullscreen, the activity windowing mode is set to
+        // fullscreen at the beginning while the task is kept in pinned mode. Skip the move
+        // animation in such case since the transition is handled in SysUI.
+        final boolean hasMovementAnimation = getTask() == null
+                ? getWindowConfiguration().hasMovementAnimations()
+                : getTask().getWindowConfiguration().hasMovementAnimations();
         if (mToken.okToAnimate()
                 && (mAttrs.privateFlags & PRIVATE_FLAG_NO_MOVE_ANIMATION) == 0
                 && !isDragResizing()
-                && getWindowConfiguration().hasMovementAnimations()
+                && hasMovementAnimation
                 && !mWinAnimator.mLastHidden
                 && !mSeamlesslyRotated) {
             startMoveAnimation(left, top);
diff --git a/services/core/java/com/android/server/wm/WindowToken.java b/services/core/java/com/android/server/wm/WindowToken.java
index 86aacf3..d86f6c9 100644
--- a/services/core/java/com/android/server/wm/WindowToken.java
+++ b/services/core/java/com/android/server/wm/WindowToken.java
@@ -172,6 +172,12 @@
                 }
             }
         }
+
+        /** The state may not only be used by self. Make sure to leave the influence by others. */
+        void disassociate(WindowToken token) {
+            mAssociatedTokens.remove(token);
+            mRotatedContainers.remove(token);
+        }
     }
 
     private class DeathRecipient implements IBinder.DeathRecipient {
@@ -531,7 +537,7 @@
     void applyFixedRotationTransform(DisplayInfo info, DisplayFrames displayFrames,
             Configuration config) {
         if (mFixedRotationTransformState != null) {
-            return;
+            mFixedRotationTransformState.disassociate(this);
         }
         mFixedRotationTransformState = new FixedRotationTransformState(info, displayFrames,
                 new Configuration(config), mDisplayContent.getRotation());
@@ -539,8 +545,7 @@
         mDisplayContent.getDisplayPolicy().simulateLayoutDisplay(displayFrames,
                 mFixedRotationTransformState.mInsetsState,
                 mFixedRotationTransformState.mBarContentFrames);
-        onConfigurationChanged(getParent().getConfiguration());
-        notifyFixedRotationTransform(true /* enabled */);
+        onFixedRotationStatePrepared();
     }
 
     /**
@@ -548,17 +553,34 @@
      * one. This takes the same effect as {@link #applyFixedRotationTransform}.
      */
     void linkFixedRotationTransform(WindowToken other) {
-        if (mFixedRotationTransformState != null) {
+        final FixedRotationTransformState fixedRotationState = other.mFixedRotationTransformState;
+        if (fixedRotationState == null || mFixedRotationTransformState == fixedRotationState) {
             return;
         }
-        final FixedRotationTransformState fixedRotationState = other.mFixedRotationTransformState;
-        if (fixedRotationState == null) {
-            return;
+        if (mFixedRotationTransformState != null) {
+            mFixedRotationTransformState.disassociate(this);
         }
         mFixedRotationTransformState = fixedRotationState;
         fixedRotationState.mAssociatedTokens.add(this);
-        onConfigurationChanged(getParent().getConfiguration());
+        onFixedRotationStatePrepared();
+    }
+
+    /**
+     * Makes the rotated states take effect for this window container and its client process.
+     * This should only be called when {@link #mFixedRotationTransformState} is non-null.
+     */
+    private void onFixedRotationStatePrepared() {
+        // Send the adjustment info first so when the client receives configuration change, it can
+        // get the rotated display metrics.
         notifyFixedRotationTransform(true /* enabled */);
+        // Resolve the rotated configuration.
+        onConfigurationChanged(getParent().getConfiguration());
+        final ActivityRecord r = asActivityRecord();
+        if (r != null && r.hasProcess()) {
+            // The application needs to be configured as in a rotated environment for compatibility.
+            // This registration will send the rotated configuration to its process.
+            r.app.registerActivityConfigurationListener(r);
+        }
     }
 
     /**
@@ -609,15 +631,12 @@
         // The state is cleared at the end, because it is used to indicate that other windows can
         // use seamless rotation when applying rotation to display.
         for (int i = state.mAssociatedTokens.size() - 1; i >= 0; i--) {
-            state.mAssociatedTokens.get(i).cleanUpFixedRotationTransformState();
+            final WindowToken token = state.mAssociatedTokens.get(i);
+            token.mFixedRotationTransformState = null;
+            token.notifyFixedRotationTransform(false /* enabled */);
         }
     }
 
-    private void cleanUpFixedRotationTransformState() {
-        mFixedRotationTransformState = null;
-        notifyFixedRotationTransform(false /* enabled */);
-    }
-
     /** Notifies application side to enable or disable the rotation adjustment of display info. */
     private void notifyFixedRotationTransform(boolean enabled) {
         FixedRotationAdjustments adjustments = null;
@@ -681,8 +700,9 @@
         if (!isFixedRotationTransforming()) {
             return null;
         }
-        return new FixedRotationAdjustments(mFixedRotationTransformState.mDisplayInfo.rotation,
-                mFixedRotationTransformState.mDisplayInfo.displayCutout);
+        final DisplayInfo displayInfo = mFixedRotationTransformState.mDisplayInfo;
+        return new FixedRotationAdjustments(displayInfo.rotation, displayInfo.appWidth,
+                displayInfo.appHeight, displayInfo.displayCutout);
     }
 
     @Override
diff --git a/services/core/jni/Android.bp b/services/core/jni/Android.bp
index e897274..ab52696 100644
--- a/services/core/jni/Android.bp
+++ b/services/core/jni/Android.bp
@@ -105,6 +105,7 @@
         "libkeystore_binder",
         "libmtp",
         "libnativehelper",
+        "libprocessgroup",
         "libutils",
         "libui",
         "libinput",
diff --git a/services/core/jni/com_android_server_am_CachedAppOptimizer.cpp b/services/core/jni/com_android_server_am_CachedAppOptimizer.cpp
index 6a6da0e..678308a 100644
--- a/services/core/jni/com_android_server_am_CachedAppOptimizer.cpp
+++ b/services/core/jni/com_android_server_am_CachedAppOptimizer.cpp
@@ -29,11 +29,16 @@
 
 #include <nativehelper/JNIHelp.h>
 #include <android_runtime/AndroidRuntime.h>
+#include <binder/IPCThreadState.h>
 #include <jni.h>
+#include <processgroup/processgroup.h>
 
 using android::base::StringPrintf;
 using android::base::WriteStringToFile;
 
+#define SYNC_RECEIVED_WHILE_FROZEN (1)
+#define ASYNC_RECEIVED_WHILE_FROZEN (2)
+
 namespace android {
 
 // This performs per-process reclaim on all processes belonging to non-app UIDs.
@@ -74,9 +79,60 @@
     }
 }
 
+static void com_android_server_am_CachedAppOptimizer_enableFreezerInternal(
+        JNIEnv *env, jobject clazz, jboolean enable) {
+    bool success = true;
+
+    if (enable) {
+        success = SetTaskProfiles(0, {"FreezerEnabled"}, true);
+    } else {
+        success = SetTaskProfiles(0, {"FreezerDisabled"}, true);
+    }
+
+    if (!success) {
+        jniThrowException(env, "java/lang/RuntimeException", "Unknown error");
+    }
+}
+
+static void com_android_server_am_CachedAppOptimizer_freezeBinder(
+        JNIEnv *env, jobject clazz, jint pid, jboolean freeze) {
+
+    if (IPCThreadState::freeze(pid, freeze, 100 /* timeout [ms] */) != 0) {
+        jniThrowException(env, "java/lang/RuntimeException", "Unable to freeze/unfreeze binder");
+    }
+}
+
+static jint com_android_server_am_CachedAppOptimizer_getBinderFreezeInfo(JNIEnv *env,
+        jobject clazz, jint pid) {
+    bool syncReceived = false, asyncReceived = false;
+
+    int error = IPCThreadState::getProcessFreezeInfo(pid, &syncReceived, &asyncReceived);
+
+    if (error < 0) {
+        jniThrowException(env, "java/lang/RuntimeException", strerror(error));
+    }
+
+    jint retVal = 0;
+
+    if(syncReceived) {
+        retVal |= SYNC_RECEIVED_WHILE_FROZEN;;
+    }
+
+    if(asyncReceived) {
+        retVal |= ASYNC_RECEIVED_WHILE_FROZEN;
+    }
+
+    return retVal;
+}
+
 static const JNINativeMethod sMethods[] = {
     /* name, signature, funcPtr */
     {"compactSystem", "()V", (void*)com_android_server_am_CachedAppOptimizer_compactSystem},
+    {"enableFreezerInternal", "(Z)V",
+        (void*)com_android_server_am_CachedAppOptimizer_enableFreezerInternal},
+    {"freezeBinder", "(IZ)V", (void*)com_android_server_am_CachedAppOptimizer_freezeBinder},
+    {"getBinderFreezeInfo", "(I)I",
+        (void*)com_android_server_am_CachedAppOptimizer_getBinderFreezeInfo}
 };
 
 int register_android_server_am_CachedAppOptimizer(JNIEnv* env)
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index cc8c751..34408d8 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -229,8 +229,14 @@
             "com.android.server.usb.UsbService$Lifecycle";
     private static final String MIDI_SERVICE_CLASS =
             "com.android.server.midi.MidiService$Lifecycle";
+    private static final String WIFI_APEX_SERVICE_JAR_PATH =
+            "/apex/com.android.wifi/javalib/service-wifi.jar";
     private static final String WIFI_SERVICE_CLASS =
             "com.android.server.wifi.WifiService";
+    private static final String WIFI_SCANNING_SERVICE_CLASS =
+            "com.android.server.wifi.scanner.WifiScanningService";
+    private static final String WIFI_RTT_SERVICE_CLASS =
+            "com.android.server.wifi.rtt.RttService";
     private static final String WIFI_AWARE_SERVICE_CLASS =
             "com.android.server.wifi.aware.WifiAwareService";
     private static final String WIFI_P2P_SERVICE_CLASS =
@@ -1491,33 +1497,36 @@
                     PackageManager.FEATURE_WIFI)) {
                 // Wifi Service must be started first for wifi-related services.
                 t.traceBegin("StartWifi");
-                mSystemServiceManager.startService(WIFI_SERVICE_CLASS);
+                mSystemServiceManager.startServiceFromJar(
+                        WIFI_SERVICE_CLASS, WIFI_APEX_SERVICE_JAR_PATH);
                 t.traceEnd();
                 t.traceBegin("StartWifiScanning");
-                mSystemServiceManager.startService(
-                        "com.android.server.wifi.scanner.WifiScanningService");
+                mSystemServiceManager.startServiceFromJar(
+                        WIFI_SCANNING_SERVICE_CLASS, WIFI_APEX_SERVICE_JAR_PATH);
                 t.traceEnd();
             }
 
             if (context.getPackageManager().hasSystemFeature(
                     PackageManager.FEATURE_WIFI_RTT)) {
                 t.traceBegin("StartRttService");
-                mSystemServiceManager.startService(
-                        "com.android.server.wifi.rtt.RttService");
+                mSystemServiceManager.startServiceFromJar(
+                        WIFI_RTT_SERVICE_CLASS, WIFI_APEX_SERVICE_JAR_PATH);
                 t.traceEnd();
             }
 
             if (context.getPackageManager().hasSystemFeature(
                     PackageManager.FEATURE_WIFI_AWARE)) {
                 t.traceBegin("StartWifiAware");
-                mSystemServiceManager.startService(WIFI_AWARE_SERVICE_CLASS);
+                mSystemServiceManager.startServiceFromJar(
+                        WIFI_AWARE_SERVICE_CLASS, WIFI_APEX_SERVICE_JAR_PATH);
                 t.traceEnd();
             }
 
             if (context.getPackageManager().hasSystemFeature(
                     PackageManager.FEATURE_WIFI_DIRECT)) {
                 t.traceBegin("StartWifiP2P");
-                mSystemServiceManager.startService(WIFI_P2P_SERVICE_CLASS);
+                mSystemServiceManager.startServiceFromJar(
+                        WIFI_P2P_SERVICE_CLASS, WIFI_APEX_SERVICE_JAR_PATH);
                 t.traceEnd();
             }
 
diff --git a/services/people/java/com/android/server/people/prediction/ShareTargetPredictor.java b/services/people/java/com/android/server/people/prediction/ShareTargetPredictor.java
index 236ac84..9e6cf84 100644
--- a/services/people/java/com/android/server/people/prediction/ShareTargetPredictor.java
+++ b/services/people/java/com/android/server/people/prediction/ShareTargetPredictor.java
@@ -86,8 +86,8 @@
             return;
         }
         List<ShareTarget> shareTargets = getDirectShareTargets();
-        SharesheetModelScorer.computeScore(shareTargets, getShareEventType(mIntentFilter),
-                System.currentTimeMillis());
+        SharesheetModelScorer.computeScoreForDirectShare(shareTargets,
+                getShareEventType(mIntentFilter), System.currentTimeMillis());
         Collections.sort(shareTargets,
                 Comparator.comparing(ShareTarget::getScore, reverseOrder())
                         .thenComparing(t -> t.getAppTarget().getRank()));
diff --git a/services/people/java/com/android/server/people/prediction/SharesheetModelScorer.java b/services/people/java/com/android/server/people/prediction/SharesheetModelScorer.java
index c77843c..d4a502d 100644
--- a/services/people/java/com/android/server/people/prediction/SharesheetModelScorer.java
+++ b/services/people/java/com/android/server/people/prediction/SharesheetModelScorer.java
@@ -20,6 +20,7 @@
 import android.annotation.Nullable;
 import android.annotation.UserIdInt;
 import android.app.usage.UsageEvents;
+import android.provider.DeviceConfig;
 import android.util.ArrayMap;
 import android.util.Pair;
 import android.util.Range;
@@ -27,12 +28,14 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.app.ChooserActivity;
+import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
 import com.android.server.people.data.AppUsageStatsData;
 import com.android.server.people.data.DataManager;
 import com.android.server.people.data.Event;
 
 import java.time.Duration;
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.Comparator;
 import java.util.List;
 import java.util.Map;
@@ -46,6 +49,7 @@
     private static final String TAG = "SharesheetModelScorer";
     private static final boolean DEBUG = false;
     private static final Integer RECENCY_SCORE_COUNT = 6;
+    private static final Integer NATIVE_RANK_COUNT = 2;
     private static final float RECENCY_INITIAL_BASE_SCORE = 0.4F;
     private static final float RECENCY_SCORE_INITIAL_DECAY = 0.05F;
     private static final float RECENCY_SCORE_SUBSEQUENT_DECAY = 0.02F;
@@ -174,6 +178,77 @@
         postProcess(shareTargets, targetsLimit, dataManager, callingUserId);
     }
 
+    /**
+     * Computes ranking score for direct sharing. Update
+     * {@link ShareTargetPredictor.ShareTargetScore}.
+     */
+    static void computeScoreForDirectShare(List<ShareTargetPredictor.ShareTarget> shareTargets,
+            int shareEventType, long now) {
+        computeScore(shareTargets, shareEventType, now);
+        promoteTopNativeRankedShortcuts(shareTargets);
+    }
+
+    /**
+     * Promotes top (NATIVE_RANK_COUNT) shortcuts for each package and class, as per shortcut native
+     * ranking provided by apps.
+     */
+    private static void promoteTopNativeRankedShortcuts(
+            List<ShareTargetPredictor.ShareTarget> shareTargets) {
+        float topShortcutBonus = DeviceConfig.getFloat(
+                DeviceConfig.NAMESPACE_SYSTEMUI,
+                SystemUiDeviceConfigFlags.TOP_NATIVE_RANKED_SHARING_SHORTCUTS_BOOSTER,
+                0f);
+        float secondTopShortcutBonus = DeviceConfig.getFloat(
+                DeviceConfig.NAMESPACE_SYSTEMUI,
+                SystemUiDeviceConfigFlags.NON_TOP_NATIVE_RANKED_SHARING_SHORTCUTS_BOOSTER,
+                0f);
+        // Populates a map which key is a packageName and className pair, value is a max heap
+        // containing top (NATIVE_RANK_COUNT) shortcuts as per shortcut native ranking provided
+        // by apps.
+        Map<Pair<String, String>, PriorityQueue<ShareTargetPredictor.ShareTarget>>
+                topNativeRankedShareTargetMap = new ArrayMap<>();
+        for (ShareTargetPredictor.ShareTarget shareTarget : shareTargets) {
+            Pair<String, String> key = new Pair<>(shareTarget.getAppTarget().getPackageName(),
+                    shareTarget.getAppTarget().getClassName());
+            if (!topNativeRankedShareTargetMap.containsKey(key)) {
+                topNativeRankedShareTargetMap.put(key,
+                        new PriorityQueue<>(NATIVE_RANK_COUNT,
+                                Collections.reverseOrder(Comparator.comparingInt(
+                                        p -> p.getAppTarget().getRank()))));
+            }
+            PriorityQueue<ShareTargetPredictor.ShareTarget> rankMaxHeap =
+                    topNativeRankedShareTargetMap.get(key);
+            if (rankMaxHeap.isEmpty() || shareTarget.getAppTarget().getRank()
+                    < rankMaxHeap.peek().getAppTarget().getRank()) {
+                if (rankMaxHeap.size() == NATIVE_RANK_COUNT) {
+                    rankMaxHeap.poll();
+                }
+                rankMaxHeap.offer(shareTarget);
+            }
+        }
+        for (PriorityQueue<ShareTargetPredictor.ShareTarget> maxHeap :
+                topNativeRankedShareTargetMap.values()) {
+            while (!maxHeap.isEmpty()) {
+                ShareTargetPredictor.ShareTarget target = maxHeap.poll();
+                float bonus = maxHeap.isEmpty() ? topShortcutBonus : secondTopShortcutBonus;
+                target.setScore(probOR(target.getScore(), bonus));
+
+                if (DEBUG) {
+                    Slog.d(TAG, String.format(
+                            "SharesheetModel: promote top shortcut as per native ranking,"
+                                    + "packageName: %s, className: %s, shortcutId: %s, bonus:%.2f,"
+                                    + "total:%.2f",
+                            target.getAppTarget().getPackageName(),
+                            target.getAppTarget().getClassName(),
+                            target.getAppTarget().getShortcutInfo() != null
+                                    ? target.getAppTarget().getShortcutInfo().getId() : null,
+                            bonus,
+                            target.getScore()));
+                }
+            }
+        }
+    }
+
     private static void postProcess(List<ShareTargetPredictor.ShareTarget> shareTargets,
             int targetsLimit, @NonNull DataManager dataManager, @UserIdInt int callingUserId) {
         // Populates a map which key is package name and value is list of shareTargets descended
diff --git a/services/robotests/backup/src/com/android/server/backup/keyvalue/KeyValueBackupTaskTest.java b/services/robotests/backup/src/com/android/server/backup/keyvalue/KeyValueBackupTaskTest.java
index bd37e58..b450a80 100644
--- a/services/robotests/backup/src/com/android/server/backup/keyvalue/KeyValueBackupTaskTest.java
+++ b/services/robotests/backup/src/com/android/server/backup/keyvalue/KeyValueBackupTaskTest.java
@@ -123,6 +123,7 @@
 import com.android.server.testing.shadows.ShadowEventLog;
 import com.android.server.testing.shadows.ShadowSystemServiceRegistry;
 
+import com.google.common.base.Charsets;
 import com.google.common.truth.IterableSubject;
 
 import org.junit.After;
@@ -1911,7 +1912,8 @@
     }
 
     @Test
-    public void testRunTask_whenTransportReturnsError_updatesFilesAndCleansUp() throws Exception {
+    public void testRunTask_whenTransportReturnsErrorForGenericPackage_updatesFilesAndCleansUp()
+            throws Exception {
         TransportMock transportMock = setUpInitializedTransport(mTransport);
         when(transportMock.transport.performBackup(
                         argThat(packageInfo(PACKAGE_1)), any(), anyInt()))
@@ -1927,6 +1929,39 @@
         assertCleansUpFilesAndAgent(mTransport, PACKAGE_1);
     }
 
+    /**
+     * Checks that TRANSPORT_ERROR during @pm@ backup keeps the state file untouched.
+     * http://b/144030477
+     */
+    @Test
+    public void testRunTask_whenTransportReturnsErrorForPm_updatesFilesAndCleansUp()
+            throws Exception {
+        // TODO(tobiast): Refactor this method to share code with
+        //  testRunTask_whenTransportReturnsErrorForGenericPackage_updatesFilesAndCleansUp
+        // See patchset 7 of http://ag/11762961
+        final PackageData packageData = PM_PACKAGE;
+        TransportMock transportMock = setUpInitializedTransport(mTransport);
+        when(transportMock.transport.performBackup(
+                argThat(packageInfo(packageData)), any(), anyInt()))
+                .thenReturn(BackupTransport.TRANSPORT_ERROR);
+
+        byte[] pmStateBytes = "fake @pm@ state for testing".getBytes(Charsets.UTF_8);
+
+        Path pmStatePath = createPmStateFile(pmStateBytes.clone());
+        PackageManagerBackupAgent pmAgent = spy(createPmAgent());
+        KeyValueBackupTask task = createKeyValueBackupTask(transportMock, packageData);
+        runTask(task);
+        verify(pmAgent, never()).onBackup(any(), any(), any());
+
+        assertThat(Files.readAllBytes(pmStatePath)).isEqualTo(pmStateBytes.clone());
+
+        boolean existed = deletePmStateFile();
+        assertThat(existed).isTrue();
+        // unbindAgent() is skipped for @pm@. Comment in KeyValueBackupTask.java:
+        // "For PM metadata (for which applicationInfo is null) there is no agent-bound state."
+        assertCleansUpFiles(mTransport, packageData);
+    }
+
     @Test
     public void testRunTask_whenTransportGetBackupQuotaThrowsForPm() throws Exception {
         TransportMock transportMock = setUpInitializedTransport(mTransport);
@@ -2708,21 +2743,29 @@
      *       </ul>
      * </ul>
      */
-    private void createPmStateFile() throws IOException {
-        createPmStateFile(mTransport);
+    private Path createPmStateFile() throws IOException {
+        return createPmStateFile("pmState".getBytes());
     }
 
-    /** @see #createPmStateFile() */
-    private void createPmStateFile(TransportData transport) throws IOException {
-        Files.write(getStateFile(transport, PM_PACKAGE), "pmState".getBytes());
+    private Path createPmStateFile(byte[] bytes) throws IOException {
+        return createPmStateFile(bytes, mTransport);
+    }
+
+    private Path createPmStateFile(TransportData transport) throws IOException {
+        return createPmStateFile("pmState".getBytes(), mTransport);
+    }
+
+    /** @see #createPmStateFile(byte[]) */
+    private Path createPmStateFile(byte[] bytes, TransportData transport) throws IOException {
+        return Files.write(getStateFile(transport, PM_PACKAGE), bytes);
     }
 
     /**
      * Forces transport initialization and call to {@link
      * UserBackupManagerService#resetBackupState(File)}
      */
-    private void deletePmStateFile() throws IOException {
-        Files.deleteIfExists(getStateFile(mTransport, PM_PACKAGE));
+    private boolean deletePmStateFile() throws IOException {
+        return Files.deleteIfExists(getStateFile(mTransport, PM_PACKAGE));
     }
 
     /**
diff --git a/services/systemcaptions/java/com/android/server/systemcaptions/SystemCaptionsManagerService.java b/services/systemcaptions/java/com/android/server/systemcaptions/SystemCaptionsManagerService.java
index 27a116c..1586a33 100644
--- a/services/systemcaptions/java/com/android/server/systemcaptions/SystemCaptionsManagerService.java
+++ b/services/systemcaptions/java/com/android/server/systemcaptions/SystemCaptionsManagerService.java
@@ -34,7 +34,8 @@
                         context,
                         com.android.internal.R.string.config_defaultSystemCaptionsManagerService),
                 /*disallowProperty=*/ null,
-                /*packageUpdatePolicy=*/ PACKAGE_UPDATE_POLICY_REFRESH_EAGER);
+                /*packageUpdatePolicy=*/ PACKAGE_UPDATE_POLICY_REFRESH_EAGER
+                    | PACKAGE_RESTART_POLICY_REFRESH_EAGER);
     }
 
     @Override
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/gestures/TouchExplorerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/gestures/TouchExplorerTest.java
index 9053234..c18cad4 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/gestures/TouchExplorerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/gestures/TouchExplorerTest.java
@@ -31,6 +31,7 @@
 import android.testing.DexmakerShareClassLoaderRule;
 import android.view.InputDevice;
 import android.view.MotionEvent;
+import android.view.ViewConfiguration;
 
 import androidx.test.InstrumentationRegistry;
 import androidx.test.runner.AndroidJUnit4;
@@ -72,6 +73,8 @@
     private EventStreamTransformation mCaptor;
     private MotionEvent mLastEvent;
     private TouchExplorer mTouchExplorer;
+    private Context mContext;
+    private int mTouchSlop;
     private long mLastDownTime = Integer.MIN_VALUE;
 
     // mock package-private GestureManifold class
@@ -109,11 +112,12 @@
 
     @Before
     public void setUp() {
-        Context context = InstrumentationRegistry.getContext();
-        AccessibilityManagerService ams = new AccessibilityManagerService(context);
+        mContext = InstrumentationRegistry.getContext();
+        mTouchSlop = ViewConfiguration.get(mContext).getScaledTouchSlop();
+        AccessibilityManagerService ams = new AccessibilityManagerService(mContext);
         GestureManifold detector = mock(GestureManifold.class);
         mCaptor = new EventCaptor();
-        mTouchExplorer = new TouchExplorer(context, ams, detector);
+        mTouchExplorer = new TouchExplorer(mContext, ams, detector);
         mTouchExplorer.setNext(mCaptor);
     }
 
@@ -158,7 +162,7 @@
         goFromStateClearTo(STATE_DRAGGING_2FINGERS);
 
         assertState(STATE_DRAGGING);
-        assertCapturedEvents(MotionEvent.ACTION_DOWN);
+        assertCapturedEvents(MotionEvent.ACTION_DOWN, MotionEvent.ACTION_MOVE);
         assertCapturedEventsNoHistory();
     }
 
@@ -170,6 +174,7 @@
         assertState(STATE_DELEGATING);
         assertCapturedEvents(
                 /* goto dragging state */ MotionEvent.ACTION_DOWN,
+                MotionEvent.ACTION_MOVE,
                 /* leave dragging state */ MotionEvent.ACTION_UP,
                 MotionEvent.ACTION_DOWN,
                 MotionEvent.ACTION_POINTER_DOWN);
@@ -226,13 +231,13 @@
                 break;
                 case STATE_DRAGGING_2FINGERS: {
                     goFromStateClearTo(STATE_TOUCH_EXPLORING_2FINGER);
-                    moveEachPointers(mLastEvent, p(10, 0), p(10, 0));
+                    moveEachPointers(mLastEvent, p(mTouchSlop, 0), p(mTouchSlop, 0));
                     send(mLastEvent);
                 }
                 break;
                 case STATE_PINCH_2FINGERS: {
                     goFromStateClearTo(STATE_DRAGGING_2FINGERS);
-                    moveEachPointers(mLastEvent, p(10, 0), p(-10, 1));
+                    moveEachPointers(mLastEvent, p(mTouchSlop, 0), p(-mTouchSlop, 1));
                     send(mLastEvent);
                 }
                 break;
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/AuthServiceTest.java b/services/tests/servicestests/src/com/android/server/biometrics/AuthServiceTest.java
index 737eeaf..6167d0d 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/AuthServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/AuthServiceTest.java
@@ -28,6 +28,7 @@
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
+import android.app.AppOpsManager;
 import android.content.Context;
 import android.content.pm.PackageManager;
 import android.hardware.biometrics.IBiometricEnabledOnKeyguardCallback;
@@ -72,6 +73,8 @@
     IIrisService mIrisService;
     @Mock
     IFaceService mFaceService;
+    @Mock
+    AppOpsManager mAppOpsManager;
 
     @Before
     public void setUp() {
@@ -90,6 +93,7 @@
         when(mInjector.getFingerprintService()).thenReturn(mFingerprintService);
         when(mInjector.getFaceService()).thenReturn(mFaceService);
         when(mInjector.getIrisService()).thenReturn(mIrisService);
+        when(mInjector.getAppOps(any())).thenReturn(mAppOpsManager);
     }
 
     @Test
@@ -137,7 +141,9 @@
 
     // TODO(b/141025588): Check that an exception is thrown when the userId != callingUserId
     @Test
-    public void testAuthenticate_callsBiometricServiceAuthenticate() throws Exception {
+    public void testAuthenticate_appOpsOk_callsBiometricServiceAuthenticate() throws Exception {
+        when(mAppOpsManager.noteOp(eq(AppOpsManager.OP_USE_BIOMETRIC), anyInt(), any(), any(),
+                any())).thenReturn(AppOpsManager.MODE_ALLOWED);
         mAuthService = new AuthService(mContext, mInjector);
         mAuthService.onStart();
 
@@ -167,6 +173,38 @@
     }
 
     @Test
+    public void testAuthenticate_appOpsDenied_doesNotCallBiometricService() throws Exception {
+        when(mAppOpsManager.noteOp(eq(AppOpsManager.OP_USE_BIOMETRIC), anyInt(), any(), any(),
+                any())).thenReturn(AppOpsManager.MODE_ERRORED);
+        mAuthService = new AuthService(mContext, mInjector);
+        mAuthService.onStart();
+
+        final Binder token = new Binder();
+        final Bundle bundle = new Bundle();
+        final long sessionId = 0;
+        final int userId = 0;
+
+        mAuthService.mImpl.authenticate(
+                token,
+                sessionId,
+                userId,
+                mReceiver,
+                TEST_OP_PACKAGE_NAME,
+                bundle);
+        waitForIdle();
+        verify(mBiometricService, never()).authenticate(
+                eq(token),
+                eq(sessionId),
+                eq(userId),
+                eq(mReceiver),
+                eq(TEST_OP_PACKAGE_NAME),
+                eq(bundle),
+                eq(Binder.getCallingUid()),
+                eq(Binder.getCallingPid()),
+                eq(UserHandle.getCallingUserId()));
+    }
+
+    @Test
     public void testCanAuthenticate_callsBiometricServiceCanAuthenticate() throws Exception {
         mAuthService = new AuthService(mContext, mInjector);
         mAuthService.onStart();
diff --git a/services/tests/servicestests/src/com/android/server/people/prediction/SharesheetModelScorerTest.java b/services/tests/servicestests/src/com/android/server/people/prediction/SharesheetModelScorerTest.java
index 45fff48..605878d 100644
--- a/services/tests/servicestests/src/com/android/server/people/prediction/SharesheetModelScorerTest.java
+++ b/services/tests/servicestests/src/com/android/server/people/prediction/SharesheetModelScorerTest.java
@@ -20,6 +20,7 @@
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.anyLong;
 import static org.mockito.ArgumentMatchers.anySet;
+import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
@@ -28,9 +29,13 @@
 import android.app.prediction.AppTarget;
 import android.app.prediction.AppTargetId;
 import android.app.usage.UsageEvents;
+import android.content.Context;
+import android.content.pm.ShortcutInfo;
 import android.os.UserHandle;
+import android.provider.DeviceConfig;
 import android.util.Range;
 
+import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
 import com.android.server.people.data.AppUsageStatsData;
 import com.android.server.people.data.DataManager;
 import com.android.server.people.data.Event;
@@ -121,6 +126,13 @@
     private ShareTargetPredictor.ShareTarget mShareTarget5;
     private ShareTargetPredictor.ShareTarget mShareTarget6;
 
+    private ShareTargetPredictor.ShareTarget mShareShortcutTarget1;
+    private ShareTargetPredictor.ShareTarget mShareShortcutTarget2;
+    private ShareTargetPredictor.ShareTarget mShareShortcutTarget3;
+    private ShareTargetPredictor.ShareTarget mShareShortcutTarget4;
+    private ShareTargetPredictor.ShareTarget mShareShortcutTarget5;
+    private ShareTargetPredictor.ShareTarget mShareShortcutTarget6;
+
     @Before
     public void setUp() {
         MockitoAnnotations.initMocks(this);
@@ -154,6 +166,46 @@
                         new AppTargetId("cls2#pkg3"), PACKAGE_3, UserHandle.of(USER_ID))
                         .setClassName(CLASS_2).build(),
                 null, null);
+
+        mShareShortcutTarget1 = new ShareTargetPredictor.ShareTarget(
+                new AppTarget.Builder(
+                        new AppTargetId("cls1#pkg1#1"), buildShortcutInfo(PACKAGE_1, 0, "1"))
+                        .setClassName(CLASS_1).setRank(2).build(),
+                mEventHistory1, null);
+        mShareShortcutTarget2 = new ShareTargetPredictor.ShareTarget(
+                new AppTarget.Builder(
+                        new AppTargetId("cls1#pkg1#2"), buildShortcutInfo(PACKAGE_1, 0, "2"))
+                        .setClassName(CLASS_1).setRank(1).build(),
+                mEventHistory2, null);
+        mShareShortcutTarget3 = new ShareTargetPredictor.ShareTarget(
+                new AppTarget.Builder(
+                        new AppTargetId("cls1#pkg1#3"), buildShortcutInfo(PACKAGE_1, 0, "3"))
+                        .setClassName(CLASS_1).setRank(0).build(),
+                mEventHistory3, null);
+        mShareShortcutTarget4 = new ShareTargetPredictor.ShareTarget(
+                new AppTarget.Builder(
+                        new AppTargetId("cls1#pkg2#1"), buildShortcutInfo(PACKAGE_2, 0, "1"))
+                        .setClassName(CLASS_1).setRank(2).build(),
+                mEventHistory4, null);
+        mShareShortcutTarget5 = new ShareTargetPredictor.ShareTarget(
+                new AppTarget.Builder(
+                        new AppTargetId("cls1#pkg2#2"), buildShortcutInfo(PACKAGE_2, 0, "2"))
+                        .setClassName(CLASS_1).setRank(1).build(),
+                mEventHistory5, null);
+        mShareShortcutTarget6 = new ShareTargetPredictor.ShareTarget(
+                new AppTarget.Builder(
+                        new AppTargetId("cls1#pkg2#3"), buildShortcutInfo(PACKAGE_2, 0, "3"))
+                        .setClassName(CLASS_1).setRank(3).build(),
+                null, null);
+
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI,
+                SystemUiDeviceConfigFlags.TOP_NATIVE_RANKED_SHARING_SHORTCUTS_BOOSTER,
+                Float.toString(0f),
+                true /* makeDefault*/);
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI,
+                SystemUiDeviceConfigFlags.NON_TOP_NATIVE_RANKED_SHARING_SHORTCUTS_BOOSTER,
+                Float.toString(0f),
+                true /* makeDefault*/);
     }
 
     @Test
@@ -433,6 +485,101 @@
         assertEquals(0f, mShareTarget6.getScore(), DELTA);
     }
 
+    @Test
+    public void testComputeScoreForDirectShare() {
+        // Frequency and recency
+        when(mEventHistory1.getEventIndex(anySet())).thenReturn(mEventIndex1);
+        when(mEventHistory2.getEventIndex(anySet())).thenReturn(mEventIndex2);
+        when(mEventHistory3.getEventIndex(anySet())).thenReturn(mEventIndex3);
+        when(mEventHistory4.getEventIndex(anySet())).thenReturn(mEventIndex4);
+        when(mEventHistory5.getEventIndex(anySet())).thenReturn(mEventIndex5);
+
+        when(mEventIndex1.getActiveTimeSlots()).thenReturn(
+                List.of(WITHIN_ONE_DAY, TWO_DAYS_AGO, FIVE_DAYS_AGO));
+        when(mEventIndex2.getActiveTimeSlots()).thenReturn(List.of(TWO_DAYS_AGO, TWELVE_DAYS_AGO));
+        when(mEventIndex3.getActiveTimeSlots()).thenReturn(List.of(FIVE_DAYS_AGO, TWENTY_DAYS_AGO));
+        when(mEventIndex4.getActiveTimeSlots()).thenReturn(
+                List.of(EIGHT_DAYS_AGO, TWELVE_DAYS_AGO, FOUR_WEEKS_AGO));
+        when(mEventIndex5.getActiveTimeSlots()).thenReturn(List.of());
+
+        when(mEventIndex1.getMostRecentActiveTimeSlot()).thenReturn(WITHIN_ONE_DAY);
+        when(mEventIndex2.getMostRecentActiveTimeSlot()).thenReturn(TWO_DAYS_AGO);
+        when(mEventIndex3.getMostRecentActiveTimeSlot()).thenReturn(FIVE_DAYS_AGO);
+        when(mEventIndex4.getMostRecentActiveTimeSlot()).thenReturn(EIGHT_DAYS_AGO);
+        when(mEventIndex5.getMostRecentActiveTimeSlot()).thenReturn(null);
+
+        // Frequency of the same mime type
+        when(mEventHistory1.getEventIndex(Event.TYPE_SHARE_TEXT)).thenReturn(mEventIndex6);
+        when(mEventHistory2.getEventIndex(Event.TYPE_SHARE_TEXT)).thenReturn(mEventIndex7);
+        when(mEventHistory3.getEventIndex(Event.TYPE_SHARE_TEXT)).thenReturn(mEventIndex8);
+        when(mEventHistory4.getEventIndex(Event.TYPE_SHARE_TEXT)).thenReturn(mEventIndex9);
+        when(mEventHistory5.getEventIndex(Event.TYPE_SHARE_TEXT)).thenReturn(mEventIndex10);
+
+        when(mEventIndex6.getActiveTimeSlots()).thenReturn(List.of(TWO_DAYS_AGO));
+        when(mEventIndex7.getActiveTimeSlots()).thenReturn(List.of(TWO_DAYS_AGO, TWELVE_DAYS_AGO));
+        when(mEventIndex8.getActiveTimeSlots()).thenReturn(List.of());
+        when(mEventIndex9.getActiveTimeSlots()).thenReturn(List.of(EIGHT_DAYS_AGO));
+        when(mEventIndex10.getActiveTimeSlots()).thenReturn(List.of());
+
+        SharesheetModelScorer.computeScore(
+                List.of(mShareShortcutTarget1, mShareShortcutTarget2, mShareShortcutTarget3,
+                        mShareShortcutTarget4, mShareShortcutTarget5, mShareShortcutTarget6),
+                Event.TYPE_SHARE_TEXT,
+                NOW);
+
+        // Verification
+        assertEquals(0.514f, mShareShortcutTarget1.getScore(), DELTA);
+        assertEquals(0.475125f, mShareShortcutTarget2.getScore(), DELTA);
+        assertEquals(0.33f, mShareShortcutTarget3.getScore(), DELTA);
+        assertEquals(0.4411f, mShareShortcutTarget4.getScore(), DELTA);
+        assertEquals(0f, mShareShortcutTarget5.getScore(), DELTA);
+        assertEquals(0f, mShareShortcutTarget6.getScore(), DELTA);
+    }
+
+    @Test
+    public void testComputeScoreForDirectShare_promoteTopNativeRankedShortcuts() {
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI,
+                SystemUiDeviceConfigFlags.TOP_NATIVE_RANKED_SHARING_SHORTCUTS_BOOSTER,
+                Float.toString(0.4f),
+                true /* makeDefault*/);
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI,
+                SystemUiDeviceConfigFlags.NON_TOP_NATIVE_RANKED_SHARING_SHORTCUTS_BOOSTER,
+                Float.toString(0.3f),
+                true /* makeDefault*/);
+
+        when(mEventHistory1.getEventIndex(anySet())).thenReturn(mEventIndex1);
+        when(mEventHistory2.getEventIndex(anySet())).thenReturn(mEventIndex2);
+        when(mEventHistory3.getEventIndex(anySet())).thenReturn(mEventIndex3);
+        when(mEventHistory4.getEventIndex(anySet())).thenReturn(mEventIndex4);
+        when(mEventHistory5.getEventIndex(anySet())).thenReturn(mEventIndex5);
+        when(mEventHistory1.getEventIndex(Event.TYPE_SHARE_TEXT)).thenReturn(mEventIndex6);
+        when(mEventHistory2.getEventIndex(Event.TYPE_SHARE_TEXT)).thenReturn(mEventIndex7);
+        when(mEventHistory3.getEventIndex(Event.TYPE_SHARE_TEXT)).thenReturn(mEventIndex8);
+        when(mEventHistory4.getEventIndex(Event.TYPE_SHARE_TEXT)).thenReturn(mEventIndex9);
+        when(mEventHistory5.getEventIndex(Event.TYPE_SHARE_TEXT)).thenReturn(mEventIndex10);
+
+        SharesheetModelScorer.computeScoreForDirectShare(
+                List.of(mShareShortcutTarget1, mShareShortcutTarget2, mShareShortcutTarget3,
+                        mShareShortcutTarget4, mShareShortcutTarget5, mShareShortcutTarget6),
+                Event.TYPE_SHARE_TEXT, 20);
+
+        assertEquals(0f, mShareShortcutTarget1.getScore(), DELTA);
+        assertEquals(0.3f, mShareShortcutTarget2.getScore(), DELTA);
+        assertEquals(0.4f, mShareShortcutTarget3.getScore(), DELTA);
+        assertEquals(0.3f, mShareShortcutTarget4.getScore(), DELTA);
+        assertEquals(0.4f, mShareShortcutTarget5.getScore(), DELTA);
+        assertEquals(0f, mShareShortcutTarget6.getScore(), DELTA);
+    }
+
+    private static ShortcutInfo buildShortcutInfo(String packageName, int userId, String id) {
+        Context mockContext = mock(Context.class);
+        when(mockContext.getPackageName()).thenReturn(packageName);
+        when(mockContext.getUserId()).thenReturn(userId);
+        when(mockContext.getUser()).thenReturn(UserHandle.of(userId));
+        ShortcutInfo.Builder builder = new ShortcutInfo.Builder(mockContext, id).setShortLabel(id);
+        return builder.build();
+    }
+
     private static UsageEvents.Event createUsageEvent(String packageName) {
         UsageEvents.Event e = new UsageEvents.Event();
         e.mPackage = packageName;
diff --git a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserInfoTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserInfoTest.java
index 66ca839..0ccc026 100644
--- a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserInfoTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserInfoTest.java
@@ -105,7 +105,7 @@
         UserData read = mUserManagerService.readUserLP(
                 data.info.id, new ByteArrayInputStream(bytes));
 
-        assertUserInfoEquals(data.info, read.info);
+        assertUserInfoEquals(data.info, read.info, /* parcelCopy= */ false);
     }
 
     @Test
@@ -123,7 +123,16 @@
         UserInfo read = UserInfo.CREATOR.createFromParcel(in);
         in.recycle();
 
-        assertUserInfoEquals(info, read);
+        assertUserInfoEquals(info, read, /* parcelCopy= */ true);
+    }
+
+    @Test
+    public void testCopyConstructor() throws Exception {
+        UserInfo info = createUser();
+
+        UserInfo copy = new UserInfo(info);
+
+        assertUserInfoEquals(info, copy, /* parcelCopy= */ false);
     }
 
     @Test
@@ -227,10 +236,11 @@
         user.partial = true;
         user.guestToRemove = true;
         user.preCreated = true;
+        user.convertedFromPreCreated = true;
         return user;
     }
 
-    private void assertUserInfoEquals(UserInfo one, UserInfo two) {
+    private void assertUserInfoEquals(UserInfo one, UserInfo two, boolean parcelCopy) {
         assertEquals("Id not preserved", one.id, two.id);
         assertEquals("Name not preserved", one.name, two.name);
         assertEquals("Icon path not preserved", one.iconPath, two.iconPath);
@@ -238,11 +248,17 @@
         assertEquals("UserType not preserved", one.userType, two.userType);
         assertEquals("profile group not preserved", one.profileGroupId,
                 two.profileGroupId);
-        assertEquals("restricted profile parent not preseved", one.restrictedProfileParentId,
+        assertEquals("restricted profile parent not preserved", one.restrictedProfileParentId,
                 two.restrictedProfileParentId);
-        assertEquals("profile badge not preseved", one.profileBadge, two.profileBadge);
-        assertEquals("partial not preseved", one.partial, two.partial);
-        assertEquals("guestToRemove not preseved", one.guestToRemove, two.guestToRemove);
-        assertEquals("preCreated not preseved", one.preCreated, two.preCreated);
+        assertEquals("profile badge not preserved", one.profileBadge, two.profileBadge);
+        assertEquals("partial not preserved", one.partial, two.partial);
+        assertEquals("guestToRemove not preserved", one.guestToRemove, two.guestToRemove);
+        assertEquals("preCreated not preserved", one.preCreated, two.preCreated);
+        if (parcelCopy) {
+            assertFalse("convertedFromPreCreated should not be set", two.convertedFromPreCreated);
+        } else {
+            assertEquals("convertedFromPreCreated not preserved", one.convertedFromPreCreated,
+                    two.convertedFromPreCreated);
+        }
     }
 }
diff --git a/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java b/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java
index 4dec7a1..a07e60c 100644
--- a/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java
+++ b/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java
@@ -112,6 +112,8 @@
     private static final int UID_SYSTEM_HEADFULL = 10002;
     private static final String PACKAGE_SYSTEM_HEADLESS = "com.example.system.headless";
     private static final int UID_SYSTEM_HEADLESS = 10003;
+    private static final String PACKAGE_WELLBEING = "com.example.wellbeing";
+    private static final int UID_WELLBEING = 10004;
     private static final int USER_ID = 0;
     private static final int USER_ID2 = 10;
     private static final UserHandle USER_HANDLE_USER2 = new UserHandle(USER_ID2);
@@ -218,6 +220,11 @@
         }
 
         @Override
+        boolean isWellbeingPackage(String packageName) {
+            return PACKAGE_WELLBEING.equals(packageName);
+        }
+
+        @Override
         void updatePowerWhitelistCache() {
         }
 
@@ -329,6 +336,12 @@
         pish.packageName = PACKAGE_SYSTEM_HEADLESS;
         packages.add(pish);
 
+        PackageInfo piw = new PackageInfo();
+        piw.applicationInfo = new ApplicationInfo();
+        piw.applicationInfo.uid = UID_WELLBEING;
+        piw.packageName = PACKAGE_WELLBEING;
+        packages.add(piw);
+
         doReturn(packages).when(mockPm).getInstalledPackagesAsUser(anyInt(), anyInt());
         try {
             for (int i = 0; i < packages.size(); ++i) {
@@ -1516,6 +1529,25 @@
         assertBucket(STANDBY_BUCKET_RARE, PACKAGE_1);
     }
 
+    @Test
+    public void testWellbeingAppElevated() {
+        reportEvent(mController, USER_INTERACTION, mInjector.mElapsedRealtime, PACKAGE_WELLBEING);
+        assertBucket(STANDBY_BUCKET_ACTIVE, PACKAGE_WELLBEING);
+        reportEvent(mController, USER_INTERACTION, mInjector.mElapsedRealtime, PACKAGE_1);
+        assertBucket(STANDBY_BUCKET_ACTIVE, PACKAGE_1);
+        mInjector.mElapsedRealtime += RESTRICTED_THRESHOLD;
+
+        // Make sure the default wellbeing app does not get lowered below WORKING_SET.
+        mController.setAppStandbyBucket(PACKAGE_WELLBEING, USER_ID, STANDBY_BUCKET_RARE,
+                REASON_MAIN_TIMEOUT);
+        assertBucket(STANDBY_BUCKET_WORKING_SET, PACKAGE_WELLBEING);
+
+        // A non default wellbeing app should be able to fall lower than WORKING_SET.
+        mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_RARE,
+                REASON_MAIN_TIMEOUT);
+        assertBucket(STANDBY_BUCKET_RARE, PACKAGE_1);
+    }
+
     private String getAdminAppsStr(int userId) {
         return getAdminAppsStr(userId, mController.getActiveAdminAppsForTest(userId));
     }
diff --git a/services/tests/uiservicestests/src/com/android/server/UiModeManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/UiModeManagerServiceTest.java
index 1d75967..dfde931 100644
--- a/services/tests/uiservicestests/src/com/android/server/UiModeManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/UiModeManagerServiceTest.java
@@ -16,6 +16,7 @@
 
 package com.android.server;
 
+import android.Manifest;
 import android.app.AlarmManager;
 import android.app.IUiModeManager;
 import android.content.BroadcastReceiver;
@@ -24,6 +25,7 @@
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.pm.PackageManager;
+import android.content.pm.UserInfo;
 import android.content.res.Configuration;
 import android.content.res.Resources;
 import android.os.Handler;
@@ -67,6 +69,7 @@
 import static org.mockito.Mockito.atLeastOnce;
 import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
@@ -230,6 +233,15 @@
     }
 
     @Test
+    public void setNightModeActivated_permissiontoChangeOtherUsers() throws RemoteException {
+        mUiManagerService.onSwitchUser(9);
+        when(mContext.checkCallingOrSelfPermission(
+                eq(Manifest.permission.INTERACT_ACROSS_USERS)))
+                .thenReturn(PackageManager.PERMISSION_DENIED);
+        assertFalse(mService.setNightModeActivated(true));
+    }
+
+    @Test
     public void autoNightModeSwitch_batterySaverOn() throws RemoteException {
         mService.setNightMode(MODE_NIGHT_NO);
         when(mTwilightState.isNight()).thenReturn(false);
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
index 10976882..24384b1 100755
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -3865,7 +3865,7 @@
         mService.updateUriPermissions(recordB, recordA, mContext.getPackageName(),
                 USER_SYSTEM);
         verify(mUgmInternal, times(1)).revokeUriPermissionFromOwner(any(),
-                eq(message1.getDataUri()), anyInt(), anyInt());
+                eq(message1.getDataUri()), anyInt(), anyInt(), eq(null), eq(-1));
 
         // Update back means we grant access to first again
         reset(mUgm);
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
index 6ab0697..a37f4be 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
@@ -20,6 +20,10 @@
 import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
 import static android.content.pm.ActivityInfo.CONFIG_ORIENTATION;
 import static android.content.pm.ActivityInfo.CONFIG_SCREEN_LAYOUT;
+import static android.content.pm.ActivityInfo.LOCK_TASK_LAUNCH_MODE_ALWAYS;
+import static android.content.pm.ActivityInfo.LOCK_TASK_LAUNCH_MODE_DEFAULT;
+import static android.content.pm.ActivityInfo.LOCK_TASK_LAUNCH_MODE_IF_ALLOWLISTED;
+import static android.content.pm.ActivityInfo.LOCK_TASK_LAUNCH_MODE_NEVER;
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE;
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
 import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
@@ -79,6 +83,7 @@
 import android.content.ComponentName;
 import android.content.Intent;
 import android.content.pm.ActivityInfo;
+import android.content.pm.ApplicationInfo;
 import android.content.res.Configuration;
 import android.content.res.Resources;
 import android.graphics.Rect;
@@ -536,14 +541,14 @@
         mActivity = new ActivityBuilder(mService)
                 .setTask(mTask)
                 .setLaunchTaskBehind(true)
-                .setConfigChanges(CONFIG_ORIENTATION)
+                .setConfigChanges(CONFIG_ORIENTATION | CONFIG_SCREEN_LAYOUT)
                 .build();
         mActivity.setState(ActivityStack.ActivityState.STOPPED, "Testing");
 
         final ActivityStack stack = new StackBuilder(mRootWindowContainer).build();
         try {
             doReturn(false).when(stack).isTranslucent(any());
-            assertFalse(mStack.shouldBeVisible(null /* starting */));
+            assertTrue(mStack.shouldBeVisible(null /* starting */));
 
             mActivity.setLastReportedConfiguration(new MergedConfiguration(new Configuration(),
                     mActivity.getConfiguration()));
@@ -1685,6 +1690,32 @@
                 .diff(wpc.getRequestedOverrideConfiguration()));
     }
 
+    @Test
+    public void testGetLockTaskLaunchMode() {
+        final ActivityOptions options = ActivityOptions.makeBasic().setLockTaskEnabled(true);
+        mActivity.info.lockTaskLaunchMode = LOCK_TASK_LAUNCH_MODE_DEFAULT;
+        assertEquals(LOCK_TASK_LAUNCH_MODE_IF_ALLOWLISTED,
+                ActivityRecord.getLockTaskLaunchMode(mActivity.info, options));
+
+        mActivity.info.lockTaskLaunchMode = LOCK_TASK_LAUNCH_MODE_ALWAYS;
+        assertEquals(LOCK_TASK_LAUNCH_MODE_DEFAULT,
+                ActivityRecord.getLockTaskLaunchMode(mActivity.info, null /*options*/));
+
+        mActivity.info.lockTaskLaunchMode = LOCK_TASK_LAUNCH_MODE_NEVER;
+        assertEquals(LOCK_TASK_LAUNCH_MODE_DEFAULT,
+                ActivityRecord.getLockTaskLaunchMode(mActivity.info, null /*options*/));
+
+        mActivity.info.applicationInfo.privateFlags |= ApplicationInfo.PRIVATE_FLAG_PRIVILEGED;
+        mActivity.info.lockTaskLaunchMode = LOCK_TASK_LAUNCH_MODE_ALWAYS;
+        assertEquals(LOCK_TASK_LAUNCH_MODE_ALWAYS,
+                ActivityRecord.getLockTaskLaunchMode(mActivity.info, null /*options*/));
+
+        mActivity.info.lockTaskLaunchMode = LOCK_TASK_LAUNCH_MODE_NEVER;
+        assertEquals(LOCK_TASK_LAUNCH_MODE_NEVER,
+                ActivityRecord.getLockTaskLaunchMode(mActivity.info, null /*options*/));
+
+    }
+
     /**
      * Creates an activity on display. For non-default display request it will also create a new
      * display with custom DisplayInfo.
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityStackTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityStackTests.java
index 1b42a04..e898c25 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityStackTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStackTests.java
@@ -1206,19 +1206,22 @@
     @Test
     public void testShouldSleepActivities() {
         // When focused activity and keyguard is going away, we should not sleep regardless
-        // of the display state
+        // of the display state, but keyguard-going-away should only take effects on default
+        // display since there is no keyguard on secondary displays (yet).
         verifyShouldSleepActivities(true /* focusedStack */, true /*keyguardGoingAway*/,
-                true /* displaySleeping */, false /* expected*/);
+                true /* displaySleeping */, true /* isDefaultDisplay */, false /* expected */);
+        verifyShouldSleepActivities(true /* focusedStack */, true /*keyguardGoingAway*/,
+                true /* displaySleeping */, false /* isDefaultDisplay */, true /* expected */);
 
         // When not the focused stack, defer to display sleeping state.
         verifyShouldSleepActivities(false /* focusedStack */, true /*keyguardGoingAway*/,
-                true /* displaySleeping */, true /* expected*/);
+                true /* displaySleeping */, true /* isDefaultDisplay */, true /* expected */);
 
         // If keyguard is going away, defer to the display sleeping state.
         verifyShouldSleepActivities(true /* focusedStack */, false /*keyguardGoingAway*/,
-                true /* displaySleeping */, true /* expected*/);
+                true /* displaySleeping */, true /* isDefaultDisplay */, true /* expected */);
         verifyShouldSleepActivities(true /* focusedStack */, false /*keyguardGoingAway*/,
-                false /* displaySleeping */, false /* expected*/);
+                false /* displaySleeping */, true /* isDefaultDisplay */, false /* expected */);
     }
 
     @Test
@@ -1428,9 +1431,11 @@
     }
 
     private void verifyShouldSleepActivities(boolean focusedStack,
-            boolean keyguardGoingAway, boolean displaySleeping, boolean expected) {
+            boolean keyguardGoingAway, boolean displaySleeping, boolean isDefaultDisplay,
+            boolean expected) {
         final DisplayContent display = mock(DisplayContent.class);
         final KeyguardController keyguardController = mSupervisor.getKeyguardController();
+        display.isDefaultDisplay = isDefaultDisplay;
 
         doReturn(display).when(mStack).getDisplay();
         doReturn(keyguardGoingAway).when(keyguardController).isKeyguardGoingAway();
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayAreaTest.java b/services/tests/wmtests/src/com/android/server/wm/DisplayAreaTest.java
index 880c486..d42ab72 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayAreaTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayAreaTest.java
@@ -20,6 +20,9 @@
 import static android.view.WindowManager.LayoutParams.TYPE_PRESENTATION;
 import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER;
 
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
+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.DisplayArea.Type.ABOVE_TASKS;
 import static com.android.server.wm.DisplayArea.Type.ANY;
 import static com.android.server.wm.DisplayArea.Type.BELOW_TASKS;
@@ -29,11 +32,15 @@
 import static com.android.server.wm.testing.Assert.assertThrows;
 
 import static org.junit.Assert.assertEquals;
+import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.Mockito.mock;
 
+import android.content.pm.ActivityInfo;
 import android.os.Binder;
 import android.platform.test.annotations.Presubmit;
 import android.view.SurfaceControl;
+import android.view.View;
+import android.view.WindowManager;
 
 import org.junit.Rule;
 import org.junit.Test;
@@ -97,6 +104,42 @@
         assertThrows(IllegalStateException.class, () -> checkChild(BELOW_TASKS, ANY));
     }
 
+
+    @Test
+    public void testGetOrientation() {
+        final DisplayArea.Tokens area = new DisplayArea.Tokens(mWmsRule.getWindowManagerService(),
+                ABOVE_TASKS, "test");
+        final WindowToken token = createWindowToken(TYPE_APPLICATION_OVERLAY);
+        spyOn(token);
+        doReturn(mock(DisplayContent.class)).when(token).getDisplayContent();
+        doNothing().when(token).setParent(any());
+        final WindowState win = createWindowState(token);
+        spyOn(win);
+        doNothing().when(win).setParent(any());
+        win.mAttrs.screenOrientation = ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE;
+        token.addChild(win, 0);
+        area.addChild(token);
+
+        doReturn(true).when(win).isVisible();
+
+        assertEquals("Visible window can request orientation",
+                ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE,
+                area.getOrientation(ActivityInfo.SCREEN_ORIENTATION_NOSENSOR));
+
+        doReturn(false).when(win).isVisible();
+
+        assertEquals("Invisible window cannot request orientation",
+                ActivityInfo.SCREEN_ORIENTATION_NOSENSOR,
+                area.getOrientation(ActivityInfo.SCREEN_ORIENTATION_NOSENSOR));
+    }
+
+    private WindowState createWindowState(WindowToken token) {
+        return new WindowState(mWmsRule.getWindowManagerService(), mock(Session.class),
+                new TestIWindow(), token, null /* parentWindow */, 0 /* appOp */, 0 /* seq*/,
+                new WindowManager.LayoutParams(), View.VISIBLE, 0 /* ownerId */, 0 /* showUserId */,
+                false /* ownerCanAddInternalSystemWindow */, null);
+    }
+
     private WindowToken createWindowToken(int type) {
         return new WindowToken(mWmsRule.getWindowManagerService(), new Binder(),
                 type, false /* persist */, null /* displayContent */,
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
index 66dfbfd..1948003a 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
@@ -28,6 +28,7 @@
 import static android.os.Build.VERSION_CODES.P;
 import static android.os.Build.VERSION_CODES.Q;
 import static android.view.Display.DEFAULT_DISPLAY;
+import static android.view.Display.FLAG_PRIVATE;
 import static android.view.DisplayCutout.BOUNDS_POSITION_LEFT;
 import static android.view.DisplayCutout.BOUNDS_POSITION_TOP;
 import static android.view.DisplayCutout.fromBoundingRect;
@@ -95,14 +96,12 @@
 import android.platform.test.annotations.Presubmit;
 import android.util.DisplayMetrics;
 import android.view.DisplayCutout;
+import android.view.DisplayInfo;
 import android.view.Gravity;
-import android.view.IDisplayWindowInsetsController;
 import android.view.IDisplayWindowRotationCallback;
 import android.view.IDisplayWindowRotationController;
 import android.view.ISystemGestureExclusionListener;
 import android.view.IWindowManager;
-import android.view.InsetsSourceControl;
-import android.view.InsetsState;
 import android.view.MotionEvent;
 import android.view.Surface;
 import android.view.SurfaceControl.Transaction;
@@ -934,28 +933,6 @@
         assertEquals(mAppWindow, mDisplayContent.computeImeControlTarget());
     }
 
-    private IDisplayWindowInsetsController createDisplayWindowInsetsController() {
-        return new IDisplayWindowInsetsController.Stub() {
-
-            @Override
-            public void insetsChanged(InsetsState insetsState) throws RemoteException {
-            }
-
-            @Override
-            public void insetsControlChanged(InsetsState insetsState,
-                    InsetsSourceControl[] insetsSourceControls) throws RemoteException {
-            }
-
-            @Override
-            public void showInsets(int i, boolean b) throws RemoteException {
-            }
-
-            @Override
-            public void hideInsets(int i, boolean b) throws RemoteException {
-            }
-        };
-    }
-
     @Test
     public void testUpdateSystemGestureExclusion() throws Exception {
         final DisplayContent dc = createNewDisplay();
@@ -1172,8 +1149,10 @@
         verify(t, never()).setPosition(any(), eq(0), eq(0));
 
         // Launch another activity before the transition is finished.
-        final ActivityRecord app2 = new ActivityTestsBase.StackBuilder(mWm.mRoot)
-                .setDisplay(mDisplayContent).build().getTopMostActivity();
+        final ActivityStack stack2 = new ActivityTestsBase.StackBuilder(mWm.mRoot)
+                .setDisplay(mDisplayContent).build();
+        final ActivityRecord app2 = new ActivityTestsBase.ActivityBuilder(mWm.mAtmService)
+                .setStack(stack2).setUseProcess(app.app).build();
         app2.setVisible(false);
         mDisplayContent.mOpeningApps.add(app2);
         app2.setRequestedOrientation(newOrientation);
@@ -1183,6 +1162,12 @@
         assertTrue(app.hasFixedRotationTransform(app2));
         assertTrue(mDisplayContent.isFixedRotationLaunchingApp(app2));
 
+        final Configuration expectedProcConfig = new Configuration(app2.app.getConfiguration());
+        expectedProcConfig.windowConfiguration.setActivityType(
+                WindowConfiguration.ACTIVITY_TYPE_UNDEFINED);
+        assertEquals("The process should receive rotated configuration for compatibility",
+                expectedProcConfig, app2.app.getConfiguration());
+
         // The fixed rotation transform can only be finished when all animation finished.
         doReturn(false).when(app2).isAnimating(anyInt(), anyInt());
         mDisplayContent.mAppTransition.notifyAppTransitionFinishedLocked(app2.token);
@@ -1291,6 +1276,27 @@
     }
 
     @Test
+    public void testNoFixedRotationOnResumedScheduledApp() {
+        final ActivityRecord app = new ActivityTestsBase.StackBuilder(mWm.mRoot)
+                .setDisplay(mDisplayContent).build().getTopMostActivity();
+        app.setVisible(false);
+        app.setState(ActivityStack.ActivityState.RESUMED, "test");
+        mDisplayContent.prepareAppTransition(WindowManager.TRANSIT_ACTIVITY_OPEN,
+                false /* alwaysKeepCurrent */);
+        mDisplayContent.mOpeningApps.add(app);
+        final int newOrientation = getRotatedOrientation(mDisplayContent);
+        app.setRequestedOrientation(newOrientation);
+
+        // The condition should reject using fixed rotation because the resumed client in real case
+        // might get display info immediately. And the fixed rotation adjustments haven't arrived
+        // client side so the info may be inconsistent with the requested orientation.
+        verify(mDisplayContent).handleTopActivityLaunchingInDifferentOrientation(eq(app),
+                eq(true) /* checkOpening */);
+        assertFalse(app.isFixedRotationTransforming());
+        assertFalse(mDisplayContent.hasTopFixedRotationLaunchingApp());
+    }
+
+    @Test
     public void testRecentsNotRotatingWithFixedRotation() {
         final DisplayRotation displayRotation = mDisplayContent.getDisplayRotation();
         doCallRealMethod().when(displayRotation).updateRotationUnchecked(anyBoolean());
@@ -1307,7 +1313,7 @@
         assertFalse(displayRotation.updateRotationUnchecked(false));
 
         // Rotation can be updated if the recents animation is finished.
-        mDisplayContent.mFixedRotationTransitionListener.onFinishRecentsAnimation(false);
+        mDisplayContent.mFixedRotationTransitionListener.onFinishRecentsAnimation();
         assertTrue(displayRotation.updateRotationUnchecked(false));
 
         // Rotation can be updated if the recents animation is animating but it is not on top, e.g.
@@ -1464,6 +1470,29 @@
         mDisplayContent.ensureActivitiesVisible(null, 0, false, false);
     }
 
+    @Test
+    public void testForceDesktopMode() {
+        mWm.mForceDesktopModeOnExternalDisplays = true;
+        // Not applicable for default display
+        final DisplayContent defaultDisplay = mWm.mRoot.getDefaultDisplay();
+        assertFalse(defaultDisplay.forceDesktopMode());
+
+        // Not applicable for private secondary display.
+        final DisplayInfo displayInfo = new DisplayInfo();
+        displayInfo.copyFrom(mDisplayInfo);
+        displayInfo.flags = FLAG_PRIVATE;
+        final DisplayContent privateDc = createNewDisplay(displayInfo);
+        assertFalse(privateDc.forceDesktopMode());
+
+        // Applicable for public secondary display.
+        final DisplayContent publicDc = createNewDisplay();
+        assertTrue(publicDc.forceDesktopMode());
+
+        // Make sure forceDesktopMode() is false when the force config is disabled.
+        mWm.mForceDesktopModeOnExternalDisplays = false;
+        assertFalse(publicDc.forceDesktopMode());
+    }
+
     private boolean isOptionsPanelAtRight(int displayId) {
         return (mWm.getPreferredOptionsPanelGravity(displayId) & Gravity.RIGHT) == Gravity.RIGHT;
     }
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java
index 1922351..58d4104 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java
@@ -20,6 +20,8 @@
 import static android.view.Gravity.LEFT;
 import static android.view.Gravity.RIGHT;
 import static android.view.Gravity.TOP;
+import static android.view.InsetsState.ITYPE_CLIMATE_BAR;
+import static android.view.InsetsState.ITYPE_EXTRA_NAVIGATION_BAR;
 import static android.view.InsetsState.ITYPE_NAVIGATION_BAR;
 import static android.view.InsetsState.ITYPE_STATUS_BAR;
 import static android.view.InsetsState.ITYPE_TOP_GESTURES;
@@ -36,12 +38,19 @@
 import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
 import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER;
 import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES;
+import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_INSET_PARENT_FRAME_BY_IME;
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_IS_SCREEN_DECOR;
 import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING;
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
+import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL;
 import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR;
+import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR_ADDITIONAL;
 import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR_SUB_PANEL;
+import static android.view.WindowManagerPolicyConstants.ALT_BAR_BOTTOM;
+import static android.view.WindowManagerPolicyConstants.ALT_BAR_LEFT;
+import static android.view.WindowManagerPolicyConstants.ALT_BAR_RIGHT;
+import static android.view.WindowManagerPolicyConstants.ALT_BAR_TOP;
 
 import static org.hamcrest.Matchers.is;
 import static org.junit.Assert.assertEquals;
@@ -60,6 +69,7 @@
 import android.util.SparseArray;
 import android.view.DisplayCutout;
 import android.view.DisplayInfo;
+import android.view.Gravity;
 import android.view.InsetsState;
 import android.view.WindowInsets.Side;
 import android.view.WindowInsets.Type;
@@ -67,7 +77,6 @@
 
 import androidx.test.filters.SmallTest;
 
-import com.android.server.policy.WindowManagerPolicy;
 import com.android.server.wm.utils.WmDisplayCutout;
 
 import org.junit.Before;
@@ -148,6 +157,8 @@
 
     @Test
     public void addingWindow_withInsetsTypes() {
+        mDisplayPolicy.removeWindowLw(mStatusBarWindow);  // Removes the existing one.
+
         WindowState win = createWindow(null, TYPE_STATUS_BAR_SUB_PANEL, "StatusBarSubPanel");
         win.mAttrs.providesInsetsTypes = new int[]{ITYPE_STATUS_BAR, ITYPE_TOP_GESTURES};
         win.getFrameLw().set(0, 0, 500, 100);
@@ -190,10 +201,56 @@
 
     @Test
     public void addingWindow_throwsException_WithMultipleInsetTypes() {
-        WindowState win = createWindow(null, TYPE_STATUS_BAR_SUB_PANEL, "StatusBarSubPanel");
-        win.mAttrs.providesInsetsTypes = new int[]{ITYPE_STATUS_BAR, ITYPE_NAVIGATION_BAR};
+        WindowState win1 = createWindow(null, TYPE_STATUS_BAR_SUB_PANEL, "StatusBarSubPanel");
+        win1.mAttrs.providesInsetsTypes = new int[]{ITYPE_STATUS_BAR, ITYPE_NAVIGATION_BAR};
 
-        expectThrows(IllegalArgumentException.class, () -> addWindow(win));
+        expectThrows(IllegalArgumentException.class, () -> addWindow(win1));
+
+        WindowState win2 = createWindow(null, TYPE_STATUS_BAR_SUB_PANEL, "StatusBarSubPanel");
+        win2.mAttrs.providesInsetsTypes = new int[]{ITYPE_CLIMATE_BAR, ITYPE_EXTRA_NAVIGATION_BAR};
+
+        expectThrows(IllegalArgumentException.class, () -> addWindow(win2));
+    }
+
+    @Test
+    public void addingWindow_variousGravities_alternateBarPosUpdated() {
+        mDisplayPolicy.removeWindowLw(mNavBarWindow);  // Removes the existing one.
+
+        WindowState win1 = createWindow(null, TYPE_NAVIGATION_BAR_PANEL, "NavBarPanel1");
+        win1.mAttrs.providesInsetsTypes = new int[]{ITYPE_NAVIGATION_BAR};
+        win1.mAttrs.gravity = Gravity.TOP;
+        win1.getFrameLw().set(0, 0, 200, 500);
+        addWindow(win1);
+
+        assertEquals(mDisplayPolicy.getAlternateNavBarPosition(), ALT_BAR_TOP);
+        mDisplayPolicy.removeWindowLw(win1);
+
+        WindowState win2 = createWindow(null, TYPE_NAVIGATION_BAR_PANEL, "NavBarPanel2");
+        win2.mAttrs.providesInsetsTypes = new int[]{ITYPE_NAVIGATION_BAR};
+        win2.mAttrs.gravity = Gravity.BOTTOM;
+        win2.getFrameLw().set(0, 0, 200, 500);
+        addWindow(win2);
+
+        assertEquals(mDisplayPolicy.getAlternateNavBarPosition(), ALT_BAR_BOTTOM);
+        mDisplayPolicy.removeWindowLw(win2);
+
+        WindowState win3 = createWindow(null, TYPE_NAVIGATION_BAR_PANEL, "NavBarPanel3");
+        win3.mAttrs.providesInsetsTypes = new int[]{ITYPE_NAVIGATION_BAR};
+        win3.mAttrs.gravity = Gravity.LEFT;
+        win3.getFrameLw().set(0, 0, 200, 500);
+        addWindow(win3);
+
+        assertEquals(mDisplayPolicy.getAlternateNavBarPosition(), ALT_BAR_LEFT);
+        mDisplayPolicy.removeWindowLw(win3);
+
+        WindowState win4 = createWindow(null, TYPE_NAVIGATION_BAR_PANEL, "NavBarPanel4");
+        win4.mAttrs.providesInsetsTypes = new int[]{ITYPE_NAVIGATION_BAR};
+        win4.mAttrs.gravity = Gravity.RIGHT;
+        win4.getFrameLw().set(0, 0, 200, 500);
+        addWindow(win4);
+
+        assertEquals(mDisplayPolicy.getAlternateNavBarPosition(), ALT_BAR_RIGHT);
+        mDisplayPolicy.removeWindowLw(win4);
     }
 
     @Test
@@ -301,6 +358,24 @@
     }
 
     @Test
+    public void layoutWindowLw_insetParentFrameByIme() {
+        final InsetsState state =
+                mDisplayContent.getInsetsStateController().getRawInsetsState();
+        state.getSource(InsetsState.ITYPE_IME).setVisible(true);
+        state.getSource(InsetsState.ITYPE_IME).setFrame(
+                0, DISPLAY_HEIGHT - IME_HEIGHT, DISPLAY_WIDTH, DISPLAY_HEIGHT);
+        mWindow.mAttrs.privateFlags |= PRIVATE_FLAG_INSET_PARENT_FRAME_BY_IME;
+        mWindow.mBehindIme = true;
+        addWindow(mWindow);
+
+        mDisplayPolicy.beginLayoutLw(mFrames, 0 /* UI mode */);
+        mDisplayPolicy.layoutWindowLw(mWindow, null, mFrames);
+
+        assertInsetByTopBottom(mWindow.getDisplayFrameLw(), STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT);
+        assertInsetByTopBottom(mWindow.getParentFrame(), STATUS_BAR_HEIGHT, IME_HEIGHT);
+    }
+
+    @Test
     public void layoutWindowLw_fitDisplayCutout() {
         addDisplayCutout();
 
@@ -679,6 +754,51 @@
     }
 
     @Test
+    public void layoutWindowLw_withFlexibleSystemBars_adjustStableFrame() {
+        mDisplayPolicy.removeWindowLw(mStatusBarWindow);
+        mDisplayPolicy.removeWindowLw(mNavBarWindow);
+
+        WindowState statusWin = spy(createWindow(null, TYPE_STATUS_BAR_ADDITIONAL,
+                "StatusBarAdditional"));
+        doNothing().when(statusWin).computeFrameLw();
+        statusWin.mAttrs.providesInsetsTypes = new int[]{ITYPE_STATUS_BAR};
+        statusWin.mAttrs.gravity = Gravity.TOP;
+        statusWin.mAttrs.height = STATUS_BAR_HEIGHT;
+        statusWin.mAttrs.width = MATCH_PARENT;
+        statusWin.getFrameLw().set(0, 0, DISPLAY_WIDTH, STATUS_BAR_HEIGHT);
+        addWindow(statusWin);
+
+        WindowState navWin = spy(createWindow(null, TYPE_NAVIGATION_BAR_PANEL,
+                "NavigationBarPanel"));
+        doNothing().when(navWin).computeFrameLw();
+        navWin.mAttrs.providesInsetsTypes = new int[]{ITYPE_NAVIGATION_BAR};
+        navWin.mAttrs.gravity = Gravity.BOTTOM;
+        navWin.mAttrs.height = NAV_BAR_HEIGHT;
+        navWin.mAttrs.width = MATCH_PARENT;
+        navWin.getFrameLw().set(0, DISPLAY_HEIGHT - NAV_BAR_HEIGHT, DISPLAY_WIDTH, DISPLAY_HEIGHT);
+        addWindow(navWin);
+
+        WindowState climateWin = spy(createWindow(null, TYPE_NAVIGATION_BAR_PANEL,
+                "ClimatePanel"));
+        doNothing().when(climateWin).computeFrameLw();
+        climateWin.mAttrs.providesInsetsTypes = new int[]{ITYPE_CLIMATE_BAR};
+        climateWin.mAttrs.gravity = Gravity.LEFT;
+        climateWin.mAttrs.height = MATCH_PARENT;
+        climateWin.mAttrs.width = 20;
+        climateWin.getFrameLw().set(0, 0, 20, DISPLAY_HEIGHT);
+        addWindow(climateWin);
+
+        mDisplayPolicy.beginLayoutLw(mFrames, 0 /* UI mode */);
+        mDisplayPolicy.layoutWindowLw(statusWin, null, mFrames);
+        mDisplayPolicy.layoutWindowLw(navWin, null, mFrames);
+        mDisplayPolicy.layoutWindowLw(climateWin, null, mFrames);
+
+        assertThat(mFrames.mStable,
+                is(new Rect(20, STATUS_BAR_HEIGHT, DISPLAY_WIDTH,
+                        DISPLAY_HEIGHT - NAV_BAR_HEIGHT)));
+    }
+
+    @Test
     public void layoutHint_appWindow() {
         mWindow.mAttrs.flags =
                 FLAG_LAYOUT_IN_SCREEN | FLAG_LAYOUT_INSET_DECOR | FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;
@@ -807,27 +927,6 @@
     }
 
     @Test
-    public void forceShowSystemBars_clearsSystemUIFlags() {
-        mDisplayPolicy.mLastSystemUiFlags |= SYSTEM_UI_FLAG_FULLSCREEN;
-        mWindow.mAttrs.subtreeSystemUiVisibility |= SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN;
-        mWindow.mAttrs.flags =
-                FLAG_LAYOUT_IN_SCREEN | FLAG_LAYOUT_INSET_DECOR | FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;
-        mWindow.mSystemUiVisibility = SYSTEM_UI_FLAG_FULLSCREEN;
-        mDisplayPolicy.setForceShowSystemBars(true);
-        addWindow(mWindow);
-
-        mDisplayPolicy.beginLayoutLw(mFrames, 0 /* UI mode */);
-        mDisplayPolicy.layoutWindowLw(mWindow, null, mFrames);
-        // triggers updateSystemUiVisibilityLw which will reset the flags as needed
-        int finishPostLayoutPolicyLw = mDisplayPolicy.focusChangedLw(mWindow, mWindow);
-
-        assertEquals(WindowManagerPolicy.FINISH_LAYOUT_REDO_LAYOUT, finishPostLayoutPolicyLw);
-        assertEquals(0, mDisplayPolicy.mLastSystemUiFlags);
-        assertEquals(0, mWindow.mAttrs.systemUiVisibility);
-        assertInsetByTopBottom(mWindow.getContentFrameLw(), STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT);
-    }
-
-    @Test
     public void testScreenDecorWindows() {
         final WindowState decorWindow = createWindow(null, TYPE_APPLICATION_OVERLAY, "decorWindow");
         mWindow.mAttrs.flags = FLAG_NOT_FOCUSABLE | FLAG_LAYOUT_IN_SCREEN | FLAG_LAYOUT_INSET_DECOR
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTestsBase.java
index 7ba3fd8..c87014a 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTestsBase.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTestsBase.java
@@ -62,7 +62,7 @@
     static final int STATUS_BAR_HEIGHT = 10;
     static final int NAV_BAR_HEIGHT = 15;
     static final int DISPLAY_CUTOUT_HEIGHT = 8;
-    static final int INPUT_METHOD_WINDOW_TOP = 585;
+    static final int IME_HEIGHT = 415;
 
     DisplayPolicy mDisplayPolicy;
 
diff --git a/services/tests/wmtests/src/com/android/server/wm/ImeInsetsSourceProviderTest.java b/services/tests/wmtests/src/com/android/server/wm/ImeInsetsSourceProviderTest.java
index ca739c0..91cfd4e 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ImeInsetsSourceProviderTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ImeInsetsSourceProviderTest.java
@@ -56,4 +56,12 @@
         mImeProvider.scheduleShowImePostLayout(appWin);
         assertTrue(mImeProvider.isImeTargetFromDisplayContentAndImeSame());
     }
+
+    @Test
+    public void testInputMethodInputTargetCanShowIme() {
+        WindowState target = createWindow(null, TYPE_APPLICATION, "app");
+        mDisplayContent.mInputMethodTarget = target;
+        mImeProvider.scheduleShowImePostLayout(target);
+        assertTrue(mImeProvider.isImeTargetFromDisplayContentAndImeSame());
+    }
 }
diff --git a/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java b/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java
index da36ba9..90af8a1 100644
--- a/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java
@@ -171,8 +171,9 @@
     }
 
     @Test
-    public void testControlsForDispatch_forceShowSystemBarsFromExternal_appHasNoControl() {
-        mDisplayContent.getDisplayPolicy().setForceShowSystemBars(true);
+    public void testControlsForDispatch_remoteInsetsControllerControlsBars_appHasNoControl() {
+        mDisplayContent.setRemoteInsetsController(createDisplayWindowInsetsController());
+        mDisplayContent.getInsetsPolicy().setRemoteInsetsControllerControlsSystemBars(true);
         addWindow(TYPE_STATUS_BAR, "statusBar");
         addWindow(TYPE_NAVIGATION_BAR, "navBar");
 
diff --git a/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java
index 0a27e1a..73ac408 100644
--- a/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java
@@ -20,6 +20,8 @@
 import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
 import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
 import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
+import static android.view.InsetsState.ITYPE_CLIMATE_BAR;
+import static android.view.InsetsState.ITYPE_EXTRA_NAVIGATION_BAR;
 import static android.view.InsetsState.ITYPE_IME;
 import static android.view.InsetsState.ITYPE_NAVIGATION_BAR;
 import static android.view.InsetsState.ITYPE_STATUS_BAR;
@@ -292,12 +294,17 @@
     public void testBarControllingWinChanged() {
         final WindowState navBar = createWindow(null, TYPE_APPLICATION, "navBar");
         final WindowState statusBar = createWindow(null, TYPE_APPLICATION, "statusBar");
+        final WindowState climateBar = createWindow(null, TYPE_APPLICATION, "climateBar");
+        final WindowState extraNavBar = createWindow(null, TYPE_APPLICATION, "extraNavBar");
         final WindowState app = createWindow(null, TYPE_APPLICATION, "app");
         getController().getSourceProvider(ITYPE_STATUS_BAR).setWindow(statusBar, null, null);
         getController().getSourceProvider(ITYPE_NAVIGATION_BAR).setWindow(navBar, null, null);
+        getController().getSourceProvider(ITYPE_CLIMATE_BAR).setWindow(climateBar, null, null);
+        getController().getSourceProvider(ITYPE_EXTRA_NAVIGATION_BAR).setWindow(extraNavBar, null,
+                null);
         getController().onBarControlTargetChanged(app, null, app, null);
         InsetsSourceControl[] controls = getController().getControlsForDispatch(app);
-        assertEquals(2, controls.length);
+        assertEquals(4, controls.length);
     }
 
     @Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java
index 8e85e7b..f277117 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java
@@ -363,12 +363,14 @@
         assertFalse(homeActivity.hasFixedRotationTransform());
     }
 
-    @Test
-    public void testClearFixedRotationLaunchingAppAfterCleanupAnimation() {
+    private ActivityRecord prepareFixedRotationLaunchingAppWithRecentsAnim() {
         final ActivityRecord homeActivity = createHomeActivity();
         homeActivity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
         final ActivityRecord activity = createActivityRecord(mDefaultDisplay,
                 WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD);
+        // Add a window so it can be animated by the recents.
+        final WindowState win = createWindow(null, TYPE_BASE_APPLICATION, activity, "win");
+        activity.addWindow(win);
         // Assume an activity is launching to different rotation.
         mDefaultDisplay.setFixedRotationLaunchingApp(activity,
                 (mDefaultDisplay.getRotation() + 1) % 4);
@@ -379,6 +381,14 @@
         // Before the transition is done, the recents animation is triggered.
         initializeRecentsAnimationController(mController, homeActivity);
         assertFalse(homeActivity.hasFixedRotationTransform());
+        assertTrue(mController.isAnimatingTask(activity.getTask()));
+
+        return activity;
+    }
+
+    @Test
+    public void testClearFixedRotationLaunchingAppAfterCleanupAnimation() {
+        final ActivityRecord activity = prepareFixedRotationLaunchingAppWithRecentsAnim();
 
         // Simulate giving up the swipe up gesture to keep the original activity as top.
         mController.cleanupAnimation(REORDER_MOVE_TO_ORIGINAL_POSITION);
@@ -388,6 +398,21 @@
     }
 
     @Test
+    public void testKeepFixedRotationWhenMovingRecentsToTop() {
+        final ActivityRecord activity = prepareFixedRotationLaunchingAppWithRecentsAnim();
+        // Assume a transition animation has started running before recents animation. Then the
+        // activity will receive onAnimationFinished that notifies app transition finished when
+        // removing the recents animation of task.
+        activity.getTask().getAnimationSources().add(activity);
+
+        // Simulate swiping to home/recents before the transition is done.
+        mController.cleanupAnimation(REORDER_MOVE_TO_TOP);
+        // The rotation transform should be preserved. In real case, it will be cleared by the next
+        // move-to-top transition.
+        assertTrue(activity.hasFixedRotationTransform());
+    }
+
+    @Test
     public void testWallpaperHasFixedRotationApplied() {
         mWm.setRecentsAnimationController(mController);
 
diff --git a/services/tests/wmtests/src/com/android/server/wm/ScreenDecorWindowTests.java b/services/tests/wmtests/src/com/android/server/wm/ScreenDecorWindowTests.java
index 31a102a..25ba6db3 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ScreenDecorWindowTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ScreenDecorWindowTests.java
@@ -25,8 +25,8 @@
 import static android.view.Gravity.LEFT;
 import static android.view.Gravity.RIGHT;
 import static android.view.Gravity.TOP;
-import static android.view.InsetsState.ITYPE_NAVIGATION_BAR;
-import static android.view.InsetsState.ITYPE_STATUS_BAR;
+import static android.view.InsetsState.ITYPE_CLIMATE_BAR;
+import static android.view.InsetsState.ITYPE_EXTRA_NAVIGATION_BAR;
 import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
 import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN;
 import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
@@ -68,6 +68,7 @@
 
 import org.junit.After;
 import org.junit.Before;
+import org.junit.Ignore;
 import org.junit.Test;
 
 import java.util.ArrayList;
@@ -142,6 +143,9 @@
         assertInsetGreaterOrEqual(mTestActivity, RIGHT, mDecorThickness);
     }
 
+    // Decor windows (i.e windows using PRIVATE_FLAG_IS_SCREEN_DECOR) are no longer supported.
+    // PRIVATE_FLAG_IS_SCREEN_DECOR and related code will be deprecated/removed soon.
+    @Ignore
     @Test
     public void testMultipleDecors() {
         // Test 2 decor windows on-top.
@@ -190,7 +194,7 @@
 
     @Test
     public void testProvidesInsetsTypes() {
-        int[] providesInsetsTypes = new int[]{ITYPE_STATUS_BAR};
+        int[] providesInsetsTypes = new int[]{ITYPE_CLIMATE_BAR};
         final View win = createWindow("StatusBarSubPanel", TOP, MATCH_PARENT, mDecorThickness, RED,
                 FLAG_LAYOUT_IN_SCREEN, 0, providesInsetsTypes);
 
@@ -199,7 +203,7 @@
 
     private View createDecorWindow(int gravity, int width, int height) {
         int[] providesInsetsTypes =
-                new int[]{gravity == TOP ? ITYPE_STATUS_BAR : ITYPE_NAVIGATION_BAR};
+                new int[]{gravity == TOP ? ITYPE_CLIMATE_BAR : ITYPE_EXTRA_NAVIGATION_BAR};
         return createWindow("decorWindow", gravity, width, height, RED,
                 FLAG_LAYOUT_IN_SCREEN, PRIVATE_FLAG_IS_SCREEN_DECOR, providesInsetsTypes);
     }
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskRecordTests.java
index fb24d86..ddaa586 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskRecordTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskRecordTests.java
@@ -403,6 +403,31 @@
     }
 
     @Test
+    public void testComputeConfigResourceLayoutOverrides() {
+        final Rect fullScreenBounds = new Rect(0, 0, 1000, 2500);
+        TestDisplayContent display = new TestDisplayContent.Builder(
+                mService, fullScreenBounds.width(), fullScreenBounds.height()).build();
+        final Task task = new TaskBuilder(mSupervisor).setDisplay(display).build();
+        final Configuration inOutConfig = new Configuration();
+        final Configuration parentConfig = new Configuration();
+        final Rect nonLongBounds = new Rect(0, 0, 1000, 1250);
+        parentConfig.windowConfiguration.setBounds(fullScreenBounds);
+        parentConfig.windowConfiguration.setAppBounds(fullScreenBounds);
+        parentConfig.densityDpi = 400;
+        parentConfig.screenHeightDp = (fullScreenBounds.bottom * 160) / parentConfig.densityDpi;
+        parentConfig.screenWidthDp = (fullScreenBounds.right * 160) / parentConfig.densityDpi;
+        parentConfig.windowConfiguration.setRotation(ROTATION_0);
+
+        // Set BOTH screenW/H to an override value
+        inOutConfig.screenWidthDp = nonLongBounds.width() * 160 / parentConfig.densityDpi;
+        inOutConfig.screenHeightDp = nonLongBounds.height() * 160 / parentConfig.densityDpi;
+        task.computeConfigResourceOverrides(inOutConfig, parentConfig);
+
+        // screenLayout should honor override when both screenW/H are set.
+        assertTrue((inOutConfig.screenLayout & Configuration.SCREENLAYOUT_LONG_NO) != 0);
+    }
+
+    @Test
     public void testComputeNestedConfigResourceOverrides() {
         final Task task = new TaskBuilder(mSupervisor).build();
         assertTrue(task.getResolvedOverrideBounds().isEmpty());
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskTests.java
index 9d88ada..9feb83f 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskTests.java
@@ -186,4 +186,19 @@
             assertTrue(r.finishing);
         });
     }
+
+    @Test
+    public void testSwitchUser() {
+        final Task rootTask = createTaskStackOnDisplay(mDisplayContent);
+        final Task childTask = createTaskInStack((ActivityStack) rootTask, 0 /* userId */);
+        final Task leafTask1 = createTaskInStack((ActivityStack) childTask, 10 /* userId */);
+        final Task leafTask2 = createTaskInStack((ActivityStack) childTask, 0 /* userId */);
+        assertEquals(1, rootTask.getChildCount());
+        assertEquals(leafTask2, childTask.getTopChild());
+
+        doReturn(true).when(leafTask1).showToCurrentUser();
+        rootTask.switchUser(10);
+        assertEquals(1, rootTask.getChildCount());
+        assertEquals(leafTask1, childTask.getTopChild());
+    }
 }
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
index f52905e..499bf66 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
@@ -60,24 +60,6 @@
     @Rule
     public ExpectedException mExpectedException = ExpectedException.none();
 
-    @Test
-    public void testForceShowSystemBarsThrowsExceptionForNonAutomotive() {
-        if (!isAutomotive()) {
-            mExpectedException.expect(UnsupportedOperationException.class);
-
-            mWm.setForceShowSystemBars(true);
-        }
-    }
-
-    @Test
-    public void testForceShowSystemBarsDoesNotThrowExceptionForAutomotiveWithStatusBarPermission() {
-        if (isAutomotive()) {
-            mExpectedException.none();
-
-            mWm.setForceShowSystemBars(true);
-        }
-    }
-
     private boolean isAutomotive() {
         return getInstrumentation().getTargetContext().getPackageManager().hasSystemFeature(
                 PackageManager.FEATURE_AUTOMOTIVE);
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerTests.java
index cdf8eb4..a46e6d3 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerTests.java
@@ -16,6 +16,8 @@
 
 package com.android.server.wm;
 
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
 import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
 import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
 import static android.view.Display.INVALID_DISPLAY;
@@ -253,6 +255,28 @@
         assertFalse(mWpc.registeredForActivityConfigChanges());
     }
 
+    @Test
+    public void testProcessLevelConfiguration() {
+        Configuration config = new Configuration();
+        config.windowConfiguration.setActivityType(ACTIVITY_TYPE_HOME);
+        mWpc.onRequestedOverrideConfigurationChanged(config);
+        assertEquals(ACTIVITY_TYPE_HOME, config.windowConfiguration.getActivityType());
+        assertEquals(ACTIVITY_TYPE_UNDEFINED, mWpc.getActivityType());
+
+        mWpc.onMergedOverrideConfigurationChanged(config);
+        assertEquals(ACTIVITY_TYPE_HOME, config.windowConfiguration.getActivityType());
+        assertEquals(ACTIVITY_TYPE_UNDEFINED, mWpc.getActivityType());
+
+        final int globalSeq = 100;
+        mRootWindowContainer.getConfiguration().seq = globalSeq;
+        invertOrientation(mWpc.getConfiguration());
+        new ActivityBuilder(mService).setCreateTask(true).setUseProcess(mWpc).build();
+
+        assertTrue(mWpc.registeredForActivityConfigChanges());
+        assertEquals("Config seq of process should not be affected by activity",
+                mWpc.getConfiguration().seq, globalSeq);
+    }
+
     private TestDisplayContent createTestDisplayContentInContainer() {
         return new TestDisplayContent.Builder(mService, 1000, 1500).build();
     }
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
index a1e5b80..156298c 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
@@ -41,11 +41,15 @@
 
 import android.content.Context;
 import android.content.Intent;
+import android.os.RemoteException;
 import android.os.UserHandle;
 import android.util.Log;
 import android.view.Display;
 import android.view.DisplayInfo;
+import android.view.IDisplayWindowInsetsController;
 import android.view.IWindow;
+import android.view.InsetsSourceControl;
+import android.view.InsetsState;
 import android.view.SurfaceControl.Transaction;
 import android.view.View;
 import android.view.WindowManager;
@@ -123,7 +127,7 @@
             mChildAppWindowBelow = createCommonWindow(mAppWindow,
                     TYPE_APPLICATION_MEDIA_OVERLAY,
                     "mChildAppWindowBelow");
-            mDisplayContent.getDisplayPolicy().setForceShowSystemBars(false);
+            mDisplayContent.getInsetsPolicy().setRemoteInsetsControllerControlsSystemBars(false);
 
             // Adding a display will cause freezing the display. Make sure to wait until it's
             // unfrozen to not run into race conditions with the tests.
@@ -344,6 +348,32 @@
         return createNewDisplay(displayInfo, false /* supportIme */);
     }
 
+    IDisplayWindowInsetsController createDisplayWindowInsetsController() {
+        return new IDisplayWindowInsetsController.Stub() {
+
+            @Override
+            public void insetsChanged(InsetsState insetsState) throws RemoteException {
+            }
+
+            @Override
+            public void insetsControlChanged(InsetsState insetsState,
+                    InsetsSourceControl[] insetsSourceControls) throws RemoteException {
+            }
+
+            @Override
+            public void showInsets(int i, boolean b) throws RemoteException {
+            }
+
+            @Override
+            public void hideInsets(int i, boolean b) throws RemoteException {
+            }
+
+            @Override
+            public void topFocusedWindowChanged(String packageName) {
+            }
+        };
+    }
+
     /** Sets the default minimum task size to 1 so that tests can use small task sizes */
     void removeGlobalMinSizeRestriction() {
         mWm.mAtmService.mRootWindowContainer.mDefaultMinSizeOfResizeableTaskDp = 1;
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTokenTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowTokenTests.java
index 23a097e..0ab9912 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowTokenTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowTokenTests.java
@@ -24,6 +24,7 @@
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
@@ -77,6 +78,10 @@
         assertFalse(token.hasWindow(window12));
         assertTrue(token.hasWindow(window2));
         assertTrue(token.hasWindow(window3));
+
+        // The child windows should have the same window token as their parents.
+        assertEquals(window1.mToken, window11.mToken);
+        assertEquals(window1.mToken, window12.mToken);
     }
 
     @Test
@@ -139,6 +144,8 @@
     public void testFinishFixedRotationTransform() {
         final WindowToken appToken = mAppWindow.mToken;
         final WindowToken wallpaperToken = mWallpaperWindow.mToken;
+        final WindowToken testToken =
+                WindowTestUtils.createTestWindowToken(TYPE_APPLICATION_OVERLAY, mDisplayContent);
         final Configuration config = new Configuration(mDisplayContent.getConfiguration());
         final int originalRotation = config.windowConfiguration.getRotation();
         final int targetRotation = (originalRotation + 1) % 4;
@@ -151,11 +158,20 @@
         assertEquals(targetRotation, appToken.getWindowConfiguration().getRotation());
         assertEquals(targetRotation, wallpaperToken.getWindowConfiguration().getRotation());
 
-        // The display doesn't rotate, the transformation will be canceled.
-        mAppWindow.mToken.finishFixedRotationTransform();
+        testToken.applyFixedRotationTransform(mDisplayInfo, mDisplayContent.mDisplayFrames, config);
+        // The wallpaperToken was linked to appToken, this should make it link to testToken.
+        wallpaperToken.linkFixedRotationTransform(testToken);
 
-        // The window tokens should restore to the original rotation.
+        // Assume the display doesn't rotate, the transformation will be canceled.
+        appToken.finishFixedRotationTransform();
+
+        // The appToken should restore to the original rotation.
         assertEquals(originalRotation, appToken.getWindowConfiguration().getRotation());
+        // The wallpaperToken is linked to testToken, it should keep the target rotation.
+        assertNotEquals(originalRotation, wallpaperToken.getWindowConfiguration().getRotation());
+
+        testToken.finishFixedRotationTransform();
+        // The rotation of wallpaperToken should be restored because its linked state is finished.
         assertEquals(originalRotation, wallpaperToken.getWindowConfiguration().getRotation());
     }
 
diff --git a/services/usage/java/com/android/server/usage/UsageStatsService.java b/services/usage/java/com/android/server/usage/UsageStatsService.java
index 321657d..a5b6760 100644
--- a/services/usage/java/com/android/server/usage/UsageStatsService.java
+++ b/services/usage/java/com/android/server/usage/UsageStatsService.java
@@ -183,6 +183,7 @@
     private static class ActivityData {
         private final String mTaskRootPackage;
         private final String mTaskRootClass;
+        public int lastEvent = Event.NONE;
         private ActivityData(String taskRootPackage, String taskRootClass) {
             mTaskRootPackage = taskRootPackage;
             mTaskRootClass = taskRootClass;
@@ -787,6 +788,7 @@
         switch (event.mEventType) {
             case Event.ACTIVITY_RESUMED:
             case Event.ACTIVITY_PAUSED:
+            case Event.ACTIVITY_STOPPED:
                 uid = mPackageManagerInternal.getPackageUid(event.mPackage, 0, userId);
                 break;
             default:
@@ -819,8 +821,10 @@
                                     .APP_USAGE_EVENT_OCCURRED__EVENT_TYPE__MOVE_TO_FOREGROUND);
                     // check if this activity has already been resumed
                     if (mVisibleActivities.get(event.mInstanceId) != null) break;
-                    mVisibleActivities.put(event.mInstanceId,
-                            new ActivityData(event.mTaskRootPackage, event.mTaskRootClass));
+                    final ActivityData resumedData = new ActivityData(event.mTaskRootPackage,
+                            event.mTaskRootClass);
+                    resumedData.lastEvent = Event.ACTIVITY_RESUMED;
+                    mVisibleActivities.put(event.mInstanceId, resumedData);
                     try {
                         switch(mUsageSource) {
                             case USAGE_SOURCE_CURRENT_ACTIVITY:
@@ -836,16 +840,17 @@
                     }
                     break;
                 case Event.ACTIVITY_PAUSED:
-                    if (event.mTaskRootPackage == null) {
-                        // Task Root info is missing. Repair the event based on previous data
-                        final ActivityData prevData = mVisibleActivities.get(event.mInstanceId);
-                        if (prevData == null) {
-                            Slog.w(TAG, "Unexpected activity event reported! (" + event.mPackage
-                                    + "/" + event.mClass + " event : " + event.mEventType
-                                    + " instanceId : " + event.mInstanceId + ")");
-                        } else {
-                            event.mTaskRootPackage = prevData.mTaskRootPackage;
-                            event.mTaskRootClass = prevData.mTaskRootClass;
+                    final ActivityData pausedData = mVisibleActivities.get(event.mInstanceId);
+                    if (pausedData == null) {
+                        Slog.w(TAG, "Unexpected activity event reported! (" + event.mPackage
+                                + "/" + event.mClass + " event : " + event.mEventType
+                                + " instanceId : " + event.mInstanceId + ")");
+                    } else {
+                        pausedData.lastEvent = Event.ACTIVITY_PAUSED;
+                        if (event.mTaskRootPackage == null) {
+                            // Task Root info is missing. Repair the event based on previous data
+                            event.mTaskRootPackage = pausedData.mTaskRootPackage;
+                            event.mTaskRootClass = pausedData.mTaskRootClass;
                         }
                     }
                     FrameworkStatsLog.write(
@@ -868,6 +873,16 @@
                         return;
                     }
 
+                    if (prevData.lastEvent != Event.ACTIVITY_PAUSED) {
+                        FrameworkStatsLog.write(
+                                FrameworkStatsLog.APP_USAGE_EVENT_OCCURRED,
+                                uid,
+                                event.mPackage,
+                                event.mClass,
+                                FrameworkStatsLog
+                                        .APP_USAGE_EVENT_OCCURRED__EVENT_TYPE__MOVE_TO_BACKGROUND);
+                    }
+
                     ArraySet<String> tokens;
                     synchronized (mUsageReporters) {
                         tokens = mUsageReporters.removeReturnOld(event.mInstanceId);
diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java
index 6c13cd7..fe0f7b8 100644
--- a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java
+++ b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java
@@ -697,7 +697,7 @@
             synchronized (mLock) {
                 ModuleProperties properties = mSoundTriggerHelper.getModuleProperties();
                 sEventLogger.log(new SoundTriggerLogger.StringEvent(
-                        "getModuleProperties(): " + properties.toString()));
+                        "getModuleProperties(): " + properties));
                 return properties;
             }
         }
@@ -1284,32 +1284,25 @@
          * @return The initialized AudioRecord
          */
         private @NonNull AudioRecord createAudioRecordForEvent(
-                @NonNull SoundTrigger.GenericRecognitionEvent event) {
+                @NonNull SoundTrigger.GenericRecognitionEvent event)
+                throws IllegalArgumentException, UnsupportedOperationException {
             AudioAttributes.Builder attributesBuilder = new AudioAttributes.Builder();
             attributesBuilder.setInternalCapturePreset(MediaRecorder.AudioSource.HOTWORD);
             AudioAttributes attributes = attributesBuilder.build();
 
-            // Use same AudioFormat processing as in RecognitionEvent.fromParcel
             AudioFormat originalFormat = event.getCaptureFormat();
-            AudioFormat captureFormat = (new AudioFormat.Builder())
-                    .setChannelMask(originalFormat.getChannelMask())
-                    .setEncoding(originalFormat.getEncoding())
-                    .setSampleRate(originalFormat.getSampleRate())
-                    .build();
-
-            int bufferSize = AudioRecord.getMinBufferSize(
-                    captureFormat.getSampleRate() == AudioFormat.SAMPLE_RATE_UNSPECIFIED
-                            ? AudioFormat.SAMPLE_RATE_HZ_MAX
-                            : captureFormat.getSampleRate(),
-                    captureFormat.getChannelCount() == 2
-                            ? AudioFormat.CHANNEL_IN_STEREO
-                            : AudioFormat.CHANNEL_IN_MONO,
-                    captureFormat.getEncoding());
 
             sEventLogger.log(new SoundTriggerLogger.StringEvent("createAudioRecordForEvent"));
 
-            return new AudioRecord(attributes, captureFormat, bufferSize,
-                    event.getCaptureSession());
+            return (new AudioRecord.Builder())
+                        .setAudioAttributes(attributes)
+                        .setAudioFormat((new AudioFormat.Builder())
+                            .setChannelMask(originalFormat.getChannelMask())
+                            .setEncoding(originalFormat.getEncoding())
+                            .setSampleRate(originalFormat.getSampleRate())
+                            .build())
+                        .setSessionId(event.getCaptureSession())
+                        .build();
         }
 
         @Override
@@ -1335,12 +1328,16 @@
                     // execute if throttled:
                     () -> {
                         if (event.isCaptureAvailable()) {
-                            AudioRecord capturedData = createAudioRecordForEvent(event);
-
-                            // Currently we need to start and release the audio record to reset
-                            // the DSP even if we don't want to process the event
-                            capturedData.startRecording();
-                            capturedData.release();
+                            try {
+                                // Currently we need to start and release the audio record to reset
+                                // the DSP even if we don't want to process the eve
+                                AudioRecord capturedData = createAudioRecordForEvent(event);
+                                capturedData.startRecording();
+                                capturedData.release();
+                            } catch (IllegalArgumentException | UnsupportedOperationException e) {
+                                Slog.w(TAG, mPuuid + ": createAudioRecordForEvent(" + event
+                                        + "), failed to create AudioRecord");
+                            }
                         }
                     }));
         }
diff --git a/telecomm/java/com/android/internal/telecom/IDeviceIdleControllerAdapter.aidl b/telecomm/java/com/android/internal/telecom/IDeviceIdleControllerAdapter.aidl
new file mode 100644
index 0000000..50bbf4c
--- /dev/null
+++ b/telecomm/java/com/android/internal/telecom/IDeviceIdleControllerAdapter.aidl
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.telecom;
+
+/*
+ * Adapter interface for using DeviceIdleController, since the PowerWhitelistManager is not
+ * directly accessible in the SYSTEM process.
+ */
+interface IDeviceIdleControllerAdapter {
+    void exemptAppTemporarilyForEvent(String packageName, long duration, int userHandle,
+            String reason);
+}
\ No newline at end of file
diff --git a/telecomm/java/com/android/internal/telecom/IInternalServiceRetriever.aidl b/telecomm/java/com/android/internal/telecom/IInternalServiceRetriever.aidl
new file mode 100644
index 0000000..b560106
--- /dev/null
+++ b/telecomm/java/com/android/internal/telecom/IInternalServiceRetriever.aidl
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.telecom;
+
+import com.android.internal.telecom.IDeviceIdleControllerAdapter;
+
+/*
+ * Interface used to retrieve services that are only accessible via LocalService in the SYSTEM
+ * process.
+ */
+interface IInternalServiceRetriever {
+    IDeviceIdleControllerAdapter getDeviceIdleController();
+}
diff --git a/telecomm/java/com/android/internal/telecom/ITelecomLoader.aidl b/telecomm/java/com/android/internal/telecom/ITelecomLoader.aidl
new file mode 100644
index 0000000..eda0f5b
--- /dev/null
+++ b/telecomm/java/com/android/internal/telecom/ITelecomLoader.aidl
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.telecom;
+
+import com.android.internal.telecom.ITelecomService;
+import com.android.internal.telecom.IInternalServiceRetriever;
+
+/*
+ * Internal interface for getting an instance of the ITelecomService for external publication.
+ * Allows the TelecomLoaderService to pass additional dependencies required for creation.
+ */
+interface ITelecomLoader {
+    ITelecomService createTelecomService(IInternalServiceRetriever retriever);
+}
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index a4f48af..0c2a649 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -1702,6 +1702,15 @@
             "hide_lte_plus_data_icon_bool";
 
     /**
+     * The combined channel bandwidth threshold (non-inclusive) in KHz required to display the
+     * LTE+ data icon. It is 20000 by default, meaning the LTE+ icon will be shown if the device is
+     * using carrier aggregation and the combined channel bandwidth is strictly greater than 20 MHz.
+     * @hide
+     */
+    public static final String KEY_LTE_PLUS_THRESHOLD_BANDWIDTH_KHZ_INT =
+            "lte_plus_threshold_bandwidth_khz_int";
+
+    /**
      * The string is used to filter redundant string from PLMN Network Name that's supplied by
      * specific carrier.
      *
@@ -4302,6 +4311,7 @@
         sDefaults.putString(KEY_OPERATOR_NAME_FILTER_PATTERN_STRING, "");
         sDefaults.putString(KEY_SHOW_CARRIER_DATA_ICON_PATTERN_STRING, "");
         sDefaults.putBoolean(KEY_HIDE_LTE_PLUS_DATA_ICON_BOOL, true);
+        sDefaults.putInt(KEY_LTE_PLUS_THRESHOLD_BANDWIDTH_KHZ_INT, 20000);
         sDefaults.putBoolean(KEY_NR_ENABLED_BOOL, true);
         sDefaults.putBoolean(KEY_LTE_ENABLED_BOOL, true);
         sDefaults.putBoolean(KEY_SUPPORT_TDSCDMA_BOOL, false);
diff --git a/telephony/java/android/telephony/CellIdentityNr.java b/telephony/java/android/telephony/CellIdentityNr.java
index 3923c756..45a67b3 100644
--- a/telephony/java/android/telephony/CellIdentityNr.java
+++ b/telephony/java/android/telephony/CellIdentityNr.java
@@ -217,7 +217,7 @@
      * Get the tracking area code.
      * @return a 24 bit integer or {@link CellInfo#UNAVAILABLE} if unknown.
      */
-    @IntRange(from = 0, to = 16777215)
+    @IntRange(from = 0, to = 65535)
     public int getTac() {
         return mTac;
     }
diff --git a/tests/RollbackTest/Android.bp b/tests/RollbackTest/Android.bp
index a23df92..4f5a305 100644
--- a/tests/RollbackTest/Android.bp
+++ b/tests/RollbackTest/Android.bp
@@ -53,6 +53,9 @@
     name: "MultiUserRollbackTest",
     srcs: ["MultiUserRollbackTest/src/**/*.java"],
     libs: ["tradefed"],
+    static_libs: [
+        "frameworks-base-hostutils",
+    ],
     test_suites: ["general-tests"],
     test_config: "MultiUserRollbackTest.xml",
 }
diff --git a/tests/RollbackTest/MultiUserRollbackTest/src/com/android/tests/rollback/host/MultiUserRollbackTest.java b/tests/RollbackTest/MultiUserRollbackTest/src/com/android/tests/rollback/host/MultiUserRollbackTest.java
index 42b886f..f160847 100644
--- a/tests/RollbackTest/MultiUserRollbackTest/src/com/android/tests/rollback/host/MultiUserRollbackTest.java
+++ b/tests/RollbackTest/MultiUserRollbackTest/src/com/android/tests/rollback/host/MultiUserRollbackTest.java
@@ -24,6 +24,7 @@
 
 import org.junit.After;
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -40,6 +41,9 @@
     private static final long SWITCH_USER_COMPLETED_NUMBER_OF_POLLS = 60;
     private static final long SWITCH_USER_COMPLETED_POLL_INTERVAL_IN_MILLIS = 1000;
 
+    @Rule
+    public AbandonSessionsRule mHostTestRule = new AbandonSessionsRule(this);
+
     @After
     public void tearDown() throws Exception {
         removeSecondaryUserIfNecessary();
@@ -59,6 +63,30 @@
         runPhaseForUsers("testBasic", mSecondaryUserId);
     }
 
+    /**
+     * Tests staged install/rollback works correctly on the 2nd user.
+     */
+    @Test
+    public void testStagedRollback() throws Exception {
+        runPhaseForUsers("testStagedRollback_Phase1", mSecondaryUserId);
+        getDevice().reboot();
+
+        // Need to unlock the user for device tests to run successfully
+        getDevice().startUser(mSecondaryUserId);
+        awaitUserUnlocked(mSecondaryUserId);
+        runPhaseForUsers("testStagedRollback_Phase2", mSecondaryUserId);
+        getDevice().reboot();
+
+        getDevice().startUser(mSecondaryUserId);
+        awaitUserUnlocked(mSecondaryUserId);
+        runPhaseForUsers("testStagedRollback_Phase3", mSecondaryUserId);
+        getDevice().reboot();
+
+        getDevice().startUser(mSecondaryUserId);
+        awaitUserUnlocked(mSecondaryUserId);
+        runPhaseForUsers("testStagedRollback_Phase4", mSecondaryUserId);
+    }
+
     @Test
     public void testMultipleUsers() throws Exception {
         runPhaseForUsers("testMultipleUsersInstallV1", mOriginalUserId, mSecondaryUserId);
diff --git a/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/MultiUserRollbackTest.java b/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/MultiUserRollbackTest.java
index 8641f4d..5d133a4 100644
--- a/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/MultiUserRollbackTest.java
+++ b/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/MultiUserRollbackTest.java
@@ -115,4 +115,32 @@
         assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1);
         InstallUtils.processUserData(TestApp.A);
     }
+
+    @Test
+    public void testStagedRollback_Phase1() throws Exception {
+        assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(-1);
+        Install.single(TestApp.A1).setStaged().commit();
+        assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(-1);
+    }
+
+    @Test
+    public void testStagedRollback_Phase2() throws Exception {
+        assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1);
+        Install.single(TestApp.A2).setStaged().setEnableRollback().commit();
+        assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1);
+    }
+
+    @Test
+    public void testStagedRollback_Phase3() throws Exception {
+        assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(2);
+        RollbackInfo rollback = RollbackUtils.waitForAvailableRollback(TestApp.A);
+        assertThat(rollback).packagesContainsExactly(Rollback.from(TestApp.A2).to(TestApp.A1));
+        RollbackUtils.rollback(rollback.getRollbackId());
+        assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(2);
+    }
+
+    @Test
+    public void testStagedRollback_Phase4() {
+        assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1);
+    }
 }
diff --git a/tests/StagedInstallTest/src/com/android/tests/stagedinstallinternal/host/StagedInstallInternalTest.java b/tests/StagedInstallTest/src/com/android/tests/stagedinstallinternal/host/StagedInstallInternalTest.java
index ccd18dd..989d650 100644
--- a/tests/StagedInstallTest/src/com/android/tests/stagedinstallinternal/host/StagedInstallInternalTest.java
+++ b/tests/StagedInstallTest/src/com/android/tests/stagedinstallinternal/host/StagedInstallInternalTest.java
@@ -147,7 +147,7 @@
 
     private void restartSystemServer() throws Exception {
         // Restart the system server
-        final long oldStartTime = getDevice().getProcessByName("system_server").getStartTime();
+        long oldStartTime = getDevice().getProcessByName("system_server").getStartTime();
 
         getDevice().enableAdbRoot(); // Need root to restart system server
         assertThat(getDevice().executeShellCommand("am restart")).contains("Restart the system");
diff --git a/wifi/java/android/net/wifi/ScanResult.java b/wifi/java/android/net/wifi/ScanResult.java
index aa3a139..b276f2ed 100644
--- a/wifi/java/android/net/wifi/ScanResult.java
+++ b/wifi/java/android/net/wifi/ScanResult.java
@@ -581,12 +581,18 @@
      * 6 GHz band frequency of first channel in MHz
      * @hide
      */
-    public static final int BAND_6_GHZ_START_FREQ_MHZ = 5945;
+    public static final int BAND_6_GHZ_START_FREQ_MHZ = 5955;
     /**
      * 6 GHz band frequency of last channel in MHz
      * @hide
      */
-    public static final int BAND_6_GHZ_END_FREQ_MHZ = 7105;
+    public static final int BAND_6_GHZ_END_FREQ_MHZ = 7115;
+
+    /**
+     * 6 GHz band operating class 136 channel 2 center frequency in MHz
+     * @hide
+     */
+    public static final int BAND_6_GHZ_OP_CLASS_136_CH_2_FREQ_MHZ = 5935;
 
     /**
      * Utility function to check if a frequency within 2.4 GHz band
@@ -618,7 +624,10 @@
      * @hide
      */
     public static boolean is6GHz(int freqMhz) {
-        return freqMhz >= BAND_6_GHZ_START_FREQ_MHZ && freqMhz <= BAND_6_GHZ_END_FREQ_MHZ;
+        if (freqMhz == BAND_6_GHZ_OP_CLASS_136_CH_2_FREQ_MHZ) {
+            return true;
+        }
+        return (freqMhz >= BAND_6_GHZ_START_FREQ_MHZ && freqMhz <= BAND_6_GHZ_END_FREQ_MHZ);
     }
 
     /**
@@ -649,6 +658,9 @@
         }
         if (band == WifiScanner.WIFI_BAND_6_GHZ) {
             if (channel >= BAND_6_GHZ_FIRST_CH_NUM && channel <= BAND_6_GHZ_LAST_CH_NUM) {
+                if (channel == 2) {
+                    return BAND_6_GHZ_OP_CLASS_136_CH_2_FREQ_MHZ;
+                }
                 return ((channel - BAND_6_GHZ_FIRST_CH_NUM) * 5) + BAND_6_GHZ_START_FREQ_MHZ;
             } else {
                 return UNSPECIFIED;
@@ -673,6 +685,9 @@
         } else if (is5GHz(freqMhz)) {
             return ((freqMhz - BAND_5_GHZ_START_FREQ_MHZ) / 5) + BAND_5_GHZ_FIRST_CH_NUM;
         } else if (is6GHz(freqMhz)) {
+            if (freqMhz == BAND_6_GHZ_OP_CLASS_136_CH_2_FREQ_MHZ) {
+                return 2;
+            }
             return ((freqMhz - BAND_6_GHZ_START_FREQ_MHZ) / 5) + BAND_6_GHZ_FIRST_CH_NUM;
         }
 
diff --git a/wifi/java/android/net/wifi/SoftApConfiguration.java b/wifi/java/android/net/wifi/SoftApConfiguration.java
index a5e76e6..d2ff658 100644
--- a/wifi/java/android/net/wifi/SoftApConfiguration.java
+++ b/wifi/java/android/net/wifi/SoftApConfiguration.java
@@ -533,6 +533,7 @@
                 wifiConfig.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE);
                 break;
             case SECURITY_TYPE_WPA2_PSK:
+            case SECURITY_TYPE_WPA3_SAE_TRANSITION:
                 wifiConfig.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA2_PSK);
                 break;
             default:
diff --git a/wifi/java/android/net/wifi/WifiEnterpriseConfig.java b/wifi/java/android/net/wifi/WifiEnterpriseConfig.java
index 77fa673..90edc45 100644
--- a/wifi/java/android/net/wifi/WifiEnterpriseConfig.java
+++ b/wifi/java/android/net/wifi/WifiEnterpriseConfig.java
@@ -30,6 +30,9 @@
 import java.nio.charset.StandardCharsets;
 import java.security.PrivateKey;
 import java.security.cert.X509Certificate;
+import java.security.interfaces.ECPublicKey;
+import java.security.interfaces.RSAPublicKey;
+import java.security.spec.ECParameterSpec;
 import java.util.Arrays;
 import java.util.HashMap;
 import java.util.List;
@@ -1442,4 +1445,50 @@
         }
         return TextUtils.isEmpty(getCaPath());
     }
+
+    /**
+     * Check if a given certificate Get the Suite-B cipher from the certificate
+     *
+     * @param x509Certificate Certificate to process
+     * @return true if the certificate OID matches the Suite-B requirements for RSA or ECDSA
+     * certificates, or false otherwise.
+     * @hide
+     */
+    public static boolean isSuiteBCipherCert(@Nullable X509Certificate x509Certificate) {
+        if (x509Certificate == null) {
+            return false;
+        }
+        final String sigAlgOid = x509Certificate.getSigAlgOID();
+
+        // Wi-Fi alliance requires the use of both ECDSA secp384r1 and RSA 3072 certificates
+        // in WPA3-Enterprise 192-bit security networks, which are also known as Suite-B-192
+        // networks, even though NSA Suite-B-192 mandates ECDSA only. The use of the term
+        // Suite-B was already coined in the IEEE 802.11-2016 specification for
+        // AKM 00-0F-AC but the test plan for WPA3-Enterprise 192-bit for APs mandates
+        // support for both RSA and ECDSA, and for STAs it mandates ECDSA and optionally
+        // RSA. In order to be compatible with all WPA3-Enterprise 192-bit deployments,
+        // we are supporting both types here.
+        if (sigAlgOid.equals("1.2.840.113549.1.1.12")) {
+            // sha384WithRSAEncryption
+            if (x509Certificate.getPublicKey() instanceof RSAPublicKey) {
+                final RSAPublicKey rsaPublicKey = (RSAPublicKey) x509Certificate.getPublicKey();
+                if (rsaPublicKey.getModulus() != null
+                        && rsaPublicKey.getModulus().bitLength() >= 3072) {
+                    return true;
+                }
+            }
+        } else if (sigAlgOid.equals("1.2.840.10045.4.3.3")) {
+            // ecdsa-with-SHA384
+            if (x509Certificate.getPublicKey() instanceof ECPublicKey) {
+                final ECPublicKey ecPublicKey = (ECPublicKey) x509Certificate.getPublicKey();
+                final ECParameterSpec ecParameterSpec = ecPublicKey.getParams();
+
+                if (ecParameterSpec != null && ecParameterSpec.getOrder() != null
+                        && ecParameterSpec.getOrder().bitLength() >= 384) {
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
 }
diff --git a/wifi/java/android/net/wifi/WifiManager.java b/wifi/java/android/net/wifi/WifiManager.java
index fb6af5b..4a61db8 100644
--- a/wifi/java/android/net/wifi/WifiManager.java
+++ b/wifi/java/android/net/wifi/WifiManager.java
@@ -1054,8 +1054,8 @@
     /**
      * Broadcast intent action indicating that the link configuration changed on wifi.
      * <br />Included Extras:
-     * <br />{@link #EXTRA_LINK_PROPERTIES}: {@link android.net.LinkProperties} object associated
-     * with the Wi-Fi network.
+     * <br />{@link #EXTRA_LINK_PROPERTIES}: may not be set starting in Android 11. Check for
+     * <br /> null before reading its value.
      * <br /> No permissions are required to listen to this broadcast.
      * @hide
      */
@@ -1071,6 +1071,10 @@
      * Included in the {@link #ACTION_LINK_CONFIGURATION_CHANGED} broadcast.
      *
      * Retrieve with {@link android.content.Intent#getParcelableExtra(String)}.
+     *
+     * Note: this extra may not be set starting in Android 11. Check for null before reading its
+     * value.
+     *
      * @hide
      */
     @SystemApi
diff --git a/wifi/java/android/net/wifi/WifiNetworkSpecifier.java b/wifi/java/android/net/wifi/WifiNetworkSpecifier.java
index b0213b0..e12bb91 100644
--- a/wifi/java/android/net/wifi/WifiNetworkSpecifier.java
+++ b/wifi/java/android/net/wifi/WifiNetworkSpecifier.java
@@ -78,12 +78,12 @@
         private @Nullable String mWpa3SaePassphrase;
         /**
          * The enterprise configuration details specifying the EAP method,
-         * certificates and other settings associated with the WPA-EAP networks.
+         * certificates and other settings associated with the WPA/WPA2-Enterprise networks.
          */
         private @Nullable WifiEnterpriseConfig mWpa2EnterpriseConfig;
         /**
          * The enterprise configuration details specifying the EAP method,
-         * certificates and other settings associated with the SuiteB networks.
+         * certificates and other settings associated with the WPA3-Enterprise networks.
          */
         private @Nullable WifiEnterpriseConfig mWpa3EnterpriseConfig;
         /**
@@ -243,7 +243,11 @@
 
         /**
          * Set the associated enterprise configuration for this network. Needed for authenticating
-         * to WPA3-SuiteB networks. See {@link WifiEnterpriseConfig} for description.
+         * to WPA3-Enterprise networks (standard and 192-bit security). See
+         * {@link WifiEnterpriseConfig} for description. For 192-bit security networks, both the
+         * client and CA certificates must be provided, and must be of type of either
+         * sha384WithRSAEncryption (OID 1.2.840.113549.1.1.12) or ecdsa-with-SHA384
+         * (OID 1.2.840.10045.4.3.3).
          *
          * @param enterpriseConfig Instance of {@link WifiEnterpriseConfig}.
          * @return Instance of {@link Builder} to enable chaining of the builder method.
@@ -284,8 +288,25 @@
             } else if (mWpa2EnterpriseConfig != null) { // WPA-EAP network
                 configuration.setSecurityParams(WifiConfiguration.SECURITY_TYPE_EAP);
                 configuration.enterpriseConfig = mWpa2EnterpriseConfig;
-            } else if (mWpa3EnterpriseConfig != null) { // WPA3-SuiteB network
-                configuration.setSecurityParams(WifiConfiguration.SECURITY_TYPE_EAP_SUITE_B);
+            } else if (mWpa3EnterpriseConfig != null) { // WPA3-Enterprise
+                if (mWpa3EnterpriseConfig.getEapMethod() == WifiEnterpriseConfig.Eap.TLS
+                        && WifiEnterpriseConfig.isSuiteBCipherCert(
+                        mWpa3EnterpriseConfig.getClientCertificate())
+                        && WifiEnterpriseConfig.isSuiteBCipherCert(
+                        mWpa3EnterpriseConfig.getCaCertificate())) {
+                    // WPA3-Enterprise in 192-bit security mode (Suite-B)
+                    configuration.setSecurityParams(WifiConfiguration.SECURITY_TYPE_EAP_SUITE_B);
+                } else {
+                    // WPA3-Enterprise
+                    configuration.setSecurityParams(WifiConfiguration.SECURITY_TYPE_EAP);
+                    configuration.allowedProtocols.set(WifiConfiguration.Protocol.RSN);
+                    configuration.allowedPairwiseCiphers.set(WifiConfiguration.PairwiseCipher.CCMP);
+                    configuration.allowedPairwiseCiphers.set(
+                            WifiConfiguration.PairwiseCipher.GCMP_256);
+                    configuration.allowedGroupCiphers.set(WifiConfiguration.GroupCipher.CCMP);
+                    configuration.allowedGroupCiphers.set(WifiConfiguration.GroupCipher.GCMP_256);
+                    configuration.requirePmf = true;
+                }
                 configuration.enterpriseConfig = mWpa3EnterpriseConfig;
             } else if (mIsEnhancedOpen) { // OWE network
                 configuration.setSecurityParams(WifiConfiguration.SECURITY_TYPE_OWE);
diff --git a/wifi/java/android/net/wifi/WifiNetworkSuggestion.java b/wifi/java/android/net/wifi/WifiNetworkSuggestion.java
index 4d3a2c0..d8be1d2 100644
--- a/wifi/java/android/net/wifi/WifiNetworkSuggestion.java
+++ b/wifi/java/android/net/wifi/WifiNetworkSuggestion.java
@@ -72,12 +72,12 @@
         private @Nullable String mWpa3SaePassphrase;
         /**
          * The enterprise configuration details specifying the EAP method,
-         * certificates and other settings associated with the WPA-EAP networks.
+         * certificates and other settings associated with the WPA/WPA2-Enterprise networks.
          */
         private @Nullable WifiEnterpriseConfig mWpa2EnterpriseConfig;
         /**
          * The enterprise configuration details specifying the EAP method,
-         * certificates and other settings associated with the SuiteB networks.
+         * certificates and other settings associated with the WPA3-Enterprise networks.
          */
         private @Nullable WifiEnterpriseConfig mWpa3EnterpriseConfig;
         /**
@@ -276,7 +276,11 @@
 
         /**
          * Set the associated enterprise configuration for this network. Needed for authenticating
-         * to WPA3 enterprise networks. See {@link WifiEnterpriseConfig} for description.
+         * to WPA3-Enterprise networks (standard and 192-bit security). See
+         * {@link WifiEnterpriseConfig} for description. For 192-bit security networks, both the
+         * client and CA certificates must be provided, and must be of type of either
+         * sha384WithRSAEncryption (OID 1.2.840.113549.1.1.12) or ecdsa-with-SHA384
+         * (OID 1.2.840.10045.4.3.3).
          *
          * @param enterpriseConfig Instance of {@link WifiEnterpriseConfig}.
          * @return Instance of {@link Builder} to enable chaining of the builder method.
@@ -522,8 +526,25 @@
             } else if (mWpa2EnterpriseConfig != null) { // WPA-EAP network
                 configuration.setSecurityParams(WifiConfiguration.SECURITY_TYPE_EAP);
                 configuration.enterpriseConfig = mWpa2EnterpriseConfig;
-            } else if (mWpa3EnterpriseConfig != null) { // WPA3-SuiteB network
-                configuration.setSecurityParams(WifiConfiguration.SECURITY_TYPE_EAP_SUITE_B);
+            } else if (mWpa3EnterpriseConfig != null) { // WPA3-Enterprise
+                if (mWpa3EnterpriseConfig.getEapMethod() == WifiEnterpriseConfig.Eap.TLS
+                        && WifiEnterpriseConfig.isSuiteBCipherCert(
+                        mWpa3EnterpriseConfig.getClientCertificate())
+                        && WifiEnterpriseConfig.isSuiteBCipherCert(
+                        mWpa3EnterpriseConfig.getCaCertificate())) {
+                    // WPA3-Enterprise in 192-bit security mode (Suite-B)
+                    configuration.setSecurityParams(WifiConfiguration.SECURITY_TYPE_EAP_SUITE_B);
+                } else {
+                    // WPA3-Enterprise
+                    configuration.setSecurityParams(WifiConfiguration.SECURITY_TYPE_EAP);
+                    configuration.allowedProtocols.set(WifiConfiguration.Protocol.RSN);
+                    configuration.allowedPairwiseCiphers.set(WifiConfiguration.PairwiseCipher.CCMP);
+                    configuration.allowedPairwiseCiphers.set(
+                            WifiConfiguration.PairwiseCipher.GCMP_256);
+                    configuration.allowedGroupCiphers.set(WifiConfiguration.GroupCipher.CCMP);
+                    configuration.allowedGroupCiphers.set(WifiConfiguration.GroupCipher.GCMP_256);
+                    configuration.requirePmf = true;
+                }
                 configuration.enterpriseConfig = mWpa3EnterpriseConfig;
             } else if (mIsEnhancedOpen) { // OWE network
                 configuration.setSecurityParams(WifiConfiguration.SECURITY_TYPE_OWE);
@@ -943,6 +964,9 @@
      */
     @Nullable
     public WifiEnterpriseConfig getEnterpriseConfig() {
+        if (!wifiConfiguration.isEnterprise()) {
+            return null;
+        }
         return wifiConfiguration.enterpriseConfig;
     }
 
diff --git a/wifi/java/android/net/wifi/hotspot2/pps/Credential.java b/wifi/java/android/net/wifi/hotspot2/pps/Credential.java
index fa806e7..282757a 100644
--- a/wifi/java/android/net/wifi/hotspot2/pps/Credential.java
+++ b/wifi/java/android/net/wifi/hotspot2/pps/Credential.java
@@ -448,6 +448,16 @@
                     return new UserCredential[size];
                 }
             };
+
+        /**
+         * Get a unique identifier for UserCredential.
+         *
+         * @hide
+         * @return a Unique identifier for a UserCredential object
+         */
+        public int getUniqueId() {
+            return Objects.hash(mUsername);
+        }
     }
     private UserCredential mUserCredential = null;
     /**
@@ -1037,7 +1047,8 @@
      * @return a Unique identifier for a Credential object
      */
     public int getUniqueId() {
-        return Objects.hash(mUserCredential, mCertCredential, mSimCredential, mRealm);
+        return Objects.hash(mUserCredential != null ? mUserCredential.getUniqueId() : 0,
+                mCertCredential, mSimCredential, mRealm);
     }
 
     @Override
diff --git a/wifi/java/android/net/wifi/hotspot2/pps/HomeSp.java b/wifi/java/android/net/wifi/hotspot2/pps/HomeSp.java
index 224c4be..8f34579 100644
--- a/wifi/java/android/net/wifi/hotspot2/pps/HomeSp.java
+++ b/wifi/java/android/net/wifi/hotspot2/pps/HomeSp.java
@@ -313,9 +313,7 @@
      * @return a Unique identifier for a HomeSp object
      */
     public int getUniqueId() {
-        return Objects.hash(mFqdn, mFriendlyName, mHomeNetworkIds, Arrays.hashCode(mMatchAllOis),
-                Arrays.hashCode(mMatchAnyOis), Arrays.hashCode(mOtherHomePartners),
-                Arrays.hashCode(mRoamingConsortiumOis));
+        return Objects.hash(mFqdn);
     }
 
 
diff --git a/wifi/tests/src/android/net/wifi/FakeKeys.java b/wifi/tests/src/android/net/wifi/FakeKeys.java
index c0d60c3..8aa6add 100644
--- a/wifi/tests/src/android/net/wifi/FakeKeys.java
+++ b/wifi/tests/src/android/net/wifi/FakeKeys.java
@@ -212,7 +212,443 @@
             (byte) 0xbc, (byte) 0xa7, (byte) 0x92, (byte) 0x90, (byte) 0xdd, (byte) 0xa1,
             (byte) 0x9c, (byte) 0xce, (byte) 0xa1, (byte) 0x87, (byte) 0x11, (byte) 0x51
     };
-    public static final PrivateKey RSA_KEY1 = loadPrivateRSAKey(FAKE_RSA_KEY_1);
+    public static final PrivateKey RSA_KEY1 = loadPrivateKey("RSA", FAKE_RSA_KEY_1);
+
+    private static final String CA_SUITE_B_RSA3072_CERT_STRING =
+            "-----BEGIN CERTIFICATE-----\n"
+                    + "MIIEnTCCAwWgAwIBAgIUD87Y8fFLzLr1HQ/64aEnjNq2R/4wDQYJKoZIhvcNAQEM\n"
+                    + "BQAwXjELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMQwwCgYDVQQHDANNVFYxEDAO\n"
+                    + "BgNVBAoMB0FuZHJvaWQxDjAMBgNVBAsMBVdpLUZpMRIwEAYDVQQDDAl1bml0ZXN0\n"
+                    + "Q0EwHhcNMjAwNzIxMDIxNzU0WhcNMzAwNTMwMDIxNzU0WjBeMQswCQYDVQQGEwJV\n"
+                    + "UzELMAkGA1UECAwCQ0ExDDAKBgNVBAcMA01UVjEQMA4GA1UECgwHQW5kcm9pZDEO\n"
+                    + "MAwGA1UECwwFV2ktRmkxEjAQBgNVBAMMCXVuaXRlc3RDQTCCAaIwDQYJKoZIhvcN\n"
+                    + "AQEBBQADggGPADCCAYoCggGBAMtrsT0otlxh0QS079KpRRbU1PQjCihSoltXnrxF\n"
+                    + "sTWZs2weVEeYVyYU5LaauCDDgISCMtjtfbfylMBeYjpWB5hYzYQOiTzo0anWhMyb\n"
+                    + "Ngb7gpMVZuIl6lwMYRyVRKwHWnTo2EUg1ZzW5rGe5fs/KHj6//hoNFm+3Oju0TQd\n"
+                    + "nraQULpoERPF5B7p85Cssk8uNbviBfZXvtCuJ4N6w7PNceOY/9bbwc1mC+pPZmzV\n"
+                    + "SOAg0vvbIQRzChm63C3jBC3xmxSOOZVrKN4zKDG2s8P0oCNGt0NlgRMrgbPRekzg\n"
+                    + "4avkbA0vTuc2AyriTEYkdea/Mt4EpRg9XuOb43U/GJ/d/vQv2/9fsxhXmsZrn8kr\n"
+                    + "Qo5MMHJFUd96GgHmvYSU3Mf/5r8gF626lvqHioGuTAuHUSnr02ri1WUxZ15LDRgY\n"
+                    + "quMjDCFZfucjJPDAdtiHcFSej/4SLJlN39z8oKKNPn3aL9Gv49oAKs9S8tfDVzMk\n"
+                    + "fDLROQFHFuW715GnnMgEAoOpRwIDAQABo1MwUTAdBgNVHQ4EFgQUeVuGmSVN4ARs\n"
+                    + "mesUMWSJ2qWLbxUwHwYDVR0jBBgwFoAUeVuGmSVN4ARsmesUMWSJ2qWLbxUwDwYD\n"
+                    + "VR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQwFAAOCAYEAit1Lo/hegZpPuT9dlWZJ\n"
+                    + "bC8JvAf95O8lnn6LFb69pgYOHCLgCIlvYXu9rdBUJgZo+V1MzJJljiO6RxWRfKbQ\n"
+                    + "8WBYkoqR1EqriR3Kn8q/SjIZCdFSaznTyU1wQMveBQ6RJWXSUhYVfE9RjyFTp7B4\n"
+                    + "UyH2uCluR/0T06HQNGfH5XpIYQqCk1Zgng5lmEmheLDPoJpa92lKeQFJMC6eYz9g\n"
+                    + "lF1GHxPxkPfbMJ6ZDp5X6Yopu6Q6uEXhVKM/iQVcgzRkx9rid+xTYl+nOKyK/XfC\n"
+                    + "z8P0/TFIoPTW02DLge5wKagdoCpy1B7HdrAXyUjoH4B8MsUkq3kYPFSjPzScuTtV\n"
+                    + "kUuDw5ipCNeXCRnhbYqRDk6PX5GUu2cmN9jtaH3tbgm3fKNOsd/BO1fLIl7qjXlR\n"
+                    + "27HHbC0JXjNvlm2DLp23v4NTxS7WZGYsxyUj5DZrxBxqCsTXu/01w1BrQKWKh9FM\n"
+                    + "aVrlA8omfVODK2CSuw+KhEMHepRv/AUgsLl4L4+RMoa+\n"
+                    + "-----END CERTIFICATE-----\n";
+    public static final X509Certificate CA_SUITE_B_RSA3072_CERT =
+            loadCertificate(CA_SUITE_B_RSA3072_CERT_STRING);
+
+    private static final String CA_SUITE_B_ECDSA_CERT_STRING =
+            "-----BEGIN CERTIFICATE-----\n"
+                    + "MIICTzCCAdSgAwIBAgIUdnLttwNPnQzFufplGOr9bTrGCqMwCgYIKoZIzj0EAwMw\n"
+                    + "XjELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMQwwCgYDVQQHDANNVFYxEDAOBgNV\n"
+                    + "BAoMB0FuZHJvaWQxDjAMBgNVBAsMBVdpLUZpMRIwEAYDVQQDDAl1bml0ZXN0Q0Ew\n"
+                    + "HhcNMjAwNzIxMDIyNDA1WhcNMzAwNTMwMDIyNDA1WjBeMQswCQYDVQQGEwJVUzEL\n"
+                    + "MAkGA1UECAwCQ0ExDDAKBgNVBAcMA01UVjEQMA4GA1UECgwHQW5kcm9pZDEOMAwG\n"
+                    + "A1UECwwFV2ktRmkxEjAQBgNVBAMMCXVuaXRlc3RDQTB2MBAGByqGSM49AgEGBSuB\n"
+                    + "BAAiA2IABFmntXwk9icqhDQFUP1xy04WyEpaGW4q6Q+8pujlSl/X3iotPZ++GZfp\n"
+                    + "Mfv3YDHDBl6sELPQ2BEjyPXmpsKjOUdiUe69e88oGEdeqT2xXiQ6uzpTfJD4170i\n"
+                    + "O/TwLrQGKKNTMFEwHQYDVR0OBBYEFCjptsX3g4g5W0L4oEP6N3gfyiZXMB8GA1Ud\n"
+                    + "IwQYMBaAFCjptsX3g4g5W0L4oEP6N3gfyiZXMA8GA1UdEwEB/wQFMAMBAf8wCgYI\n"
+                    + "KoZIzj0EAwMDaQAwZgIxAK61brUYRbLmQKiaEboZgrHtnPAcGo7Yzx3MwHecx3Dm\n"
+                    + "5soIeLVYc8bPYN1pbhXW1gIxALdEe2sh03nBHyQH4adYoZungoCwt8mp/7sJFxou\n"
+                    + "9UnRegyBgGzf74ROWdpZHzh+Pg==\n"
+                    + "-----END CERTIFICATE-----\n";
+    public static final X509Certificate CA_SUITE_B_ECDSA_CERT =
+            loadCertificate(CA_SUITE_B_ECDSA_CERT_STRING);
+
+    private static final String CLIENT_SUITE_B_RSA3072_CERT_STRING =
+            "-----BEGIN CERTIFICATE-----\n"
+                    + "MIIERzCCAq8CFDopjyNgaj+c2TN2k06h7okEWpHJMA0GCSqGSIb3DQEBDAUAMF4x\n"
+                    + "CzAJBgNVBAYTAlVTMQswCQYDVQQIDAJDQTEMMAoGA1UEBwwDTVRWMRAwDgYDVQQK\n"
+                    + "DAdBbmRyb2lkMQ4wDAYDVQQLDAVXaS1GaTESMBAGA1UEAwwJdW5pdGVzdENBMB4X\n"
+                    + "DTIwMDcyMTAyMjkxMVoXDTMwMDUzMDAyMjkxMVowYjELMAkGA1UEBhMCVVMxCzAJ\n"
+                    + "BgNVBAgMAkNBMQwwCgYDVQQHDANNVFYxEDAOBgNVBAoMB0FuZHJvaWQxDjAMBgNV\n"
+                    + "BAsMBVdpLUZpMRYwFAYDVQQDDA11bml0ZXN0Q2xpZW50MIIBojANBgkqhkiG9w0B\n"
+                    + "AQEFAAOCAY8AMIIBigKCAYEAwSK3C5K5udtCKTnE14e8z2cZvwmB4Xe+a8+7QLud\n"
+                    + "Hooc/lQzClgK4MbVUC0D3FE+U32C78SxKoTaRWtvPmNm+UaFT8KkwyUno/dv+2XD\n"
+                    + "pd/zARQ+3FwAfWopAhEyCVSxwsCa+slQ4juRIMIuUC1Mm0NaptZyM3Tj/ICQEfpk\n"
+                    + "o9qVIbiK6eoJMTkY8EWfAn7RTFdfR1OLuO0mVOjgLW9/+upYv6hZ19nAMAxw4QTJ\n"
+                    + "x7lLwALX7B+tDYNEZHDqYL2zyvQWAj2HClere8QYILxkvktgBg2crEJJe4XbDH7L\n"
+                    + "A3rrXmsiqf1ZbfFFEzK9NFqovL+qGh+zIP+588ShJFO9H/RDnDpiTnAFTWXQdTwg\n"
+                    + "szSS0Vw2PB+JqEABAa9DeMvXT1Oy+NY3ItPHyy63nQZVI2rXANw4NhwS0Z6DF+Qs\n"
+                    + "TNrj+GU7e4SG/EGR8SvldjYfQTWFLg1l/UT1hOOkQZwdsaW1zgKyeuiFB2KdMmbA\n"
+                    + "Sq+Ux1L1KICo0IglwWcB/8nnAgMBAAEwDQYJKoZIhvcNAQEMBQADggGBAMYwJkNw\n"
+                    + "BaCviKFmReDTMwWPRy4AMNViEeqAXgERwDEKwM7efjsaj5gctWfKsxX6UdLzkhgg\n"
+                    + "6S/T6PxVWKzJ6l7SoOuTa6tMQOZp+h3R1mdfEQbw8B5cXBxZ+batzAai6Fiy1FKS\n"
+                    + "/ka3INbcGfYuIYghfTrb4/NJKN06ZaQ1bpPwq0e4gN7800T2nbawvSf7r+8ZLcG3\n"
+                    + "6bGCjRMwDSIipNvOwoj3TG315XC7TccX5difQ4sKOY+d2MkVJ3RiO0Ciw2ZbEW8d\n"
+                    + "1FH5vUQJWnBUfSFznosGzLwH3iWfqlP+27jNE+qB2igEwCRFgVAouURx5ou43xuX\n"
+                    + "qf6JkdI3HTJGLIWxkp7gOeln4dEaYzKjYw+P0VqJvKVqQ0IXiLjHgE0J9p0vgyD6\n"
+                    + "HVVcP7U8RgqrbIjL1QgHU4KBhGi+WSUh/mRplUCNvHgcYdcHi/gHpj/j6ubwqIGV\n"
+                    + "z4iSolAHYTmBWcLyE0NgpzE6ntp+53r2KaUJA99l2iGVzbWTwqPSm0XAVw==\n"
+                    + "-----END CERTIFICATE-----\n";
+    public static final X509Certificate CLIENT_SUITE_B_RSA3072_CERT =
+            loadCertificate(CLIENT_SUITE_B_RSA3072_CERT_STRING);
+
+    private static final byte[] CLIENT_SUITE_B_RSA3072_KEY_DATA = new byte[]{
+            (byte) 0x30, (byte) 0x82, (byte) 0x06, (byte) 0xfe, (byte) 0x02, (byte) 0x01,
+            (byte) 0x00, (byte) 0x30, (byte) 0x0d, (byte) 0x06, (byte) 0x09, (byte) 0x2a,
+            (byte) 0x86, (byte) 0x48, (byte) 0x86, (byte) 0xf7, (byte) 0x0d, (byte) 0x01,
+            (byte) 0x01, (byte) 0x01, (byte) 0x05, (byte) 0x00, (byte) 0x04, (byte) 0x82,
+            (byte) 0x06, (byte) 0xe8, (byte) 0x30, (byte) 0x82, (byte) 0x06, (byte) 0xe4,
+            (byte) 0x02, (byte) 0x01, (byte) 0x00, (byte) 0x02, (byte) 0x82, (byte) 0x01,
+            (byte) 0x81, (byte) 0x00, (byte) 0xc1, (byte) 0x22, (byte) 0xb7, (byte) 0x0b,
+            (byte) 0x92, (byte) 0xb9, (byte) 0xb9, (byte) 0xdb, (byte) 0x42, (byte) 0x29,
+            (byte) 0x39, (byte) 0xc4, (byte) 0xd7, (byte) 0x87, (byte) 0xbc, (byte) 0xcf,
+            (byte) 0x67, (byte) 0x19, (byte) 0xbf, (byte) 0x09, (byte) 0x81, (byte) 0xe1,
+            (byte) 0x77, (byte) 0xbe, (byte) 0x6b, (byte) 0xcf, (byte) 0xbb, (byte) 0x40,
+            (byte) 0xbb, (byte) 0x9d, (byte) 0x1e, (byte) 0x8a, (byte) 0x1c, (byte) 0xfe,
+            (byte) 0x54, (byte) 0x33, (byte) 0x0a, (byte) 0x58, (byte) 0x0a, (byte) 0xe0,
+            (byte) 0xc6, (byte) 0xd5, (byte) 0x50, (byte) 0x2d, (byte) 0x03, (byte) 0xdc,
+            (byte) 0x51, (byte) 0x3e, (byte) 0x53, (byte) 0x7d, (byte) 0x82, (byte) 0xef,
+            (byte) 0xc4, (byte) 0xb1, (byte) 0x2a, (byte) 0x84, (byte) 0xda, (byte) 0x45,
+            (byte) 0x6b, (byte) 0x6f, (byte) 0x3e, (byte) 0x63, (byte) 0x66, (byte) 0xf9,
+            (byte) 0x46, (byte) 0x85, (byte) 0x4f, (byte) 0xc2, (byte) 0xa4, (byte) 0xc3,
+            (byte) 0x25, (byte) 0x27, (byte) 0xa3, (byte) 0xf7, (byte) 0x6f, (byte) 0xfb,
+            (byte) 0x65, (byte) 0xc3, (byte) 0xa5, (byte) 0xdf, (byte) 0xf3, (byte) 0x01,
+            (byte) 0x14, (byte) 0x3e, (byte) 0xdc, (byte) 0x5c, (byte) 0x00, (byte) 0x7d,
+            (byte) 0x6a, (byte) 0x29, (byte) 0x02, (byte) 0x11, (byte) 0x32, (byte) 0x09,
+            (byte) 0x54, (byte) 0xb1, (byte) 0xc2, (byte) 0xc0, (byte) 0x9a, (byte) 0xfa,
+            (byte) 0xc9, (byte) 0x50, (byte) 0xe2, (byte) 0x3b, (byte) 0x91, (byte) 0x20,
+            (byte) 0xc2, (byte) 0x2e, (byte) 0x50, (byte) 0x2d, (byte) 0x4c, (byte) 0x9b,
+            (byte) 0x43, (byte) 0x5a, (byte) 0xa6, (byte) 0xd6, (byte) 0x72, (byte) 0x33,
+            (byte) 0x74, (byte) 0xe3, (byte) 0xfc, (byte) 0x80, (byte) 0x90, (byte) 0x11,
+            (byte) 0xfa, (byte) 0x64, (byte) 0xa3, (byte) 0xda, (byte) 0x95, (byte) 0x21,
+            (byte) 0xb8, (byte) 0x8a, (byte) 0xe9, (byte) 0xea, (byte) 0x09, (byte) 0x31,
+            (byte) 0x39, (byte) 0x18, (byte) 0xf0, (byte) 0x45, (byte) 0x9f, (byte) 0x02,
+            (byte) 0x7e, (byte) 0xd1, (byte) 0x4c, (byte) 0x57, (byte) 0x5f, (byte) 0x47,
+            (byte) 0x53, (byte) 0x8b, (byte) 0xb8, (byte) 0xed, (byte) 0x26, (byte) 0x54,
+            (byte) 0xe8, (byte) 0xe0, (byte) 0x2d, (byte) 0x6f, (byte) 0x7f, (byte) 0xfa,
+            (byte) 0xea, (byte) 0x58, (byte) 0xbf, (byte) 0xa8, (byte) 0x59, (byte) 0xd7,
+            (byte) 0xd9, (byte) 0xc0, (byte) 0x30, (byte) 0x0c, (byte) 0x70, (byte) 0xe1,
+            (byte) 0x04, (byte) 0xc9, (byte) 0xc7, (byte) 0xb9, (byte) 0x4b, (byte) 0xc0,
+            (byte) 0x02, (byte) 0xd7, (byte) 0xec, (byte) 0x1f, (byte) 0xad, (byte) 0x0d,
+            (byte) 0x83, (byte) 0x44, (byte) 0x64, (byte) 0x70, (byte) 0xea, (byte) 0x60,
+            (byte) 0xbd, (byte) 0xb3, (byte) 0xca, (byte) 0xf4, (byte) 0x16, (byte) 0x02,
+            (byte) 0x3d, (byte) 0x87, (byte) 0x0a, (byte) 0x57, (byte) 0xab, (byte) 0x7b,
+            (byte) 0xc4, (byte) 0x18, (byte) 0x20, (byte) 0xbc, (byte) 0x64, (byte) 0xbe,
+            (byte) 0x4b, (byte) 0x60, (byte) 0x06, (byte) 0x0d, (byte) 0x9c, (byte) 0xac,
+            (byte) 0x42, (byte) 0x49, (byte) 0x7b, (byte) 0x85, (byte) 0xdb, (byte) 0x0c,
+            (byte) 0x7e, (byte) 0xcb, (byte) 0x03, (byte) 0x7a, (byte) 0xeb, (byte) 0x5e,
+            (byte) 0x6b, (byte) 0x22, (byte) 0xa9, (byte) 0xfd, (byte) 0x59, (byte) 0x6d,
+            (byte) 0xf1, (byte) 0x45, (byte) 0x13, (byte) 0x32, (byte) 0xbd, (byte) 0x34,
+            (byte) 0x5a, (byte) 0xa8, (byte) 0xbc, (byte) 0xbf, (byte) 0xaa, (byte) 0x1a,
+            (byte) 0x1f, (byte) 0xb3, (byte) 0x20, (byte) 0xff, (byte) 0xb9, (byte) 0xf3,
+            (byte) 0xc4, (byte) 0xa1, (byte) 0x24, (byte) 0x53, (byte) 0xbd, (byte) 0x1f,
+            (byte) 0xf4, (byte) 0x43, (byte) 0x9c, (byte) 0x3a, (byte) 0x62, (byte) 0x4e,
+            (byte) 0x70, (byte) 0x05, (byte) 0x4d, (byte) 0x65, (byte) 0xd0, (byte) 0x75,
+            (byte) 0x3c, (byte) 0x20, (byte) 0xb3, (byte) 0x34, (byte) 0x92, (byte) 0xd1,
+            (byte) 0x5c, (byte) 0x36, (byte) 0x3c, (byte) 0x1f, (byte) 0x89, (byte) 0xa8,
+            (byte) 0x40, (byte) 0x01, (byte) 0x01, (byte) 0xaf, (byte) 0x43, (byte) 0x78,
+            (byte) 0xcb, (byte) 0xd7, (byte) 0x4f, (byte) 0x53, (byte) 0xb2, (byte) 0xf8,
+            (byte) 0xd6, (byte) 0x37, (byte) 0x22, (byte) 0xd3, (byte) 0xc7, (byte) 0xcb,
+            (byte) 0x2e, (byte) 0xb7, (byte) 0x9d, (byte) 0x06, (byte) 0x55, (byte) 0x23,
+            (byte) 0x6a, (byte) 0xd7, (byte) 0x00, (byte) 0xdc, (byte) 0x38, (byte) 0x36,
+            (byte) 0x1c, (byte) 0x12, (byte) 0xd1, (byte) 0x9e, (byte) 0x83, (byte) 0x17,
+            (byte) 0xe4, (byte) 0x2c, (byte) 0x4c, (byte) 0xda, (byte) 0xe3, (byte) 0xf8,
+            (byte) 0x65, (byte) 0x3b, (byte) 0x7b, (byte) 0x84, (byte) 0x86, (byte) 0xfc,
+            (byte) 0x41, (byte) 0x91, (byte) 0xf1, (byte) 0x2b, (byte) 0xe5, (byte) 0x76,
+            (byte) 0x36, (byte) 0x1f, (byte) 0x41, (byte) 0x35, (byte) 0x85, (byte) 0x2e,
+            (byte) 0x0d, (byte) 0x65, (byte) 0xfd, (byte) 0x44, (byte) 0xf5, (byte) 0x84,
+            (byte) 0xe3, (byte) 0xa4, (byte) 0x41, (byte) 0x9c, (byte) 0x1d, (byte) 0xb1,
+            (byte) 0xa5, (byte) 0xb5, (byte) 0xce, (byte) 0x02, (byte) 0xb2, (byte) 0x7a,
+            (byte) 0xe8, (byte) 0x85, (byte) 0x07, (byte) 0x62, (byte) 0x9d, (byte) 0x32,
+            (byte) 0x66, (byte) 0xc0, (byte) 0x4a, (byte) 0xaf, (byte) 0x94, (byte) 0xc7,
+            (byte) 0x52, (byte) 0xf5, (byte) 0x28, (byte) 0x80, (byte) 0xa8, (byte) 0xd0,
+            (byte) 0x88, (byte) 0x25, (byte) 0xc1, (byte) 0x67, (byte) 0x01, (byte) 0xff,
+            (byte) 0xc9, (byte) 0xe7, (byte) 0x02, (byte) 0x03, (byte) 0x01, (byte) 0x00,
+            (byte) 0x01, (byte) 0x02, (byte) 0x82, (byte) 0x01, (byte) 0x80, (byte) 0x04,
+            (byte) 0xb1, (byte) 0xcc, (byte) 0x53, (byte) 0x3a, (byte) 0xb0, (byte) 0xcb,
+            (byte) 0x04, (byte) 0xba, (byte) 0x59, (byte) 0xf8, (byte) 0x2e, (byte) 0x81,
+            (byte) 0xb2, (byte) 0xa9, (byte) 0xf3, (byte) 0x3c, (byte) 0xa5, (byte) 0x52,
+            (byte) 0x90, (byte) 0x6f, (byte) 0x98, (byte) 0xc4, (byte) 0x69, (byte) 0x5b,
+            (byte) 0x83, (byte) 0x84, (byte) 0x20, (byte) 0xb1, (byte) 0xae, (byte) 0xc3,
+            (byte) 0x04, (byte) 0x46, (byte) 0x6a, (byte) 0x24, (byte) 0x2f, (byte) 0xcd,
+            (byte) 0x6b, (byte) 0x90, (byte) 0x70, (byte) 0x20, (byte) 0x45, (byte) 0x25,
+            (byte) 0x1a, (byte) 0xc3, (byte) 0x02, (byte) 0x42, (byte) 0xf3, (byte) 0x49,
+            (byte) 0xe2, (byte) 0x3e, (byte) 0x21, (byte) 0x87, (byte) 0xdd, (byte) 0x6a,
+            (byte) 0x94, (byte) 0x2a, (byte) 0x1e, (byte) 0x0f, (byte) 0xdb, (byte) 0x77,
+            (byte) 0x5f, (byte) 0xc1, (byte) 0x2c, (byte) 0x03, (byte) 0xfb, (byte) 0xcf,
+            (byte) 0x91, (byte) 0x82, (byte) 0xa1, (byte) 0xbf, (byte) 0xb0, (byte) 0x73,
+            (byte) 0xfa, (byte) 0xda, (byte) 0xbc, (byte) 0xf8, (byte) 0x9f, (byte) 0x45,
+            (byte) 0xd3, (byte) 0xe8, (byte) 0xbb, (byte) 0x38, (byte) 0xfb, (byte) 0xc2,
+            (byte) 0x2d, (byte) 0x76, (byte) 0x51, (byte) 0x96, (byte) 0x18, (byte) 0x03,
+            (byte) 0x15, (byte) 0xd9, (byte) 0xea, (byte) 0x82, (byte) 0x25, (byte) 0x83,
+            (byte) 0xff, (byte) 0x5c, (byte) 0x85, (byte) 0x06, (byte) 0x09, (byte) 0xb2,
+            (byte) 0x46, (byte) 0x12, (byte) 0x64, (byte) 0x02, (byte) 0x74, (byte) 0x4f,
+            (byte) 0xbc, (byte) 0x9a, (byte) 0x25, (byte) 0x18, (byte) 0x01, (byte) 0x07,
+            (byte) 0x17, (byte) 0x25, (byte) 0x55, (byte) 0x7c, (byte) 0xdc, (byte) 0xe1,
+            (byte) 0xd1, (byte) 0x5a, (byte) 0x2f, (byte) 0x25, (byte) 0xaf, (byte) 0xf6,
+            (byte) 0x8f, (byte) 0xa4, (byte) 0x9a, (byte) 0x5a, (byte) 0x3a, (byte) 0xfe,
+            (byte) 0x2e, (byte) 0x93, (byte) 0x24, (byte) 0xa0, (byte) 0x27, (byte) 0xac,
+            (byte) 0x07, (byte) 0x75, (byte) 0x33, (byte) 0x01, (byte) 0x54, (byte) 0x23,
+            (byte) 0x0f, (byte) 0xe8, (byte) 0x9f, (byte) 0xfa, (byte) 0x36, (byte) 0xe6,
+            (byte) 0x3a, (byte) 0xd5, (byte) 0x78, (byte) 0xb0, (byte) 0xe4, (byte) 0x6a,
+            (byte) 0x16, (byte) 0x50, (byte) 0xbd, (byte) 0x0f, (byte) 0x9f, (byte) 0x32,
+            (byte) 0xa1, (byte) 0x6b, (byte) 0xf5, (byte) 0xa4, (byte) 0x34, (byte) 0x58,
+            (byte) 0xb6, (byte) 0xa4, (byte) 0xb3, (byte) 0xc3, (byte) 0x83, (byte) 0x08,
+            (byte) 0x18, (byte) 0xc7, (byte) 0xef, (byte) 0x95, (byte) 0xe2, (byte) 0x1b,
+            (byte) 0xba, (byte) 0x35, (byte) 0x61, (byte) 0xa3, (byte) 0xb4, (byte) 0x30,
+            (byte) 0xe0, (byte) 0xd1, (byte) 0xc1, (byte) 0xa2, (byte) 0x3a, (byte) 0xc6,
+            (byte) 0xb4, (byte) 0xd2, (byte) 0x80, (byte) 0x5a, (byte) 0xaf, (byte) 0xa4,
+            (byte) 0x54, (byte) 0x3c, (byte) 0x66, (byte) 0x5a, (byte) 0x1c, (byte) 0x4d,
+            (byte) 0xe1, (byte) 0xd9, (byte) 0x98, (byte) 0x44, (byte) 0x01, (byte) 0x1b,
+            (byte) 0x8c, (byte) 0xe9, (byte) 0x80, (byte) 0x54, (byte) 0x83, (byte) 0x3d,
+            (byte) 0x96, (byte) 0x25, (byte) 0x41, (byte) 0x1c, (byte) 0xad, (byte) 0xae,
+            (byte) 0x3b, (byte) 0x7a, (byte) 0xd7, (byte) 0x9d, (byte) 0x10, (byte) 0x7c,
+            (byte) 0xd1, (byte) 0xa7, (byte) 0x96, (byte) 0x39, (byte) 0xa5, (byte) 0x2f,
+            (byte) 0xbe, (byte) 0xc3, (byte) 0x2c, (byte) 0x64, (byte) 0x01, (byte) 0xfe,
+            (byte) 0xa2, (byte) 0xd1, (byte) 0x6a, (byte) 0xcf, (byte) 0x4c, (byte) 0x76,
+            (byte) 0x3b, (byte) 0xc8, (byte) 0x35, (byte) 0x21, (byte) 0xda, (byte) 0x98,
+            (byte) 0xcf, (byte) 0xf9, (byte) 0x29, (byte) 0xff, (byte) 0x30, (byte) 0x59,
+            (byte) 0x36, (byte) 0x53, (byte) 0x0b, (byte) 0xbb, (byte) 0xfa, (byte) 0xba,
+            (byte) 0xc4, (byte) 0x03, (byte) 0x23, (byte) 0xe0, (byte) 0xd3, (byte) 0x33,
+            (byte) 0xff, (byte) 0x32, (byte) 0xdb, (byte) 0x30, (byte) 0x64, (byte) 0xc7,
+            (byte) 0x56, (byte) 0xca, (byte) 0x55, (byte) 0x14, (byte) 0xee, (byte) 0x58,
+            (byte) 0xfe, (byte) 0x96, (byte) 0x7e, (byte) 0x1c, (byte) 0x34, (byte) 0x16,
+            (byte) 0xeb, (byte) 0x76, (byte) 0x26, (byte) 0x48, (byte) 0xe2, (byte) 0xe5,
+            (byte) 0x5c, (byte) 0xd5, (byte) 0x83, (byte) 0x37, (byte) 0xd9, (byte) 0x09,
+            (byte) 0x71, (byte) 0xbc, (byte) 0x54, (byte) 0x25, (byte) 0xca, (byte) 0x2e,
+            (byte) 0xdb, (byte) 0x36, (byte) 0x39, (byte) 0xcc, (byte) 0x3a, (byte) 0x81,
+            (byte) 0x95, (byte) 0x9e, (byte) 0xf4, (byte) 0x01, (byte) 0xa7, (byte) 0xc0,
+            (byte) 0x20, (byte) 0xce, (byte) 0x70, (byte) 0x55, (byte) 0x2c, (byte) 0xe0,
+            (byte) 0x93, (byte) 0x72, (byte) 0xa6, (byte) 0x25, (byte) 0xda, (byte) 0x64,
+            (byte) 0x19, (byte) 0x18, (byte) 0xd2, (byte) 0x31, (byte) 0xe2, (byte) 0x7c,
+            (byte) 0xf2, (byte) 0x30, (byte) 0x9e, (byte) 0x8d, (byte) 0xc6, (byte) 0x14,
+            (byte) 0x8a, (byte) 0x38, (byte) 0xf0, (byte) 0x94, (byte) 0xeb, (byte) 0xf4,
+            (byte) 0x64, (byte) 0x92, (byte) 0x3d, (byte) 0x67, (byte) 0xa6, (byte) 0x2c,
+            (byte) 0x52, (byte) 0xfc, (byte) 0x60, (byte) 0xca, (byte) 0x2a, (byte) 0xcf,
+            (byte) 0x24, (byte) 0xd5, (byte) 0x42, (byte) 0x5f, (byte) 0xc7, (byte) 0x9f,
+            (byte) 0xf3, (byte) 0xb4, (byte) 0xdf, (byte) 0x76, (byte) 0x6e, (byte) 0x53,
+            (byte) 0xa1, (byte) 0x7b, (byte) 0xae, (byte) 0xa5, (byte) 0x84, (byte) 0x1f,
+            (byte) 0xfa, (byte) 0xc0, (byte) 0xb4, (byte) 0x6c, (byte) 0xc9, (byte) 0x02,
+            (byte) 0x81, (byte) 0xc1, (byte) 0x00, (byte) 0xf3, (byte) 0x17, (byte) 0xd9,
+            (byte) 0x48, (byte) 0x17, (byte) 0x87, (byte) 0x84, (byte) 0x16, (byte) 0xea,
+            (byte) 0x2d, (byte) 0x31, (byte) 0x1b, (byte) 0xce, (byte) 0xec, (byte) 0xaf,
+            (byte) 0xdc, (byte) 0x6b, (byte) 0xaf, (byte) 0xc8, (byte) 0xf1, (byte) 0x40,
+            (byte) 0xa7, (byte) 0x4f, (byte) 0xef, (byte) 0x48, (byte) 0x08, (byte) 0x5e,
+            (byte) 0x9a, (byte) 0xd1, (byte) 0xc0, (byte) 0xb1, (byte) 0xfe, (byte) 0xe7,
+            (byte) 0x03, (byte) 0xd5, (byte) 0x96, (byte) 0x01, (byte) 0xe8, (byte) 0x40,
+            (byte) 0xca, (byte) 0x78, (byte) 0xcb, (byte) 0xb3, (byte) 0x28, (byte) 0x1a,
+            (byte) 0xf0, (byte) 0xe5, (byte) 0xf6, (byte) 0x46, (byte) 0xef, (byte) 0xcd,
+            (byte) 0x1a, (byte) 0x0f, (byte) 0x13, (byte) 0x2d, (byte) 0x38, (byte) 0xf8,
+            (byte) 0xf7, (byte) 0x88, (byte) 0x21, (byte) 0x15, (byte) 0xce, (byte) 0x48,
+            (byte) 0xf4, (byte) 0x92, (byte) 0x7e, (byte) 0x9b, (byte) 0x2e, (byte) 0x2f,
+            (byte) 0x22, (byte) 0x3e, (byte) 0x5c, (byte) 0x67, (byte) 0xd7, (byte) 0x58,
+            (byte) 0xf6, (byte) 0xef, (byte) 0x1f, (byte) 0xb4, (byte) 0x04, (byte) 0xc7,
+            (byte) 0xfd, (byte) 0x8c, (byte) 0x4e, (byte) 0x27, (byte) 0x9e, (byte) 0xb9,
+            (byte) 0xef, (byte) 0x0f, (byte) 0xf7, (byte) 0x4a, (byte) 0xc2, (byte) 0xf4,
+            (byte) 0x64, (byte) 0x6b, (byte) 0xe0, (byte) 0xfb, (byte) 0xe3, (byte) 0x45,
+            (byte) 0xd5, (byte) 0x37, (byte) 0xa0, (byte) 0x2a, (byte) 0xc6, (byte) 0xf3,
+            (byte) 0xf6, (byte) 0xcc, (byte) 0xb5, (byte) 0x94, (byte) 0xbf, (byte) 0x56,
+            (byte) 0xa0, (byte) 0x61, (byte) 0x36, (byte) 0x88, (byte) 0x35, (byte) 0xd5,
+            (byte) 0xa5, (byte) 0xad, (byte) 0x20, (byte) 0x48, (byte) 0xda, (byte) 0x70,
+            (byte) 0x35, (byte) 0xd9, (byte) 0x75, (byte) 0x66, (byte) 0xa5, (byte) 0xac,
+            (byte) 0x86, (byte) 0x7a, (byte) 0x75, (byte) 0x49, (byte) 0x88, (byte) 0x40,
+            (byte) 0xce, (byte) 0xb0, (byte) 0x6f, (byte) 0x57, (byte) 0x15, (byte) 0x54,
+            (byte) 0xd3, (byte) 0x2f, (byte) 0x11, (byte) 0x9b, (byte) 0xe3, (byte) 0x87,
+            (byte) 0xc8, (byte) 0x8d, (byte) 0x98, (byte) 0xc6, (byte) 0xe0, (byte) 0xbc,
+            (byte) 0x85, (byte) 0xb9, (byte) 0x04, (byte) 0x43, (byte) 0xa9, (byte) 0x41,
+            (byte) 0xce, (byte) 0x42, (byte) 0x1a, (byte) 0x57, (byte) 0x10, (byte) 0xd8,
+            (byte) 0xe4, (byte) 0x6a, (byte) 0x51, (byte) 0x10, (byte) 0x0a, (byte) 0xec,
+            (byte) 0xe4, (byte) 0x57, (byte) 0xc7, (byte) 0xee, (byte) 0xe9, (byte) 0xd6,
+            (byte) 0xcb, (byte) 0x3e, (byte) 0xba, (byte) 0xfa, (byte) 0xe9, (byte) 0x0e,
+            (byte) 0xed, (byte) 0x87, (byte) 0x04, (byte) 0x9a, (byte) 0x48, (byte) 0xba,
+            (byte) 0xaf, (byte) 0x08, (byte) 0xf5, (byte) 0x02, (byte) 0x81, (byte) 0xc1,
+            (byte) 0x00, (byte) 0xcb, (byte) 0x63, (byte) 0xd6, (byte) 0x54, (byte) 0xb6,
+            (byte) 0xf3, (byte) 0xf3, (byte) 0x8c, (byte) 0xf8, (byte) 0xd0, (byte) 0xd2,
+            (byte) 0x84, (byte) 0xc1, (byte) 0xf5, (byte) 0x12, (byte) 0xe0, (byte) 0x02,
+            (byte) 0x80, (byte) 0x42, (byte) 0x92, (byte) 0x4e, (byte) 0xa4, (byte) 0x5c,
+            (byte) 0xa5, (byte) 0x64, (byte) 0xec, (byte) 0xb7, (byte) 0xdc, (byte) 0xe0,
+            (byte) 0x2d, (byte) 0x5d, (byte) 0xac, (byte) 0x0e, (byte) 0x24, (byte) 0x48,
+            (byte) 0x13, (byte) 0x05, (byte) 0xe8, (byte) 0xff, (byte) 0x96, (byte) 0x93,
+            (byte) 0xba, (byte) 0x3c, (byte) 0x88, (byte) 0xcc, (byte) 0x80, (byte) 0xf9,
+            (byte) 0xdb, (byte) 0xa8, (byte) 0x4d, (byte) 0x86, (byte) 0x47, (byte) 0xc8,
+            (byte) 0xbf, (byte) 0x34, (byte) 0x2d, (byte) 0xda, (byte) 0xb6, (byte) 0x28,
+            (byte) 0xf0, (byte) 0x1e, (byte) 0xd2, (byte) 0x46, (byte) 0x0d, (byte) 0x6f,
+            (byte) 0x36, (byte) 0x8e, (byte) 0x84, (byte) 0xd8, (byte) 0xaf, (byte) 0xf7,
+            (byte) 0x69, (byte) 0x23, (byte) 0x77, (byte) 0xfb, (byte) 0xc5, (byte) 0x04,
+            (byte) 0x08, (byte) 0x18, (byte) 0xac, (byte) 0x85, (byte) 0x80, (byte) 0x87,
+            (byte) 0x1c, (byte) 0xfe, (byte) 0x8e, (byte) 0x5d, (byte) 0x00, (byte) 0x7f,
+            (byte) 0x5b, (byte) 0x33, (byte) 0xf5, (byte) 0xdf, (byte) 0x70, (byte) 0x81,
+            (byte) 0xad, (byte) 0x81, (byte) 0xf4, (byte) 0x5a, (byte) 0x37, (byte) 0x8a,
+            (byte) 0x79, (byte) 0x09, (byte) 0xc5, (byte) 0x55, (byte) 0xab, (byte) 0x58,
+            (byte) 0x7c, (byte) 0x47, (byte) 0xca, (byte) 0xa5, (byte) 0x80, (byte) 0x49,
+            (byte) 0x5f, (byte) 0x71, (byte) 0x83, (byte) 0xfb, (byte) 0x3b, (byte) 0x06,
+            (byte) 0xec, (byte) 0x75, (byte) 0x23, (byte) 0xc4, (byte) 0x32, (byte) 0xc7,
+            (byte) 0x18, (byte) 0xf6, (byte) 0x82, (byte) 0x95, (byte) 0x98, (byte) 0x39,
+            (byte) 0xf7, (byte) 0x92, (byte) 0x31, (byte) 0xc0, (byte) 0x89, (byte) 0xba,
+            (byte) 0xd4, (byte) 0xd4, (byte) 0x58, (byte) 0x4e, (byte) 0x38, (byte) 0x35,
+            (byte) 0x10, (byte) 0xb9, (byte) 0xf1, (byte) 0x27, (byte) 0xdc, (byte) 0xff,
+            (byte) 0xc7, (byte) 0xb2, (byte) 0xba, (byte) 0x1f, (byte) 0x27, (byte) 0xaf,
+            (byte) 0x99, (byte) 0xd5, (byte) 0xb0, (byte) 0x39, (byte) 0xe7, (byte) 0x43,
+            (byte) 0x88, (byte) 0xd3, (byte) 0xce, (byte) 0x38, (byte) 0xc2, (byte) 0x99,
+            (byte) 0x43, (byte) 0xfc, (byte) 0x8a, (byte) 0xe3, (byte) 0x60, (byte) 0x0d,
+            (byte) 0x0a, (byte) 0xb8, (byte) 0xc4, (byte) 0x29, (byte) 0xca, (byte) 0x0d,
+            (byte) 0x30, (byte) 0xaf, (byte) 0xca, (byte) 0xd0, (byte) 0xaa, (byte) 0x67,
+            (byte) 0xb1, (byte) 0xdd, (byte) 0xdb, (byte) 0x7a, (byte) 0x11, (byte) 0xad,
+            (byte) 0xeb, (byte) 0x02, (byte) 0x81, (byte) 0xc0, (byte) 0x71, (byte) 0xb8,
+            (byte) 0xcf, (byte) 0x72, (byte) 0x35, (byte) 0x67, (byte) 0xb5, (byte) 0x38,
+            (byte) 0x8f, (byte) 0x16, (byte) 0xd3, (byte) 0x29, (byte) 0x82, (byte) 0x35,
+            (byte) 0x21, (byte) 0xd4, (byte) 0x49, (byte) 0x20, (byte) 0x74, (byte) 0x2d,
+            (byte) 0xc0, (byte) 0xa4, (byte) 0x44, (byte) 0xf5, (byte) 0xd8, (byte) 0xc9,
+            (byte) 0xe9, (byte) 0x90, (byte) 0x1d, (byte) 0xde, (byte) 0x3a, (byte) 0xa6,
+            (byte) 0xd7, (byte) 0xe5, (byte) 0xe8, (byte) 0x4e, (byte) 0x83, (byte) 0xd7,
+            (byte) 0xe6, (byte) 0x2f, (byte) 0x92, (byte) 0x31, (byte) 0x21, (byte) 0x3f,
+            (byte) 0xfa, (byte) 0xd2, (byte) 0x85, (byte) 0x92, (byte) 0x1f, (byte) 0xff,
+            (byte) 0x61, (byte) 0x00, (byte) 0xf6, (byte) 0xda, (byte) 0x6e, (byte) 0xc6,
+            (byte) 0x7f, (byte) 0x5a, (byte) 0x35, (byte) 0x79, (byte) 0xdc, (byte) 0xdc,
+            (byte) 0xa3, (byte) 0x2e, (byte) 0x9f, (byte) 0x35, (byte) 0xd1, (byte) 0x5c,
+            (byte) 0xda, (byte) 0xb9, (byte) 0xf7, (byte) 0x58, (byte) 0x7d, (byte) 0x4f,
+            (byte) 0xb6, (byte) 0x13, (byte) 0xd7, (byte) 0x2c, (byte) 0x0a, (byte) 0xa8,
+            (byte) 0x4d, (byte) 0xf2, (byte) 0xe4, (byte) 0x67, (byte) 0x4f, (byte) 0x8b,
+            (byte) 0xa6, (byte) 0xca, (byte) 0x1a, (byte) 0xbb, (byte) 0x02, (byte) 0x63,
+            (byte) 0x8f, (byte) 0xb7, (byte) 0x46, (byte) 0xec, (byte) 0x7a, (byte) 0x8a,
+            (byte) 0x09, (byte) 0x0a, (byte) 0x45, (byte) 0x3a, (byte) 0x8d, (byte) 0xa8,
+            (byte) 0x83, (byte) 0x4b, (byte) 0x0a, (byte) 0xdb, (byte) 0x4b, (byte) 0x99,
+            (byte) 0xf3, (byte) 0x69, (byte) 0x95, (byte) 0xf0, (byte) 0xcf, (byte) 0xe9,
+            (byte) 0xf7, (byte) 0x67, (byte) 0xc9, (byte) 0x45, (byte) 0x18, (byte) 0x2f,
+            (byte) 0xf0, (byte) 0x5c, (byte) 0x90, (byte) 0xbd, (byte) 0xa6, (byte) 0x66,
+            (byte) 0x8c, (byte) 0xfe, (byte) 0x60, (byte) 0x5d, (byte) 0x6c, (byte) 0x27,
+            (byte) 0xec, (byte) 0xc1, (byte) 0x84, (byte) 0xb2, (byte) 0xa1, (byte) 0x97,
+            (byte) 0x9e, (byte) 0x16, (byte) 0x29, (byte) 0xa7, (byte) 0xe0, (byte) 0x38,
+            (byte) 0xa2, (byte) 0x36, (byte) 0x05, (byte) 0x5f, (byte) 0xda, (byte) 0x72,
+            (byte) 0x1a, (byte) 0x5f, (byte) 0xa8, (byte) 0x7d, (byte) 0x41, (byte) 0x35,
+            (byte) 0xf6, (byte) 0x4e, (byte) 0x0a, (byte) 0x88, (byte) 0x8e, (byte) 0x00,
+            (byte) 0x98, (byte) 0xa6, (byte) 0xca, (byte) 0xc1, (byte) 0xdf, (byte) 0x72,
+            (byte) 0x6c, (byte) 0xfe, (byte) 0x29, (byte) 0xbe, (byte) 0xa3, (byte) 0x9b,
+            (byte) 0x0b, (byte) 0x5c, (byte) 0x0b, (byte) 0x9d, (byte) 0xa7, (byte) 0x71,
+            (byte) 0xce, (byte) 0x04, (byte) 0xfa, (byte) 0xac, (byte) 0x01, (byte) 0x8d,
+            (byte) 0x52, (byte) 0xa0, (byte) 0x3d, (byte) 0xdd, (byte) 0x02, (byte) 0x81,
+            (byte) 0xc1, (byte) 0x00, (byte) 0xc1, (byte) 0xc0, (byte) 0x2e, (byte) 0xa9,
+            (byte) 0xee, (byte) 0xca, (byte) 0xff, (byte) 0xe4, (byte) 0xf8, (byte) 0x15,
+            (byte) 0xfd, (byte) 0xa5, (byte) 0x68, (byte) 0x1b, (byte) 0x2d, (byte) 0x4a,
+            (byte) 0xe6, (byte) 0x37, (byte) 0x06, (byte) 0xb3, (byte) 0xd7, (byte) 0x64,
+            (byte) 0xad, (byte) 0xb9, (byte) 0x05, (byte) 0x26, (byte) 0x97, (byte) 0x94,
+            (byte) 0x3a, (byte) 0x9e, (byte) 0x1c, (byte) 0xd0, (byte) 0xcd, (byte) 0x7b,
+            (byte) 0xf4, (byte) 0x88, (byte) 0xe2, (byte) 0xa5, (byte) 0x6d, (byte) 0xed,
+            (byte) 0x24, (byte) 0x77, (byte) 0x52, (byte) 0x39, (byte) 0x43, (byte) 0x0f,
+            (byte) 0x4e, (byte) 0x75, (byte) 0xd8, (byte) 0xa3, (byte) 0x59, (byte) 0x5a,
+            (byte) 0xc2, (byte) 0xba, (byte) 0x9a, (byte) 0x5b, (byte) 0x60, (byte) 0x31,
+            (byte) 0x0d, (byte) 0x58, (byte) 0x89, (byte) 0x13, (byte) 0xe8, (byte) 0x95,
+            (byte) 0xdd, (byte) 0xae, (byte) 0xcc, (byte) 0x1f, (byte) 0x73, (byte) 0x48,
+            (byte) 0x55, (byte) 0xd8, (byte) 0xfb, (byte) 0x67, (byte) 0xce, (byte) 0x18,
+            (byte) 0x85, (byte) 0x59, (byte) 0xad, (byte) 0x1f, (byte) 0x93, (byte) 0xe1,
+            (byte) 0xb7, (byte) 0x54, (byte) 0x80, (byte) 0x8e, (byte) 0x5f, (byte) 0xbc,
+            (byte) 0x1c, (byte) 0x96, (byte) 0x66, (byte) 0x2e, (byte) 0x40, (byte) 0x17,
+            (byte) 0x2e, (byte) 0x01, (byte) 0x7a, (byte) 0x7d, (byte) 0xaa, (byte) 0xff,
+            (byte) 0xa3, (byte) 0xd2, (byte) 0xdf, (byte) 0xe2, (byte) 0xf3, (byte) 0x54,
+            (byte) 0x51, (byte) 0xeb, (byte) 0xba, (byte) 0x7c, (byte) 0x2a, (byte) 0x22,
+            (byte) 0xc6, (byte) 0x42, (byte) 0xbc, (byte) 0xa1, (byte) 0x6c, (byte) 0xcf,
+            (byte) 0x73, (byte) 0x2e, (byte) 0x07, (byte) 0xfc, (byte) 0xf5, (byte) 0x67,
+            (byte) 0x25, (byte) 0xd0, (byte) 0xfa, (byte) 0xeb, (byte) 0xb4, (byte) 0xd4,
+            (byte) 0x19, (byte) 0xcc, (byte) 0x64, (byte) 0xa1, (byte) 0x2e, (byte) 0x78,
+            (byte) 0x45, (byte) 0xd9, (byte) 0x7f, (byte) 0x1b, (byte) 0x4c, (byte) 0x10,
+            (byte) 0x31, (byte) 0x44, (byte) 0xe8, (byte) 0xcc, (byte) 0xf9, (byte) 0x1b,
+            (byte) 0x87, (byte) 0x31, (byte) 0xd6, (byte) 0x69, (byte) 0x85, (byte) 0x4a,
+            (byte) 0x49, (byte) 0xf6, (byte) 0xb2, (byte) 0xe0, (byte) 0xb8, (byte) 0x98,
+            (byte) 0x3c, (byte) 0xf6, (byte) 0x78, (byte) 0x46, (byte) 0xc8, (byte) 0x3d,
+            (byte) 0x60, (byte) 0xc1, (byte) 0xaa, (byte) 0x2f, (byte) 0x28, (byte) 0xa1,
+            (byte) 0x14, (byte) 0x6b, (byte) 0x75, (byte) 0x4d, (byte) 0xb1, (byte) 0x3d,
+            (byte) 0x80, (byte) 0x49, (byte) 0x33, (byte) 0xfd, (byte) 0x71, (byte) 0xc0,
+            (byte) 0x13, (byte) 0x1e, (byte) 0x16, (byte) 0x69, (byte) 0x80, (byte) 0xa4,
+            (byte) 0x9c, (byte) 0xd7, (byte) 0x02, (byte) 0x81, (byte) 0xc1, (byte) 0x00,
+            (byte) 0x8c, (byte) 0x33, (byte) 0x2d, (byte) 0xd9, (byte) 0xf3, (byte) 0x42,
+            (byte) 0x4d, (byte) 0xca, (byte) 0x5e, (byte) 0x60, (byte) 0x14, (byte) 0x10,
+            (byte) 0xf6, (byte) 0xf3, (byte) 0x71, (byte) 0x15, (byte) 0x88, (byte) 0x54,
+            (byte) 0x84, (byte) 0x21, (byte) 0x04, (byte) 0xb1, (byte) 0xaf, (byte) 0x02,
+            (byte) 0x11, (byte) 0x7f, (byte) 0x42, (byte) 0x3e, (byte) 0x86, (byte) 0xcb,
+            (byte) 0x6c, (byte) 0xf5, (byte) 0x57, (byte) 0x78, (byte) 0x4a, (byte) 0x03,
+            (byte) 0x9b, (byte) 0x80, (byte) 0xc2, (byte) 0x04, (byte) 0x3a, (byte) 0x6b,
+            (byte) 0xb3, (byte) 0x30, (byte) 0x31, (byte) 0x7e, (byte) 0xc3, (byte) 0x89,
+            (byte) 0x09, (byte) 0x4e, (byte) 0x86, (byte) 0x59, (byte) 0x41, (byte) 0xb5,
+            (byte) 0xae, (byte) 0xd5, (byte) 0xc6, (byte) 0x38, (byte) 0xbc, (byte) 0xd7,
+            (byte) 0xd7, (byte) 0x8e, (byte) 0xa3, (byte) 0x1a, (byte) 0xde, (byte) 0x32,
+            (byte) 0xad, (byte) 0x8d, (byte) 0x15, (byte) 0x81, (byte) 0xfe, (byte) 0xac,
+            (byte) 0xbd, (byte) 0xd0, (byte) 0xca, (byte) 0xbc, (byte) 0xd8, (byte) 0x6a,
+            (byte) 0xe1, (byte) 0xfe, (byte) 0xda, (byte) 0xc4, (byte) 0xd8, (byte) 0x62,
+            (byte) 0x71, (byte) 0x20, (byte) 0xa3, (byte) 0xd3, (byte) 0x06, (byte) 0x11,
+            (byte) 0xa9, (byte) 0x53, (byte) 0x7a, (byte) 0x44, (byte) 0x89, (byte) 0x3d,
+            (byte) 0x28, (byte) 0x5e, (byte) 0x7d, (byte) 0xf0, (byte) 0x60, (byte) 0xeb,
+            (byte) 0xb5, (byte) 0xdf, (byte) 0xed, (byte) 0x4f, (byte) 0x6d, (byte) 0x05,
+            (byte) 0x59, (byte) 0x06, (byte) 0xb0, (byte) 0x62, (byte) 0x50, (byte) 0x1c,
+            (byte) 0xb7, (byte) 0x2c, (byte) 0x44, (byte) 0xa4, (byte) 0x49, (byte) 0xf8,
+            (byte) 0x4f, (byte) 0x4b, (byte) 0xab, (byte) 0x71, (byte) 0x5b, (byte) 0xcb,
+            (byte) 0x31, (byte) 0x10, (byte) 0x41, (byte) 0xe0, (byte) 0x1a, (byte) 0x15,
+            (byte) 0xdc, (byte) 0x4c, (byte) 0x5d, (byte) 0x4f, (byte) 0x62, (byte) 0x83,
+            (byte) 0xa4, (byte) 0x80, (byte) 0x06, (byte) 0x36, (byte) 0xba, (byte) 0xc9,
+            (byte) 0xe2, (byte) 0xa4, (byte) 0x11, (byte) 0x98, (byte) 0x6b, (byte) 0x4c,
+            (byte) 0xe9, (byte) 0x90, (byte) 0x55, (byte) 0x18, (byte) 0xde, (byte) 0xe1,
+            (byte) 0x42, (byte) 0x38, (byte) 0x28, (byte) 0xa3, (byte) 0x54, (byte) 0x56,
+            (byte) 0x31, (byte) 0xaf, (byte) 0x5a, (byte) 0xd6, (byte) 0xf0, (byte) 0x26,
+            (byte) 0xe0, (byte) 0x7a, (byte) 0xd9, (byte) 0x6c, (byte) 0x64, (byte) 0xca,
+            (byte) 0x5d, (byte) 0x6d, (byte) 0x3d, (byte) 0x9a, (byte) 0xfe, (byte) 0x36,
+            (byte) 0x93, (byte) 0x9e, (byte) 0x62, (byte) 0x94, (byte) 0xc6, (byte) 0x07,
+            (byte) 0x83, (byte) 0x96, (byte) 0xd6, (byte) 0x27, (byte) 0xa6, (byte) 0xd8
+    };
+    public static final PrivateKey CLIENT_SUITE_B_RSA3072_KEY =
+            loadPrivateKey("RSA", CLIENT_SUITE_B_RSA3072_KEY_DATA);
+
+    private static final String CLIENT_SUITE_B_ECDSA_CERT_STRING =
+            "-----BEGIN CERTIFICATE-----\n"
+                    + "MIIB9zCCAX4CFDpfSZh3AH07BEfGWuMDa7Ynz6y+MAoGCCqGSM49BAMDMF4xCzAJ\n"
+                    + "BgNVBAYTAlVTMQswCQYDVQQIDAJDQTEMMAoGA1UEBwwDTVRWMRAwDgYDVQQKDAdB\n"
+                    + "bmRyb2lkMQ4wDAYDVQQLDAVXaS1GaTESMBAGA1UEAwwJdW5pdGVzdENBMB4XDTIw\n"
+                    + "MDcyMTAyMjk1MFoXDTMwMDUzMDAyMjk1MFowYjELMAkGA1UEBhMCVVMxCzAJBgNV\n"
+                    + "BAgMAkNBMQwwCgYDVQQHDANNVFYxEDAOBgNVBAoMB0FuZHJvaWQxDjAMBgNVBAsM\n"
+                    + "BVdpLUZpMRYwFAYDVQQDDA11bml0ZXN0Q2xpZW50MHYwEAYHKoZIzj0CAQYFK4EE\n"
+                    + "ACIDYgAEhxhVJ7dcSqrto0X+dgRxtd8BWG8cWmPjBji3MIxDLfpcMDoIB84ae1Ew\n"
+                    + "gJn4YUYHrWsUDiVNihv8j7a/Ol1qcIY2ybH7tbezefLmagqA4vXEUXZXoUyL4ZNC\n"
+                    + "DWcdw6LrMAoGCCqGSM49BAMDA2cAMGQCMH4aP73HrriRUJRguiuRic+X4Cqj/7YQ\n"
+                    + "ueJmP87KF92/thhoQ9OrRo8uJITPmNDswwIwP2Q1AZCSL4BI9dYrqu07Ar+pSkXE\n"
+                    + "R7oOqGdZR+d/MvXcFSrbIaLKEoHXmQamIHLe\n"
+                    + "-----END CERTIFICATE-----\n";
+    public static final X509Certificate CLIENT_SUITE_B_ECDSA_CERT =
+            loadCertificate(CLIENT_SUITE_B_ECDSA_CERT_STRING);
+
+    private static final byte[] CLIENT_SUITE_B_ECC_KEY_DATA = new byte[]{
+            (byte) 0x30, (byte) 0x81, (byte) 0xb6, (byte) 0x02, (byte) 0x01, (byte) 0x00,
+            (byte) 0x30, (byte) 0x10, (byte) 0x06, (byte) 0x07, (byte) 0x2a, (byte) 0x86,
+            (byte) 0x48, (byte) 0xce, (byte) 0x3d, (byte) 0x02, (byte) 0x01, (byte) 0x06,
+            (byte) 0x05, (byte) 0x2b, (byte) 0x81, (byte) 0x04, (byte) 0x00, (byte) 0x22,
+            (byte) 0x04, (byte) 0x81, (byte) 0x9e, (byte) 0x30, (byte) 0x81, (byte) 0x9b,
+            (byte) 0x02, (byte) 0x01, (byte) 0x01, (byte) 0x04, (byte) 0x30, (byte) 0xea,
+            (byte) 0x6c, (byte) 0x4b, (byte) 0x6d, (byte) 0x43, (byte) 0xf9, (byte) 0x6c,
+            (byte) 0x91, (byte) 0xdc, (byte) 0x2d, (byte) 0x6e, (byte) 0x87, (byte) 0x4f,
+            (byte) 0x0a, (byte) 0x0b, (byte) 0x97, (byte) 0x25, (byte) 0x1c, (byte) 0x79,
+            (byte) 0xa2, (byte) 0x07, (byte) 0xdc, (byte) 0x94, (byte) 0xc2, (byte) 0xee,
+            (byte) 0x64, (byte) 0x51, (byte) 0x6d, (byte) 0x4e, (byte) 0x35, (byte) 0x1c,
+            (byte) 0x22, (byte) 0x2f, (byte) 0xc0, (byte) 0xea, (byte) 0x09, (byte) 0x47,
+            (byte) 0x3e, (byte) 0xb9, (byte) 0xb6, (byte) 0xb8, (byte) 0x83, (byte) 0x9e,
+            (byte) 0xed, (byte) 0x59, (byte) 0xe5, (byte) 0xe7, (byte) 0x0f, (byte) 0xa1,
+            (byte) 0x64, (byte) 0x03, (byte) 0x62, (byte) 0x00, (byte) 0x04, (byte) 0x87,
+            (byte) 0x18, (byte) 0x55, (byte) 0x27, (byte) 0xb7, (byte) 0x5c, (byte) 0x4a,
+            (byte) 0xaa, (byte) 0xed, (byte) 0xa3, (byte) 0x45, (byte) 0xfe, (byte) 0x76,
+            (byte) 0x04, (byte) 0x71, (byte) 0xb5, (byte) 0xdf, (byte) 0x01, (byte) 0x58,
+            (byte) 0x6f, (byte) 0x1c, (byte) 0x5a, (byte) 0x63, (byte) 0xe3, (byte) 0x06,
+            (byte) 0x38, (byte) 0xb7, (byte) 0x30, (byte) 0x8c, (byte) 0x43, (byte) 0x2d,
+            (byte) 0xfa, (byte) 0x5c, (byte) 0x30, (byte) 0x3a, (byte) 0x08, (byte) 0x07,
+            (byte) 0xce, (byte) 0x1a, (byte) 0x7b, (byte) 0x51, (byte) 0x30, (byte) 0x80,
+            (byte) 0x99, (byte) 0xf8, (byte) 0x61, (byte) 0x46, (byte) 0x07, (byte) 0xad,
+            (byte) 0x6b, (byte) 0x14, (byte) 0x0e, (byte) 0x25, (byte) 0x4d, (byte) 0x8a,
+            (byte) 0x1b, (byte) 0xfc, (byte) 0x8f, (byte) 0xb6, (byte) 0xbf, (byte) 0x3a,
+            (byte) 0x5d, (byte) 0x6a, (byte) 0x70, (byte) 0x86, (byte) 0x36, (byte) 0xc9,
+            (byte) 0xb1, (byte) 0xfb, (byte) 0xb5, (byte) 0xb7, (byte) 0xb3, (byte) 0x79,
+            (byte) 0xf2, (byte) 0xe6, (byte) 0x6a, (byte) 0x0a, (byte) 0x80, (byte) 0xe2,
+            (byte) 0xf5, (byte) 0xc4, (byte) 0x51, (byte) 0x76, (byte) 0x57, (byte) 0xa1,
+            (byte) 0x4c, (byte) 0x8b, (byte) 0xe1, (byte) 0x93, (byte) 0x42, (byte) 0x0d,
+            (byte) 0x67, (byte) 0x1d, (byte) 0xc3, (byte) 0xa2, (byte) 0xeb
+    };
+    public static final PrivateKey CLIENT_SUITE_B_ECC_KEY =
+            loadPrivateKey("EC", CLIENT_SUITE_B_ECC_KEY_DATA);
 
     private static X509Certificate loadCertificate(String blob) {
         try {
@@ -226,9 +662,9 @@
         }
     }
 
-    private static PrivateKey loadPrivateRSAKey(byte[] fakeKey) {
+    private static PrivateKey loadPrivateKey(String algorithm, byte[] fakeKey) {
         try {
-            KeyFactory kf = KeyFactory.getInstance("RSA");
+            KeyFactory kf = KeyFactory.getInstance(algorithm);
             return kf.generatePrivate(new PKCS8EncodedKeySpec(fakeKey));
         } catch (InvalidKeySpecException | NoSuchAlgorithmException e) {
             return null;
diff --git a/wifi/tests/src/android/net/wifi/ScanResultTest.java b/wifi/tests/src/android/net/wifi/ScanResultTest.java
index 5516f43..f1ec5e8 100644
--- a/wifi/tests/src/android/net/wifi/ScanResultTest.java
+++ b/wifi/tests/src/android/net/wifi/ScanResultTest.java
@@ -102,9 +102,10 @@
             5845, WifiScanner.WIFI_BAND_5_GHZ, 169,
             5865, WifiScanner.WIFI_BAND_5_GHZ, 173,
             /* Now some 6GHz channels */
-            5945, WifiScanner.WIFI_BAND_6_GHZ, 1,
-            5960, WifiScanner.WIFI_BAND_6_GHZ, 4,
-            6100, WifiScanner.WIFI_BAND_6_GHZ, 32
+            5955, WifiScanner.WIFI_BAND_6_GHZ, 1,
+            5935, WifiScanner.WIFI_BAND_6_GHZ, 2,
+            5970, WifiScanner.WIFI_BAND_6_GHZ, 4,
+            6110, WifiScanner.WIFI_BAND_6_GHZ, 32
     };
 
     /**
diff --git a/wifi/tests/src/android/net/wifi/SoftApConfigurationTest.java b/wifi/tests/src/android/net/wifi/SoftApConfigurationTest.java
index 31e508c..2d7e535 100644
--- a/wifi/tests/src/android/net/wifi/SoftApConfigurationTest.java
+++ b/wifi/tests/src/android/net/wifi/SoftApConfigurationTest.java
@@ -282,12 +282,6 @@
                 .build();
 
         assertNull(band_6g_config.toWifiConfiguration());
-        SoftApConfiguration sae_transition_config = new SoftApConfiguration.Builder()
-                .setPassphrase("secretsecret",
-                        SoftApConfiguration.SECURITY_TYPE_WPA3_SAE_TRANSITION)
-                .build();
-
-        assertNull(sae_transition_config.toWifiConfiguration());
     }
 
     @Test
@@ -330,5 +324,16 @@
         assertThat(wifiConfig_2g5g.apBand).isEqualTo(WifiConfiguration.AP_BAND_ANY);
         assertThat(wifiConfig_2g5g.apChannel).isEqualTo(0);
         assertThat(wifiConfig_2g5g.hiddenSSID).isEqualTo(true);
+
+        SoftApConfiguration softApConfig_sae_transition = new SoftApConfiguration.Builder()
+                .setPassphrase("secretsecret",
+                SoftApConfiguration.SECURITY_TYPE_WPA3_SAE_TRANSITION)
+                .build();
+
+        WifiConfiguration wifiConfig_sae_transition =
+                softApConfig_sae_transition.toWifiConfiguration();
+        assertThat(wifiConfig_sae_transition.getAuthType())
+                .isEqualTo(WifiConfiguration.KeyMgmt.WPA2_PSK);
+        assertThat(wifiConfig_sae_transition.preSharedKey).isEqualTo("secretsecret");
     }
 }
diff --git a/wifi/tests/src/android/net/wifi/WifiNetworkSpecifierTest.java b/wifi/tests/src/android/net/wifi/WifiNetworkSpecifierTest.java
index fc0ef46..6f47f3d 100644
--- a/wifi/tests/src/android/net/wifi/WifiNetworkSpecifierTest.java
+++ b/wifi/tests/src/android/net/wifi/WifiNetworkSpecifierTest.java
@@ -22,6 +22,8 @@
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 
 import android.net.MacAddress;
@@ -35,6 +37,8 @@
 
 import org.junit.Test;
 
+import java.security.cert.X509Certificate;
+
 /**
  * Unit tests for {@link android.net.wifi.WifiNetworkSpecifier}.
  */
@@ -45,6 +49,7 @@
     private static final String TEST_BSSID_OUI_MASK = "ff:ff:ff:00:00:00";
     private static final String TEST_BSSID = "12:12:12:12:12:12";
     private static final String TEST_PRESHARED_KEY = "\"Test123\"";
+    private static final String TEST_DOMAIN_SUFFIX_MATCH = "domainSuffixMatch";
 
     /**
      * Validate correctness of WifiNetworkSpecifier object created by
@@ -135,6 +140,106 @@
                 wifiNetworkSpecifier.wifiConfiguration.enterpriseConfig.getPhase2Method());
     }
 
+    /**
+     * Validate correctness of WifiNetworkSuggestion object created by
+     * {@link WifiNetworkSuggestion.Builder#build()} for WPA3-Enterprise network.
+     */
+    @Test
+    public void testWifiNetworkSuggestionBuilderForWpa3EapNetwork() {
+        WifiEnterpriseConfig enterpriseConfig = new WifiEnterpriseConfig();
+        enterpriseConfig.setEapMethod(WifiEnterpriseConfig.Eap.TLS);
+        enterpriseConfig.setCaCertificate(FakeKeys.CA_CERT0);
+        enterpriseConfig.setDomainSuffixMatch(TEST_DOMAIN_SUFFIX_MATCH);
+
+        NetworkSpecifier specifier = new WifiNetworkSpecifier.Builder()
+                .setSsid(TEST_SSID)
+                .setWpa3EnterpriseConfig(enterpriseConfig)
+                .build();
+
+        assertTrue(specifier instanceof WifiNetworkSpecifier);
+        WifiNetworkSpecifier wifiNetworkSpecifier = (WifiNetworkSpecifier) specifier;
+
+        assertEquals("\"" + TEST_SSID + "\"", wifiNetworkSpecifier.wifiConfiguration.SSID);
+        assertTrue(wifiNetworkSpecifier.wifiConfiguration.allowedKeyManagement
+                .get(WifiConfiguration.KeyMgmt.IEEE8021X));
+        assertTrue(wifiNetworkSpecifier.wifiConfiguration.allowedKeyManagement
+                .get(WifiConfiguration.KeyMgmt.WPA_EAP));
+        assertFalse(wifiNetworkSpecifier.wifiConfiguration.allowedKeyManagement
+                .get(WifiConfiguration.KeyMgmt.SUITE_B_192));
+        assertTrue(wifiNetworkSpecifier.wifiConfiguration.allowedGroupCiphers
+                .get(WifiConfiguration.GroupCipher.CCMP));
+        assertTrue(wifiNetworkSpecifier.wifiConfiguration.requirePmf);
+        assertNull(wifiNetworkSpecifier.wifiConfiguration.preSharedKey);
+        assertNotNull(wifiNetworkSpecifier.wifiConfiguration.enterpriseConfig);
+    }
+
+    /**
+     * Validate correctness of WifiNetworkSuggestion object created by
+     * {@link WifiNetworkSuggestion.Builder#build()} for WPA3-Enterprise 192-bit RSA SuiteB network.
+     */
+    @Test
+    public void testWifiNetworkSuggestionBuilderForWpa3SuiteBRsaEapNetwork() {
+        WifiEnterpriseConfig enterpriseConfig = new WifiEnterpriseConfig();
+        enterpriseConfig.setEapMethod(WifiEnterpriseConfig.Eap.TLS);
+        enterpriseConfig.setCaCertificate(FakeKeys.CA_SUITE_B_RSA3072_CERT);
+        enterpriseConfig.setClientKeyEntryWithCertificateChain(FakeKeys.CLIENT_SUITE_B_RSA3072_KEY,
+                new X509Certificate[] {FakeKeys.CLIENT_SUITE_B_RSA3072_CERT});
+
+        enterpriseConfig.setDomainSuffixMatch(TEST_DOMAIN_SUFFIX_MATCH);
+
+        NetworkSpecifier specifier = new WifiNetworkSpecifier.Builder()
+                .setSsid(TEST_SSID)
+                .setWpa3EnterpriseConfig(enterpriseConfig)
+                .build();
+
+        assertTrue(specifier instanceof WifiNetworkSpecifier);
+        WifiNetworkSpecifier wifiNetworkSpecifier = (WifiNetworkSpecifier) specifier;
+
+        assertEquals("\"" + TEST_SSID + "\"", wifiNetworkSpecifier.wifiConfiguration.SSID);
+        assertTrue(wifiNetworkSpecifier.wifiConfiguration.allowedKeyManagement
+                .get(WifiConfiguration.KeyMgmt.SUITE_B_192));
+        assertTrue(wifiNetworkSpecifier.wifiConfiguration.allowedGroupCiphers
+                .get(WifiConfiguration.GroupCipher.GCMP_256));
+        assertTrue(wifiNetworkSpecifier.wifiConfiguration.allowedGroupManagementCiphers
+                .get(WifiConfiguration.GroupMgmtCipher.BIP_GMAC_256));
+        assertTrue(wifiNetworkSpecifier.wifiConfiguration.requirePmf);
+        assertNull(wifiNetworkSpecifier.wifiConfiguration.preSharedKey);
+        assertNotNull(wifiNetworkSpecifier.wifiConfiguration.enterpriseConfig);
+    }
+
+    /**
+     * Validate correctness of WifiNetworkSuggestion object created by
+     * {@link WifiNetworkSuggestion.Builder#build()} for WPA3-Enterprise 192-bit ECC SuiteB network.
+     */
+    @Test
+    public void testWifiNetworkSuggestionBuilderForWpa3SuiteBEccEapNetwork() {
+        WifiEnterpriseConfig enterpriseConfig = new WifiEnterpriseConfig();
+        enterpriseConfig.setEapMethod(WifiEnterpriseConfig.Eap.TLS);
+        enterpriseConfig.setCaCertificate(FakeKeys.CA_SUITE_B_ECDSA_CERT);
+        enterpriseConfig.setClientKeyEntryWithCertificateChain(FakeKeys.CLIENT_SUITE_B_ECC_KEY,
+                new X509Certificate[] {FakeKeys.CLIENT_SUITE_B_ECDSA_CERT});
+
+        enterpriseConfig.setDomainSuffixMatch(TEST_DOMAIN_SUFFIX_MATCH);
+
+        NetworkSpecifier specifier = new WifiNetworkSpecifier.Builder()
+                .setSsid(TEST_SSID)
+                .setWpa3EnterpriseConfig(enterpriseConfig)
+                .build();
+
+        assertTrue(specifier instanceof WifiNetworkSpecifier);
+        WifiNetworkSpecifier wifiNetworkSpecifier = (WifiNetworkSpecifier) specifier;
+
+        assertEquals("\"" + TEST_SSID + "\"", wifiNetworkSpecifier.wifiConfiguration.SSID);
+        assertTrue(wifiNetworkSpecifier.wifiConfiguration.allowedKeyManagement
+                .get(WifiConfiguration.KeyMgmt.SUITE_B_192));
+        assertTrue(wifiNetworkSpecifier.wifiConfiguration.allowedGroupCiphers
+                .get(WifiConfiguration.GroupCipher.GCMP_256));
+        assertTrue(wifiNetworkSpecifier.wifiConfiguration.allowedGroupManagementCiphers
+                .get(WifiConfiguration.GroupMgmtCipher.BIP_GMAC_256));
+        assertTrue(wifiNetworkSpecifier.wifiConfiguration.requirePmf);
+        assertNull(wifiNetworkSpecifier.wifiConfiguration.preSharedKey);
+        assertNotNull(wifiNetworkSpecifier.wifiConfiguration.enterpriseConfig);
+    }
 
     /**
      * Ensure {@link WifiNetworkSpecifier.Builder#setSsid(String)} throws an exception
diff --git a/wifi/tests/src/android/net/wifi/WifiNetworkSuggestionTest.java b/wifi/tests/src/android/net/wifi/WifiNetworkSuggestionTest.java
index 16b4ad0..00a0442 100644
--- a/wifi/tests/src/android/net/wifi/WifiNetworkSuggestionTest.java
+++ b/wifi/tests/src/android/net/wifi/WifiNetworkSuggestionTest.java
@@ -27,6 +27,8 @@
 
 import org.junit.Test;
 
+import java.security.cert.X509Certificate;
+
 /**
  * Unit tests for {@link android.net.wifi.WifiNetworkSuggestion}.
  */
@@ -199,16 +201,14 @@
         assertFalse(suggestion.isInitialAutoJoinEnabled);
     }
 
-
     /**
      * Validate correctness of WifiNetworkSuggestion object created by
-     * {@link WifiNetworkSuggestion.Builder#build()} for SuiteB network.
+     * {@link WifiNetworkSuggestion.Builder#build()} for WPA3-Enterprise network.
      */
     @Test
     public void testWifiNetworkSuggestionBuilderForWpa3EapNetwork() {
         WifiEnterpriseConfig enterpriseConfig = new WifiEnterpriseConfig();
         enterpriseConfig.setEapMethod(WifiEnterpriseConfig.Eap.TLS);
-        enterpriseConfig.setPhase2Method(WifiEnterpriseConfig.Phase2.GTC);
         enterpriseConfig.setCaCertificate(FakeKeys.CA_CERT0);
         enterpriseConfig.setDomainSuffixMatch(TEST_DOMAIN_SUFFIX_MATCH);
 
@@ -219,6 +219,78 @@
 
         assertEquals("\"" + TEST_SSID + "\"", suggestion.wifiConfiguration.SSID);
         assertTrue(suggestion.wifiConfiguration.allowedKeyManagement
+                .get(WifiConfiguration.KeyMgmt.IEEE8021X));
+        assertTrue(suggestion.wifiConfiguration.allowedKeyManagement
+                .get(WifiConfiguration.KeyMgmt.WPA_EAP));
+        assertFalse(suggestion.wifiConfiguration.allowedKeyManagement
+                .get(WifiConfiguration.KeyMgmt.SUITE_B_192));
+        assertTrue(suggestion.wifiConfiguration.allowedGroupCiphers
+                .get(WifiConfiguration.GroupCipher.CCMP));
+        assertTrue(suggestion.wifiConfiguration.requirePmf);
+        assertNull(suggestion.wifiConfiguration.preSharedKey);
+        // allowedSuiteBCiphers are set according to the loaded certificate and cannot be tested
+        // here.
+        assertTrue(suggestion.isUserAllowedToManuallyConnect);
+        assertTrue(suggestion.isInitialAutoJoinEnabled);
+        assertNotNull(suggestion.getEnterpriseConfig());
+    }
+
+    /**
+     * Validate correctness of WifiNetworkSuggestion object created by
+     * {@link WifiNetworkSuggestion.Builder#build()} for WPA3-Enterprise 192-bit RSA SuiteB network.
+     */
+    @Test
+    public void testWifiNetworkSuggestionBuilderForWpa3SuiteBRsaEapNetwork() {
+        WifiEnterpriseConfig enterpriseConfig = new WifiEnterpriseConfig();
+        enterpriseConfig.setEapMethod(WifiEnterpriseConfig.Eap.TLS);
+        enterpriseConfig.setCaCertificate(FakeKeys.CA_SUITE_B_RSA3072_CERT);
+        enterpriseConfig.setClientKeyEntryWithCertificateChain(FakeKeys.CLIENT_SUITE_B_RSA3072_KEY,
+                new X509Certificate[] {FakeKeys.CLIENT_SUITE_B_RSA3072_CERT});
+
+        enterpriseConfig.setDomainSuffixMatch(TEST_DOMAIN_SUFFIX_MATCH);
+
+        WifiNetworkSuggestion suggestion = new WifiNetworkSuggestion.Builder()
+                .setSsid(TEST_SSID)
+                .setWpa3EnterpriseConfig(enterpriseConfig)
+                .build();
+
+        assertEquals("\"" + TEST_SSID + "\"", suggestion.wifiConfiguration.SSID);
+        assertTrue(suggestion.wifiConfiguration.allowedKeyManagement
+                .get(WifiConfiguration.KeyMgmt.SUITE_B_192));
+        assertTrue(suggestion.wifiConfiguration.allowedGroupCiphers
+                .get(WifiConfiguration.GroupCipher.GCMP_256));
+        assertTrue(suggestion.wifiConfiguration.allowedGroupManagementCiphers
+                .get(WifiConfiguration.GroupMgmtCipher.BIP_GMAC_256));
+        assertTrue(suggestion.wifiConfiguration.requirePmf);
+        assertNull(suggestion.wifiConfiguration.preSharedKey);
+        // allowedSuiteBCiphers are set according to the loaded certificate and cannot be tested
+        // here.
+        assertTrue(suggestion.isUserAllowedToManuallyConnect);
+        assertTrue(suggestion.isInitialAutoJoinEnabled);
+        assertNotNull(suggestion.getEnterpriseConfig());
+    }
+
+    /**
+     * Validate correctness of WifiNetworkSuggestion object created by
+     * {@link WifiNetworkSuggestion.Builder#build()} for WPA3-Enterprise 192-bit ECC SuiteB network.
+     */
+    @Test
+    public void testWifiNetworkSuggestionBuilderForWpa3SuiteBEccEapNetwork() {
+        WifiEnterpriseConfig enterpriseConfig = new WifiEnterpriseConfig();
+        enterpriseConfig.setEapMethod(WifiEnterpriseConfig.Eap.TLS);
+        enterpriseConfig.setCaCertificate(FakeKeys.CA_SUITE_B_ECDSA_CERT);
+        enterpriseConfig.setClientKeyEntryWithCertificateChain(FakeKeys.CLIENT_SUITE_B_ECC_KEY,
+                new X509Certificate[] {FakeKeys.CLIENT_SUITE_B_ECDSA_CERT});
+
+        enterpriseConfig.setDomainSuffixMatch(TEST_DOMAIN_SUFFIX_MATCH);
+
+        WifiNetworkSuggestion suggestion = new WifiNetworkSuggestion.Builder()
+                .setSsid(TEST_SSID)
+                .setWpa3EnterpriseConfig(enterpriseConfig)
+                .build();
+
+        assertEquals("\"" + TEST_SSID + "\"", suggestion.wifiConfiguration.SSID);
+        assertTrue(suggestion.wifiConfiguration.allowedKeyManagement
                 .get(WifiConfiguration.KeyMgmt.SUITE_B_192));
         assertTrue(suggestion.wifiConfiguration.allowedGroupCiphers
                 .get(WifiConfiguration.GroupCipher.GCMP_256));
diff --git a/wifi/tests/src/android/net/wifi/hotspot2/PasspointConfigurationTest.java b/wifi/tests/src/android/net/wifi/hotspot2/PasspointConfigurationTest.java
index 638efb9..8270d64 100644
--- a/wifi/tests/src/android/net/wifi/hotspot2/PasspointConfigurationTest.java
+++ b/wifi/tests/src/android/net/wifi/hotspot2/PasspointConfigurationTest.java
@@ -23,6 +23,8 @@
 import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertTrue;
 
+import android.net.wifi.EAPConstants;
+import android.net.wifi.FakeKeys;
 import android.net.wifi.hotspot2.pps.Credential;
 import android.net.wifi.hotspot2.pps.HomeSp;
 import android.os.Parcel;
@@ -32,6 +34,11 @@
 import org.junit.Test;
 
 import java.nio.charset.StandardCharsets;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.security.PrivateKey;
+import java.security.cert.CertificateEncodingException;
+import java.security.cert.X509Certificate;
 import java.util.Arrays;
 import java.util.HashMap;
 import java.util.Map;
@@ -383,19 +390,39 @@
     }
 
     /**
-     * Verify that the unique identifier generated is different for two instances with different
-     * HomeSp node
+     * Verify that the unique identifier generated is the same for two instances with different
+     * HomeSp node but same FQDN
      *
      * @throws Exception
      */
     @Test
-    public void validateUniqueIdDifferentHomeSp() throws Exception {
+    public void validateUniqueIdDifferentHomeSpSameFqdn() throws Exception {
         PasspointConfiguration config1 = PasspointTestUtils.createConfig();
 
-        // Modify config2's RCOIs to a different set of values
+        // Modify config2's RCOIs and friendly name to a different set of values
         PasspointConfiguration config2 = PasspointTestUtils.createConfig();
         HomeSp homeSp = config2.getHomeSp();
         homeSp.setRoamingConsortiumOis(new long[] {0xaa, 0xbb});
+        homeSp.setFriendlyName("Some other name");
+        config2.setHomeSp(homeSp);
+
+        assertEquals(config1.getUniqueId(), config2.getUniqueId());
+    }
+
+    /**
+     * Verify that the unique identifier generated is different for two instances with the same
+     * HomeSp node but different FQDN
+     *
+     * @throws Exception
+     */
+    @Test
+    public void validateUniqueIdSameHomeSpDifferentFqdn() throws Exception {
+        PasspointConfiguration config1 = PasspointTestUtils.createConfig();
+
+        // Modify config2's FQDN to a different value
+        PasspointConfiguration config2 = PasspointTestUtils.createConfig();
+        HomeSp homeSp = config2.getHomeSp();
+        homeSp.setFqdn("fqdn2.com");
         config2.setHomeSp(homeSp);
 
         assertNotEquals(config1.getUniqueId(), config2.getUniqueId());
@@ -403,15 +430,15 @@
 
     /**
      * Verify that the unique identifier generated is different for two instances with different
-     * Credential node
+     * SIM Credential node
      *
      * @throws Exception
      */
     @Test
-    public void validateUniqueIdDifferentCredential() throws Exception {
+    public void validateUniqueIdDifferentSimCredential() throws Exception {
         PasspointConfiguration config1 = PasspointTestUtils.createConfig();
 
-        // Modify config2's RCOIs to a different set of values
+        // Modify config2's realm and SIM credential to a different set of values
         PasspointConfiguration config2 = PasspointTestUtils.createConfig();
         Credential credential = config2.getCredential();
         credential.setRealm("realm2.example.com");
@@ -422,6 +449,157 @@
     }
 
     /**
+     * Verify that the unique identifier generated is different for two instances with different
+     * Realm in the Credential node
+     *
+     * @throws Exception
+     */
+    @Test
+    public void validateUniqueIdDifferentRealm() throws Exception {
+        PasspointConfiguration config1 = PasspointTestUtils.createConfig();
+
+        // Modify config2's realm to a different set of values
+        PasspointConfiguration config2 = PasspointTestUtils.createConfig();
+        Credential credential = config2.getCredential();
+        credential.setRealm("realm2.example.com");
+        config2.setCredential(credential);
+
+        assertNotEquals(config1.getUniqueId(), config2.getUniqueId());
+    }
+
+    /**
+     * Verify that the unique identifier generated is the same for two instances with different
+     * password and same username in the User Credential node
+     *
+     * @throws Exception
+     */
+    @Test
+    public void validateUniqueIdSameUserInUserCredential() throws Exception {
+        PasspointConfiguration config1 = PasspointTestUtils.createConfig();
+        Credential credential = createCredentialWithUserCredential("user", "passwd");
+        config1.setCredential(credential);
+
+        // Modify config2's Passpowrd to a different set of values
+        PasspointConfiguration config2 = PasspointTestUtils.createConfig();
+        credential = createCredentialWithUserCredential("user", "newpasswd");
+        config2.setCredential(credential);
+
+        assertEquals(config1.getUniqueId(), config2.getUniqueId());
+    }
+
+    /**
+     * Verify that the unique identifier generated is different for two instances with different
+     * username in the User Credential node
+     *
+     * @throws Exception
+     */
+    @Test
+    public void validateUniqueIdDifferentUserCredential() throws Exception {
+        PasspointConfiguration config1 = PasspointTestUtils.createConfig();
+        Credential credential = createCredentialWithUserCredential("user", "passwd");
+        config1.setCredential(credential);
+
+        // Modify config2's username to a different value
+        PasspointConfiguration config2 = PasspointTestUtils.createConfig();
+        credential = createCredentialWithUserCredential("user2", "passwd");
+        config2.setCredential(credential);
+
+        assertNotEquals(config1.getUniqueId(), config2.getUniqueId());
+    }
+
+    /**
+     * Verify that the unique identifier generated is different for two instances with different
+     * Cert Credential node
+     *
+     * @throws Exception
+     */
+    @Test
+    public void validateUniqueIdDifferentCertCredential() throws Exception {
+        PasspointConfiguration config1 = PasspointTestUtils.createConfig();
+        Credential credential = createCredentialWithCertificateCredential(true, true);
+        config1.setCredential(credential);
+
+        // Modify config2's cert credential to a different set of values
+        PasspointConfiguration config2 = PasspointTestUtils.createConfig();
+        credential = createCredentialWithCertificateCredential(false, false);
+        config2.setCredential(credential);
+
+        assertNotEquals(config1.getUniqueId(), config2.getUniqueId());
+    }
+
+    /**
+     * Helper function for generating certificate credential for testing.
+     *
+     * @return {@link Credential}
+     */
+    private static Credential createCredentialWithCertificateCredential(Boolean useCaCert0,
+            Boolean useCert0)
+            throws NoSuchAlgorithmException, CertificateEncodingException {
+        Credential.CertificateCredential certCred = new Credential.CertificateCredential();
+        certCred.setCertType("x509v3");
+        if (useCert0) {
+            certCred.setCertSha256Fingerprint(
+                    MessageDigest.getInstance("SHA-256").digest(FakeKeys.CLIENT_CERT.getEncoded()));
+        } else {
+            certCred.setCertSha256Fingerprint(MessageDigest.getInstance("SHA-256")
+                    .digest(FakeKeys.CLIENT_SUITE_B_RSA3072_CERT.getEncoded()));
+        }
+        return createCredential(null, certCred, null, new X509Certificate[] {FakeKeys.CLIENT_CERT},
+                FakeKeys.RSA_KEY1, useCaCert0 ? FakeKeys.CA_CERT0 : FakeKeys.CA_CERT1);
+    }
+
+    /**
+     * Helper function for generating user credential for testing.
+     *
+     * @return {@link Credential}
+     */
+    private static Credential createCredentialWithUserCredential(String username, String password) {
+        Credential.UserCredential userCred = new Credential.UserCredential();
+        userCred.setUsername(username);
+        userCred.setPassword(password);
+        userCred.setMachineManaged(true);
+        userCred.setAbleToShare(true);
+        userCred.setSoftTokenApp("TestApp");
+        userCred.setEapType(EAPConstants.EAP_TTLS);
+        userCred.setNonEapInnerMethod("MS-CHAP");
+        return createCredential(userCred, null, null, null, null, FakeKeys.CA_CERT0);
+    }
+
+    /**
+     * Helper function for generating Credential for testing.
+     *
+     * @param userCred Instance of UserCredential
+     * @param certCred Instance of CertificateCredential
+     * @param simCred Instance of SimCredential
+     * @param clientCertificateChain Chain of client certificates
+     * @param clientPrivateKey Client private key
+     * @param caCerts CA certificates
+     * @return {@link Credential}
+     */
+    private static Credential createCredential(Credential.UserCredential userCred,
+            Credential.CertificateCredential certCred,
+            Credential.SimCredential simCred,
+            X509Certificate[] clientCertificateChain, PrivateKey clientPrivateKey,
+            X509Certificate... caCerts) {
+        Credential cred = new Credential();
+        cred.setCreationTimeInMillis(123455L);
+        cred.setExpirationTimeInMillis(2310093L);
+        cred.setRealm("realm");
+        cred.setCheckAaaServerCertStatus(true);
+        cred.setUserCredential(userCred);
+        cred.setCertCredential(certCred);
+        cred.setSimCredential(simCred);
+        if (caCerts != null && caCerts.length == 1) {
+            cred.setCaCertificate(caCerts[0]);
+        } else {
+            cred.setCaCertificates(caCerts);
+        }
+        cred.setClientCertificateChain(clientCertificateChain);
+        cred.setClientPrivateKey(clientPrivateKey);
+        return cred;
+    }
+
+    /**
      * Verify that the unique identifier API generates an exception if HomeSP is not initialized.
      *
      * @throws Exception
diff --git a/wifi/tests/src/android/net/wifi/hotspot2/pps/CredentialTest.java b/wifi/tests/src/android/net/wifi/hotspot2/pps/CredentialTest.java
index 829d8f0..a44df40 100644
--- a/wifi/tests/src/android/net/wifi/hotspot2/pps/CredentialTest.java
+++ b/wifi/tests/src/android/net/wifi/hotspot2/pps/CredentialTest.java
@@ -593,10 +593,10 @@
     }
 
     /**
-     * Verify that unique identifiers are different for a credential with different values
+     * Verify that unique identifiers are different for a credential with different username
      */
     @Test
-    public void testUniqueIdDifferentForUserCredentialsWithDifferentValues() throws Exception {
+    public void testUniqueIdDifferentForUserCredentialsWithDifferentUsername() throws Exception {
         Credential userCred1 = createCredentialWithUserCredential();
         Credential userCred2 = createCredentialWithUserCredential();
         userCred2.getUserCredential().setUsername("anotheruser");
@@ -605,7 +605,24 @@
     }
 
     /**
-     * Verify that unique identifiers are different for a credential with different values
+     * Verify that unique identifiers are different for a credential with different password and
+     * other values other than username
+     */
+    @Test
+    public void testUniqueIdSameForUserCredentialsWithDifferentPassword() throws Exception {
+        Credential userCred1 = createCredentialWithUserCredential();
+        Credential userCred2 = createCredentialWithUserCredential();
+        userCred2.getUserCredential().setPassword("someotherpassword!");
+        userCred2.getUserCredential().setMachineManaged(false);
+        userCred2.getUserCredential().setAbleToShare(false);
+        userCred2.getUserCredential().setSoftTokenApp("TestApp2");
+        userCred2.getUserCredential().setNonEapInnerMethod("PAP");
+
+        assertEquals(userCred1.getUniqueId(), userCred2.getUniqueId());
+    }
+
+    /**
+     * Verify that unique identifiers are different for a cert credential with different values
      */
     @Test
     public void testUniqueIdDifferentForCertCredentialsWithDifferentValues() throws Exception {