Merge "Use padding in pages instead of pageMargin" into sc-dev
diff --git a/.prebuilt_info/prebuilt_info_packages_CtsShim_apk__arm_CtsShimPriv_apk.asciipb b/.prebuilt_info/prebuilt_info_packages_CtsShim_apk__arm_CtsShimPriv_apk.asciipb
index cfcb4e7..80317e4 100644
--- a/.prebuilt_info/prebuilt_info_packages_CtsShim_apk__arm_CtsShimPriv_apk.asciipb
+++ b/.prebuilt_info/prebuilt_info_packages_CtsShim_apk__arm_CtsShimPriv_apk.asciipb
@@ -1,6 +1,6 @@
 drops {
   android_build_drop {
-    build_id: "7351002"
+    build_id: "7396576"
     target: "CtsShim"
     source_file: "aosp_arm64/CtsShimPriv.apk"
   }
@@ -9,4 +9,5 @@
   version_group: ""
   git_project: "platform/frameworks/base"
   git_branch: "sc-dev"
+  transform: TRANSFORM_NONE
 }
diff --git a/.prebuilt_info/prebuilt_info_packages_CtsShim_apk__arm_CtsShim_apk.asciipb b/.prebuilt_info/prebuilt_info_packages_CtsShim_apk__arm_CtsShim_apk.asciipb
index 0948e47..3605b6d 100644
--- a/.prebuilt_info/prebuilt_info_packages_CtsShim_apk__arm_CtsShim_apk.asciipb
+++ b/.prebuilt_info/prebuilt_info_packages_CtsShim_apk__arm_CtsShim_apk.asciipb
@@ -1,6 +1,6 @@
 drops {
   android_build_drop {
-    build_id: "7351002"
+    build_id: "7396576"
     target: "CtsShim"
     source_file: "aosp_arm64/CtsShim.apk"
   }
@@ -9,4 +9,5 @@
   version_group: ""
   git_project: "platform/frameworks/base"
   git_branch: "sc-dev"
+  transform: TRANSFORM_NONE
 }
diff --git a/.prebuilt_info/prebuilt_info_packages_CtsShim_apk__x86_CtsShimPriv_apk.asciipb b/.prebuilt_info/prebuilt_info_packages_CtsShim_apk__x86_CtsShimPriv_apk.asciipb
index db64475..025ec3a 100644
--- a/.prebuilt_info/prebuilt_info_packages_CtsShim_apk__x86_CtsShimPriv_apk.asciipb
+++ b/.prebuilt_info/prebuilt_info_packages_CtsShim_apk__x86_CtsShimPriv_apk.asciipb
@@ -1,6 +1,6 @@
 drops {
   android_build_drop {
-    build_id: "7351002"
+    build_id: "7396576"
     target: "CtsShim"
     source_file: "aosp_x86_64/CtsShimPriv.apk"
   }
@@ -9,4 +9,5 @@
   version_group: ""
   git_project: "platform/frameworks/base"
   git_branch: "sc-dev"
+  transform: TRANSFORM_NONE
 }
diff --git a/.prebuilt_info/prebuilt_info_packages_CtsShim_apk__x86_CtsShim_apk.asciipb b/.prebuilt_info/prebuilt_info_packages_CtsShim_apk__x86_CtsShim_apk.asciipb
index 80812df..e19235a 100644
--- a/.prebuilt_info/prebuilt_info_packages_CtsShim_apk__x86_CtsShim_apk.asciipb
+++ b/.prebuilt_info/prebuilt_info_packages_CtsShim_apk__x86_CtsShim_apk.asciipb
@@ -1,6 +1,6 @@
 drops {
   android_build_drop {
-    build_id: "7351002"
+    build_id: "7396576"
     target: "CtsShim"
     source_file: "aosp_x86_64/CtsShim.apk"
   }
@@ -9,4 +9,5 @@
   version_group: ""
   git_project: "platform/frameworks/base"
   git_branch: "sc-dev"
+  transform: TRANSFORM_NONE
 }
diff --git a/core/java/android/app/ActivityManagerInternal.java b/core/java/android/app/ActivityManagerInternal.java
index ab610e4..d962fa3 100644
--- a/core/java/android/app/ActivityManagerInternal.java
+++ b/core/java/android/app/ActivityManagerInternal.java
@@ -48,6 +48,23 @@
  */
 public abstract class ActivityManagerInternal {
 
+    public enum ServiceNotificationPolicy {
+        /**
+         * The Notification is not associated with any foreground service.
+         */
+        NOT_FOREGROUND_SERVICE,
+        /**
+         * The Notification is associated with a foreground service, but the
+         * notification system should handle it just like non-FGS notifications.
+         */
+        SHOW_IMMEDIATELY,
+        /**
+         * The Notification is associated with a foreground service, and the
+         * notification system should ignore it unless it has already been shown (in
+         * which case it should be used to update the currently displayed UI).
+         */
+        UPDATE_ONLY
+    }
 
     // Access modes for handleIncomingUser.
     public static final int ALLOW_NON_FULL = 0;
@@ -458,6 +475,24 @@
             String channelId);
 
     /**
+     * Tell the service lifecycle logic that the given Notification content is now
+     * canonical for any foreground-service visibility policy purposes.
+     *
+     * Returns a description of any FGs-related policy around the given Notification:
+     * not associated with an FGS; ensure display; or only update if already displayed.
+     */
+    public abstract ServiceNotificationPolicy applyForegroundServiceNotification(
+            Notification notification, int id, String pkg, @UserIdInt int userId);
+
+    /**
+     * Callback from the notification subsystem that the given FGS notification has
+     * been shown or updated.  This can happen after either Service.startForeground()
+     * or NotificationManager.notify().
+     */
+    public abstract void onForegroundServiceNotificationUpdate(Notification notification,
+            int id, String pkg, @UserIdInt int userId);
+
+    /**
      * If the given app has any FGSs whose notifications are in the given channel,
      * stop them.
      */
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index 02520af..1415212 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -2784,16 +2784,6 @@
     private static final ThreadLocal<Integer> sBinderThreadCallingUid = new ThreadLocal<>();
 
     /**
-     * Optimization: we need to propagate to IPCs whether the current thread is collecting
-     * app ops but using only the thread local above is too slow as it requires a map lookup
-     * on every IPC. We add this static var that is lockless and stores an OR-ed mask of the
-     * thread id's currently collecting ops, thus reducing the map lookup to a simple bit
-     * operation except the extremely unlikely case when threads with overlapping id bits
-     * execute op collecting ops.
-     */
-    private static volatile long sThreadsListeningForOpNotedInBinderTransaction = 0L;
-
-    /**
      * If a thread is currently executing a two-way binder transaction, this stores the
      * ops that were noted blaming any app (the caller, the caller of the caller, etc).
      *
@@ -8903,7 +8893,6 @@
      * @hide
      */
     public static void startNotedAppOpsCollection(int callingUid) {
-        sThreadsListeningForOpNotedInBinderTransaction |= Thread.currentThread().getId();
         sBinderThreadCallingUid.set(callingUid);
     }
 
@@ -8918,7 +8907,6 @@
      */
     public static void finishNotedAppOpsCollection() {
         sBinderThreadCallingUid.remove();
-        sThreadsListeningForOpNotedInBinderTransaction &= ~Thread.currentThread().getId();
         sAppOpsNotedInThisBinderTransaction.remove();
     }
 
@@ -9263,9 +9251,7 @@
      * @return whether we are in a binder transaction and collecting appops.
      */
     private static boolean isListeningForOpNotedInBinderTransaction() {
-        return (sThreadsListeningForOpNotedInBinderTransaction
-                        & Thread.currentThread().getId()) != 0
-                && sBinderThreadCallingUid.get() != null;
+        return sBinderThreadCallingUid.get() != null;
     }
 
     /**
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index 432d99d..64b100f 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -450,10 +450,12 @@
         STANDARD_LAYOUTS.add(R.layout.notification_template_material_big_text);
         STANDARD_LAYOUTS.add(R.layout.notification_template_material_inbox);
         STANDARD_LAYOUTS.add(R.layout.notification_template_material_messaging);
+        STANDARD_LAYOUTS.add(R.layout.notification_template_material_big_messaging);
         STANDARD_LAYOUTS.add(R.layout.notification_template_material_conversation);
         STANDARD_LAYOUTS.add(R.layout.notification_template_material_media);
         STANDARD_LAYOUTS.add(R.layout.notification_template_material_big_media);
         STANDARD_LAYOUTS.add(R.layout.notification_template_material_call);
+        STANDARD_LAYOUTS.add(R.layout.notification_template_material_big_call);
         STANDARD_LAYOUTS.add(R.layout.notification_template_header);
     }
 
@@ -5817,7 +5819,7 @@
          *   @hide
          */
         public RemoteViews createContentView(boolean increasedHeight) {
-            if (mN.contentView != null && useExistingRemoteView()) {
+            if (useExistingRemoteView(mN.contentView)) {
                 return fullyCustomViewRequiresDecoration(false /* fromStyle */)
                         ? minimallyDecoratedContentView(mN.contentView) : mN.contentView;
             } else if (mStyle != null) {
@@ -5833,8 +5835,24 @@
             return applyStandardTemplate(getBaseLayoutResource(), p, null /* result */);
         }
 
-        private boolean useExistingRemoteView() {
-            return mStyle == null || !mStyle.displayCustomViewInline();
+        private boolean useExistingRemoteView(RemoteViews customContent) {
+            if (customContent == null) {
+                return false;
+            }
+            if (styleDisplaysCustomViewInline()) {
+                // the provided custom view is intended to be wrapped by the style.
+                return false;
+            }
+            if (fullyCustomViewRequiresDecoration(false)
+                    && STANDARD_LAYOUTS.contains(customContent.getLayoutId())) {
+                // If the app's custom views are objects returned from Builder.create*ContentView()
+                // then the app is most likely attempting to spoof the user.  Even if they are not,
+                // the result would be broken (b/189189308) so we will ignore it.
+                Log.w(TAG, "For apps targeting S, a custom content view that is a modified "
+                        + "version of any standard layout is disallowed.");
+                return false;
+            }
+            return true;
         }
 
         /**
@@ -5842,7 +5860,7 @@
          */
         public RemoteViews createBigContentView() {
             RemoteViews result = null;
-            if (mN.bigContentView != null && useExistingRemoteView()) {
+            if (useExistingRemoteView(mN.bigContentView)) {
                 return fullyCustomViewRequiresDecoration(false /* fromStyle */)
                         ? minimallyDecoratedBigContentView(mN.bigContentView) : mN.bigContentView;
             }
@@ -5947,7 +5965,7 @@
          * @hide
          */
         public RemoteViews createHeadsUpContentView(boolean increasedHeight) {
-            if (mN.headsUpContentView != null && useExistingRemoteView()) {
+            if (useExistingRemoteView(mN.headsUpContentView)) {
                 return fullyCustomViewRequiresDecoration(false /* fromStyle */)
                         ? minimallyDecoratedHeadsUpContentView(mN.headsUpContentView)
                         : mN.headsUpContentView;
@@ -6396,7 +6414,7 @@
             mN.reduceImageSizes(mContext);
 
             if (mContext.getApplicationInfo().targetSdkVersion < Build.VERSION_CODES.N
-                    && (useExistingRemoteView())) {
+                    && !styleDisplaysCustomViewInline()) {
                 if (mN.contentView == null) {
                     mN.contentView = createContentView();
                     mN.extras.putInt(EXTRA_REBUILD_CONTENT_VIEW_ACTION_COUNT,
@@ -6427,6 +6445,10 @@
             return mN;
         }
 
+        private boolean styleDisplaysCustomViewInline() {
+            return mStyle != null && mStyle.displayCustomViewInline();
+        }
+
         /**
          * Apply this Builder to an existing {@link Notification} object.
          *
@@ -6576,7 +6598,7 @@
         public boolean usesTemplate() {
             return (mN.contentView == null && mN.headsUpContentView == null
                     && mN.bigContentView == null)
-                    || (mStyle != null && mStyle.displayCustomViewInline());
+                    || styleDisplaysCustomViewInline();
         }
     }
 
@@ -10137,6 +10159,8 @@
              * {@link Activity#isLaunchedFromBubble()} will return with {@code true}.
              * </p>
              *
+             * Note that the pending intent used here requires PendingIntent.FLAG_MUTABLE.
+             *
              * @throws NullPointerException if intent is null.
              * @throws NullPointerException if icon is null.
              */
diff --git a/core/java/android/appwidget/AppWidgetManager.java b/core/java/android/appwidget/AppWidgetManager.java
index a88aed7..18c6381 100644
--- a/core/java/android/appwidget/AppWidgetManager.java
+++ b/core/java/android/appwidget/AppWidgetManager.java
@@ -963,6 +963,9 @@
     /**
      * Set the component for a given appWidgetId.
      *
+     * If successful, the app widget provider will receive a {@link #ACTION_APPWIDGET_UPDATE}
+     * broadcast.
+     *
      * <p class="note">You need the BIND_APPWIDGET permission or the user must have enabled binding
      *         widgets always for your component. Should be used by apps that host widgets; if this
      *         method returns false, call {@link #ACTION_APPWIDGET_BIND} to request permission to
@@ -983,6 +986,9 @@
     /**
      * Set the component for a given appWidgetId.
      *
+     * If successful, the app widget provider will receive a {@link #ACTION_APPWIDGET_UPDATE}
+     * broadcast.
+     *
      * <p class="note">You need the BIND_APPWIDGET permission or the user must have enabled binding
      *         widgets always for your component. Should be used by apps that host widgets; if this
      *         method returns false, call {@link #ACTION_APPWIDGET_BIND} to request permission to
@@ -1006,6 +1012,10 @@
 
     /**
      * Set the provider for a given appWidgetId if the caller has a permission.
+     *
+     * If successful, the app widget provider will receive a {@link #ACTION_APPWIDGET_UPDATE}
+     * broadcast.
+     *
      * <p>
      * <strong>Note:</strong> You need the {@link android.Manifest.permission#BIND_APPWIDGET}
      * permission or the user must have enabled binding widgets always for your component.
diff --git a/core/java/android/net/NetworkTemplate.java b/core/java/android/net/NetworkTemplate.java
index fd446cd..249154a 100644
--- a/core/java/android/net/NetworkTemplate.java
+++ b/core/java/android/net/NetworkTemplate.java
@@ -514,6 +514,10 @@
         return mSubscriberIdMatchRule;
     }
 
+    public int getMeteredness() {
+        return mMetered;
+    }
+
     /**
      * Test if given {@link NetworkIdentity} matches this template.
      */
diff --git a/core/java/android/os/PowerManager.java b/core/java/android/os/PowerManager.java
index 9d1fd50..3aa0bcb 100644
--- a/core/java/android/os/PowerManager.java
+++ b/core/java/android/os/PowerManager.java
@@ -2107,6 +2107,8 @@
      * Return whether the given application package name is on the device's power allowlist.
      * Apps can be placed on the allowlist through the settings UI invoked by
      * {@link android.provider.Settings#ACTION_IGNORE_BATTERY_OPTIMIZATION_SETTINGS}.
+     * <p>Being on the power allowlist means that the system will not apply most power saving
+     * features to the app. Guardrails for extreme cases may still be applied.
      */
     public boolean isIgnoringBatteryOptimizations(String packageName) {
         return getPowerWhitelistManager().isWhitelisted(packageName, true);
diff --git a/core/java/android/os/VibrationEffect.java b/core/java/android/os/VibrationEffect.java
index 781800d..a0cbbfe 100644
--- a/core/java/android/os/VibrationEffect.java
+++ b/core/java/android/os/VibrationEffect.java
@@ -182,6 +182,11 @@
      * @return The desired effect.
      */
     public static VibrationEffect createOneShot(long milliseconds, int amplitude) {
+        if (amplitude == 0) {
+            throw new IllegalArgumentException(
+                    "amplitude must either be DEFAULT_AMPLITUDE, "
+                            + "or between 1 and 255 inclusive (amplitude=" + amplitude + ")");
+        }
         return createWaveform(new long[]{milliseconds}, new int[]{amplitude}, -1 /* repeat */);
     }
 
@@ -581,22 +586,16 @@
         public void validate() {
             int segmentCount = mSegments.size();
             boolean hasNonZeroDuration = false;
-            boolean hasNonZeroAmplitude = false;
             for (int i = 0; i < segmentCount; i++) {
                 VibrationEffectSegment segment = mSegments.get(i);
                 segment.validate();
                 // A segment with unknown duration = -1 still counts as a non-zero duration.
                 hasNonZeroDuration |= segment.getDuration() != 0;
-                hasNonZeroAmplitude |= segment.hasNonZeroAmplitude();
             }
             if (!hasNonZeroDuration) {
                 throw new IllegalArgumentException("at least one timing must be non-zero"
                         + " (segments=" + mSegments + ")");
             }
-            if (!hasNonZeroAmplitude) {
-                throw new IllegalArgumentException("at least one amplitude must be non-zero"
-                        + " (segments=" + mSegments + ")");
-            }
             if (mRepeatIndex != -1) {
                 Preconditions.checkArgumentInRange(mRepeatIndex, 0, segmentCount - 1,
                         "repeat index must be within the bounds of the segments (segments.length="
diff --git a/core/java/android/permission/PermissionControllerService.java b/core/java/android/permission/PermissionControllerService.java
index 0b99b85..8854e27 100644
--- a/core/java/android/permission/PermissionControllerService.java
+++ b/core/java/android/permission/PermissionControllerService.java
@@ -17,6 +17,7 @@
 package android.permission;
 
 import static android.app.admin.DevicePolicyManager.PERMISSION_GRANT_STATE_DENIED;
+import static android.app.admin.DevicePolicyManager.PERMISSION_GRANT_STATE_GRANTED;
 import static android.permission.PermissionControllerManager.COUNT_ONLY_WHEN_GRANTED;
 import static android.permission.PermissionControllerManager.COUNT_WHEN_SYSTEM;
 
@@ -510,7 +511,7 @@
                     String callerPackageName, AdminPermissionControlParams params,
                     AndroidFuture callback) {
                 checkStringNotEmpty(callerPackageName);
-                if (params.getGrantState() == PERMISSION_GRANT_STATE_DENIED) {
+                if (params.getGrantState() == PERMISSION_GRANT_STATE_GRANTED) {
                     enforceSomePermissionsGrantedToCaller(
                             Manifest.permission.GRANT_RUNTIME_PERMISSIONS);
                 }
@@ -542,6 +543,9 @@
             public void updateUserSensitiveForApp(int uid, @NonNull AndroidFuture callback) {
                 Preconditions.checkNotNull(callback, "callback cannot be null");
 
+                enforceSomePermissionsGrantedToCaller(
+                        Manifest.permission.ADJUST_RUNTIME_PERMISSIONS_POLICY);
+
                 try {
                     onUpdateUserSensitivePermissionFlags(uid, () -> callback.complete(null));
                 } catch (Exception e) {
@@ -608,9 +612,7 @@
                 try {
                     Objects.requireNonNull(permissionGroupName);
                     Objects.requireNonNull(callback);
-                    PermissionControllerService
-                            .this
-                            .onGetGroupOfPlatformPermission(
+                    PermissionControllerService.this.onGetGroupOfPlatformPermission(
                             permissionGroupName, callback::complete);
                 } catch (Throwable t) {
                     callback.completeExceptionally(t);
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index e3302d1..13e5cda 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -2773,6 +2773,7 @@
 
         private final ArraySet<String> mReadableFields;
         private final ArraySet<String> mAllFields;
+        private final ArrayMap<String, Integer> mReadableFieldsWithMaxTargetSdk;
 
         @GuardedBy("this")
         private GenerationTracker mGenerationTracker;
@@ -2794,7 +2795,9 @@
             mProviderHolder = providerHolder;
             mReadableFields = new ArraySet<>();
             mAllFields = new ArraySet<>();
-            getPublicSettingsForClass(callerClass, mAllFields, mReadableFields);
+            mReadableFieldsWithMaxTargetSdk = new ArrayMap<>();
+            getPublicSettingsForClass(callerClass, mAllFields, mReadableFields,
+                    mReadableFieldsWithMaxTargetSdk);
         }
 
         public boolean putStringForUser(ContentResolver cr, String name, String value,
@@ -2851,13 +2854,34 @@
             // Settings.Global and is not annotated as @Readable.
             // Notice that a key string that is not defined in any of the Settings.* classes will
             // still be regarded as readable.
-            if (!isCallerExemptFromReadableRestriction()
-                    && mAllFields.contains(name) && !mReadableFields.contains(name)) {
-                throw new SecurityException(
-                        "Settings key: <" + name + "> is not readable. From S+, settings keys "
-                                + "annotated with @hide are restricted to system_server and system "
-                                + "apps only, unless they are annotated with @Readable.");
+            if (!isCallerExemptFromReadableRestriction() && mAllFields.contains(name)) {
+                if (!mReadableFields.contains(name)) {
+                    throw new SecurityException(
+                            "Settings key: <" + name + "> is not readable. From S+, settings keys "
+                                    + "annotated with @hide are restricted to system_server and "
+                                    + "system apps only, unless they are annotated with @Readable."
+                    );
+                } else {
+                    // When the target settings key has @Readable annotation, if the caller app's
+                    // target sdk is higher than the maxTargetSdk of the annotation, reject access.
+                    if (mReadableFieldsWithMaxTargetSdk.containsKey(name)) {
+                        final int maxTargetSdk = mReadableFieldsWithMaxTargetSdk.get(name);
+                        final Application application = ActivityThread.currentApplication();
+                        final boolean targetSdkCheckOk = application != null
+                                && application.getApplicationInfo() != null
+                                && application.getApplicationInfo().targetSdkVersion
+                                <= maxTargetSdk;
+                        if (!targetSdkCheckOk) {
+                            throw new SecurityException(
+                                    "Settings key: <" + name + "> is only readable to apps with "
+                                            + "targetSdkVersion lower than or equal to: "
+                                            + maxTargetSdk
+                            );
+                        }
+                    }
+                }
             }
+
             final boolean isSelf = (userHandle == UserHandle.myUserId());
             int currentGeneration = -1;
             if (isSelf) {
@@ -3225,10 +3249,12 @@
     @Target({ ElementType.FIELD })
     @Retention(RetentionPolicy.RUNTIME)
     private @interface Readable {
+        int maxTargetSdk() default 0;
     }
 
     private static <T extends NameValueTable> void getPublicSettingsForClass(
-            Class<T> callerClass, Set<String> allKeys, Set<String> readableKeys) {
+            Class<T> callerClass, Set<String> allKeys, Set<String> readableKeys,
+            ArrayMap<String, Integer> keysWithMaxTargetSdk) {
         final Field[] allFields = callerClass.getDeclaredFields();
         try {
             for (int i = 0; i < allFields.length; i++) {
@@ -3241,8 +3267,15 @@
                     continue;
                 }
                 allKeys.add((String) value);
-                if (field.getAnnotation(Readable.class) != null) {
-                    readableKeys.add((String) value);
+                final Readable annotation = field.getAnnotation(Readable.class);
+
+                if (annotation != null) {
+                    final String key = (String) value;
+                    final int maxTargetSdk = annotation.maxTargetSdk();
+                    readableKeys.add(key);
+                    if (maxTargetSdk != 0) {
+                        keysWithMaxTargetSdk.put(key, maxTargetSdk);
+                    }
                 }
             }
         } catch (IllegalAccessException ignored) {
@@ -3404,8 +3437,10 @@
         }
 
         /** @hide */
-        public static void getPublicSettings(Set<String> allKeys, Set<String> readableKeys) {
-            getPublicSettingsForClass(System.class, allKeys, readableKeys);
+        public static void getPublicSettings(Set<String> allKeys, Set<String> readableKeys,
+                ArrayMap<String, Integer> readableKeysWithMaxTargetSdk) {
+            getPublicSettingsForClass(System.class, allKeys, readableKeys,
+                    readableKeysWithMaxTargetSdk);
         }
 
         /**
@@ -5734,8 +5769,10 @@
         }
 
         /** @hide */
-        public static void getPublicSettings(Set<String> allKeys, Set<String> readableKeys) {
-            getPublicSettingsForClass(Secure.class, allKeys, readableKeys);
+        public static void getPublicSettings(Set<String> allKeys, Set<String> readableKeys,
+                ArrayMap<String, Integer> readableKeysWithMaxTargetSdk) {
+            getPublicSettingsForClass(Secure.class, allKeys, readableKeys,
+                    readableKeysWithMaxTargetSdk);
         }
 
         /**
@@ -11023,7 +11060,7 @@
         * @hide
         */
         @UnsupportedAppUsage
-        @Readable
+        @Readable(maxTargetSdk = Build.VERSION_CODES.R)
         public static final String MOBILE_DATA = "mobile_data";
 
         /**
@@ -14965,8 +15002,10 @@
         }
 
         /** @hide */
-        public static void getPublicSettings(Set<String> allKeys, Set<String> readableKeys) {
-            getPublicSettingsForClass(Global.class, allKeys, readableKeys);
+        public static void getPublicSettings(Set<String> allKeys, Set<String> readableKeys,
+                ArrayMap<String, Integer> readableKeysWithMaxTargetSdk) {
+            getPublicSettingsForClass(Global.class, allKeys, readableKeys,
+                    readableKeysWithMaxTargetSdk);
         }
 
         /**
diff --git a/core/java/android/service/smartspace/SmartspaceService.java b/core/java/android/service/smartspace/SmartspaceService.java
index 09b7310..7dd85cc 100644
--- a/core/java/android/service/smartspace/SmartspaceService.java
+++ b/core/java/android/service/smartspace/SmartspaceService.java
@@ -122,14 +122,18 @@
     @Override
     public void onCreate() {
         super.onCreate();
-        Log.d(TAG, "onCreate mSessionCallbacks: " + mSessionCallbacks);
+        if (DEBUG) {
+            Log.d(TAG, "onCreate mSessionCallbacks: " + mSessionCallbacks);
+        }
         mHandler = new Handler(Looper.getMainLooper(), null, true);
     }
 
     @Override
     @NonNull
     public final IBinder onBind(@NonNull Intent intent) {
-        Log.d(TAG, "onBind mSessionCallbacks: " + mSessionCallbacks);
+        if (DEBUG) {
+            Log.d(TAG, "onBind mSessionCallbacks: " + mSessionCallbacks);
+        }
         if (SERVICE_INTERFACE.equals(intent.getAction())) {
             return mInterface.asBinder();
         }
@@ -140,7 +144,9 @@
 
     private void doCreateSmartspaceSession(@NonNull SmartspaceConfig config,
             @NonNull SmartspaceSessionId sessionId) {
-        Log.d(TAG, "doCreateSmartspaceSession mSessionCallbacks: " + mSessionCallbacks);
+        if (DEBUG) {
+            Log.d(TAG, "doCreateSmartspaceSession mSessionCallbacks: " + mSessionCallbacks);
+        }
         mSessionCallbacks.put(sessionId, new ArrayList<>());
         onCreateSmartspaceSession(config, sessionId);
     }
@@ -166,7 +172,9 @@
 
     private void doRegisterSmartspaceUpdates(@NonNull SmartspaceSessionId sessionId,
             @NonNull ISmartspaceCallback callback) {
-        Log.d(TAG, "doRegisterSmartspaceUpdates mSessionCallbacks: " + mSessionCallbacks);
+        if (DEBUG) {
+            Log.d(TAG, "doRegisterSmartspaceUpdates mSessionCallbacks: " + mSessionCallbacks);
+        }
         final ArrayList<CallbackWrapper> callbacks = mSessionCallbacks.get(sessionId);
         if (callbacks == null) {
             Slog.e(TAG, "Failed to register for updates for unknown session: " + sessionId);
@@ -184,7 +192,9 @@
 
     private void doUnregisterSmartspaceUpdates(@NonNull SmartspaceSessionId sessionId,
             @NonNull ISmartspaceCallback callback) {
-        Log.d(TAG, "doUnregisterSmartspaceUpdates mSessionCallbacks: " + mSessionCallbacks);
+        if (DEBUG) {
+            Log.d(TAG, "doUnregisterSmartspaceUpdates mSessionCallbacks: " + mSessionCallbacks);
+        }
         final ArrayList<CallbackWrapper> callbacks = mSessionCallbacks.get(sessionId);
         if (callbacks == null) {
             Slog.e(TAG, "Failed to unregister for updates for unknown session: " + sessionId);
@@ -198,7 +208,9 @@
     }
 
     private void doRequestPredictionUpdate(@NonNull SmartspaceSessionId sessionId) {
-        Log.d(TAG, "doRequestPredictionUpdate mSessionCallbacks: " + mSessionCallbacks);
+        if (DEBUG) {
+            Log.d(TAG, "doRequestPredictionUpdate mSessionCallbacks: " + mSessionCallbacks);
+        }
         // Just an optimization, if there are no callbacks, then don't bother notifying the service
         final ArrayList<CallbackWrapper> callbacks = mSessionCallbacks.get(sessionId);
         if (callbacks != null && !callbacks.isEmpty()) {
@@ -246,7 +258,9 @@
      */
     public final void updateSmartspaceTargets(@NonNull SmartspaceSessionId sessionId,
             @NonNull List<SmartspaceTarget> targets) {
-        Log.d(TAG, "updateSmartspaceTargets mSessionCallbacks: " + mSessionCallbacks);
+        if (DEBUG) {
+            Log.d(TAG, "updateSmartspaceTargets mSessionCallbacks: " + mSessionCallbacks);
+        }
         List<CallbackWrapper> callbacks = mSessionCallbacks.get(sessionId);
         if (callbacks != null) {
             for (CallbackWrapper callback : callbacks) {
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 908d236..8138b3d3 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -4727,9 +4727,10 @@
         WindowInsetsAnimation.Callback mWindowInsetsAnimationCallback;
 
         /**
-         * This lives here since it's only valid for interactive views.
+         * This lives here since it's only valid for interactive views. This list is null until the
+         * first use.
          */
-        private List<Rect> mSystemGestureExclusionRects;
+        private List<Rect> mSystemGestureExclusionRects = null;
 
         /**
          * Used to track {@link #mSystemGestureExclusionRects}
@@ -11603,8 +11604,6 @@
      * a precision touch gesture in a small area in either the X or Y dimension, such as
      * an edge swipe or dragging a <code>SeekBar</code> thumb.</p>
      *
-     * <p>Do not modify the provided list after this method is called.</p>
-     *
      * <p>Note: the system will put a limit of <code>200dp</code> on the vertical extent of the
      * exclusions it takes into account. The limit does not apply while the navigation
      * bar is {@link #SYSTEM_UI_FLAG_IMMERSIVE_STICKY stickily} hidden, nor to the
@@ -11618,13 +11617,17 @@
         if (rects.isEmpty() && mListenerInfo == null) return;
 
         final ListenerInfo info = getListenerInfo();
+        if (info.mSystemGestureExclusionRects != null) {
+            info.mSystemGestureExclusionRects.clear();
+            info.mSystemGestureExclusionRects.addAll(rects);
+        } else {
+            info.mSystemGestureExclusionRects = new ArrayList<>(rects);
+        }
         if (rects.isEmpty()) {
-            info.mSystemGestureExclusionRects = null;
             if (info.mPositionUpdateListener != null) {
                 mRenderNode.removePositionUpdateListener(info.mPositionUpdateListener);
             }
         } else {
-            info.mSystemGestureExclusionRects = rects;
             if (info.mPositionUpdateListener == null) {
                 info.mPositionUpdateListener = new RenderNode.PositionUpdateListener() {
                     @Override
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index afd6878..67cf85c 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -2783,7 +2783,7 @@
                         & WindowManagerGlobal.RELAYOUT_RES_DRAG_RESIZING_DOCKED) != 0;
                 final boolean dragResizing = freeformResizing || dockedResizing;
                 if (mSurfaceControl.isValid()) {
-                    updateOpacity(params, dragResizing);
+                    updateOpacity(mWindowAttributes, dragResizing);
                 }
 
                 if (DEBUG_LAYOUT) Log.v(mTag, "relayout: frame=" + frame.toShortString()
@@ -7749,9 +7749,10 @@
         return relayoutResult;
     }
 
-    private void updateOpacity(@Nullable WindowManager.LayoutParams params, boolean dragResizing) {
+    private void updateOpacity(WindowManager.LayoutParams params, boolean dragResizing) {
         boolean opaque = false;
-        if (params != null && !PixelFormat.formatHasAlpha(params.format)
+
+        if (!PixelFormat.formatHasAlpha(params.format)
                 // Don't make surface with surfaceInsets opaque as they display a
                 // translucent shadow.
                 && params.surfaceInsets.left == 0
diff --git a/core/java/com/android/internal/policy/IKeyguardService.aidl b/core/java/com/android/internal/policy/IKeyguardService.aidl
index b01e4a8..76aa7a0 100644
--- a/core/java/com/android/internal/policy/IKeyguardService.aidl
+++ b/core/java/com/android/internal/policy/IKeyguardService.aidl
@@ -63,8 +63,10 @@
 
      * @param pmWakeReason One of PowerManager.WAKE_REASON_*, detailing the reason we're waking up,
      * such as WAKE_REASON_POWER_BUTTON or WAKE_REASON_GESTURE.
+     * @param cameraGestureTriggered Whether we're waking up due to a power button double tap
+     * gesture.
      */
-    void onStartedWakingUp(int pmWakeReason);
+    void onStartedWakingUp(int pmWakeReason,  boolean cameraGestureTriggered);
 
     /**
      * Called when the device has finished waking up.
diff --git a/core/java/com/android/internal/view/ScrollViewCaptureHelper.java b/core/java/com/android/internal/view/ScrollViewCaptureHelper.java
index a1d202e..a360f63 100644
--- a/core/java/com/android/internal/view/ScrollViewCaptureHelper.java
+++ b/core/java/com/android/internal/view/ScrollViewCaptureHelper.java
@@ -125,10 +125,19 @@
                 view.getScrollX() - contentView.getLeft(),
                 view.getScrollY() - contentView.getTop());
 
+        Rect input = new Rect(requestedContentBounds);
+
+        // Expand input rect to get the requested rect to be in the center
+        int remainingHeight = view.getHeight() - view.getPaddingTop()
+                - view.getPaddingBottom() - input.height();
+        if (remainingHeight > 0) {
+            input.inset(0, -remainingHeight / 2);
+        }
+
         // requestRect is now local to contentView as requestedContentBounds
         // contentView (and each parent in turn if possible) will be scrolled
         // (if necessary) to make all of requestedContent visible, (if possible!)
-        contentView.requestRectangleOnScreen(new Rect(requestedContentBounds), true);
+        contentView.requestRectangleOnScreen(input, true);
 
         // update new offset between starting and current scroll position
         scrollDelta = view.getScrollY() - mStartScrollY;
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 1ddff32..e5f4588 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -1792,6 +1792,9 @@
     <!-- Boolean indicating if placing the phone face down will result in a screen off. -->
     <bool name="config_flipToScreenOffEnabled">true</bool>
 
+    <!-- Integer to set a max latency the accelerometer will batch sensor requests with. -->
+    <integer name="config_flipToScreenOffMaxLatencyMicros">2000000</integer>
+
     <!-- Boolean indicating if current platform supports bluetooth SCO for off call
     use cases -->
     <bool name="config_bluetooth_sco_off_call">true</bool>
@@ -1968,6 +1971,9 @@
     <string name="config_systemActivityRecognizer" translatable="false"></string>
     <!-- The name of the package that will hold the system ui role -->
     <string name="config_systemUi" translatable="false">com.android.systemui</string>
+    <!-- The name of the package that will hold the television remote service role.
+        TODO(b/189347385) make this a @SystemAPI -->
+    <string name="config_systemTelevisionRemoteService" translatable="false">@string/config_tvRemoteServicePackage</string>
 
     <!-- The name of the package that will be allowed to change its components' label/icon. -->
     <string name="config_overrideComponentUiPackage" translatable="false">com.android.stk</string>
diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml
index d334306..ea93520 100644
--- a/core/res/res/values/dimens.xml
+++ b/core/res/res/values/dimens.xml
@@ -939,4 +939,8 @@
     <!-- System-provided padding for inner views on app widgets. The resolved value of this resource may change at runtime. @removed -->
     <dimen name="__removed_system_app_widget_internal_padding">16dp</dimen>
 
+    <!-- The width/height of the icon view on staring surface. -->
+    <dimen name="starting_surface_icon_size">160dp</dimen>
+    <!-- The default width/height of the icon on the spec of adaptive icon drawable. -->
+    <dimen name="starting_surface_default_icon_size">108dp</dimen>
 </resources>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 4da5859..374cea7 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -276,6 +276,7 @@
   <java-symbol type="bool" name="config_avoidGfxAccel" />
   <java-symbol type="bool" name="config_bluetooth_address_validation" />
   <java-symbol type="bool" name="config_flipToScreenOffEnabled" />
+  <java-symbol type="integer" name="config_flipToScreenOffMaxLatencyMicros" />
   <java-symbol type="bool" name="config_bluetooth_sco_off_call" />
   <java-symbol type="bool" name="config_bluetooth_le_peripheral_mode_supported" />
   <java-symbol type="bool" name="config_bluetooth_hfp_inband_ringing_support" />
@@ -4388,4 +4389,7 @@
 
   <java-symbol type="bool" name="config_supportsMicToggle" />
   <java-symbol type="bool" name="config_supportsCamToggle" />
+
+  <java-symbol type="dimen" name="starting_surface_icon_size" />
+  <java-symbol type="dimen" name="starting_surface_default_icon_size" />
 </resources>
diff --git a/core/tests/coretests/src/android/os/VibrationEffectTest.java b/core/tests/coretests/src/android/os/VibrationEffectTest.java
index 009665f..6cbfffc 100644
--- a/core/tests/coretests/src/android/os/VibrationEffectTest.java
+++ b/core/tests/coretests/src/android/os/VibrationEffectTest.java
@@ -102,7 +102,9 @@
         assertThrows(IllegalArgumentException.class,
                 () -> VibrationEffect.createOneShot(1, -2).validate());
         assertThrows(IllegalArgumentException.class,
-                () -> VibrationEffect.createOneShot(1, 256).validate());
+                () -> VibrationEffect.createOneShot(1, 0).validate());
+        assertThrows(IllegalArgumentException.class,
+                () -> VibrationEffect.createOneShot(-1, 255).validate());
     }
 
     @Test
@@ -117,6 +119,7 @@
     @Test
     public void testValidateWaveform() {
         VibrationEffect.createWaveform(TEST_TIMINGS, TEST_AMPLITUDES, -1).validate();
+        VibrationEffect.createWaveform(new long[]{10, 10}, new int[] {0, 0}, -1).validate();
         VibrationEffect.createWaveform(TEST_TIMINGS, TEST_AMPLITUDES, 0).validate();
         VibrationEffect.startWaveform()
                 .addStep(/* amplitude= */ 1, /* duration= */ 10)
diff --git a/core/tests/coretests/src/com/android/internal/view/ScrollViewCaptureHelperTest.java b/core/tests/coretests/src/com/android/internal/view/ScrollViewCaptureHelperTest.java
index ab13fd7d..fc02ea9 100644
--- a/core/tests/coretests/src/com/android/internal/view/ScrollViewCaptureHelperTest.java
+++ b/core/tests/coretests/src/com/android/internal/view/ScrollViewCaptureHelperTest.java
@@ -164,7 +164,8 @@
         assertRectEquals(request, scrollResult.requestedArea);
         assertRectEquals(request, scrollResult.availableArea);
         assertRequestedRectCompletelyVisible(startScrollY, request, getVisibleRect(mContent));
-        assertEquals(CAPTURE_HEIGHT, scrollResult.scrollDelta);
+        assertEquals(CAPTURE_HEIGHT + (WINDOW_HEIGHT - CAPTURE_HEIGHT) / 2,
+                scrollResult.scrollDelta);
     }
 
     @Test
@@ -182,7 +183,8 @@
         assertRectEquals(request, scrollResult.requestedArea);
         assertRectEquals(request, scrollResult.availableArea);
         assertRequestedRectCompletelyVisible(startScrollY, request, getVisibleRect(mContent));
-        assertEquals(-CAPTURE_HEIGHT, scrollResult.scrollDelta);
+        assertEquals(-CAPTURE_HEIGHT - (WINDOW_HEIGHT - CAPTURE_HEIGHT) / 2,
+                scrollResult.scrollDelta);
     }
 
     @Test
@@ -201,7 +203,8 @@
         assertRectEquals(request, scrollResult.requestedArea);
         assertRectEquals(request, scrollResult.availableArea);
         assertRequestedRectCompletelyVisible(startScrollY, request, getVisibleRect(mContent));
-        assertEquals(CAPTURE_HEIGHT, scrollResult.scrollDelta);
+        assertEquals(CAPTURE_HEIGHT + (WINDOW_HEIGHT - CAPTURE_HEIGHT) / 2,
+                scrollResult.scrollDelta);
 
     }
 
@@ -220,7 +223,8 @@
         assertRectEquals(request, scrollResult.requestedArea);
         assertRectEquals(request, scrollResult.availableArea);
         assertRequestedRectCompletelyVisible(startScrollY, request, getVisibleRect(mContent));
-        assertEquals(-CAPTURE_HEIGHT, scrollResult.scrollDelta);
+        assertEquals(-CAPTURE_HEIGHT - (WINDOW_HEIGHT - CAPTURE_HEIGHT) / 2,
+                scrollResult.scrollDelta);
     }
 
     @Test
diff --git a/libs/WindowManager/Shell/res/values/dimen.xml b/libs/WindowManager/Shell/res/values/dimen.xml
index ce8ce51..3caff35 100644
--- a/libs/WindowManager/Shell/res/values/dimen.xml
+++ b/libs/WindowManager/Shell/res/values/dimen.xml
@@ -180,11 +180,6 @@
         individual_bubble_size + some padding. -->
     <dimen name="bubble_stack_user_education_side_inset">72dp</dimen>
 
-    <!-- The width/height of the icon view on staring surface. -->
-    <dimen name="starting_surface_icon_size">160dp</dimen>
-    <!-- The default width/height of the icon on the spec of adaptive icon drawable. -->
-    <dimen name="default_icon_size">108dp</dimen>
-
     <!-- The width/height of the size compat restart button. -->
     <dimen name="size_compat_button_size">48dp</dimen>
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
index 536165b..7e48a7e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
@@ -938,6 +938,8 @@
             if (ev.getAction() == MotionEvent.ACTION_DOWN) {
                 if (mShowingManage) {
                     showManageMenu(false /* show */);
+                } else if (mStackEduView != null && mStackEduView.getVisibility() == VISIBLE) {
+                    mStackEduView.hide(false);
                 } else if (mBubbleData.isExpanded()) {
                     mBubbleData.setExpanded(false);
                 }
@@ -1152,6 +1154,7 @@
             mStackEduView = new StackEducationView(mContext);
             addView(mStackEduView);
         }
+        mBubbleContainer.bringToFront();
         return mStackEduView.show(mPositioner.getDefaultStartPosition());
     }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java
index 093c272..1d37a12 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java
@@ -124,9 +124,9 @@
 
     private void updateDensity() {
         mIconSize = mContext.getResources().getDimensionPixelSize(
-                com.android.wm.shell.R.dimen.starting_surface_icon_size);
+                com.android.internal.R.dimen.starting_surface_icon_size);
         mDefaultIconSize = mContext.getResources().getDimensionPixelSize(
-                com.android.wm.shell.R.dimen.default_icon_size);
+                com.android.internal.R.dimen.starting_surface_default_icon_size);
         mBrandingImageWidth = mContext.getResources().getDimensionPixelSize(
                 com.android.wm.shell.R.dimen.starting_surface_brand_image_width);
         mBrandingImageHeight = mContext.getResources().getDimensionPixelSize(
@@ -135,7 +135,7 @@
                 com.android.wm.shell.R.dimen.starting_surface_exit_animation_window_shift_length);
     }
 
-    private int getSystemBGColor() {
+    private static int getSystemBGColor() {
         final Context systemContext = ActivityThread.currentApplication();
         if (systemContext == null) {
             Slog.e(TAG, "System context does not exist!");
@@ -145,17 +145,18 @@
         return res.getColor(com.android.wm.shell.R.color.splash_window_background_default);
     }
 
-    private Drawable createDefaultBackgroundDrawable() {
+    private static Drawable createDefaultBackgroundDrawable() {
         return new ColorDrawable(getSystemBGColor());
     }
 
-    private @ColorInt int peekWindowBGColor(Context context) {
+    /** Extract the window background color from {@code attrs}. */
+    public static int peekWindowBGColor(Context context, SplashScreenWindowAttrs attrs) {
         Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "peekWindowBGColor");
         final Drawable themeBGDrawable;
-        if (mTmpAttrs.mWindowBgColor != 0) {
-            themeBGDrawable = new ColorDrawable(mTmpAttrs.mWindowBgColor);
-        } else if (mTmpAttrs.mWindowBgResId != 0) {
-            themeBGDrawable = context.getDrawable(mTmpAttrs.mWindowBgResId);
+        if (attrs.mWindowBgColor != 0) {
+            themeBGDrawable = new ColorDrawable(attrs.mWindowBgColor);
+        } else if (attrs.mWindowBgResId != 0) {
+            themeBGDrawable = context.getDrawable(attrs.mWindowBgResId);
         } else {
             themeBGDrawable = createDefaultBackgroundDrawable();
             Slog.w(TAG, "Window background does not exist, using " + themeBGDrawable);
@@ -165,7 +166,7 @@
         return estimatedWindowBGColor;
     }
 
-    private int estimateWindowBGColor(Drawable themeBGDrawable) {
+    private static int estimateWindowBGColor(Drawable themeBGDrawable) {
         final DrawableColorTester themeBGTester =
                 new DrawableColorTester(themeBGDrawable, true /* filterTransparent */);
         if (themeBGTester.nonTransparentRatio() == 0) {
@@ -183,7 +184,7 @@
 
         getWindowAttrs(context, mTmpAttrs);
         final StartingWindowViewBuilder builder = new StartingWindowViewBuilder();
-        final int themeBGColor = peekWindowBGColor(context);
+        final int themeBGColor = peekWindowBGColor(context, this.mTmpAttrs);
         // TODO (b/173975965) Tracking the performance on improved splash screen.
         return builder
                 .setContext(context)
@@ -193,7 +194,11 @@
                 .build();
     }
 
-    private static void getWindowAttrs(Context context, SplashScreenWindowAttrs attrs) {
+    /**
+     * Get the {@link SplashScreenWindowAttrs} from {@code context} and fill them into
+     * {@code attrs}.
+     */
+    public static void getWindowAttrs(Context context, SplashScreenWindowAttrs attrs) {
         final TypedArray typedArray = context.obtainStyledAttributes(
                 com.android.internal.R.styleable.Window);
         attrs.mWindowBgResId = typedArray.getResourceId(R.styleable.Window_windowBackground, 0);
@@ -216,7 +221,8 @@
         }
     }
 
-    private static class SplashScreenWindowAttrs {
+    /** The configuration of the splash screen window. */
+    public static class SplashScreenWindowAttrs {
         private int mWindowBgResId = 0;
         private int mWindowBgColor = Color.TRANSPARENT;
         private Drawable mReplaceIcon = null;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java
index bdc8ff1..ff91d82 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java
@@ -29,6 +29,7 @@
 import android.content.res.Configuration;
 import android.content.res.Resources;
 import android.content.res.TypedArray;
+import android.graphics.PixelFormat;
 import android.graphics.Rect;
 import android.hardware.display.DisplayManager;
 import android.os.IBinder;
@@ -215,6 +216,7 @@
                 WindowManager.LayoutParams.TYPE_APPLICATION_STARTING);
         params.setFitInsetsSides(0);
         params.setFitInsetsTypes(0);
+        params.format = PixelFormat.TRANSLUCENT;
         int windowFlags = WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED
                 | WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS
                 | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
diff --git a/packages/CtsShim/apk/arm/CtsShim.apk b/packages/CtsShim/apk/arm/CtsShim.apk
index ca69a28..bb6dfa3 100644
--- a/packages/CtsShim/apk/arm/CtsShim.apk
+++ b/packages/CtsShim/apk/arm/CtsShim.apk
Binary files differ
diff --git a/packages/CtsShim/apk/arm/CtsShimPriv.apk b/packages/CtsShim/apk/arm/CtsShimPriv.apk
index d7cfb96..2835d57 100644
--- a/packages/CtsShim/apk/arm/CtsShimPriv.apk
+++ b/packages/CtsShim/apk/arm/CtsShimPriv.apk
Binary files differ
diff --git a/packages/CtsShim/apk/x86/CtsShim.apk b/packages/CtsShim/apk/x86/CtsShim.apk
index ca69a28..bb6dfa3 100644
--- a/packages/CtsShim/apk/x86/CtsShim.apk
+++ b/packages/CtsShim/apk/x86/CtsShim.apk
Binary files differ
diff --git a/packages/CtsShim/apk/x86/CtsShimPriv.apk b/packages/CtsShim/apk/x86/CtsShimPriv.apk
index 84c3401..2e1a789 100644
--- a/packages/CtsShim/apk/x86/CtsShimPriv.apk
+++ b/packages/CtsShim/apk/x86/CtsShimPriv.apk
Binary files differ
diff --git a/packages/SettingsLib/CollapsingToolbarBaseActivity/res/values/dimens.xml b/packages/SettingsLib/CollapsingToolbarBaseActivity/res/values/dimens.xml
index f0cdaf6..626fd3a 100644
--- a/packages/SettingsLib/CollapsingToolbarBaseActivity/res/values/dimens.xml
+++ b/packages/SettingsLib/CollapsingToolbarBaseActivity/res/values/dimens.xml
@@ -16,10 +16,12 @@
 -->
 <resources>
     <!-- Collapsing toolbar layout dimensions -->
-    <dimen name="toolbar_one_line_height">226dp</dimen>
-    <dimen name="toolbar_two_lines_height">270dp</dimen>
-    <dimen name="toolbar_three_lines_height">314dp</dimen>
+    <dimen name="toolbar_one_line_height">216dp</dimen>
+    <dimen name="toolbar_two_lines_height">260dp</dimen>
+    <dimen name="toolbar_three_lines_height">304dp</dimen>
     <dimen name="scrim_visible_height_trigger">174dp</dimen>
+    <dimen name="scrim_visible_height_trigger_two_lines">218dp</dimen>
+    <dimen name="scrim_visible_height_trigger_three_lines">262dp</dimen>
     <dimen name="expanded_title_margin_start">24dp</dimen>
     <dimen name="expanded_title_margin_end">24dp</dimen>
 </resources>
\ No newline at end of file
diff --git a/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/AdjustableToolbarLayout.java b/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/AdjustableToolbarLayout.java
index b3053ac..0e7e595 100644
--- a/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/AdjustableToolbarLayout.java
+++ b/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/AdjustableToolbarLayout.java
@@ -26,12 +26,9 @@
 
 import com.google.android.material.appbar.CollapsingToolbarLayout;
 
-import java.lang.reflect.Field;
-import java.lang.reflect.Method;
-
 /**
- * A customized version of CollapsingToolbarLayout that can apply different font size based on
- * the line count of its title.
+ * A customized version of CollapsingToolbarLayout that can apply different font size based on the
+ * line count of its title.
  */
 public class AdjustableToolbarLayout extends CollapsingToolbarLayout {
 
@@ -59,43 +56,25 @@
             public void onLayoutChange(View v, int left, int top, int right, int bottom,
                     int oldLeft, int oldTop, int oldRight, int oldBottom) {
                 v.removeOnLayoutChangeListener(this);
-                final int count = getLineCountWithReflection();
+                final int count = getLineCount();
                 if (count > TOOLBAR_MAX_LINE_NUMBER) {
                     final ViewGroup.LayoutParams lp = getLayoutParams();
                     lp.height = getResources()
                             .getDimensionPixelSize(R.dimen.toolbar_three_lines_height);
+                    setScrimVisibleHeightTrigger(
+                            getResources().getDimensionPixelSize(
+                                    R.dimen.scrim_visible_height_trigger_three_lines));
                     setLayoutParams(lp);
                 } else if (count == TOOLBAR_MAX_LINE_NUMBER) {
                     final ViewGroup.LayoutParams lp = getLayoutParams();
                     lp.height = getResources()
                             .getDimensionPixelSize(R.dimen.toolbar_two_lines_height);
+                    setScrimVisibleHeightTrigger(
+                            getResources().getDimensionPixelSize(
+                                    R.dimen.scrim_visible_height_trigger_two_lines));
                     setLayoutParams(lp);
                 }
             }
         });
     }
-
-    /**
-     * Returns a number of title line count for CollapsingToolbarLayout so that facilitates the
-     * determination to apply what kind of font size. Since the title of CollapsingToolbarLayout is
-     * drawn in a canvas and the text process is wrapped in a CollapsingTextHelper, the way we used
-     * here is to get the line count from the CollapsingTextHelper via Java Reflection.
-     */
-    private int getLineCountWithReflection() {
-        try {
-            final Field textHelperField =
-                    this.getClass().getSuperclass().getDeclaredField("collapsingTextHelper");
-            textHelperField.setAccessible(true);
-            final Object textHelperObj = textHelperField.get(this);
-
-            final Field layoutField = textHelperObj.getClass().getDeclaredField("textLayout");
-            layoutField.setAccessible(true);
-            final Object layoutObj = layoutField.get(textHelperObj);
-
-            final Method method = layoutObj.getClass().getDeclaredMethod("getLineCount");
-            return (int) method.invoke(layoutObj);
-        } catch (Exception e) {
-            return 0;
-        }
-    }
 }
diff --git a/packages/SettingsLib/MainSwitchPreference/res/layout-v31/settingslib_main_switch_bar.xml b/packages/SettingsLib/MainSwitchPreference/res/layout-v31/settingslib_main_switch_bar.xml
index 010b9ba..2d214fe 100644
--- a/packages/SettingsLib/MainSwitchPreference/res/layout-v31/settingslib_main_switch_bar.xml
+++ b/packages/SettingsLib/MainSwitchPreference/res/layout-v31/settingslib_main_switch_bar.xml
@@ -48,7 +48,6 @@
             android:layout_width="@dimen/settingslib_restricted_icon_size"
             android:layout_height="@dimen/settingslib_restricted_icon_size"
             android:tint="?android:attr/colorAccent"
-            android:theme="@android:style/Theme.Material"
             android:layout_gravity="center_vertical"
             android:layout_marginEnd="@dimen/settingslib_restricted_icon_margin_end"
             android:src="@*android:drawable/ic_info"
diff --git a/packages/SettingsLib/SettingsTheme/res/values-v31/colors.xml b/packages/SettingsLib/SettingsTheme/res/values-v31/colors.xml
index c9bc583..89e9a00 100644
--- a/packages/SettingsLib/SettingsTheme/res/values-v31/colors.xml
+++ b/packages/SettingsLib/SettingsTheme/res/values-v31/colors.xml
@@ -15,18 +15,18 @@
   limitations under the License.
   -->
 
-<resources xmlns:androidprv="http://schemas.android.com/apk/prv/res/android">
+<resources>
     <!-- Material next state on color-->
-    <color name="settingslib_state_on_color">?androidprv:attr/colorAccentPrimary</color>
+    <color name="settingslib_state_on_color">@android:color/system_accent1_100</color>
 
     <!-- Material next state off color-->
-    <color name="settingslib_state_off_color">?androidprv:attr/colorAccentSecondary</color>
+    <color name="settingslib_state_off_color">@android:color/system_accent2_100</color>
 
     <!-- Material next thumb off color-->
     <color name="settingslib_thumb_off_color">@android:color/system_neutral2_100</color>
 
     <!-- Material next track on color-->
-    <color name="settingslib_track_on_color">?androidprv:attr/colorAccentPrimaryVariant</color>
+    <color name="settingslib_track_on_color">@android:color/system_accent1_600</color>
 
     <!-- Material next track off color-->
     <color name="settingslib_track_off_color">@android:color/system_neutral2_600</color>
diff --git a/packages/SettingsLib/src/com/android/settingslib/RestrictedPreferenceHelper.java b/packages/SettingsLib/src/com/android/settingslib/RestrictedPreferenceHelper.java
index 2dbfef0..83a6973 100644
--- a/packages/SettingsLib/src/com/android/settingslib/RestrictedPreferenceHelper.java
+++ b/packages/SettingsLib/src/com/android/settingslib/RestrictedPreferenceHelper.java
@@ -139,6 +139,17 @@
     }
 
     /**
+     * @return EnforcedAdmin if we have been passed the restriction in the xml.
+     */
+    public EnforcedAdmin checkRestrictionEnforced() {
+        if (mAttrUserRestriction == null) {
+            return null;
+        }
+        return RestrictedLockUtilsInternal.checkIfRestrictionEnforced(mContext,
+                mAttrUserRestriction, UserHandle.myUserId());
+    }
+
+    /**
      * Disable this preference based on the enforce admin.
      *
      * @param admin details of the admin who enforced the restriction. If it is
diff --git a/packages/SettingsLib/src/com/android/settingslib/enterprise/ActionDisabledByAdminController.java b/packages/SettingsLib/src/com/android/settingslib/enterprise/ActionDisabledByAdminController.java
index 6d7f8df..a537394 100644
--- a/packages/SettingsLib/src/com/android/settingslib/enterprise/ActionDisabledByAdminController.java
+++ b/packages/SettingsLib/src/com/android/settingslib/enterprise/ActionDisabledByAdminController.java
@@ -16,6 +16,7 @@
 
 package com.android.settingslib.enterprise;
 
+import android.annotation.UserIdInt;
 import android.content.Context;
 
 import androidx.annotation.Nullable;
@@ -28,10 +29,15 @@
 public interface ActionDisabledByAdminController {
 
     /**
+     * Sets the {@link ActionDisabledLearnMoreButtonLauncher}.
+     */
+    void initialize(ActionDisabledLearnMoreButtonLauncher launcher);
+
+    /**
      * Handles the adding and setting up of the learn more button. If button is not needed, then
      * this method can be left empty.
      */
-    void setupLearnMoreButton(Context context, Object alertDialogBuilder);
+    void setupLearnMoreButton(Context context);
 
     /**
      * Returns the admin support dialog's title resource id.
@@ -41,11 +47,11 @@
     /**
      * Returns the admin support dialog's content string.
      */
-    CharSequence getAdminSupportContentString(
-            Context context, @Nullable CharSequence supportMessage);
+    CharSequence getAdminSupportContentString(Context context,
+            @Nullable CharSequence supportMessage);
 
     /**
      * Updates the enforced admin
      */
-    void updateEnforcedAdmin(RestrictedLockUtils.EnforcedAdmin admin, int adminUserId);
+    void updateEnforcedAdmin(RestrictedLockUtils.EnforcedAdmin admin, @UserIdInt int adminUserId);
 }
diff --git a/packages/SettingsLib/src/com/android/settingslib/enterprise/ActionDisabledByAdminControllerFactory.java b/packages/SettingsLib/src/com/android/settingslib/enterprise/ActionDisabledByAdminControllerFactory.java
index 7eecd19..da42e33 100644
--- a/packages/SettingsLib/src/com/android/settingslib/enterprise/ActionDisabledByAdminControllerFactory.java
+++ b/packages/SettingsLib/src/com/android/settingslib/enterprise/ActionDisabledByAdminControllerFactory.java
@@ -19,29 +19,30 @@
 import static android.app.admin.DevicePolicyManager.DEVICE_OWNER_TYPE_FINANCED;
 
 import android.app.admin.DevicePolicyManager;
+import android.content.Context;
 
 /**
  * A factory that returns the relevant instance of {@link ActionDisabledByAdminController}.
  */
-public class ActionDisabledByAdminControllerFactory {
+public final class ActionDisabledByAdminControllerFactory {
 
     /**
      * Returns the relevant instance of {@link ActionDisabledByAdminController}.
      */
-    public static ActionDisabledByAdminController createInstance(
-            DevicePolicyManager dpm,
-            ActionDisabledLearnMoreButtonLauncher helper,
-            DeviceAdminStringProvider deviceAdminStringProvider) {
-        if (isFinancedDevice(dpm)) {
-            return new FinancedDeviceActionDisabledByAdminController(
-                    helper, deviceAdminStringProvider);
-        }
-        return new ManagedDeviceActionDisabledByAdminController(
-                helper, deviceAdminStringProvider);
+    public static ActionDisabledByAdminController createInstance(Context context,
+            DeviceAdminStringProvider stringProvider) {
+        return isFinancedDevice(context)
+                ? new FinancedDeviceActionDisabledByAdminController(stringProvider)
+                : new ManagedDeviceActionDisabledByAdminController(stringProvider);
     }
 
-    private static boolean isFinancedDevice(DevicePolicyManager dpm) {
+    private static boolean isFinancedDevice(Context context) {
+        DevicePolicyManager dpm = context.getSystemService(DevicePolicyManager.class);
         return dpm.isDeviceManaged() && dpm.getDeviceOwnerType(
                 dpm.getDeviceOwnerComponentOnAnyUser()) == DEVICE_OWNER_TYPE_FINANCED;
     }
+
+    private ActionDisabledByAdminControllerFactory() {
+        throw new UnsupportedOperationException("provides only static methods");
+    }
 }
diff --git a/packages/SettingsLib/src/com/android/settingslib/enterprise/ActionDisabledLearnMoreButtonLauncher.java b/packages/SettingsLib/src/com/android/settingslib/enterprise/ActionDisabledLearnMoreButtonLauncher.java
index 65b91f1..78a42be 100644
--- a/packages/SettingsLib/src/com/android/settingslib/enterprise/ActionDisabledLearnMoreButtonLauncher.java
+++ b/packages/SettingsLib/src/com/android/settingslib/enterprise/ActionDisabledLearnMoreButtonLauncher.java
@@ -16,29 +16,100 @@
 
 package com.android.settingslib.enterprise;
 
-import android.content.Context;
+import static java.util.Objects.requireNonNull;
 
-import com.android.settingslib.RestrictedLockUtils;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.net.Uri;
+import android.os.UserHandle;
+import android.os.UserManager;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.settingslib.RestrictedLockUtils.EnforcedAdmin;
 
 /**
- * Helper interface meant to set up the "Learn more" button in the action disabled dialog.
+ * Helper class meant to set up the "Learn more" button in the action disabled dialog.
  */
-public interface ActionDisabledLearnMoreButtonLauncher {
+public abstract class ActionDisabledLearnMoreButtonLauncher {
 
     /**
      * Sets up a "learn more" button which shows a screen with device policy settings
      */
-    void setupLearnMoreButtonToShowAdminPolicies(
-            Context context,
-            Object alertDialogBuilder,
-            int enforcementAdminUserId,
-            RestrictedLockUtils.EnforcedAdmin enforcedAdmin);
+    public final void setupLearnMoreButtonToShowAdminPolicies(Context context,
+            int enforcementAdminUserId, EnforcedAdmin enforcedAdmin) {
+        requireNonNull(context, "context cannot be null");
+        requireNonNull(enforcedAdmin, "enforcedAdmin cannot be null");
+
+        // The "Learn more" button appears only if the restriction is enforced by an admin in the
+        // same profile group. Otherwise the admin package and its policies are not accessible to
+        // the current user.
+        if (isSameProfileGroup(context, enforcementAdminUserId)) {
+            setLearnMoreButton(() -> showAdminPolicies(context, enforcedAdmin));
+        }
+    }
 
     /**
      * Sets up a "learn more" button which launches a help page
      */
-    void setupLearnMoreButtonToLaunchHelpPage(
-            Context context,
-            Object alertDialogBuilder,
-            String url);
+    public final void setupLearnMoreButtonToLaunchHelpPage(Context context, String url) {
+        requireNonNull(context, "context cannot be null");
+        requireNonNull(url, "url cannot be null");
+
+        setLearnMoreButton(() -> showHelpPage(context, url));
+    }
+
+    /**
+     * Sets the "learning more" button.
+     *
+     * @param action action to be run when the button is tapped.
+     */
+    public abstract void setLearnMoreButton(Runnable action);
+
+    /**
+     * Launches the settings page with info about the given admin.
+     */
+    protected abstract void launchShowAdminPolicies(Context context, UserHandle user,
+            ComponentName admin);
+
+    /**
+     * Launches the settings page that shows all admins.
+     */
+    protected abstract void launchShowAdminSettings(Context context);
+
+    /**
+     * Callback to finish the activity associated with the launcher.
+     */
+    protected void finishSelf() {
+    }
+
+    @VisibleForTesting
+    protected boolean isSameProfileGroup(Context context, int enforcementAdminUserId) {
+        UserManager um = context.getSystemService(UserManager.class);
+
+        return um.isSameProfileGroup(enforcementAdminUserId, um.getUserHandle());
+    }
+
+    /**
+     * Shows the help page using the given {@code url}.
+     */
+    @VisibleForTesting
+    public void showHelpPage(Context context, String url) {
+        context.startActivityAsUser(createLearnMoreIntent(url), UserHandle.of(context.getUserId()));
+        finishSelf();
+    }
+
+    private void showAdminPolicies(Context context, EnforcedAdmin enforcedAdmin) {
+        if (enforcedAdmin.component != null) {
+            launchShowAdminPolicies(context, enforcedAdmin.user, enforcedAdmin.component);
+        } else {
+            launchShowAdminSettings(context);
+        }
+        finishSelf();
+    }
+
+    private static Intent createLearnMoreIntent(String url) {
+        return new Intent(Intent.ACTION_VIEW, Uri.parse(url)).setFlags(
+                Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
+    }
 }
diff --git a/packages/SettingsLib/src/com/android/settingslib/enterprise/BaseActionDisabledByAdminController.java b/packages/SettingsLib/src/com/android/settingslib/enterprise/BaseActionDisabledByAdminController.java
new file mode 100644
index 0000000..dd71557
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/enterprise/BaseActionDisabledByAdminController.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2021 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.settingslib.enterprise;
+
+import static java.util.Objects.requireNonNull;
+
+import android.annotation.UserIdInt;
+
+import com.android.internal.util.Preconditions;
+import com.android.settingslib.RestrictedLockUtils.EnforcedAdmin;
+
+/**
+ * Base class for {@link ActionDisabledByAdminController} implementations.
+ */
+abstract class BaseActionDisabledByAdminController
+        implements ActionDisabledByAdminController {
+
+    protected @UserIdInt int mEnforcementAdminUserId;
+    protected EnforcedAdmin mEnforcedAdmin;
+    protected ActionDisabledLearnMoreButtonLauncher mLauncher;
+    protected final DeviceAdminStringProvider mStringProvider;
+
+    BaseActionDisabledByAdminController(DeviceAdminStringProvider stringProvider) {
+        mStringProvider = stringProvider;
+    }
+
+    @Override
+    public final void initialize(ActionDisabledLearnMoreButtonLauncher launcher) {
+        mLauncher = requireNonNull(launcher, "launcher cannot be null");
+    }
+
+    @Override
+    public final void updateEnforcedAdmin(EnforcedAdmin admin, int adminUserId) {
+        assertInitialized();
+        mEnforcementAdminUserId = adminUserId;
+        mEnforcedAdmin = requireNonNull(admin, "admin cannot be null");
+    }
+
+    protected final void assertInitialized() {
+        Preconditions.checkState(mLauncher != null, "must call initialize() first");
+    }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/enterprise/FinancedDeviceActionDisabledByAdminController.java b/packages/SettingsLib/src/com/android/settingslib/enterprise/FinancedDeviceActionDisabledByAdminController.java
index cd816e88..2ed0dc4 100644
--- a/packages/SettingsLib/src/com/android/settingslib/enterprise/FinancedDeviceActionDisabledByAdminController.java
+++ b/packages/SettingsLib/src/com/android/settingslib/enterprise/FinancedDeviceActionDisabledByAdminController.java
@@ -16,52 +16,31 @@
 
 package com.android.settingslib.enterprise;
 
-import static java.util.Objects.requireNonNull;
-
-import android.annotation.UserIdInt;
 import android.content.Context;
 
 import androidx.annotation.Nullable;
 
-import com.android.settingslib.RestrictedLockUtils.EnforcedAdmin;
-
 /**
  * An {@link ActionDisabledByAdminController} to be used with financed devices.
  */
-public class FinancedDeviceActionDisabledByAdminController
-        implements ActionDisabledByAdminController {
+final class FinancedDeviceActionDisabledByAdminController
+        extends BaseActionDisabledByAdminController {
 
-    private @UserIdInt int mEnforcementAdminUserId;
-    private EnforcedAdmin mEnforcedAdmin;
-    private final ActionDisabledLearnMoreButtonLauncher mHelper;
-    private final DeviceAdminStringProvider mDeviceAdminStringProvider;
-
-    FinancedDeviceActionDisabledByAdminController(
-            ActionDisabledLearnMoreButtonLauncher helper,
-            DeviceAdminStringProvider deviceAdminStringProvider) {
-        mHelper = requireNonNull(helper, "helper cannot be null");
-        mDeviceAdminStringProvider = requireNonNull(deviceAdminStringProvider,
-                "deviceAdminStringProvider cannot be null");
+    FinancedDeviceActionDisabledByAdminController(DeviceAdminStringProvider stringProvider) {
+        super(stringProvider);
     }
 
     @Override
-    public void updateEnforcedAdmin(EnforcedAdmin admin, int adminUserId) {
-        mEnforcementAdminUserId = adminUserId;
-        mEnforcedAdmin = requireNonNull(admin, "admin cannot be null");
-    }
+    public void setupLearnMoreButton(Context context) {
+        assertInitialized();
 
-    @Override
-    public void setupLearnMoreButton(Context context, Object alertDialogBuilder) {
-        mHelper.setupLearnMoreButtonToShowAdminPolicies(
-                context,
-                alertDialogBuilder,
-                mEnforcementAdminUserId,
+        mLauncher.setupLearnMoreButtonToShowAdminPolicies(context, mEnforcementAdminUserId,
                 mEnforcedAdmin);
     }
 
     @Override
     public String getAdminSupportTitle(@Nullable String restriction) {
-        return mDeviceAdminStringProvider.getDisabledByPolicyTitleForFinancedDevice();
+        return mStringProvider.getDisabledByPolicyTitleForFinancedDevice();
     }
 
     @Override
diff --git a/packages/SettingsLib/src/com/android/settingslib/enterprise/ManagedDeviceActionDisabledByAdminController.java b/packages/SettingsLib/src/com/android/settingslib/enterprise/ManagedDeviceActionDisabledByAdminController.java
index 70e19f9..df6bab7 100644
--- a/packages/SettingsLib/src/com/android/settingslib/enterprise/ManagedDeviceActionDisabledByAdminController.java
+++ b/packages/SettingsLib/src/com/android/settingslib/enterprise/ManagedDeviceActionDisabledByAdminController.java
@@ -16,55 +16,39 @@
 
 package com.android.settingslib.enterprise;
 
-import static java.util.Objects.requireNonNull;
-
-import android.annotation.UserIdInt;
 import android.app.admin.DevicePolicyManager;
 import android.content.Context;
 import android.os.UserManager;
 import android.text.TextUtils;
 
-import com.android.settingslib.RestrictedLockUtils;
+import androidx.annotation.Nullable;
+
 
 /**
  * An {@link ActionDisabledByAdminController} to be used with managed devices.
  */
-class ManagedDeviceActionDisabledByAdminController implements
-        ActionDisabledByAdminController {
-    private @UserIdInt int mEnforcementAdminUserId;
-    private RestrictedLockUtils.EnforcedAdmin mEnforcedAdmin;
-    private final ActionDisabledLearnMoreButtonLauncher mHelper;
-    private final DeviceAdminStringProvider mStringProvider;
+final class ManagedDeviceActionDisabledByAdminController
+        extends BaseActionDisabledByAdminController {
 
-    ManagedDeviceActionDisabledByAdminController(
-            ActionDisabledLearnMoreButtonLauncher helper,
-            DeviceAdminStringProvider stringProvider) {
-        mHelper = requireNonNull(helper);
-        mStringProvider = requireNonNull(stringProvider);
+    ManagedDeviceActionDisabledByAdminController(DeviceAdminStringProvider stringProvider) {
+        super(stringProvider);
     }
 
     @Override
-    public void updateEnforcedAdmin(RestrictedLockUtils.EnforcedAdmin admin, int adminUserId) {
-        mEnforcementAdminUserId = adminUserId;
-        mEnforcedAdmin = requireNonNull(admin, "admin cannot be null");
-    }
+    public void setupLearnMoreButton(Context context) {
+        assertInitialized();
 
-    @Override
-    public void setupLearnMoreButton(Context context, Object alertDialogBuilder) {
         String url = mStringProvider.getLearnMoreHelpPageUrl();
         if (TextUtils.isEmpty(url)) {
-            mHelper.setupLearnMoreButtonToShowAdminPolicies(
-                    context,
-                    alertDialogBuilder,
-                    mEnforcementAdminUserId,
+            mLauncher.setupLearnMoreButtonToShowAdminPolicies(context, mEnforcementAdminUserId,
                     mEnforcedAdmin);
         } else {
-            mHelper.setupLearnMoreButtonToLaunchHelpPage(context, alertDialogBuilder, url);
+            mLauncher.setupLearnMoreButtonToLaunchHelpPage(context, url);
         }
     }
 
     @Override
-    public String getAdminSupportTitle(String restriction) {
+    public String getAdminSupportTitle(@Nullable String restriction) {
         if (restriction == null) {
             return mStringProvider.getDefaultDisabledByPolicyTitle();
         }
@@ -88,9 +72,8 @@
 
     @Override
     public CharSequence getAdminSupportContentString(Context context, CharSequence supportMessage) {
-        if (supportMessage != null) {
-            return supportMessage;
-        }
-        return mStringProvider.getDefaultDisabledByPolicyContent();
+        return supportMessage != null
+                ? supportMessage
+                : mStringProvider.getDefaultDisabledByPolicyContent();
     }
 }
diff --git a/packages/SettingsLib/src/com/android/settingslib/net/DataUsageUtils.java b/packages/SettingsLib/src/com/android/settingslib/net/DataUsageUtils.java
index b1234f2..51e533a 100644
--- a/packages/SettingsLib/src/com/android/settingslib/net/DataUsageUtils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/net/DataUsageUtils.java
@@ -73,6 +73,7 @@
 
     private static NetworkTemplate getMobileTemplateForSubId(
             TelephonyManager telephonyManager, int subId) {
-        return NetworkTemplate.buildTemplateMobileAll(telephonyManager.getSubscriberId(subId));
+        return NetworkTemplate.buildTemplateCarrierMetered(
+                telephonyManager.getSubscriberId(subId));
     }
 }
diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/utils/NetworkPolicyEditorTest.java b/packages/SettingsLib/tests/integ/src/com/android/settingslib/utils/NetworkPolicyEditorTest.java
index 37f2600..7e389a1 100644
--- a/packages/SettingsLib/tests/integ/src/com/android/settingslib/utils/NetworkPolicyEditorTest.java
+++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/utils/NetworkPolicyEditorTest.java
@@ -44,7 +44,7 @@
 
     @Before
     public void setUp() {
-        mNetworkTemplate = NetworkTemplate.buildTemplateMobileAll("123456789123456");
+        mNetworkTemplate = NetworkTemplate.buildTemplateCarrierMetered("123456789123456");
         NetworkPolicyManager policyManager = NetworkPolicyManager.from(InstrumentationRegistry
                 .getContext());
         mNetworkPolicyEditor = new NetworkPolicyEditor(policyManager);
diff --git a/packages/SettingsLib/tests/robotests/Android.bp b/packages/SettingsLib/tests/robotests/Android.bp
index 63cfe59..2d1a516 100644
--- a/packages/SettingsLib/tests/robotests/Android.bp
+++ b/packages/SettingsLib/tests/robotests/Android.bp
@@ -45,6 +45,7 @@
         "SettingsLib-robo-testutils",
         "androidx.test.core",
         "androidx.core_core",
+        "testng", // TODO: remove once JUnit on Android provides assertThrows
     ],
     java_resource_dirs: ["config"],
     instrumentation_for: "SettingsLibShell",
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/enterprise/ActionDisabledByAdminControllerTestUtils.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/enterprise/ActionDisabledByAdminControllerTestUtils.java
index 4b51790..e57335f 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/enterprise/ActionDisabledByAdminControllerTestUtils.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/enterprise/ActionDisabledByAdminControllerTestUtils.java
@@ -16,51 +16,81 @@
 
 package com.android.settingslib.enterprise;
 
-import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
 
-import android.app.Activity;
+import android.content.ComponentName;
 import android.content.Context;
+import android.os.UserHandle;
+import android.util.DebugUtils;
 
-import androidx.appcompat.app.AlertDialog;
-
-import com.android.settingslib.RestrictedLockUtils;
+import com.android.settingslib.RestrictedLockUtils.EnforcedAdmin;
 
 /**
  * Utils related to the action disabled by admin dialogs.
  */
-class ActionDisabledByAdminControllerTestUtils {
-    static final int LEARN_MORE_ACTION_NONE = 0;
-    static final int LEARN_MORE_ACTION_SHOW_ADMIN_POLICIES = 1;
-    static final int LEARN_MORE_ACTION_LAUNCH_HELP_PAGE = 2;
+// NOTE: must be public because of DebugUtils.constantToString() call
+public final class ActionDisabledByAdminControllerTestUtils {
+
+    static final int ENFORCEMENT_ADMIN_USER_ID = 123;
+    static final UserHandle ENFORCEMENT_ADMIN_USER = UserHandle.of(ENFORCEMENT_ADMIN_USER_ID);
+
+    static final String SUPPORT_MESSAGE = "support message";
+
+    static final ComponentName ADMIN_COMPONENT =
+            new ComponentName("some.package.name", "some.package.name.SomeClass");
+    static final EnforcedAdmin ENFORCED_ADMIN = new EnforcedAdmin(
+                    ADMIN_COMPONENT, UserHandle.of(ENFORCEMENT_ADMIN_USER_ID));
+    static final EnforcedAdmin ENFORCED_ADMIN_WITHOUT_COMPONENT = new EnforcedAdmin(
+            /* component= */ null, UserHandle.of(ENFORCEMENT_ADMIN_USER_ID));
+
+    static final String URL = "https://testexample.com";
+
+    // NOTE: fields below must be public because of DebugUtils.constantToString() call
+    public static final int LEARN_MORE_ACTION_NONE = 0;
+    public static final int LEARN_MORE_ACTION_SHOW_ADMIN_POLICIES = 1;
+    public static final int LEARN_MORE_ACTION_SHOW_ADMIN_SETTINGS = 2;
+    public static final int LEARN_MORE_ACTION_LAUNCH_HELP_PAGE = 3;
 
     private int mLearnMoreButtonAction = LEARN_MORE_ACTION_NONE;
 
     ActionDisabledLearnMoreButtonLauncher createLearnMoreButtonLauncher() {
         return new ActionDisabledLearnMoreButtonLauncher() {
+
             @Override
-            public void setupLearnMoreButtonToShowAdminPolicies(Context context,
-                    Object alertDialogBuilder, int enforcementAdminUserId,
-                    RestrictedLockUtils.EnforcedAdmin enforcedAdmin) {
+            public void setLearnMoreButton(Runnable action) {
+                action.run();
+            }
+
+            @Override
+            protected void launchShowAdminPolicies(Context context, UserHandle user,
+                    ComponentName admin) {
                 mLearnMoreButtonAction = LEARN_MORE_ACTION_SHOW_ADMIN_POLICIES;
             }
 
             @Override
-            public void setupLearnMoreButtonToLaunchHelpPage(Context context,
-                    Object alertDialogBuilder, String url) {
+            protected void launchShowAdminSettings(Context context) {
+                mLearnMoreButtonAction = LEARN_MORE_ACTION_SHOW_ADMIN_SETTINGS;
+            }
+
+            @Override
+            public void showHelpPage(Context context, String url) {
                 mLearnMoreButtonAction = LEARN_MORE_ACTION_LAUNCH_HELP_PAGE;
             }
+
+            @Override
+            protected boolean isSameProfileGroup(Context context, int enforcementAdminUserId) {
+                return true;
+            }
         };
     }
 
     void assertLearnMoreAction(int learnMoreActionShowAdminPolicies) {
-        assertThat(learnMoreActionShowAdminPolicies).isEqualTo(mLearnMoreButtonAction);
+        assertWithMessage("action").that(actionToString(mLearnMoreButtonAction))
+                .isEqualTo(actionToString(learnMoreActionShowAdminPolicies));
     }
 
-    AlertDialog createAlertDialog(ActionDisabledByAdminController mController, Activity activity) {
-        AlertDialog.Builder builder = new AlertDialog.Builder(activity);
-        mController.setupLearnMoreButton(activity, builder);
-        AlertDialog alertDialog = builder.create();
-        alertDialog.show();
-        return alertDialog;
+    private static String actionToString(int action) {
+        return DebugUtils.constantToString(ActionDisabledByAdminControllerTestUtils.class,
+                "LEARN_MORE_ACTION_", action);
     }
 }
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/enterprise/ActionDisabledLearnMoreButtonLauncherTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/enterprise/ActionDisabledLearnMoreButtonLauncherTest.java
new file mode 100644
index 0000000..7014da0
--- /dev/null
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/enterprise/ActionDisabledLearnMoreButtonLauncherTest.java
@@ -0,0 +1,175 @@
+/*
+ * Copyright (C) 2021 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.settingslib.enterprise;
+
+import static com.android.settingslib.enterprise.ActionDisabledByAdminControllerTestUtils.ADMIN_COMPONENT;
+import static com.android.settingslib.enterprise.ActionDisabledByAdminControllerTestUtils.ENFORCED_ADMIN;
+import static com.android.settingslib.enterprise.ActionDisabledByAdminControllerTestUtils.ENFORCED_ADMIN_WITHOUT_COMPONENT;
+import static com.android.settingslib.enterprise.ActionDisabledByAdminControllerTestUtils.ENFORCEMENT_ADMIN_USER;
+import static com.android.settingslib.enterprise.ActionDisabledByAdminControllerTestUtils.ENFORCEMENT_ADMIN_USER_ID;
+import static com.android.settingslib.enterprise.ActionDisabledByAdminControllerTestUtils.URL;
+
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+import static org.testng.Assert.assertThrows;
+
+import android.content.Context;
+import android.content.Intent;
+import android.net.Uri;
+import android.os.UserHandle;
+import android.os.UserManager;
+
+import org.junit.Before;
+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.Spy;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+import org.robolectric.RobolectricTestRunner;
+
+@RunWith(RobolectricTestRunner.class)// NOTE: this test doesn't need RoboElectric...
+public final class ActionDisabledLearnMoreButtonLauncherTest {
+
+    private static final int CONTEXT_USER_ID = -ENFORCEMENT_ADMIN_USER_ID;
+    private static final UserHandle CONTEXT_USER = UserHandle.of(CONTEXT_USER_ID);
+
+    @Rule
+    public final MockitoRule mMockitoRule = MockitoJUnit.rule();
+
+    @Mock
+    private Context mContext;
+
+    @Mock
+    private UserManager mUserManager;
+
+    @Spy
+    private ActionDisabledLearnMoreButtonLauncher mLauncher;
+
+    @Captor
+    private ArgumentCaptor<Runnable> mLearnMoreActionCaptor;
+
+    @Captor
+    private ArgumentCaptor<Intent> mIntentCaptor;
+
+    @Before
+    public void setUp() {
+        when(mContext.getUserId()).thenReturn(CONTEXT_USER_ID);
+        when(mUserManager.getUserHandle()).thenReturn(CONTEXT_USER_ID);
+        when(mContext.getSystemService(UserManager.class)).thenReturn(mUserManager);
+    }
+
+    @Test
+    public void testSetupLearnMoreButtonToShowAdminPolicies_nullContext() {
+        assertThrows(NullPointerException.class,
+                () -> mLauncher.setupLearnMoreButtonToShowAdminPolicies(/* context= */ null,
+                        ENFORCEMENT_ADMIN_USER_ID, ENFORCED_ADMIN));
+    }
+
+    @Test
+    public void testSetupLearnMoreButtonToShowAdminPolicies_nullEnforcedAdmin() {
+        assertThrows(NullPointerException.class,
+                () -> mLauncher.setupLearnMoreButtonToShowAdminPolicies(/* context= */ null,
+                        ENFORCEMENT_ADMIN_USER_ID, /* enforcedAdmin= */ null));
+    }
+
+    @Test
+    public void testSetupLearnMoreButtonToShowAdminPolicies_differentProfileGroup() {
+        mockDifferentProfileGroup();
+
+        mLauncher.setupLearnMoreButtonToShowAdminPolicies(mContext, ENFORCEMENT_ADMIN_USER_ID,
+                ENFORCED_ADMIN);
+
+        verify(mLauncher, never()).setLearnMoreButton(any());
+    }
+
+    @Test
+    public void testSetupLearnMoreButtonToShowAdminPolicies_sameProfileGroup_noComponent() {
+        mockSameProfileGroup();
+
+        mLauncher.setupLearnMoreButtonToShowAdminPolicies(mContext, ENFORCEMENT_ADMIN_USER_ID,
+                ENFORCED_ADMIN_WITHOUT_COMPONENT);
+        tapLearnMore();
+
+        verify(mLauncher, never()).launchShowAdminPolicies(any(), any(), any());
+        verify(mLauncher).launchShowAdminSettings(mContext);
+        verifyFinishSelf();
+    }
+
+    @Test
+    public void testSetupLearnMoreButtonToShowAdminPolicies_sameProfileGroup_withComponent() {
+        mockSameProfileGroup();
+
+        mLauncher.setupLearnMoreButtonToShowAdminPolicies(mContext, ENFORCEMENT_ADMIN_USER_ID,
+                ENFORCED_ADMIN);
+        tapLearnMore();
+
+        verify(mLauncher).launchShowAdminPolicies(mContext, ENFORCEMENT_ADMIN_USER,
+                ADMIN_COMPONENT);
+        verify(mLauncher, never()).launchShowAdminSettings(any());
+        verifyFinishSelf();
+    }
+
+    @Test
+    public void testSetupLearnMoreButtonToLaunchHelpPage_nullContext() {
+        assertThrows(NullPointerException.class,
+                () -> mLauncher.setupLearnMoreButtonToLaunchHelpPage(/* context= */ null, URL));
+    }
+
+    @Test
+    public void testSetupLearnMoreButtonToLaunchHelpPage_nullUrl() {
+        assertThrows(NullPointerException.class,
+                () -> mLauncher.setupLearnMoreButtonToLaunchHelpPage(mContext, /* url= */ null));
+    }
+
+    @Test
+    public void testSetupLearnMoreButtonToLaunchHelpPage() {
+        mLauncher.setupLearnMoreButtonToLaunchHelpPage(mContext, URL);
+        tapLearnMore();
+
+        verify(mContext).startActivityAsUser(mIntentCaptor.capture(), eq(CONTEXT_USER));
+        Intent intent = mIntentCaptor.getValue();
+        assertWithMessage("wrong url on intent %s", intent).that(intent.getData())
+                .isEqualTo(Uri.parse(URL));
+        verifyFinishSelf();
+    }
+
+    private void mockDifferentProfileGroup() {
+        // No need to mock anything - isSameProfileGroup() will return false by default
+    }
+
+    private void mockSameProfileGroup() {
+        when(mUserManager.isSameProfileGroup(ENFORCEMENT_ADMIN_USER_ID, CONTEXT_USER_ID))
+                .thenReturn(true);
+    }
+
+    private void tapLearnMore() {
+        verify(mLauncher).setLearnMoreButton(mLearnMoreActionCaptor.capture());
+        mLearnMoreActionCaptor.getValue().run();
+    }
+
+    private void verifyFinishSelf() {
+        verify(mLauncher).finishSelf();
+    }
+}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/enterprise/FakeDeviceAdminStringProvider.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/enterprise/FakeDeviceAdminStringProvider.java
index 8c07d75..be3e9fc 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/enterprise/FakeDeviceAdminStringProvider.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/enterprise/FakeDeviceAdminStringProvider.java
@@ -30,6 +30,8 @@
     static final String DEFAULT_DISABLED_BY_POLICY_CONTENT = "default_disabled_by_policy_content";
     static final String DEFAULT_DISABLED_BY_POLICY_TITLE_FINANCED_DEVICE =
             "default_disabled_by_policy_title_financed_device";
+    static final DeviceAdminStringProvider DEFAULT_DEVICE_ADMIN_STRING_PROVIDER =
+            new FakeDeviceAdminStringProvider(/* url = */ null);
 
     private final String mUrl;
 
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/enterprise/FinancedDeviceActionDisabledByAdminControllerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/enterprise/FinancedDeviceActionDisabledByAdminControllerTest.java
index 2fe2262..7b08fee 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/enterprise/FinancedDeviceActionDisabledByAdminControllerTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/enterprise/FinancedDeviceActionDisabledByAdminControllerTest.java
@@ -16,22 +16,21 @@
 
 package com.android.settingslib.enterprise;
 
+import static com.android.settingslib.enterprise.ActionDisabledByAdminControllerTestUtils.ENFORCED_ADMIN;
+import static com.android.settingslib.enterprise.ActionDisabledByAdminControllerTestUtils.ENFORCEMENT_ADMIN_USER_ID;
 import static com.android.settingslib.enterprise.ActionDisabledByAdminControllerTestUtils.LEARN_MORE_ACTION_SHOW_ADMIN_POLICIES;
+import static com.android.settingslib.enterprise.ActionDisabledByAdminControllerTestUtils.SUPPORT_MESSAGE;
+import static com.android.settingslib.enterprise.FakeDeviceAdminStringProvider.DEFAULT_DEVICE_ADMIN_STRING_PROVIDER;
 import static com.android.settingslib.enterprise.FakeDeviceAdminStringProvider.DEFAULT_DISABLED_BY_POLICY_TITLE_FINANCED_DEVICE;
 
 import static com.google.common.truth.Truth.assertThat;
 
 import android.app.Activity;
-import android.app.Dialog;
-import android.content.ComponentName;
 import android.content.Context;
-import android.os.UserHandle;
 
-import androidx.appcompat.app.AlertDialog;
 import androidx.test.core.app.ApplicationProvider;
 
 import com.android.settingslib.R;
-import com.android.settingslib.RestrictedLockUtils;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -41,73 +40,46 @@
 
 @RunWith(RobolectricTestRunner.class)
 public class FinancedDeviceActionDisabledByAdminControllerTest {
-    private static final int ENFORCEMENT_ADMIN_USER_ID = 123;
-    private static final ComponentName ADMIN_COMPONENT =
-            new ComponentName("some.package.name", "some.package.name.SomeClass");
-    private static final String SUPPORT_MESSAGE = "support message";
-    private static final DeviceAdminStringProvider DEVICE_ADMIN_STRING_PROVIDER =
-            new FakeDeviceAdminStringProvider(/* url = */ null);
-    private static final RestrictedLockUtils.EnforcedAdmin ENFORCED_ADMIN =
-            new RestrictedLockUtils.EnforcedAdmin(
-                    ADMIN_COMPONENT, UserHandle.of(ENFORCEMENT_ADMIN_USER_ID));
 
     private final Context mContext = ApplicationProvider.getApplicationContext();
     private final Activity mActivity = ActivityController.of(new Activity()).get();
     private final ActionDisabledByAdminControllerTestUtils mTestUtils =
             new ActionDisabledByAdminControllerTestUtils();
-    private final ActionDisabledLearnMoreButtonLauncher mLauncher =
-            mTestUtils.createLearnMoreButtonLauncher();
+    private final FinancedDeviceActionDisabledByAdminController mController =
+            new FinancedDeviceActionDisabledByAdminController(
+                    DEFAULT_DEVICE_ADMIN_STRING_PROVIDER);
 
     @Before
     public void setUp() {
         mActivity.setTheme(R.style.Theme_AppCompat_DayNight);
+
+        mController.initialize(mTestUtils.createLearnMoreButtonLauncher());
+        mController.updateEnforcedAdmin(ENFORCED_ADMIN, ENFORCEMENT_ADMIN_USER_ID);
+
     }
 
     @Test
     public void setupLearnMoreButton_negativeButtonSet() {
-        FinancedDeviceActionDisabledByAdminController mController = createController(mLauncher);
-        AlertDialog alertDialog = mTestUtils.createAlertDialog(mController, mActivity);
-
-        alertDialog.getButton(Dialog.BUTTON_NEUTRAL).performClick();
+        mController.setupLearnMoreButton(mContext);
 
         mTestUtils.assertLearnMoreAction(LEARN_MORE_ACTION_SHOW_ADMIN_POLICIES);
     }
 
     @Test
     public void getAdminSupportTitleResource_works() {
-        FinancedDeviceActionDisabledByAdminController mController = createController();
-
         assertThat(mController.getAdminSupportTitle(null))
                 .isEqualTo(DEFAULT_DISABLED_BY_POLICY_TITLE_FINANCED_DEVICE);
     }
 
     @Test
     public void getAdminSupportContentString_withSupportMessage_returnsSupportMessage() {
-        FinancedDeviceActionDisabledByAdminController mController = createController();
-
         assertThat(mController.getAdminSupportContentString(mContext, SUPPORT_MESSAGE))
                 .isEqualTo(SUPPORT_MESSAGE);
     }
 
     @Test
     public void getAdminSupportContentString_noSupportMessage_returnsNull() {
-        FinancedDeviceActionDisabledByAdminController mController = createController();
-
         assertThat(mController.getAdminSupportContentString(mContext, /* supportMessage= */ null))
                 .isNull();
     }
-
-    private FinancedDeviceActionDisabledByAdminController createController() {
-        return createController(mLauncher);
-    }
-
-    private FinancedDeviceActionDisabledByAdminController createController(
-            ActionDisabledLearnMoreButtonLauncher buttonHelper) {
-        FinancedDeviceActionDisabledByAdminController controller =
-                new FinancedDeviceActionDisabledByAdminController(
-                        buttonHelper,
-                        DEVICE_ADMIN_STRING_PROVIDER);
-        controller.updateEnforcedAdmin(ENFORCED_ADMIN, ENFORCEMENT_ADMIN_USER_ID);
-        return controller;
-    }
 }
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/enterprise/ManagedDeviceActionDisabledByAdminControllerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/enterprise/ManagedDeviceActionDisabledByAdminControllerTest.java
index eb1dc96..19f6aa1 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/enterprise/ManagedDeviceActionDisabledByAdminControllerTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/enterprise/ManagedDeviceActionDisabledByAdminControllerTest.java
@@ -16,8 +16,12 @@
 
 package com.android.settingslib.enterprise;
 
+import static com.android.settingslib.enterprise.ActionDisabledByAdminControllerTestUtils.ENFORCED_ADMIN;
+import static com.android.settingslib.enterprise.ActionDisabledByAdminControllerTestUtils.ENFORCEMENT_ADMIN_USER_ID;
 import static com.android.settingslib.enterprise.ActionDisabledByAdminControllerTestUtils.LEARN_MORE_ACTION_LAUNCH_HELP_PAGE;
 import static com.android.settingslib.enterprise.ActionDisabledByAdminControllerTestUtils.LEARN_MORE_ACTION_SHOW_ADMIN_POLICIES;
+import static com.android.settingslib.enterprise.ActionDisabledByAdminControllerTestUtils.SUPPORT_MESSAGE;
+import static com.android.settingslib.enterprise.ActionDisabledByAdminControllerTestUtils.URL;
 import static com.android.settingslib.enterprise.FakeDeviceAdminStringProvider.DEFAULT_DISABLED_BY_POLICY_CONTENT;
 import static com.android.settingslib.enterprise.FakeDeviceAdminStringProvider.DEFAULT_DISABLED_BY_POLICY_TITLE;
 import static com.android.settingslib.enterprise.FakeDeviceAdminStringProvider.DISALLOW_ADJUST_VOLUME_TITLE;
@@ -25,15 +29,12 @@
 import static com.google.common.truth.Truth.assertThat;
 
 import android.app.Activity;
-import android.app.Dialog;
-import android.content.ComponentName;
-import android.os.UserHandle;
+import android.content.Context;
 import android.os.UserManager;
 
-import androidx.appcompat.app.AlertDialog;
+import androidx.test.core.app.ApplicationProvider;
 
 import com.android.settingslib.R;
-import com.android.settingslib.RestrictedLockUtils;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -43,23 +44,15 @@
 
 @RunWith(RobolectricTestRunner.class)
 public class ManagedDeviceActionDisabledByAdminControllerTest {
-    private static final int ENFORCEMENT_ADMIN_USER_ID = 123;
-    private static final ComponentName ADMIN_COMPONENT =
-            new ComponentName("some.package.name", "some.package.name.SomeClass");
-    private static final String SUPPORT_MESSAGE = "support message";
+
     private static final String RESTRICTION = UserManager.DISALLOW_ADJUST_VOLUME;
-    private static final String URL = "https://testexample.com";
     private static final String EMPTY_URL = "";
-    private static final RestrictedLockUtils.EnforcedAdmin ENFORCED_ADMIN =
-            new RestrictedLockUtils.EnforcedAdmin(
-                    ADMIN_COMPONENT, UserHandle.of(ENFORCEMENT_ADMIN_USER_ID));
     private static final String SUPPORT_TITLE_FOR_RESTRICTION = DISALLOW_ADJUST_VOLUME_TITLE;
 
+    private final Context mContext = ApplicationProvider.getApplicationContext();
     private final Activity mActivity = ActivityController.of(new Activity()).get();
     private final ActionDisabledByAdminControllerTestUtils mTestUtils =
             new ActionDisabledByAdminControllerTestUtils();
-    private final ActionDisabledLearnMoreButtonLauncher mLauncher =
-            mTestUtils.createLearnMoreButtonLauncher();
 
     @Before
     public void setUp() {
@@ -68,68 +61,63 @@
 
     @Test
     public void setupLearnMoreButton_validUrl_negativeButtonSet() {
-        ManagedDeviceActionDisabledByAdminController mController =
-                createController(mLauncher, URL);
-        AlertDialog alertDialog = mTestUtils.createAlertDialog(mController, mActivity);
+        ManagedDeviceActionDisabledByAdminController controller = createController(URL);
 
-        alertDialog.getButton(Dialog.BUTTON_NEUTRAL).performClick();
+        controller.setupLearnMoreButton(mContext);
 
         mTestUtils.assertLearnMoreAction(LEARN_MORE_ACTION_LAUNCH_HELP_PAGE);
     }
 
     @Test
     public void setupLearnMoreButton_noUrl_negativeButtonSet() {
-        ManagedDeviceActionDisabledByAdminController mController =
-                createController(mLauncher, EMPTY_URL);
-        AlertDialog alertDialog = mTestUtils.createAlertDialog(mController, mActivity);
+        ManagedDeviceActionDisabledByAdminController controller = createController(EMPTY_URL);
 
-        alertDialog.getButton(Dialog.BUTTON_NEUTRAL).performClick();
+        controller.setupLearnMoreButton(mContext);
 
         mTestUtils.assertLearnMoreAction(LEARN_MORE_ACTION_SHOW_ADMIN_POLICIES);
     }
 
     @Test
     public void getAdminSupportTitleResource_noRestriction_works() {
-        ManagedDeviceActionDisabledByAdminController mController = createController();
+        ManagedDeviceActionDisabledByAdminController controller = createController();
 
-        assertThat(mController.getAdminSupportTitle(null))
+        assertThat(controller.getAdminSupportTitle(null))
                 .isEqualTo(DEFAULT_DISABLED_BY_POLICY_TITLE);
     }
 
     @Test
     public void getAdminSupportTitleResource_withRestriction_works() {
-        ManagedDeviceActionDisabledByAdminController mController = createController();
+        ManagedDeviceActionDisabledByAdminController controller = createController();
 
-        assertThat(mController.getAdminSupportTitle(RESTRICTION))
+        assertThat(controller.getAdminSupportTitle(RESTRICTION))
                 .isEqualTo(SUPPORT_TITLE_FOR_RESTRICTION);
     }
 
     @Test
     public void getAdminSupportContentString_withSupportMessage_returnsSupportMessage() {
-        ManagedDeviceActionDisabledByAdminController mController = createController();
+        ManagedDeviceActionDisabledByAdminController controller = createController();
 
-        assertThat(mController.getAdminSupportContentString(mActivity, SUPPORT_MESSAGE))
+        assertThat(controller.getAdminSupportContentString(mActivity, SUPPORT_MESSAGE))
                 .isEqualTo(SUPPORT_MESSAGE);
     }
 
     @Test
     public void getAdminSupportContentString_noSupportMessage_returnsDefault() {
-        ManagedDeviceActionDisabledByAdminController mController = createController();
+        ManagedDeviceActionDisabledByAdminController controller = createController();
 
-        assertThat(mController.getAdminSupportContentString(mActivity, /* supportMessage= */ null))
+        assertThat(controller.getAdminSupportContentString(mActivity, /* supportMessage= */ null))
                 .isEqualTo(DEFAULT_DISABLED_BY_POLICY_CONTENT);
     }
 
     private ManagedDeviceActionDisabledByAdminController createController() {
-        return createController(mLauncher, /* url= */ null);
+        return createController(/* url= */ null);
     }
 
-    private ManagedDeviceActionDisabledByAdminController createController(
-            ActionDisabledLearnMoreButtonLauncher buttonHelper, String url) {
+    private ManagedDeviceActionDisabledByAdminController createController(String url) {
         ManagedDeviceActionDisabledByAdminController controller =
                 new ManagedDeviceActionDisabledByAdminController(
-                        buttonHelper,
                         new FakeDeviceAdminStringProvider(url));
+        controller.initialize(mTestUtils.createLearnMoreButtonLauncher());
         controller.updateEnforcedAdmin(ENFORCED_ADMIN, ENFORCEMENT_ADMIN_USER_ID);
         return controller;
     }
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/net/DataUsageControllerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/net/DataUsageControllerTest.java
index 27d877d..9be783d 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/net/DataUsageControllerTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/net/DataUsageControllerTest.java
@@ -87,8 +87,8 @@
         ShadowSubscriptionManager.setDefaultDataSubscriptionId(mDefaultSubscriptionId);
         doReturn(SUB_ID).when(mTelephonyManager).getSubscriberId();
 
-        mNetworkTemplate = NetworkTemplate.buildTemplateMobileAll(SUB_ID);
-        mNetworkTemplate2 = NetworkTemplate.buildTemplateMobileAll(SUB_ID_2);
+        mNetworkTemplate = NetworkTemplate.buildTemplateCarrierMetered(SUB_ID);
+        mNetworkTemplate2 = NetworkTemplate.buildTemplateCarrierMetered(SUB_ID_2);
         mWifiNetworkTemplate = NetworkTemplate.buildTemplateWifi(
                 NetworkTemplate.WIFI_NETWORKID_ALL, null);
     }
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/net/NetworkCycleDataForUidLoaderTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/net/NetworkCycleDataForUidLoaderTest.java
index 877eb61..e8d5844 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/net/NetworkCycleDataForUidLoaderTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/net/NetworkCycleDataForUidLoaderTest.java
@@ -62,7 +62,7 @@
         when(mContext.getSystemService(Context.NETWORK_POLICY_SERVICE))
                 .thenReturn(mNetworkPolicyManager);
         when(mNetworkPolicyManager.getNetworkPolicies()).thenReturn(new NetworkPolicy[0]);
-        mNetworkTemplate = NetworkTemplate.buildTemplateMobileAll(SUB_ID);
+        mNetworkTemplate = NetworkTemplate.buildTemplateCarrierMetered(SUB_ID);
     }
 
     @Test
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
index bce576d..dd9a6ee 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
@@ -310,20 +310,29 @@
 
     private static final Set<String> sAllSecureSettings = new ArraySet<>();
     private static final Set<String> sReadableSecureSettings = new ArraySet<>();
+    private static final ArrayMap<String, Integer> sReadableSecureSettingsWithMaxTargetSdk =
+            new ArrayMap<>();
     static {
-        Settings.Secure.getPublicSettings(sAllSecureSettings, sReadableSecureSettings);
+        Settings.Secure.getPublicSettings(sAllSecureSettings, sReadableSecureSettings,
+                sReadableSecureSettingsWithMaxTargetSdk);
     }
 
     private static final Set<String> sAllSystemSettings = new ArraySet<>();
     private static final Set<String> sReadableSystemSettings = new ArraySet<>();
+    private static final ArrayMap<String, Integer> sReadableSystemSettingsWithMaxTargetSdk =
+            new ArrayMap<>();
     static {
-        Settings.System.getPublicSettings(sAllSystemSettings, sReadableSystemSettings);
+        Settings.System.getPublicSettings(sAllSystemSettings, sReadableSystemSettings,
+                sReadableSystemSettingsWithMaxTargetSdk);
     }
 
     private static final Set<String> sAllGlobalSettings = new ArraySet<>();
     private static final Set<String> sReadableGlobalSettings = new ArraySet<>();
+    private static final ArrayMap<String, Integer> sReadableGlobalSettingsWithMaxTargetSdk =
+            new ArrayMap<>();
     static {
-        Settings.Global.getPublicSettings(sAllGlobalSettings, sReadableGlobalSettings);
+        Settings.Global.getPublicSettings(sAllGlobalSettings, sReadableGlobalSettings,
+                sReadableGlobalSettingsWithMaxTargetSdk);
     }
 
     private final Object mLock = new Object();
@@ -2065,7 +2074,7 @@
         }
         if ((ai.flags & ApplicationInfo.FLAG_TEST_ONLY) == 0) {
             // Skip checking readable annotations for test_only apps
-            checkReadableAnnotation(settingsType, settingName);
+            checkReadableAnnotation(settingsType, settingName, ai.targetSandboxVersion);
         }
         /**
          * some settings need additional permission check, this is to have a matching security
@@ -2101,35 +2110,55 @@
     /**
      * Check if the target settings key is readable. Reject if the caller app is trying to access a
      * settings key defined in the Settings.Secure, Settings.System or Settings.Global and is not
-     * annotated as @Readable.
+     * annotated as @Readable. Reject if the caller app is targeting an SDK level that is higher
+     * than the maxTargetSdk specified in the @Readable annotation.
      * Notice that a key string that is not defined in any of the Settings.* classes will still be
      * regarded as readable.
      */
-    private void checkReadableAnnotation(int settingsType, String settingName) {
+    private void checkReadableAnnotation(int settingsType, String settingName,
+            int targetSdkVersion) {
         final Set<String> allFields;
         final Set<String> readableFields;
+        final ArrayMap<String, Integer> readableFieldsWithMaxTargetSdk;
         switch (settingsType) {
             case SETTINGS_TYPE_GLOBAL:
                 allFields = sAllGlobalSettings;
                 readableFields = sReadableGlobalSettings;
+                readableFieldsWithMaxTargetSdk = sReadableGlobalSettingsWithMaxTargetSdk;
                 break;
             case SETTINGS_TYPE_SYSTEM:
                 allFields = sAllSystemSettings;
                 readableFields = sReadableSystemSettings;
+                readableFieldsWithMaxTargetSdk = sReadableSystemSettingsWithMaxTargetSdk;
                 break;
             case SETTINGS_TYPE_SECURE:
                 allFields = sAllSecureSettings;
                 readableFields = sReadableSecureSettings;
+                readableFieldsWithMaxTargetSdk = sReadableSecureSettingsWithMaxTargetSdk;
                 break;
             default:
                 throw new IllegalArgumentException("Invalid settings type: " + settingsType);
         }
 
-        if (allFields.contains(settingName) && !readableFields.contains(settingName)) {
-            throw new SecurityException(
-                    "Settings key: <" + settingName + "> is not readable. From S+, settings keys "
-                            + "annotated with @hide are restricted to system_server and system "
-                            + "apps only, unless they are annotated with @Readable.");
+        if (allFields.contains(settingName)) {
+            if (!readableFields.contains(settingName)) {
+                throw new SecurityException(
+                        "Settings key: <" + settingName + "> is not readable. From S+, settings "
+                                + "keys annotated with @hide are restricted to system_server and "
+                                + "system apps only, unless they are annotated with @Readable."
+                );
+            } else {
+                if (readableFieldsWithMaxTargetSdk.containsKey(settingName)) {
+                    final int maxTargetSdk = readableFieldsWithMaxTargetSdk.get(settingName);
+                    if (targetSdkVersion > maxTargetSdk) {
+                        throw new SecurityException(
+                                "Settings key: <" + settingName + "> is only readable to apps with "
+                                        + "targetSdkVersion lower than or equal to: "
+                                        + maxTargetSdk
+                        );
+                    }
+                }
+            }
         }
     }
 
diff --git a/packages/SystemUI/animation/Android.bp b/packages/SystemUI/animation/Android.bp
index 1b15d20..761b1f4 100644
--- a/packages/SystemUI/animation/Android.bp
+++ b/packages/SystemUI/animation/Android.bp
@@ -36,6 +36,7 @@
 
     static_libs: [
         "PluginCoreLib",
+        "WindowManager-Shell",
     ],
 
     manifest: "AndroidManifest.xml",
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt
index 4f179c4..d185ba36 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt
@@ -5,13 +5,18 @@
 import android.animation.ValueAnimator
 import android.app.ActivityManager
 import android.app.ActivityTaskManager
+import android.app.AppGlobals
 import android.app.PendingIntent
 import android.content.Context
 import android.graphics.Matrix
+import android.graphics.PorterDuff
+import android.graphics.PorterDuffXfermode
 import android.graphics.Rect
 import android.graphics.RectF
+import android.graphics.drawable.GradientDrawable
 import android.os.Looper
 import android.os.RemoteException
+import android.os.UserHandle
 import android.util.Log
 import android.util.MathUtils
 import android.view.IRemoteAnimationFinishedCallback
@@ -26,6 +31,8 @@
 import android.view.animation.PathInterpolator
 import com.android.internal.annotations.VisibleForTesting
 import com.android.internal.policy.ScreenDecorationsUtils
+import com.android.wm.shell.startingsurface.SplashscreenContentDrawer
+import com.android.wm.shell.startingsurface.SplashscreenContentDrawer.SplashScreenWindowAttrs
 import kotlin.math.roundToInt
 
 /**
@@ -40,9 +47,9 @@
 
     companion object {
         const val ANIMATION_DURATION = 500L
-        const val ANIMATION_DURATION_FADE_OUT_CONTENT = 183L
-        const val ANIMATION_DURATION_FADE_IN_WINDOW = 217L
-        const val ANIMATION_DELAY_FADE_IN_WINDOW = 167L
+        private const val ANIMATION_DURATION_FADE_OUT_CONTENT = 150L
+        private const val ANIMATION_DURATION_FADE_IN_WINDOW = 183L
+        private const val ANIMATION_DELAY_FADE_IN_WINDOW = ANIMATION_DURATION_FADE_OUT_CONTENT
         private const val ANIMATION_DURATION_NAV_FADE_IN = 266L
         private const val ANIMATION_DURATION_NAV_FADE_OUT = 133L
         private const val ANIMATION_DELAY_NAV_FADE_IN =
@@ -54,6 +61,8 @@
         private val NAV_FADE_IN_INTERPOLATOR = PathInterpolator(0f, 0f, 0f, 1f)
         private val NAV_FADE_OUT_INTERPOLATOR = PathInterpolator(0.2f, 0f, 1f, 1f)
 
+        private val SRC_MODE = PorterDuffXfermode(PorterDuff.Mode.SRC)
+
         /**
          * Given the [linearProgress] of a launch animation, return the linear progress of the
          * sub-animation starting [delay] ms after the launch animation and that lasts [duration].
@@ -68,6 +77,8 @@
         }
     }
 
+    private val packageManager = AppGlobals.getPackageManager()
+
     /** The interpolator used for the width, height, Y position and corner radius. */
     private val animationInterpolator = AnimationUtils.loadInterpolator(context,
             R.interpolator.launch_animation_interpolator_y)
@@ -76,6 +87,8 @@
     private val animationInterpolatorX = AnimationUtils.loadInterpolator(context,
             R.interpolator.launch_animation_interpolator_x)
 
+    private val cornerRadii = FloatArray(8)
+
     /**
      * Start an intent and animate the opening window. The intent will be started by running
      * [intentStarter], which should use the provided [RemoteAnimationAdapter] and return the launch
@@ -288,10 +301,7 @@
         var right: Int,
 
         var topCornerRadius: Float = 0f,
-        var bottomCornerRadius: Float = 0f,
-
-        var contentAlpha: Float = 1f,
-        var backgroundAlpha: Float = 1f
+        var bottomCornerRadius: Float = 0f
     ) {
         private val startTop = top
         private val startBottom = bottom
@@ -331,6 +341,9 @@
 
         val centerY: Float
             get() = top + height / 2f
+
+        /** Whether the expanded view should be visible or hidden. */
+        var visible: Boolean = true
     }
 
     @VisibleForTesting
@@ -452,22 +465,39 @@
                 0f
             }
 
+            // We add an extra layer with the same color as the app splash screen background color,
+            // which is usually the same color of the app background. We first fade in this layer
+            // to hide the expanding view, then we fade it out with SRC mode to draw a hole in the
+            // launch container and reveal the opening window.
+            val windowBackgroundColor = extractSplashScreenBackgroundColor(window)
+            val windowBackgroundLayer = GradientDrawable().apply {
+                setColor(windowBackgroundColor)
+                alpha = 0
+            }
+
             // Update state.
             val animator = ValueAnimator.ofFloat(0f, 1f)
             this.animator = animator
             animator.duration = ANIMATION_DURATION
             animator.interpolator = Interpolators.LINEAR
 
+            val launchContainerOverlay = launchContainer.overlay
             animator.addListener(object : AnimatorListenerAdapter() {
                 override fun onAnimationStart(animation: Animator?, isReverse: Boolean) {
                     Log.d(TAG, "Animation started")
                     controller.onLaunchAnimationStart(isExpandingFullyAbove)
+
+                    // Add the drawable to the launch container overlay. Overlays always draw
+                    // drawables after views, so we know that it will be drawn above any view added
+                    // by the controller.
+                    launchContainerOverlay.add(windowBackgroundLayer)
                 }
 
                 override fun onAnimationEnd(animation: Animator?) {
                     Log.d(TAG, "Animation ended")
                     iCallback?.invoke()
                     controller.onLaunchAnimationEnd(isExpandingFullyAbove)
+                    launchContainerOverlay.remove(windowBackgroundLayer)
                 }
             })
 
@@ -491,24 +521,61 @@
                 state.bottomCornerRadius =
                     MathUtils.lerp(startBottomCornerRadius, endRadius, progress)
 
-                val contentAlphaProgress = getProgress(linearProgress, 0,
-                        ANIMATION_DURATION_FADE_OUT_CONTENT)
-                state.contentAlpha =
-                        1 - CONTENT_FADE_OUT_INTERPOLATOR.getInterpolation(contentAlphaProgress)
-
-                val backgroundAlphaProgress = getProgress(linearProgress,
-                        ANIMATION_DELAY_FADE_IN_WINDOW, ANIMATION_DURATION_FADE_IN_WINDOW)
-                state.backgroundAlpha =
-                        1 - WINDOW_FADE_IN_INTERPOLATOR.getInterpolation(backgroundAlphaProgress)
+                // The expanding view can/should be hidden once it is completely coverred by the
+                // windowBackgroundLayer.
+                state.visible =
+                        getProgress(linearProgress, 0, ANIMATION_DURATION_FADE_OUT_CONTENT) < 1
 
                 applyStateToWindow(window, state)
+                applyStateToWindowBackgroundLayer(windowBackgroundLayer, state, linearProgress)
                 navigationBar?.let { applyStateToNavigationBar(it, state, linearProgress) }
+
+                // If we started expanding the view, we make it 1 pixel smaller on all sides to
+                // avoid artefacts on the corners caused by anti-aliasing of the view background and
+                // the window background layer.
+                if (state.top != startTop && state.left != startLeft &&
+                        state.bottom != startBottom && state.right != startRight) {
+                    state.top += 1
+                    state.left += 1
+                    state.right -= 1
+                    state.bottom -= 1
+                }
                 controller.onLaunchAnimationProgress(state, progress, linearProgress)
             }
 
             animator.start()
         }
 
+        /** Extract the background color of the app splash screen. */
+        private fun extractSplashScreenBackgroundColor(window: RemoteAnimationTarget): Int {
+            val taskInfo = window.taskInfo
+            val windowPackage = taskInfo.topActivity.packageName
+            val userId = taskInfo.userId
+            val windowContext = context.createPackageContextAsUser(
+                    windowPackage, Context.CONTEXT_RESTRICTED, UserHandle.of(userId))
+            val activityInfo = taskInfo.topActivityInfo
+            val splashScreenThemeName = packageManager.getSplashScreenTheme(windowPackage, userId)
+            val splashScreenThemeId = if (splashScreenThemeName != null) {
+                windowContext.resources.getIdentifier(splashScreenThemeName, null, null)
+            } else {
+                0
+            }
+
+            val themeResId = when {
+                splashScreenThemeId != 0 -> splashScreenThemeId
+                activityInfo.themeResource != 0 -> activityInfo.themeResource
+                else -> com.android.internal.R.style.Theme_DeviceDefault_DayNight
+            }
+
+            if (themeResId != windowContext.themeResId) {
+                windowContext.setTheme(themeResId)
+            }
+
+            val windowAttrs = SplashScreenWindowAttrs()
+            SplashscreenContentDrawer.getWindowAttrs(windowContext, windowAttrs)
+            return SplashscreenContentDrawer.peekWindowBGColor(windowContext, windowAttrs)
+        }
+
         private fun applyStateToWindow(window: RemoteAnimationTarget, state: State) {
             val screenBounds = window.screenSpaceBounds
             val centerX = (screenBounds.left + screenBounds.right) / 2f
@@ -563,6 +630,41 @@
             transactionApplier.scheduleApply(params)
         }
 
+        private fun applyStateToWindowBackgroundLayer(
+            drawable: GradientDrawable,
+            state: State,
+            linearProgress: Float
+        ) {
+            // Update position.
+            drawable.setBounds(state.left, state.top, state.right, state.bottom)
+
+            // Update radius.
+            cornerRadii[0] = state.topCornerRadius
+            cornerRadii[1] = state.topCornerRadius
+            cornerRadii[2] = state.topCornerRadius
+            cornerRadii[3] = state.topCornerRadius
+            cornerRadii[4] = state.bottomCornerRadius
+            cornerRadii[5] = state.bottomCornerRadius
+            cornerRadii[6] = state.bottomCornerRadius
+            cornerRadii[7] = state.bottomCornerRadius
+            drawable.cornerRadii = cornerRadii
+
+            // We first fade in the background layer to hide the expanding view, then fade it out
+            // with SRC mode to draw a hole punch in the status bar and reveal the opening window.
+            val fadeInProgress = getProgress(linearProgress, 0, ANIMATION_DURATION_FADE_OUT_CONTENT)
+            if (fadeInProgress < 1) {
+                val alpha = CONTENT_FADE_OUT_INTERPOLATOR.getInterpolation(fadeInProgress)
+                drawable.alpha = (alpha * 0xFF).roundToInt()
+                drawable.setXfermode(null)
+            } else {
+                val fadeOutProgress = getProgress(linearProgress,
+                        ANIMATION_DELAY_FADE_IN_WINDOW, ANIMATION_DURATION_FADE_IN_WINDOW)
+                val alpha = 1 - WINDOW_FADE_IN_INTERPOLATOR.getInterpolation(fadeOutProgress)
+                drawable.alpha = (alpha * 0xFF).roundToInt()
+                drawable.setXfermode(SRC_MODE)
+            }
+        }
+
         private fun applyStateToNavigationBar(
             navigationBar: RemoteAnimationTarget,
             state: State,
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewLaunchAnimatorController.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewLaunchAnimatorController.kt
index ce9feed..4b655a1 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewLaunchAnimatorController.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewLaunchAnimatorController.kt
@@ -4,8 +4,6 @@
 import android.graphics.ColorFilter
 import android.graphics.Matrix
 import android.graphics.PixelFormat
-import android.graphics.PorterDuff
-import android.graphics.PorterDuffXfermode
 import android.graphics.Rect
 import android.graphics.drawable.Drawable
 import android.graphics.drawable.GradientDrawable
@@ -111,9 +109,7 @@
     }
 
     override fun onLaunchAnimationStart(isExpandingFullyAbove: Boolean) {
-        backgroundView = FrameLayout(launchContainer.context).apply {
-            forceHasOverlappingRendering(false)
-        }
+        backgroundView = FrameLayout(launchContainer.context)
         launchContainerOverlay.add(backgroundView)
 
         // We wrap the ghosted view background and use it to draw the expandable background. Its
@@ -125,9 +121,7 @@
 
         // Create a ghost of the view that will be moving and fading out. This allows to fade out
         // the content before fading out the background.
-        ghostView = GhostView.addGhost(ghostedView, launchContainer).apply {
-            setLayerType(View.LAYER_TYPE_HARDWARE, null)
-        }
+        ghostView = GhostView.addGhost(ghostedView, launchContainer)
 
         val matrix = ghostView?.animationMatrix ?: Matrix.IDENTITY_MATRIX
         matrix.getValues(initialGhostViewMatrixValues)
@@ -139,7 +133,18 @@
         linearProgress: Float
     ) {
         val ghostView = this.ghostView!!
-        ghostView.alpha = state.contentAlpha
+        val backgroundView = this.backgroundView!!
+
+        if (!state.visible) {
+            if (ghostView.visibility == View.VISIBLE) {
+                // Making the ghost view invisible will make the ghosted view visible, so order is
+                // important here.
+                ghostView.visibility = View.INVISIBLE
+                ghostedView.visibility = View.INVISIBLE
+                backgroundView.visibility = View.INVISIBLE
+            }
+            return
+        }
 
         val scale = min(state.widthRatio, state.heightRatio)
         ghostViewMatrix.setValues(initialGhostViewMatrixValues)
@@ -150,14 +155,12 @@
         )
         ghostView.animationMatrix = ghostViewMatrix
 
-        val backgroundView = this.backgroundView!!
         backgroundView.top = state.top
         backgroundView.bottom = state.bottom
         backgroundView.left = state.left
         backgroundView.right = state.right
 
         val backgroundDrawable = backgroundDrawable!!
-        backgroundDrawable.alpha = (0xFF * state.backgroundAlpha).toInt()
         backgroundDrawable.wrapped?.let {
             setBackgroundCornerRadius(it, state.topCornerRadius, state.bottomCornerRadius)
         }
@@ -168,6 +171,7 @@
 
         GhostView.removeGhost(ghostedView)
         launchContainerOverlay.remove(backgroundView)
+        ghostedView.visibility = View.VISIBLE
         ghostedView.invalidate()
     }
 
@@ -203,10 +207,6 @@
     }
 
     private class WrappedDrawable(val wrapped: Drawable?) : Drawable() {
-        companion object {
-            private val SRC_MODE = PorterDuffXfermode(PorterDuff.Mode.SRC)
-        }
-
         private var currentAlpha = 0xFF
         private var previousBounds = Rect()
 
@@ -220,7 +220,6 @@
 
             wrapped.alpha = currentAlpha
             wrapped.bounds = bounds
-            setXfermode(wrapped, SRC_MODE)
             applyBackgroundRadii()
 
             wrapped.draw(canvas)
@@ -230,7 +229,6 @@
             // background.
             wrapped.alpha = 0
             wrapped.bounds = previousBounds
-            setXfermode(wrapped, null)
             restoreBackgroundRadii()
         }
 
@@ -257,27 +255,6 @@
             wrapped?.colorFilter = filter
         }
 
-        private fun setXfermode(background: Drawable, mode: PorterDuffXfermode?) {
-            if (background is InsetDrawable) {
-                background.drawable?.let { setXfermode(it, mode) }
-                return
-            }
-
-            if (background !is LayerDrawable) {
-                background.setXfermode(mode)
-                return
-            }
-
-            // We set the xfermode on the first layer that is not a mask. Most of the time it will
-            // be the "background layer".
-            for (i in 0 until background.numberOfLayers) {
-                if (background.getId(i) != android.R.id.mask) {
-                    background.getDrawable(i).setXfermode(mode)
-                    break
-                }
-            }
-        }
-
         fun setBackgroundRadius(topCornerRadius: Float, bottomCornerRadius: Float) {
             updateRadii(cornerRadii, topCornerRadius, bottomCornerRadius)
             invalidateSelf()
diff --git a/packages/SystemUI/docs/qs-tiles.md b/packages/SystemUI/docs/qs-tiles.md
index 5216209..89c28a0 100644
--- a/packages/SystemUI/docs/qs-tiles.md
+++ b/packages/SystemUI/docs/qs-tiles.md
@@ -305,6 +305,7 @@
     * Inject a `Provider` for the tile created before.
     * Add a case to the `switch` with a unique String spec for the chosen tile.
 5. In [SystemUI/res/values/config.xml](/packages/SystemUI/res/values/config.xml), modify `quick_settings_tiles_stock` and add the spec defined in the previous step. If necessary, add it also to `quick_settings_tiles_default`. The first one contains a list of all the tiles that SystemUI knows how to create (to show to the user in the customization screen). The second one contains only the default tiles that the user will experience on a fresh boot or after they reset their tiles.
+6. In [SystemUI/res/values/tiles_states_strings.xml](/packages/SystemUI/res/values/tiles_states_strings.xml), add a new array for your tile. The name has to be `tile_states_<spec>`. Use a good description to help the translators.
 
 #### Abstract methods in QSTileImpl
 
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSTile.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSTile.java
index f1c1477..77018d7 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSTile.java
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSTile.java
@@ -166,11 +166,13 @@
         public boolean handlesLongClick = true;
         public boolean showRippleEffect = true;
         public Drawable sideViewCustomDrawable;
+        public String spec;
 
         public boolean copyTo(State other) {
             if (other == null) throw new IllegalArgumentException();
             if (!other.getClass().equals(getClass())) throw new IllegalArgumentException();
-            final boolean changed = !Objects.equals(other.icon, icon)
+            final boolean changed = !Objects.equals(other.spec, spec)
+                    || !Objects.equals(other.icon, icon)
                     || !Objects.equals(other.iconSupplier, iconSupplier)
                     || !Objects.equals(other.label, label)
                     || !Objects.equals(other.secondaryLabel, secondaryLabel)
@@ -188,6 +190,7 @@
                     || !Objects.equals(other.handlesLongClick, handlesLongClick)
                     || !Objects.equals(other.showRippleEffect, showRippleEffect)
                     || !Objects.equals(other.sideViewCustomDrawable, sideViewCustomDrawable);
+            other.spec = spec;
             other.icon = icon;
             other.iconSupplier = iconSupplier;
             other.label = label;
@@ -216,6 +219,7 @@
         // This string may be used for CTS testing of tiles, so removing elements is discouraged.
         protected StringBuilder toStringBuilder() {
             final StringBuilder sb = new StringBuilder(getClass().getSimpleName()).append('[');
+            sb.append("spec=").append(spec);
             sb.append(",icon=").append(icon);
             sb.append(",iconSupplier=").append(iconSupplier);
             sb.append(",label=").append(label);
diff --git a/packages/SystemUI/res/layout/accessibility_floating_menu_item.xml b/packages/SystemUI/res/layout/accessibility_floating_menu_item.xml
index f7357b2..2067f85 100644
--- a/packages/SystemUI/res/layout/accessibility_floating_menu_item.xml
+++ b/packages/SystemUI/res/layout/accessibility_floating_menu_item.xml
@@ -19,8 +19,8 @@
     xmlns:android="http://schemas.android.com/apk/res/android"
     android:layout_width="match_parent"
     android:layout_height="wrap_content"
-    android:paddingStart="@dimen/accessibility_floating_menu_padding"
-    android:paddingEnd="@dimen/accessibility_floating_menu_padding"
+    android:paddingStart="@dimen/accessibility_floating_menu_small_padding"
+    android:paddingEnd="@dimen/accessibility_floating_menu_small_padding"
     android:orientation="vertical"
     android:gravity="center">
 
@@ -29,9 +29,4 @@
         android:layout_width="@dimen/accessibility_floating_menu_small_width_height"
         android:layout_height="@dimen/accessibility_floating_menu_small_width_height"/>
 
-    <View
-        android:id="@+id/transparent_divider"
-        android:layout_width="match_parent"
-        android:layout_height="@dimen/accessibility_floating_menu_padding"/>
-
 </LinearLayout>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/keyguard_bottom_area.xml b/packages/SystemUI/res/layout/keyguard_bottom_area.xml
index 0dc1473..80b7d1f 100644
--- a/packages/SystemUI/res/layout/keyguard_bottom_area.xml
+++ b/packages/SystemUI/res/layout/keyguard_bottom_area.xml
@@ -53,6 +53,7 @@
             android:paddingEnd="@dimen/keyguard_indication_text_padding"
             android:textAppearance="@style/TextAppearance.Keyguard.BottomArea"
             android:alpha=".8"
+            android:accessibilityLiveRegion="polite"
             android:visibility="gone"/>
 
     </LinearLayout>
diff --git a/packages/SystemUI/res/layout/privacy_dialog.xml b/packages/SystemUI/res/layout/privacy_dialog.xml
index 720ae8db..459fb66 100644
--- a/packages/SystemUI/res/layout/privacy_dialog.xml
+++ b/packages/SystemUI/res/layout/privacy_dialog.xml
@@ -24,8 +24,6 @@
     android:layout_marginEnd="@dimen/ongoing_appops_dialog_side_margins"
     android:layout_marginTop="8dp"
     android:orientation="vertical"
-    android:paddingLeft="@dimen/ongoing_appops_dialog_side_padding"
-    android:paddingRight="@dimen/ongoing_appops_dialog_side_padding"
     android:paddingBottom="12dp"
     android:paddingTop="8dp"
     android:background="@drawable/qs_dialog_bg"
diff --git a/packages/SystemUI/res/layout/privacy_dialog_item.xml b/packages/SystemUI/res/layout/privacy_dialog_item.xml
index b91fb29c..7c8945e 100644
--- a/packages/SystemUI/res/layout/privacy_dialog_item.xml
+++ b/packages/SystemUI/res/layout/privacy_dialog_item.xml
@@ -23,6 +23,9 @@
     android:orientation="horizontal"
     android:layout_marginTop="4dp"
     android:importantForAccessibility="yes"
+    android:background="?android:attr/selectableItemBackground"
+    android:paddingLeft="@dimen/ongoing_appops_dialog_side_padding"
+    android:paddingRight="@dimen/ongoing_appops_dialog_side_padding"
     android:focusable="true"
     >
     <!-- 4dp marginTop makes 20dp minimum between icons -->
diff --git a/packages/SystemUI/res/layout/qs_tile_side_icon.xml b/packages/SystemUI/res/layout/qs_tile_side_icon.xml
index 1ae0a1c..f1b7259 100644
--- a/packages/SystemUI/res/layout/qs_tile_side_icon.xml
+++ b/packages/SystemUI/res/layout/qs_tile_side_icon.xml
@@ -23,7 +23,7 @@
     <ImageView
         android:id="@+id/customDrawable"
         android:layout_width="wrap_content"
-        android:layout_height="@dimen/qs_icon_size"
+        android:layout_height="@dimen/qs_side_view_size"
         android:layout_marginEnd="@dimen/qs_drawable_end_margin"
         android:adjustViewBounds="true"
         android:scaleType="fitCenter"
@@ -39,4 +39,4 @@
         android:visibility="gone"
         android:importantForAccessibility="no"
     />
-</FrameLayout>
\ No newline at end of file
+</FrameLayout>
diff --git a/packages/SystemUI/res/layout/status_bar_expanded.xml b/packages/SystemUI/res/layout/status_bar_expanded.xml
index fb39d3e..dbbf641 100644
--- a/packages/SystemUI/res/layout/status_bar_expanded.xml
+++ b/packages/SystemUI/res/layout/status_bar_expanded.xml
@@ -64,18 +64,6 @@
         android:clipToPadding="false"
         android:clipChildren="false">
 
-        <com.android.systemui.scrim.ScrimView
-            android:id="@+id/scrim_notifications"
-            android:layout_width="0dp"
-            android:layout_height="0dp"
-            android:importantForAccessibility="no"
-            systemui:ignoreRightInset="true"
-            systemui:layout_constraintStart_toStartOf="parent"
-            systemui:layout_constraintEnd_toEndOf="parent"
-            systemui:layout_constraintTop_toTopOf="parent"
-            systemui:layout_constraintBottom_toBottomOf="parent"
-        />
-
         <include
             layout="@layout/keyguard_status_view"
             android:visibility="gone"/>
diff --git a/packages/SystemUI/res/layout/super_notification_shade.xml b/packages/SystemUI/res/layout/super_notification_shade.xml
index 08284a0..bea50e8 100644
--- a/packages/SystemUI/res/layout/super_notification_shade.xml
+++ b/packages/SystemUI/res/layout/super_notification_shade.xml
@@ -51,6 +51,14 @@
         sysui:ignoreRightInset="true"
         />
 
+    <com.android.systemui.scrim.ScrimView
+        android:id="@+id/scrim_notifications"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:importantForAccessibility="no"
+        sysui:ignoreRightInset="true"
+        />
+
     <com.android.systemui.statusbar.LightRevealScrim
             android:id="@+id/light_reveal_scrim"
             android:layout_width="match_parent"
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index ab606ba..0be648f 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -561,6 +561,7 @@
     <dimen name="qs_tile_icon_background_stroke_width">-1dp</dimen>
     <dimen name="qs_tile_background_size">56dp</dimen>
     <dimen name="qs_icon_size">20dp</dimen>
+    <dimen name="qs_side_view_size">28dp</dimen>
     <dimen name="qs_label_container_margin">10dp</dimen>
     <dimen name="qs_quick_tile_size">60dp</dimen>
     <dimen name="qs_tile_padding">12dp</dimen>
@@ -1467,16 +1468,17 @@
     <dimen name="medium_content_padding_above_name">4dp</dimen>
 
     <!-- Accessibility floating menu -->
-    <dimen name="accessibility_floating_menu_elevation">5dp</dimen>
+    <dimen name="accessibility_floating_menu_elevation">3dp</dimen>
     <dimen name="accessibility_floating_menu_stroke_width">1dp</dimen>
     <dimen name="accessibility_floating_menu_stroke_inset">-2dp</dimen>
     <dimen name="accessibility_floating_menu_margin">16dp</dimen>
-    <dimen name="accessibility_floating_menu_padding">6dp</dimen>
+    <dimen name="accessibility_floating_menu_small_padding">6dp</dimen>
     <dimen name="accessibility_floating_menu_small_width_height">36dp</dimen>
     <dimen name="accessibility_floating_menu_small_single_radius">25dp</dimen>
     <dimen name="accessibility_floating_menu_small_multiple_radius">20dp</dimen>
+    <dimen name="accessibility_floating_menu_large_padding">8dp</dimen>
     <dimen name="accessibility_floating_menu_large_width_height">56dp</dimen>
-    <dimen name="accessibility_floating_menu_large_single_radius">33dp</dimen>
+    <dimen name="accessibility_floating_menu_large_single_radius">35dp</dimen>
     <dimen name="accessibility_floating_menu_large_multiple_radius">35dp</dimen>
 
     <dimen name="accessibility_floating_tooltip_arrow_width">8dp</dimen>
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index aadcaba..6a848fb 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -199,11 +199,11 @@
         <item name="android:textSize">@dimen/qs_tile_text_size</item>
         <item name="android:letterSpacing">0.01</item>
         <item name="android:lineHeight">20sp</item>
-        <item name="android:fontFamily">@*android:string/config_headlineFontFamilyMedium</item>
+        <item name="android:fontFamily">@*android:string/config_bodyFontFamilyMedium</item>
     </style>
 
     <style name="TextAppearance.QS.TileLabel.Secondary">
-        <item name="android:fontFamily">@*android:string/config_headlineFontFamily</item>
+        <item name="android:fontFamily">@*android:string/config_bodyFontFamily</item>
     </style>
 
     <style name="TextAppearance.QS.UserSwitcher">
@@ -223,7 +223,7 @@
     </style>
 
     <style name="TextAppearance.QS.Status">
-        <item name="android:fontFamily">@*android:string/config_headlineFontFamilyMedium</item>
+        <item name="android:fontFamily">@*android:string/config_bodyFontFamilyMedium</item>
         <item name="android:textColor">?android:attr/textColorPrimary</item>
         <item name="android:textSize">14sp</item>
         <item name="android:letterSpacing">0.01</item>
@@ -231,20 +231,19 @@
     </style>
 
     <style name="TextAppearance.QS.SecurityFooter" parent="@style/TextAppearance.QS.Status">
-        <item name="android:fontFamily">@*android:string/config_headlineFontFamily</item>
+        <item name="android:fontFamily">@*android:string/config_bodyFontFamily</item>
         <item name="android:textColor">?android:attr/textColorSecondary</item>
     </style>
 
-    <style name="TextAppearance.QS.Status.Carriers">
-        <item name="android:fontFamily">@*android:string/config_headlineFontFamily</item>
-    </style>
+    <style name="TextAppearance.QS.Status.Carriers" />
 
     <style name="TextAppearance.QS.Status.Carriers.NoCarrierText">
+        <item name="android:fontFamily">@*android:string/config_bodyFontFamily</item>
         <item name="android:textColor">?android:attr/textColorSecondary</item>
     </style>
 
     <style name="TextAppearance.QS.Status.Build">
-        <item name="android:fontFamily">@*android:string/config_headlineFontFamily</item>
+        <item name="android:fontFamily">@*android:string/config_bodyFontFamily</item>
         <item name="android:textColor">?android:attr/textColorSecondary</item>
     </style>
 
diff --git a/packages/SystemUI/res/values/tiles_states_strings.xml b/packages/SystemUI/res/values/tiles_states_strings.xml
new file mode 100644
index 0000000..5ac7c1d
--- /dev/null
+++ b/packages/SystemUI/res/values/tiles_states_strings.xml
@@ -0,0 +1,284 @@
+<!--
+  ~ Copyright (C) 2021 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 resources set the default subtitle for tiles. This way, each tile can be translated
+     separately.
+     The indices in the array correspond to the state values in QSTile:
+      * STATE_UNAVAILABLE
+      * STATE_INACTIVE
+      * STATE_ACTIVE
+     This subtitle is shown when the tile is in that particular state but does not set its own
+     subtitle, so some of these may never appear on screen. They should still be translated as if
+     they could appear.
+-->
+<resources>
+    <!-- Default names for tiles states: unavailable, off, on.
+         This subtitle is shown when the tile is in that particular state but does not set its own
+         subtitle, so some of these may never appear on screen. They should still be translated as
+         if they could appear. [CHAR LIMIT=32] -->
+    <string-array name="tile_states_default">
+        <item>@string/tile_unavailable</item>
+        <item>@string/switch_bar_off</item>
+        <item>@string/switch_bar_on</item>
+    </string-array>
+
+    <!-- State names for internet tile: unavailable, off, on.
+         This subtitle is shown when the tile is in that particular state but does not set its own
+         subtitle, so some of these may never appear on screen. They should still be translated as
+         if they could appear. [CHAR LIMIT=32] -->
+    <string-array name="tile_states_internet">
+        <item>@string/tile_unavailable</item>
+        <item>@string/switch_bar_off</item>
+        <item>@string/switch_bar_on</item>
+    </string-array>
+
+    <!-- State names for wifi tile: unavailable, off, on.
+         This subtitle is shown when the tile is in that particular state but does not set its own
+         subtitle, so some of these may never appear on screen. They should still be translated as
+         if they could appear. [CHAR LIMIT=32] -->
+    <string-array name="tile_states_wifi">
+        <item>@string/tile_unavailable</item>
+        <item>@string/switch_bar_off</item>
+        <item>@string/switch_bar_on</item>
+    </string-array>
+
+    <!-- State names for cell (data) tile: unavailable, off, on.
+         This subtitle is shown when the tile is in that particular state but does not set its own
+         subtitle, so some of these may never appear on screen. They should still be translated as
+         if they could appear.[CHAR LIMIT=32] -->
+    <string-array name="tile_states_cell">
+        <item>@string/tile_unavailable</item>
+        <item>@string/switch_bar_off</item>
+        <item>@string/switch_bar_on</item>
+    </string-array>
+
+    <!-- State names for battery (saver) tile: unavailable, off, on.
+         This subtitle is shown when the tile is in that particular state but does not set its own
+         subtitle, so some of these may never appear on screen. They should still be translated as
+         if they could appear. [CHAR LIMIT=32] -->
+    <string-array name="tile_states_battery">
+        <item>@string/tile_unavailable</item>
+        <item>@string/switch_bar_off</item>
+        <item>@string/switch_bar_on</item>
+    </string-array>
+
+    <!-- State names for dnd (Do not disturb) tile: unavailable, off, on.
+         This subtitle is shown when the tile is in that particular state but does not set its own
+         subtitle, so some of these may never appear on screen. They should still be translated as
+         if they could appear. [CHAR LIMIT=32] -->
+    <string-array name="tile_states_dnd">
+        <item>@string/tile_unavailable</item>
+        <item>@string/switch_bar_off</item>
+        <item>@string/switch_bar_on</item>
+    </string-array>
+
+    <!-- State names for flashlight tile: unavailable, off, on.
+         This subtitle is shown when the tile is in that particular state but does not set its own
+         subtitle, so some of these may never appear on screen. They should still be translated as
+         if they could appear. [CHAR LIMIT=32] -->
+    <string-array name="tile_states_flashlight">
+        <item>@string/tile_unavailable</item>
+        <item>@string/switch_bar_off</item>
+        <item>@string/switch_bar_on</item>
+    </string-array>
+
+    <!-- State names for rotation (lock) tile: unavailable, off, on.
+         This subtitle is shown when the tile is in that particular state but does not set its own
+         subtitle, so some of these may never appear on screen. They should still be translated as
+         if they could appear. [CHAR LIMIT=32] -->
+    <string-array name="tile_states_rotation">
+        <item>@string/tile_unavailable</item>
+        <item>@string/switch_bar_off</item>
+        <item>@string/switch_bar_on</item>
+    </string-array>
+
+    <!-- State names for bt (bluetooth) tile: unavailable, off, on.
+         This subtitle is shown when the tile is in that particular state but does not set its own
+         subtitle, so some of these may never appear on screen. They should still be translated as
+         if they could appear. [CHAR LIMIT=32] -->
+    <string-array name="tile_states_bt">
+        <item>@string/tile_unavailable</item>
+        <item>@string/switch_bar_off</item>
+        <item>@string/switch_bar_on</item>
+    </string-array>
+
+    <!-- State names for airplane tile: unavailable, off, on [CHAR LIMIT=32] -->
+    <string-array name="tile_states_airplane">
+        <item>@string/tile_unavailable</item>
+        <item>@string/switch_bar_off</item>
+        <item>@string/switch_bar_on</item>
+    </string-array>
+
+    <!-- State names for location tile: unavailable, off, on.
+         This subtitle is shown when the tile is in that particular state but does not set its own
+         subtitle, so some of these may never appear on screen. They should still be translated as
+         if they could appear. [CHAR LIMIT=32] -->
+    <string-array name="tile_states_location">
+        <item>@string/tile_unavailable</item>
+        <item>@string/switch_bar_off</item>
+        <item>@string/switch_bar_on</item>
+    </string-array>
+
+    <!-- State names for hotspot tile: unavailable, off, on.
+         This subtitle is shown when the tile is in that particular state but does not set its own
+         subtitle, so some of these may never appear on screen. They should still be translated as
+         if they could appear. [CHAR LIMIT=32] -->
+    <string-array name="tile_states_hotspot">
+        <item>@string/tile_unavailable</item>
+        <item>@string/switch_bar_off</item>
+        <item>@string/switch_bar_on</item>
+    </string-array>
+
+    <!-- State names for (color) inversion tile: unavailable, off, on.
+         This subtitle is shown when the tile is in that particular state but does not set its own
+         subtitle, so some of these may never appear on screen. They should still be translated as
+         if they could appear. [CHAR LIMIT=32] -->
+    <string-array name="tile_states_inversion">
+        <item>@string/tile_unavailable</item>
+        <item>@string/switch_bar_off</item>
+        <item>@string/switch_bar_on</item>
+    </string-array>
+
+    <!-- State names for (data) saver tile: unavailable, off, on.
+         This subtitle is shown when the tile is in that particular state but does not set its own
+         subtitle, so some of these may never appear on screen. They should still be translated as
+         if they could appear. [CHAR LIMIT=32] -->
+    <string-array name="tile_states_saver">
+        <item>@string/tile_unavailable</item>
+        <item>@string/switch_bar_off</item>
+        <item>@string/switch_bar_on</item>
+    </string-array>
+
+    <!-- State names for dark (mode) tile: unavailable, off, on.
+         This subtitle is shown when the tile is in that particular state but does not set its own
+         subtitle, so some of these may never appear on screen. They should still be translated as
+         if they could appear. [CHAR LIMIT=32] -->
+    <string-array name="tile_states_dark">
+        <item>@string/tile_unavailable</item>
+        <item>@string/switch_bar_off</item>
+        <item>@string/switch_bar_on</item>
+    </string-array>
+
+    <!-- State names for work (mode) tile: unavailable, off, on.
+         This subtitle is shown when the tile is in that particular state but does not set its own
+         subtitle, so some of these may never appear on screen. They should still be translated as
+         if they could appear. [CHAR LIMIT=32] -->
+    <string-array name="tile_states_work">
+        <item>@string/tile_unavailable</item>
+        <item>@string/switch_bar_off</item>
+        <item>@string/switch_bar_on</item>
+    </string-array>
+
+    <!-- State names for cast tile: unavailable, off, on.
+         This subtitle is shown when the tile is in that particular state but does not set its own
+         subtitle, so some of these may never appear on screen. They should still be translated as
+         if they could appear. [CHAR LIMIT=32] -->
+    <string-array name="tile_states_cast">
+        <item>@string/tile_unavailable</item>
+        <item>@string/switch_bar_off</item>
+        <item>@string/switch_bar_on</item>
+    </string-array>
+
+    <!-- State names for night (light) tile: unavailable, off, on.
+         This subtitle is shown when the tile is in that particular state but does not set its own
+         subtitle, so some of these may never appear on screen. They should still be translated as
+         if they could appear. [CHAR LIMIT=32] -->
+    <string-array name="tile_states_night">
+        <item>@string/tile_unavailable</item>
+        <item>@string/switch_bar_off</item>
+        <item>@string/switch_bar_on</item>
+    </string-array>
+
+    <!-- State names for screenrecord tile: unavailable, off, on.
+         This subtitle is shown when the tile is in that particular state but does not set its own
+         subtitle, so some of these may never appear on screen. They should still be translated as
+         if they could appear. [CHAR LIMIT=32] -->
+    <string-array name="tile_states_screenrecord">
+        <item>@string/tile_unavailable</item>
+        <item>@string/switch_bar_off</item>
+        <item>@string/switch_bar_on</item>
+    </string-array>
+
+    <!-- State names for reverse (charging) tile: unavailable, off, on.
+         This subtitle is shown when the tile is in that particular state but does not set its own
+         subtitle, so some of these may never appear on screen. They should still be translated as
+         if they could appear. [CHAR LIMIT=32] -->
+    <string-array name="tile_states_reverse">
+        <item>@string/tile_unavailable</item>
+        <item>@string/switch_bar_off</item>
+        <item>@string/switch_bar_on</item>
+    </string-array>
+
+    <!-- State names for reduce_brightness tile: unavailable, off, on.
+         This subtitle is shown when the tile is in that particular state but does not set its own
+         subtitle, so some of these may never appear on screen. They should still be translated as
+         if they could appear. [CHAR LIMIT=32] -->
+    <string-array name="tile_states_reduce_brightness">
+        <item>@string/tile_unavailable</item>
+        <item>@string/switch_bar_off</item>
+        <item>@string/switch_bar_on</item>
+    </string-array>
+
+    <!-- State names for cameratoggle tile: unavailable, off, on.
+         This subtitle is shown when the tile is in that particular state but does not set its own
+         subtitle, so some of these may never appear on screen. They should still be translated as
+         if they could appear.[CHAR LIMIT=32] -->
+    <string-array name="tile_states_cameratoggle">
+        <item>@string/tile_unavailable</item>
+        <item>@string/switch_bar_off</item>
+        <item>@string/switch_bar_on</item>
+    </string-array>
+
+    <!-- State names for mictoggle tile: unavailable, off, on.
+         This subtitle is shown when the tile is in that particular state but does not set its own
+         subtitle, so some of these may never appear on screen. They should still be translated as
+         if they could appear. [CHAR LIMIT=32] -->
+    <string-array name="tile_states_mictoggle">
+        <item>@string/tile_unavailable</item>
+        <item>@string/switch_bar_off</item>
+        <item>@string/switch_bar_on</item>
+    </string-array>
+
+    <!-- State names for (home) controls tile: unavailable, off, on.
+         This subtitle is shown when the tile is in that particular state but does not set its own
+         subtitle, so some of these may never appear on screen. They should still be translated as
+         if they could appear. [CHAR LIMIT=32] -->
+    <string-array name="tile_states_controls">
+        <item>@string/tile_unavailable</item>
+        <item>@string/switch_bar_off</item>
+        <item>@string/switch_bar_on</item>
+    </string-array>
+
+    <!-- State names for (quick access) wallet tile: unavailable, off, on.
+         This subtitle is shown when the tile is in that particular state but does not set its own
+         subtitle, so some of these may never appear on screen. They should still be translated as
+         if they could appear. [CHAR LIMIT=32] -->
+    <string-array name="tile_states_wallet">
+        <item>@string/tile_unavailable</item>
+        <item>@string/switch_bar_off</item>
+        <item>@string/switch_bar_on</item>
+    </string-array>
+
+    <!-- State names for alarm tile: unavailable, off, on.
+         This subtitle is shown when the tile is in that particular state but does not set its own
+         subtitle, so some of these may never appear on screen. They should still be translated as
+         if they could appear. [CHAR LIMIT=32] -->
+    <string-array name="tile_states_alarm">
+        <item>@string/tile_unavailable</item>
+        <item>@string/switch_bar_off</item>
+        <item>@string/switch_bar_on</item>
+    </string-array>
+</resources>
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/keyguard/AnimatableClockController.java b/packages/SystemUI/src/com/android/keyguard/AnimatableClockController.java
index 7cb4846..92af58e 100644
--- a/packages/SystemUI/src/com/android/keyguard/AnimatableClockController.java
+++ b/packages/SystemUI/src/com/android/keyguard/AnimatableClockController.java
@@ -107,6 +107,21 @@
         }
     };
 
+    private final StatusBarStateController.StateListener mStatusBarStatePersistentListener =
+            new StatusBarStateController.StateListener() {
+                @Override
+                public void onDozeAmountChanged(float linear, float eased) {
+                    boolean noAnimation = (mDozeAmount == 0f && linear == 1f)
+                            || (mDozeAmount == 1f && linear == 0f);
+                    boolean isDozing = linear > mDozeAmount;
+                    mDozeAmount = linear;
+                    if (mIsDozing != isDozing) {
+                        mIsDozing = isDozing;
+                        mView.animateDoze(mIsDozing, !noAnimation);
+                    }
+                }
+            };
+
     private final KeyguardUpdateMonitorCallback mKeyguardUpdateMonitorCallback =
             new KeyguardUpdateMonitorCallback() {
         @Override
@@ -133,14 +148,15 @@
         updateLocale();
         mBroadcastDispatcher.registerReceiver(mLocaleBroadcastReceiver,
                 new IntentFilter(Intent.ACTION_LOCALE_CHANGED));
-        mStatusBarStateController.addCallback(mStatusBarStateListener);
-
         mIsDozing = mStatusBarStateController.isDozing();
         mDozeAmount = mStatusBarStateController.getDozeAmount();
         mBatteryController.addCallback(mBatteryCallback);
         mKeyguardUpdateMonitor.registerCallback(mKeyguardUpdateMonitorCallback);
         mKeyguardShowing = true;
 
+        mStatusBarStateController.removeCallback(mStatusBarStatePersistentListener);
+        mStatusBarStateController.addCallback(mStatusBarStatePersistentListener);
+
         refreshTime();
         initColors();
         mView.animateDoze(mIsDozing, false);
@@ -149,9 +165,11 @@
     @Override
     protected void onViewDetached() {
         mBroadcastDispatcher.unregisterReceiver(mLocaleBroadcastReceiver);
-        mStatusBarStateController.removeCallback(mStatusBarStateListener);
         mKeyguardUpdateMonitor.removeCallback(mKeyguardUpdateMonitorCallback);
         mBatteryController.removeCallback(mBatteryCallback);
+        if (!mView.isAttachedToWindow()) {
+            mStatusBarStateController.removeCallback(mStatusBarStatePersistentListener);
+        }
     }
 
     /** Animate the clock appearance */
@@ -199,19 +217,4 @@
         mView.setColors(mDozingColor, mLockScreenColor);
         mView.animateDoze(mIsDozing, false);
     }
-
-    private final StatusBarStateController.StateListener mStatusBarStateListener =
-            new StatusBarStateController.StateListener() {
-                @Override
-                public void onDozeAmountChanged(float linear, float eased) {
-                    boolean noAnimation = (mDozeAmount == 0f && linear == 1f)
-                            || (mDozeAmount == 1f && linear == 0f);
-                    boolean isDozing = linear > mDozeAmount;
-                    mDozeAmount = linear;
-                    if (mIsDozing != isDozing) {
-                        mIsDozing = isDozing;
-                        mView.animateDoze(mIsDozing, !noAnimation);
-                    }
-                }
-            };
 }
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
index 3e084b7..fbea1e9 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
@@ -305,10 +305,6 @@
             // know that it should re-position our SmartSpace.
             if (mKeyguardUnlockAnimationController.isUnlockingWithSmartSpaceTransition()) {
                 mKeyguardUnlockAnimationController.updateLockscreenSmartSpacePosition();
-            } else {
-                // Otherwise, reset Y translation in case it's still offset from a previous shared
-                // element transition.
-                ((View) mSmartspaceView).setTranslationY(0f);
             }
         }
 
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
index 7b6514a..e8cc5c8 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
@@ -27,6 +27,7 @@
 import com.android.systemui.statusbar.notification.stack.AnimationProperties;
 import com.android.systemui.statusbar.notification.stack.StackStateAnimator;
 import com.android.systemui.statusbar.phone.DozeParameters;
+import com.android.systemui.statusbar.phone.UnlockedScreenOffAnimationController;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 import com.android.systemui.util.ViewController;
@@ -66,7 +67,8 @@
             ConfigurationController configurationController,
             DozeParameters dozeParameters,
             KeyguardUnlockAnimationController keyguardUnlockAnimationController,
-            SmartspaceTransitionController smartspaceTransitionController) {
+            SmartspaceTransitionController smartspaceTransitionController,
+            UnlockedScreenOffAnimationController unlockedScreenOffAnimationController) {
         super(keyguardStatusView);
         mKeyguardSliceViewController = keyguardSliceViewController;
         mKeyguardClockSwitchController = keyguardClockSwitchController;
@@ -75,7 +77,7 @@
         mDozeParameters = dozeParameters;
         mKeyguardStateController = keyguardStateController;
         mKeyguardVisibilityHelper = new KeyguardVisibilityHelper(mView, keyguardStateController,
-                dozeParameters);
+                dozeParameters, unlockedScreenOffAnimationController);
         mKeyguardUnlockAnimationController = keyguardUnlockAnimationController;
         mSmartspaceTransitionController = smartspaceTransitionController;
 
@@ -87,7 +89,6 @@
                 // element transition.
                 if (keyguardStateController.isShowing()) {
                     mView.setChildrenAlphaExcludingClockView(1f);
-                    mKeyguardClockSwitchController.setChildrenAlphaExcludingSmartspace(1f);
                 }
             }
         });
@@ -238,13 +239,6 @@
     }
 
     /**
-     * @return {@code true} if we are currently animating the screen off from unlock
-     */
-    public boolean isAnimatingScreenOffFromUnlocked() {
-        return mKeyguardVisibilityHelper.isAnimatingScreenOffFromUnlocked();
-    }
-
-    /**
      * Set the visibility of the keyguard status view based on some new state.
      */
     public void setKeyguardStatusViewVisibility(
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java b/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java
index b6a58dc..7edecc8 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java
@@ -27,6 +27,7 @@
 import com.android.systemui.statusbar.notification.stack.AnimationProperties;
 import com.android.systemui.statusbar.notification.stack.StackStateAnimator;
 import com.android.systemui.statusbar.phone.DozeParameters;
+import com.android.systemui.statusbar.phone.UnlockedScreenOffAnimationController;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 
 /**
@@ -38,16 +39,19 @@
     private View mView;
     private final KeyguardStateController mKeyguardStateController;
     private final DozeParameters mDozeParameters;
+    private final UnlockedScreenOffAnimationController mUnlockedScreenOffAnimationController;
     private boolean mKeyguardViewVisibilityAnimating;
     private boolean mLastOccludedState = false;
-    private boolean mAnimatingScreenOff;
     private final AnimationProperties mAnimationProperties = new AnimationProperties();
 
-    public KeyguardVisibilityHelper(View view, KeyguardStateController keyguardStateController,
-            DozeParameters dozeParameters) {
+    public KeyguardVisibilityHelper(View view,
+            KeyguardStateController keyguardStateController,
+            DozeParameters dozeParameters,
+            UnlockedScreenOffAnimationController unlockedScreenOffAnimationController) {
         mView = view;
         mKeyguardStateController = keyguardStateController;
         mDozeParameters = dozeParameters;
+        mUnlockedScreenOffAnimationController = unlockedScreenOffAnimationController;
     }
 
     public boolean isVisibilityAnimating() {
@@ -122,32 +126,14 @@
                         .alpha(1f)
                         .withEndAction(mAnimateKeyguardStatusViewVisibleEndRunnable)
                         .start();
-            } else if (mDozeParameters.shouldControlUnlockedScreenOff()) {
+            } else if (mUnlockedScreenOffAnimationController
+                        .isScreenOffLightRevealAnimationPlaying()) {
                 mKeyguardViewVisibilityAnimating = true;
-                mAnimatingScreenOff = true;
 
-                mView.setVisibility(View.VISIBLE);
-                mView.setAlpha(0f);
-                float currentY = mView.getY();
-                mView.setY(currentY - mView.getHeight() * 0.1f);
-                int duration = StackStateAnimator.ANIMATION_DURATION_WAKEUP;
-                int delay = (int) (duration * .6f);
-                // We animate the Y properly separately using the PropertyAnimator, as the panel
-                // view als needs to update the end position.
-                mAnimationProperties.setDuration(duration).setDelay(delay);
-                PropertyAnimator.cancelAnimation(mView, AnimatableProperty.Y);
-                PropertyAnimator.setProperty(mView, AnimatableProperty.Y, currentY,
-                        mAnimationProperties,
-                        true /* animate */);
-
-                mView.animate()
-                        .setStartDelay(delay)
-                        .setDuration(duration)
-                        .setInterpolator(Interpolators.FAST_OUT_SLOW_IN)
-                        .alpha(1f)
-                        .withEndAction(mAnimateKeyguardStatusViewVisibleEndRunnable)
-                        .start();
-
+                // Ask the screen off animation controller to animate the keyguard visibility for us
+                // since it may need to be cancelled due to keyguard lifecycle events.
+                mUnlockedScreenOffAnimationController.animateInKeyguard(
+                        mView, mAnimateKeyguardStatusViewVisibleEndRunnable);
             } else {
                 mView.setVisibility(View.VISIBLE);
                 mView.setAlpha(1f);
@@ -172,13 +158,5 @@
 
     private final Runnable mAnimateKeyguardStatusViewVisibleEndRunnable = () -> {
         mKeyguardViewVisibilityAnimating = false;
-        mAnimatingScreenOff = false;
     };
-
-    /**
-     * @return {@code true} if we are currently animating the screen off from unlock
-     */
-    public boolean isAnimatingScreenOffFromUnlocked() {
-        return mAnimatingScreenOff;
-    }
 }
diff --git a/packages/SystemUI/src/com/android/keyguard/LockIconView.java b/packages/SystemUI/src/com/android/keyguard/LockIconView.java
index 2167876..eff412e 100644
--- a/packages/SystemUI/src/com/android/keyguard/LockIconView.java
+++ b/packages/SystemUI/src/com/android/keyguard/LockIconView.java
@@ -87,6 +87,11 @@
         updateSensorRect(h, w);
     }
 
+    @Override
+    public boolean hasOverlappingRendering() {
+        return false;
+    }
+
     float getLocationTop() {
         return mLockIconCenter.y - mRadius;
     }
diff --git a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java
index 6b85ba8..ccc4879 100644
--- a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java
@@ -451,4 +451,11 @@
 
     private final AccessibilityManager.TouchExplorationStateChangeListener
             mTouchExplorationStateChangeListener = enabled -> updateClickListener();
+
+    /**
+     * Set the alpha of this view.
+     */
+    public void setAlpha(float alpha) {
+        mView.setAlpha(alpha);
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationModeSwitch.java b/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationModeSwitch.java
index 964b135..2e6c9e4 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationModeSwitch.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationModeSwitch.java
@@ -18,6 +18,7 @@
 
 import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_NONE;
 import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW;
+import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
 
 import android.annotation.NonNull;
 import android.annotation.UiContext;
@@ -36,6 +37,7 @@
 import android.view.WindowInsets;
 import android.view.WindowManager;
 import android.view.WindowManager.LayoutParams;
+import android.view.WindowMetrics;
 import android.view.accessibility.AccessibilityManager;
 import android.view.accessibility.AccessibilityNodeInfo;
 import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
@@ -106,16 +108,41 @@
                         R.string.magnification_mode_switch_click_label));
                 info.addAction(clickAction);
                 info.setClickable(true);
+                info.addAction(new AccessibilityAction(R.id.accessibility_action_move_up,
+                        mContext.getString(R.string.accessibility_control_move_up)));
+                info.addAction(new AccessibilityAction(R.id.accessibility_action_move_down,
+                        mContext.getString(R.string.accessibility_control_move_down)));
+                info.addAction(new AccessibilityAction(R.id.accessibility_action_move_left,
+                        mContext.getString(R.string.accessibility_control_move_left)));
+                info.addAction(new AccessibilityAction(R.id.accessibility_action_move_right,
+                        mContext.getString(R.string.accessibility_control_move_right)));
             }
 
             @Override
             public boolean performAccessibilityAction(View host, int action, Bundle args) {
-                if (action == AccessibilityAction.ACTION_CLICK.getId()) {
-                    handleSingleTap();
+                if (performA11yAction(action)) {
                     return true;
                 }
                 return super.performAccessibilityAction(host, action, args);
             }
+
+            private boolean performA11yAction(int action) {
+                final Rect windowBounds = mWindowManager.getCurrentWindowMetrics().getBounds();
+                if (action == AccessibilityAction.ACTION_CLICK.getId()) {
+                    handleSingleTap();
+                } else if (action == R.id.accessibility_action_move_up) {
+                    moveButton(0, -windowBounds.height());
+                } else if (action == R.id.accessibility_action_move_down) {
+                    moveButton(0, windowBounds.height());
+                } else if (action == R.id.accessibility_action_move_left) {
+                    moveButton(-windowBounds.width(), 0);
+                } else if (action == R.id.accessibility_action_move_right) {
+                    moveButton(windowBounds.width(), 0);
+                } else {
+                    return false;
+                }
+                return true;
+            }
         });
         mWindowInsetChangeRunnable = this::onWindowInsetChanged;
         mImageView.setOnApplyWindowInsetsListener((v, insets) -> {
@@ -362,21 +389,21 @@
                 PixelFormat.TRANSPARENT);
         params.gravity = Gravity.TOP | Gravity.LEFT;
         params.accessibilityTitle = getAccessibilityWindowTitle(context);
+        params.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
         return params;
     }
 
     private Rect getDraggableWindowBounds() {
         final int layoutMargin = mContext.getResources().getDimensionPixelSize(
                 R.dimen.magnification_switch_button_margin);
-        final Rect boundRect = new Rect(mWindowManager.getCurrentWindowMetrics().getBounds());
-        final Insets systemBars =
-                mWindowManager.getCurrentWindowMetrics().getWindowInsets()
-                        .getInsetsIgnoringVisibility(WindowInsets.Type.systemBars());
-        final Rect insets = new Rect(layoutMargin,
-                systemBars.top + layoutMargin,
-                mParams.width + layoutMargin,
-                mParams.height + layoutMargin + systemBars.bottom);
-        boundRect.inset(insets);
+        final WindowMetrics windowMetrics = mWindowManager.getCurrentWindowMetrics();
+        final Insets windowInsets = windowMetrics.getWindowInsets().getInsetsIgnoringVisibility(
+                WindowInsets.Type.systemBars() | WindowInsets.Type.displayCutout());
+        final Rect boundRect = new Rect(windowMetrics.getBounds());
+        boundRect.offsetTo(0, 0);
+        boundRect.inset(0, 0, mParams.width, mParams.height);
+        boundRect.inset(windowInsets);
+        boundRect.inset(layoutMargin, layoutMargin);
         return boundRect;
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuView.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuView.java
index e85bd88..259a9f7 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuView.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuView.java
@@ -18,6 +18,8 @@
 
 import static android.util.MathUtils.constrain;
 import static android.util.MathUtils.sq;
+import static android.view.WindowInsets.Type.ime;
+import static android.view.WindowInsets.Type.navigationBars;
 
 import static java.util.Objects.requireNonNull;
 
@@ -29,6 +31,7 @@
 import android.content.pm.ActivityInfo;
 import android.content.res.Configuration;
 import android.content.res.Resources;
+import android.graphics.Insets;
 import android.graphics.PixelFormat;
 import android.graphics.Rect;
 import android.graphics.drawable.Drawable;
@@ -42,7 +45,9 @@
 import android.view.MotionEvent;
 import android.view.ViewConfiguration;
 import android.view.ViewGroup;
+import android.view.WindowInsets;
 import android.view.WindowManager;
+import android.view.WindowMetrics;
 import android.view.accessibility.AccessibilityNodeInfo;
 import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
 import android.view.animation.Animation;
@@ -90,6 +95,7 @@
     private boolean mIsShowing;
     private boolean mIsDownInEnlargedTouchArea;
     private boolean mIsDragging = false;
+    private boolean mImeVisibility;
     @Alignment
     private int mAlignment = Alignment.RIGHT;
     @SizeType
@@ -369,6 +375,8 @@
 
         mIsShowing = true;
         mWindowManager.addView(this, mCurrentLayoutParams);
+
+        setOnApplyWindowInsetsListener((view, insets) -> onWindowInsetsApplied(insets));
         setSystemGestureExclusion();
     }
 
@@ -379,6 +387,8 @@
 
         mIsShowing = false;
         mWindowManager.removeView(this);
+
+        setOnApplyWindowInsetsListener(null);
         setSystemGestureExclusion();
     }
 
@@ -409,11 +419,13 @@
 
         mSizeType = newSizeType;
 
-        updateIconSizeWith(newSizeType);
+        updateItemViewWith(newSizeType);
         updateRadiusWith(newSizeType, mRadiusType, mTargets.size());
 
         // When the icon sized changed, the menu size and location will be impacted.
         updateLocationWith(mAlignment, mPercentageY);
+        updateScrollModeWith(hasExceededMaxLayoutHeight());
+        updateOffsetWith(mShapeType, mAlignment);
         setSystemGestureExclusion();
 
         fadeOut();
@@ -565,6 +577,16 @@
         return mListView.dispatchTouchEvent(event);
     }
 
+    private WindowInsets onWindowInsetsApplied(WindowInsets insets) {
+        final boolean currentImeVisibility = insets.isVisible(ime());
+        if (currentImeVisibility != mImeVisibility) {
+            mImeVisibility = currentImeVisibility;
+            updateLocationWith(mAlignment, mPercentageY);
+        }
+
+        return insets;
+    }
+
     private boolean isMovingTowardsScreenEdge(@Alignment int side, int currentRawX, int downX) {
         return (side == Alignment.RIGHT && currentRawX > downX)
                 || (side == Alignment.LEFT && downX > currentRawX);
@@ -601,24 +623,35 @@
         mScreenHeight = dm.heightPixels;
         mMargin =
                 res.getDimensionPixelSize(R.dimen.accessibility_floating_menu_margin);
-        mPadding =
-                res.getDimensionPixelSize(R.dimen.accessibility_floating_menu_padding);
         mInset =
                 res.getDimensionPixelSize(R.dimen.accessibility_floating_menu_stroke_inset);
 
         mSquareScaledTouchSlop =
                 sq(ViewConfiguration.get(getContext()).getScaledTouchSlop());
+
+        updateItemViewDimensionsWith(mSizeType);
     }
 
-    private void updateIconSizeWith(@SizeType int sizeType) {
+    private void updateItemViewDimensionsWith(@SizeType int sizeType) {
         final Resources res = getResources();
+        final int paddingResId =
+                sizeType == SizeType.SMALL
+                        ? R.dimen.accessibility_floating_menu_small_padding
+                        : R.dimen.accessibility_floating_menu_large_padding;
+        mPadding = res.getDimensionPixelSize(paddingResId);
+
         final int iconResId =
                 sizeType == SizeType.SMALL
                         ? R.dimen.accessibility_floating_menu_small_width_height
                         : R.dimen.accessibility_floating_menu_large_width_height;
         mIconWidth = res.getDimensionPixelSize(iconResId);
         mIconHeight = mIconWidth;
+    }
 
+    private void updateItemViewWith(@SizeType int sizeType) {
+        updateItemViewDimensionsWith(sizeType);
+
+        mAdapter.setItemPadding(mPadding);
         mAdapter.setIconWidthHeight(mIconWidth);
         mAdapter.notifyDataSetChanged();
     }
@@ -630,7 +663,6 @@
         final LayoutParams layoutParams =
                 new LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
                         ViewGroup.LayoutParams.WRAP_CONTENT);
-        layoutParams.setMargins(mMargin, mMargin, mMargin, mMargin);
         mListView.setLayoutParams(layoutParams);
         final InstantInsetLayerDrawable layerDrawable =
                 new InstantInsetLayerDrawable(new Drawable[]{background});
@@ -645,6 +677,10 @@
     }
 
     private void updateListView() {
+        final LayoutParams layoutParams = (FrameLayout.LayoutParams) mListView.getLayoutParams();
+        layoutParams.setMargins(mMargin, mMargin, mMargin, mMargin);
+        mListView.setLayoutParams(layoutParams);
+
         final int elevation =
                 getResources().getDimensionPixelSize(R.dimen.accessibility_floating_menu_elevation);
         mListView.setElevation(elevation);
@@ -658,6 +694,7 @@
                 WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
                         | WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS,
                 PixelFormat.TRANSLUCENT);
+        params.receiveInsetsIgnoringZOrder = true;
         params.windowAnimations = android.R.style.Animation_Translucent;
         params.gravity = Gravity.START | Gravity.TOP;
         params.x = getMaxWindowX();
@@ -676,11 +713,13 @@
 
         updateDimensions();
         updateListView();
-        updateIconSizeWith(mSizeType);
+        updateItemViewWith(mSizeType);
         updateColor();
         updateStrokeWith(newConfig.uiMode, mAlignment);
         updateLocationWith(mAlignment, mPercentageY);
+        updateRadiusWith(mSizeType, mRadiusType, mTargets.size());
         updateScrollModeWith(hasExceededMaxLayoutHeight());
+        setSystemGestureExclusion();
 
         mLastConfiguration.setTo(newConfig);
     }
@@ -728,10 +767,30 @@
      */
     private void updateLocationWith(@Alignment int side, float percentageCurrentY) {
         mCurrentLayoutParams.x = (side == Alignment.RIGHT) ? getMaxWindowX() : getMinWindowX();
-        mCurrentLayoutParams.y = (int) (percentageCurrentY * getMaxWindowY());
+        final int currentLayoutY = (int) (percentageCurrentY * getMaxWindowY());
+        mCurrentLayoutParams.y = Math.max(MIN_WINDOW_Y, currentLayoutY - getInterval());
         mWindowManager.updateViewLayout(this, mCurrentLayoutParams);
     }
 
+    /**
+     * Gets the moving interval to not overlap between the keyboard and menu view.
+     *
+     * @return the moving interval if they overlap each other, otherwise 0.
+     */
+    private int getInterval() {
+        if (!mImeVisibility) {
+            return 0;
+        }
+
+        final WindowMetrics windowMetrics = mWindowManager.getCurrentWindowMetrics();
+        final Insets imeInsets = windowMetrics.getWindowInsets().getInsets(
+                ime() | navigationBars());
+        final int imeY = mScreenHeight - imeInsets.bottom;
+        final int layoutBottomY = mCurrentLayoutParams.y + getWindowHeight();
+
+        return layoutBottomY > imeY ? (layoutBottomY - imeY) : 0;
+    }
+
     private void updateOffsetWith(@ShapeType int shapeType, @Alignment int side) {
         final float halfWidth = getLayoutWidth() / 2.0f;
         final float offset = (shapeType == ShapeType.OVAL) ? 0 : halfWidth;
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityTargetAdapter.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityTargetAdapter.java
index 76106e7..63cfd51 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityTargetAdapter.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityTargetAdapter.java
@@ -16,8 +16,6 @@
 
 package com.android.systemui.accessibility.floatingmenu;
 
-import static android.view.View.GONE;
-
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
@@ -43,6 +41,7 @@
  */
 public class AccessibilityTargetAdapter extends Adapter<ViewHolder> {
     private int mIconWidthHeight;
+    private int mItemPadding;
     private final List<AccessibilityTarget> mTargets;
 
     @IntDef({
@@ -84,6 +83,7 @@
         final AccessibilityTarget target = mTargets.get(position);
         holder.mIconView.setBackground(target.getIcon());
         holder.updateIconWidthHeight(mIconWidthHeight);
+        holder.updateItemPadding(mItemPadding, getItemCount());
         holder.itemView.setOnClickListener((v) -> target.onSelected());
         holder.itemView.setStateDescription(target.getStateDescription());
         holder.itemView.setContentDescription(target.getLabel());
@@ -120,14 +120,16 @@
         mIconWidthHeight = iconWidthHeight;
     }
 
+    public void setItemPadding(int itemPadding) {
+        mItemPadding = itemPadding;
+    }
+
     static class ViewHolder extends RecyclerView.ViewHolder {
         final View mIconView;
-        final View mDivider;
 
         ViewHolder(View itemView) {
             super(itemView);
             mIconView = itemView.findViewById(R.id.icon_view);
-            mDivider = itemView.findViewById(R.id.transparent_divider);
         }
 
         void updateIconWidthHeight(int newValue) {
@@ -139,21 +141,31 @@
             layoutParams.height = newValue;
             mIconView.setLayoutParams(layoutParams);
         }
+
+        void updateItemPadding(int padding, int size) {
+            itemView.setPaddingRelative(padding, padding, padding, padding);
+        }
     }
 
     static class TopViewHolder extends ViewHolder {
         TopViewHolder(View itemView) {
             super(itemView);
-            final int padding = itemView.getPaddingStart();
-            itemView.setPaddingRelative(padding, padding, padding, 0);
+        }
+
+        @Override
+        void updateItemPadding(int padding, int size) {
+            final int paddingBottom = size <= 2 ? padding : 0;
+            itemView.setPaddingRelative(padding, padding, padding, paddingBottom);
         }
     }
 
     static class BottomViewHolder extends ViewHolder {
         BottomViewHolder(View itemView) {
             super(itemView);
-            mDivider.setVisibility(GONE);
-            final int padding = itemView.getPaddingStart();
+        }
+
+        @Override
+        void updateItemPadding(int padding, int size) {
             itemView.setPaddingRelative(padding, 0, padding, padding);
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/BaseTooltipView.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/BaseTooltipView.java
index 3085854..61fc7ed 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/BaseTooltipView.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/BaseTooltipView.java
@@ -214,6 +214,8 @@
 
         final GradientDrawable gradientDrawable = (GradientDrawable) mTextView.getBackground();
         gradientDrawable.setCornerRadius(mTextViewCornerRadius);
+        gradientDrawable.setColor(Utils.getColorAttrDefaultColor(getContext(),
+                com.android.internal.R.attr.colorAccentPrimary));
     }
 
     private void updateArrowWith(Rect anchorViewLocation) {
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricView.java
index 1ac1df1..29cd76d 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricView.java
@@ -596,10 +596,6 @@
         Utils.notifyAccessibilityContentChanged(mAccessibilityManager, this);
     }
 
-    private void setText(TextView view, CharSequence charSequence) {
-        view.setText(charSequence);
-    }
-
     // Remove all pending icon and text animations
     private void removePendingAnimations() {
         mHandler.removeCallbacks(mResetHelpRunnable);
@@ -688,7 +684,7 @@
      */
     @VisibleForTesting
     void onAttachedToWindowInternal() {
-        setText(mTitleView, mPromptInfo.getTitle());
+        mTitleView.setText(mPromptInfo.getTitle());
 
         if (isDeviceCredentialAllowed()) {
             final CharSequence credentialButtonText;
@@ -718,7 +714,7 @@
             mUseCredentialButton.setText(credentialButtonText);
             mUseCredentialButton.setVisibility(View.VISIBLE);
         } else {
-            setText(mNegativeButton, mPromptInfo.getNegativeButtonText());
+            mNegativeButton.setText(mPromptInfo.getNegativeButtonText());
         }
 
         setTextOrHide(mSubtitleView, mPromptInfo.getSubtitle());
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
index 6f1a387..9ec7bd0c 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
@@ -575,7 +575,7 @@
         if (mBiometricView != null) {
             mBiometricView.restoreState(savedState);
         }
-        wm.addView(this, getLayoutParams(mWindowToken));
+        wm.addView(this, getLayoutParams(mWindowToken, mConfig.mPromptInfo.getTitle()));
     }
 
     @Override
@@ -728,11 +728,9 @@
         }
     }
 
-    /**
-     * @param windowToken token for the window
-     * @return
-     */
-    public static WindowManager.LayoutParams getLayoutParams(IBinder windowToken) {
+    @VisibleForTesting
+    static WindowManager.LayoutParams getLayoutParams(IBinder windowToken,
+            CharSequence title) {
         final int windowFlags = WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED
                 | WindowManager.LayoutParams.FLAG_SECURE;
         final WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
@@ -744,15 +742,11 @@
         lp.privateFlags |= WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS;
         lp.setFitInsetsTypes(lp.getFitInsetsTypes() & ~WindowInsets.Type.ime());
         lp.setTitle("BiometricPrompt");
+        lp.accessibilityTitle = title;
         lp.token = windowToken;
         return lp;
     }
 
-    private boolean hasFaceAndFingerprintSensors() {
-        final int[] ids = findFaceAndFingerprintSensors();
-        return ids[0] >= 0 && ids[1] >= 0;
-    }
-
     // returns [face, fingerprint] sensor ids (id is -1 if not present)
     private int[] findFaceAndFingerprintSensors() {
         int faceSensorId = -1;
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.java
index 2b4c1ab..9a32412 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.java
@@ -108,6 +108,7 @@
         mStatusBarStateController.addCallback(mStateListener);
 
         mUdfpsRequested = false;
+
         mStatusBarState = mStatusBarStateController.getState();
         mQsExpanded = mKeyguardViewManager.isQsExpanded();
         mInputBouncerHiddenAmount = KeyguardBouncer.EXPANSION_HIDDEN;
@@ -125,7 +126,7 @@
         mFaceDetectRunning = false;
 
         mStatusBarStateController.removeCallback(mStateListener);
-        mKeyguardViewManager.setAlternateAuthInterceptor(null);
+        mKeyguardViewManager.removeAlternateAuthInterceptor(mAlternateAuthInterceptor);
         mTransitioningFromHome = false;
         mKeyguardUpdateMonitor.requestFaceAuthOnOccludingApp(false);
 
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/Utils.java b/packages/SystemUI/src/com/android/systemui/biometrics/Utils.java
index 076c7cb..4d4e4dd 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/Utils.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/Utils.java
@@ -29,7 +29,6 @@
 import android.hardware.biometrics.PromptInfo;
 import android.hardware.biometrics.SensorPropertiesInternal;
 import android.os.UserManager;
-import android.util.DisplayMetrics;
 import android.view.ViewGroup;
 import android.view.accessibility.AccessibilityEvent;
 import android.view.accessibility.AccessibilityManager;
@@ -50,17 +49,6 @@
     @IntDef({CREDENTIAL_PIN, CREDENTIAL_PATTERN, CREDENTIAL_PASSWORD})
     @interface CredentialType {}
 
-
-    static float dpToPixels(Context context, float dp) {
-        return dp * ((float) context.getResources().getDisplayMetrics().densityDpi
-                / DisplayMetrics.DENSITY_DEFAULT);
-    }
-
-    static float pixelsToDp(Context context, float pixels) {
-        return pixels / ((float) context.getResources().getDisplayMetrics().densityDpi
-                / DisplayMetrics.DENSITY_DEFAULT);
-    }
-
     static void notifyAccessibilityContentChanged(AccessibilityManager am, ViewGroup view) {
         if (!am.isEnabled()) {
             return;
diff --git a/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingAnimation.java b/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingAnimation.java
index f01ac68..508262d 100644
--- a/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingAnimation.java
+++ b/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingAnimation.java
@@ -28,6 +28,9 @@
 import android.view.Gravity;
 import android.view.WindowManager;
 
+import com.android.internal.logging.UiEvent;
+import com.android.internal.logging.UiEventLogger;
+
 /**
  * A WirelessChargingAnimation is a view containing view + animation for wireless charging.
  * @hide
@@ -54,9 +57,10 @@
      * @hide
      */
     public WirelessChargingAnimation(@NonNull Context context, @Nullable Looper looper,
-            int transmittingBatteryLevel, int batteryLevel, Callback callback, boolean isDozing) {
+            int transmittingBatteryLevel, int batteryLevel, Callback callback, boolean isDozing,
+            UiEventLogger uiEventLogger) {
         mCurrentWirelessChargingView = new WirelessChargingView(context, looper,
-                transmittingBatteryLevel, batteryLevel, callback, isDozing);
+                transmittingBatteryLevel, batteryLevel, callback, isDozing, uiEventLogger);
     }
 
     /**
@@ -66,9 +70,9 @@
      */
     public static WirelessChargingAnimation makeWirelessChargingAnimation(@NonNull Context context,
             @Nullable Looper looper, int transmittingBatteryLevel, int batteryLevel,
-            Callback callback, boolean isDozing) {
+            Callback callback, boolean isDozing, UiEventLogger uiEventLogger) {
         return new WirelessChargingAnimation(context, looper, transmittingBatteryLevel,
-                batteryLevel, callback, isDozing);
+                batteryLevel, callback, isDozing, uiEventLogger);
     }
 
     /**
@@ -95,6 +99,7 @@
 
         private final WindowManager.LayoutParams mParams = new WindowManager.LayoutParams();
         private final Handler mHandler;
+        private final UiEventLogger mUiEventLogger;
 
         private int mGravity;
         private WirelessChargingLayout mView;
@@ -104,11 +109,12 @@
 
         public WirelessChargingView(Context context, @Nullable Looper looper,
                 int transmittingBatteryLevel, int batteryLevel, Callback callback,
-                boolean isDozing) {
+                boolean isDozing, UiEventLogger uiEventLogger) {
             mCallback = callback;
             mNextView = new WirelessChargingLayout(context, transmittingBatteryLevel, batteryLevel,
                     isDozing);
             mGravity = Gravity.CENTER_HORIZONTAL | Gravity.CENTER;
+            mUiEventLogger = uiEventLogger;
 
             final WindowManager.LayoutParams params = mParams;
             params.height = WindowManager.LayoutParams.MATCH_PARENT;
@@ -195,6 +201,7 @@
                         mCallback.onAnimationStarting();
                     }
                     mWM.addView(mView, mParams);
+                    mUiEventLogger.log(WirelessChargingRippleEvent.WIRELESS_RIPPLE_PLAYED);
                 } catch (WindowManager.BadTokenException e) {
                     Slog.d(TAG, "Unable to add wireless charging view. " + e);
                 }
@@ -215,5 +222,19 @@
                 mView = null;
             }
         }
+
+        enum WirelessChargingRippleEvent implements UiEventLogger.UiEventEnum {
+            @UiEvent(doc = "Wireless charging ripple effect played")
+            WIRELESS_RIPPLE_PLAYED(830);
+
+            private final int mInt;
+            WirelessChargingRippleEvent(int id) {
+                mInt = id;
+            }
+
+            @Override public int getId() {
+                return mInt;
+            }
+        }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/DetailDialog.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/DetailDialog.kt
index 06fbf73..8a47a36 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/DetailDialog.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/DetailDialog.kt
@@ -102,6 +102,10 @@
         override fun onReleased() {
             removeDetailTask()
         }
+
+        override fun onBackPressedOnTaskRoot(taskId: Int) {
+            dismiss()
+        }
     }
 
     init {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
index 9b372b8..5a5cce8f 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
@@ -232,10 +232,11 @@
         }
 
         @Override // Binder interface
-        public void onStartedWakingUp(@PowerManager.WakeReason int pmWakeReason) {
+        public void onStartedWakingUp(
+                @PowerManager.WakeReason int pmWakeReason, boolean cameraGestureTriggered) {
             Trace.beginSection("KeyguardService.mBinder#onStartedWakingUp");
             checkPermission();
-            mKeyguardViewMediator.onStartedWakingUp();
+            mKeyguardViewMediator.onStartedWakingUp(cameraGestureTriggered);
             mKeyguardLifecyclesDispatcher.dispatch(
                     KeyguardLifecyclesDispatcher.STARTED_WAKING_UP, pmWakeReason);
             Trace.endSection();
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index 387f24f..da8a3b9 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -106,12 +106,14 @@
 import com.android.systemui.navigationbar.NavigationModeController;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.shared.system.QuickStepContract;
+import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.SysuiStatusBarStateController;
 import com.android.systemui.statusbar.phone.BiometricUnlockController;
 import com.android.systemui.statusbar.phone.DozeParameters;
 import com.android.systemui.statusbar.phone.KeyguardBypassController;
 import com.android.systemui.statusbar.phone.NotificationPanelViewController;
 import com.android.systemui.statusbar.phone.StatusBar;
+import com.android.systemui.statusbar.phone.UnlockedScreenOffAnimationController;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 import com.android.systemui.util.DeviceConfigProxy;
 
@@ -234,6 +236,7 @@
     private StatusBarManager mStatusBarManager;
     private final SysuiStatusBarStateController mStatusBarStateController;
     private final Executor mUiBgExecutor;
+    private final UnlockedScreenOffAnimationController mUnlockedScreenOffAnimationController;
 
     private boolean mSystemReady;
     private boolean mBootCompleted;
@@ -387,6 +390,19 @@
     private boolean mPendingLock;
 
     /**
+     * Whether a power button gesture (such as double tap for camera) has been detected. This is
+     * delivered directly from {@link KeyguardService}, immediately upon the gesture being detected.
+     * This is used in {@link #onStartedWakingUp} to decide whether to execute the pending lock, or
+     * ignore and reset it because we are actually launching an activity.
+     *
+     * This needs to be delivered directly to us, rather than waiting for
+     * {@link CommandQueue#onCameraLaunchGestureDetected}, because that call is asynchronous and is
+     * often delivered after the call to {@link #onStartedWakingUp}, which results in us locking the
+     * keyguard and then launching the activity behind it.
+     */
+    private boolean mPowerGestureIntercepted = false;
+
+    /**
      * Controller for showing individual "work challenge" lock screen windows inside managed profile
      * tasks when the current user has been unlocked but the profile is still locked.
      */
@@ -790,7 +806,8 @@
             DozeParameters dozeParameters,
             SysuiStatusBarStateController statusBarStateController,
             KeyguardStateController keyguardStateController,
-            Lazy<KeyguardUnlockAnimationController> keyguardUnlockAnimationControllerLazy) {
+            Lazy<KeyguardUnlockAnimationController> keyguardUnlockAnimationControllerLazy,
+            UnlockedScreenOffAnimationController unlockedScreenOffAnimationController) {
         super(context);
         mFalsingCollector = falsingCollector;
         mLockPatternUtils = lockPatternUtils;
@@ -822,6 +839,7 @@
 
         mKeyguardStateController = keyguardStateController;
         mKeyguardUnlockAnimationControllerLazy = keyguardUnlockAnimationControllerLazy;
+        mUnlockedScreenOffAnimationController = unlockedScreenOffAnimationController;
     }
 
     public void userActivity() {
@@ -941,6 +959,7 @@
         if (DEBUG) Log.d(TAG, "onStartedGoingToSleep(" + offReason + ")");
         synchronized (this) {
             mDeviceInteractive = false;
+            mPowerGestureIntercepted = false;
             mGoingToSleep = true;
 
             // Reset keyguard going away state so we can start listening for fingerprint. We
@@ -1010,7 +1029,6 @@
             notifyFinishedGoingToSleep();
 
             if (cameraGestureTriggered) {
-                Log.i(TAG, "Camera gesture was triggered, preventing Keyguard locking.");
 
                 // Just to make sure, make sure the device is awake.
                 mContext.getSystemService(PowerManager.class).wakeUp(SystemClock.uptimeMillis(),
@@ -1025,10 +1043,7 @@
                 mPendingReset = false;
             }
 
-            if (mPendingLock) {
-                doKeyguardLocked(null);
-                mPendingLock = false;
-            }
+            maybeHandlePendingLock();
 
             // We do not have timeout and power button instant lock setting for profile lock.
             // So we use the personal setting if there is any. But if there is no device
@@ -1041,6 +1056,20 @@
         mUpdateMonitor.dispatchFinishedGoingToSleep(offReason);
     }
 
+    /**
+     * Locks the keyguard if {@link #mPendingLock} is true, unless we're playing the screen off
+     * animation.
+     *
+     * If we are, we will lock the keyguard either when the screen off animation ends, or in
+     * {@link #onStartedWakingUp} if the animation is cancelled.
+     */
+    public void maybeHandlePendingLock() {
+        if (mPendingLock && !mUnlockedScreenOffAnimationController.isScreenOffAnimationPlaying()) {
+            doKeyguardLocked(null);
+            mPendingLock = false;
+        }
+    }
+
     private boolean isKeyguardServiceEnabled() {
         try {
             return mContext.getPackageManager().getServiceInfo(
@@ -1149,12 +1178,15 @@
     /**
      * Let's us know when the device is waking up.
      */
-    public void onStartedWakingUp() {
+    public void onStartedWakingUp(boolean cameraGestureTriggered) {
         Trace.beginSection("KeyguardViewMediator#onStartedWakingUp");
 
         // TODO: Rename all screen off/on references to interactive/sleeping
         synchronized (this) {
             mDeviceInteractive = true;
+            if (mPendingLock && !cameraGestureTriggered) {
+                doKeyguardLocked(null);
+            }
             mAnimatingScreenOff = false;
             cancelDoKeyguardLaterLocked();
             cancelDoKeyguardForChildProfilesLocked();
@@ -1971,6 +2003,7 @@
 
             mHiding = false;
             mWakeAndUnlocking = false;
+            mPendingLock = false;
             setShowingLocked(true);
             mKeyguardViewControllerLazy.get().show(options);
             resetKeyguardDonePendingLocked();
@@ -2620,7 +2653,12 @@
         if (!dozing) {
             mAnimatingScreenOff = false;
         }
-        setShowingLocked(mShowing);
+
+        // Don't hide the keyguard due to a doze change if there's a lock pending, because we're
+        // just going to show it again.
+        if (mShowing || !mPendingLock) {
+            setShowingLocked(mShowing);
+        }
     }
 
     @Override
@@ -2677,14 +2715,7 @@
         mAodShowing = aodShowing;
         if (notifyDefaultDisplayCallbacks) {
             notifyDefaultDisplayCallbacks(showing);
-
-            if (!showing || !mAnimatingScreenOff) {
-                // Update the activity lock screen state unless we're animating in the keyguard
-                // for a screen off animation. In that case, we want the activity to remain visible
-                // until the animation completes. setShowingLocked is called again when the
-                // animation ends, so the activity lock screen will be shown at that time.
-                updateActivityLockScreenState(showing, aodShowing);
-            }
+            updateActivityLockScreenState(showing, aodShowing);
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
index 119e9c4..b071b943 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
@@ -51,6 +51,7 @@
 import com.android.systemui.statusbar.phone.DozeParameters;
 import com.android.systemui.statusbar.phone.KeyguardLiftController;
 import com.android.systemui.statusbar.phone.StatusBar;
+import com.android.systemui.statusbar.phone.UnlockedScreenOffAnimationController;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 import com.android.systemui.util.DeviceConfigProxy;
 import com.android.systemui.util.sensors.AsyncSensorManager;
@@ -97,7 +98,8 @@
             DozeParameters dozeParameters,
             SysuiStatusBarStateController statusBarStateController,
             KeyguardStateController keyguardStateController,
-            Lazy<KeyguardUnlockAnimationController> keyguardUnlockAnimationController) {
+            Lazy<KeyguardUnlockAnimationController> keyguardUnlockAnimationController,
+            UnlockedScreenOffAnimationController unlockedScreenOffAnimationController) {
         return new KeyguardViewMediator(
                 context,
                 falsingCollector,
@@ -116,7 +118,8 @@
                 dozeParameters,
                 statusBarStateController,
                 keyguardStateController,
-                keyguardUnlockAnimationController
+                keyguardUnlockAnimationController,
+                unlockedScreenOffAnimationController
         );
     }
 
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 12c6906..ba65d51 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java
@@ -291,6 +291,7 @@
 
     @Override
     public boolean onFailedToRecycleView(Holder holder) {
+        holder.stopDrag();
         holder.clearDrag();
         return true;
     }
@@ -565,12 +566,6 @@
 
         public void clearDrag() {
             itemView.clearAnimation();
-            if (mTileView instanceof CustomizeTileView) {
-                mTileView.findViewById(R.id.tile_label).clearAnimation();
-                mTileView.findViewById(R.id.tile_label).setAlpha(1);
-                mTileView.getSecondaryLabel().clearAnimation();
-                mTileView.getSecondaryLabel().setAlpha(.6f);
-            }
         }
 
         public void startDrag() {
@@ -578,14 +573,6 @@
                     .setDuration(DRAG_LENGTH)
                     .scaleX(DRAG_SCALE)
                     .scaleY(DRAG_SCALE);
-            if (mTileView instanceof CustomizeTileView) {
-                mTileView.findViewById(R.id.tile_label).animate()
-                        .setDuration(DRAG_LENGTH)
-                        .alpha(0);
-                mTileView.getSecondaryLabel().animate()
-                        .setDuration(DRAG_LENGTH)
-                        .alpha(0);
-            }
         }
 
         public void stopDrag() {
@@ -593,14 +580,6 @@
                     .setDuration(DRAG_LENGTH)
                     .scaleX(1)
                     .scaleY(1);
-            if (mTileView instanceof CustomizeTileView) {
-                mTileView.findViewById(R.id.tile_label).animate()
-                        .setDuration(DRAG_LENGTH)
-                        .alpha(1);
-                mTileView.getSecondaryLabel().animate()
-                        .setDuration(DRAG_LENGTH)
-                        .alpha(.6f);
-            }
         }
 
         boolean canRemove() {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java
index 47d80bb..a938821 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java
@@ -181,14 +181,15 @@
         mStatusBarStateController = statusBarStateController;
         mActivityStarter = activityStarter;
 
-        mState = newTileState();
-        mTmpState = newTileState();
+        resetStates();
         mUiHandler.post(() -> mLifecycle.setCurrentState(CREATED));
     }
 
     protected final void resetStates() {
         mState = newTileState();
         mTmpState = newTileState();
+        mState.spec = mTileSpec;
+        mTmpState.spec = mTileSpec;
     }
 
     @NonNull
@@ -225,6 +226,8 @@
 
     public void setTileSpec(String tileSpec) {
         mTileSpec = tileSpec;
+        mState.spec = tileSpec;
+        mTmpState.spec = tileSpec;
     }
 
     public QSHost getHost() {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt
index cd76b4d..8aed0a8 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt
@@ -38,6 +38,7 @@
 import android.widget.LinearLayout
 import android.widget.Switch
 import android.widget.TextView
+import androidx.annotation.VisibleForTesting
 import com.android.settingslib.Utils
 import com.android.systemui.FontSizeUtils
 import com.android.systemui.R
@@ -62,6 +63,8 @@
         private const val SECONDARY_LABEL_NAME = "secondaryLabel"
         private const val CHEVRON_NAME = "chevron"
         const val UNAVAILABLE_ALPHA = 0.3f
+        @VisibleForTesting
+        internal const val TILE_STATE_RES_PREFIX = "tile_states_"
     }
 
     override var heightOverride: Int = HeightOverrideable.NO_OVERRIDE
@@ -484,16 +487,18 @@
     }
 
     private fun getStateText(state: QSTile.State): String {
-        return if (state.disabledByPolicy) {
-            context.getString(R.string.tile_disabled)
-        } else if (state.state == Tile.STATE_UNAVAILABLE) {
-            context.getString(R.string.tile_unavailable)
-        } else if (state is BooleanState) {
-            if (state.state == Tile.STATE_INACTIVE) {
-                context.getString(R.string.switch_bar_off)
-            } else {
-                context.getString(R.string.switch_bar_on)
+        if (state.disabledByPolicy) {
+            return context.getString(R.string.tile_disabled)
+        }
+
+        return if (state.state == Tile.STATE_UNAVAILABLE || state is BooleanState) {
+            val resName = "$TILE_STATE_RES_PREFIX${state.spec}"
+            var arrayResId = resources.getIdentifier(resName, "array", context.packageName)
+            if (arrayResId == 0) {
+                arrayResId = R.array.tile_states_default
             }
+            val array = resources.getStringArray(arrayResId)
+            array[state.state]
         } else {
             ""
         }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTile.java
index 74d3425..7cb1421 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTile.java
@@ -45,7 +45,6 @@
 import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.plugins.FalsingManager;
 import com.android.systemui.plugins.qs.QSIconView;
-import com.android.systemui.plugins.qs.QSTile;
 import com.android.systemui.plugins.qs.QSTile.SignalState;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.qs.AlphaControlledSignalTileView;
@@ -72,7 +71,6 @@
 
     protected final NetworkController mController;
     private final DataUsageController mDataController;
-    private final QSTile.SignalState mStateBeforeClick = newTileState();
     // The last updated tile state, 0: mobile, 1: wifi, 2: ethernet.
     private int mLastTileState = -1;
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java
index efac141..41a3020 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java
@@ -94,6 +94,7 @@
         mWifiController = accessPointController;
         mDetailAdapter = (WifiDetailAdapter) createDetailAdapter();
         mController.observe(getLifecycle(), mSignalCallback);
+        mStateBeforeClick.spec = "wifi";
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingController.java b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingController.java
index 7ca8277..060d7b1 100644
--- a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingController.java
+++ b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingController.java
@@ -33,7 +33,7 @@
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.statusbar.policy.CallbackController;
 
-import java.util.ArrayList;
+import java.util.concurrent.CopyOnWriteArrayList;
 
 import javax.inject.Inject;
 
@@ -58,7 +58,8 @@
             "com.android.systemui.screenrecord.UPDATE_STATE";
     protected static final String EXTRA_STATE = "extra_state";
 
-    private ArrayList<RecordingStateChangeCallback> mListeners = new ArrayList<>();
+    private CopyOnWriteArrayList<RecordingStateChangeCallback> mListeners =
+            new CopyOnWriteArrayList<>();
 
     @VisibleForTesting
     protected final BroadcastReceiver mUserChangeReceiver = new BroadcastReceiver() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt
index af8d808..4ed376a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt
@@ -287,6 +287,8 @@
                     // TODO: appear media also in split shade
                     val mediaAmount = if (useSplitShade) 0f else field
                     mediaHierarchyManager.setTransitionToFullShadeAmount(mediaAmount)
+                    // Fade out all content only visible on the lockscreen
+                    notificationPanelController.setKeyguardOnlyContentAlpha(1.0f - scrimProgress)
                 }
             }
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/charging/WiredChargingRippleController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/charging/WiredChargingRippleController.kt
index 05afc57f..22bbb81b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/charging/WiredChargingRippleController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/charging/WiredChargingRippleController.kt
@@ -25,6 +25,8 @@
 import android.view.View
 import android.view.WindowManager
 import com.android.internal.annotations.VisibleForTesting
+import com.android.internal.logging.UiEvent
+import com.android.internal.logging.UiEventLogger
 import com.android.settingslib.Utils
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.statusbar.FeatureFlags
@@ -55,7 +57,8 @@
     featureFlags: FeatureFlags,
     private val context: Context,
     private val windowManager: WindowManager,
-    private val systemClock: SystemClock
+    private val systemClock: SystemClock,
+    private val uiEventLogger: UiEventLogger
 ) {
     private var pluggedIn: Boolean? = null
     private val rippleEnabled: Boolean = featureFlags.isChargingRippleEnabled &&
@@ -164,6 +167,7 @@
             }
         })
         windowManager.addView(rippleView, windowLayoutParams)
+        uiEventLogger.log(WiredChargingRippleEvent.CHARGING_RIPPLE_PLAYED)
     }
 
     private fun layoutRipple() {
@@ -203,4 +207,11 @@
             pw.println("Usage: adb shell cmd statusbar charging-ripple")
         }
     }
+
+    enum class WiredChargingRippleEvent(private val _id: Int) : UiEventLogger.UiEventEnum {
+        @UiEvent(doc = "Wired charging ripple effect played")
+        CHARGING_RIPPLE_PLAYED(829);
+
+        override fun getId() = _id
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemStatusAnimationScheduler.kt b/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemStatusAnimationScheduler.kt
index 33aa7c7..8479b30 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemStatusAnimationScheduler.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemStatusAnimationScheduler.kt
@@ -315,7 +315,11 @@
     ) : AnimatorListenerAdapter() {
         override fun onAnimationEnd(p0: Animator?) {
             chipAnimationController.onChipAnimationEnd(animationState)
-            animationState = endState
+            animationState = if (endState == SHOWING_PERSISTENT_DOT && !hasPersistentDot) {
+                IDLE
+            } else {
+                endState
+            }
         }
 
         override fun onAnimationStart(p0: Animator?) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinator.kt
index 7afb015..84728f6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinator.kt
@@ -28,6 +28,7 @@
 import com.android.systemui.statusbar.phone.DozeParameters
 import com.android.systemui.statusbar.phone.KeyguardBypassController
 import com.android.systemui.statusbar.phone.PanelExpansionListener
+import com.android.systemui.statusbar.phone.UnlockedScreenOffAnimationController
 import com.android.systemui.statusbar.policy.HeadsUpManager
 import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener
 import javax.inject.Inject
@@ -38,7 +39,8 @@
     private val mHeadsUpManager: HeadsUpManager,
     private val statusBarStateController: StatusBarStateController,
     private val bypassController: KeyguardBypassController,
-    private val dozeParameters: DozeParameters
+    private val dozeParameters: DozeParameters,
+    private val unlockedScreenOffAnimationController: UnlockedScreenOffAnimationController
 ) : OnHeadsUpChangedListener, StatusBarStateController.StateListener, PanelExpansionListener {
 
     private val mNotificationVisibility = object : FloatProperty<NotificationWakeUpCoordinator>(
@@ -264,7 +266,7 @@
     }
 
     override fun onStateChanged(newState: Int) {
-        if (dozeParameters.shouldControlUnlockedScreenOff()) {
+        if (unlockedScreenOffAnimationController.shouldPlayScreenOffAnimation()) {
             if (animatingScreenOff &&
                     state == StatusBarState.KEYGUARD &&
                     newState == StatusBarState.SHADE) {
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 c6753b3..d68271a 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
@@ -73,7 +73,6 @@
 import com.android.internal.widget.CallLayout;
 import com.android.systemui.Dependency;
 import com.android.systemui.R;
-import com.android.systemui.animation.ActivityLaunchAnimator;
 import com.android.systemui.animation.Interpolators;
 import com.android.systemui.classifier.FalsingCollector;
 import com.android.systemui.plugins.FalsingManager;
@@ -2020,6 +2019,14 @@
         if (params == null) {
             return;
         }
+
+        if (!params.getVisible()) {
+            if (getVisibility() == View.VISIBLE) {
+                setVisibility(View.INVISIBLE);
+            }
+            return;
+        }
+
         float zProgress = Interpolators.FAST_OUT_SLOW_IN.getInterpolation(
                 params.getProgress(0, 50));
         float translationZ = MathUtils.lerp(params.getStartTranslationZ(),
@@ -2077,10 +2084,6 @@
             contentView = mGuts;
         }
         if (expandAnimationRunning) {
-            contentView.animate()
-                    .alpha(0f)
-                    .setDuration(ActivityLaunchAnimator.ANIMATION_DURATION_FADE_OUT_CONTENT)
-                    .setInterpolator(ActivityLaunchAnimator.CONTENT_FADE_OUT_INTERPOLATOR);
             setAboveShelf(true);
             mExpandAnimationRunning = true;
             getViewState().cancelAnimations(this);
@@ -2088,6 +2091,7 @@
         } else {
             mExpandAnimationRunning = false;
             setAboveShelf(isAboveShelf());
+            setVisibility(View.VISIBLE);
             if (mGuts != null) {
                 mGuts.setAlpha(1.0f);
             }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationBackgroundView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationBackgroundView.java
index f6ab409..4b1f679 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationBackgroundView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationBackgroundView.java
@@ -20,7 +20,6 @@
 import android.content.res.ColorStateList;
 import android.graphics.Canvas;
 import android.graphics.PorterDuff;
-import android.graphics.PorterDuffXfermode;
 import android.graphics.drawable.Drawable;
 import android.graphics.drawable.GradientDrawable;
 import android.graphics.drawable.LayerDrawable;
@@ -30,8 +29,6 @@
 
 import com.android.internal.util.ArrayUtils;
 import com.android.systemui.R;
-import com.android.systemui.animation.ActivityLaunchAnimator;
-import com.android.systemui.animation.Interpolators;
 import com.android.systemui.statusbar.notification.ExpandAnimationParameters;
 
 /**
@@ -281,11 +278,6 @@
     public void setExpandAnimationParams(ExpandAnimationParameters params) {
         mActualHeight = params.getHeight();
         mActualWidth = params.getWidth();
-        float alphaProgress = Interpolators.ALPHA_IN.getInterpolation(
-                params.getProgress(
-                        ActivityLaunchAnimator.ANIMATION_DELAY_FADE_IN_WINDOW /* delay */,
-                        ActivityLaunchAnimator.ANIMATION_DURATION_FADE_IN_WINDOW /* duration */));
-        mBackground.setAlpha((int) (mDrawableAlpha * (1.0f - alphaProgress)));
         invalidate();
     }
 
@@ -294,8 +286,6 @@
         if (mBackground instanceof LayerDrawable) {
             GradientDrawable gradientDrawable =
                     (GradientDrawable) ((LayerDrawable) mBackground).getDrawable(0);
-            gradientDrawable.setXfermode(
-                    running ? new PorterDuffXfermode(PorterDuff.Mode.SRC) : null);
             // Speed optimization: disable AA if transfer mode is not SRC_OVER. AA is not easy to
             // spot during animation anyways.
             gradientDrawable.setAntiAlias(!running);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
index 94edbd0..64f228f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
@@ -43,6 +43,7 @@
 import android.graphics.PointF;
 import android.graphics.Rect;
 import android.os.Bundle;
+import android.os.SystemProperties;
 import android.os.UserHandle;
 import android.provider.Settings;
 import android.util.AttributeSet;
@@ -129,7 +130,12 @@
 
     public static final float BACKGROUND_ALPHA_DIMMED = 0.7f;
     private static final String TAG = "StackScroller";
-    private static final boolean DEBUG = false;
+
+    // Usage:
+    // adb shell setprop persist.debug.nssl true && adb reboot
+    private static final boolean DEBUG = SystemProperties.getBoolean("persist.debug.nssl",
+            false /* default */);
+
     private static final float RUBBER_BAND_FACTOR_NORMAL = 0.35f;
     private static final float RUBBER_BAND_FACTOR_AFTER_EXPAND = 0.15f;
     private static final float RUBBER_BAND_FACTOR_ON_PANEL_EXPAND = 0.21f;
@@ -4667,8 +4673,9 @@
     @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
         pw.println(String.format("[%s: pulsing=%s qsCustomizerShowing=%s visibility=%s"
-                        + " alpha:%f scrollY:%d maxTopPadding:%d showShelfOnly=%s"
-                        + " qsExpandFraction=%f]",
+                        + " alpha=%f scrollY:%d maxTopPadding=%d showShelfOnly=%s"
+                        + " qsExpandFraction=%f"
+                        + " hideAmount=%f]",
                 this.getClass().getSimpleName(),
                 mPulsing ? "T" : "f",
                 mAmbientState.isQsCustomizerShowing() ? "T" : "f",
@@ -4679,7 +4686,8 @@
                 mAmbientState.getScrollY(),
                 mMaxTopPadding,
                 mShouldShowShelfOnly ? "T" : "f",
-                mQsExpansionFraction));
+                mQsExpansionFraction,
+                mAmbientState.getHideAmount()));
         int childCount = getChildCount();
         pw.println("  Number of children: " + childCount);
         pw.println();
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 7737420..eb46fe3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
@@ -334,6 +334,8 @@
     private NotificationsQuickSettingsContainer mNotificationContainerParent;
     private boolean mAnimateNextPositionUpdate;
     private float mQuickQsOffsetHeight;
+    private UnlockedScreenOffAnimationController mUnlockedScreenOffAnimationController;
+
     private int mTrackingPointer;
     private VelocityTracker mQsVelocityTracker;
     private boolean mQsTracking;
@@ -577,6 +579,11 @@
      */
     private ValueAnimator mQsClippingAnimation = null;
     private final Rect mKeyguardStatusAreaClipBounds = new Rect();
+
+    /**
+     * The alpha of the views which only show on the keyguard but not in shade / shade locked
+     */
+    private float mKeyguardOnlyContentAlpha = 1.0f;
     private int mOldLayoutDirection;
     private NotificationShelfController mNotificationShelfController;
     private int mScrimCornerRadius;
@@ -670,7 +677,8 @@
             FragmentService fragmentService,
             QuickAccessWalletController quickAccessWalletController,
             @Main Executor uiExecutor,
-            SecureSettings secureSettings) {
+            SecureSettings secureSettings,
+            UnlockedScreenOffAnimationController unlockedScreenOffAnimationController) {
         super(view, falsingManager, dozeLog, keyguardStateController,
                 (SysuiStatusBarStateController) statusBarStateController, vibratorHelper,
                 statusBarKeyguardViewManager, latencyTracker, flingAnimationUtilsBuilder.get(),
@@ -765,6 +773,7 @@
         mConversationNotificationManager = conversationNotificationManager;
         mAuthController = authController;
         mLockIconViewController = lockIconViewController;
+        mUnlockedScreenOffAnimationController = unlockedScreenOffAnimationController;
 
         mView.setBackgroundColor(Color.TRANSPARENT);
         OnAttachStateChangeListener onAttachStateChangeListener = new OnAttachStateChangeListener();
@@ -1240,10 +1249,11 @@
         int userIconHeight = mKeyguardQsUserSwitchController != null
                 ? mKeyguardQsUserSwitchController.getUserIconHeight() : 0;
         float expandedFraction =
-                mKeyguardStatusViewController.isAnimatingScreenOffFromUnlocked() ? 1.0f
-                        : getExpandedFraction();
-        float darkamount = mKeyguardStatusViewController.isAnimatingScreenOffFromUnlocked() ? 1.0f
-                : mInterpolatedDarkAmount;
+                mUnlockedScreenOffAnimationController.isScreenOffAnimationPlaying()
+                        ? 1.0f : getExpandedFraction();
+        float darkamount =
+                mUnlockedScreenOffAnimationController.isScreenOffAnimationPlaying()
+                        ? 1.0f : mInterpolatedDarkAmount;
         mClockPositionAlgorithm.setup(mStatusBarHeaderHeightKeyguard,
                 totalHeight - bottomPadding,
                 mNotificationStackScrollLayoutController.getIntrinsicContentHeight(),
@@ -1412,12 +1422,13 @@
     }
 
     private void updateClock() {
-        mKeyguardStatusViewController.setAlpha(mClockPositionResult.clockAlpha);
+        float alpha = mClockPositionResult.clockAlpha * mKeyguardOnlyContentAlpha;
+        mKeyguardStatusViewController.setAlpha(alpha);
         if (mKeyguardQsUserSwitchController != null) {
-            mKeyguardQsUserSwitchController.setAlpha(mClockPositionResult.clockAlpha);
+            mKeyguardQsUserSwitchController.setAlpha(alpha);
         }
         if (mKeyguardUserSwitcherController != null) {
-            mKeyguardUserSwitcherController.setAlpha(mClockPositionResult.clockAlpha);
+            mKeyguardUserSwitcherController.setAlpha(alpha);
         }
     }
 
@@ -2024,6 +2035,7 @@
     private void maybeAnimateBottomAreaAlpha() {
         mBottomAreaShadeAlphaAnimator.cancel();
         if (mBarState == StatusBarState.SHADE_LOCKED) {
+            mBottomAreaShadeAlphaAnimator.setFloatValues(mBottomAreaShadeAlpha, 0.0f);
             mBottomAreaShadeAlphaAnimator.start();
         } else {
             mBottomAreaShadeAlpha = 1f;
@@ -2439,6 +2451,20 @@
         updateQsExpansion();
     }
 
+    /**
+     * Set the alpha of the keyguard elements which only show on the lockscreen, but not in
+     * shade locked / shade. This is used when dragging down to the full shade.
+     */
+    public void setKeyguardOnlyContentAlpha(float keyguardAlpha) {
+        mKeyguardOnlyContentAlpha = Interpolators.ALPHA_IN.getInterpolation(keyguardAlpha);
+        if (mBarState == KEYGUARD) {
+            // If the animator is running, it's already fading out the content and this is a reset
+            mBottomAreaShadeAlpha = mKeyguardOnlyContentAlpha;
+            updateKeyguardBottomAreaAlpha();
+        }
+        updateClock();
+    }
+
     private void trackMovement(MotionEvent event) {
         if (mQsVelocityTracker != null) mQsVelocityTracker.addMovement(event);
     }
@@ -2897,6 +2923,7 @@
         if (ambientIndicationContainer != null) {
             ambientIndicationContainer.setAlpha(alpha);
         }
+        mLockIconViewController.setAlpha(alpha);
     }
 
     /**
@@ -4209,7 +4236,9 @@
             int oldState = mBarState;
             boolean keyguardShowing = statusBarState == KEYGUARD;
 
-            if (mDozeParameters.shouldControlUnlockedScreenOff() && isDozing() && keyguardShowing) {
+            if (mUnlockedScreenOffAnimationController.shouldPlayScreenOffAnimation()
+                    && oldState == StatusBarState.SHADE
+                    && statusBarState == KEYGUARD) {
                 // This means we're doing the screen off animation - position the keyguard status
                 // view where it'll be on AOD, so we can animate it in.
                 mKeyguardStatusViewController.updatePosition(
@@ -4280,6 +4309,18 @@
     }
 
     /**
+     * Reconfigures the shade to show the AOD UI (clock, smartspace, etc). This is called by the
+     * screen off animation controller in order to animate in AOD without "actually" fully switching
+     * to the KEYGUARD state.
+     */
+    public void showAodUi() {
+        setDozing(true /* dozing */, false /* animate */, null);
+        mStatusBarStateListener.onStateChanged(KEYGUARD);
+        mStatusBarStateListener.onDozeAmountChanged(1f, 1f);
+        setExpandedFraction(1f);
+    }
+
+    /**
      * Sets the overstretch amount in raw pixels when dragging down.
      */
     public void setOverStrechAmount(float amount) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
index b92f7c0..1331829 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
@@ -169,6 +169,7 @@
     private final KeyguardVisibilityCallback mKeyguardVisibilityCallback;
     private final Handler mHandler;
     private final Executor mMainExecutor;
+    private UnlockedScreenOffAnimationController mUnlockedScreenOffAnimationController;
 
     private GradientColors mColors;
     private boolean mNeedsDrawableColorUpdate;
@@ -224,7 +225,8 @@
             AlarmManager alarmManager, KeyguardStateController keyguardStateController,
             DelayedWakeLock.Builder delayedWakeLockBuilder, Handler handler,
             KeyguardUpdateMonitor keyguardUpdateMonitor, DockManager dockManager,
-            ConfigurationController configurationController, @Main Executor mainExecutor) {
+            ConfigurationController configurationController, @Main Executor mainExecutor,
+            UnlockedScreenOffAnimationController unlockedScreenOffAnimationController) {
         mScrimStateListener = lightBarController::setScrimState;
         mDefaultScrimAlpha = BUSY_SCRIM_ALPHA;
         ScrimState.BUBBLE_EXPANDED.setBubbleAlpha(BUBBLE_SCRIM_ALPHA);
@@ -235,6 +237,7 @@
         mKeyguardVisibilityCallback = new KeyguardVisibilityCallback();
         mHandler = handler;
         mMainExecutor = mainExecutor;
+        mUnlockedScreenOffAnimationController = unlockedScreenOffAnimationController;
         mTimeTicker = new AlarmTimeout(alarmManager, this::onHideWallpaperTimeout,
                 "hide_aod_wallpaper", mHandler);
         mWakeLock = delayedWakeLockBuilder.setHandler(mHandler).setTag("Scrims").build();
@@ -640,17 +643,20 @@
         }
 
         if (mState == ScrimState.UNLOCKED) {
-            // Darken scrim as you pull down the shade when unlocked
-            float behindFraction = getInterpolatedFraction();
-            behindFraction = (float) Math.pow(behindFraction, 0.8f);
-            if (mClipsQsScrim) {
-                mBehindAlpha = 1;
-                mNotificationsAlpha = behindFraction * mDefaultScrimAlpha;
-            } else {
-                mBehindAlpha = behindFraction * mDefaultScrimAlpha;
-                mNotificationsAlpha = mBehindAlpha;
+            // Darken scrim as you pull down the shade when unlocked, unless the shade is expanding
+            // because we're doing the screen off animation.
+            if (!mUnlockedScreenOffAnimationController.isScreenOffAnimationPlaying()) {
+                float behindFraction = getInterpolatedFraction();
+                behindFraction = (float) Math.pow(behindFraction, 0.8f);
+                if (mClipsQsScrim) {
+                    mBehindAlpha = 1;
+                    mNotificationsAlpha = behindFraction * mDefaultScrimAlpha;
+                } else {
+                    mBehindAlpha = behindFraction * mDefaultScrimAlpha;
+                    mNotificationsAlpha = mBehindAlpha;
+                }
+                mInFrontAlpha = 0;
             }
-            mInFrontAlpha = 0;
         } else if (mState == ScrimState.BUBBLE_EXPANDED) {
             // Darken scrim as you pull down the shade when unlocked
             float behindFraction = getInterpolatedFraction();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java
index 35dda44..e52e1fa 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java
@@ -189,9 +189,11 @@
             mBubbleAlpha = ScrimController.TRANSPARENT;
 
             mAnimationDuration = ScrimController.ANIMATION_DURATION_LONG;
-            // DisplayPowerManager may blank the screen for us,
-            // in this case we just need to set our state.
-            mAnimateChange = mDozeParameters.shouldControlScreenOff();
+            // DisplayPowerManager may blank the screen for us, or we might blank it for ourselves
+            // by animating the screen off via the LightRevelScrim. In either case we just need to
+            // set our state.
+            mAnimateChange = mDozeParameters.shouldControlScreenOff()
+                    && !mDozeParameters.shouldControlUnlockedScreenOff();
         }
 
         @Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
index 2396272..8e8dcbd 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
@@ -468,6 +468,7 @@
     private final BrightnessSlider.Factory mBrightnessSliderFactory;
     private final FeatureFlags mFeatureFlags;
     private final KeyguardUnlockAnimationController mKeyguardUnlockAnimationController;
+    private final UnlockedScreenOffAnimationController mUnlockedScreenOffAnimationController;
 
     private final List<ExpansionChangedListener> mExpansionChangedListeners;
 
@@ -806,7 +807,8 @@
             StatusBarLocationPublisher locationPublisher,
             LockscreenShadeTransitionController lockscreenShadeTransitionController,
             FeatureFlags featureFlags,
-            KeyguardUnlockAnimationController keyguardUnlockAnimationController) {
+            KeyguardUnlockAnimationController keyguardUnlockAnimationController,
+            UnlockedScreenOffAnimationController unlockedScreenOffAnimationController) {
         super(context);
         mNotificationsController = notificationsController;
         mLightBarController = lightBarController;
@@ -890,6 +892,7 @@
         mStatusBarLocationPublisher = locationPublisher;
         mFeatureFlags = featureFlags;
         mKeyguardUnlockAnimationController = keyguardUnlockAnimationController;
+        mUnlockedScreenOffAnimationController = unlockedScreenOffAnimationController;
 
         mLockscreenShadeTransitionController = lockscreenShadeTransitionController;
         lockscreenShadeTransitionController.setStatusbar(this);
@@ -1244,6 +1247,7 @@
         mScrimController.attachViews(scrimBehind, notificationsScrim, scrimInFront, scrimForBubble);
 
         mLightRevealScrim = mNotificationShadeWindowView.findViewById(R.id.light_reveal_scrim);
+        mUnlockedScreenOffAnimationController.initialize(this, mLightRevealScrim);
         updateLightRevealScrimVisibility();
 
         mNotificationPanelViewController.initDependencies(
@@ -1474,7 +1478,9 @@
      * @param why the reason for the wake up
      */
     public void wakeUpIfDozing(long time, View where, String why) {
-        if (mDozing && !mKeyguardViewMediator.isAnimatingScreenOff()) {
+        if (mDozing && !(mKeyguardViewMediator.isAnimatingScreenOff()
+                || mUnlockedScreenOffAnimationController
+                    .isScreenOffLightRevealAnimationPlaying())) {
             mPowerManager.wakeUp(
                     time, PowerManager.WAKE_REASON_GESTURE, "com.android.systemui:" + why);
             mWakeUpComingFromTouch = true;
@@ -2581,7 +2587,7 @@
                     public void onAnimationEnded() {
                         mNotificationShadeWindowController.setRequestTopUi(false, TAG);
                     }
-                }, false).show(animationDelay);
+                }, false, sUiEventLogger).show(animationDelay);
     }
 
     @Override
@@ -3416,8 +3422,9 @@
             updatePanelExpansionForKeyguard();
         }
         if (shouldBeKeyguard) {
-            if (isGoingToSleep()
-                    && mScreenLifecycle.getScreenState() == ScreenLifecycle.SCREEN_TURNING_OFF) {
+            if (mUnlockedScreenOffAnimationController.isScreenOffAnimationPlaying()
+                    || (isGoingToSleep()
+                    && mScreenLifecycle.getScreenState() == ScreenLifecycle.SCREEN_TURNING_OFF)) {
                 // Delay showing the keyguard until screen turned off.
             } else {
                 showKeyguardImpl();
@@ -3588,6 +3595,7 @@
         mNotificationPanelViewController.cancelAnimation();
         mNotificationPanelViewController.setAlpha(1f);
         mNotificationPanelViewController.resetViewGroupFade();
+        updateDozingState();
         updateScrimController();
         Trace.endSection();
         return staying;
@@ -4050,6 +4058,13 @@
             mWakeUpCoordinator.setFullyAwake(false);
             mBypassHeadsUpNotifier.setFullyAwake(false);
             mKeyguardBypassController.onStartedGoingToSleep();
+
+            // The screen off animation uses our LightRevealScrim - we need to be expanded for it to
+            // be visible.
+            if (mUnlockedScreenOffAnimationController.shouldPlayScreenOffAnimation()) {
+                makeExpandedVisible(true);
+            }
+
             DejankUtils.stopDetectingBlockingIpcs(tag);
         }
 
@@ -4070,6 +4085,13 @@
             // once we fully woke up.
             updateNotificationPanelTouchState();
             mPulseExpansionHandler.onStartedWakingUp();
+
+            // If we are waking up during the screen off animation, we should undo making the
+            // expanded visible (we did that so the LightRevealScrim would be visible).
+            if (mUnlockedScreenOffAnimationController.isScreenOffLightRevealAnimationPlaying()) {
+                makeExpandedInvisible();
+            }
+
             DejankUtils.stopDetectingBlockingIpcs(tag);
         }
 
@@ -4442,8 +4464,9 @@
     }
 
     public boolean shouldIgnoreTouch() {
-        return mStatusBarStateController.isDozing()
-                && mDozeServiceHost.getIgnoreTouchWhilePulsing();
+        return (mStatusBarStateController.isDozing()
+                && mDozeServiceHost.getIgnoreTouchWhilePulsing())
+                || mUnlockedScreenOffAnimationController.isScreenOffAnimationPlaying();
     }
 
     // Begin Extra BaseStatusBar methods.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
index 91d1bd7..c0957c0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -35,6 +35,7 @@
 import android.view.ViewRootImpl;
 import android.view.WindowManagerGlobal;
 
+import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 import androidx.annotation.VisibleForTesting;
 
@@ -63,6 +64,7 @@
 
 import java.io.PrintWriter;
 import java.util.ArrayList;
+import java.util.Objects;
 import java.util.Optional;
 
 import javax.inject.Inject;
@@ -268,10 +270,23 @@
         registerListeners();
     }
 
-    public void setAlternateAuthInterceptor(@Nullable AlternateAuthInterceptor authInterceptor) {
-        final boolean newlyNull = authInterceptor == null && mAlternateAuthInterceptor != null;
+    /**
+     * Sets the given alt auth interceptor to null if it's the current auth interceptor. Else,
+     * does nothing.
+     */
+    public void removeAlternateAuthInterceptor(@NonNull AlternateAuthInterceptor authInterceptor) {
+        if (Objects.equals(mAlternateAuthInterceptor, authInterceptor)) {
+            mAlternateAuthInterceptor = null;
+            resetAlternateAuth(true);
+        }
+    }
+
+    /**
+     * Sets a new alt auth interceptor.
+     */
+    public void setAlternateAuthInterceptor(@NonNull AlternateAuthInterceptor authInterceptor) {
         mAlternateAuthInterceptor = authInterceptor;
-        resetAlternateAuth(newlyNull);
+        resetAlternateAuth(false);
     }
 
     private void registerListeners() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt
new file mode 100644
index 0000000..e135cc5
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt
@@ -0,0 +1,178 @@
+package com.android.systemui.statusbar.phone
+
+import android.animation.Animator
+import android.animation.AnimatorListenerAdapter
+import android.animation.ValueAnimator
+import android.os.Handler
+import android.view.View
+import com.android.systemui.animation.Interpolators
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.keyguard.KeyguardViewMediator
+import com.android.systemui.keyguard.WakefulnessLifecycle
+import com.android.systemui.statusbar.LightRevealScrim
+import com.android.systemui.statusbar.StatusBarState
+import com.android.systemui.statusbar.StatusBarStateControllerImpl
+import com.android.systemui.statusbar.notification.AnimatableProperty
+import com.android.systemui.statusbar.notification.PropertyAnimator
+import com.android.systemui.statusbar.notification.stack.AnimationProperties
+import com.android.systemui.statusbar.notification.stack.StackStateAnimator
+import javax.inject.Inject
+
+/**
+ * When to show the keyguard (AOD) view. This should be once the light reveal scrim is barely
+ * visible, because the transition to KEYGUARD causes brief jank.
+ */
+private const val ANIMATE_IN_KEYGUARD_DELAY = 600L
+
+/**
+ * Duration for the light reveal portion of the animation.
+ */
+private const val LIGHT_REVEAL_ANIMATION_DURATION = 750L
+
+/**
+ * Controller for the unlocked screen off animation, which runs when the device is going to sleep
+ * and we're unlocked.
+ *
+ * This animation uses a [LightRevealScrim] that lives in the status bar to hide the screen contents
+ * and then animates in the AOD UI.
+ */
+@SysUISingleton
+class UnlockedScreenOffAnimationController @Inject constructor(
+    private val wakefulnessLifecycle: WakefulnessLifecycle,
+    private val statusBarStateControllerImpl: StatusBarStateControllerImpl,
+    private val keyguardViewMediatorLazy: dagger.Lazy<KeyguardViewMediator>,
+    private val dozeParameters: DozeParameters
+) : WakefulnessLifecycle.Observer {
+    private val handler = Handler()
+
+    private lateinit var statusBar: StatusBar
+    private lateinit var lightRevealScrim: LightRevealScrim
+
+    private var lightRevealAnimationPlaying = false
+    private var aodUiAnimationPlaying = false
+
+    private val lightRevealAnimator = ValueAnimator.ofFloat(1f, 0f).apply {
+        duration = LIGHT_REVEAL_ANIMATION_DURATION
+        interpolator = Interpolators.FAST_OUT_SLOW_IN_REVERSE
+        addUpdateListener { lightRevealScrim.revealAmount = it.animatedValue as Float }
+        addListener(object : AnimatorListenerAdapter() {
+            override fun onAnimationCancel(animation: Animator?) {
+                lightRevealScrim.revealAmount = 1f
+                lightRevealAnimationPlaying = false
+            }
+
+            override fun onAnimationEnd(animation: Animator?) {
+                lightRevealAnimationPlaying = false
+            }
+        })
+    }
+
+    fun initialize(
+        statusBar: StatusBar,
+        lightRevealScrim: LightRevealScrim
+    ) {
+        this.lightRevealScrim = lightRevealScrim
+        this.statusBar = statusBar
+
+        wakefulnessLifecycle.addObserver(this)
+    }
+
+    /**
+     * Animates in the provided keyguard view, ending in the same position that it will be in on
+     * AOD.
+     */
+    fun animateInKeyguard(keyguardView: View, after: Runnable) {
+        keyguardView.alpha = 0f
+        keyguardView.visibility = View.VISIBLE
+
+        val currentY = keyguardView.y
+
+        // Move the keyguard up by 10% so we can animate it back down.
+        keyguardView.y = currentY - keyguardView.height * 0.1f
+
+        val duration = StackStateAnimator.ANIMATION_DURATION_WAKEUP
+
+        // We animate the Y properly separately using the PropertyAnimator, as the panel
+        // view also needs to update the end position.
+        PropertyAnimator.cancelAnimation(keyguardView, AnimatableProperty.Y)
+        PropertyAnimator.setProperty<View>(keyguardView, AnimatableProperty.Y, currentY,
+                AnimationProperties().setDuration(duration.toLong()),
+                true /* animate */)
+
+        keyguardView.animate()
+                .setDuration(duration.toLong())
+                .setInterpolator(Interpolators.FAST_OUT_SLOW_IN)
+                .alpha(1f)
+                .withEndAction {
+                    aodUiAnimationPlaying = false
+
+                    // Lock the keyguard if it was waiting for the screen off animation to end.
+                    keyguardViewMediatorLazy.get().maybeHandlePendingLock()
+
+                    // Tell the StatusBar to become keyguard for real - we waited on that since it
+                    // is slow and would have caused the animation to jank.
+                    statusBar.updateIsKeyguard()
+
+                    // Run the callback given to us by the KeyguardVisibilityHelper.
+                    after.run()
+                }
+                .start()
+    }
+
+    override fun onStartedWakingUp() {
+        lightRevealAnimator.cancel()
+        handler.removeCallbacksAndMessages(null)
+    }
+
+    override fun onFinishedWakingUp() {
+        // Set this to false in onFinishedWakingUp rather than onStartedWakingUp so that other
+        // observers (such as StatusBar) can ask us whether we were playing the screen off animation
+        // and reset accordingly.
+        lightRevealAnimationPlaying = false
+        aodUiAnimationPlaying = false
+
+        // Make sure the status bar is in the correct keyguard state, since we might have left it in
+        // the KEYGUARD state if this wakeup cancelled the screen off animation.
+        statusBar.updateIsKeyguard()
+    }
+
+    override fun onStartedGoingToSleep() {
+        if (shouldPlayScreenOffAnimation()) {
+            lightRevealAnimationPlaying = true
+            lightRevealAnimator.start()
+
+            handler.postDelayed({
+                aodUiAnimationPlaying = true
+
+                // Show AOD. That'll cause the KeyguardVisibilityHelper to call #animateInKeyguard.
+                statusBar.notificationPanelViewController.showAodUi()
+            }, ANIMATE_IN_KEYGUARD_DELAY)
+        }
+    }
+
+    /**
+     * Whether we should play the screen off animation when the phone starts going to sleep. We can
+     * do that if dozeParameters says we can control the unlocked screen off animation and we are in
+     * the SHADE state. If we're in KEYGUARD or SHADE_LOCKED, the regular
+     */
+    fun shouldPlayScreenOffAnimation(): Boolean {
+        return dozeParameters.shouldControlUnlockedScreenOff() &&
+                statusBarStateControllerImpl.state == StatusBarState.SHADE
+    }
+
+    /**
+     * Whether we're doing the light reveal animation or we're done with that and animating in the
+     * AOD UI.
+     */
+    fun isScreenOffAnimationPlaying(): Boolean {
+        return lightRevealAnimationPlaying || aodUiAnimationPlaying
+    }
+
+    /**
+     * Whether the light reveal animation is playing. The second part of the screen off animation,
+     * where AOD animates in, might still be playing if this returns false.
+     */
+    fun isScreenOffLightRevealAnimationPlaying(): Boolean {
+        return lightRevealAnimationPlaying
+    }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java
index 9722d68..2611ab5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java
@@ -94,6 +94,7 @@
 import com.android.systemui.statusbar.phone.StatusBarNotificationActivityStarter;
 import com.android.systemui.statusbar.phone.StatusBarSignalPolicy;
 import com.android.systemui.statusbar.phone.StatusBarTouchableRegionManager;
+import com.android.systemui.statusbar.phone.UnlockedScreenOffAnimationController;
 import com.android.systemui.statusbar.phone.ongoingcall.OngoingCallController;
 import com.android.systemui.statusbar.policy.BatteryController;
 import com.android.systemui.statusbar.policy.ConfigurationController;
@@ -216,7 +217,8 @@
             StatusBarLocationPublisher locationPublisher,
             LockscreenShadeTransitionController transitionController,
             FeatureFlags featureFlags,
-            KeyguardUnlockAnimationController keyguardUnlockAnimationController) {
+            KeyguardUnlockAnimationController keyguardUnlockAnimationController,
+            UnlockedScreenOffAnimationController unlockedScreenOffAnimationController) {
         return new StatusBar(
                 context,
                 notificationsController,
@@ -303,6 +305,7 @@
                 locationPublisher,
                 transitionController,
                 featureFlags,
-                keyguardUnlockAnimationController);
+                keyguardUnlockAnimationController,
+                unlockedScreenOffAnimationController);
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java
index f39acf9..2b79733 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java
@@ -31,6 +31,7 @@
 
 import androidx.annotation.NonNull;
 
+import com.android.internal.annotations.GuardedBy;
 import com.android.settingslib.bluetooth.BluetoothCallback;
 import com.android.settingslib.bluetooth.CachedBluetoothDevice;
 import com.android.settingslib.bluetooth.LocalBluetoothManager;
@@ -66,6 +67,7 @@
     private final WeakHashMap<CachedBluetoothDevice, ActuallyCachedState> mCachedState =
             new WeakHashMap<>();
     private final Handler mBgHandler;
+    @GuardedBy("mConnectedDevices")
     private final List<CachedBluetoothDevice> mConnectedDevices = new ArrayList<>();
 
     private boolean mEnabled;
@@ -118,7 +120,7 @@
         pw.print("  mConnectionState="); pw.println(stateToString(mConnectionState));
         pw.print("  mAudioProfileOnly="); pw.println(mAudioProfileOnly);
         pw.print("  mIsActive="); pw.println(mIsActive);
-        pw.print("  mConnectedDevices="); pw.println(mConnectedDevices);
+        pw.print("  mConnectedDevices="); pw.println(getConnectedDevices());
         pw.print("  mCallbacks.size="); pw.println(mHandler.mCallbacks.size());
         pw.println("  Bluetooth Devices:");
         for (CachedBluetoothDevice device : getDevices()) {
@@ -151,7 +153,11 @@
 
     @Override
     public List<CachedBluetoothDevice> getConnectedDevices() {
-        return mConnectedDevices;
+        List<CachedBluetoothDevice> out;
+        synchronized (mConnectedDevices) {
+            out = new ArrayList<>(mConnectedDevices);
+        }
+        return out;
     }
 
     @Override
@@ -226,8 +232,10 @@
 
     @Override
     public String getConnectedDeviceName() {
-        if (mConnectedDevices.size() == 1) {
-            return mConnectedDevices.get(0).getName();
+        synchronized (mConnectedDevices) {
+            if (mConnectedDevices.size() == 1) {
+                return mConnectedDevices.get(0).getName();
+            }
         }
         return null;
     }
@@ -242,7 +250,7 @@
     private void updateConnected() {
         // Make sure our connection state is up to date.
         int state = mLocalBluetoothManager.getBluetoothAdapter().getConnectionState();
-        mConnectedDevices.clear();
+        List<CachedBluetoothDevice> newList = new ArrayList<>();
         // If any of the devices are in a higher state than the adapter, move the adapter into
         // that state.
         for (CachedBluetoothDevice device : getDevices()) {
@@ -251,15 +259,19 @@
                 state = maxDeviceState;
             }
             if (device.isConnected()) {
-                mConnectedDevices.add(device);
+                newList.add(device);
             }
         }
 
-        if (mConnectedDevices.isEmpty() && state == BluetoothAdapter.STATE_CONNECTED) {
+        if (newList.isEmpty() && state == BluetoothAdapter.STATE_CONNECTED) {
             // If somehow we think we are connected, but have no connected devices, we aren't
             // connected.
             state = BluetoothAdapter.STATE_DISCONNECTED;
         }
+        synchronized (mConnectedDevices) {
+            mConnectedDevices.clear();
+            mConnectedDevices.addAll(newList);
+        }
         if (state != mConnectionState) {
             mConnectionState = state;
             mHandler.sendEmptyMessage(H.MSG_STATE_CHANGED);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchController.java
index 9d667805f..2ecd4b3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchController.java
@@ -45,6 +45,7 @@
 import com.android.systemui.statusbar.notification.stack.StackStateAnimator;
 import com.android.systemui.statusbar.phone.DozeParameters;
 import com.android.systemui.statusbar.phone.NotificationPanelViewController;
+import com.android.systemui.statusbar.phone.UnlockedScreenOffAnimationController;
 import com.android.systemui.statusbar.phone.UserAvatarView;
 import com.android.systemui.util.ViewController;
 
@@ -122,7 +123,8 @@
             ConfigurationController configurationController,
             SysuiStatusBarStateController statusBarStateController,
             DozeParameters dozeParameters,
-            Provider<UserDetailView.Adapter> userDetailViewAdapterProvider) {
+            Provider<UserDetailView.Adapter> userDetailViewAdapterProvider,
+            UnlockedScreenOffAnimationController unlockedScreenOffAnimationController) {
         super(view);
         if (DEBUG) Log.d(TAG, "New KeyguardQsUserSwitchController");
         mContext = context;
@@ -135,7 +137,7 @@
         mConfigurationController = configurationController;
         mStatusBarStateController = statusBarStateController;
         mKeyguardVisibilityHelper = new KeyguardVisibilityHelper(mView,
-                keyguardStateController, dozeParameters);
+                keyguardStateController, dozeParameters, unlockedScreenOffAnimationController);
         mUserDetailAdapter = new KeyguardUserDetailAdapter(context, userDetailViewAdapterProvider);
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherController.java
index e2c52f9..68f2a62 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherController.java
@@ -51,6 +51,7 @@
 import com.android.systemui.statusbar.notification.stack.AnimationProperties;
 import com.android.systemui.statusbar.notification.stack.StackStateAnimator;
 import com.android.systemui.statusbar.phone.DozeParameters;
+import com.android.systemui.statusbar.phone.UnlockedScreenOffAnimationController;
 import com.android.systemui.util.ViewController;
 
 import java.util.ArrayList;
@@ -159,7 +160,8 @@
             KeyguardStateController keyguardStateController,
             SysuiStatusBarStateController statusBarStateController,
             KeyguardUpdateMonitor keyguardUpdateMonitor,
-            DozeParameters dozeParameters) {
+            DozeParameters dozeParameters,
+            UnlockedScreenOffAnimationController unlockedScreenOffAnimationController) {
         super(keyguardUserSwitcherView);
         if (DEBUG) Log.d(TAG, "New KeyguardUserSwitcherController");
         mContext = context;
@@ -171,7 +173,7 @@
         mAdapter = new KeyguardUserAdapter(mContext, resources, layoutInflater,
                 mUserSwitcherController, this);
         mKeyguardVisibilityHelper = new KeyguardVisibilityHelper(mView,
-                keyguardStateController, dozeParameters);
+                keyguardStateController, dozeParameters, unlockedScreenOffAnimationController);
         mBackground = new KeyguardUserSwitcherScrim(context);
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/usb/UsbDebuggingActivity.java b/packages/SystemUI/src/com/android/systemui/usb/UsbDebuggingActivity.java
index b1241b1..941cd77 100644
--- a/packages/SystemUI/src/com/android/systemui/usb/UsbDebuggingActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/usb/UsbDebuggingActivity.java
@@ -68,7 +68,9 @@
 
         super.onCreate(icicle);
 
-        if (SystemProperties.getInt("service.adb.tcp.port", 0) == 0) {
+        // Emulator does not support reseating the usb cable to reshow the dialog.
+        boolean isEmulator = SystemProperties.get("ro.boot.qemu").equals("1");
+        if (SystemProperties.getInt("service.adb.tcp.port", 0) == 0 && !isEmulator) {
             mDisconnectedReceiver = new UsbDisconnectedReceiver(this);
             IntentFilter filter = new IntentFilter(UsbManager.ACTION_USB_STATE);
             mBroadcastDispatcher.registerReceiver(mDisconnectedReceiver, filter);
diff --git a/packages/SystemUI/src/com/android/systemui/util/sensors/ProximitySensor.java b/packages/SystemUI/src/com/android/systemui/util/sensors/ProximitySensor.java
index 6a648bd..19ed284 100644
--- a/packages/SystemUI/src/com/android/systemui/util/sensors/ProximitySensor.java
+++ b/packages/SystemUI/src/com/android/systemui/util/sensors/ProximitySensor.java
@@ -21,8 +21,8 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.systemui.dagger.qualifiers.Main;
-import com.android.systemui.util.Assert;
 import com.android.systemui.util.concurrency.DelayableExecutor;
+import com.android.systemui.util.concurrency.Execution;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -62,6 +62,7 @@
     private final ThresholdSensor mPrimaryThresholdSensor;
     private final ThresholdSensor mSecondaryThresholdSensor;
     private final DelayableExecutor mDelayableExecutor;
+    private final Execution mExecution;
     private final List<ThresholdSensor.Listener> mListeners = new ArrayList<>();
     private String mTag = null;
     @VisibleForTesting protected boolean mPaused;
@@ -74,14 +75,10 @@
     private boolean mInitializedListeners = false;
     private boolean mSecondarySafe = false;
 
-    private ThresholdSensor.Listener mPrimaryEventListener = new ThresholdSensor.Listener() {
-        @Override
-        public void onThresholdCrossed(ThresholdSensorEvent event) {
-            onPrimarySensorEvent(event);
-        }
-    };
+    private final ThresholdSensor.Listener mPrimaryEventListener = this::onPrimarySensorEvent;
 
-    private ThresholdSensor.Listener mSecondaryEventListener = new ThresholdSensor.Listener() {
+    private final ThresholdSensor.Listener mSecondaryEventListener =
+            new ThresholdSensor.Listener() {
         @Override
         public void onThresholdCrossed(ThresholdSensorEvent event) {
             // If we no longer have a "below" signal and the secondary sensor is not
@@ -110,12 +107,15 @@
     };
 
     @Inject
-    public ProximitySensor(@PrimaryProxSensor ThresholdSensor primary,
+    public ProximitySensor(
+            @PrimaryProxSensor ThresholdSensor primary,
             @SecondaryProxSensor ThresholdSensor  secondary,
-            @Main DelayableExecutor delayableExecutor) {
+            @Main DelayableExecutor delayableExecutor,
+            Execution execution) {
         mPrimaryThresholdSensor = primary;
         mSecondaryThresholdSensor = secondary;
         mDelayableExecutor = delayableExecutor;
+        mExecution = execution;
     }
 
     @Override
@@ -127,7 +127,7 @@
 
     @Override
     public void setDelay(int delay) {
-        Assert.isMainThread();
+        mExecution.assertIsMainThread();
         mPrimaryThresholdSensor.setDelay(delay);
         mSecondaryThresholdSensor.setDelay(delay);
     }
@@ -137,7 +137,7 @@
      */
     @Override
     public void pause() {
-        Assert.isMainThread();
+        mExecution.assertIsMainThread();
         mPaused = true;
         unregisterInternal();
     }
@@ -147,18 +147,23 @@
      */
     @Override
     public void resume() {
-        Assert.isMainThread();
+        mExecution.assertIsMainThread();
         mPaused = false;
         registerInternal();
     }
 
     /**
      * Sets that it is safe to leave the secondary sensor on indefinitely.
+     *
+     * The secondary sensor will be turned on if there are any registered listeners, regardless
+     * of what is reported by the primary sensor.
      */
     public void setSecondarySafe(boolean safe) {
         mSecondarySafe = safe;
         if (!mSecondarySafe) {
             mSecondaryThresholdSensor.pause();
+        } else {
+            mSecondaryThresholdSensor.resume();
         }
     }
 
@@ -185,7 +190,7 @@
      */
     @Override
     public void register(ThresholdSensor.Listener listener) {
-        Assert.isMainThread();
+        mExecution.assertIsMainThread();
         if (!isLoaded()) {
             return;
         }
@@ -199,13 +204,15 @@
     }
 
     protected void registerInternal() {
-        Assert.isMainThread();
+        mExecution.assertIsMainThread();
         if (mRegistered || mPaused || mListeners.isEmpty()) {
             return;
         }
         if (!mInitializedListeners) {
             mPrimaryThresholdSensor.register(mPrimaryEventListener);
-            mSecondaryThresholdSensor.pause();
+            if (!mSecondarySafe) {
+                mSecondaryThresholdSensor.pause();
+            }
             mSecondaryThresholdSensor.register(mSecondaryEventListener);
             mInitializedListeners = true;
         }
@@ -222,7 +229,7 @@
      */
     @Override
     public void unregister(ThresholdSensor.Listener listener) {
-        Assert.isMainThread();
+        mExecution.assertIsMainThread();
         mListeners.remove(listener);
         if (mListeners.size() == 0) {
             unregisterInternal();
@@ -230,7 +237,7 @@
     }
 
     protected void unregisterInternal() {
-        Assert.isMainThread();
+        mExecution.assertIsMainThread();
         if (!mRegistered) {
             return;
         }
@@ -252,7 +259,7 @@
 
     /** Update all listeners with the last value this class received from the sensor. */
     public void alertListeners() {
-        Assert.isMainThread();
+        mExecution.assertIsMainThread();
         if (mAlerting.getAndSet(true)) {
             return;
         }
@@ -267,7 +274,7 @@
     }
 
     private void onPrimarySensorEvent(ThresholdSensorEvent event) {
-        Assert.isMainThread();
+        mExecution.assertIsMainThread();
         if (mLastPrimaryEvent != null && event.getBelow() == mLastPrimaryEvent.getBelow()) {
             return;
         }
@@ -290,7 +297,7 @@
     }
 
     private void onSensorEvent(ThresholdSensorEvent event) {
-        Assert.isMainThread();
+        mExecution.assertIsMainThread();
         if (mLastEvent != null && event.getBelow() == mLastEvent.getBelow()) {
             return;
         }
@@ -306,9 +313,9 @@
     @Override
     public String toString() {
         return String.format("{registered=%s, paused=%s, near=%s, primarySensor=%s, "
-                + "secondarySensor=%s}",
+                + "secondarySensor=%s secondarySafe=%s}",
                 isRegistered(), mPaused, isNear(), mPrimaryThresholdSensor,
-                mSecondaryThresholdSensor);
+                mSecondaryThresholdSensor, mSecondarySafe);
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/util/sensors/ThresholdSensorImpl.java b/packages/SystemUI/src/com/android/systemui/util/sensors/ThresholdSensorImpl.java
index 71b2552..31c3072 100644
--- a/packages/SystemUI/src/com/android/systemui/util/sensors/ThresholdSensorImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/util/sensors/ThresholdSensorImpl.java
@@ -25,7 +25,7 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.systemui.dagger.qualifiers.Main;
-import com.android.systemui.util.Assert;
+import com.android.systemui.util.concurrency.Execution;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -37,6 +37,7 @@
     private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
 
     private final AsyncSensorManager mSensorManager;
+    private final Execution mExecution;
     private final Sensor mSensor;
     private final float mThreshold;
     private boolean mRegistered;
@@ -61,9 +62,10 @@
         }
     };
 
-    private ThresholdSensorImpl(AsyncSensorManager sensorManager,
-            Sensor sensor, float threshold, float thresholdLatch, int sensorDelay) {
+    private ThresholdSensorImpl(AsyncSensorManager sensorManager, Sensor sensor,
+            Execution execution,  float threshold, float thresholdLatch, int sensorDelay) {
         mSensorManager = sensorManager;
+        mExecution = execution;
         mSensor = sensor;
         mThreshold = threshold;
         mThresholdLatch = thresholdLatch;
@@ -107,7 +109,7 @@
      */
     @Override
     public void register(Listener listener) {
-        Assert.isMainThread();
+        mExecution.assertIsMainThread();
         if (!mListeners.contains(listener)) {
             mListeners.add(listener);
         }
@@ -116,7 +118,7 @@
 
     @Override
     public void unregister(Listener listener) {
-        Assert.isMainThread();
+        mExecution.assertIsMainThread();
         mListeners.remove(listener);
         unregisterInternal();
     }
@@ -126,7 +128,7 @@
      */
     @Override
     public void pause() {
-        Assert.isMainThread();
+        mExecution.assertIsMainThread();
         mPaused = true;
         unregisterInternal();
     }
@@ -136,7 +138,7 @@
      */
     @Override
     public void resume() {
-        Assert.isMainThread();
+        mExecution.assertIsMainThread();
         mPaused = false;
         registerInternal();
     }
@@ -148,7 +150,7 @@
     }
 
     private void registerInternal() {
-        Assert.isMainThread();
+        mExecution.assertIsMainThread();
         if (mRegistered || mPaused || mListeners.isEmpty()) {
             return;
         }
@@ -158,7 +160,7 @@
     }
 
     private void unregisterInternal() {
-        Assert.isMainThread();
+        mExecution.assertIsMainThread();
         if (!mRegistered) {
             return;
         }
@@ -177,7 +179,7 @@
      * still appears entirely binary.
      */
     private void onSensorEvent(boolean belowThreshold, boolean aboveThreshold, long timestampNs) {
-        Assert.isMainThread();
+        mExecution.assertIsMainThread();
         if (!mRegistered) {
             return;
         }
@@ -212,6 +214,7 @@
     static class Builder {
         private final Resources mResources;
         private final AsyncSensorManager mSensorManager;
+        private final Execution mExecution;
         private int mSensorDelay = SensorManager.SENSOR_DELAY_NORMAL;;
         private float mThresholdValue;
         private float mThresholdLatchValue;
@@ -221,9 +224,10 @@
         private boolean mThresholdLatchValueSet;
 
         @Inject
-        Builder(@Main Resources resources, AsyncSensorManager sensorManager) {
+        Builder(@Main Resources resources, AsyncSensorManager sensorManager, Execution execution) {
             mResources = resources;
             mSensorManager = sensorManager;
+            mExecution = execution;
         }
 
 
@@ -302,7 +306,8 @@
             }
 
             return new ThresholdSensorImpl(
-                    mSensorManager, mSensor, mThresholdValue, mThresholdLatchValue, mSensorDelay);
+                    mSensorManager, mSensor, mExecution,
+                    mThresholdValue, mThresholdLatchValue, mSensorDelay);
         }
 
         private Sensor findSensorByType(String sensorType) {
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java
index f9b6d44..83c2227 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java
@@ -25,6 +25,7 @@
 import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
 import com.android.systemui.shared.system.smartspace.SmartspaceTransitionController;
 import com.android.systemui.statusbar.phone.DozeParameters;
+import com.android.systemui.statusbar.phone.UnlockedScreenOffAnimationController;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 
@@ -56,6 +57,8 @@
     KeyguardUnlockAnimationController mKeyguardUnlockAnimationController;
     @Mock
     SmartspaceTransitionController mSmartSpaceTransitionController;
+    @Mock
+    UnlockedScreenOffAnimationController mUnlockedScreenOffAnimationController;
 
     private KeyguardStatusViewController mController;
 
@@ -72,7 +75,8 @@
                 mConfigurationController,
                 mDozeParameters,
                 mKeyguardUnlockAnimationController,
-                mSmartSpaceTransitionController);
+                mSmartSpaceTransitionController,
+                mUnlockedScreenOffAnimationController);
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/MagnificationModeSwitchTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/MagnificationModeSwitchTest.java
index 936ec80..49604ff 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/MagnificationModeSwitchTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/MagnificationModeSwitchTest.java
@@ -22,6 +22,7 @@
 import static android.view.MotionEvent.ACTION_DOWN;
 import static android.view.MotionEvent.ACTION_MOVE;
 import static android.view.MotionEvent.ACTION_UP;
+import static android.view.WindowInsets.Type.displayCutout;
 import static android.view.WindowInsets.Type.systemBars;
 import static android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction.ACTION_CLICK;
 
@@ -69,6 +70,7 @@
 import android.view.accessibility.AccessibilityNodeInfo;
 import android.widget.ImageView;
 
+import androidx.test.filters.FlakyTest;
 import androidx.test.filters.SmallTest;
 
 import com.android.internal.graphics.SfVsyncFrameCallbackProvider;
@@ -88,8 +90,9 @@
 import java.util.List;
 
 @SmallTest
+@FlakyTest(bugId = 188890599)
 @RunWith(AndroidTestingRunner.class)
-@TestableLooper.RunWithLooper
+@TestableLooper.RunWithLooper(setAsMainLooper = true)
 public class MagnificationModeSwitchTest extends SysuiTestCase {
 
     private static final float FADE_IN_ALPHA = 1f;
@@ -223,7 +226,7 @@
     }
 
     @Test
-    public void onApplyWindowInsetsWithWindowInsetsChange_buttonIsShowing_draggableBoundsChanged() {
+    public void onSystemBarsInsetsChanged_buttonIsShowing_draggableBoundsChanged() {
         mMagnificationModeSwitch.showButton(ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN);
         final Rect oldDraggableBounds = new Rect(mMagnificationModeSwitch.mDraggableWindowBounds);
 
@@ -236,6 +239,19 @@
     }
 
     @Test
+    public void onDisplayCutoutInsetsChanged_buttonIsShowing_draggableBoundsChanged() {
+        mMagnificationModeSwitch.showButton(ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN);
+        final Rect oldDraggableBounds = new Rect(mMagnificationModeSwitch.mDraggableWindowBounds);
+
+        mWindowManager.setWindowInsets(new WindowInsets.Builder()
+                .setInsetsIgnoringVisibility(displayCutout(), Insets.of(20, 30, 20, 30))
+                .build());
+        mSpyImageView.onApplyWindowInsets(WindowInsets.CONSUMED);
+
+        assertNotEquals(oldDraggableBounds, mMagnificationModeSwitch.mDraggableWindowBounds);
+    }
+
+    @Test
     public void onDraggingGestureFinish_buttonIsShowing_stickToRightEdge() {
         final int windowHalfWidth =
                 mWindowManager.getCurrentWindowMetrics().getBounds().width() / 2;
@@ -378,10 +394,26 @@
                 hasItems(new AccessibilityNodeInfo.AccessibilityAction(
                         ACTION_CLICK.getId(), mContext.getResources().getString(
                         R.string.magnification_mode_switch_click_label))));
+        assertThat(nodeInfo.getActionList(),
+                hasItems(new AccessibilityNodeInfo.AccessibilityAction(
+                        R.id.accessibility_action_move_up, mContext.getResources().getString(
+                        R.string.accessibility_control_move_up))));
+        assertThat(nodeInfo.getActionList(),
+                hasItems(new AccessibilityNodeInfo.AccessibilityAction(
+                        R.id.accessibility_action_move_down, mContext.getResources().getString(
+                        R.string.accessibility_control_move_down))));
+        assertThat(nodeInfo.getActionList(),
+                hasItems(new AccessibilityNodeInfo.AccessibilityAction(
+                        R.id.accessibility_action_move_left, mContext.getResources().getString(
+                        R.string.accessibility_control_move_left))));
+        assertThat(nodeInfo.getActionList(),
+                hasItems(new AccessibilityNodeInfo.AccessibilityAction(
+                        R.id.accessibility_action_move_right, mContext.getResources().getString(
+                        R.string.accessibility_control_move_right))));
     }
 
     @Test
-    public void performA11yActions_showWindowModeButton_verifyTapAction() {
+    public void performClickA11yActions_showWindowModeButton_verifyTapAction() {
         mMagnificationModeSwitch.showButton(ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW);
         resetAndStubMockImageViewAndAnimator();
 
@@ -392,6 +424,16 @@
     }
 
     @Test
+    public void performMoveLeftA11yAction_showButtonAtRightEdge_moveToLeftEdge() {
+        mMagnificationModeSwitch.showButton(ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW);
+
+        mSpyImageView.performAccessibilityAction(
+                R.id.accessibility_action_move_left, null);
+
+        assertLayoutPosition(/* toLeftScreenEdge= */true);
+    }
+
+    @Test
     public void showButton_showFadeOutAnimation_fadeOutAnimationCanceled() {
         mMagnificationModeSwitch.showButton(ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN);
         assertShowFadingAnimation(FADE_OUT_ALPHA);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuViewTest.java
index 0de257a..448211e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuViewTest.java
@@ -18,6 +18,8 @@
 
 import static android.view.View.OVER_SCROLL_ALWAYS;
 import static android.view.View.OVER_SCROLL_NEVER;
+import static android.view.WindowInsets.Type.ime;
+import static android.view.WindowInsets.Type.navigationBars;
 
 import static com.google.common.truth.Truth.assertThat;
 
@@ -31,9 +33,11 @@
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
 
 import android.content.Context;
 import android.content.res.Resources;
+import android.graphics.Insets;
 import android.graphics.Rect;
 import android.graphics.drawable.Drawable;
 import android.graphics.drawable.GradientDrawable;
@@ -43,7 +47,9 @@
 import android.view.MotionEvent;
 import android.view.View;
 import android.view.ViewPropertyAnimator;
+import android.view.WindowInsets;
 import android.view.WindowManager;
+import android.view.WindowMetrics;
 import android.view.accessibility.AccessibilityNodeInfo;
 
 import androidx.annotation.NonNull;
@@ -79,12 +85,17 @@
     @Mock
     private ViewPropertyAnimator mAnimator;
 
+    @Mock
+    private WindowMetrics mWindowMetrics;
+
     private MotionEvent mInterceptMotionEvent;
 
     private RecyclerView mListView;
 
     private Rect mAvailableBounds = new Rect(100, 200, 300, 400);
 
+    private int mScreenHeight;
+    private int mMenuWindowHeight;
     private int mMenuHalfWidth;
     private int mMenuHalfHeight;
     private int mScreenHalfWidth;
@@ -111,18 +122,19 @@
         final int margin =
                 res.getDimensionPixelSize(R.dimen.accessibility_floating_menu_margin);
         final int padding =
-                res.getDimensionPixelSize(R.dimen.accessibility_floating_menu_padding);
+                res.getDimensionPixelSize(R.dimen.accessibility_floating_menu_small_padding);
         final int iconWidthHeight =
                 res.getDimensionPixelSize(R.dimen.accessibility_floating_menu_small_width_height);
         final int menuWidth = padding * 2 + iconWidthHeight;
         final int menuHeight = (padding + iconWidthHeight) * mTargets.size() + padding;
         final int screenWidth = mContext.getResources().getDisplayMetrics().widthPixels;
-        final int screenHeight = mContext.getResources().getDisplayMetrics().heightPixels;
+        mScreenHeight = mContext.getResources().getDisplayMetrics().heightPixels;
         mMenuHalfWidth = menuWidth / 2;
         mMenuHalfHeight = menuHeight / 2;
         mScreenHalfWidth = screenWidth / 2;
-        mScreenHalfHeight = screenHeight / 2;
+        mScreenHalfHeight = mScreenHeight / 2;
         mMaxWindowX = screenWidth - margin - menuWidth;
+        mMenuWindowHeight = menuHeight + margin * 2;
     }
 
     @Test
@@ -464,12 +476,80 @@
         assertThat(mListView.getOverScrollMode()).isEqualTo(OVER_SCROLL_NEVER);
     }
 
+    @Test
+    public void showMenuView_insetsListener_overlapWithIme_menuViewShifted() {
+        final int offset = 200;
+
+        showMenuWithLatestStatus();
+        final WindowInsets imeInset = fakeImeInsetWith(offset);
+        when(mWindowManager.getCurrentWindowMetrics()).thenReturn(mWindowMetrics);
+        when(mWindowMetrics.getWindowInsets()).thenReturn(imeInset);
+        final int expectedLayoutY = mMenuView.mCurrentLayoutParams.y - offset;
+        mMenuView.dispatchApplyWindowInsets(imeInset);
+
+        assertThat(mMenuView.mCurrentLayoutParams.y).isEqualTo(expectedLayoutY);
+    }
+
+    @Test
+    public void hideIme_onMenuViewShifted_menuViewMovedBack() {
+        final int offset = 200;
+        showMenuWithLatestStatus();
+        final WindowInsets imeInset = fakeImeInsetWith(offset);
+        when(mWindowManager.getCurrentWindowMetrics()).thenReturn(mWindowMetrics);
+        when(mWindowMetrics.getWindowInsets()).thenReturn(imeInset);
+        final int expectedLayoutY = mMenuView.mCurrentLayoutParams.y;
+        mMenuView.dispatchApplyWindowInsets(imeInset);
+
+        mMenuView.dispatchApplyWindowInsets(
+                new WindowInsets.Builder().setVisible(ime(), false).build());
+
+        assertThat(mMenuView.mCurrentLayoutParams.y).isEqualTo(expectedLayoutY);
+    }
+
+    @Test
+    public void showMenuAndIme_withHigherIme_alignScreenTopEdge() {
+        final int offset = 99999;
+
+        showMenuWithLatestStatus();
+        final WindowInsets imeInset = fakeImeInsetWith(offset);
+        when(mWindowManager.getCurrentWindowMetrics()).thenReturn(mWindowMetrics);
+        when(mWindowMetrics.getWindowInsets()).thenReturn(imeInset);
+        mMenuView.dispatchApplyWindowInsets(imeInset);
+
+        assertThat(mMenuView.mCurrentLayoutParams.y).isEqualTo(0);
+    }
+
     @After
     public void tearDown() {
         mInterceptMotionEvent = null;
         mMotionEventHelper.recycleEvents();
     }
 
+    private void showMenuWithLatestStatus() {
+        mMenuView.show();
+        mMenuView.onTargetsChanged(mTargets);
+        mMenuView.setSizeType(0);
+        mMenuView.setShapeType(0);
+    }
+
+    /**
+     * Based on the current menu status, fake the ime inset component {@link WindowInsets} used
+     * for testing.
+     *
+     * @param offset is used for the y-axis position of ime higher than the y-axis position of menu.
+     * @return the ime inset
+     */
+    private WindowInsets fakeImeInsetWith(int offset) {
+        // Ensure the keyboard has overlapped on the menu view.
+        final int fakeImeHeight =
+                mScreenHeight - (mMenuView.mCurrentLayoutParams.y + mMenuWindowHeight) + offset;
+
+        return new WindowInsets.Builder()
+                .setVisible(ime() | navigationBars(), true)
+                .setInsets(ime() | navigationBars(), Insets.of(0, 0, 0, fakeImeHeight))
+                .build();
+    }
+
     private class TestAccessibilityFloatingMenu extends AccessibilityFloatingMenuView {
         TestAccessibilityFloatingMenu(Context context, RecyclerView listView) {
             super(context, listView);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/animation/ActivityLaunchAnimatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/animation/ActivityLaunchAnimatorTest.kt
index 9a0ed98..d499011 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/animation/ActivityLaunchAnimatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/animation/ActivityLaunchAnimatorTest.kt
@@ -2,6 +2,9 @@
 
 import android.app.ActivityManager
 import android.app.WindowConfiguration
+import android.content.ComponentName
+import android.content.pm.ActivityInfo
+import android.content.pm.ApplicationInfo
 import android.graphics.Point
 import android.graphics.Rect
 import android.os.Looper
@@ -167,10 +170,16 @@
 
     private fun fakeWindow(): RemoteAnimationTarget {
         val bounds = Rect(10 /* left */, 20 /* top */, 30 /* right */, 40 /* bottom */)
+        val taskInfo = ActivityManager.RunningTaskInfo()
+        taskInfo.topActivity = ComponentName("com.android.systemui", "FakeActivity")
+        taskInfo.topActivityInfo = ActivityInfo().apply {
+            applicationInfo = ApplicationInfo()
+        }
+
         return RemoteAnimationTarget(
                 0, RemoteAnimationTarget.MODE_OPENING, SurfaceControl(), false, Rect(), Rect(), 0,
                 Point(), Rect(), bounds, WindowConfiguration(), false, SurfaceControl(), Rect(),
-                ActivityManager.RunningTaskInfo()
+                taskInfo
         )
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.java
index f41c100..0b399cf 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.java
@@ -224,7 +224,7 @@
     public void testLayoutParams_hasSecureWindowFlag() {
         final IBinder windowToken = mock(IBinder.class);
         final WindowManager.LayoutParams layoutParams =
-                AuthContainerView.getLayoutParams(windowToken);
+                AuthContainerView.getLayoutParams(windowToken, "");
         assertTrue((layoutParams.flags & WindowManager.LayoutParams.FLAG_SECURE) != 0);
     }
 
@@ -232,7 +232,7 @@
     public void testLayoutParams_excludesImeInsets() {
         final IBinder windowToken = mock(IBinder.class);
         final WindowManager.LayoutParams layoutParams =
-                AuthContainerView.getLayoutParams(windowToken);
+                AuthContainerView.getLayoutParams(windowToken, "");
         assertTrue((layoutParams.getFitInsetsTypes() & WindowInsets.Type.ime()) == 0);
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerTest.java
index a7c63c1..5923de6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerTest.java
@@ -254,15 +254,15 @@
 
     @Test
     public void testOnDetachedStateReset() {
-        // GIVEN view is attached, alt auth is force being shown
+        // GIVEN view is attached
         mController.onViewAttached();
-        captureStatusBarStateListeners();
+        captureAltAuthInterceptor();
 
         // WHEN view is detached
         mController.onViewDetached();
 
-        // THEN set alternate auth interceptor to null
-        verify(mStatusBarKeyguardViewManager).setAlternateAuthInterceptor(null);
+        // THEN remove alternate auth interceptor
+        verify(mStatusBarKeyguardViewManager).removeAlternateAuthInterceptor(mAltAuthInterceptor);
     }
 
     private void sendStatusBarStateChanged(int statusBarState) {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
index 6f03f5d..e6f9aaf 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
@@ -49,6 +49,7 @@
 import com.android.systemui.statusbar.SysuiStatusBarStateController;
 import com.android.systemui.statusbar.phone.DozeParameters;
 import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
+import com.android.systemui.statusbar.phone.UnlockedScreenOffAnimationController;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 import com.android.systemui.util.DeviceConfigProxy;
 import com.android.systemui.util.DeviceConfigProxyFake;
@@ -82,6 +83,7 @@
     private @Mock SysuiStatusBarStateController mStatusBarStateController;
     private @Mock KeyguardStateController mKeyguardStateController;
     private @Mock KeyguardUnlockAnimationController mKeyguardUnlockAnimationController;
+    private @Mock UnlockedScreenOffAnimationController mUnlockedScreenOffAnimationController;
     private DeviceConfigProxy mDeviceConfig = new DeviceConfigProxyFake();
     private FakeExecutor mUiBgExecutor = new FakeExecutor(new FakeSystemClock());
 
@@ -101,7 +103,8 @@
                 mDismissCallbackRegistry, mUpdateMonitor, mDumpManager, mUiBgExecutor,
                 mPowerManager, mTrustManager, mDeviceConfig, mNavigationModeController,
                 mKeyguardDisplayManager, mDozeParameters, mStatusBarStateController,
-                mKeyguardStateController, () -> mKeyguardUnlockAnimationController);
+                mKeyguardStateController, () -> mKeyguardUnlockAnimationController,
+                mUnlockedScreenOffAnimationController);
         mViewMediator.start();
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileViewImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileViewImplTest.kt
index 126dca5..a2b5013 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileViewImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileViewImplTest.kt
@@ -23,6 +23,7 @@
 import android.testing.TestableLooper
 import android.text.TextUtils
 import android.view.View
+import android.widget.TextView
 import androidx.test.filters.SmallTest
 import com.android.systemui.R
 import com.android.systemui.SysuiTestCase
@@ -52,6 +53,7 @@
     @Before
     fun setUp() {
         MockitoAnnotations.initMocks(this)
+        context.ensureTestableResources()
 
         tileView = FakeTileView(context, iconView, false)
         customDrawableView = tileView.requireViewById(R.id.customDrawable)
@@ -117,7 +119,7 @@
     }
 
     @Test
-    fun testSecondaryLabelDescription_unavailable() {
+    fun testSecondaryLabelDescription_unavailable_default() {
         val state = QSTile.State()
         state.state = Tile.STATE_UNAVAILABLE
         state.secondaryLabel = ""
@@ -130,7 +132,7 @@
     }
 
     @Test
-    fun testSecondaryLabelDescription_booleanInactive() {
+    fun testSecondaryLabelDescription_booleanInactive_default() {
         val state = QSTile.BooleanState()
         state.state = Tile.STATE_INACTIVE
         state.secondaryLabel = ""
@@ -143,7 +145,7 @@
     }
 
     @Test
-    fun testSecondaryLabelDescription_booleanActive() {
+    fun testSecondaryLabelDescription_booleanActive_default() {
         val state = QSTile.BooleanState()
         state.state = Tile.STATE_ACTIVE
         state.secondaryLabel = ""
@@ -220,6 +222,41 @@
         assertThat(chevronView.visibility).isEqualTo(View.GONE)
     }
 
+    @Test
+    fun testUseStateStringsForKnownSpec_Boolean() {
+        val state = QSTile.BooleanState()
+        val spec = "internet"
+        state.spec = spec
+
+        val unavailableString = "${spec}_unavailable"
+        val offString = "${spec}_off"
+        val onString = "${spec}_on"
+
+        context.orCreateTestableResources.addOverride(R.array.tile_states_internet, arrayOf(
+            unavailableString,
+            offString,
+            onString
+        ))
+
+        // State UNAVAILABLE
+        state.secondaryLabel = ""
+        state.state = Tile.STATE_UNAVAILABLE
+        tileView.changeState(state)
+        assertThat((tileView.secondaryLabel as TextView).text).isEqualTo(unavailableString)
+
+        // State INACTIVE
+        state.secondaryLabel = ""
+        state.state = Tile.STATE_INACTIVE
+        tileView.changeState(state)
+        assertThat((tileView.secondaryLabel as TextView).text).isEqualTo(offString)
+
+        // State ACTIVE
+        state.secondaryLabel = ""
+        state.state = Tile.STATE_ACTIVE
+        tileView.changeState(state)
+        assertThat((tileView.secondaryLabel as TextView).text).isEqualTo(onString)
+    }
+
     class FakeTileView(
         context: Context,
         icon: QSIconView,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/TilesStatesTextTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/TilesStatesTextTest.kt
new file mode 100644
index 0000000..19ffa49
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/TilesStatesTextTest.kt
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2021 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.tileimpl
+
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.R
+import com.android.systemui.SysuiTestCase
+import com.google.common.truth.Truth.assertThat
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertNotEquals
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+class TilesStatesTextTest : SysuiTestCase() {
+
+    @Test
+    fun testStockTilesHaveStatesArray() {
+        val tiles = mContext.getString(R.string.quick_settings_tiles_stock).split(",")
+        tiles.forEach { spec ->
+            val resName = "${QSTileViewImpl.TILE_STATE_RES_PREFIX}$spec"
+            val resId = mContext.resources.getIdentifier(resName, "array", mContext.packageName)
+
+            assertNotEquals("Missing resource for $resName", 0, resId)
+
+            val array = mContext.resources.getStringArray(resId)
+
+            assertEquals("Array for $spec is of wrong size", 3, array.size)
+        }
+    }
+
+    @Test
+    fun testDefaultArray() {
+        val array = mContext.resources.getStringArray(R.array.tile_states_default)
+
+        assertThat(array.size).isEqualTo(3)
+    }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/charging/WiredChargingRippleControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/charging/WiredChargingRippleControllerTest.kt
index 03744b7..85ec3fa 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/charging/WiredChargingRippleControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/charging/WiredChargingRippleControllerTest.kt
@@ -20,6 +20,7 @@
 import android.view.View
 import android.view.WindowManager
 import androidx.test.filters.SmallTest
+import com.android.internal.logging.UiEventLogger
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.statusbar.FeatureFlags
 import com.android.systemui.statusbar.commandline.CommandRegistry
@@ -50,6 +51,7 @@
     @Mock private lateinit var configurationController: ConfigurationController
     @Mock private lateinit var rippleView: ChargingRippleView
     @Mock private lateinit var windowManager: WindowManager
+    @Mock private lateinit var uiEventLogger: UiEventLogger
     private val systemClock = FakeSystemClock()
 
     @Before
@@ -58,7 +60,7 @@
         `when`(featureFlags.isChargingRippleEnabled).thenReturn(true)
         controller = WiredChargingRippleController(
                 commandRegistry, batteryController, configurationController,
-                featureFlags, context, windowManager, systemClock)
+                featureFlags, context, windowManager, systemClock, uiEventLogger)
         controller.rippleView = rippleView // Replace the real ripple view with a mock instance
     }
 
@@ -87,6 +89,10 @@
         // Verify ripple removed
         runnableCaptor.value.run()
         verify(windowManager).removeView(rippleView)
+
+        // Verify event logged
+        verify(uiEventLogger).log(
+                WiredChargingRippleController.WiredChargingRippleEvent.CHARGING_RIPPLE_PLAYED)
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewTest.java
index ee8d120..ffb53a8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewTest.java
@@ -160,6 +160,8 @@
     @Mock
     private DozeParameters mDozeParameters;
     @Mock
+    private UnlockedScreenOffAnimationController mUnlockedScreenOffAnimationController;
+    @Mock
     private NotificationPanelView mView;
     @Mock
     private LayoutInflater mLayoutInflater;
@@ -328,7 +330,8 @@
                         mock(HeadsUpManagerPhone.class),
                         new StatusBarStateControllerImpl(new UiEventLoggerFake()),
                         mKeyguardBypassController,
-                        mDozeParameters);
+                        mDozeParameters,
+                        mUnlockedScreenOffAnimationController);
         PulseExpansionHandler expansionHandler = new PulseExpansionHandler(
                 mContext,
                 coordinator,
@@ -386,7 +389,8 @@
                 mFragmentService,
                 mQuickAccessWalletController,
                 new FakeExecutor(new FakeSystemClock()),
-                mSecureSettings);
+                mSecureSettings,
+                mUnlockedScreenOffAnimationController);
         mNotificationPanelViewController.initDependencies(
                 mStatusBar,
                 mNotificationShelfController);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
index 8b0b579..075d1dd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
@@ -108,6 +108,8 @@
     private DockManager mDockManager;
     @Mock
     private ConfigurationController mConfigurationController;
+    @Mock
+    private UnlockedScreenOffAnimationController mUnlockedScreenOffAnimationController;
 
 
     private static class AnimatorListener implements Animator.AnimatorListener {
@@ -221,7 +223,8 @@
         mScrimController = new ScrimController(mLightBarController,
                 mDozeParameters, mAlarmManager, mKeyguardStateController, mDelayedWakeLockBuilder,
                 new FakeHandler(mLooper.getLooper()), mKeyguardUpdateMonitor,
-                mDockManager, mConfigurationController, new FakeExecutor(new FakeSystemClock()));
+                mDockManager, mConfigurationController, new FakeExecutor(new FakeSystemClock()),
+                mUnlockedScreenOffAnimationController);
         mScrimController.setScrimVisibleListener(visible -> mScrimVisibility = visible);
         mScrimController.attachViews(mScrimBehind, mNotificationsScrim, mScrimInFront,
                 mScrimForBubble);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
index 5a3683e..deff204 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
@@ -272,6 +272,7 @@
     @Mock private FeatureFlags mFeatureFlags;
     @Mock private IWallpaperManager mWallpaperManager;
     @Mock private KeyguardUnlockAnimationController mKeyguardUnlockAnimationController;
+    @Mock private UnlockedScreenOffAnimationController mUnlockedScreenOffAnimationController;
     private ShadeController mShadeController;
     private FakeExecutor mUiBgExecutor = new FakeExecutor(new FakeSystemClock());
     private InitController mInitController = new InitController();
@@ -441,7 +442,8 @@
                 mLocationPublisher,
                 mLockscreenTransitionController,
                 mFeatureFlags,
-                mKeyguardUnlockAnimationController);
+                mKeyguardUnlockAnimationController,
+                mUnlockedScreenOffAnimationController);
         when(mKeyguardViewMediator.registerStatusBar(any(StatusBar.class), any(ViewGroup.class),
                 any(NotificationPanelViewController.class), any(BiometricUnlockController.class),
                 any(ViewGroup.class), any(KeyguardBypassController.class)))
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/sensors/FakeProximitySensor.java b/packages/SystemUI/tests/src/com/android/systemui/util/sensors/FakeProximitySensor.java
index 9bb4c4b..50947ab 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/util/sensors/FakeProximitySensor.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/sensors/FakeProximitySensor.java
@@ -17,6 +17,7 @@
 package com.android.systemui.util.sensors;
 
 import com.android.systemui.util.concurrency.DelayableExecutor;
+import com.android.systemui.util.concurrency.FakeExecution;
 
 public class FakeProximitySensor extends ProximitySensor {
     private boolean mAvailable;
@@ -25,7 +26,7 @@
     public FakeProximitySensor(ThresholdSensor primary, ThresholdSensor secondary,
             DelayableExecutor delayableExecutor) {
         super(primary, secondary == null ? new FakeThresholdSensor() : secondary,
-                delayableExecutor);
+                delayableExecutor, new FakeExecution());
         mAvailable = true;
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/sensors/ProximitySensorDualTest.java b/packages/SystemUI/tests/src/com/android/systemui/util/sensors/ProximitySensorDualTest.java
index bae1d98..8f07545 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/util/sensors/ProximitySensorDualTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/sensors/ProximitySensorDualTest.java
@@ -27,6 +27,7 @@
 import androidx.test.filters.SmallTest;
 
 import com.android.systemui.SysuiTestCase;
+import com.android.systemui.util.concurrency.FakeExecution;
 import com.android.systemui.util.concurrency.FakeExecutor;
 import com.android.systemui.util.time.FakeSystemClock;
 
@@ -54,7 +55,8 @@
         mThresholdSensorSecondary.setLoaded(true);
 
         mProximitySensor = new ProximitySensor(
-                mThresholdSensorPrimary, mThresholdSensorSecondary, mFakeExecutor);
+                mThresholdSensorPrimary, mThresholdSensorSecondary, mFakeExecutor,
+                new FakeExecution());
     }
 
     @Test
@@ -324,9 +326,10 @@
 
         TestableListener listener = new TestableListener();
 
+        // WE immediately register the secondary sensor.
         mProximitySensor.register(listener);
         assertFalse(mThresholdSensorPrimary.isPaused());
-        assertTrue(mThresholdSensorSecondary.isPaused());
+        assertFalse(mThresholdSensorSecondary.isPaused());
         assertNull(listener.mLastEvent);
         assertEquals(0, listener.mCallCount);
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/sensors/ProximitySensorSingleTest.java b/packages/SystemUI/tests/src/com/android/systemui/util/sensors/ProximitySensorSingleTest.java
index f2d5284..6c6d355 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/util/sensors/ProximitySensorSingleTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/sensors/ProximitySensorSingleTest.java
@@ -27,6 +27,7 @@
 import androidx.test.filters.SmallTest;
 
 import com.android.systemui.SysuiTestCase;
+import com.android.systemui.util.concurrency.FakeExecution;
 import com.android.systemui.util.concurrency.FakeExecutor;
 import com.android.systemui.util.time.FakeSystemClock;
 
@@ -54,7 +55,7 @@
         mThresholdSensor.setLoaded(true);
 
         mProximitySensor = new ProximitySensor(
-                mThresholdSensor, new FakeThresholdSensor(), mFakeExecutor);
+                mThresholdSensor, new FakeThresholdSensor(), mFakeExecutor, new FakeExecution());
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/sensors/ThresholdSensorImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/util/sensors/ThresholdSensorImplTest.java
index d3a35a7..1276567 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/util/sensors/ThresholdSensorImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/sensors/ThresholdSensorImplTest.java
@@ -24,7 +24,7 @@
 import android.testing.AndroidTestingRunner;
 
 import com.android.systemui.SysuiTestCase;
-import com.android.systemui.util.Assert;
+import com.android.systemui.util.concurrency.FakeExecution;
 import com.android.systemui.util.concurrency.FakeExecutor;
 import com.android.systemui.util.concurrency.FakeThreadFactory;
 import com.android.systemui.util.time.FakeSystemClock;
@@ -52,7 +52,7 @@
 
         mFakeProximitySensor = mSensorManager.getFakeProximitySensor();
         ThresholdSensorImpl.Builder thresholdSensorBuilder = new ThresholdSensorImpl.Builder(
-                null, mAsyncSensorManager);
+                null, mAsyncSensorManager, new FakeExecution());
         mThresholdSensor = (ThresholdSensorImpl) thresholdSensorBuilder
                 .setSensor(mFakeProximitySensor.getSensor())
                 .setThresholdValue(mFakeProximitySensor.getSensor().getMaximumRange())
@@ -61,7 +61,6 @@
 
     @Test
     public void testSingleListener() {
-        Assert.setTestThread(Thread.currentThread());
         TestableListener listener = new TestableListener();
 
         assertFalse(mThresholdSensor.isRegistered());
@@ -83,7 +82,6 @@
 
     @Test
     public void testMultiListener() {
-        Assert.setTestThread(Thread.currentThread());
         TestableListener listenerA = new TestableListener();
         TestableListener listenerB = new TestableListener();
 
@@ -117,7 +115,6 @@
 
     @Test
     public void testDuplicateListener() {
-        Assert.setTestThread(Thread.currentThread());
         TestableListener listenerA = new TestableListener();
 
         assertFalse(mThresholdSensor.isRegistered());
@@ -142,7 +139,6 @@
     }
     @Test
     public void testUnregister() {
-        Assert.setTestThread(Thread.currentThread());
         TestableListener listener = new TestableListener();
 
         assertFalse(mThresholdSensor.isRegistered());
@@ -162,7 +158,6 @@
 
     @Test
     public void testPauseAndResume() {
-        Assert.setTestThread(Thread.currentThread());
         TestableListener listener = new TestableListener();
 
         assertFalse(mThresholdSensor.isRegistered());
@@ -205,7 +200,6 @@
 
     @Test
     public void testAlertListeners() {
-        Assert.setTestThread(Thread.currentThread());
         TestableListener listenerA = new TestableListener();
         TestableListener listenerB = new TestableListener();
 
@@ -237,12 +231,11 @@
 
     @Test
     public void testHysteresis() {
-        Assert.setTestThread(Thread.currentThread());
         float lowValue = 10f;
         float highValue = 100f;
         FakeSensorManager.FakeGenericSensor sensor = mSensorManager.getFakeLightSensor();
         ThresholdSensorImpl.Builder thresholdSensorBuilder = new ThresholdSensorImpl.Builder(
-                null, mAsyncSensorManager);
+                null, mAsyncSensorManager, new FakeExecution());
         ThresholdSensorImpl thresholdSensor = (ThresholdSensorImpl) thresholdSensorBuilder
                 .setSensor(sensor.getSensor())
                 .setThresholdValue(lowValue)
@@ -286,7 +279,6 @@
 
     @Test
     public void testAlertAfterPause() {
-        Assert.setTestThread(Thread.currentThread());
         TestableListener listener = new TestableListener();
 
         mThresholdSensor.register(listener);
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index 71d6a48..8041ec4 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -79,8 +79,10 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.UptimeMillisLong;
+import android.annotation.UserIdInt;
 import android.app.ActivityManager;
 import android.app.ActivityManagerInternal;
+import android.app.ActivityManagerInternal.ServiceNotificationPolicy;
 import android.app.ActivityThread;
 import android.app.AppGlobals;
 import android.app.AppOpsManager;
@@ -213,6 +215,9 @@
     // at the same time.
     final int mMaxStartingBackground;
 
+    /**
+     * Master service bookkeeping, keyed by user number.
+     */
     final SparseArray<ServiceMap> mServiceMap = new SparseArray<>();
 
     /**
@@ -1811,7 +1816,7 @@
                         showFgsBgRestrictedNotificationLocked(r);
                         updateServiceForegroundLocked(psr, true);
                         ignoreForeground = true;
-                        logForegroundServiceStateChanged(r,
+                        logFGSStateChangeLocked(r,
                                 FrameworkStatsLog.FOREGROUND_SERVICE_STATE_CHANGED__STATE__DENIED,
                                 0);
                         if (CompatChanges.isChangeEnabled(FGS_START_EXCEPTION_CHANGE_ID,
@@ -1859,6 +1864,7 @@
                             active.mNumActive++;
                         }
                         r.isForeground = true;
+                        r.mLogEntering = true;
                         enterForeground = true;
                         r.mStartForegroundCount++;
                         r.mFgsEnterTime = SystemClock.uptimeMillis();
@@ -1881,14 +1887,7 @@
                     }
                     // Even if the service is already a FGS, we need to update the notification,
                     // so we need to call it again.
-                    postFgsNotificationLocked(r);
-                    if (enterForeground) {
-                        // Because we want to log what's updated in postFgsNotificationLocked(),
-                        // this must be called after postFgsNotificationLocked().
-                        logForegroundServiceStateChanged(r,
-                                FrameworkStatsLog.FOREGROUND_SERVICE_STATE_CHANGED__STATE__ENTER,
-                                0);
-                    }
+                    r.postNotification();
                     if (r.app != null) {
                         updateServiceForegroundLocked(psr, true);
                     }
@@ -1937,7 +1936,7 @@
                         AppOpsManager.getToken(mAm.mAppOpsService),
                         AppOpsManager.OP_START_FOREGROUND, r.appInfo.uid, r.packageName, null);
                 unregisterAppOpCallbackLocked(r);
-                logForegroundServiceStateChanged(r,
+                logFGSStateChangeLocked(r,
                         FrameworkStatsLog.FOREGROUND_SERVICE_STATE_CHANGED__STATE__EXIT,
                         r.mFgsExitTime > r.mFgsEnterTime
                                 ? (int)(r.mFgsExitTime - r.mFgsEnterTime) : 0);
@@ -1964,7 +1963,18 @@
         }
     }
 
-    private boolean withinFgsDeferRateLimit(final int uid, final long now) {
+    private boolean withinFgsDeferRateLimit(ServiceRecord sr, final long now) {
+        // If we're still within the service's deferral period, then by definition
+        // deferral is not rate limited.
+        if (now < sr.fgDisplayTime) {
+            if (DEBUG_FOREGROUND_SERVICE) {
+                Slog.d(TAG_SERVICE, "FGS transition for " + sr
+                        + " within deferral period, no rate limit applied");
+            }
+            return false;
+        }
+
+        final int uid = sr.appInfo.uid;
         final long eligible = mFgsDeferralEligible.get(uid, 0L);
         if (DEBUG_FOREGROUND_SERVICE) {
             if (now < eligible) {
@@ -1975,62 +1985,137 @@
         return now < eligible;
     }
 
-    // TODO: remove as part of fixing b/173627642
+    ServiceNotificationPolicy applyForegroundServiceNotificationLocked(Notification notification,
+            final int id, final String pkg, final int userId) {
+        if (DEBUG_FOREGROUND_SERVICE) {
+            Slog.d(TAG_SERVICE, "Evaluating FGS policy for id=" + id
+                    + " pkg=" + pkg + " not=" + notification);
+        }
+        // Is there an FGS using this notification?
+        final ServiceMap smap = mServiceMap.get(userId);
+        if (smap == null) {
+            // No services in this user at all
+            return ServiceNotificationPolicy.NOT_FOREGROUND_SERVICE;
+        }
+
+        for (int i = 0; i < smap.mServicesByInstanceName.size(); i++) {
+            final ServiceRecord sr = smap.mServicesByInstanceName.valueAt(i);
+            if (id != sr.foregroundId || !pkg.equals(sr.appInfo.packageName)) {
+                // Not this one; keep looking
+                continue;
+            }
+
+            // Found; it is associated with an FGS.  Make sure that it's flagged:
+            // it may have entered the bookkeeping outside of Service-related
+            // APIs.  We also make sure to take this latest Notification as
+            // the content to be shown (immediately or eventually).
+            if (DEBUG_FOREGROUND_SERVICE) {
+                Slog.d(TAG_SERVICE, "   FOUND: notification is for " + sr);
+            }
+            notification.flags |= Notification.FLAG_FOREGROUND_SERVICE;
+            sr.foregroundNoti = notification;
+
+            // ...and determine immediate vs deferred display policy for it
+            final boolean showNow = shouldShowFgsNotificationLocked(sr);
+            if (showNow) {
+                if (DEBUG_FOREGROUND_SERVICE) {
+                    Slog.d(TAG_SERVICE, "   Showing immediately due to policy");
+                }
+                sr.mFgsNotificationDeferred = false;
+                return ServiceNotificationPolicy.SHOW_IMMEDIATELY;
+            }
+
+            // Deferring - kick off the timer if necessary, and tell the caller
+            // that it's to be shown only if it's an update to already-
+            // visible content (e.g. if it's an FGS adopting a
+            // previously-posted Notification).
+            if (DEBUG_FOREGROUND_SERVICE) {
+                Slog.d(TAG_SERVICE, "   Deferring / update-only");
+            }
+            startFgsDeferralTimerLocked(sr);
+            return ServiceNotificationPolicy.UPDATE_ONLY;
+        }
+
+        // None of the services in this user are FGSs
+        return ServiceNotificationPolicy.NOT_FOREGROUND_SERVICE;
+    }
+
+    // No legacy-app behavior skew intended but there's a runtime E-stop if a need
+    // arises, so note that
     @SuppressWarnings("AndroidFrameworkCompatChange")
-    private void postFgsNotificationLocked(ServiceRecord r) {
-        final int uid = r.appInfo.uid;
+    private boolean shouldShowFgsNotificationLocked(ServiceRecord r) {
         final long now = SystemClock.uptimeMillis();
-        final boolean isLegacyApp = (r.appInfo.targetSdkVersion < Build.VERSION_CODES.S);
 
         // Is the behavior enabled at all?
-        boolean showNow = !mAm.mConstants.mFlagFgsNotificationDeferralEnabled;
-        if (!showNow) {
-            // Did the app have another FGS notification deferred recently?
-            showNow = withinFgsDeferRateLimit(uid, now);
+        if (!mAm.mConstants.mFlagFgsNotificationDeferralEnabled) {
+            return true;
         }
-        if (!showNow) {
-            // Legacy apps' FGS notifications are not deferred unless the relevant
+
+        // Has this service's deferral timer expired?
+        if (r.mFgsNotificationDeferred && now >= r.fgDisplayTime) {
+            if (DEBUG_FOREGROUND_SERVICE) {
+                Slog.d(TAG, "FGS reached end of deferral period: " + r);
+            }
+            return true;
+        }
+
+        // Did the app have another FGS notification deferred recently?
+        if (withinFgsDeferRateLimit(r, now)) {
+            return true;
+        }
+
+        if (mAm.mConstants.mFlagFgsNotificationDeferralApiGated) {
+            // Legacy apps' FGS notifications are also deferred unless the relevant
             // DeviceConfig element has been set
-            showNow = isLegacyApp && mAm.mConstants.mFlagFgsNotificationDeferralApiGated;
+            final boolean isLegacyApp = (r.appInfo.targetSdkVersion < Build.VERSION_CODES.S);
+            if (isLegacyApp) {
+                return true;
+            }
         }
-        if (!showNow) {
-            // has the app forced deferral?
-            if (!r.foregroundNoti.isForegroundDisplayForceDeferred()) {
-                // is the notification such that it should show right away?
-                showNow = r.foregroundNoti.shouldShowForegroundImmediately();
-                if (DEBUG_FOREGROUND_SERVICE && showNow) {
+
+        // did we already show it?
+        if (r.mFgsNotificationShown) {
+            return true;
+        }
+
+        // has the app forced deferral?
+        if (!r.foregroundNoti.isForegroundDisplayForceDeferred()) {
+            // is the notification such that it should show right away?
+            if (r.foregroundNoti.shouldShowForegroundImmediately()) {
+                if (DEBUG_FOREGROUND_SERVICE) {
                     Slog.d(TAG_SERVICE, "FGS " + r
                             + " notification policy says show immediately");
                 }
-                // or is this an type of FGS that always shows immediately?
-                if (!showNow) {
-                    if ((r.foregroundServiceType & FGS_IMMEDIATE_DISPLAY_MASK) != 0) {
-                        if (DEBUG_FOREGROUND_SERVICE) {
-                            Slog.d(TAG_SERVICE, "FGS " + r
-                                    + " type gets immediate display");
-                        }
-                        showNow = true;
-                    }
-                }
-            } else {
+                return true;
+            }
+
+            // or is this an type of FGS that always shows immediately?
+            if ((r.foregroundServiceType & FGS_IMMEDIATE_DISPLAY_MASK) != 0) {
                 if (DEBUG_FOREGROUND_SERVICE) {
-                    Slog.d(TAG_SERVICE, "FGS " + r + " notification is app deferred");
+                    Slog.d(TAG_SERVICE, "FGS " + r
+                            + " type gets immediate display");
                 }
+                return true;
             }
-        }
 
-        if (showNow) {
+            // fall through to return false: no policy dictates immediate display
+        } else {
             if (DEBUG_FOREGROUND_SERVICE) {
-                Slog.d(TAG_SERVICE, "FGS " + r + " non-deferred notification");
+                Slog.d(TAG_SERVICE, "FGS " + r + " notification is app deferred");
             }
-            r.postNotification();
-            r.mFgsNotificationDeferred = false;
-            r.mFgsNotificationShown = true;
-            return;
+            // fall through to return false
         }
 
-        r.mFgsNotificationDeferred = true;
-        r.mFgsNotificationShown = false;
+        return false;
+    }
+
+    // Target SDK consultation here is strictly for logging purposes, not
+    // behavioral variation.
+    @SuppressWarnings("AndroidFrameworkCompatChange")
+    private void startFgsDeferralTimerLocked(ServiceRecord r) {
+        final long now = SystemClock.uptimeMillis();
+        final int uid = r.appInfo.uid;
+
         // schedule the actual notification post
         long when = now + mAm.mConstants.mFgsNotificationDeferralInterval;
         // If there are already deferred FGS notifications for this app,
@@ -2053,11 +2138,14 @@
         final long nextEligible = when + mAm.mConstants.mFgsNotificationDeferralExclusionTime;
         mFgsDeferralEligible.put(uid, nextEligible);
         r.fgDisplayTime = when;
+        r.mFgsNotificationDeferred = true;
+        r.mFgsNotificationShown = false;
         mPendingFgsNotifications.add(r);
         if (DEBUG_FOREGROUND_SERVICE) {
             Slog.d(TAG_SERVICE, "FGS " + r
                     + " notification in " + (when - now) + " ms");
         }
+        final boolean isLegacyApp = (r.appInfo.targetSdkVersion < Build.VERSION_CODES.S);
         if (isLegacyApp) {
             Slog.i(TAG_SERVICE, "Deferring FGS notification in legacy app "
                     + r.appInfo.packageName + "/" + UserHandle.formatUid(r.appInfo.uid)
@@ -2089,9 +2177,16 @@
                         if (r.isForeground && r.app != null) {
                             r.postNotification();
                             r.mFgsNotificationShown = true;
-                        } else if (DEBUG_FOREGROUND_SERVICE) {
-                            Slog.d(TAG_SERVICE, "  - service no longer running/fg, ignoring");
+                        } else {
+                            if (DEBUG_FOREGROUND_SERVICE) {
+                                Slog.d(TAG_SERVICE, "  - service no longer running/fg, ignoring");
+                            }
                         }
+                        // Regardless of whether we needed to post the notification or the
+                        // service is no longer running, we may not have logged its FGS
+                        // transition yet depending on the timing and API sequence that led
+                        // to this point - so make sure to do so.
+                        maybeLogFGSStateEnteredLocked(r);
                     }
                 }
                 if (DEBUG_FOREGROUND_SERVICE) {
@@ -2102,6 +2197,60 @@
         }
     };
 
+    private void maybeLogFGSStateEnteredLocked(ServiceRecord r) {
+        if (r.mLogEntering) {
+            logFGSStateChangeLocked(r,
+                    FrameworkStatsLog
+                            .FOREGROUND_SERVICE_STATE_CHANGED__STATE__ENTER,
+                    0);
+            r.mLogEntering = false;
+        }
+    }
+
+    /**
+     * Callback from NotificationManagerService whenever it posts a notification
+     * associated with a foreground service.  This is the unified handling point
+     * for the disjoint code flows that affect an FGS's notifiation content and
+     * visibility, starting with both Service.startForeground() and
+     * NotificationManager.notify().
+     */
+    public void onForegroundServiceNotificationUpdateLocked(Notification notification,
+            final int id, final String pkg, @UserIdInt final int userId) {
+        // If this happens to be a Notification for an FGS still in its deferral period,
+        // drop the deferral and make sure our content bookkeeping is up to date.
+        for (int i = mPendingFgsNotifications.size() - 1; i >= 0; i--) {
+            final ServiceRecord sr = mPendingFgsNotifications.get(i);
+            if (userId == sr.userId
+                    && id == sr.foregroundId
+                    && sr.appInfo.packageName.equals(pkg)) {
+                if (DEBUG_FOREGROUND_SERVICE) {
+                    Slog.d(TAG_SERVICE, "Notification shown; canceling deferral of "
+                            + sr);
+                }
+                maybeLogFGSStateEnteredLocked(sr);
+                sr.mFgsNotificationShown = true;
+                sr.mFgsNotificationDeferred = false;
+                mPendingFgsNotifications.remove(i);
+            }
+        }
+        // And make sure to retain the latest notification content for the FGS
+        ServiceMap smap = mServiceMap.get(userId);
+        if (smap != null) {
+            for (int i = 0; i < smap.mServicesByInstanceName.size(); i++) {
+                final ServiceRecord sr = smap.mServicesByInstanceName.valueAt(i);
+                if (sr.isForeground
+                        && id == sr.foregroundId
+                        && sr.appInfo.packageName.equals(pkg)) {
+                    if (DEBUG_FOREGROUND_SERVICE) {
+                        Slog.d(TAG_SERVICE, "Recording shown notification for "
+                                + sr);
+                    }
+                    sr.foregroundNoti = notification;
+                }
+            }
+        }
+    }
+
     /** Registers an AppOpCallback for monitoring special AppOps for this foreground service. */
     private void registerAppOpCallbackLocked(@NonNull ServiceRecord r) {
         if (r.app == null) {
@@ -4016,7 +4165,7 @@
                     AppOpsManager.OP_START_FOREGROUND, r.appInfo.uid, r.packageName, null);
             unregisterAppOpCallbackLocked(r);
             r.mFgsExitTime = SystemClock.uptimeMillis();
-            logForegroundServiceStateChanged(r,
+            logFGSStateChangeLocked(r,
                     FrameworkStatsLog.FOREGROUND_SERVICE_STATE_CHANGED__STATE__EXIT,
                     r.mFgsExitTime > r.mFgsEnterTime
                             ? (int)(r.mFgsExitTime - r.mFgsEnterTime) : 0);
@@ -6022,7 +6171,7 @@
      * @param state one of ENTER/EXIT/DENIED event.
      * @param durationMs Only meaningful for EXIT event, the duration from ENTER and EXIT state.
      */
-    private void logForegroundServiceStateChanged(ServiceRecord r, int state, int durationMs) {
+    private void logFGSStateChangeLocked(ServiceRecord r, int state, int durationMs) {
         if (!ActivityManagerUtils.shouldSamplePackageForAtom(
                 r.packageName, mAm.mConstants.mFgsAtomSampleRate)) {
             return;
diff --git a/services/core/java/com/android/server/am/ActivityManagerConstants.java b/services/core/java/com/android/server/am/ActivityManagerConstants.java
index 0fff8be..0d19efc 100644
--- a/services/core/java/com/android/server/am/ActivityManagerConstants.java
+++ b/services/core/java/com/android/server/am/ActivityManagerConstants.java
@@ -453,7 +453,7 @@
     volatile long mFgsNotificationDeferralInterval = 10_000;
 
     // Rate limit: minimum time after an app's FGS notification is deferred
-    // before another FGS notifiction from that app can be deferred.
+    // before another FGS notification from that app can be deferred.
     volatile long mFgsNotificationDeferralExclusionTime = 2 * 60 * 1000L;
 
     /**
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 3e6a0a8..6e500e4 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -16059,6 +16059,24 @@
         }
 
         @Override
+        public ServiceNotificationPolicy applyForegroundServiceNotification(
+                Notification notification, int id, String pkg, int userId) {
+            synchronized (ActivityManagerService.this) {
+                return mServices.applyForegroundServiceNotificationLocked(notification,
+                        id, pkg, userId);
+            }
+        }
+
+        @Override
+        public void onForegroundServiceNotificationUpdate(Notification notification,
+                int id, String pkg, @UserIdInt int userId) {
+            synchronized (ActivityManagerService.this) {
+                mServices.onForegroundServiceNotificationUpdateLocked(notification,
+                        id, pkg, userId);
+            }
+        }
+
+        @Override
         public void stopForegroundServicesForChannel(String pkg, int userId,
                 String channelId) {
             synchronized (ActivityManagerService.this) {
diff --git a/services/core/java/com/android/server/am/ServiceRecord.java b/services/core/java/com/android/server/am/ServiceRecord.java
index dbb2f65..dd1ddd7 100644
--- a/services/core/java/com/android/server/am/ServiceRecord.java
+++ b/services/core/java/com/android/server/am/ServiceRecord.java
@@ -109,6 +109,7 @@
     boolean fgWaiting;      // is a timeout for going foreground already scheduled?
     boolean isNotAppComponentUsage; // is service binding not considered component/package usage?
     boolean isForeground;   // is service currently in foreground mode?
+    boolean mLogEntering;    // need to report fgs transition once deferral policy is known
     int foregroundId;       // Notification ID of last foreground req.
     Notification foregroundNoti; // Notification record of foreground state.
     long fgDisplayTime;     // time at which the FGS notification should become visible
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsService.java b/services/core/java/com/android/server/locksettings/LockSettingsService.java
index 5b03989..0bec09c 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsService.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsService.java
@@ -105,6 +105,7 @@
 import android.security.keystore2.AndroidKeyStoreProvider;
 import android.service.gatekeeper.GateKeeperResponse;
 import android.service.gatekeeper.IGateKeeperService;
+import android.system.keystore2.Domain;
 import android.text.TextUtils;
 import android.util.ArrayMap;
 import android.util.ArraySet;
@@ -254,8 +255,7 @@
      * The UIDs that are used for system credential storage in keystore.
      */
     private static final int[] SYSTEM_CREDENTIAL_UIDS = {
-            Process.WIFI_UID, Process.VPN_UID,
-            Process.ROOT_UID, Process.SYSTEM_UID };
+            Process.VPN_UID, Process.ROOT_UID, Process.SYSTEM_UID};
 
     // This class manages life cycle events for encrypted users on File Based Encryption (FBE)
     // devices. The most basic of these is to show/hide notifications about missing features until
@@ -2123,9 +2123,14 @@
             // Clear all the users credentials could have been installed in for this user.
             for (int profileId : mUserManager.getProfileIdsWithDisabled(userId)) {
                 for (int uid : SYSTEM_CREDENTIAL_UIDS) {
-                    mKeyStore.clearUid(UserHandle.getUid(profileId, uid));
+                    AndroidKeyStoreMaintenance.clearNamespace(Domain.APP,
+                            UserHandle.getUid(profileId, uid));
                 }
             }
+            if (mUserManager.getUserInfo(userId).isPrimary()) {
+                AndroidKeyStoreMaintenance.clearNamespace(Domain.SELINUX,
+                        KeyProperties.NAMESPACE_WIFI);
+            }
         } finally {
             if (managedUserId != -1 && managedUserDecryptedPassword != null) {
                 if (DEBUG) Slog.v(TAG, "Restore tied profile lock");
diff --git a/services/core/java/com/android/server/media/MediaSession2Record.java b/services/core/java/com/android/server/media/MediaSession2Record.java
index 162c388..607218e 100644
--- a/services/core/java/com/android/server/media/MediaSession2Record.java
+++ b/services/core/java/com/android/server/media/MediaSession2Record.java
@@ -56,13 +56,17 @@
 
     public MediaSession2Record(Session2Token sessionToken, MediaSessionService service,
             Looper handlerLooper, int policies) {
-        mSessionToken = sessionToken;
-        mService = service;
-        mHandlerExecutor = new HandlerExecutor(new Handler(handlerLooper));
-        mController = new MediaController2.Builder(service.getContext(), sessionToken)
-                .setControllerCallback(mHandlerExecutor, new Controller2Callback())
-                .build();
-        mPolicies = policies;
+        // The lock is required to prevent `Controller2Callback` from using partially initialized
+        // `MediaSession2Record.this`.
+        synchronized (mLock) {
+            mSessionToken = sessionToken;
+            mService = service;
+            mHandlerExecutor = new HandlerExecutor(new Handler(handlerLooper));
+            mController = new MediaController2.Builder(service.getContext(), sessionToken)
+                    .setControllerCallback(mHandlerExecutor, new Controller2Callback())
+                    .build();
+            mPolicies = policies;
+        }
     }
 
     @Override
@@ -176,10 +180,12 @@
             if (DEBUG) {
                 Log.d(TAG, "connected to " + mSessionToken + ", allowed=" + allowedCommands);
             }
+            MediaSessionService service;
             synchronized (mLock) {
                 mIsConnected = true;
+                service = mService;
             }
-            mService.onSessionActiveStateChanged(MediaSession2Record.this);
+            service.onSessionActiveStateChanged(MediaSession2Record.this);
         }
 
         @Override
@@ -187,10 +193,12 @@
             if (DEBUG) {
                 Log.d(TAG, "disconnected from " + mSessionToken);
             }
+            MediaSessionService service;
             synchronized (mLock) {
                 mIsConnected = false;
+                service = mService;
             }
-            mService.onSessionDied(MediaSession2Record.this);
+            service.onSessionDied(MediaSession2Record.this);
         }
 
         @Override
@@ -199,7 +207,11 @@
                 Log.d(TAG, "playback active changed, " + mSessionToken + ", active="
                         + playbackActive);
             }
-            mService.onSessionPlaybackStateChanged(MediaSession2Record.this, playbackActive);
+            MediaSessionService service;
+            synchronized (mLock) {
+                service = mService;
+            }
+            service.onSessionPlaybackStateChanged(MediaSession2Record.this, playbackActive);
         }
     }
 }
diff --git a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
index 8c1fd36..cfd5750 100644
--- a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
+++ b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
@@ -99,8 +99,12 @@
 import static android.net.NetworkPolicyManager.uidPoliciesToString;
 import static android.net.NetworkPolicyManager.uidRulesToString;
 import static android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK;
+import static android.net.NetworkStats.METERED_ALL;
+import static android.net.NetworkStats.METERED_YES;
+import static android.net.NetworkTemplate.MATCH_CARRIER;
 import static android.net.NetworkTemplate.MATCH_MOBILE;
 import static android.net.NetworkTemplate.MATCH_WIFI;
+import static android.net.NetworkTemplate.buildTemplateCarrierMetered;
 import static android.net.NetworkTemplate.buildTemplateMobileAll;
 import static android.net.TrafficStats.MB_IN_BYTES;
 import static android.net.netstats.provider.NetworkStatsProvider.QUOTA_UNLIMITED;
@@ -349,7 +353,8 @@
     private static final int VERSION_SWITCH_UID = 10;
     private static final int VERSION_ADDED_CYCLE = 11;
     private static final int VERSION_ADDED_NETWORK_TYPES = 12;
-    private static final int VERSION_LATEST = VERSION_ADDED_NETWORK_TYPES;
+    private static final int VERSION_SUPPORTED_CARRIER_USAGE = 13;
+    private static final int VERSION_LATEST = VERSION_SUPPORTED_CARRIER_USAGE;
 
     @VisibleForTesting
     public static final int TYPE_WARNING = SystemMessage.NOTE_NET_WARNING;
@@ -374,7 +379,9 @@
     private static final String ATTR_RESTRICT_BACKGROUND = "restrictBackground";
     private static final String ATTR_NETWORK_TEMPLATE = "networkTemplate";
     private static final String ATTR_SUBSCRIBER_ID = "subscriberId";
+    private static final String ATTR_SUBSCRIBER_ID_MATCH_RULE = "subscriberIdMatchRule";
     private static final String ATTR_NETWORK_ID = "networkId";
+    private static final String ATTR_TEMPLATE_METERED = "templateMetered";
     @Deprecated private static final String ATTR_CYCLE_DAY = "cycleDay";
     @Deprecated private static final String ATTR_CYCLE_TIMEZONE = "cycleTimezone";
     private static final String ATTR_CYCLE_START = "cycleStart";
@@ -1451,7 +1458,7 @@
      */
     @GuardedBy("mNetworkPoliciesSecondLock")
     private int findRelevantSubIdNL(NetworkTemplate template) {
-        // Mobile template is relevant when any active subscriber matches
+        // Carrier template is relevant when any active subscriber matches
         for (int i = 0; i < mSubIdToSubscriberId.size(); i++) {
             final int subId = mSubIdToSubscriberId.keyAt(i);
             final String subscriberId = mSubIdToSubscriberId.valueAt(i);
@@ -1530,6 +1537,7 @@
             }
             case TYPE_LIMIT: {
                 switch (policy.template.getMatchRule()) {
+                    case MATCH_CARRIER:
                     case MATCH_MOBILE:
                         title = res.getText(R.string.data_usage_mobile_limit_title);
                         break;
@@ -1558,6 +1566,7 @@
             }
             case TYPE_LIMIT_SNOOZED: {
                 switch (policy.template.getMatchRule()) {
+                    case MATCH_CARRIER:
                     case MATCH_MOBILE:
                         title = res.getText(R.string.data_usage_mobile_limit_snoozed_title);
                         break;
@@ -1655,7 +1664,7 @@
 
         synchronized (mUidRulesFirstLock) {
             synchronized (mNetworkPoliciesSecondLock) {
-                ensureActiveMobilePolicyAL();
+                ensureActiveCarrierPolicyAL();
                 normalizePoliciesNL();
                 updateNetworkEnabledNL();
                 updateNetworkRulesNL();
@@ -1680,17 +1689,17 @@
     }
 
     /**
-     * Update mobile policies with data cycle information from {@link CarrierConfigManager}
+     * Update carrier policies with data cycle information from {@link CarrierConfigManager}
      * if necessary.
      *
      * @param subId that has its associated NetworkPolicy updated if necessary
      * @return if any policies were updated
      */
     @GuardedBy("mNetworkPoliciesSecondLock")
-    private boolean maybeUpdateMobilePolicyCycleAL(int subId, String subscriberId) {
-        if (LOGV) Slog.v(TAG, "maybeUpdateMobilePolicyCycleAL()");
+    private boolean maybeUpdateCarrierPolicyCycleAL(int subId, String subscriberId) {
+        if (LOGV) Slog.v(TAG, "maybeUpdateCarrierPolicyCycleAL()");
 
-        // find and update the mobile NetworkPolicy for this subscriber id
+        // find and update the carrier NetworkPolicy for this subscriber id
         boolean policyUpdated = false;
         final NetworkIdentity probeIdent = new NetworkIdentity(TYPE_MOBILE,
                 TelephonyManager.NETWORK_TYPE_UNKNOWN, subscriberId, null, false, true, true,
@@ -1699,21 +1708,21 @@
             final NetworkTemplate template = mNetworkPolicy.keyAt(i);
             if (template.matches(probeIdent)) {
                 final NetworkPolicy policy = mNetworkPolicy.valueAt(i);
-                policyUpdated |= updateDefaultMobilePolicyAL(subId, policy);
+                policyUpdated |= updateDefaultCarrierPolicyAL(subId, policy);
             }
         }
         return policyUpdated;
     }
 
     /**
-     * Returns the cycle day that should be used for a mobile NetworkPolicy.
+     * Returns the cycle day that should be used for a carrier NetworkPolicy.
      *
      * It attempts to get an appropriate cycle day from the passed in CarrierConfig. If it's unable
      * to do so, it returns the fallback value.
      *
      * @param config The CarrierConfig to read the value from.
      * @param fallbackCycleDay to return if the CarrierConfig can't be read.
-     * @return cycleDay to use in the mobile NetworkPolicy.
+     * @return cycleDay to use in the carrier NetworkPolicy.
      */
     @VisibleForTesting
     int getCycleDayFromCarrierConfig(@Nullable PersistableBundle config,
@@ -1738,14 +1747,14 @@
     }
 
     /**
-     * Returns the warning bytes that should be used for a mobile NetworkPolicy.
+     * Returns the warning bytes that should be used for a carrier NetworkPolicy.
      *
      * It attempts to get an appropriate value from the passed in CarrierConfig. If it's unable
      * to do so, it returns the fallback value.
      *
      * @param config The CarrierConfig to read the value from.
      * @param fallbackWarningBytes to return if the CarrierConfig can't be read.
-     * @return warningBytes to use in the mobile NetworkPolicy.
+     * @return warningBytes to use in the carrier NetworkPolicy.
      */
     @VisibleForTesting
     long getWarningBytesFromCarrierConfig(@Nullable PersistableBundle config,
@@ -1771,14 +1780,14 @@
     }
 
     /**
-     * Returns the limit bytes that should be used for a mobile NetworkPolicy.
+     * Returns the limit bytes that should be used for a carrier NetworkPolicy.
      *
      * It attempts to get an appropriate value from the passed in CarrierConfig. If it's unable
      * to do so, it returns the fallback value.
      *
      * @param config The CarrierConfig to read the value from.
      * @param fallbackLimitBytes to return if the CarrierConfig can't be read.
-     * @return limitBytes to use in the mobile NetworkPolicy.
+     * @return limitBytes to use in the carrier NetworkPolicy.
      */
     @VisibleForTesting
     long getLimitBytesFromCarrierConfig(@Nullable PersistableBundle config,
@@ -1824,8 +1833,8 @@
                 synchronized (mNetworkPoliciesSecondLock) {
                     final String subscriberId = mSubIdToSubscriberId.get(subId, null);
                     if (subscriberId != null) {
-                        ensureActiveMobilePolicyAL(subId, subscriberId);
-                        maybeUpdateMobilePolicyCycleAL(subId, subscriberId);
+                        ensureActiveCarrierPolicyAL(subId, subscriberId);
+                        maybeUpdateCarrierPolicyCycleAL(subId, subscriberId);
                     } else {
                         Slog.wtf(TAG, "Missing subscriberId for subId " + subId);
                     }
@@ -1911,10 +1920,12 @@
         // TODO: reach into ConnectivityManager to proactively disable bringing
         // up this network, since we know that traffic will be blocked.
 
-        if (template.getMatchRule() == MATCH_MOBILE) {
-            // If mobile data usage hits the limit or if the user resumes the data, we need to
+        if (template.getMatchRule() == MATCH_MOBILE
+                || template.getMatchRule() == MATCH_CARRIER) {
+            // If carrier data usage hits the limit or if the user resumes the data, we need to
             // notify telephony.
 
+            // TODO: It needs to check if it matches the merged WIFI and notify to wifi module.
             final IntArray matchingSubIds = new IntArray();
             synchronized (mNetworkPoliciesSecondLock) {
                 for (int i = 0; i < mSubIdToSubscriberId.size(); i++) {
@@ -2174,7 +2185,7 @@
                         .truncatedTo(ChronoUnit.DAYS)
                         .toInstant().toEpochMilli();
                 final long totalBytes = getTotalBytes(
-                        NetworkTemplate.buildTemplateMobileAll(snapshot.getSubscriberId()),
+                        buildTemplateCarrierMetered(snapshot.getSubscriberId()),
                         start, startOfDay);
                 final long remainingBytes = limitBytes - totalBytes;
                 // Number of remaining days including current day
@@ -2200,31 +2211,31 @@
 
     /**
      * Once any {@link #mNetworkPolicy} are loaded from disk, ensure that we
-     * have at least a default mobile policy defined.
+     * have at least a default carrier policy defined.
      */
     @GuardedBy("mNetworkPoliciesSecondLock")
-    private void ensureActiveMobilePolicyAL() {
-        if (LOGV) Slog.v(TAG, "ensureActiveMobilePolicyAL()");
+    private void ensureActiveCarrierPolicyAL() {
+        if (LOGV) Slog.v(TAG, "ensureActiveCarrierPolicyAL()");
         if (mSuppressDefaultPolicy) return;
 
         for (int i = 0; i < mSubIdToSubscriberId.size(); i++) {
             final int subId = mSubIdToSubscriberId.keyAt(i);
             final String subscriberId = mSubIdToSubscriberId.valueAt(i);
 
-            ensureActiveMobilePolicyAL(subId, subscriberId);
+            ensureActiveCarrierPolicyAL(subId, subscriberId);
         }
     }
 
     /**
      * Once any {@link #mNetworkPolicy} are loaded from disk, ensure that we
-     * have at least a default mobile policy defined.
+     * have at least a default carrier policy defined.
      *
      * @param subId to build a default policy for
      * @param subscriberId that we check for an existing policy
-     * @return true if a mobile network policy was added, or false one already existed.
+     * @return true if a carrier network policy was added, or false one already existed.
      */
     @GuardedBy("mNetworkPoliciesSecondLock")
-    private boolean ensureActiveMobilePolicyAL(int subId, String subscriberId) {
+    private boolean ensureActiveCarrierPolicyAL(int subId, String subscriberId) {
         // Poke around to see if we already have a policy
         final NetworkIdentity probeIdent = new NetworkIdentity(TYPE_MOBILE,
                 TelephonyManager.NETWORK_TYPE_UNKNOWN, subscriberId, null, false, true, true,
@@ -2243,7 +2254,7 @@
         Slog.i(TAG, "No policy for subscriber "
                 + NetworkIdentityUtils.scrubSubscriberId(subscriberId)
                 + "; generating default policy");
-        final NetworkPolicy policy = buildDefaultMobilePolicy(subId, subscriberId);
+        final NetworkPolicy policy = buildDefaultCarrierPolicy(subId, subscriberId);
         addNetworkPolicyAL(policy);
         return true;
     }
@@ -2263,8 +2274,8 @@
     }
 
     @VisibleForTesting
-    NetworkPolicy buildDefaultMobilePolicy(int subId, String subscriberId) {
-        final NetworkTemplate template = buildTemplateMobileAll(subscriberId);
+    NetworkPolicy buildDefaultCarrierPolicy(int subId, String subscriberId) {
+        final NetworkTemplate template = buildTemplateCarrierMetered(subscriberId);
         final RecurrenceRule cycleRule = NetworkPolicy
                 .buildRule(ZonedDateTime.now().getDayOfMonth(), ZoneId.systemDefault());
         final NetworkPolicy policy = new NetworkPolicy(template, cycleRule,
@@ -2272,7 +2283,7 @@
                 SNOOZE_NEVER, SNOOZE_NEVER, true, true);
         synchronized (mUidRulesFirstLock) {
             synchronized (mNetworkPoliciesSecondLock) {
-                updateDefaultMobilePolicyAL(subId, policy);
+                updateDefaultCarrierPolicyAL(subId, policy);
             }
         }
         return policy;
@@ -2286,7 +2297,7 @@
      * @return if the policy was modified
      */
     @GuardedBy("mNetworkPoliciesSecondLock")
-    private boolean updateDefaultMobilePolicyAL(int subId, NetworkPolicy policy) {
+    private boolean updateDefaultCarrierPolicyAL(int subId, NetworkPolicy policy) {
         if (!policy.inferred) {
             if (LOGD) Slog.d(TAG, "Ignoring user-defined policy " + policy);
             return false;
@@ -2372,14 +2383,33 @@
                         mLoadedRestrictBackground = (version >= VERSION_ADDED_RESTRICT_BACKGROUND)
                                 && readBooleanAttribute(in, ATTR_RESTRICT_BACKGROUND);
                     } else if (TAG_NETWORK_POLICY.equals(tag)) {
-                        final int networkTemplate = readIntAttribute(in, ATTR_NETWORK_TEMPLATE);
+                        int templateType = readIntAttribute(in, ATTR_NETWORK_TEMPLATE);
                         final String subscriberId = in.getAttributeValue(null, ATTR_SUBSCRIBER_ID);
                         final String networkId;
+                        final int subscriberIdMatchRule;
+                        final int templateMeteredness;
                         if (version >= VERSION_ADDED_NETWORK_ID) {
                             networkId = in.getAttributeValue(null, ATTR_NETWORK_ID);
                         } else {
                             networkId = null;
                         }
+
+                        if (version >= VERSION_SUPPORTED_CARRIER_USAGE) {
+                            subscriberIdMatchRule = readIntAttribute(in,
+                                    ATTR_SUBSCRIBER_ID_MATCH_RULE);
+                            templateMeteredness = readIntAttribute(in, ATTR_TEMPLATE_METERED);
+
+                        } else {
+                            subscriberIdMatchRule = NetworkTemplate.SUBSCRIBER_ID_MATCH_RULE_EXACT;
+                            if (templateType == MATCH_MOBILE) {
+                                Log.d(TAG, "Update template match rule from mobile to carrier and"
+                                        + " force to metered");
+                                templateType = MATCH_CARRIER;
+                                templateMeteredness = METERED_YES;
+                            } else {
+                                templateMeteredness = METERED_ALL;
+                            }
+                        }
                         final RecurrenceRule cycleRule;
                         if (version >= VERSION_ADDED_CYCLE) {
                             final String start = readStringAttribute(in, ATTR_CYCLE_START);
@@ -2413,7 +2443,7 @@
                         if (version >= VERSION_ADDED_METERED) {
                             metered = readBooleanAttribute(in, ATTR_METERED);
                         } else {
-                            switch (networkTemplate) {
+                            switch (templateType) {
                                 case MATCH_MOBILE:
                                     metered = true;
                                     break;
@@ -2433,9 +2463,11 @@
                         } else {
                             inferred = false;
                         }
-
-                        final NetworkTemplate template = new NetworkTemplate(networkTemplate,
-                                subscriberId, networkId);
+                        final NetworkTemplate template = new NetworkTemplate(templateType,
+                                subscriberId, new String[] { subscriberId },
+                                networkId, templateMeteredness, NetworkStats.ROAMING_ALL,
+                                NetworkStats.DEFAULT_NETWORK_ALL, NetworkTemplate.NETWORK_TYPE_ALL,
+                                NetworkTemplate.OEM_MANAGED_ALL, subscriberIdMatchRule);
                         if (template.isPersistable()) {
                             mNetworkPolicy.put(template, new NetworkPolicy(template, cycleRule,
                                     warningBytes, limitBytes, lastWarningSnooze,
@@ -2642,10 +2674,14 @@
                 if (subscriberId != null) {
                     out.attribute(null, ATTR_SUBSCRIBER_ID, subscriberId);
                 }
+                writeIntAttribute(out, ATTR_SUBSCRIBER_ID_MATCH_RULE,
+                        template.getSubscriberIdMatchRule());
                 final String networkId = template.getNetworkId();
                 if (networkId != null) {
                     out.attribute(null, ATTR_NETWORK_ID, networkId);
                 }
+                writeIntAttribute(out, ATTR_TEMPLATE_METERED,
+                        template.getMeteredness());
                 writeStringAttribute(out, ATTR_CYCLE_START,
                         RecurrenceRule.convertZonedDateTime(policy.cycleRule.start));
                 writeStringAttribute(out, ATTR_CYCLE_END,
@@ -3513,8 +3549,8 @@
 
                     final String subscriberId = mSubIdToSubscriberId.get(subId, null);
                     if (subscriberId != null) {
-                        ensureActiveMobilePolicyAL(subId, subscriberId);
-                        maybeUpdateMobilePolicyCycleAL(subId, subscriberId);
+                        ensureActiveCarrierPolicyAL(subId, subscriberId);
+                        maybeUpdateCarrierPolicyCycleAL(subId, subscriberId);
                     } else {
                         Slog.wtf(TAG, "Missing subscriberId for subId " + subId);
                     }
@@ -5575,11 +5611,15 @@
             return;
         }
 
-        // Turn mobile data limit off
+        // Turn carrier/mobile data limit off
         NetworkPolicy[] policies = getNetworkPolicies(mContext.getOpPackageName());
-        NetworkTemplate template = NetworkTemplate.buildTemplateMobileAll(subscriber);
+        NetworkTemplate templateCarrier = buildTemplateCarrierMetered(subscriber);
+        NetworkTemplate templateMobile = buildTemplateMobileAll(subscriber);
         for (NetworkPolicy policy : policies) {
-            if (policy.template.equals(template)) {
+            //  All policies loaded from disk will be carrier templates, and setting will also only
+            //  set carrier templates, but we clear mobile templates just in case one is set by
+            //  some other caller
+            if (policy.template.equals(templateCarrier) || policy.template.equals(templateMobile)) {
                 policy.limitBytes = NetworkPolicy.LIMIT_DISABLED;
                 policy.inferred = false;
                 policy.clearSnooze();
diff --git a/services/core/java/com/android/server/notification/NotificationManagerInternal.java b/services/core/java/com/android/server/notification/NotificationManagerInternal.java
index dc9839c..0528b95 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerInternal.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerInternal.java
@@ -30,6 +30,9 @@
     void cancelNotification(String pkg, String basePkg, int callingUid, int callingPid,
             String tag, int id, int userId);
 
+    /** is the given notification currently showing? */
+    boolean isNotificationShown(String pkg, String tag, int notificationId, int userId);
+
     void removeForegroundServiceFlagFromNotification(String pkg, int notificationId, int userId);
 
     void onConversationRemoved(String pkg, int uid, Set<String> shortcuts);
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 0dd9b29..e24c4af 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -130,6 +130,7 @@
 import android.annotation.WorkerThread;
 import android.app.ActivityManager;
 import android.app.ActivityManagerInternal;
+import android.app.ActivityManagerInternal.ServiceNotificationPolicy;
 import android.app.AlarmManager;
 import android.app.AppGlobals;
 import android.app.AppOpsManager;
@@ -211,9 +212,7 @@
 import android.os.Trace;
 import android.os.UserHandle;
 import android.os.UserManager;
-import android.os.VibrationAttributes;
 import android.os.VibrationEffect;
-import android.os.Vibrator;
 import android.provider.DeviceConfig;
 import android.provider.Settings;
 import android.service.notification.Adjustment;
@@ -369,12 +368,8 @@
     // 1 second past the ANR timeout.
     static final int FINISH_TOKEN_TIMEOUT = 11 * 1000;
 
-    static final long[] DEFAULT_VIBRATE_PATTERN = {0, 250, 250, 250};
-
     static final long SNOOZE_UNTIL_UNSPECIFIED = -1;
 
-    static final int VIBRATE_PATTERN_MAXLEN = 8 * 2 + 1; // up to eight bumps
-
     static final int INVALID_UID = -1;
     static final String ROOT_PKG = "root";
 
@@ -482,7 +477,6 @@
     AudioManagerInternal mAudioManagerInternal;
     // Can be null for wear
     @Nullable StatusBarManagerInternal mStatusBar;
-    Vibrator mVibrator;
     private WindowManagerInternal mWindowManagerInternal;
     private AlarmManager mAlarmManager;
     private ICompanionDeviceManager mCompanionManager;
@@ -504,7 +498,6 @@
     private LogicalLight mNotificationLight;
     LogicalLight mAttentionLight;
 
-    private long[] mFallbackVibrationPattern;
     private boolean mUseAttentionLight;
     boolean mHasLight = true;
     boolean mLightEnabled;
@@ -583,6 +576,7 @@
     RankingHelper mRankingHelper;
     @VisibleForTesting
     PreferencesHelper mPreferencesHelper;
+    private VibratorHelper mVibratorHelper;
 
     private final UserProfiles mUserProfiles = new UserProfiles();
     private NotificationListeners mListeners;
@@ -1597,10 +1591,7 @@
         mVibrateNotificationKey = null;
         final long identity = Binder.clearCallingIdentity();
         try {
-            // Stop all vibrations with usage of class alarm (ringtone, alarm, notification usages).
-            int usageFilter =
-                    VibrationAttributes.USAGE_CLASS_ALARM | ~VibrationAttributes.USAGE_CLASS_MASK;
-            mVibrator.cancel(usageFilter);
+            mVibratorHelper.cancelVibration();
         } finally {
             Binder.restoreCallingIdentity(identity);
         }
@@ -1994,19 +1985,6 @@
     private SettingsObserver mSettingsObserver;
     protected ZenModeHelper mZenModeHelper;
 
-    static long[] getLongArray(Resources r, int resid, int maxlen, long[] def) {
-        int[] ar = r.getIntArray(resid);
-        if (ar == null) {
-            return def;
-        }
-        final int len = ar.length > maxlen ? maxlen : ar.length;
-        long[] out = new long[len];
-        for (int i=0; i<len; i++) {
-            out[i] = ar[i];
-        }
-        return out;
-    }
-
     public NotificationManagerService(Context context) {
         this(context,
                 new NotificationRecordLoggerImpl(),
@@ -2040,13 +2018,18 @@
     }
 
     @VisibleForTesting
-    void setHints(int hints) {
-        mListenerHints = hints;
+    VibratorHelper getVibratorHelper() {
+        return mVibratorHelper;
     }
 
     @VisibleForTesting
-    void setVibrator(Vibrator vibrator) {
-        mVibrator = vibrator;
+    void setVibratorHelper(VibratorHelper helper) {
+        mVibratorHelper = helper;
+    }
+
+    @VisibleForTesting
+    void setHints(int hints) {
+        mListenerHints = hints;
     }
 
     @VisibleForTesting
@@ -2121,11 +2104,6 @@
     }
 
     @VisibleForTesting
-    void setFallbackVibrationPattern(long[] vibrationPattern) {
-        mFallbackVibrationPattern = vibrationPattern;
-    }
-
-    @VisibleForTesting
     void setPackageManager(IPackageManager packageManager) {
         mPackageManager = packageManager;
     }
@@ -2199,7 +2177,6 @@
         mPackageManager = packageManager;
         mPackageManagerClient = packageManagerClient;
         mAppOps = appOps;
-        mVibrator = (Vibrator) getContext().getSystemService(Context.VIBRATOR_SERVICE);
         mAppUsageStats = appUsageStats;
         mAlarmManager = (AlarmManager) getContext().getSystemService(Context.ALARM_SERVICE);
         mCompanionManager = companionManager;
@@ -2287,6 +2264,7 @@
                 extractorNames);
         mSnoozeHelper = snoozeHelper;
         mGroupHelper = groupHelper;
+        mVibratorHelper = new VibratorHelper(getContext());
         mHistoryManager = historyManager;
 
         // This is a ManagedServices object that keeps track of the listeners.
@@ -2308,10 +2286,6 @@
         mNotificationLight = lightsManager.getLight(LightsManager.LIGHT_ID_NOTIFICATIONS);
         mAttentionLight = lightsManager.getLight(LightsManager.LIGHT_ID_ATTENTION);
 
-        mFallbackVibrationPattern = getLongArray(resources,
-                R.array.config_notificationFallbackVibePattern,
-                VIBRATE_PATTERN_MAXLEN,
-                DEFAULT_VIBRATE_PATTERN);
         mInCallNotificationUri = Uri.parse("file://" +
                 resources.getString(R.string.config_inCallNotificationSound));
         mInCallNotificationAudioAttributes = new AudioAttributes.Builder()
@@ -3078,6 +3052,20 @@
         }
     }
 
+    protected void maybeReportForegroundServiceUpdate(final NotificationRecord r) {
+        if (r.isForegroundService()) {
+            // snapshot live state for the asynchronous operation
+            final StatusBarNotification sbn = r.getSbn();
+            final Notification notification = sbn.getNotification();
+            final int id = sbn.getId();
+            final String pkg = sbn.getPackageName();
+            final int userId = sbn.getUser().getIdentifier();
+            mHandler.post(() -> {
+                mAmi.onForegroundServiceNotificationUpdate(notification, id, pkg, userId);
+            });
+        }
+    }
+
     private String getHistoryTitle(Notification n) {
         CharSequence title = null;
         if (n.extras != null) {
@@ -5070,7 +5058,7 @@
         }
 
         @Override
-        public boolean isNotificationPolicyAccessGrantedForPackage(String pkg) {;
+        public boolean isNotificationPolicyAccessGrantedForPackage(String pkg) {
             enforceSystemOrSystemUIOrSamePackage(pkg,
                     "request policy access status for another package");
             return checkPolicyAccess(pkg);
@@ -5725,9 +5713,7 @@
                 summaryNotification.extras.putAll(extras);
                 Intent appIntent = getContext().getPackageManager().getLaunchIntentForPackage(pkg);
                 if (appIntent != null) {
-                    final ActivityManagerInternal ami = LocalServices
-                            .getService(ActivityManagerInternal.class);
-                    summaryNotification.contentIntent = ami.getPendingIntentActivityAsApp(
+                    summaryNotification.contentIntent = mAmi.getPendingIntentActivityAsApp(
                             0, appIntent, PendingIntent.FLAG_IMMUTABLE, null,
                             pkg, appInfo.uid);
                 }
@@ -5778,7 +5764,7 @@
             return "callState";
         }
         return null;
-    };
+    }
 
     private void dumpJson(PrintWriter pw, @NonNull DumpFilter filter) {
         JSONObject dump = new JSONObject();
@@ -6059,6 +6045,11 @@
         }
 
         @Override
+        public boolean isNotificationShown(String pkg, String tag, int notificationId, int userId) {
+            return isNotificationShownInternal(pkg, tag, notificationId, userId);
+        }
+
+        @Override
         public void removeForegroundServiceFlagFromNotification(String pkg, int notificationId,
                 int userId) {
             checkCallerIsSystem();
@@ -6141,16 +6132,22 @@
                 mustNotHaveFlags, false, userId, REASON_APP_CANCEL, null);
     }
 
+    boolean isNotificationShownInternal(String pkg, String tag, int notificationId, int userId) {
+        synchronized (mNotificationLock) {
+            return findNotificationLocked(pkg, tag, notificationId, userId) != null;
+        }
+    }
+
     void enqueueNotificationInternal(final String pkg, final String opPkg, final int callingUid,
             final int callingPid, final String tag, final int id, final Notification notification,
             int incomingUserId) {
         enqueueNotificationInternal(pkg, opPkg, callingUid, callingPid, tag, id, notification,
-        incomingUserId, false);
+                incomingUserId, false);
     }
 
     void enqueueNotificationInternal(final String pkg, final String opPkg, final int callingUid,
-        final int callingPid, final String tag, final int id, final Notification notification,
-        int incomingUserId, boolean postSilently) {
+            final int callingPid, final String tag, final int id, final Notification notification,
+            int incomingUserId, boolean postSilently) {
         if (DBG) {
             Slog.v(TAG, "enqueueNotificationInternal: pkg=" + pkg + " id=" + id
                     + " notification=" + notification);
@@ -6185,6 +6182,22 @@
             return;
         }
 
+        // Notifications passed to setForegroundService() have FLAG_FOREGROUND_SERVICE,
+        // but it's also possible that the app has called notify() with an update to an
+        // FGS notification that hasn't yet been displayed.  Make sure we check for any
+        // FGS-related situation up front, outside of any locks so it's safe to call into
+        // the Activity Manager.
+        final ServiceNotificationPolicy policy = mAmi.applyForegroundServiceNotification(
+                notification, id, pkg, userId);
+        if (policy == ServiceNotificationPolicy.UPDATE_ONLY) {
+            // Proceed if the notification is already showing/known, otherwise ignore
+            // because the service lifecycle logic has retained responsibility for its
+            // handling.
+            if (!isNotificationShownInternal(pkg, tag, id, userId)) {
+                return;
+            }
+        }
+
         mUsageStats.registerEnqueuedByApp(pkg);
 
         final StatusBarNotification n = new StatusBarNotification(
@@ -6287,19 +6300,17 @@
         if (notification.allPendingIntents != null) {
             final int intentCount = notification.allPendingIntents.size();
             if (intentCount > 0) {
-                final ActivityManagerInternal am = LocalServices
-                        .getService(ActivityManagerInternal.class);
                 final long duration = LocalServices.getService(
                         DeviceIdleInternal.class).getNotificationAllowlistDuration();
                 for (int i = 0; i < intentCount; i++) {
                     PendingIntent pendingIntent = notification.allPendingIntents.valueAt(i);
                     if (pendingIntent != null) {
-                        am.setPendingIntentAllowlistDuration(pendingIntent.getTarget(),
+                        mAmi.setPendingIntentAllowlistDuration(pendingIntent.getTarget(),
                                 ALLOWLIST_TOKEN, duration,
                                 TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED,
                                 REASON_NOTIFICATION_SERVICE,
                                 "NotificationManagerService");
-                        am.setPendingIntentAllowBgActivityStarts(pendingIntent.getTarget(),
+                        mAmi.setPendingIntentAllowBgActivityStarts(pendingIntent.getTarget(),
                                 ALLOWLIST_TOKEN, (FLAG_ACTIVITY_SENDER | FLAG_BROADCAST_SENDER
                                         | FLAG_SERVICE_SENDER));
                     }
@@ -6568,15 +6579,14 @@
         }
 
         // bubble or inline reply that's immutable?
-        // TODO (b/171418004): renable after app outreach
-        /*if (n.getBubbleMetadata() != null
+        if (n.getBubbleMetadata() != null
                 && n.getBubbleMetadata().getIntent() != null
                 && hasFlag(mAmi.getPendingIntentFlags(
                         n.getBubbleMetadata().getIntent().getTarget()),
                         PendingIntent.FLAG_IMMUTABLE)) {
             throw new IllegalArgumentException(r.getKey() + " Not posted."
                     + " PendingIntents attached to bubbles must be mutable");
-        }*/
+        }
 
         if (n.actions != null) {
             for (Notification.Action action : n.actions) {
@@ -6821,12 +6831,14 @@
                         mUsageStats.registerClickedByUser(r);
                     }
 
-                    if (mReason == REASON_LISTENER_CANCEL
-                            && r.getNotification().isBubbleNotification()) {
+                    if ((mReason == REASON_LISTENER_CANCEL
+                            && r.getNotification().isBubbleNotification())
+                            || (mReason == REASON_CLICK && r.canBubble()
+                            && r.isFlagBubbleRemoved())) {
                         boolean isBubbleSuppressed = r.getNotification().getBubbleMetadata() != null
                                 && r.getNotification().getBubbleMetadata().isBubbleSuppressed();
                         mNotificationDelegate.onBubbleNotificationSuppressionChanged(
-                                r.getKey(), true /* suppressed */, isBubbleSuppressed);
+                                r.getKey(), true /* notifSuppressed */, isBubbleSuppressed);
                         return;
                     }
 
@@ -7108,6 +7120,7 @@
 
                     maybeRecordInterruptionLocked(r);
                     maybeRegisterMessageSent(r);
+                    maybeReportForegroundServiceUpdate(r);
 
                     // Log event to statsd
                     mNotificationRecordLogger.maybeLogNotificationPosted(r, old, position,
@@ -7398,7 +7411,7 @@
             if (mSystemReady && mAudioManager != null) {
                 Uri soundUri = record.getSound();
                 hasValidSound = soundUri != null && !Uri.EMPTY.equals(soundUri);
-                long[] vibration = record.getVibration();
+                VibrationEffect vibration = record.getVibration();
                 // Demote sound to vibration if vibration missing & phone in vibration mode.
                 if (vibration == null
                         && hasValidSound
@@ -7406,7 +7419,8 @@
                         == AudioManager.RINGER_MODE_VIBRATE)
                         && mAudioManager.getStreamVolume(
                         AudioAttributes.toLegacyStreamType(record.getAudioAttributes())) == 0) {
-                    vibration = mFallbackVibrationPattern;
+                    boolean insistent = (record.getFlags() & Notification.FLAG_INSISTENT) != 0;
+                    vibration = mVibratorHelper.createFallbackVibration(insistent);
                 }
                 hasValidVibrate = vibration != null;
                 boolean hasAudibleAlert = hasValidSound || hasValidVibrate;
@@ -7633,23 +7647,12 @@
         return false;
     }
 
-    private boolean playVibration(final NotificationRecord record, long[] vibration,
+    private boolean playVibration(final NotificationRecord record, final VibrationEffect effect,
             boolean delayVibForSound) {
         // Escalate privileges so we can use the vibrator even if the
         // notifying app does not have the VIBRATE permission.
         final long identity = Binder.clearCallingIdentity();
         try {
-            final VibrationEffect effect;
-            try {
-                final boolean insistent =
-                        (record.getNotification().flags & Notification.FLAG_INSISTENT) != 0;
-                effect = VibrationEffect.createWaveform(
-                        vibration, insistent ? 0 : -1 /*repeatIndex*/);
-            } catch (IllegalArgumentException e) {
-                Slog.e(TAG, "Error creating vibration waveform with pattern: " +
-                        Arrays.toString(vibration));
-                return false;
-            }
             if (delayVibForSound) {
                 new Thread(() -> {
                     // delay the vibration by the same amount as the notification sound
@@ -7686,8 +7689,7 @@
         // to the reason so we can still debug from bugreports
         String reason = "Notification (" + record.getSbn().getOpPkg() + " "
                 + record.getSbn().getUid() + ") " + (delayed ? "(Delayed)" : "");
-        mVibrator.vibrate(Process.SYSTEM_UID, PackageManagerService.PLATFORM_PACKAGE_NAME,
-                effect, reason, record.getAudioAttributes());
+        mVibratorHelper.vibrate(effect, record.getAudioAttributes(), reason);
     }
 
     private boolean isNotificationForCurrentUser(NotificationRecord record) {
diff --git a/services/core/java/com/android/server/notification/NotificationRecord.java b/services/core/java/com/android/server/notification/NotificationRecord.java
index e875065..f66cfa9 100644
--- a/services/core/java/com/android/server/notification/NotificationRecord.java
+++ b/services/core/java/com/android/server/notification/NotificationRecord.java
@@ -47,6 +47,7 @@
 import android.os.Bundle;
 import android.os.IBinder;
 import android.os.UserHandle;
+import android.os.VibrationEffect;
 import android.provider.Settings;
 import android.service.notification.Adjustment;
 import android.service.notification.NotificationListenerService;
@@ -158,7 +159,7 @@
     private String mUserExplanation;
     private boolean mPreChannelsNotification = true;
     private Uri mSound;
-    private long[] mVibration;
+    private VibrationEffect mVibration;
     private AudioAttributes mAttributes;
     private NotificationChannel mChannel;
     private ArrayList<String> mPeopleOverride;
@@ -287,29 +288,28 @@
         return light;
     }
 
-    private long[] calculateVibration() {
-        long[] vibration;
-        final long[] defaultVibration =  NotificationManagerService.getLongArray(
-                mContext.getResources(),
-                com.android.internal.R.array.config_defaultNotificationVibePattern,
-                NotificationManagerService.VIBRATE_PATTERN_MAXLEN,
-                NotificationManagerService.DEFAULT_VIBRATE_PATTERN);
+    private VibrationEffect calculateVibration() {
+        VibratorHelper helper = new VibratorHelper(mContext);
+        final Notification notification = getSbn().getNotification();
+        final boolean insistent = (notification.flags & Notification.FLAG_INSISTENT) != 0;
+        VibrationEffect defaultVibration = helper.createDefaultVibration(insistent);
+        VibrationEffect vibration;
         if (getChannel().shouldVibrate()) {
             vibration = getChannel().getVibrationPattern() == null
-                    ? defaultVibration : getChannel().getVibrationPattern();
+                    ? defaultVibration
+                    : helper.createWaveformVibration(getChannel().getVibrationPattern(), insistent);
         } else {
             vibration = null;
         }
         if (mPreChannelsNotification
                 && (getChannel().getUserLockedFields()
                 & NotificationChannel.USER_LOCKED_VIBRATION) == 0) {
-            final Notification notification = getSbn().getNotification();
             final boolean useDefaultVibrate =
                     (notification.defaults & Notification.DEFAULT_VIBRATE) != 0;
             if (useDefaultVibrate) {
                 vibration = defaultVibration;
             } else {
-                vibration = notification.vibrate;
+                vibration = helper.createWaveformVibration(notification.vibrate, insistent);
             }
         }
         return vibration;
@@ -877,6 +877,10 @@
         return mHidden;
     }
 
+    public boolean isForegroundService() {
+        return 0 != (getFlags() & Notification.FLAG_FOREGROUND_SERVICE);
+    }
+
     /**
      * Override of all alerting information on the channel and notification. Used when notifications
      * are reposted in response to direct user action and thus don't need to alert.
@@ -1067,7 +1071,7 @@
         return mSound;
     }
 
-    public long[] getVibration() {
+    public VibrationEffect getVibration() {
         return mVibration;
     }
 
diff --git a/services/core/java/com/android/server/notification/VibratorHelper.java b/services/core/java/com/android/server/notification/VibratorHelper.java
new file mode 100644
index 0000000..f25b047
--- /dev/null
+++ b/services/core/java/com/android/server/notification/VibratorHelper.java
@@ -0,0 +1,153 @@
+/*
+ * Copyright (C) 2021 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.notification;
+
+import android.annotation.Nullable;
+import android.content.Context;
+import android.content.res.Resources;
+import android.media.AudioAttributes;
+import android.os.Process;
+import android.os.VibrationAttributes;
+import android.os.VibrationEffect;
+import android.os.Vibrator;
+import android.util.Slog;
+
+import com.android.internal.R;
+import com.android.server.pm.PackageManagerService;
+
+import java.util.Arrays;
+
+/**
+ * NotificationManagerService helper for functionality related to the vibrator.
+ */
+public final class VibratorHelper {
+    private static final String TAG = "NotificationVibratorHelper";
+
+    private static final long[] DEFAULT_VIBRATE_PATTERN = {0, 250, 250, 250};
+    private static final int VIBRATE_PATTERN_MAXLEN = 8 * 2 + 1; // up to eight bumps
+
+    private final Vibrator mVibrator;
+    private final long[] mDefaultPattern;
+    private final long[] mFallbackPattern;
+
+    public VibratorHelper(Context context) {
+        mVibrator = context.getSystemService(Vibrator.class);
+        mDefaultPattern = getLongArray(
+                context.getResources(),
+                com.android.internal.R.array.config_defaultNotificationVibePattern,
+                VIBRATE_PATTERN_MAXLEN,
+                DEFAULT_VIBRATE_PATTERN);
+        mFallbackPattern = getLongArray(context.getResources(),
+                R.array.config_notificationFallbackVibePattern,
+                VIBRATE_PATTERN_MAXLEN,
+                DEFAULT_VIBRATE_PATTERN);
+    }
+
+    /**
+     * Safely create a {@link VibrationEffect} from given vibration {@code pattern}.
+     *
+     * <p>This method returns {@code null} if the pattern is also {@code null} or invalid.
+     *
+     * @param pattern The off/on vibration pattern, where each item is a duration in milliseconds.
+     * @param insistent {@code true} if the vibration should loop until it is cancelled.
+     */
+    @Nullable
+    public static VibrationEffect createWaveformVibration(@Nullable long[] pattern,
+            boolean insistent) {
+        try {
+            if (pattern != null) {
+                return VibrationEffect.createWaveform(pattern, /* repeat= */ insistent ? 0 : -1);
+            }
+        } catch (IllegalArgumentException e) {
+            Slog.e(TAG, "Error creating vibration waveform with pattern: "
+                    + Arrays.toString(pattern));
+        }
+        return null;
+    }
+
+    /**
+     * Vibrate the device with given {@code effect}.
+     *
+     * <p>We need to vibrate as "android" so we can breakthrough DND.
+     */
+    public void vibrate(VibrationEffect effect, AudioAttributes attrs, String reason) {
+        mVibrator.vibrate(Process.SYSTEM_UID, PackageManagerService.PLATFORM_PACKAGE_NAME,
+                effect, reason, attrs);
+    }
+
+    /** Stop all notification vibrations (ringtone, alarm, notification usages). */
+    public void cancelVibration() {
+        int usageFilter =
+                VibrationAttributes.USAGE_CLASS_ALARM | ~VibrationAttributes.USAGE_CLASS_MASK;
+        mVibrator.cancel(usageFilter);
+    }
+
+    /**
+     * Creates a vibration to be used as fallback when the device is in vibrate mode.
+     *
+     * @param insistent {@code true} if the vibration should loop until it is cancelled.
+     */
+    public VibrationEffect createFallbackVibration(boolean insistent) {
+        if (mVibrator.hasFrequencyControl()) {
+            return createChirpVibration(insistent);
+        }
+        return createWaveformVibration(mFallbackPattern, insistent);
+    }
+
+    /**
+     * Creates a vibration to be used by notifications without a custom pattern.
+     *
+     * @param insistent {@code true} if the vibration should loop until it is cancelled.
+     */
+    public VibrationEffect createDefaultVibration(boolean insistent) {
+        if (mVibrator.hasFrequencyControl()) {
+            return createChirpVibration(insistent);
+        }
+        return createWaveformVibration(mDefaultPattern, insistent);
+    }
+
+    private static VibrationEffect createChirpVibration(boolean insistent) {
+        VibrationEffect.WaveformBuilder waveformBuilder = VibrationEffect.startWaveform()
+                .addStep(/* amplitude= */ 0, /* frequency= */ -0.85f, /* duration= */ 0)
+                .addRamp(/* amplitude= */ 1, /* frequency= */ -0.25f, /* duration= */ 100)
+                .addStep(/* amplitude= */ 1, /* duration= */ 150)
+                .addRamp(/* amplitude= */ 0, /* frequency= */ -0.85f, /* duration= */ 250);
+
+        if (insistent) {
+            return waveformBuilder.build(/* repeat= */ 0);
+        }
+
+        VibrationEffect singleBeat = waveformBuilder.build();
+        return VibrationEffect.startComposition()
+                .addEffect(singleBeat)
+                .addEffect(singleBeat)
+                .compose();
+    }
+
+    private static long[] getLongArray(Resources resources, int resId, int maxLength, long[] def) {
+        int[] ar = resources.getIntArray(resId);
+        if (ar == null) {
+            return def;
+        }
+        final int len = ar.length > maxLength ? maxLength : ar.length;
+        long[] out = new long[len];
+        for (int i = 0; i < len; i++) {
+            out[i] = ar[i];
+        }
+        return out;
+    }
+}
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 2f8ba6d..1e35bf4 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -1413,8 +1413,8 @@
             mStaticLibsByDeclaringPackage = new WatchedArrayMap<>();
     private final SnapshotCache<WatchedArrayMap<String, WatchedLongSparseArray<SharedLibraryInfo>>>
             mStaticLibsByDeclaringPackageSnapshot =
-            new SnapshotCache.Auto<>(mSharedLibraries, mSharedLibraries,
-                                     "PackageManagerService.mSharedLibraries");
+            new SnapshotCache.Auto<>(mStaticLibsByDeclaringPackage, mStaticLibsByDeclaringPackage,
+                                     "PackageManagerService.mStaticLibsByDeclaringPackage");
 
     // Mapping from instrumentation class names to info about them.
     @Watched
@@ -13224,9 +13224,8 @@
                 if (!sharedLibraryInfo.isStatic()) {
                     continue;
                 }
-                final PackageSetting staticLibPkgSetting = getPackageSetting(
-                        toStaticSharedLibraryPackageName(sharedLibraryInfo.getName(),
-                                sharedLibraryInfo.getLongVersion()));
+                final PackageSetting staticLibPkgSetting =
+                        getPackageSetting(sharedLibraryInfo.getPackageName());
                 if (staticLibPkgSetting == null) {
                     Slog.wtf(TAG, "Shared lib without setting: " + sharedLibraryInfo);
                     continue;
@@ -22726,7 +22725,8 @@
      */
     public int getPreferredActivitiesInternal(List<WatchedIntentFilter> outFilters,
             List<ComponentName> outActivities, String packageName) {
-        if (getInstantAppPackageName(Binder.getCallingUid()) != null) {
+        final int callingUid = Binder.getCallingUid();
+        if (getInstantAppPackageName(callingUid) != null) {
             return 0;
         }
         int num = 0;
@@ -22738,9 +22738,13 @@
                 final Iterator<PreferredActivity> it = pir.filterIterator();
                 while (it.hasNext()) {
                     final PreferredActivity pa = it.next();
+                    final String prefPackageName = pa.mPref.mComponent.getPackageName();
                     if (packageName == null
-                            || (pa.mPref.mComponent.getPackageName().equals(packageName)
-                                    && pa.mPref.mAlways)) {
+                            || (prefPackageName.equals(packageName) && pa.mPref.mAlways)) {
+                        if (shouldFilterApplicationLocked(
+                                mSettings.getPackageLPr(prefPackageName), callingUid, userId)) {
+                            continue;
+                        }
                         if (outFilters != null) {
                             outFilters.add(new WatchedIntentFilter(pa.getIntentFilter()));
                         }
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 9c25159..ae8f967 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -436,6 +436,7 @@
     volatile boolean mPowerKeyHandled;
     volatile boolean mBackKeyHandled;
     volatile boolean mEndCallKeyHandled;
+    volatile boolean mCameraGestureTriggered;
     volatile boolean mCameraGestureTriggeredDuringGoingToSleep;
 
     /**
@@ -3833,6 +3834,9 @@
         final MutableBoolean outLaunched = new MutableBoolean(false);
         final boolean gesturedServiceIntercepted = gestureService.interceptPowerKeyDown(event,
                 interactive, outLaunched);
+        if (outLaunched.value) {
+            mCameraGestureTriggered = true;
+        }
         if (outLaunched.value && mRequestedOrSleepingDefaultDisplay) {
             mCameraGestureTriggeredDuringGoingToSleep = true;
         }
@@ -4209,13 +4213,13 @@
         mDefaultDisplayRotation.updateOrientationListener();
 
         if (mKeyguardDelegate != null) {
-            mKeyguardDelegate.onFinishedGoingToSleep(pmSleepReason,
-                    mCameraGestureTriggeredDuringGoingToSleep);
+            mKeyguardDelegate.onFinishedGoingToSleep(pmSleepReason, mCameraGestureTriggered);
         }
         if (mDisplayFoldController != null) {
             mDisplayFoldController.finishedGoingToSleep();
         }
         mCameraGestureTriggeredDuringGoingToSleep = false;
+        mCameraGestureTriggered = false;
     }
 
     // Called on the PowerManager's Notifier thread.
@@ -4242,8 +4246,10 @@
         mDefaultDisplayRotation.updateOrientationListener();
 
         if (mKeyguardDelegate != null) {
-            mKeyguardDelegate.onStartedWakingUp(pmWakeReason);
+            mKeyguardDelegate.onStartedWakingUp(pmWakeReason, mCameraGestureTriggered);
         }
+
+        mCameraGestureTriggered = false;
     }
 
     // Called on the PowerManager's Notifier thread.
diff --git a/services/core/java/com/android/server/policy/keyguard/KeyguardServiceDelegate.java b/services/core/java/com/android/server/policy/keyguard/KeyguardServiceDelegate.java
index 44f14b4..cdce660 100644
--- a/services/core/java/com/android/server/policy/keyguard/KeyguardServiceDelegate.java
+++ b/services/core/java/com/android/server/policy/keyguard/KeyguardServiceDelegate.java
@@ -178,7 +178,8 @@
                 // This is used to hide the scrim once keyguard displays.
                 if (mKeyguardState.interactiveState == INTERACTIVE_STATE_AWAKE
                         || mKeyguardState.interactiveState == INTERACTIVE_STATE_WAKING) {
-                    mKeyguardService.onStartedWakingUp(PowerManager.WAKE_REASON_UNKNOWN);
+                    mKeyguardService.onStartedWakingUp(PowerManager.WAKE_REASON_UNKNOWN,
+                            false /* cameraGestureTriggered */);
                 }
                 if (mKeyguardState.interactiveState == INTERACTIVE_STATE_AWAKE) {
                     mKeyguardService.onFinishedWakingUp();
@@ -297,10 +298,11 @@
         mKeyguardState.dreaming = false;
     }
 
-    public void onStartedWakingUp(@PowerManager.WakeReason int pmWakeReason) {
+    public void onStartedWakingUp(
+            @PowerManager.WakeReason int pmWakeReason, boolean cameraGestureTriggered) {
         if (mKeyguardService != null) {
             if (DEBUG) Log.v(TAG, "onStartedWakingUp()");
-            mKeyguardService.onStartedWakingUp(pmWakeReason);
+            mKeyguardService.onStartedWakingUp(pmWakeReason, cameraGestureTriggered);
         }
         mKeyguardState.interactiveState = INTERACTIVE_STATE_WAKING;
     }
diff --git a/services/core/java/com/android/server/policy/keyguard/KeyguardServiceWrapper.java b/services/core/java/com/android/server/policy/keyguard/KeyguardServiceWrapper.java
index 0872b3a..855a1cc 100644
--- a/services/core/java/com/android/server/policy/keyguard/KeyguardServiceWrapper.java
+++ b/services/core/java/com/android/server/policy/keyguard/KeyguardServiceWrapper.java
@@ -121,9 +121,10 @@
     }
 
     @Override
-    public void onStartedWakingUp(@PowerManager.WakeReason int pmWakeReason) {
+    public void onStartedWakingUp(
+            @PowerManager.WakeReason int pmWakeReason, boolean cameraGestureTriggered) {
         try {
-            mService.onStartedWakingUp(pmWakeReason);
+            mService.onStartedWakingUp(pmWakeReason, cameraGestureTriggered);
         } catch (RemoteException e) {
             Slog.w(TAG , "Remote Exception", e);
         }
diff --git a/services/core/java/com/android/server/power/FaceDownDetector.java b/services/core/java/com/android/server/power/FaceDownDetector.java
index 816c81d..b237ca2 100644
--- a/services/core/java/com/android/server/power/FaceDownDetector.java
+++ b/services/core/java/com/android/server/power/FaceDownDetector.java
@@ -77,6 +77,8 @@
 
     private boolean mIsEnabled;
 
+    private int mSensorMaxLatencyMicros;
+
     /**
      * DeviceConfig flag name, determines how long to disable sensor when user interacts while
      * device is flipped.
@@ -202,7 +204,11 @@
         if (mActive != shouldBeActive) {
             if (shouldBeActive) {
                 mSensorManager.registerListener(
-                        this, mAccelerometer, SensorManager.SENSOR_DELAY_NORMAL);
+                        this,
+                        mAccelerometer,
+                        SensorManager.SENSOR_DELAY_NORMAL,
+                        mSensorMaxLatencyMicros
+                );
                 if (mPreviousResultType == SCREEN_OFF_RESULT) {
                     logScreenOff();
                 }
@@ -226,6 +232,7 @@
         pw.println("  mFaceDown=" + mFaceDown);
         pw.println("  mActive=" + mActive);
         pw.println("  mLastFlipTime=" + mLastFlipTime);
+        pw.println("  mSensorMaxLatencyMicros=" + mSensorMaxLatencyMicros);
         pw.println("  mUserInteractionBackoffMillis=" + mUserInteractionBackoffMillis);
         pw.println("  mPreviousResultTime=" + mPreviousResultTime);
         pw.println("  mPreviousResultType=" + mPreviousResultType);
@@ -356,6 +363,11 @@
                 3600_000);
     }
 
+    private int getSensorMaxLatencyMicros() {
+        return mContext.getResources().getInteger(
+                com.android.internal.R.integer.config_flipToScreenOffMaxLatencyMicros);
+    }
+
     private float getFloatFlagValue(String key, float defaultValue, float min, float max) {
         final float value = DeviceConfig.getFloat(NAMESPACE_ATTENTION_MANAGER_SERVICE,
                 key,
@@ -416,6 +428,7 @@
         mZAccelerationThreshold = getZAccelerationThreshold();
         mZAccelerationThresholdLenient = mZAccelerationThreshold + 1.0f;
         mTimeThreshold = getTimeThreshold();
+        mSensorMaxLatencyMicros = getSensorMaxLatencyMicros();
         mUserInteractionBackoffMillis = getUserInteractionBackoffMillis();
         final boolean oldEnabled = mIsEnabled;
         mIsEnabled = isEnabled();
diff --git a/services/core/java/com/android/server/sensors/SensorManagerInternal.java b/services/core/java/com/android/server/sensors/SensorManagerInternal.java
new file mode 100644
index 0000000..fbb6644
--- /dev/null
+++ b/services/core/java/com/android/server/sensors/SensorManagerInternal.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2021 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.sensors;
+
+import android.annotation.NonNull;
+
+import java.util.concurrent.Executor;
+
+/**
+ * Local system service interface for sensors.
+ *
+ * @hide Only for use within system server.
+ */
+public abstract class SensorManagerInternal {
+    /**
+     * Adds a listener for changes in proximity sensor state.
+     * @param executor The {@link Executor} to {@link Executor#execute invoke} the listener on.
+     * @param listener The listener to add.
+     *
+     * @throws IllegalArgumentException when adding a listener that is already listening
+     */
+    public abstract void addProximityActiveListener(@NonNull Executor executor,
+            @NonNull ProximityActiveListener listener);
+
+    /**
+     * Removes a previously registered listener of proximity sensor state changes.
+     * @param listener The listener to remove.
+     */
+    public abstract void removeProximityActiveListener(@NonNull ProximityActiveListener listener);
+
+    /**
+     * Listener for proximity sensor state changes.
+     */
+    public interface ProximityActiveListener {
+        /**
+         * Callback invoked when the proximity sensor state changes
+         * @param isActive whether the sensor is being enabled or disabled.
+         */
+        void onProximityActive(boolean isActive);
+    }
+}
diff --git a/services/core/java/com/android/server/sensors/SensorService.java b/services/core/java/com/android/server/sensors/SensorService.java
index f7ed8e7..8fe2d52 100644
--- a/services/core/java/com/android/server/sensors/SensorService.java
+++ b/services/core/java/com/android/server/sensors/SensorService.java
@@ -16,25 +16,41 @@
 
 package com.android.server.sensors;
 
+import static com.android.server.sensors.SensorManagerInternal.ProximityActiveListener;
+
+import android.annotation.NonNull;
 import android.content.Context;
+import android.util.ArrayMap;
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.util.ConcurrentUtils;
+import com.android.server.LocalServices;
 import com.android.server.SystemServerInitThreadPool;
 import com.android.server.SystemService;
 import com.android.server.utils.TimingsTraceAndSlog;
 
+import java.util.Objects;
+import java.util.concurrent.Executor;
 import java.util.concurrent.Future;
 
 public class SensorService extends SystemService {
     private static final String START_NATIVE_SENSOR_SERVICE = "StartNativeSensorService";
     private final Object mLock = new Object();
     @GuardedBy("mLock")
+    private final ArrayMap<ProximityActiveListener, ProximityListenerProxy> mProximityListeners =
+            new ArrayMap<>();
+    @GuardedBy("mLock")
     private Future<?> mSensorServiceStart;
+    @GuardedBy("mLock")
+    private long mPtr;
 
 
     /** Start the sensor service. This is a blocking call and can take time. */
-    private static native void startNativeSensorService();
+    private static native long startSensorServiceNative(ProximityActiveListener listener);
+
+    private static native void registerProximityActiveListenerNative(long ptr);
+    private static native void unregisterProximityActiveListenerNative(long ptr);
+
 
     public SensorService(Context ctx) {
         super(ctx);
@@ -42,14 +58,19 @@
             mSensorServiceStart = SystemServerInitThreadPool.submit(() -> {
                 TimingsTraceAndSlog traceLog = TimingsTraceAndSlog.newAsyncLog();
                 traceLog.traceBegin(START_NATIVE_SENSOR_SERVICE);
-                startNativeSensorService();
+                long ptr = startSensorServiceNative(new ProximityListenerDelegate());
+                synchronized (mLock) {
+                    mPtr = ptr;
+                }
                 traceLog.traceEnd();
             }, START_NATIVE_SENSOR_SERVICE);
         }
     }
 
     @Override
-    public void onStart() { }
+    public void onStart() {
+        LocalServices.addService(SensorManagerInternal.class, new LocalService());
+    }
 
     @Override
     public void onBootPhase(int phase) {
@@ -61,4 +82,70 @@
             }
         }
     }
+
+    class LocalService extends SensorManagerInternal {
+        @Override
+        public void addProximityActiveListener(@NonNull Executor executor,
+                @NonNull ProximityActiveListener listener) {
+            Objects.requireNonNull(executor, "executor must not be null");
+            Objects.requireNonNull(listener, "listener must not be null");
+            ProximityListenerProxy proxy = new ProximityListenerProxy(executor, listener);
+            synchronized (mLock) {
+                if (mProximityListeners.containsKey(listener)) {
+                    throw new IllegalArgumentException("listener already registered");
+                }
+                mProximityListeners.put(listener, proxy);
+                if (mProximityListeners.size() == 1) {
+                    registerProximityActiveListenerNative(mPtr);
+                }
+            }
+        }
+
+        @Override
+        public void removeProximityActiveListener(@NonNull ProximityActiveListener listener) {
+            Objects.requireNonNull(listener, "listener must not be null");
+            synchronized (mLock) {
+                ProximityListenerProxy proxy = mProximityListeners.remove(listener);
+                if (proxy == null) {
+                    throw new IllegalArgumentException(
+                            "listener was not registered with sensor service");
+                }
+                if (mProximityListeners.isEmpty()) {
+                    unregisterProximityActiveListenerNative(mPtr);
+                }
+            }
+        }
+    }
+
+    private static class ProximityListenerProxy implements ProximityActiveListener {
+        private final Executor mExecutor;
+        private final ProximityActiveListener mListener;
+
+        ProximityListenerProxy(Executor executor, ProximityActiveListener listener) {
+            mExecutor = executor;
+            mListener = listener;
+        }
+
+        @Override
+        public void onProximityActive(boolean isActive) {
+            mExecutor.execute(() -> mListener.onProximityActive(isActive));
+        }
+    }
+
+    private class ProximityListenerDelegate implements ProximityActiveListener {
+        @Override
+        public void onProximityActive(boolean isActive) {
+            final ProximityListenerProxy[] listeners;
+            // We can't call out while holding the lock because clients might be calling into us
+            // while holding their own  locks (e.g. when registering / unregistering their
+            // listeners).This would break lock ordering and create deadlocks. Instead, we need to
+            // copy the listeners out and then only invoke them once we've dropped the lock.
+            synchronized (mLock) {
+                listeners = mProximityListeners.values().toArray(new ProximityListenerProxy[0]);
+            }
+            for (ProximityListenerProxy listener : listeners) {
+                listener.onProximityActive(isActive);
+            }
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/textclassifier/TextClassificationManagerService.java b/services/core/java/com/android/server/textclassifier/TextClassificationManagerService.java
index 753b42b..0f37450 100644
--- a/services/core/java/com/android/server/textclassifier/TextClassificationManagerService.java
+++ b/services/core/java/com/android/server/textclassifier/TextClassificationManagerService.java
@@ -25,6 +25,8 @@
 import android.content.Intent;
 import android.content.ServiceConnection;
 import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.content.pm.ServiceInfo;
 import android.graphics.drawable.Icon;
 import android.net.Uri;
 import android.os.Binder;
@@ -59,6 +61,7 @@
 import android.view.textclassifier.TextSelection;
 
 import com.android.internal.annotations.GuardedBy;
+import com.android.internal.content.PackageMonitor;
 import com.android.internal.util.DumpUtils;
 import com.android.internal.util.FunctionalUtils;
 import com.android.internal.util.FunctionalUtils.ThrowingConsumer;
@@ -110,6 +113,7 @@
             try {
                 publishBinderService(Context.TEXT_CLASSIFICATION_SERVICE, mManagerService);
                 mManagerService.startListenSettings();
+                mManagerService.startTrackingPackageChanges();
             } catch (Throwable t) {
                 // Starting this service is not critical to the running of this device and should
                 // therefore not crash the device. If it fails, log the error and continue.
@@ -119,11 +123,14 @@
 
         @Override
         public void onUserStarting(@NonNull TargetUser user) {
+            updatePackageStateForUser(user.getUserIdentifier());
             processAnyPendingWork(user.getUserIdentifier());
         }
 
         @Override
         public void onUserUnlocking(@NonNull TargetUser user) {
+            // refresh if we failed earlier due to locked encrypted user
+            updatePackageStateForUser(user.getUserIdentifier());
             // Rebind if we failed earlier due to locked encrypted user
             processAnyPendingWork(user.getUserIdentifier());
         }
@@ -134,6 +141,14 @@
             }
         }
 
+        private void updatePackageStateForUser(int userId) {
+            synchronized (mManagerService.mLock) {
+                // Update the cached disable status, the TextClassfier may not be direct boot aware,
+                // we should update the disable status after user unlock
+                mManagerService.getUserStateLocked(userId).updatePackageStateLocked();
+            }
+        }
+
         @Override
         public void onUserStopping(@NonNull TargetUser user) {
             int userId = user.getUserIdentifier();
@@ -160,6 +175,8 @@
     private final String mDefaultTextClassifierPackage;
     @Nullable
     private final String mSystemTextClassifierPackage;
+    // TODO: consider using device config to control it.
+    private boolean DEBUG = false;
 
     private TextClassificationManagerService(Context context) {
         mContext = Objects.requireNonNull(context);
@@ -176,6 +193,46 @@
         mSettingsListener.registerObserver();
     }
 
+    void startTrackingPackageChanges() {
+        final PackageMonitor monitor = new PackageMonitor() {
+
+            @Override
+            public void onPackageAdded(String packageName, int uid) {
+                notifyPackageInstallStatusChange(packageName, /* installed*/ true);
+            }
+
+            @Override
+            public void onPackageRemoved(String packageName, int uid) {
+                notifyPackageInstallStatusChange(packageName, /* installed= */ false);
+            }
+
+            @Override
+            public void onPackageModified(String packageName) {
+                final int userId = getChangingUserId();
+                synchronized (mLock) {
+                    final UserState userState = getUserStateLocked(userId);
+                    final ServiceState serviceState = userState.getServiceStateLocked(packageName);
+                    if (serviceState != null) {
+                        serviceState.onPackageModifiedLocked();
+                    }
+                }
+            }
+
+            private void notifyPackageInstallStatusChange(String packageName, boolean installed) {
+                final int userId = getChangingUserId();
+                synchronized (mLock) {
+                    final UserState userState = getUserStateLocked(userId);
+                    final ServiceState serviceState = userState.getServiceStateLocked(packageName);
+                    if (serviceState != null) {
+                        serviceState.onPackageInstallStatusChangeLocked(installed);
+                    }
+                }
+            }
+        };
+
+        monitor.register(mContext, null,  UserHandle.ALL, true);
+    }
+
     @Override
     public void onConnectedStateChanged(@ConnectionState int connected) {
     }
@@ -452,6 +509,14 @@
             if (serviceState == null) {
                 Slog.d(LOG_TAG, "No configured system TextClassifierService");
                 callback.onFailure();
+            } else if (!serviceState.isInstalledLocked() || !serviceState.isEnabledLocked()) {
+                if (DEBUG) {
+                    Slog.d(LOG_TAG,
+                            serviceState.mPackageName + " is not available in user " + userId
+                                    + ". Installed: " + serviceState.isInstalledLocked()
+                                    + ", enabled:" + serviceState.isEnabledLocked());
+                }
+                callback.onFailure();
             } else if (attemptToBind && !serviceState.bindLocked()) {
                 Slog.d(LOG_TAG, "Unable to bind TextClassifierService at " + methodName);
                 callback.onFailure();
@@ -761,6 +826,24 @@
             return serviceStates;
         }
 
+        @GuardedBy("mLock")
+        @Nullable
+        private ServiceState getServiceStateLocked(String packageName) {
+            for (ServiceState serviceState : getAllServiceStatesLocked()) {
+                if (serviceState.mPackageName.equals(packageName)) {
+                    return serviceState;
+                }
+            }
+            return null;
+        }
+
+        @GuardedBy("mLock")
+        private void updatePackageStateLocked() {
+            for (ServiceState serviceState : getAllServiceStatesLocked()) {
+                serviceState.updatePackageStateLocked();
+            }
+        }
+
         void dump(IndentingPrintWriter pw) {
             synchronized (mLock) {
                 pw.increaseIndent();
@@ -814,6 +897,10 @@
         ComponentName mBoundComponentName = null;
         @GuardedBy("mLock")
         int mBoundServiceUid = Process.INVALID_UID;
+        @GuardedBy("mLock")
+        boolean mInstalled;
+        @GuardedBy("mLock")
+        boolean mEnabled;
 
         private ServiceState(
                 @UserIdInt int userId, @NonNull String packageName, boolean isTrusted) {
@@ -822,6 +909,8 @@
             mConnection = new TextClassifierServiceConnection(mUserId);
             mIsTrusted = isTrusted;
             mBindServiceFlags = createBindServiceFlags(packageName);
+            mInstalled = isPackageInstalledForUser();
+            mEnabled = isServiceEnabledForUser();
         }
 
         @Context.BindServiceFlags
@@ -833,6 +922,54 @@
             return flags;
         }
 
+        private boolean isPackageInstalledForUser() {
+            try {
+                PackageManager packageManager = mContext.getPackageManager();
+                return packageManager.getPackageInfoAsUser(mPackageName, 0, mUserId) != null;
+            } catch (PackageManager.NameNotFoundException e) {
+                return false;
+            }
+        }
+
+        private boolean isServiceEnabledForUser() {
+            PackageManager packageManager = mContext.getPackageManager();
+            Intent intent = new Intent(TextClassifierService.SERVICE_INTERFACE);
+            intent.setPackage(mPackageName);
+            ResolveInfo resolveInfo = packageManager.resolveServiceAsUser(intent,
+                    PackageManager.GET_SERVICES, mUserId);
+            ServiceInfo serviceInfo = resolveInfo == null ? null : resolveInfo.serviceInfo;
+            return serviceInfo != null;
+        }
+
+        @GuardedBy("mLock")
+        @NonNull
+        private void onPackageInstallStatusChangeLocked(boolean installed) {
+            mInstalled = installed;
+        }
+
+        @GuardedBy("mLock")
+        @NonNull
+        private void onPackageModifiedLocked() {
+            mEnabled = isServiceEnabledForUser();
+        }
+
+        @GuardedBy("mLock")
+        @NonNull
+        private void updatePackageStateLocked() {
+            mInstalled = isPackageInstalledForUser();
+            mEnabled = isServiceEnabledForUser();
+        }
+
+        @GuardedBy("mLock")
+        boolean isInstalledLocked() {
+            return mInstalled;
+        }
+
+        @GuardedBy("mLock")
+        boolean isEnabledLocked() {
+            return mEnabled;
+        }
+
         @GuardedBy("mLock")
         boolean isBoundLocked() {
             return mService != null;
@@ -923,6 +1060,8 @@
             pw.printPair("userId", mUserId);
             synchronized (mLock) {
                 pw.printPair("packageName", mPackageName);
+                pw.printPair("installed", mInstalled);
+                pw.printPair("enabled", mEnabled);
                 pw.printPair("boundComponentName", mBoundComponentName);
                 pw.printPair("isTrusted", mIsTrusted);
                 pw.printPair("bindServiceFlags", mBindServiceFlags);
diff --git a/services/core/java/com/android/server/vibrator/DeviceVibrationEffectAdapter.java b/services/core/java/com/android/server/vibrator/DeviceVibrationEffectAdapter.java
index 7f2b07b..8eff2b9 100644
--- a/services/core/java/com/android/server/vibrator/DeviceVibrationEffectAdapter.java
+++ b/services/core/java/com/android/server/vibrator/DeviceVibrationEffectAdapter.java
@@ -26,11 +26,14 @@
 import android.util.Range;
 
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.List;
 
 /** Adapts a {@link VibrationEffect} to a specific device, taking into account its capabilities. */
 final class DeviceVibrationEffectAdapter implements VibrationEffectModifier<VibratorInfo> {
 
+    private static final int RAMP_STEP_DURATION_MILLIS = 5;
+
     /** Adapts a sequence of {@link VibrationEffectSegment} to device's capabilities. */
     interface SegmentsAdapter {
 
@@ -38,16 +41,17 @@
          * Modifies the given segments list by adding/removing segments to it based on the
          * device capabilities specified by given {@link VibratorInfo}.
          *
-         * @param segments    List of {@link VibrationEffectSegment} to be adapter to the device.
-         * @param repeatIndex Repeat index on the current segment list.
+         * @param segments    List of {@link VibrationEffectSegment} to be modified.
+         * @param repeatIndex Repeat index of the vibration with given segment list.
          * @param info        The device vibrator info that the segments must be adapted to.
-         * @return The new repeat index on the modifies list.
+         * @return The new repeat index to be used for the modified list.
          */
         int apply(List<VibrationEffectSegment> segments, int repeatIndex, VibratorInfo info);
     }
 
     private final SegmentsAdapter mAmplitudeFrequencyAdapter;
     private final SegmentsAdapter mStepToRampAdapter;
+    private final SegmentsAdapter mRampToStepsAdapter;
 
     DeviceVibrationEffectAdapter() {
         this(new ClippingAmplitudeFrequencyAdapter());
@@ -56,6 +60,7 @@
     DeviceVibrationEffectAdapter(SegmentsAdapter amplitudeFrequencyAdapter) {
         mAmplitudeFrequencyAdapter = amplitudeFrequencyAdapter;
         mStepToRampAdapter = new StepToRampAdapter();
+        mRampToStepsAdapter = new RampToStepsAdapter(RAMP_STEP_DURATION_MILLIS);
     }
 
     @Override
@@ -68,7 +73,10 @@
         List<VibrationEffectSegment> newSegments = new ArrayList<>(composed.getSegments());
         int newRepeatIndex = composed.getRepeatIndex();
 
-        // Maps steps that should be handled by PWLE to ramps.
+        // Replace ramps with a sequence of fixed steps, or no-op if PWLE capability present.
+        newRepeatIndex = mRampToStepsAdapter.apply(newSegments, newRepeatIndex, info);
+
+        // Replace steps that should be handled by PWLE to ramps, or no-op if capability missing.
         // This should be done before frequency is converted from relative to absolute values.
         newRepeatIndex = mStepToRampAdapter.apply(newSegments, newRepeatIndex, info);
 
@@ -76,7 +84,6 @@
         // to absolute values in Hertz.
         newRepeatIndex = mAmplitudeFrequencyAdapter.apply(newSegments, newRepeatIndex, info);
 
-        // TODO(b/167947076): add ramp to step adapter
         // TODO(b/167947076): add filter that removes unsupported primitives
         // TODO(b/167947076): add filter that replaces unsupported prebaked with fallback
 
@@ -86,7 +93,7 @@
     /**
      * Adapter that converts step segments that should be handled as PWLEs to ramp segments.
      *
-     * <p>This leves the list unchanged if the device do not have compose PWLE capability.
+     * <p>This leaves the list unchanged if the device do not have compose PWLE capability.
      */
     private static final class StepToRampAdapter implements SegmentsAdapter {
         @Override
@@ -124,6 +131,71 @@
     }
 
     /**
+     * Adapter that converts ramp segments that to a sequence of fixed step segments.
+     *
+     * <p>This leaves the list unchanged if the device have compose PWLE capability.
+     */
+    private static final class RampToStepsAdapter implements SegmentsAdapter {
+        private final int mStepDuration;
+
+        RampToStepsAdapter(int stepDuration) {
+            mStepDuration = stepDuration;
+        }
+
+        @Override
+        public int apply(List<VibrationEffectSegment> segments, int repeatIndex,
+                VibratorInfo info) {
+            if (info.hasCapability(IVibrator.CAP_COMPOSE_PWLE_EFFECTS)) {
+                // The vibrator have PWLE capability, so keep the segments unchanged.
+                return repeatIndex;
+            }
+            int segmentCount = segments.size();
+            for (int i = 0; i < segmentCount; i++) {
+                VibrationEffectSegment segment = segments.get(i);
+                if (!(segment instanceof RampSegment)) {
+                    continue;
+                }
+                List<StepSegment> steps = apply((RampSegment) segment);
+                segments.remove(i);
+                segments.addAll(i, steps);
+                int addedSegments = steps.size() - 1;
+                if (repeatIndex > i) {
+                    repeatIndex += addedSegments;
+                }
+                i += addedSegments;
+                segmentCount += addedSegments;
+            }
+            return repeatIndex;
+        }
+
+        private List<StepSegment> apply(RampSegment ramp) {
+            if (Float.compare(ramp.getStartAmplitude(), ramp.getEndAmplitude()) == 0) {
+                // Amplitude is the same, so return a single step to simulate this ramp.
+                return Arrays.asList(
+                        new StepSegment(ramp.getStartAmplitude(), ramp.getStartFrequency(),
+                                (int) ramp.getDuration()));
+            }
+
+            List<StepSegment> steps = new ArrayList<>();
+            int stepCount = (int) (ramp.getDuration() + mStepDuration - 1) / mStepDuration;
+            for (int i = 0; i < stepCount - 1; i++) {
+                float pos = (float) i / stepCount;
+                steps.add(new StepSegment(
+                        interpolate(ramp.getStartAmplitude(), ramp.getEndAmplitude(), pos),
+                        interpolate(ramp.getStartFrequency(), ramp.getEndFrequency(), pos),
+                        mStepDuration));
+            }
+            int duration = (int) ramp.getDuration() - mStepDuration * (stepCount - 1);
+            steps.add(new StepSegment(ramp.getEndAmplitude(), ramp.getEndFrequency(), duration));
+            return steps;
+        }
+
+        private static float interpolate(float start, float end, float position) {
+            return start + position * (end - start);
+        }
+    }
+
+    /**
      * Adapter that clips frequency values to {@link VibratorInfo#getFrequencyRange()} and
      * amplitude values to respective {@link VibratorInfo#getMaxAmplitude}.
      *
diff --git a/services/core/java/com/android/server/vibrator/VibrationThread.java b/services/core/java/com/android/server/vibrator/VibrationThread.java
index bc61478..b3f1bcd4 100644
--- a/services/core/java/com/android/server/vibrator/VibrationThread.java
+++ b/services/core/java/com/android/server/vibrator/VibrationThread.java
@@ -45,8 +45,10 @@
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Iterator;
+import java.util.LinkedList;
 import java.util.List;
 import java.util.PriorityQueue;
+import java.util.Queue;
 
 /** Plays a {@link Vibration} in dedicated thread. */
 final class VibrationThread extends Thread implements IBinder.DeathRecipient {
@@ -171,7 +173,7 @@
                 Slog.d(TAG, "Synced vibration complete reported by vibrator manager");
             }
             for (int i = 0; i < mVibrators.size(); i++) {
-                mStepQueue.consumeOnVibratorComplete(mVibrators.keyAt(i));
+                mStepQueue.notifyVibratorComplete(mVibrators.keyAt(i));
             }
             mLock.notify();
         }
@@ -183,7 +185,7 @@
             if (DEBUG) {
                 Slog.d(TAG, "Vibration complete reported by vibrator " + vibratorId);
             }
-            mStepQueue.consumeOnVibratorComplete(vibratorId);
+            mStepQueue.notifyVibratorComplete(vibratorId);
             mLock.notify();
         }
     }
@@ -192,25 +194,24 @@
         Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "playVibration");
         try {
             CombinedVibration.Sequential effect = toSequential(mVibration.getEffect());
+            mStepQueue.offer(new StartVibrateStep(effect));
+
             int stepsPlayed = 0;
-
-            synchronized (mLock) {
-                mStepQueue.offer(new StartVibrateStep(effect));
-                Step topOfQueue;
-
-                while ((topOfQueue = mStepQueue.peek()) != null) {
-                    long waitTime = topOfQueue.calculateWaitTime();
-                    if (waitTime <= 0) {
-                        stepsPlayed += mStepQueue.consume();
-                    } else {
+            while (!mStepQueue.isEmpty()) {
+                long waitTime = mStepQueue.calculateWaitTime();
+                if (waitTime <= 0) {
+                    stepsPlayed += mStepQueue.consumeNext();
+                } else {
+                    synchronized (mLock) {
                         try {
                             mLock.wait(waitTime);
-                        } catch (InterruptedException e) { }
+                        } catch (InterruptedException e) {
+                        }
                     }
-                    if (mForceStop) {
-                        mStepQueue.cancel();
-                        return Vibration.Status.CANCELLED;
-                    }
+                }
+                if (mForceStop) {
+                    mStepQueue.cancel();
+                    return Vibration.Status.CANCELLED;
                 }
             }
 
@@ -295,54 +296,75 @@
     private final class StepQueue {
         @GuardedBy("mLock")
         private final PriorityQueue<Step> mNextSteps = new PriorityQueue<>();
-
         @GuardedBy("mLock")
+        private final Queue<Step> mPendingOnVibratorCompleteSteps = new LinkedList<>();
+
         public void offer(@NonNull Step step) {
-            mNextSteps.offer(step);
+            synchronized (mLock) {
+                mNextSteps.offer(step);
+            }
         }
 
-        @GuardedBy("mLock")
-        @Nullable
-        public Step peek() {
-            return mNextSteps.peek();
+        public boolean isEmpty() {
+            synchronized (mLock) {
+                return mPendingOnVibratorCompleteSteps.isEmpty() && mNextSteps.isEmpty();
+            }
+        }
+
+        /** Returns the time in millis to wait before calling {@link #consumeNext()}. */
+        public long calculateWaitTime() {
+            Step nextStep;
+            synchronized (mLock) {
+                if (!mPendingOnVibratorCompleteSteps.isEmpty()) {
+                    // Steps anticipated by vibrator complete callback should be played right away.
+                    return 0;
+                }
+                nextStep = mNextSteps.peek();
+            }
+            return nextStep == null ? 0 : nextStep.calculateWaitTime();
         }
 
         /**
-         * Play and remove the step at the top of this queue, and also adds the next steps
-         * generated to be played next.
+         * Play and remove the step at the top of this queue, and also adds the next steps generated
+         * to be played next.
          *
          * @return the number of steps played
          */
-        @GuardedBy("mLock")
-        public int consume() {
-            Step nextStep = mNextSteps.poll();
+        public int consumeNext() {
+            Step nextStep = pollNext();
             if (nextStep != null) {
-                mNextSteps.addAll(nextStep.play());
+                // This might turn on the vibrator and have a HAL latency. Execute this outside any
+                // lock to avoid blocking other interactions with the thread.
+                List<Step> nextSteps = nextStep.play();
+                synchronized (mLock) {
+                    mNextSteps.addAll(nextSteps);
+                }
                 return 1;
             }
             return 0;
         }
 
         /**
-         * Play and remove the step in this queue that should be anticipated by the vibrator
-         * completion callback.
+         * Notify the step in this queue that should be anticipated by the vibrator completion
+         * callback and keep it separate to be consumed by {@link #consumeNext()}.
+         *
+         * <p>This is a lightweight method that do not trigger any operation from {@link
+         * VibratorController}, so it can be called directly from a native callback.
          *
          * <p>This assumes only one of the next steps is waiting on this given vibrator, so the
-         * first step found is played by this method, in no particular order.
+         * first step found will be anticipated by this method, in no particular order.
          */
         @GuardedBy("mLock")
-        public void consumeOnVibratorComplete(int vibratorId) {
+        public void notifyVibratorComplete(int vibratorId) {
             Iterator<Step> it = mNextSteps.iterator();
-            List<Step> nextSteps = EMPTY_STEP_LIST;
             while (it.hasNext()) {
                 Step step = it.next();
                 if (step.shouldPlayWhenVibratorComplete(vibratorId)) {
                     it.remove();
-                    nextSteps = step.play();
+                    mPendingOnVibratorCompleteSteps.offer(step);
                     break;
                 }
             }
-            mNextSteps.addAll(nextSteps);
         }
 
         /**
@@ -350,13 +372,25 @@
          *
          * <p>This will remove and trigger {@link Step#cancel()} in all steps, in order.
          */
-        @GuardedBy("mLock")
         public void cancel() {
             Step step;
-            while ((step = mNextSteps.poll()) != null) {
+            while ((step = pollNext()) != null) {
+                // This might turn off the vibrator and have a HAL latency. Execute this outside
+                // any lock to avoid blocking other interactions with the thread.
                 step.cancel();
             }
         }
+
+        @Nullable
+        private Step pollNext() {
+            synchronized (mLock) {
+                // Prioritize the steps anticipated by a vibrator complete callback.
+                if (!mPendingOnVibratorCompleteSteps.isEmpty()) {
+                    return mPendingOnVibratorCompleteSteps.poll();
+                }
+                return mNextSteps.poll();
+            }
+        }
     }
 
     /**
diff --git a/services/core/java/com/android/server/vibrator/VibratorController.java b/services/core/java/com/android/server/vibrator/VibratorController.java
index a09bfc5..5dceac2d0 100644
--- a/services/core/java/com/android/server/vibrator/VibratorController.java
+++ b/services/core/java/com/android/server/vibrator/VibratorController.java
@@ -67,7 +67,7 @@
         mNativeWrapper = nativeWrapper;
         mNativeWrapper.init(vibratorId, listener);
         // TODO(b/167947076): load suggested range from config
-        mVibratorInfo = mNativeWrapper.getInfo(/* suggestedFrequencyRange= */ 100);
+        mVibratorInfo = mNativeWrapper.getInfo(/* suggestedFrequencyRange= */ 200);
         Preconditions.checkNotNull(mVibratorInfo, "Failed to retrieve data for vibrator %d",
                 vibratorId);
     }
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 7df5744..4a9c1bb 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -2329,6 +2329,17 @@
                 }
                 result |= RELAYOUT_RES_SURFACE_CHANGED;
                 if (!win.mWillReplaceWindow) {
+                    // When FLAG_SHOW_WALLPAPER flag is removed from a window, we usually set a flag
+                    // in DC#pendingLayoutChanges and update the wallpaper target later.
+                    // However it's possible that FLAG_SHOW_WALLPAPER flag is removed from a window
+                    // when the window is about to exit, so we update the wallpaper target
+                    // immediately here. Otherwise this window will be stuck in exiting and its
+                    // surface remains on the screen.
+                    // TODO(b/189856716): Allow destroying surface even if it belongs to the
+                    //  keyguard target.
+                    if (wallpaperMayMove) {
+                        displayContent.mWallpaperController.adjustWallpaperWindows();
+                    }
                     focusMayChange = tryStartExitingAnimation(win, winAnimator, focusMayChange);
                 }
             }
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 5f47986..20a992d2 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -1500,7 +1500,9 @@
         }
 
         boolean didFrameInsetsChange = setReportResizeHints();
-        boolean configChanged = !isLastConfigReportedToClient();
+        // The latest configuration will be returned by the out parameter of relayout, so it is
+        // unnecessary to report resize if this window is running relayout.
+        final boolean configChanged = !mInRelayout && !isLastConfigReportedToClient();
         if (DEBUG_CONFIGURATION && configChanged) {
             Slog.v(TAG_WM, "Win " + this + " config changed: " + getConfiguration());
         }
diff --git a/services/core/jni/com_android_server_sensor_SensorService.cpp b/services/core/jni/com_android_server_sensor_SensorService.cpp
index acad1bc..d0f56e6 100644
--- a/services/core/jni/com_android_server_sensor_SensorService.cpp
+++ b/services/core/jni/com_android_server_sensor_SensorService.cpp
@@ -14,34 +14,123 @@
  * limitations under the License.
  */
 
-#define LOG_TAG "SensorService"
+#define LOG_TAG "NativeSensorService"
 
-#include <nativehelper/JNIHelp.h>
-#include "android_runtime/AndroidRuntime.h"
-#include "core_jni_helpers.h"
-#include "jni.h"
-
+#include <android-base/properties.h>
+#include <android_runtime/AndroidRuntime.h>
+#include <core_jni_helpers.h>
 #include <cutils/properties.h>
+#include <jni.h>
 #include <sensorservice/SensorService.h>
 #include <utils/Log.h>
 #include <utils/misc.h>
 
+#include <mutex>
+
+#define PROXIMITY_ACTIVE_CLASS \
+    "com/android/server/sensors/SensorManagerInternal$ProximityActiveListener"
+
 namespace android {
 
-static void startNativeSensorService(JNIEnv* env, jclass clazz) {
-    char propBuf[PROPERTY_VALUE_MAX];
-    property_get("system_init.startsensorservice", propBuf, "1");
-    if (strcmp(propBuf, "1") == 0) {
-        SensorService::publish(false /* allowIsolated */,
-                               IServiceManager::DUMP_FLAG_PRIORITY_CRITICAL);
+static jmethodID sMethodIdOnProximityActive;
+
+class NativeSensorService {
+public:
+    NativeSensorService(JNIEnv* env, jobject listener);
+
+    void registerProximityActiveListener();
+    void unregisterProximityActiveListener();
+
+private:
+    sp<SensorService> mService;
+
+    class ProximityActiveListenerDelegate : public SensorService::ProximityActiveListener {
+    public:
+        ProximityActiveListenerDelegate(JNIEnv* env, jobject listener);
+        ~ProximityActiveListenerDelegate();
+
+        void onProximityActive(bool isActive) override;
+
+    private:
+        jobject mListener;
+    };
+    sp<ProximityActiveListenerDelegate> mProximityActiveListenerDelegate;
+};
+
+NativeSensorService::NativeSensorService(JNIEnv* env, jobject listener)
+      : mProximityActiveListenerDelegate(new ProximityActiveListenerDelegate(env, listener)) {
+    if (base::GetBoolProperty("system_init.startsensorservice", true)) {
+        sp<IServiceManager> sm(defaultServiceManager());
+        mService = new SensorService();
+        sm->addService(String16(SensorService::getServiceName()), mService,
+                       false /* allowIsolated */, IServiceManager::DUMP_FLAG_PRIORITY_CRITICAL);
     }
 }
 
+void NativeSensorService::registerProximityActiveListener() {
+    if (mService == nullptr) {
+        ALOGD("Dropping registerProximityActiveListener, sensor service not available.");
+        return;
+    }
+    mService->addProximityActiveListener(mProximityActiveListenerDelegate);
+}
+
+void NativeSensorService::unregisterProximityActiveListener() {
+    if (mService == nullptr) {
+        ALOGD("Dropping unregisterProximityActiveListener, sensor service not available.");
+        return;
+    }
+
+    mService->removeProximityActiveListener(mProximityActiveListenerDelegate);
+}
+
+NativeSensorService::ProximityActiveListenerDelegate::ProximityActiveListenerDelegate(
+        JNIEnv* env, jobject listener)
+      : mListener(env->NewGlobalRef(listener)) {}
+
+NativeSensorService::ProximityActiveListenerDelegate::~ProximityActiveListenerDelegate() {
+    AndroidRuntime::getJNIEnv()->DeleteGlobalRef(mListener);
+}
+
+void NativeSensorService::ProximityActiveListenerDelegate::onProximityActive(bool isActive) {
+    AndroidRuntime::getJNIEnv()->CallVoidMethod(mListener, sMethodIdOnProximityActive,
+                                                static_cast<jboolean>(isActive));
+}
+
+static jlong startSensorServiceNative(JNIEnv* env, jclass, jobject listener) {
+    NativeSensorService* service = new NativeSensorService(env, listener);
+    return reinterpret_cast<jlong>(service);
+}
+
+static void registerProximityActiveListenerNative(JNIEnv* env, jclass, jlong ptr) {
+    auto* service = reinterpret_cast<NativeSensorService*>(ptr);
+    service->registerProximityActiveListener();
+}
+
+static void unregisterProximityActiveListenerNative(JNIEnv* env, jclass, jlong ptr) {
+    auto* service = reinterpret_cast<NativeSensorService*>(ptr);
+    service->unregisterProximityActiveListener();
+}
+
 static const JNINativeMethod methods[] = {
-        {"startNativeSensorService", "()V", (void*)startNativeSensorService},
+        {
+                "startSensorServiceNative", "(L" PROXIMITY_ACTIVE_CLASS ";)J",
+                reinterpret_cast<void*>(startSensorServiceNative)
+        },
+        {
+                "registerProximityActiveListenerNative", "(J)V",
+                reinterpret_cast<void*>(registerProximityActiveListenerNative)
+        },
+        {
+                "unregisterProximityActiveListenerNative", "(J)V",
+                reinterpret_cast<void*>(unregisterProximityActiveListenerNative)
+         },
+
 };
 
 int register_android_server_sensor_SensorService(JNIEnv* env) {
+    jclass listenerClass = FindClassOrDie(env, PROXIMITY_ACTIVE_CLASS);
+    sMethodIdOnProximityActive = GetMethodIDOrDie(env, listenerClass, "onProximityActive", "(Z)V");
     return jniRegisterNativeMethods(env, "com/android/server/sensors/SensorService", methods,
                                     NELEM(methods));
 }
diff --git a/services/tests/servicestests/assets/NetworkPolicyManagerServiceTest/netpolicy/network-policy-mobile.xml b/services/tests/servicestests/assets/NetworkPolicyManagerServiceTest/netpolicy/network-policy-mobile.xml
new file mode 100644
index 0000000..d1357e7
--- /dev/null
+++ b/services/tests/servicestests/assets/NetworkPolicyManagerServiceTest/netpolicy/network-policy-mobile.xml
@@ -0,0 +1,6 @@
+<?xml version='1.0' encoding='utf-8' standalone='yes' ?>
+<policy-list version="12" restrictBackground="false">
+  <network-policy networkTemplate="1" subscriberId="466977604504520" cycleStart="2020-01-09T00:00+08:00[Asia/Taipei]" cyclePeriod="P1M" warningBytes="2147483648" limitBytes="-1" lastWarningSnooze="-1" lastLimitSnooze="-1" metered="true" inferred="true" />
+</policy-list>
+<whitelist />
+
diff --git a/services/tests/servicestests/assets/NetworkPolicyManagerServiceTest/netpolicy/network-policy-wifi-with-subscriberId-match-rule-all-and-templateMetered-no.xml b/services/tests/servicestests/assets/NetworkPolicyManagerServiceTest/netpolicy/network-policy-wifi-with-subscriberId-match-rule-all-and-templateMetered-no.xml
new file mode 100644
index 0000000..60d7d25
--- /dev/null
+++ b/services/tests/servicestests/assets/NetworkPolicyManagerServiceTest/netpolicy/network-policy-wifi-with-subscriberId-match-rule-all-and-templateMetered-no.xml
@@ -0,0 +1,5 @@
+<?xml version='1.0' encoding='utf-8' standalone='yes' ?>
+<policy-list version="13" restrictBackground="false">
+<network-policy networkTemplate="4" subscriberIdMatchRule="1" networkId="TEST_SSID" templateMetered="0" cycleStart="2020-01-09T00:00+08:00[Asia/Taipei]" cyclePeriod="P1M" warningBytes="2147483648" limitBytes="-1" lastWarningSnooze="-1" lastLimitSnooze="-1" metered="true" inferred="true" />
+</policy-list>
+<whitelist />
diff --git a/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java
index da6c30e..23517a9 100644
--- a/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java
@@ -50,10 +50,12 @@
 import static android.net.NetworkPolicyManager.uidRulesToString;
 import static android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK;
 import static android.net.NetworkStats.IFACE_ALL;
+import static android.net.NetworkStats.METERED_NO;
+import static android.net.NetworkStats.METERED_YES;
 import static android.net.NetworkStats.SET_ALL;
 import static android.net.NetworkStats.TAG_ALL;
 import static android.net.NetworkStats.TAG_NONE;
-import static android.net.NetworkTemplate.buildTemplateMobileAll;
+import static android.net.NetworkTemplate.buildTemplateCarrierMetered;
 import static android.net.NetworkTemplate.buildTemplateWifi;
 import static android.net.TrafficStats.MB_IN_BYTES;
 import static android.telephony.CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED;
@@ -231,7 +233,8 @@
 
 
     private static NetworkTemplate sTemplateWifi = buildTemplateWifi(TEST_SSID);
-    private static NetworkTemplate sTemplateMobileAll = buildTemplateMobileAll(TEST_IMSI);
+    private static NetworkTemplate sTemplateCarrierMetered =
+            buildTemplateCarrierMetered(TEST_IMSI);
 
     /**
      * Path on assets where files used by {@link NetPolicyXml} are located.
@@ -452,7 +455,7 @@
         verify(mNetworkManager).registerObserver(networkObserver.capture());
         mNetworkObserver = networkObserver.getValue();
 
-        NetworkPolicy defaultPolicy = mService.buildDefaultMobilePolicy(0, "");
+        NetworkPolicy defaultPolicy = mService.buildDefaultCarrierPolicy(0, "");
         mDefaultWarningBytes = defaultPolicy.warningBytes;
         mDefaultLimitBytes = defaultPolicy.limitBytes;
     }
@@ -1231,7 +1234,7 @@
             reset(mTelephonyManager, mNetworkManager, mNotifManager);
             TelephonyManager tmSub = expectMobileDefaults();
 
-            mService.snoozeLimit(NetworkTemplate.buildTemplateMobileAll(TEST_IMSI));
+            mService.snoozeLimit(NetworkTemplate.buildTemplateCarrierMetered(TEST_IMSI));
             mService.updateNetworks();
 
             verify(tmSub, atLeastOnce()).setPolicyDataEnabled(true);
@@ -1484,7 +1487,7 @@
         assertEquals(mDefaultLimitBytes, actualLimitBytes);
     }
 
-    private PersistableBundle setupUpdateMobilePolicyCycleTests() throws RemoteException {
+    private PersistableBundle setupUpdateCarrierPolicyCycleTests() throws RemoteException {
         when(mConnManager.getAllNetworkStateSnapshots())
                 .thenReturn(new ArrayList<NetworkStateSnapshot>());
 
@@ -1492,19 +1495,19 @@
 
         PersistableBundle bundle = CarrierConfigManager.getDefaultConfig();
         when(mCarrierConfigManager.getConfigForSubId(FAKE_SUB_ID)).thenReturn(bundle);
-        setNetworkPolicies(buildDefaultFakeMobilePolicy());
+        setNetworkPolicies(buildDefaultFakeCarrierPolicy());
         return bundle;
     }
 
     @Test
-    public void testUpdateMobilePolicyCycleWithNullConfig() throws RemoteException {
+    public void testUpdateCarrierPolicyCycleWithNullConfig() throws RemoteException {
         when(mConnManager.getAllNetworkStateSnapshots())
                 .thenReturn(new ArrayList<NetworkStateSnapshot>());
 
         setupTelephonySubscriptionManagers(FAKE_SUB_ID, FAKE_SUBSCRIBER_ID);
 
         when(mCarrierConfigManager.getConfigForSubId(FAKE_SUB_ID)).thenReturn(null);
-        setNetworkPolicies(buildDefaultFakeMobilePolicy());
+        setNetworkPolicies(buildDefaultFakeCarrierPolicy());
         // smoke test to make sure no errors are raised
         mServiceContext.sendBroadcast(
                 new Intent(ACTION_CARRIER_CONFIG_CHANGED)
@@ -1515,8 +1518,8 @@
     }
 
     @Test
-    public void testUpdateMobilePolicyCycleWithInvalidConfig() throws RemoteException {
-        PersistableBundle bundle = setupUpdateMobilePolicyCycleTests();
+    public void testUpdateCarrierPolicyCycleWithInvalidConfig() throws RemoteException {
+        PersistableBundle bundle = setupUpdateCarrierPolicyCycleTests();
         // Test with an invalid CarrierConfig, there should be no changes or crashes.
         bundle.putInt(CarrierConfigManager.KEY_MONTHLY_DATA_CYCLE_DAY_INT, -100);
         bundle.putLong(CarrierConfigManager.KEY_DATA_WARNING_THRESHOLD_BYTES_LONG, -100);
@@ -1531,8 +1534,8 @@
     }
 
     @Test
-    public void testUpdateMobilePolicyCycleWithDefaultConfig() throws RemoteException {
-        PersistableBundle bundle = setupUpdateMobilePolicyCycleTests();
+    public void testUpdateCarrierPolicyCycleWithDefaultConfig() throws RemoteException {
+        PersistableBundle bundle = setupUpdateCarrierPolicyCycleTests();
         // Test that we respect the platform values when told to
         bundle.putInt(CarrierConfigManager.KEY_MONTHLY_DATA_CYCLE_DAY_INT,
                 DATA_CYCLE_USE_PLATFORM_DEFAULT);
@@ -1550,11 +1553,11 @@
     }
 
     @Test
-    public void testUpdateMobilePolicyCycleWithUserOverrides() throws RemoteException {
-        PersistableBundle bundle = setupUpdateMobilePolicyCycleTests();
+    public void testUpdateCarrierPolicyCycleWithUserOverrides() throws RemoteException {
+        PersistableBundle bundle = setupUpdateCarrierPolicyCycleTests();
 
         // inferred = false implies that a user manually modified this policy.
-        NetworkPolicy policy = buildDefaultFakeMobilePolicy();
+        NetworkPolicy policy = buildDefaultFakeCarrierPolicy();
         policy.inferred = false;
         setNetworkPolicies(policy);
 
@@ -1573,8 +1576,8 @@
     }
 
     @Test
-    public void testUpdateMobilePolicyCycleUpdatesDataCycle() throws RemoteException {
-        PersistableBundle bundle = setupUpdateMobilePolicyCycleTests();
+    public void testUpdateCarrierPolicyCycleUpdatesDataCycle() throws RemoteException {
+        PersistableBundle bundle = setupUpdateCarrierPolicyCycleTests();
 
         bundle.putInt(CarrierConfigManager.KEY_MONTHLY_DATA_CYCLE_DAY_INT, 31);
         bundle.putLong(CarrierConfigManager.KEY_DATA_WARNING_THRESHOLD_BYTES_LONG, 9999);
@@ -1588,8 +1591,8 @@
     }
 
     @Test
-    public void testUpdateMobilePolicyCycleDisableThresholds() throws RemoteException {
-        PersistableBundle bundle = setupUpdateMobilePolicyCycleTests();
+    public void testUpdateCarrierPolicyCycleDisableThresholds() throws RemoteException {
+        PersistableBundle bundle = setupUpdateCarrierPolicyCycleTests();
 
         bundle.putInt(CarrierConfigManager.KEY_MONTHLY_DATA_CYCLE_DAY_INT, 31);
         bundle.putLong(CarrierConfigManager.KEY_DATA_WARNING_THRESHOLD_BYTES_LONG,
@@ -1605,8 +1608,8 @@
     }
 
     @Test
-    public void testUpdateMobilePolicyCycleRevertsToDefault() throws RemoteException {
-        PersistableBundle bundle = setupUpdateMobilePolicyCycleTests();
+    public void testUpdateCarrierPolicyCycleRevertsToDefault() throws RemoteException {
+        PersistableBundle bundle = setupUpdateCarrierPolicyCycleTests();
 
         bundle.putInt(CarrierConfigManager.KEY_MONTHLY_DATA_CYCLE_DAY_INT, 31);
         bundle.putLong(CarrierConfigManager.KEY_DATA_WARNING_THRESHOLD_BYTES_LONG,
@@ -1775,7 +1778,7 @@
     @Test
     public void testSetNetworkPolicies_withNullPolicies_doesNotThrow() {
         NetworkPolicy[] policies = new NetworkPolicy[3];
-        policies[1] = buildDefaultFakeMobilePolicy();
+        policies[1] = buildDefaultFakeCarrierPolicy();
         setNetworkPolicies(policies);
 
         assertNetworkPolicyEquals(DEFAULT_CYCLE_DAY, mDefaultWarningBytes, mDefaultLimitBytes,
@@ -1821,7 +1824,8 @@
 
         // Set warning to 7KB and limit to 10KB.
         setNetworkPolicies(new NetworkPolicy(
-                sTemplateMobileAll, CYCLE_DAY, TIMEZONE_UTC, 7000L, 10000L, true));
+                sTemplateCarrierMetered, CYCLE_DAY, TIMEZONE_UTC, 7000L, 10000L,
+                true));
         postMsgAndWaitForCompletion();
 
         // Verifies that remaining quotas are set to providers.
@@ -1972,6 +1976,40 @@
         }
     }
 
+    @Test
+    @NetPolicyXml("network-policy-mobile.xml")
+    public void testStartToSupportCarrierUsagePolicy() throws Exception {
+        NetworkPolicy[] policies = mService.getNetworkPolicies(
+                mServiceContext.getOpPackageName());
+        assertEquals("Unexpected number of network policies", 1, policies.length);
+        NetworkPolicy actualPolicy = policies[0];
+        assertEquals("Unexpected template match rule in network policies",
+                NetworkTemplate.MATCH_CARRIER,
+                actualPolicy.template.getMatchRule());
+        assertEquals("Unexpected subscriberId match rule in network policies",
+                NetworkTemplate.SUBSCRIBER_ID_MATCH_RULE_EXACT,
+                actualPolicy.template.getSubscriberIdMatchRule());
+        assertEquals("Unexpected template meteredness in network policies",
+                METERED_YES, actualPolicy.template.getMeteredness());
+    }
+
+    @Test
+    @NetPolicyXml("network-policy-wifi-with-subscriberId-match-rule-all-and-templateMetered-no.xml")
+    public void testSupportedCarrierUsagePolicy() throws Exception {
+        NetworkPolicy[] policies = mService.getNetworkPolicies(
+                mServiceContext.getOpPackageName());
+        assertEquals("Unexpected number of network policies", 1, policies.length);
+        NetworkPolicy actualPolicy = policies[0];
+        assertEquals("Unexpected template match rule in network policies",
+                NetworkTemplate.MATCH_WIFI,
+                actualPolicy.template.getMatchRule());
+        assertEquals("Unexpected subscriberId match rule in network policies",
+                NetworkTemplate.SUBSCRIBER_ID_MATCH_RULE_ALL,
+                actualPolicy.template.getSubscriberIdMatchRule());
+        assertEquals("Unexpected template meteredness in network policies",
+                METERED_NO, actualPolicy.template.getMeteredness());
+    }
+
     private String formatBlockedStateError(int uid, int rule, boolean metered,
             boolean backgroundRestricted) {
         return String.format(
@@ -2024,8 +2062,8 @@
         return builder.build();
     }
 
-    private NetworkPolicy buildDefaultFakeMobilePolicy() {
-        NetworkPolicy p = mService.buildDefaultMobilePolicy(FAKE_SUB_ID, FAKE_SUBSCRIBER_ID);
+    private NetworkPolicy buildDefaultFakeCarrierPolicy() {
+        NetworkPolicy p = mService.buildDefaultCarrierPolicy(FAKE_SUB_ID, FAKE_SUBSCRIBER_ID);
         // set a deterministic cycle date
         p.cycleRule = new RecurrenceRule(
                 p.cycleRule.start.withDayOfMonth(DEFAULT_CYCLE_DAY),
@@ -2033,9 +2071,9 @@
         return p;
     }
 
-    private static NetworkPolicy buildFakeMobilePolicy(int cycleDay, long warningBytes,
+    private static NetworkPolicy buildFakeCarrierPolicy(int cycleDay, long warningBytes,
             long limitBytes, boolean inferred) {
-        final NetworkTemplate template = buildTemplateMobileAll(FAKE_SUBSCRIBER_ID);
+        final NetworkTemplate template = buildTemplateCarrierMetered(FAKE_SUBSCRIBER_ID);
         return new NetworkPolicy(template, cycleDay, TimeZone.getDefault().getID(), warningBytes,
                 limitBytes, SNOOZE_NEVER, SNOOZE_NEVER, true, inferred);
     }
@@ -2046,8 +2084,8 @@
                 mServiceContext.getOpPackageName());
         assertEquals("Unexpected number of network policies", 1, policies.length);
         NetworkPolicy actualPolicy = policies[0];
-        NetworkPolicy expectedPolicy = buildFakeMobilePolicy(expectedCycleDay, expectedWarningBytes,
-                expectedLimitBytes, expectedInferred);
+        NetworkPolicy expectedPolicy = buildFakeCarrierPolicy(expectedCycleDay,
+                expectedWarningBytes, expectedLimitBytes, expectedInferred);
         assertEquals(expectedPolicy, actualPolicy);
     }
 
diff --git a/services/tests/servicestests/src/com/android/server/vibrator/DeviceVibrationEffectAdapterTest.java b/services/tests/servicestests/src/com/android/server/vibrator/DeviceVibrationEffectAdapterTest.java
index f659698..00b05d4 100644
--- a/services/tests/servicestests/src/com/android/server/vibrator/DeviceVibrationEffectAdapterTest.java
+++ b/services/tests/servicestests/src/com/android/server/vibrator/DeviceVibrationEffectAdapterTest.java
@@ -77,6 +77,38 @@
     }
 
     @Test
+    public void testStepAndRampSegments_withoutPwleCapability_convertsRampsToSteps() {
+        VibrationEffect.Composed effect = new VibrationEffect.Composed(Arrays.asList(
+                new StepSegment(/* amplitude= */ 0, /* frequency= */ 1, /* duration= */ 10),
+                new StepSegment(/* amplitude= */ 0.5f, /* frequency= */ 0, /* duration= */ 100),
+                new RampSegment(/* startAmplitude= */ 1, /* endAmplitude= */ 0.2f,
+                        /* startFrequency= */ -4, /* endFrequency= */ 2, /* duration= */ 10),
+                new RampSegment(/* startAmplitude= */ 0.8f, /* endAmplitude= */ 0.2f,
+                        /* startFrequency= */ 0, /* endFrequency= */ 0, /* duration= */ 11),
+                new RampSegment(/* startAmplitude= */ 0.65f, /* endAmplitude= */ 0.65f,
+                        /* startFrequency= */ 0, /* endFrequency= */ 1, /* duration= */ 200)),
+                /* repeatIndex= */ 3);
+
+        VibrationEffect.Composed expected = new VibrationEffect.Composed(Arrays.asList(
+                new StepSegment(/* amplitude= */ 0, Float.NaN, /* duration= */ 10),
+                new StepSegment(/* amplitude= */ 0.5f, Float.NaN, /* duration= */ 100),
+                // 10ms ramp becomes 2 steps
+                new StepSegment(/* amplitude= */ 1, Float.NaN, /* duration= */ 5),
+                new StepSegment(/* amplitude= */ 0.2f, Float.NaN, /* duration= */ 5),
+                // 11ms ramp becomes 3 steps
+                new StepSegment(/* amplitude= */ 0.8f, Float.NaN, /* duration= */ 5),
+                new StepSegment(/* amplitude= */ 0.6f, Float.NaN, /* duration= */ 5),
+                new StepSegment(/* amplitude= */ 0.2f, Float.NaN, /* duration= */ 1),
+                // 200ms ramp with same amplitude becomes a single step
+                new StepSegment(/* amplitude= */ 0.65f, Float.NaN, /* duration= */ 200)),
+                // Repeat index fixed after intermediate steps added
+                /* repeatIndex= */ 4);
+
+        VibratorInfo info = createVibratorInfo(EMPTY_FREQUENCY_MAPPING);
+        assertEquals(expected, mAdapter.apply(effect, info));
+    }
+
+    @Test
     public void testStepAndRampSegments_withPwleCapability_convertsStepsToRamps() {
         VibrationEffect.Composed effect = new VibrationEffect.Composed(Arrays.asList(
                 new StepSegment(/* amplitude= */ 0, /* frequency= */ 1, /* duration= */ 10),
@@ -131,7 +163,7 @@
     }
 
     @Test
-    public void testStepAndRampSegments_emptyMapping_returnsSameAmplitudesAndFrequencyZero() {
+    public void testStepAndRampSegments_withEmptyFreqMapping_returnsSameAmplitudesAndZeroFreq() {
         VibrationEffect.Composed effect = new VibrationEffect.Composed(Arrays.asList(
                 new StepSegment(/* amplitude= */ 0, /* frequency= */ 1, /* duration= */ 10),
                 new StepSegment(/* amplitude= */ 0.5f, /* frequency= */ 0, /* duration= */ 100),
@@ -142,8 +174,11 @@
                 /* repeatIndex= */ 2);
 
         VibrationEffect.Composed expected = new VibrationEffect.Composed(Arrays.asList(
-                new StepSegment(/* amplitude= */ 0, /* frequency= */ Float.NaN, /* duration= */ 10),
-                new StepSegment(/* amplitude= */ 0.5f, /* frequency= */ Float.NaN,
+                new RampSegment(/* startAmplitude= */ 0, /* endAmplitude= */ 0,
+                        /* startFrequency= */ Float.NaN, /* endFrequency= */ Float.NaN,
+                        /* duration= */ 10),
+                new RampSegment(/* startAmplitude= */ 0.5f, /* endAmplitude= */ 0.5f,
+                        /* startFrequency= */ Float.NaN, /* endFrequency= */ Float.NaN,
                         /* duration= */ 100),
                 new RampSegment(/* startAmplitude= */ 0.8f, /* endAmplitude= */ 1,
                         /* startFrequency= */ Float.NaN, /* endFrequency= */ Float.NaN,
@@ -153,11 +188,13 @@
                         /* duration= */ 20)),
                 /* repeatIndex= */ 2);
 
-        assertEquals(expected, mAdapter.apply(effect, createVibratorInfo(EMPTY_FREQUENCY_MAPPING)));
+        VibratorInfo info = createVibratorInfo(EMPTY_FREQUENCY_MAPPING,
+                IVibrator.CAP_COMPOSE_PWLE_EFFECTS);
+        assertEquals(expected, mAdapter.apply(effect, info));
     }
 
     @Test
-    public void testStepAndRampSegments_nonEmptyMapping_returnsClippedValues() {
+    public void testStepAndRampSegments_withValidFreqMapping_returnsClippedValues() {
         VibrationEffect.Composed effect = new VibrationEffect.Composed(Arrays.asList(
                 new StepSegment(/* amplitude= */ 0.5f, /* frequency= */ 0, /* duration= */ 10),
                 new StepSegment(/* amplitude= */ 1, /* frequency= */ -1, /* duration= */ 100),
@@ -168,15 +205,21 @@
                 /* repeatIndex= */ 2);
 
         VibrationEffect.Composed expected = new VibrationEffect.Composed(Arrays.asList(
-                new StepSegment(/* amplitude= */ 0.5f, /* frequency= */ 150, /* duration= */ 10),
-                new StepSegment(/* amplitude= */ 0.8f, /* frequency= */ 125, /* duration= */ 100),
+                new RampSegment(/* startAmplitude= */ 0.5f, /* endAmplitude= */ 0.5f,
+                        /* startFrequency= */ 150, /* endFrequency= */ 150,
+                        /* duration= */ 10),
+                new RampSegment(/* startAmplitude= */ 0.8f, /* endAmplitude= */ 0.8f,
+                        /* startFrequency= */ 125, /* endFrequency= */ 125,
+                        /* duration= */ 100),
                 new RampSegment(/* startAmplitude= */ 0.1f, /* endAmplitude= */ 0.8f,
                         /* startFrequency= */ 50, /* endFrequency= */ 200, /* duration= */ 50),
                 new RampSegment(/* startAmplitude= */ 0.8f, /* endAmplitude= */ 0.1f,
                         /* startFrequency= */ 200, /* endFrequency= */ 50, /* duration= */ 20)),
                 /* repeatIndex= */ 2);
 
-        assertEquals(expected, mAdapter.apply(effect, createVibratorInfo(TEST_FREQUENCY_MAPPING)));
+        VibratorInfo info = createVibratorInfo(TEST_FREQUENCY_MAPPING,
+                IVibrator.CAP_COMPOSE_PWLE_EFFECTS);
+        assertEquals(expected, mAdapter.apply(effect, info));
     }
 
     private static VibratorInfo createVibratorInfo(VibratorInfo.FrequencyMapping frequencyMapping,
diff --git a/services/tests/servicestests/src/com/android/server/vibrator/VibrationThreadTest.java b/services/tests/servicestests/src/com/android/server/vibrator/VibrationThreadTest.java
index 5743982..ac3e05d 100644
--- a/services/tests/servicestests/src/com/android/server/vibrator/VibrationThreadTest.java
+++ b/services/tests/servicestests/src/com/android/server/vibrator/VibrationThreadTest.java
@@ -85,12 +85,17 @@
     private static final String PACKAGE_NAME = "package";
     private static final VibrationAttributes ATTRS = new VibrationAttributes.Builder().build();
 
-    @Rule public MockitoRule mMockitoRule = MockitoJUnit.rule();
+    @Rule
+    public MockitoRule mMockitoRule = MockitoJUnit.rule();
 
-    @Mock private VibrationThread.VibrationCallbacks mThreadCallbacks;
-    @Mock private VibratorController.OnVibrationCompleteListener mControllerCallbacks;
-    @Mock private IBinder mVibrationToken;
-    @Mock private IBatteryStats mIBatteryStatsMock;
+    @Mock
+    private VibrationThread.VibrationCallbacks mThreadCallbacks;
+    @Mock
+    private VibratorController.OnVibrationCompleteListener mControllerCallbacks;
+    @Mock
+    private IBinder mVibrationToken;
+    @Mock
+    private IBatteryStats mIBatteryStatsMock;
 
     private final Map<Integer, FakeVibratorControllerProvider> mVibratorProviders = new HashMap<>();
     private PowerManager.WakeLock mWakeLock;
@@ -253,7 +258,7 @@
         Thread cancellingThread = new Thread(() -> vibrationThread.cancel());
         cancellingThread.start();
 
-        waitForCompletion(vibrationThread, 20);
+        waitForCompletion(vibrationThread, /* timeout= */ 50);
         waitForCompletion(cancellingThread);
 
         verify(mThreadCallbacks).onVibrationEnded(eq(vibrationId), eq(Vibration.Status.CANCELLED));
@@ -278,7 +283,7 @@
         Thread cancellingThread = new Thread(() -> vibrationThread.cancel());
         cancellingThread.start();
 
-        waitForCompletion(vibrationThread, 20);
+        waitForCompletion(vibrationThread, /* timeout= */ 50);
         waitForCompletion(cancellingThread);
 
         verify(mThreadCallbacks).onVibrationEnded(eq(vibrationId), eq(Vibration.Status.CANCELLED));
@@ -844,6 +849,39 @@
                 delay < maxDelay);
     }
 
+    @LargeTest
+    @Test
+    public void vibrate_cancelSlowVibrator_cancelIsNotBlockedByVibrationThread() throws Exception {
+        FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(VIBRATOR_ID);
+        fakeVibrator.setSupportedEffects(VibrationEffect.EFFECT_CLICK);
+
+        long latency = 5_000; // 5s
+        fakeVibrator.setLatency(latency);
+
+        long vibrationId = 1;
+        VibrationEffect effect = VibrationEffect.get(VibrationEffect.EFFECT_CLICK);
+        VibrationThread vibrationThread = startThreadAndDispatcher(vibrationId, effect);
+
+        assertTrue(waitUntil(
+                t -> !fakeVibrator.getEffectSegments().isEmpty(),
+                vibrationThread, TEST_TIMEOUT_MILLIS));
+        assertTrue(vibrationThread.isAlive());
+
+        // Run cancel in a separate thread so if VibrationThread.cancel blocks then this test should
+        // fail at waitForCompletion(cancellingThread).
+        Thread cancellingThread = new Thread(() -> vibrationThread.cancel());
+        cancellingThread.start();
+
+        // Cancelling the vibration should be fast and return right away, even if the thread is
+        // stuck at the slow call to the vibrator.
+        waitForCompletion(cancellingThread, /* timeout= */ 50);
+
+        // After the vibrator call ends the vibration is cancelled and the vibrator is turned off.
+        waitForCompletion(vibrationThread, /* timeout= */ latency + TEST_TIMEOUT_MILLIS);
+        verify(mThreadCallbacks).onVibrationEnded(eq(vibrationId), eq(Vibration.Status.CANCELLED));
+        assertFalse(vibrationThread.getVibrators().get(VIBRATOR_ID).isVibrating());
+    }
+
     @Test
     public void vibrate_multiplePredefinedCancel_cancelsVibrationImmediately() throws Exception {
         mockVibrators(1, 2);
@@ -870,7 +908,7 @@
         Thread cancellingThread = new Thread(() -> vibrationThread.cancel());
         cancellingThread.start();
 
-        waitForCompletion(vibrationThread, 20);
+        waitForCompletion(vibrationThread, /* timeout= */ 50);
         waitForCompletion(cancellingThread);
 
         verify(mThreadCallbacks).onVibrationEnded(eq(vibrationId), eq(Vibration.Status.CANCELLED));
@@ -902,7 +940,7 @@
         Thread cancellingThread = new Thread(() -> vibrationThread.cancel());
         cancellingThread.start();
 
-        waitForCompletion(vibrationThread, 20);
+        waitForCompletion(vibrationThread, /* timeout= */ 50);
         waitForCompletion(cancellingThread);
 
         verify(mThreadCallbacks).onVibrationEnded(eq(vibrationId), eq(Vibration.Status.CANCELLED));
diff --git a/services/tests/servicestests/src/com/android/server/vibrator/VibratorControllerTest.java b/services/tests/servicestests/src/com/android/server/vibrator/VibratorControllerTest.java
index 59370e2..9e98e7d 100644
--- a/services/tests/servicestests/src/com/android/server/vibrator/VibratorControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/vibrator/VibratorControllerTest.java
@@ -21,6 +21,7 @@
 import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyFloat;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.anyLong;
 import static org.mockito.ArgumentMatchers.eq;
@@ -297,7 +298,7 @@
     private void mockVibratorCapabilities(int capabilities) {
         VibratorInfo.FrequencyMapping frequencyMapping = new VibratorInfo.FrequencyMapping(
                 Float.NaN, Float.NaN, Float.NaN, Float.NaN, null);
-        when(mNativeWrapperMock.getInfo(/* suggestedFrequencyRange= */ 100)).thenReturn(
+        when(mNativeWrapperMock.getInfo(anyFloat())).thenReturn(
                 new VibratorInfo.Builder(VIBRATOR_ID)
                         .setCapabilities(capabilities)
                         .setFrequencyMapping(frequencyMapping)
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/BuzzBeepBlinkTest.java b/services/tests/uiservicestests/src/com/android/server/notification/BuzzBeepBlinkTest.java
index 00eb0f2..3862d75 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/BuzzBeepBlinkTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/BuzzBeepBlinkTest.java
@@ -93,6 +93,9 @@
 import org.mockito.Mock;
 import org.mockito.Mockito;
 import org.mockito.MockitoAnnotations;
+import org.mockito.verification.VerificationMode;
+
+import java.util.Objects;
 
 @SmallTest
 @RunWith(AndroidJUnit4.class)
@@ -137,20 +140,19 @@
     private static final int CUSTOM_LIGHT_COLOR = Color.BLACK;
     private static final int CUSTOM_LIGHT_ON = 10000;
     private static final int CUSTOM_LIGHT_OFF = 10000;
-    private static final long[] FALLBACK_VIBRATION_PATTERN = new long[] {100, 100, 100};
-    private static final VibrationEffect FALLBACK_VIBRATION =
-            VibrationEffect.createWaveform(FALLBACK_VIBRATION_PATTERN, -1);
     private static final int MAX_VIBRATION_DELAY = 1000;
 
     @Before
     public void setUp() throws Exception {
         MockitoAnnotations.initMocks(this);
+        getContext().addMockSystemService(Vibrator.class, mVibrator);
 
         when(mAudioManager.isAudioFocusExclusive()).thenReturn(false);
         when(mAudioManager.getRingtonePlayer()).thenReturn(mRingtonePlayer);
         when(mAudioManager.getStreamVolume(anyInt())).thenReturn(10);
         when(mAudioManager.getRingerModeInternal()).thenReturn(AudioManager.RINGER_MODE_NORMAL);
         when(mUsageStats.isAlertRateLimited(any())).thenReturn(false);
+        when(mVibrator.hasFrequencyControl()).thenReturn(false);
 
         long serviceReturnValue = IntPair.of(
                 AccessibilityManager.STATE_FLAG_ACCESSIBILITY_ENABLED,
@@ -164,13 +166,12 @@
 
         mService = spy(new NotificationManagerService(getContext(), mNotificationRecordLogger,
                 mNotificationInstanceIdSequence));
+        mService.setVibratorHelper(new VibratorHelper(getContext()));
         mService.setAudioManager(mAudioManager);
-        mService.setVibrator(mVibrator);
         mService.setSystemReady(true);
         mService.setHandler(mHandler);
         mService.setLights(mLight);
         mService.setScreenOn(false);
-        mService.setFallbackVibrationPattern(FALLBACK_VIBRATION_PATTERN);
         mService.setUsageStats(mUsageStats);
         mService.setAccessibilityManager(accessibilityManager);
         mService.mScreenOn = false;
@@ -416,32 +417,34 @@
     }
 
     private void verifyVibrate() {
-        ArgumentCaptor<AudioAttributes> captor = ArgumentCaptor.forClass(AudioAttributes.class);
-        verify(mVibrator, times(1)).vibrate(anyInt(), anyString(), argThat(mVibrateOnceMatcher),
-                anyString(), captor.capture());
-        assertEquals(0, (captor.getValue().getAllFlags()
-                & AudioAttributes.FLAG_BYPASS_INTERRUPTION_POLICY));
+        verifyVibrate(/* times= */ 1);
     }
 
     private void verifyVibrate(int times) {
-        verify(mVibrator, times(times)).vibrate(eq(Process.SYSTEM_UID),
-                eq(PackageManagerService.PLATFORM_PACKAGE_NAME), any(), anyString(),
-                any(AudioAttributes.class));
+        verifyVibrate(mVibrateOnceMatcher, times(times));
     }
 
     private void verifyVibrateLooped() {
-        verify(mVibrator, times(1)).vibrate(anyInt(), anyString(), argThat(mVibrateLoopMatcher),
-                anyString(), any(AudioAttributes.class));
+        verifyVibrate(mVibrateLoopMatcher, times(1));
     }
 
     private void verifyDelayedVibrateLooped() {
-        verify(mVibrator, timeout(MAX_VIBRATION_DELAY).times(1)).vibrate(anyInt(), anyString(),
-                argThat(mVibrateLoopMatcher), anyString(), any(AudioAttributes.class));
+        verifyVibrate(mVibrateLoopMatcher, timeout(MAX_VIBRATION_DELAY).times(1));
     }
 
-    private void verifyDelayedVibrate() {
-        verify(mVibrator, timeout(MAX_VIBRATION_DELAY).times(1)).vibrate(anyInt(), anyString(),
-                argThat(mVibrateOnceMatcher), anyString(), any(AudioAttributes.class));
+    private void verifyDelayedVibrate(VibrationEffect effect) {
+        verifyVibrate(argument -> Objects.equals(effect, argument),
+                timeout(MAX_VIBRATION_DELAY).times(1));
+    }
+
+    private void verifyVibrate(ArgumentMatcher<VibrationEffect> effectMatcher,
+            VerificationMode verification) {
+        ArgumentCaptor<AudioAttributes> captor = ArgumentCaptor.forClass(AudioAttributes.class);
+        verify(mVibrator, verification).vibrate(eq(Process.SYSTEM_UID),
+                eq(PackageManagerService.PLATFORM_PACKAGE_NAME), argThat(effectMatcher),
+                anyString(), captor.capture());
+        assertEquals(0, (captor.getValue().getAllFlags()
+                & AudioAttributes.FLAG_BYPASS_INTERRUPTION_POLICY));
     }
 
     private void verifyStopVibrate() {
@@ -761,11 +764,7 @@
 
         mService.buzzBeepBlinkLocked(r);
 
-        VibrationEffect effect = VibrationEffect.createWaveform(r.getVibration(), -1);
-
-        verify(mVibrator, timeout(MAX_VIBRATION_DELAY).times(1)).vibrate(anyInt(), anyString(),
-                eq(effect), anyString(),
-                (AudioAttributes) anyObject());
+        verifyDelayedVibrate(r.getVibration());
         assertTrue(r.isInterruptive());
         assertNotEquals(-1, r.getLastAudiblyAlertedMs());
     }
@@ -800,8 +799,8 @@
 
         mService.buzzBeepBlinkLocked(r);
 
-        verify(mVibrator, timeout(MAX_VIBRATION_DELAY).times(1)).vibrate(anyInt(), anyString(),
-                eq(FALLBACK_VIBRATION), anyString(), (AudioAttributes) anyObject());
+        verifyDelayedVibrate(
+                mService.getVibratorHelper().createFallbackVibration(/* insistent= */ false));
         verify(mRingtonePlayer, never()).playAsync
                 (anyObject(), anyObject(), anyBoolean(), anyObject());
         assertTrue(r.isInterruptive());
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationComparatorTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationComparatorTest.java
index 6a8f602..5eed30b 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationComparatorTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationComparatorTest.java
@@ -37,6 +37,7 @@
 import android.graphics.Color;
 import android.os.Build;
 import android.os.UserHandle;
+import android.os.Vibrator;
 import android.provider.Settings;
 import android.service.notification.StatusBarNotification;
 import android.telecom.TelecomManager;
@@ -63,6 +64,7 @@
     @Mock TelecomManager mTm;
     @Mock RankingHandler handler;
     @Mock PackageManager mPm;
+    @Mock Vibrator mVibrator;
 
     private final String callPkg = "com.android.server.notification";
     private final int callUid = 10;
@@ -98,6 +100,7 @@
         when(mContext.getContentResolver()).thenReturn(getContext().getContentResolver());
         when(mContext.getPackageManager()).thenReturn(mPm);
         when(mContext.getSystemService(eq(Context.TELECOM_SERVICE))).thenReturn(mTm);
+        when(mContext.getSystemService(Vibrator.class)).thenReturn(mVibrator);
         when(mContext.getString(anyInt())).thenCallRealMethod();
         when(mContext.getColor(anyInt())).thenCallRealMethod();
         when(mTm.getDefaultDialerPackage()).thenReturn(callPkg);
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 c33287c..c480258 100755
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -7223,6 +7223,47 @@
                 mNotificationRecordLogger.event(0));
     }
 
+    /**
+     * When something is bubble'd and the bubble is dismissed, but the notification is still
+     * visible, clicking on the notification shouldn't auto-cancel it because clicking on
+     * it will produce a bubble.
+     */
+    @Test
+    public void testNotificationBubbles_bubbleStays_whenClicked_afterBubbleDismissed()
+            throws Exception {
+        setUpPrefsForBubbles(PKG, mUid,
+                true /* global */,
+                BUBBLE_PREFERENCE_ALL /* app */,
+                true /* channel */);
+
+        // GIVEN a notification that has the auto cancels flag (cancel on click) and is a bubble
+        final NotificationRecord nr = generateNotificationRecord(mTestNotificationChannel);
+        nr.getSbn().getNotification().flags |= FLAG_BUBBLE | FLAG_AUTO_CANCEL;
+        nr.setAllowBubble(true);
+        mService.addNotification(nr);
+
+        // And the bubble is dismissed
+        mService.mNotificationDelegate.onNotificationBubbleChanged(nr.getKey(),
+                false /* isBubble */, 0 /* bubbleFlags */);
+        waitForIdle();
+        assertTrue(nr.isFlagBubbleRemoved());
+
+        // WHEN we click the notification
+        final NotificationVisibility nv = NotificationVisibility.obtain(nr.getKey(), 1, 2, true);
+        mService.mNotificationDelegate.onNotificationClick(mUid, Binder.getCallingPid(),
+                nr.getKey(), nv);
+        waitForIdle();
+
+        // THEN the bubble should still exist
+        StatusBarNotification[] notifsAfter = mBinderService.getActiveNotifications(PKG);
+        assertEquals(1, notifsAfter.length);
+
+        // Check we got the click log
+        assertEquals(1, mNotificationRecordLogger.numCalls());
+        assertEquals(NotificationRecordLogger.NotificationEvent.NOTIFICATION_CLICKED,
+                mNotificationRecordLogger.event(0));
+    }
+
     @Test
     public void testLoadDefaultApprovedServices_emptyResources() {
         TestableResources tr = mContext.getOrCreateTestableResources();
@@ -7770,8 +7811,7 @@
         inOrder.verify(child).recordDismissalSentiment(anyInt());
     }
 
-    // TODO (b/171418004): renable after app outreach
-    /*@Test
+    @Test
     public void testImmutableBubbleIntent() throws Exception {
         when(mAmi.getPendingIntentFlags(pi1))
                 .thenReturn(FLAG_IMMUTABLE | FLAG_ONE_SHOT);
@@ -7786,7 +7826,7 @@
         } catch (IllegalArgumentException e) {
             // good
         }
-    }*/
+    }
 
     @Test
     public void testMutableBubbleIntent() throws Exception {
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordTest.java
index 38c470d..49b6386 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordTest.java
@@ -62,6 +62,7 @@
 import android.os.Build;
 import android.os.Bundle;
 import android.os.UserHandle;
+import android.os.Vibrator;
 import android.provider.Settings;
 import android.service.notification.Adjustment;
 import android.service.notification.StatusBarNotification;
@@ -82,7 +83,6 @@
 import org.mockito.MockitoAnnotations;
 
 import java.util.ArrayList;
-import java.util.Arrays;
 
 @SmallTest
 @RunWith(AndroidJUnit4.class)
@@ -91,6 +91,7 @@
     private final Context mMockContext = mock(Context.class);
     @Mock private PackageManager mPm;
     @Mock private ContentResolver mContentResolver;
+    @Mock private Vibrator mVibrator;
 
     private final String mPkg = PKG_O;
     private final int uid = 9583;
@@ -122,6 +123,7 @@
     public void setUp() {
         MockitoAnnotations.initMocks(this);
 
+        when(mMockContext.getSystemService(eq(Vibrator.class))).thenReturn(mVibrator);
         when(mMockContext.getResources()).thenReturn(getContext().getResources());
         when(mMockContext.getPackageManager()).thenReturn(mPm);
         when(mMockContext.getContentResolver()).thenReturn(mContentResolver);
@@ -203,6 +205,26 @@
         return new StatusBarNotification(mPkg, mPkg, id1, tag1, uid, uid, n, mUser, null, uid);
     }
 
+
+    private StatusBarNotification getInsistentNotification(boolean defaultVibration) {
+        final Builder builder = new Builder(mMockContext)
+                .setContentTitle("foo")
+                .setSmallIcon(android.R.drawable.sym_def_app_icon)
+                .setPriority(Notification.PRIORITY_HIGH);
+        int defaults = 0;
+        if (defaultVibration) {
+            defaults |= Notification.DEFAULT_VIBRATE;
+        } else {
+            builder.setVibrate(CUSTOM_VIBRATION);
+            channel.setVibrationPattern(CUSTOM_CHANNEL_VIBRATION);
+        }
+        builder.setDefaults(defaults);
+        builder.setFlag(Notification.FLAG_INSISTENT, true);
+
+        Notification n = builder.build();
+        return new StatusBarNotification(mPkg, mPkg, id1, tag1, uid, uid, n, mUser, null, uid);
+    }
+
     private StatusBarNotification getMessagingStyleNotification() {
         return getMessagingStyleNotification(mPkg);
     }
@@ -309,7 +331,8 @@
                 false /* lights */, false /* defaultLights */, null /* group */);
 
         NotificationRecord record = new NotificationRecord(mMockContext, sbn, defaultChannel);
-        assertEquals(CUSTOM_VIBRATION, record.getVibration());
+        assertEquals(VibratorHelper.createWaveformVibration(
+                CUSTOM_VIBRATION, /* insistent= */ false), record.getVibration());
     }
 
     @Test
@@ -322,7 +345,8 @@
                 false /* lights */, false /* defaultLights */, null /* group */);
 
         NotificationRecord record = new NotificationRecord(mMockContext, sbn, defaultChannel);
-        assertTrue(!Arrays.equals(CUSTOM_VIBRATION, record.getVibration()));
+        assertNotEquals(VibratorHelper.createWaveformVibration(
+                CUSTOM_VIBRATION, /* insistent= */ false), record.getVibration());
     }
 
     @Test
@@ -334,7 +358,18 @@
                 false /* lights */, false /* defaultLights */, null /* group */);
 
         NotificationRecord record = new NotificationRecord(mMockContext, sbn, channel);
-        assertEquals(CUSTOM_CHANNEL_VIBRATION, record.getVibration());
+        assertEquals(VibratorHelper.createWaveformVibration(
+                CUSTOM_CHANNEL_VIBRATION, /* insistent= */ false), record.getVibration());
+    }
+
+    @Test
+    public void testVibration_insistent_createsInsistentVibrationEffect() {
+        channel.enableVibration(true);
+        StatusBarNotification sbn = getInsistentNotification(false /* defaultBuzz */);
+
+        NotificationRecord record = new NotificationRecord(mMockContext, sbn, channel);
+        assertEquals(VibratorHelper.createWaveformVibration(
+                CUSTOM_CHANNEL_VIBRATION, /* insistent= */ true), record.getVibration());
     }
 
     @Test
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/RankingHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/RankingHelperTest.java
index b83f9f2..4217881 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/RankingHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/RankingHelperTest.java
@@ -41,6 +41,7 @@
 import android.net.Uri;
 import android.os.Build;
 import android.os.UserHandle;
+import android.os.Vibrator;
 import android.service.notification.StatusBarNotification;
 import android.test.suitebuilder.annotation.SmallTest;
 import android.testing.TestableContentResolver;
@@ -84,6 +85,7 @@
     @Mock Context mContext;
     @Mock ZenModeHelper mMockZenModeHelper;
     @Mock RankingConfig mConfig;
+    @Mock Vibrator mVibrator;
 
     private NotificationManager.Policy mTestNotificationPolicy;
     private Notification mNotiGroupGSortA;
@@ -125,6 +127,7 @@
                 InstrumentationRegistry.getContext().getContentResolver());
         when(mContext.getPackageManager()).thenReturn(mPm);
         when(mContext.getApplicationInfo()).thenReturn(legacy);
+        when(mContext.getSystemService(Vibrator.class)).thenReturn(mVibrator);
         TestableContentResolver contentResolver = getContext().getContentResolver();
         contentResolver.setFallbackToExisting(false);
 
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/VibratorHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/VibratorHelperTest.java
new file mode 100644
index 0000000..c77a474
--- /dev/null
+++ b/services/tests/uiservicestests/src/com/android/server/notification/VibratorHelperTest.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2021 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.notification;
+
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertNull;
+import static junit.framework.Assert.assertTrue;
+
+import static org.mockito.Mockito.when;
+
+import android.os.VibrationEffect;
+import android.os.Vibrator;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.server.UiServiceTestCase;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class VibratorHelperTest extends UiServiceTestCase {
+
+    private static final long[] CUSTOM_PATTERN = new long[] { 100, 200, 300, 400 };
+
+    @Mock private Vibrator mVibrator;
+
+    VibratorHelper mVibratorHelper;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        getContext().addMockSystemService(Vibrator.class, mVibrator);
+
+        mVibratorHelper = new VibratorHelper(getContext());
+    }
+
+    @Test
+    public void createWaveformVibration_insistent_createsRepeatingVibration() {
+        assertRepeatingVibration(
+                VibratorHelper.createWaveformVibration(CUSTOM_PATTERN, /* insistent= */ true));
+    }
+
+    @Test
+    public void createWaveformVibration_nonInsistent_createsSingleShotVibration() {
+        assertSingleVibration(
+                VibratorHelper.createWaveformVibration(CUSTOM_PATTERN, /* insistent= */ false));
+    }
+
+    @Test
+    public void createWaveformVibration_invalidPattern_returnsNullAndDoesNotCrash() {
+        assertNull(VibratorHelper.createWaveformVibration(null, false));
+        assertNull(VibratorHelper.createWaveformVibration(new long[0], false));
+        assertNull(VibratorHelper.createWaveformVibration(new long[] { 0, 0 }, false));
+    }
+
+    @Test
+    public void createVibration_insistent_createsRepeatingVibration() {
+        when(mVibrator.hasFrequencyControl()).thenReturn(false);
+        assertRepeatingVibration(mVibratorHelper.createDefaultVibration(/* insistent= */ true));
+        assertRepeatingVibration(mVibratorHelper.createFallbackVibration(/* insistent= */ true));
+
+        when(mVibrator.hasFrequencyControl()).thenReturn(true);
+        assertRepeatingVibration(mVibratorHelper.createDefaultVibration(/* insistent= */ true));
+        assertRepeatingVibration(mVibratorHelper.createFallbackVibration(/* insistent= */ true));
+    }
+
+    @Test
+    public void createVibration_nonInsistent_createsSingleShotVibration() {
+        when(mVibrator.hasFrequencyControl()).thenReturn(false);
+        assertSingleVibration(mVibratorHelper.createDefaultVibration(/* insistent= */ false));
+        assertSingleVibration(mVibratorHelper.createFallbackVibration(/* insistent= */ false));
+
+        when(mVibrator.hasFrequencyControl()).thenReturn(true);
+        assertSingleVibration(mVibratorHelper.createDefaultVibration(/* insistent= */ false));
+        assertSingleVibration(mVibratorHelper.createFallbackVibration(/* insistent= */ false));
+    }
+
+    private void assertRepeatingVibration(VibrationEffect effect) {
+        assertTrue(getRepeatIndex(effect) >= 0);
+    }
+
+    private void assertSingleVibration(VibrationEffect effect) {
+        assertEquals(-1, getRepeatIndex(effect));
+    }
+
+    private static int getRepeatIndex(VibrationEffect effect) {
+        assertTrue("Unknown vibration effect " + effect,
+                effect instanceof VibrationEffect.Composed);
+        return ((VibrationEffect.Composed) effect).getRepeatIndex();
+    }
+}
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index 6b7fc2f..8fb805b 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -4346,7 +4346,7 @@
                     "+g.3gpp.icsi-ref=\"urn%3Aurn-7%3A3gpp-service.ims.icsi.oma.cpm.deferred\"",
                     "+g.gsma.rcs.cpm.pager-large",
                     "+g.3gpp.icsi-ref=\"urn%3Aurn-7%3A3gpp-service.ims.icsi.oma.cpm.session\"",
-                    "+g.3gpp.icsi-ref=\"urn%3Aurn-7%3A3gpp-service.ims.icsi.oma.cpm.session\"",
+                    "+g.3gpp.icsi-ref=\"urn%3Aurn-7%3A3gpp-service.ims.icsi.oma.cpm.filetransfer\"",
                     "+g.3gpp.iari-ref=\"urn%3Aurn-7%3A3gpp-application.ims.iari.rcs.fthttp\"",
                     "+g.3gpp.iari-ref=\"urn%3Aurn-7%3A3gpp-application.ims.iari.rcs.ftsms\"",
                     "+g.3gpp.iari-ref=\"urn%3Aurn-7%3A3gpp-service.ims.icsi.gsma.callcomposer\"",