Merge "getBlobAshmemSize -> getOpenAshmemSize" am: b146c8bedb am: ab8578378d

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

Change-Id: Iafc23be921f28c084b6a5fecaadf60d8bf424a1f
diff --git a/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java b/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java
index 45588e8..9eb7bb71 100644
--- a/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java
+++ b/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java
@@ -3941,6 +3941,10 @@
         if (idleUntil) {
             mAlarmManager.setIdleUntil(AlarmManager.ELAPSED_REALTIME_WAKEUP,
                     mNextAlarmTime, "DeviceIdleController.deep", mDeepAlarmListener, mHandler);
+        } else if (mState == STATE_LOCATING) {
+            // Use setExact so we don't keep the GPS active for too long.
+            mAlarmManager.setExact(AlarmManager.ELAPSED_REALTIME_WAKEUP,
+                    mNextAlarmTime, "DeviceIdleController.deep", mDeepAlarmListener, mHandler);
         } else {
             if (mConstants.USE_WINDOW_ALARMS) {
                 mAlarmManager.setWindow(AlarmManager.ELAPSED_REALTIME_WAKEUP,
diff --git a/core/java/android/accounts/Account.java b/core/java/android/accounts/Account.java
index 0d6a079..e6cdcc0 100644
--- a/core/java/android/accounts/Account.java
+++ b/core/java/android/accounts/Account.java
@@ -31,6 +31,7 @@
 
 import com.android.internal.annotations.GuardedBy;
 
+import java.util.Objects;
 import java.util.Set;
 
 /**
@@ -86,6 +87,12 @@
         if (TextUtils.isEmpty(type)) {
             throw new IllegalArgumentException("the type must not be empty: " + type);
         }
+        if (name.length() > 200) {
+            throw new IllegalArgumentException("account name is longer than 200 characters");
+        }
+        if (type.length() > 200) {
+            throw new IllegalArgumentException("account type is longer than 200 characters");
+        }
         this.name = name;
         this.type = type;
         this.accessId = accessId;
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 6c3795d..3b0a5f3 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -464,11 +464,7 @@
 
         @Override
         public int hashCode() {
-            return hashCode(authority, userId);
-        }
-
-        public static int hashCode(final String auth, final int userIdent) {
-            return ((auth != null) ? auth.hashCode() : 0) ^ userIdent;
+            return ((authority != null) ? authority.hashCode() : 0) ^ userId;
         }
     }
 
@@ -490,7 +486,7 @@
     // Note we never removes items from this map but that's okay because there are only so many
     // users and so many authorities.
     @GuardedBy("mGetProviderKeys")
-    final SparseArray<ProviderKey> mGetProviderKeys = new SparseArray<>();
+    final ArrayMap<ProviderKey, ProviderKey> mGetProviderKeys = new ArrayMap<>();
 
     final ArrayMap<Activity, ArrayList<OnActivityPausedListener>> mOnPauseListeners
         = new ArrayMap<Activity, ArrayList<OnActivityPausedListener>>();
@@ -4910,7 +4906,8 @@
                 Slog.w(TAG, "Activity top position already set to onTop=" + onTop);
                 return;
             }
-            throw new IllegalStateException("Activity top position already set to onTop=" + onTop);
+            // TODO(b/197484331): Remove this short-term workaround while fixing the binder failure.
+            Slog.e(TAG, "Activity top position already set to onTop=" + onTop);
         }
 
         r.isTopResumedActivity = onTop;
@@ -7020,11 +7017,11 @@
     }
 
     private ProviderKey getGetProviderKey(String auth, int userId) {
-        final int key = ProviderKey.hashCode(auth, userId);
+        final ProviderKey key = new ProviderKey(auth, userId);
         synchronized (mGetProviderKeys) {
             ProviderKey lock = mGetProviderKeys.get(key);
             if (lock == null) {
-                lock = new ProviderKey(auth, userId);
+                lock = key;
                 mGetProviderKeys.put(key, lock);
             }
             return lock;
diff --git a/core/java/android/appwidget/AppWidgetProviderInfo.java b/core/java/android/appwidget/AppWidgetProviderInfo.java
index 063ba11..2e94dd1 100644
--- a/core/java/android/appwidget/AppWidgetProviderInfo.java
+++ b/core/java/android/appwidget/AppWidgetProviderInfo.java
@@ -143,7 +143,7 @@
     public ComponentName provider;
 
     /**
-     * The default height of the widget when added to a host, in dp. The widget will get
+     * The default height of the widget when added to a host, in px. The widget will get
      * at least this width, and will often be given more, depending on the host.
      *
      * <p>This field corresponds to the <code>android:minWidth</code> attribute in
@@ -152,7 +152,7 @@
     public int minWidth;
 
     /**
-     * The default height of the widget when added to a host, in dp. The widget will get
+     * The default height of the widget when added to a host, in px. The widget will get
      * at least this height, and will often be given more, depending on the host.
      *
      * <p>This field corresponds to the <code>android:minHeight</code> attribute in
@@ -161,7 +161,7 @@
     public int minHeight;
 
     /**
-     * Minimum width (in dp) which the widget can be resized to. This field has no effect if it
+     * Minimum width (in px) which the widget can be resized to. This field has no effect if it
      * is greater than minWidth or if horizontal resizing isn't enabled (see {@link #resizeMode}).
      *
      * <p>This field corresponds to the <code>android:minResizeWidth</code> attribute in
@@ -170,7 +170,7 @@
     public int minResizeWidth;
 
     /**
-     * Minimum height (in dp) which the widget can be resized to. This field has no effect if it
+     * Minimum height (in px) which the widget can be resized to. This field has no effect if it
      * is greater than minHeight or if vertical resizing isn't enabled (see {@link #resizeMode}).
      *
      * <p>This field corresponds to the <code>android:minResizeHeight</code> attribute in
@@ -179,7 +179,7 @@
     public int minResizeHeight;
 
     /**
-     * Maximum width (in dp) which the widget can be resized to. This field has no effect if it is
+     * Maximum width (in px) which the widget can be resized to. This field has no effect if it is
      * smaller than minWidth or if horizontal resizing isn't enabled (see {@link #resizeMode}).
      *
      * <p>This field corresponds to the <code>android:maxResizeWidth</code> attribute in the
@@ -189,7 +189,7 @@
     public int maxResizeWidth;
 
     /**
-     * Maximum height (in dp) which the widget can be resized to. This field has no effect if it is
+     * Maximum height (in px) which the widget can be resized to. This field has no effect if it is
      * smaller than minHeight or if vertical resizing isn't enabled (see {@link #resizeMode}).
      *
      * <p>This field corresponds to the <code>android:maxResizeHeight</code> attribute in the
diff --git a/core/java/android/bluetooth/BluetoothDevice.java b/core/java/android/bluetooth/BluetoothDevice.java
index c71fcc6..6e918bd 100644
--- a/core/java/android/bluetooth/BluetoothDevice.java
+++ b/core/java/android/bluetooth/BluetoothDevice.java
@@ -1340,7 +1340,10 @@
             if (alias == null) {
                 return getName();
             }
-            return alias;
+            return alias
+                    .replace('\t', ' ')
+                    .replace('\n', ' ')
+                    .replace('\r', ' ');
         } catch (RemoteException e) {
             Log.e(TAG, "", e);
         }
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index 6885c10..d9b261f 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -3286,7 +3286,7 @@
      * apps targeting SDK Version {@link android.os.Build.VERSION_CODES#O}
      * or higher are not allowed to start background services from the background.
      * See
-     * <a href="{@docRoot}/about/versions/oreo/background">
+     * <a href="/about/versions/oreo/background">
      * Background Execution Limits</a>
      * for more details.
      *
@@ -3295,7 +3295,7 @@
      * apps targeting SDK Version {@link android.os.Build.VERSION_CODES#S}
      * or higher are not allowed to start foreground services from the background.
      * See
-     * <a href="{@docRoot}/about/versions/12/behavior-changes-12">
+     * <a href="/about/versions/12/behavior-changes-12">
      * Behavior changes: Apps targeting Android 12
      * </a>
      * for more details.
@@ -3349,7 +3349,7 @@
      * apps targeting SDK Version {@link android.os.Build.VERSION_CODES#S}
      * or higher are not allowed to start foreground services from the background.
      * See
-     * <a href="{@docRoot}/about/versions/12/behavior-changes-12">
+     * <a href="/about/versions/12/behavior-changes-12">
      * Behavior changes: Apps targeting Android 12
      * </a>
      * for more details.
diff --git a/core/java/android/hardware/display/DisplayManager.java b/core/java/android/hardware/display/DisplayManager.java
index e13a7b6..fc8337a 100644
--- a/core/java/android/hardware/display/DisplayManager.java
+++ b/core/java/android/hardware/display/DisplayManager.java
@@ -1257,6 +1257,23 @@
          */
         String KEY_FIXED_REFRESH_RATE_HIGH_AMBIENT_BRIGHTNESS_THRESHOLDS =
                 "fixed_refresh_rate_high_ambient_brightness_thresholds";
+
+        /**
+         * Key for refresh rate when the device is in high brightness mode for sunlight visility.
+         *
+         * @see android.provider.DeviceConfig#NAMESPACE_DISPLAY_MANAGER
+         * @see android.R.integer#config_defaultRefreshRateInHbmSunlight
+         */
+        String KEY_REFRESH_RATE_IN_HBM_SUNLIGHT = "refresh_rate_in_hbm_sunlight";
+
+        /**
+         * Key for refresh rate when the device is in high brightness mode for HDR.
+         *
+         * @see android.provider.DeviceConfig#NAMESPACE_DISPLAY_MANAGER
+         * @see android.R.integer#config_defaultRefreshRateInHbmHdr
+         */
+        String KEY_REFRESH_RATE_IN_HBM_HDR = "refresh_rate_in_hbm_hdr";
+
         /**
          * Key for default peak refresh rate
          *
diff --git a/core/java/android/service/translation/ITranslationService.aidl b/core/java/android/service/translation/ITranslationService.aidl
index e9dd2c3b..4cc732a 100644
--- a/core/java/android/service/translation/ITranslationService.aidl
+++ b/core/java/android/service/translation/ITranslationService.aidl
@@ -24,7 +24,7 @@
 /**
  * System-wide on-device translation service.
  *
- * <p>Services requests to translate text between different languages. The primary use case for this
+ * <p>Services requests to translate data between different languages. The primary use case for this
  * service is automatic translation of text and web views, when the auto Translate feature is
  * enabled.
  *
diff --git a/core/java/android/service/translation/TranslationService.java b/core/java/android/service/translation/TranslationService.java
index 93c006a..d454c39 100644
--- a/core/java/android/service/translation/TranslationService.java
+++ b/core/java/android/service/translation/TranslationService.java
@@ -48,6 +48,7 @@
 import android.view.translation.TranslationRequest;
 import android.view.translation.TranslationResponse;
 import android.view.translation.TranslationSpec;
+import android.view.translation.Translator;
 
 import com.android.internal.os.IResultReceiver;
 
@@ -81,7 +82,10 @@
      * android.R.styleable#TranslationService translation-service}&gt;</code> tag.
      *
      * <p>Here's an example of how to use it on {@code AndroidManifest.xml}:
-     * TODO: fill in doc example (check CCService/AFService).
+     * <pre> &lt;translation-service
+     *     android:settingsActivity="foo.bar.SettingsActivity"
+     *     . . .
+     * /&gt;</pre>
      */
     public static final String SERVICE_META_DATA = "android.translation_service";
 
@@ -148,7 +152,6 @@
         void onTranslationSuccess(@NonNull TranslationResponse response);
 
         /**
-         * TODO: implement javadoc
          * @removed use {@link #onTranslationSuccess} with an error response instead.
          */
         @Deprecated
@@ -225,7 +228,7 @@
      * should call back with {@code false}.</p>
      *
      * @param translationContext the {@link TranslationContext} of the session being created.
-     * @param sessionId the int id of the session.
+     * @param sessionId the id of the session.
      * @param callback {@link Consumer} to notify whether the session was successfully created.
      */
     // TODO(b/176464808): the session id won't be unique cross client/server process. Need to find
@@ -234,8 +237,6 @@
             int sessionId, @NonNull Consumer<Boolean> callback);
 
     /**
-     * TODO: fill in javadoc.
-     *
      * @removed use {@link #onCreateTranslationSession(TranslationContext, int, Consumer)}
      * instead.
      */
@@ -246,19 +247,16 @@
     }
 
     /**
-     * TODO: fill in javadoc.
+     * Called when a translation session is finished.
      *
-     * @param sessionId
+     * <p>The translation session is finished when the client calls {@link Translator#destroy()} on
+     * the corresponding translator.
+     *
+     * @param sessionId id of the session that finished.
      */
     public abstract void onFinishTranslationSession(int sessionId);
 
     /**
-     * TODO: fill in javadoc.
-     *
-     * @param request
-     * @param sessionId
-     * @param callback
-     * @param cancellationSignal
      * @removed use
      * {@link #onTranslationRequest(TranslationRequest, int, CancellationSignal, Consumer)} instead.
      */
@@ -276,23 +274,29 @@
      * {@link TranslationRequest#FLAG_PARTIAL_RESPONSES} was set, the service may call
      * {@code callback.accept()} multiple times with partial responses.</p>
      *
-     * @param request
-     * @param sessionId
-     * @param callback
-     * @param cancellationSignal
+     * @param request The translation request containing the data to be translated.
+     * @param sessionId id of the session that sent the translation request.
+     * @param cancellationSignal A {@link CancellationSignal} that notifies when a client has
+     *                           cancelled the operation in progress.
+     * @param callback {@link Consumer} to pass back the translation response.
      */
     public abstract void onTranslationRequest(@NonNull TranslationRequest request, int sessionId,
             @Nullable CancellationSignal cancellationSignal,
             @NonNull Consumer<TranslationResponse> callback);
 
     /**
-     * TODO: fill in javadoc
+     * Called to request a set of {@link TranslationCapability}s that are supported by the service.
+     *
+     * <p>The set of translation capabilities are limited to those supporting the source and target
+     * {@link TranslationSpec.DataFormat}. e.g. Calling this with
+     * {@link TranslationSpec#DATA_FORMAT_TEXT} as source and target returns only capabilities that
+     * translates text to text.</p>
      *
      * <p>Must call {@code callback.accept} to pass back the set of translation capabilities.</p>
      *
-     * @param sourceFormat
-     * @param targetFormat
-     * @param callback
+     * @param sourceFormat data format restriction of the translation source spec.
+     * @param targetFormat data format restriction of the translation target spec.
+     * @param callback {@link Consumer} to pass back the set of translation capabilities.
      */
     public abstract void onTranslationCapabilitiesRequest(
             @TranslationSpec.DataFormat int sourceFormat,
diff --git a/core/java/android/text/TextLine.java b/core/java/android/text/TextLine.java
index 1f11d10..1a7ec7f 100644
--- a/core/java/android/text/TextLine.java
+++ b/core/java/android/text/TextLine.java
@@ -782,7 +782,7 @@
 
         int spanStart = runStart;
         int spanLimit;
-        if (mSpanned == null) {
+        if (mSpanned == null || runStart == runLimit) {
             spanLimit = runLimit;
         } else {
             int target = after ? offset + 1 : offset;
diff --git a/core/java/android/view/Surface.java b/core/java/android/view/Surface.java
index ff2d2eb..fa7330f 100644
--- a/core/java/android/view/Surface.java
+++ b/core/java/android/view/Surface.java
@@ -98,6 +98,7 @@
 
     private static native int nativeSetFrameRate(
             long nativeObject, float frameRate, int compatibility, int changeFrameRateStrategy);
+    private static native void nativeDestroy(long nativeObject);
 
     public static final @android.annotation.NonNull Parcelable.Creator<Surface> CREATOR =
             new Parcelable.Creator<Surface>() {
@@ -339,6 +340,9 @@
      */
     @UnsupportedAppUsage
     public void destroy() {
+        if (mNativeObject != 0) {
+            nativeDestroy(mNativeObject);
+        }
         release();
     }
 
diff --git a/core/java/android/view/SurfaceView.java b/core/java/android/view/SurfaceView.java
index c1956e4..4b8b607de 100644
--- a/core/java/android/view/SurfaceView.java
+++ b/core/java/android/view/SurfaceView.java
@@ -903,7 +903,7 @@
         mSurfaceAlpha = 1f;
 
         synchronized (mSurfaceControlLock) {
-            mSurface.release();
+            mSurface.destroy();
             if (mBlastBufferQueue != null) {
                 mBlastBufferQueue.destroy();
                 mBlastBufferQueue = null;
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 3550a31..93138e5 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -2836,8 +2836,13 @@
                     }
                 }
 
+                final boolean surfaceControlChanged =
+                        (relayoutResult & RELAYOUT_RES_SURFACE_CHANGED)
+                                == RELAYOUT_RES_SURFACE_CHANGED;
+
                 if (mSurfaceControl.isValid()) {
-                    updateOpacity(mWindowAttributes, dragResizing);
+                    updateOpacity(mWindowAttributes, dragResizing,
+                            surfaceControlChanged /*forceUpdate */);
                 }
 
                 if (DEBUG_LAYOUT) Log.v(mTag, "relayout: frame=" + frame.toShortString()
@@ -2872,9 +2877,7 @@
                 // RELAYOUT_RES_SURFACE_CHANGED since it should indicate that WMS created a new
                 // SurfaceControl.
                 surfaceReplaced = (surfaceGenerationId != mSurface.getGenerationId()
-                        || (relayoutResult & RELAYOUT_RES_SURFACE_CHANGED)
-                        == RELAYOUT_RES_SURFACE_CHANGED)
-                        && mSurface.isValid();
+                        || surfaceControlChanged) && mSurface.isValid();
                 if (surfaceReplaced) {
                     mSurfaceSequenceId++;
                 }
@@ -7824,7 +7827,8 @@
         return relayoutResult;
     }
 
-    private void updateOpacity(WindowManager.LayoutParams params, boolean dragResizing) {
+    private void updateOpacity(WindowManager.LayoutParams params, boolean dragResizing,
+            boolean forceUpdate) {
         boolean opaque = false;
 
         if (!PixelFormat.formatHasAlpha(params.format)
@@ -7840,7 +7844,7 @@
             opaque = true;
         }
 
-        if (mIsSurfaceOpaque == opaque) {
+        if (!forceUpdate && mIsSurfaceOpaque == opaque) {
             return;
         }
 
diff --git a/core/java/android/view/translation/TranslationCapability.java b/core/java/android/view/translation/TranslationCapability.java
index 65b749a..b7e13dd 100644
--- a/core/java/android/view/translation/TranslationCapability.java
+++ b/core/java/android/view/translation/TranslationCapability.java
@@ -40,15 +40,18 @@
 public final class TranslationCapability implements Parcelable {
 
     /**
-     * TODO: fill in javadoc
+     * The translation service supports translation between the source and target specs, and it is
+     * ready to be downloaded onto the device.
      */
     public static final @ModelState int STATE_AVAILABLE_TO_DOWNLOAD = 1;
     /**
-     * TODO: fill in javadoc
+     * The translation service supports translation between the source and target specs, and it is
+     * being downloaded onto the device currently.
      */
     public static final @ModelState int STATE_DOWNLOADING = 2;
     /**
-     * TODO: fill in javadoc
+     * The translation service supports translation between the source and target specs, and it is
+     * downloaded and ready to use on device.
      */
     public static final @ModelState int STATE_ON_DEVICE = 3;
     /**
@@ -305,7 +308,7 @@
     };
 
     @DataClass.Generated(
-            time = 1624307114468L,
+            time = 1629158466039L,
             codegenVersion = "1.0.23",
             sourceFile = "frameworks/base/core/java/android/view/translation/TranslationCapability.java",
             inputSignatures = "public static final @android.view.translation.TranslationCapability.ModelState int STATE_AVAILABLE_TO_DOWNLOAD\npublic static final @android.view.translation.TranslationCapability.ModelState int STATE_DOWNLOADING\npublic static final @android.view.translation.TranslationCapability.ModelState int STATE_ON_DEVICE\npublic static final @android.view.translation.TranslationCapability.ModelState int STATE_NOT_AVAILABLE\npublic static final @android.view.translation.TranslationCapability.ModelState int STATE_REMOVED_AND_AVAILABLE\nprivate final @android.view.translation.TranslationCapability.ModelState int mState\nprivate final @android.annotation.NonNull android.view.translation.TranslationSpec mSourceSpec\nprivate final @android.annotation.NonNull android.view.translation.TranslationSpec mTargetSpec\nprivate final  boolean mUiTranslationEnabled\nprivate final @android.view.translation.TranslationContext.TranslationFlag int mSupportedTranslationFlags\nclass TranslationCapability extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genHiddenConstDefs=true, genToString=true, genConstructor=false)")
diff --git a/core/java/android/view/translation/TranslationRequest.java b/core/java/android/view/translation/TranslationRequest.java
index df4836e..0d41851 100644
--- a/core/java/android/view/translation/TranslationRequest.java
+++ b/core/java/android/view/translation/TranslationRequest.java
@@ -39,12 +39,16 @@
     public static final @RequestFlags int FLAG_TRANSLATION_RESULT = 0x1;
     /**
      * Indicates this request wants to receive the dictionary result.
-     * TODO: describe the structure of the result.
+     *
+     * <p>See {@link TranslationResponseValue#EXTRA_DEFINITIONS} for more detail on the structure
+     * of the returned data.
      */
     public static final @RequestFlags int FLAG_DICTIONARY_RESULT = 0x2;
     /**
      * Indicates this request wants to receive the transliteration result.
-     * TODO: describe the structure of the result.
+     *
+     * <p>This returns a CharSequence representation of the transliteration of the translated text.
+     *  See {@link TranslationResponseValue#getTransliteration()}.
      */
     public static final @RequestFlags int FLAG_TRANSLITERATION_RESULT = 0x4;
     /**
@@ -327,7 +331,8 @@
             return this;
         }
 
-        /** @see #setTranslationRequestValues
+        /**
+         * @see #setTranslationRequestValues
          * @removed
          */
         @DataClass.Generated.Member
@@ -352,7 +357,8 @@
             return this;
         }
 
-        /** @see #setViewTranslationRequests
+        /**
+         * @see #setViewTranslationRequests
          * @removed
          */
         @DataClass.Generated.Member
@@ -394,7 +400,7 @@
     }
 
     @DataClass.Generated(
-            time = 1620429997487L,
+            time = 1629159107226L,
             codegenVersion = "1.0.23",
             sourceFile = "frameworks/base/core/java/android/view/translation/TranslationRequest.java",
             inputSignatures = "public static final @android.view.translation.TranslationRequest.RequestFlags int FLAG_TRANSLATION_RESULT\npublic static final @android.view.translation.TranslationRequest.RequestFlags int FLAG_DICTIONARY_RESULT\npublic static final @android.view.translation.TranslationRequest.RequestFlags int FLAG_TRANSLITERATION_RESULT\npublic static final @android.view.translation.TranslationRequest.RequestFlags int FLAG_PARTIAL_RESPONSES\nprivate final @android.view.translation.TranslationRequest.RequestFlags int mFlags\nprivate final @android.annotation.NonNull @com.android.internal.util.DataClass.PluralOf(\"translationRequestValue\") java.util.List<android.view.translation.TranslationRequestValue> mTranslationRequestValues\nprivate final @android.annotation.NonNull @com.android.internal.util.DataClass.PluralOf(\"viewTranslationRequest\") java.util.List<android.view.translation.ViewTranslationRequest> mViewTranslationRequests\nprivate static  int defaultFlags()\nprivate static  java.util.List<android.view.translation.TranslationRequestValue> defaultTranslationRequestValues()\nprivate static  java.util.List<android.view.translation.ViewTranslationRequest> defaultViewTranslationRequests()\nclass TranslationRequest extends java.lang.Object implements [android.os.Parcelable]\npublic abstract @java.lang.Deprecated android.view.translation.TranslationRequest.Builder addTranslationRequestValue(android.view.translation.TranslationRequestValue)\npublic abstract @java.lang.Deprecated android.view.translation.TranslationRequest.Builder addViewTranslationRequest(android.view.translation.ViewTranslationRequest)\nclass BaseBuilder extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genToString=true, genHiddenConstDefs=true, genBuilder=true)\npublic abstract @java.lang.Deprecated android.view.translation.TranslationRequest.Builder addTranslationRequestValue(android.view.translation.TranslationRequestValue)\npublic abstract @java.lang.Deprecated android.view.translation.TranslationRequest.Builder addViewTranslationRequest(android.view.translation.ViewTranslationRequest)\nclass BaseBuilder extends java.lang.Object implements []")
diff --git a/core/java/android/view/translation/TranslationResponseValue.java b/core/java/android/view/translation/TranslationResponseValue.java
index a24dbc3..9dff2d5 100644
--- a/core/java/android/view/translation/TranslationResponseValue.java
+++ b/core/java/android/view/translation/TranslationResponseValue.java
@@ -93,9 +93,11 @@
     @NonNull
     private final Bundle mExtras;
 
+    // TODO: Add example of transliteration.
     /**
      * The transliteration result of the translated text.
-     * TODO: Describe the result structure.
+     *
+     * <p>This returns a CharSequence representation of the transliteration of the translated text.
      */
     @Nullable
     private final CharSequence mTransliteration;
@@ -223,7 +225,8 @@
 
     /**
      * The transliteration result of the translated text.
-     * TODO: Describe the result structure.
+     *
+     * <p>This returns a CharSequence representation of the transliteration of the translated text.
      */
     @DataClass.Generated.Member
     public @Nullable CharSequence getTransliteration() {
@@ -407,7 +410,8 @@
 
         /**
          * The transliteration result of the translated text.
-         * TODO: Describe the result structure.
+         *
+         * <p>This returns a CharSequence representation of the transliteration of the translated text.
          */
         @DataClass.Generated.Member
         public @NonNull Builder setTransliteration(@NonNull CharSequence value) {
@@ -448,7 +452,7 @@
     }
 
     @DataClass.Generated(
-            time = 1622133051937L,
+            time = 1631057245846L,
             codegenVersion = "1.0.23",
             sourceFile = "frameworks/base/core/java/android/view/translation/TranslationResponseValue.java",
             inputSignatures = "public static final  int STATUS_SUCCESS\npublic static final  int STATUS_ERROR\npublic static final  java.lang.String EXTRA_DEFINITIONS\nprivate final @android.view.translation.TranslationResponseValue.Status int mStatusCode\nprivate final @android.annotation.Nullable java.lang.CharSequence mText\nprivate final @android.annotation.NonNull android.os.Bundle mExtras\nprivate final @android.annotation.Nullable java.lang.CharSequence mTransliteration\npublic static @android.annotation.NonNull android.view.translation.TranslationResponseValue forError()\nprivate static  java.lang.CharSequence defaultText()\nprivate static  android.os.Bundle defaultExtras()\nprivate  boolean extrasEquals(android.os.Bundle)\nprivate static  java.lang.CharSequence defaultTransliteration()\nclass TranslationResponseValue extends java.lang.Object implements [android.os.Parcelable]\nclass BaseBuilder extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genBuilder=true, genToString=true, genEqualsHashCode=true, genHiddenConstDefs=true)\nclass BaseBuilder extends java.lang.Object implements []")
diff --git a/core/java/android/view/translation/UiTranslationController.java b/core/java/android/view/translation/UiTranslationController.java
index a833591..442d099 100644
--- a/core/java/android/view/translation/UiTranslationController.java
+++ b/core/java/android/view/translation/UiTranslationController.java
@@ -356,7 +356,11 @@
             }
             for (int i = 0; i < translatedResult.size(); i++) {
                 final AutofillId autofillId = new AutofillId(translatedResult.keyAt(i));
-                final View view = mViews.get(autofillId).get();
+                final WeakReference<View> viewRef = mViews.get(autofillId);
+                if (viewRef == null) {
+                    continue;
+                }
+                final View view = viewRef.get();
                 if (view == null) {
                     Log.w(TAG, "onTranslationCompleted: the view for autofill id " + autofillId
                             + " may be gone.");
@@ -416,7 +420,11 @@
                     Log.w(TAG, "No AutofillId is set in ViewTranslationResponse");
                     continue;
                 }
-                final View view = mViews.get(autofillId).get();
+                final WeakReference<View> viewRef = mViews.get(autofillId);
+                if (viewRef == null) {
+                    continue;
+                }
+                final View view = viewRef.get();
                 if (view == null) {
                     Log.w(TAG, "onTranslationCompleted: the view for autofill id " + autofillId
                             + " may be gone.");
diff --git a/core/java/android/view/translation/UiTranslationManager.java b/core/java/android/view/translation/UiTranslationManager.java
index b9ed32c..3012e93 100644
--- a/core/java/android/view/translation/UiTranslationManager.java
+++ b/core/java/android/view/translation/UiTranslationManager.java
@@ -33,6 +33,7 @@
 import android.util.Log;
 import android.view.View;
 import android.view.autofill.AutofillId;
+import android.widget.TextView;
 
 import com.android.internal.annotations.GuardedBy;
 
@@ -42,11 +43,50 @@
 import java.util.Map;
 import java.util.Objects;
 import java.util.concurrent.Executor;
+import java.util.function.Consumer;
 
-// TODO(b/178044703): Describe what UI Translation is.
 /**
- * The {@link UiTranslationManager} class provides ways for apps to use the ui translation
+ * <p>The {@link UiTranslationManager} class provides ways for apps to use the ui translation
  * function in framework.
+ *
+ * <p> The UI translation provides ways for apps to support inline translation for the views. For
+ * example the system supports text translation for {@link TextView}. To support UI translation for
+ * your views, you should override the following methods to provide the content to be translated
+ * and deal with the translated result. Here is an example for {@link TextView}-like views:
+ *
+ * <pre><code>
+ * public class MyTextView extends View {
+ *     public MyTextView(...) {
+ *         // implements how to show the translated result in your View in
+ *         // ViewTranslationCallback and set it by setViewTranslationCallback()
+ *         setViewTranslationCallback(new MyViewTranslationCallback());
+ *     }
+ *
+ *     public void onCreateViewTranslationRequest(int[] supportedFormats,
+ *             Consumer<ViewTranslationRequest> requestsCollector) {
+ *        // collect the information that needs to be translated
+ *        ViewTranslationRequest.Builder requestBuilder =
+ *                     new ViewTranslationRequest.Builder(getAutofillId());
+ *        requestBuilder.setValue(ViewTranslationRequest.ID_TEXT,
+ *                         TranslationRequestValue.forText(etText()));
+ *        requestsCollector.accept(requestBuilder.build());
+ *     }
+ *
+ *     public void onProvideContentCaptureStructure(
+ *             ViewStructure structure, int flags) {
+ *         // set ViewTranslationResponse
+ *         super.onViewTranslationResponse(response);
+ *     }
+ * }
+ * </code></pre>
+ *
+ * <p>If your view provides its own virtual hierarchy (for example, if it's a browser that draws the
+ * HTML using {@link android.graphics.Canvas} or native libraries in a different render process),
+ * you must override {@link View#onCreateVirtualViewTranslationRequests(long[], int[], Consumer)} to
+ * provide the content to be translated and implement
+ * {@link View#onVirtualViewTranslationResponses(android.util.LongSparseArray)} for the translated
+ * result. You also need to implement {@link android.view.translation.ViewTranslationCallback} to
+ * handle the translated information show or hide in your {@link View}.
  */
 public final class UiTranslationManager {
 
@@ -248,14 +288,14 @@
         }
     }
 
-    // TODO(b/178044703): Fix the View API link when it becomes public.
     /**
      * Register for notifications of UI Translation state changes on the foreground activity. This
      * is available to the owning application itself and also the current input method.
      * <p>
      * The application whose UI is being translated can use this to customize the UI Translation
      * behavior in ways that aren't made easy by methods like
-     * View#onCreateTranslationRequest().
+     * {@link View#onCreateViewTranslationRequest(int[], Consumer)}.
+     *
      * <p>
      * Input methods can use this to offer complementary features to UI Translation; for example,
      * enabling outgoing message translation when the system is translating incoming messages in a
diff --git a/core/java/android/view/translation/ViewTranslationCallback.java b/core/java/android/view/translation/ViewTranslationCallback.java
index 6efd621..a075662 100644
--- a/core/java/android/view/translation/ViewTranslationCallback.java
+++ b/core/java/android/view/translation/ViewTranslationCallback.java
@@ -19,9 +19,17 @@
 import android.annotation.NonNull;
 import android.annotation.UiThread;
 import android.view.View;
+import android.view.contentcapture.ContentCaptureSession;
 
 /**
- * Callback for handling the translated information show or hide in the {@link View}.
+ * <p> Callback for handling the translated information show or hide in the {@link View}.
+ *
+ * <p> When the platform intelligence starts translation of an app's ui, the system will call
+ * {@link View#dispatchCreateViewTranslationRequest} to collect the {@link ViewTranslationRequest}s
+ * for translation purpose by traversing the hierarchy then send to translation service. After
+ * receiving the {@link ViewTranslationResponse}, the system will call
+ * {@link ViewTranslationCallback#onShowTranslation(View)} to show the translated information for
+ * the {@link View}.
  */
 @UiThread
 public interface ViewTranslationCallback {
@@ -33,13 +41,19 @@
      * method will not be called before {@link View#onViewTranslationResponse} or
      * {@link View#onVirtualViewTranslationResponses}.
      *
+     * <p> NOTE: For TextView implementation, {@link ContentCaptureSession#notifyViewTextChanged}
+     * shouldn't be called with the translated text, simply calling setText() here will trigger the
+     * method. You should either override {@code View#onProvideContentCaptureStructure()} to report
+     * the original text instead of the translated text or use a different approach to display the
+     * translated text.
+     *
      * See {@link View#onViewTranslationResponse} for how to get the translated information.
      *
      * @return {@code true} if the View handles showing the translation.
      */
     boolean onShowTranslation(@NonNull View view);
     /**
-     * Called when the user wants to show the original text instead of the translated text. This
+     * Called when user wants to view the original content instead of the translated content. This
      * method will not be called before {@link View#onViewTranslationResponse} or
      * {@link View#onViewTranslationResponse}.
      *
@@ -47,7 +61,8 @@
      */
     boolean onHideTranslation(@NonNull View view);
     /**
-     * Called when the user finish the Ui translation and no longer to show the translated text.
+     * Called when the translation state is no longer needed. It should restore the original content
+     * and clear all saved states.
      *
      * @return {@code true} if the View handles clearing the translation.
      */
diff --git a/core/java/android/window/ITaskOrganizer.aidl b/core/java/android/window/ITaskOrganizer.aidl
index 8b8dba8..69bc1b5 100644
--- a/core/java/android/window/ITaskOrganizer.aidl
+++ b/core/java/android/window/ITaskOrganizer.aidl
@@ -88,4 +88,9 @@
      * user has pressed back on the root activity of a task controlled by the task organizer.
      */
     void onBackPressedOnTaskRoot(in ActivityManager.RunningTaskInfo taskInfo);
+
+    /**
+     * Called when the IME has drawn on the organized task.
+     */
+    void onImeDrawnOnTask(int taskId);
 }
diff --git a/core/java/android/window/TaskOrganizer.java b/core/java/android/window/TaskOrganizer.java
index c7c91cd..d8723a8 100644
--- a/core/java/android/window/TaskOrganizer.java
+++ b/core/java/android/window/TaskOrganizer.java
@@ -144,6 +144,10 @@
     @BinderThread
     public void onBackPressedOnTaskRoot(@NonNull ActivityManager.RunningTaskInfo taskInfo) {}
 
+    /** @hide */
+    @BinderThread
+    public void onImeDrawnOnTask(int taskId) {}
+
     /**
      * Creates a persistent root task in WM for a particular windowing-mode.
      * @param displayId The display to create the root task on.
@@ -287,6 +291,11 @@
         public void onBackPressedOnTaskRoot(ActivityManager.RunningTaskInfo info) {
             mExecutor.execute(() -> TaskOrganizer.this.onBackPressedOnTaskRoot(info));
         }
+
+        @Override
+        public void onImeDrawnOnTask(int taskId) {
+            mExecutor.execute(() -> TaskOrganizer.this.onImeDrawnOnTask(taskId));
+        }
     };
 
     private ITaskOrganizerController getController() {
diff --git a/core/java/com/android/internal/infra/OWNERS b/core/java/com/android/internal/infra/OWNERS
new file mode 100644
index 0000000..4550358
--- /dev/null
+++ b/core/java/com/android/internal/infra/OWNERS
@@ -0,0 +1,6 @@
+per-file AndroidFuture.java = eugenesusla@google.com
+per-file RemoteStream.java = eugenesusla@google.com
+per-file PerUser.java = eugenesusla@google.com
+per-file ServiceConnector.java = eugenesusla@google.com
+per-file AndroidFuture.aidl = eugenesusla@google.com
+per-file IAndroidFuture.aidl = eugenesusla@google.com
\ No newline at end of file
diff --git a/core/java/com/android/internal/jank/FrameTracker.java b/core/java/com/android/internal/jank/FrameTracker.java
index 8e7fae7..d12c870 100644
--- a/core/java/com/android/internal/jank/FrameTracker.java
+++ b/core/java/com/android/internal/jank/FrameTracker.java
@@ -50,6 +50,7 @@
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
+import java.util.concurrent.TimeUnit;
 
 /**
  * A class that allows the app to get the frame metrics from HardwareRendererObserver.
@@ -103,6 +104,7 @@
     private boolean mCancelled = false;
     private FrameTrackerListener mListener;
     private boolean mTracingStarted = false;
+    private Runnable mWaitForFinishTimedOut;
 
     private static class JankInfo {
         long frameVsyncId;
@@ -263,10 +265,16 @@
             if (mListener != null) {
                 mListener.onCujEvents(mSession, ACTION_SESSION_END);
             }
+            // We don't remove observer here,
+            // will remove it when all the frame metrics in this duration are called back.
+            // See onFrameMetricsAvailable for the logic of removing the observer.
+            // Waiting at most 10 seconds for all callbacks to finish.
+            mWaitForFinishTimedOut = () -> {
+                Log.e(TAG, "force finish cuj because of time out:" + mSession.getName());
+                finish(mJankInfos.size() - 1);
+            };
+            mHandler.postDelayed(mWaitForFinishTimedOut, TimeUnit.SECONDS.toMillis(10));
         }
-        // We don't remove observer here,
-        // will remove it when all the frame metrics in this duration are called back.
-        // See onFrameMetricsAvailable for the logic of removing the observer.
     }
 
     /**
@@ -396,7 +404,8 @@
     }
 
     private void finish(int indexOnOrAfterEnd) {
-
+        mHandler.removeCallbacks(mWaitForFinishTimedOut);
+        mWaitForFinishTimedOut = null;
         mMetricsFinalized = true;
 
         // The tracing has been ended, remove the observer, see if need to trigger perfetto.
@@ -481,7 +490,7 @@
             }
         }
         if (DEBUG) {
-            Log.i(TAG, "FrameTracker: CUJ=" + mSession.getName()
+            Log.i(TAG, "finish: CUJ=" + mSession.getName()
                     + " (" + mBeginVsyncId + "," + mEndVsyncId + ")"
                     + " totalFrames=" + totalFramesCount
                     + " missedAppFrames=" + missedAppFramesCount
diff --git a/core/java/com/android/internal/jank/InteractionJankMonitor.java b/core/java/com/android/internal/jank/InteractionJankMonitor.java
index aabcd7f..610cd73 100644
--- a/core/java/com/android/internal/jank/InteractionJankMonitor.java
+++ b/core/java/com/android/internal/jank/InteractionJankMonitor.java
@@ -103,7 +103,7 @@
     private static final String ACTION_PREFIX = InteractionJankMonitor.class.getCanonicalName();
 
     private static final String DEFAULT_WORKER_NAME = TAG + "-Worker";
-    private static final long DEFAULT_TIMEOUT_MS = TimeUnit.SECONDS.toMillis(5L);
+    private static final long DEFAULT_TIMEOUT_MS = TimeUnit.SECONDS.toMillis(2L);
     private static final String SETTINGS_ENABLED_KEY = "enabled";
     private static final String SETTINGS_SAMPLING_INTERVAL_KEY = "sampling_interval";
     private static final String SETTINGS_THRESHOLD_MISSED_FRAMES_KEY =
diff --git a/core/java/com/android/internal/util/function/pooled/OWNERS b/core/java/com/android/internal/util/function/pooled/OWNERS
new file mode 100644
index 0000000..da723b3
--- /dev/null
+++ b/core/java/com/android/internal/util/function/pooled/OWNERS
@@ -0,0 +1 @@
+eugenesusla@google.com
\ No newline at end of file
diff --git a/core/jni/OWNERS b/core/jni/OWNERS
index 78787e5..da89663 100644
--- a/core/jni/OWNERS
+++ b/core/jni/OWNERS
@@ -1,6 +1,6 @@
 # Camera
 per-file *Camera*,*camera* = cychen@google.com, epeev@google.com, etalvala@google.com
-per-file *Camera*,*camera* = shuzhenwang@google.com, zhijunhe@google.com
+per-file *Camera*,*camera* = shuzhenwang@google.com, yinchiayeh@google.com, zhijunhe@google.com
 
 # Connectivity
 per-file android_net_* = codewiz@google.com, jchalard@google.com, lorenzo@google.com, reminv@google.com, satk@google.com
diff --git a/core/jni/android_view_Surface.cpp b/core/jni/android_view_Surface.cpp
index 0957067..869b53d 100644
--- a/core/jni/android_view_Surface.cpp
+++ b/core/jni/android_view_Surface.cpp
@@ -449,6 +449,11 @@
                         int(changeFrameRateStrategy));
 }
 
+static void nativeDestroy(JNIEnv* env, jclass clazz, jlong nativeObject) {
+    sp<Surface> surface(reinterpret_cast<Surface*>(nativeObject));
+    surface->destroy();
+}
+
 // ----------------------------------------------------------------------------
 
 static const JNINativeMethod gSurfaceMethods[] = {
@@ -477,6 +482,7 @@
         {"nativeSetAutoRefreshEnabled", "(JZ)I", (void*)nativeSetAutoRefreshEnabled},
         {"nativeSetFrameRate", "(JFII)I", (void*)nativeSetFrameRate},
         {"nativeGetFromBlastBufferQueue", "(JJ)J", (void*)nativeGetFromBlastBufferQueue},
+        {"nativeDestroy", "(J)V", (void*)nativeDestroy},
 };
 
 int register_android_view_Surface(JNIEnv* env)
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index aa05b13..ada4f0e 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -4497,6 +4497,13 @@
          If non-positive, then the refresh rate is unchanged even if thresholds are configured. -->
     <integer name="config_fixedRefreshRateInHighZone">0</integer>
 
+    <!-- Default refresh rate while the device has high brightness mode enabled for Sunlight.
+         This value overrides values from DisplayDeviceConfig -->
+    <integer name="config_defaultRefreshRateInHbmSunlight">0</integer>
+
+    <!-- Default refresh rate while the device has high brightness mode enabled for HDR. -->
+    <integer name="config_defaultRefreshRateInHbmHdr">0</integer>
+
     <!-- The type of the light sensor to be used by the display framework for things like
          auto-brightness. If unset, then it just gets the default sensor of type TYPE_LIGHT. -->
     <string name="config_displayLightSensorType" translatable="false" />
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 092be40..bd10263 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -3949,6 +3949,8 @@
   <java-symbol type="integer" name="config_defaultRefreshRateInZone" />
   <java-symbol type="array" name="config_brightnessThresholdsOfPeakRefreshRate" />
   <java-symbol type="array" name="config_ambientThresholdsOfPeakRefreshRate" />
+  <java-symbol type="integer" name="config_defaultRefreshRateInHbmSunlight" />
+  <java-symbol type="integer" name="config_defaultRefreshRateInHbmHdr" />
 
   <!-- For fixed refresh rate displays in high brightness-->
   <java-symbol type="integer" name="config_fixedRefreshRateInHighZone" />
diff --git a/graphics/java/android/graphics/ImageFormat.java b/graphics/java/android/graphics/ImageFormat.java
index 5b79d76..884d27f 100644
--- a/graphics/java/android/graphics/ImageFormat.java
+++ b/graphics/java/android/graphics/ImageFormat.java
@@ -178,22 +178,8 @@
      * <p>Android YUV P010 format.</p>
      *
      * P010 is a 4:2:0 YCbCr semiplanar format comprised of a WxH Y plane
-     * followed immediately by a Wx(H/2) CbCr plane. Each sample is
-     * represented by a 16-bit little-endian value, with the lower 6 bits set
-     * to zero.
-     *
-     * <p>This format assumes
-     * <ul>
-     * <li>an even height</li>
-     * <li>a vertical stride equal to the height</li>
-     * </ul>
-     * </p>
-     *
-     * <pre>   stride_in_bytes = stride * 2 </pre>
-     * <pre>   y_size = stride_in_bytes * height </pre>
-     * <pre>   cbcr_size = stride_in_bytes * (height / 2) </pre>
-     * <pre>   cb_offset = y_size </pre>
-     * <pre>   cr_offset = cb_offset + 2 </pre>
+     * followed by a Wx(H/2) CbCr plane. Each sample is represented by a 16-bit
+     * little-endian value, with the lower 6 bits set to zero.
      *
      * <p>For example, the {@link android.media.Image} object can provide data
      * in this format from a {@link android.hardware.camera2.CameraDevice}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
index ba0ab6d..656bdff 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
@@ -336,6 +336,13 @@
     }
 
     @Override
+    public void onImeDrawnOnTask(int taskId) {
+        if (mStartingWindow != null) {
+            mStartingWindow.onImeDrawnOnTask(taskId);
+        }
+    }
+
+    @Override
     public void onTaskAppeared(RunningTaskInfo taskInfo, SurfaceControl leash) {
         synchronized (mLock) {
             onTaskAppeared(new TaskAppearedInfo(taskInfo, leash));
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 fc7c86d..147f5e3 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
@@ -545,6 +545,15 @@
         removeWindowSynced(taskId, null, null, false);
     }
 
+    void onImeDrawnOnTask(int taskId) {
+        final StartingWindowRecord record = mStartingWindowRecords.get(taskId);
+        if (record != null && record.mTaskSnapshotWindow != null
+                && record.mTaskSnapshotWindow.hasImeSurface()) {
+            record.mTaskSnapshotWindow.removeImmediately();
+        }
+        mStartingWindowRecords.remove(taskId);
+    }
+
     protected void removeWindowSynced(int taskId, SurfaceControl leash, Rect frame,
             boolean playRevealAnimation) {
         final StartingWindowRecord record = mStartingWindowRecords.get(taskId);
@@ -572,14 +581,15 @@
                     Slog.e(TAG, "Found empty splash screen, remove!");
                     removeWindowInner(record.mDecorView, false);
                 }
+                mStartingWindowRecords.remove(taskId);
             }
             if (record.mTaskSnapshotWindow != null) {
                 if (DEBUG_TASK_SNAPSHOT) {
                     Slog.v(TAG, "Removing task snapshot window for " + taskId);
                 }
-                record.mTaskSnapshotWindow.remove();
+                record.mTaskSnapshotWindow.scheduleRemove(
+                        () -> mStartingWindowRecords.remove(taskId));
             }
-            mStartingWindowRecords.remove(taskId);
         }
     }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingWindowController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingWindowController.java
index e84d498..dee21b0 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingWindowController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingWindowController.java
@@ -177,6 +177,13 @@
     }
 
     /**
+     * Called when the IME has drawn on the organized task.
+     */
+    public void onImeDrawnOnTask(int taskId) {
+        mSplashScreenExecutor.execute(() -> mStartingSurfaceDrawer.onImeDrawnOnTask(taskId));
+    }
+
+    /**
      * Called when the content of a task is ready to show, starting window can be removed.
      */
     public void removeStartingWindow(int taskId, SurfaceControl leash, Rect frame,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/TaskSnapshotWindow.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/TaskSnapshotWindow.java
index 6052d3d..dfb1ae3 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/TaskSnapshotWindow.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/TaskSnapshotWindow.java
@@ -64,7 +64,6 @@
 import android.hardware.HardwareBuffer;
 import android.os.IBinder;
 import android.os.RemoteException;
-import android.os.SystemClock;
 import android.os.Trace;
 import android.util.MergedConfiguration;
 import android.util.Slog;
@@ -119,7 +118,12 @@
     private static final String TITLE_FORMAT = "SnapshotStartingWindow for taskId=%s";
 
     private static final long DELAY_REMOVAL_TIME_GENERAL = 100;
-    private static final long DELAY_REMOVAL_TIME_IME_VISIBLE = 350;
+    /**
+     * The max delay time in milliseconds for removing the task snapshot window with IME visible.
+     * Ideally the delay time will be shorter when receiving
+     * {@link StartingSurfaceDrawer#onImeDrawnOnTask(int)}.
+     */
+    private static final long MAX_DELAY_REMOVAL_TIME_IME_VISIBLE = 600;
 
     //tmp vars for unused relayout params
     private static final Point TMP_SURFACE_SIZE = new Point();
@@ -138,7 +142,6 @@
     private final RectF mTmpDstFrame = new RectF();
     private final CharSequence mTitle;
     private boolean mHasDrawn;
-    private long mShownTime;
     private boolean mSizeMismatch;
     private final Paint mBackgroundPaint = new Paint();
     private final int mActivityType;
@@ -148,6 +151,8 @@
     private final SurfaceControl.Transaction mTransaction;
     private final Matrix mSnapshotMatrix = new Matrix();
     private final float[] mTmpFloat9 = new float[9];
+    private Runnable mScheduledRunnable;
+    private final boolean mHasImeSurface;
 
     static TaskSnapshotWindow create(StartingWindowInfo info, IBinder appToken,
             TaskSnapshot snapshot, ShellExecutor splashScreenExecutor,
@@ -216,7 +221,7 @@
             taskDescription.setBackgroundColor(WHITE);
         }
 
-        final long delayRemovalTime = snapshot.hasImeSurface() ? DELAY_REMOVAL_TIME_IME_VISIBLE
+        final long delayRemovalTime = snapshot.hasImeSurface() ? MAX_DELAY_REMOVAL_TIME_IME_VISIBLE
                 : DELAY_REMOVAL_TIME_GENERAL;
 
         final TaskSnapshotWindow snapshotSurface = new TaskSnapshotWindow(
@@ -281,12 +286,17 @@
         mDelayRemovalTime = delayRemovalTime;
         mTransaction = new SurfaceControl.Transaction();
         mClearWindowHandler = clearWindowHandler;
+        mHasImeSurface = snapshot.hasImeSurface();
     }
 
     int getBackgroundColor() {
         return mBackgroundPaint.getColor();
     }
 
+    boolean hasImeSurface() {
+        return mHasImeSurface;
+    }
+
     /**
      * Ask system bar background painter to draw status bar background.
      * @hide
@@ -304,21 +314,32 @@
         mSystemBarBackgroundPainter.drawNavigationBarBackground(c);
     }
 
-    void remove() {
-        final long now = SystemClock.uptimeMillis();
-        if ((now - mShownTime < mDelayRemovalTime)
-                // Show the latest content as soon as possible for unlocking to home.
-                && mActivityType != ACTIVITY_TYPE_HOME) {
-            final long delayTime = mShownTime + mDelayRemovalTime - now;
-            mSplashScreenExecutor.executeDelayed(() -> remove(), delayTime);
-            if (DEBUG) {
-                Slog.d(TAG, "Defer removing snapshot surface in " + delayTime);
-            }
+    void scheduleRemove(Runnable onRemove) {
+        // Show the latest content as soon as possible for unlocking to home.
+        if (mActivityType == ACTIVITY_TYPE_HOME) {
+            removeImmediately();
+            onRemove.run();
             return;
         }
+        if (mScheduledRunnable != null) {
+            mSplashScreenExecutor.removeCallbacks(mScheduledRunnable);
+            mScheduledRunnable = null;
+        }
+        mScheduledRunnable = () -> {
+            TaskSnapshotWindow.this.removeImmediately();
+            onRemove.run();
+        };
+        mSplashScreenExecutor.executeDelayed(mScheduledRunnable, mDelayRemovalTime);
+        if (DEBUG) {
+            Slog.d(TAG, "Defer removing snapshot surface in " + mDelayRemovalTime);
+        }
+    }
+
+    void removeImmediately() {
+        mSplashScreenExecutor.removeCallbacks(mScheduledRunnable);
         try {
             if (DEBUG) {
-                Slog.d(TAG, "Removing snapshot surface, mHasDrawn: " + mHasDrawn);
+                Slog.d(TAG, "Removing taskSnapshot surface, mHasDrawn: " + mHasDrawn);
             }
             mSession.remove(mWindow);
         } catch (RemoteException e) {
@@ -356,7 +377,6 @@
         } else {
             drawSizeMatchSnapshot();
         }
-        mShownTime = SystemClock.uptimeMillis();
         mHasDrawn = true;
         reportDrawn();
 
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawerTests.java
index d536adb..eef0d9b 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawerTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawerTests.java
@@ -15,38 +15,53 @@
  */
 package com.android.wm.shell.startingsurface;
 
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
 import static android.window.StartingWindowInfo.STARTING_WINDOW_TYPE_SPLASH_SCREEN;
 
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.spy;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotEquals;
 import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
 
 import android.app.ActivityManager;
 import android.content.BroadcastReceiver;
+import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.pm.ActivityInfo;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
+import android.graphics.ColorSpace;
+import android.graphics.Point;
 import android.graphics.Rect;
+import android.hardware.HardwareBuffer;
 import android.net.Uri;
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.Looper;
 import android.os.UserHandle;
 import android.testing.TestableContext;
+import android.view.IWindowSession;
+import android.view.InsetsState;
+import android.view.Surface;
 import android.view.SurfaceControl;
 import android.view.View;
 import android.view.WindowManager;
+import android.view.WindowManagerGlobal;
 import android.view.WindowMetrics;
 import android.window.StartingWindowInfo;
+import android.window.TaskSnapshot;
 
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
@@ -61,6 +76,7 @@
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
+import org.mockito.MockitoSession;
 
 import java.util.function.IntSupplier;
 
@@ -78,6 +94,7 @@
     private TransactionPool mTransactionPool;
 
     private final Handler mTestHandler = new Handler(Looper.getMainLooper());
+    private ShellExecutor mTestExecutor;
     private final TestableContext mTestContext = new TestContext(
             InstrumentationRegistry.getInstrumentation().getTargetContext());
     TestStartingSurfaceDrawer mStartingSurfaceDrawer;
@@ -138,9 +155,9 @@
 
         doReturn(metrics).when(mMockWindowManager).getMaximumWindowMetrics();
         doNothing().when(mMockWindowManager).addView(any(), any());
-
-        mStartingSurfaceDrawer = spy(new TestStartingSurfaceDrawer(mTestContext,
-                new HandlerExecutor(mTestHandler), mTransactionPool));
+        mTestExecutor = new HandlerExecutor(mTestHandler);
+        mStartingSurfaceDrawer = spy(
+                new TestStartingSurfaceDrawer(mTestContext, mTestExecutor, mTransactionPool));
     }
 
     @Test
@@ -205,6 +222,48 @@
         assertEquals(0, windowColor3.mReuseCount);
     }
 
+    @Test
+    public void testRemoveTaskSnapshotWithImeSurfaceWhenOnImeDrawn() throws Exception {
+        final int taskId = 1;
+        final StartingWindowInfo windowInfo =
+                createWindowInfo(taskId, android.R.style.Theme);
+        TaskSnapshot snapshot = createTaskSnapshot(100, 100, new Point(100, 100),
+                new Rect(0, 0, 0, 50), true /* hasImeSurface */);
+        final IWindowSession session = WindowManagerGlobal.getWindowSession();
+        spyOn(session);
+        doReturn(WindowManagerGlobal.ADD_OKAY).when(session).addToDisplay(
+                any() /* window */, any() /* attrs */,
+                anyInt() /* viewVisibility */, anyInt() /* displayId */,
+                any() /* requestedVisibility */, any() /* outInputChannel */,
+                any() /* outInsetsState */, any() /* outActiveControls */);
+        TaskSnapshotWindow mockSnapshotWindow = TaskSnapshotWindow.create(windowInfo,
+                mBinder,
+                snapshot, mTestExecutor, () -> {
+                });
+        spyOn(mockSnapshotWindow);
+        try (AutoCloseable mockTaskSnapshotSession = new AutoCloseable() {
+            MockitoSession mockSession = mockitoSession()
+                    .initMocks(this)
+                    .mockStatic(TaskSnapshotWindow.class)
+                    .startMocking();
+            @Override
+            public void close() {
+                mockSession.finishMocking();
+            }
+        }) {
+            when(TaskSnapshotWindow.create(eq(windowInfo), eq(mBinder), eq(snapshot), any(),
+                    any())).thenReturn(mockSnapshotWindow);
+            // Simulate a task snapshot window created with IME snapshot shown.
+            mStartingSurfaceDrawer.makeTaskSnapshotWindow(windowInfo, mBinder, snapshot);
+            waitHandlerIdle(mTestHandler);
+
+            // Verify the task snapshot with IME snapshot will be removed when received the real IME
+            // drawn callback.
+            mStartingSurfaceDrawer.onImeDrawnOnTask(1);
+            verify(mockSnapshotWindow).removeImmediately();
+        }
+    }
+
     private StartingWindowInfo createWindowInfo(int taskId, int themeResId) {
         StartingWindowInfo windowInfo = new StartingWindowInfo();
         final ActivityInfo info = new ActivityInfo();
@@ -216,10 +275,27 @@
         taskInfo.taskId = taskId;
         windowInfo.targetActivityInfo = info;
         windowInfo.taskInfo = taskInfo;
+        windowInfo.topOpaqueWindowInsetsState = new InsetsState();
+        windowInfo.mainWindowLayoutParams = new WindowManager.LayoutParams();
+        windowInfo.topOpaqueWindowLayoutParams = new WindowManager.LayoutParams();
         return windowInfo;
     }
 
     private static void waitHandlerIdle(Handler handler) {
         handler.runWithScissors(() -> { }, 0 /* timeout */);
     }
+
+    private TaskSnapshot createTaskSnapshot(int width, int height, Point taskSize,
+            Rect contentInsets, boolean hasImeSurface) {
+        final HardwareBuffer buffer = HardwareBuffer.create(width, height, HardwareBuffer.RGBA_8888,
+                1, HardwareBuffer.USAGE_CPU_READ_RARELY);
+        return new TaskSnapshot(
+                System.currentTimeMillis(),
+                new ComponentName("", ""), buffer,
+                ColorSpace.get(ColorSpace.Named.SRGB), ORIENTATION_PORTRAIT,
+                Surface.ROTATION_0, taskSize, contentInsets, false,
+                true /* isRealSnapshot */, WINDOWING_MODE_FULLSCREEN,
+                0 /* systemUiVisibility */, false /* isTranslucent */,
+                hasImeSurface /* hasImeSurface */);
+    }
 }
diff --git a/libs/hwui/Properties.cpp b/libs/hwui/Properties.cpp
index b8fa55a..109b535 100644
--- a/libs/hwui/Properties.cpp
+++ b/libs/hwui/Properties.cpp
@@ -84,7 +84,7 @@
 bool Properties::useHintManager = true;
 int Properties::targetCpuTimePercentage = 70;
 
-bool Properties::enableWebViewOverlays = false;
+bool Properties::enableWebViewOverlays = true;
 
 StretchEffectBehavior Properties::stretchEffectBehavior = StretchEffectBehavior::ShaderHWUI;
 
@@ -139,7 +139,7 @@
     targetCpuTimePercentage = base::GetIntProperty(PROPERTY_TARGET_CPU_TIME_PERCENTAGE, 70);
     if (targetCpuTimePercentage <= 0 || targetCpuTimePercentage > 100) targetCpuTimePercentage = 70;
 
-    enableWebViewOverlays = base::GetBoolProperty(PROPERTY_WEBVIEW_OVERLAYS_ENABLED, false);
+    enableWebViewOverlays = base::GetBoolProperty(PROPERTY_WEBVIEW_OVERLAYS_ENABLED, true);
 
     return (prevDebugLayersUpdates != debugLayersUpdates) || (prevDebugOverdraw != debugOverdraw);
 }
diff --git a/libs/hwui/WebViewFunctorManager.cpp b/libs/hwui/WebViewFunctorManager.cpp
index df41011..5aad821 100644
--- a/libs/hwui/WebViewFunctorManager.cpp
+++ b/libs/hwui/WebViewFunctorManager.cpp
@@ -100,6 +100,9 @@
     destroyContext();
 
     ATRACE_NAME("WebViewFunctor::onDestroy");
+    if (mSurfaceControl) {
+        removeOverlays();
+    }
     mCallbacks.onDestroyed(mFunctor, mData);
 }
 
diff --git a/location/java/android/location/GpsNavigationMessage.java b/location/java/android/location/GpsNavigationMessage.java
index dc1e99f..2b978f7 100644
--- a/location/java/android/location/GpsNavigationMessage.java
+++ b/location/java/android/location/GpsNavigationMessage.java
@@ -262,12 +262,8 @@
             parcel.readByteArray(data);
             navigationMessage.setData(data);
 
-            if (parcel.dataAvail() >= Integer.SIZE) {
-                int status = parcel.readInt();
-                navigationMessage.setStatus((short) status);
-            } else {
-                navigationMessage.setStatus(STATUS_UNKNOWN);
-            }
+            int status = parcel.readInt();
+            navigationMessage.setStatus((short) status);
 
             return navigationMessage;
         }
diff --git a/media/java/android/media/PlayerBase.java b/media/java/android/media/PlayerBase.java
index 86ed50b..72ee00f 100644
--- a/media/java/android/media/PlayerBase.java
+++ b/media/java/android/media/PlayerBase.java
@@ -102,6 +102,13 @@
         mState = AudioPlaybackConfiguration.PLAYER_STATE_IDLE;
     };
 
+    /** @hide */
+    public int getPlayerIId() {
+        synchronized (mLock) {
+            return mPlayerIId;
+        }
+    }
+
     /**
      * Call from derived class when instantiation / initialization is successful
      */
diff --git a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java
index c1a0a9a..b4cafd8 100644
--- a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java
+++ b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java
@@ -93,9 +93,9 @@
             final DeviceFilterPair selectedDevice = getService().mDevicesFound.get(0);
             setTitle(Html.fromHtml(getString(
                     R.string.confirmation_title,
-                    getCallingAppName(),
-                    profileName,
-                    selectedDevice.getDisplayName()), 0));
+                    Html.escapeHtml(getCallingAppName()),
+                    Html.escapeHtml(selectedDevice.getDisplayName())), 0));
+
             mPairButton = findViewById(R.id.button_pair);
             mPairButton.setOnClickListener(v -> onDeviceConfirmed(getService().mSelectedDevice));
             getService().mSelectedDevice = selectedDevice;
@@ -108,8 +108,8 @@
             mPairButton = findViewById(R.id.button_pair);
             mPairButton.setVisibility(View.GONE);
             setTitle(Html.fromHtml(getString(R.string.chooser_title,
-                    profileName,
-                    getCallingAppName()), 0));
+                    Html.escapeHtml(profileName),
+                    Html.escapeHtml(getCallingAppName())), 0));
             mDeviceListView = findViewById(R.id.device_list);
             mDevicesAdapter = new DevicesAdapter();
             mDeviceListView.setAdapter(mDevicesAdapter);
diff --git a/packages/PrintRecommendationService/src/com/android/printservice/recommendation/plugin/mdnsFilter/MDNSFilterPlugin.java b/packages/PrintRecommendationService/src/com/android/printservice/recommendation/plugin/mdnsFilter/MDNSFilterPlugin.java
index 5f107d6..34e7e3d 100644
--- a/packages/PrintRecommendationService/src/com/android/printservice/recommendation/plugin/mdnsFilter/MDNSFilterPlugin.java
+++ b/packages/PrintRecommendationService/src/com/android/printservice/recommendation/plugin/mdnsFilter/MDNSFilterPlugin.java
@@ -84,8 +84,7 @@
      */
     public MDNSFilterPlugin(@NonNull Context context, @NonNull String name,
             @NonNull CharSequence packageName, @NonNull List<String> mDNSNames) {
-        mName = context.getResources().getIdentifier(name, null,
-                "com.android.printservice.recommendation");
+        mName = context.getResources().getIdentifier(name, null, context.getPackageName());
         mPackageName = packageName;
         mMDNSFilteredDiscovery = new MDNSFilteredDiscovery(context, PRINTER_SERVICE_TYPES,
                 new VendorNameFilter(new HashSet<>(mDNSNames)));
diff --git a/packages/SettingsLib/BannerMessagePreference/res/values-v31/styles.xml b/packages/SettingsLib/BannerMessagePreference/res/values-v31/styles.xml
index e74ac44..fede44f 100644
--- a/packages/SettingsLib/BannerMessagePreference/res/values-v31/styles.xml
+++ b/packages/SettingsLib/BannerMessagePreference/res/values-v31/styles.xml
@@ -33,18 +33,18 @@
     <style name="Banner.Title.SettingsLib"
         parent="@android:style/TextAppearance.Material.Subhead">
         <item name="android:textSize">20sp</item>
-        <item name="android:fontFamily">@*android:string/config_headlineFontFamily</item>
+        <item name="android:fontFamily">@string/settingslib_config_headlineFontFamily</item>
         <item name="android:textColor">?android:attr/textColorPrimary</item>
     </style>
 
     <style name="Banner.Subtitle.SettingsLib"
-        parent="@*android:style/TextAppearance.DeviceDefault.Body1">
+        parent="@android:style/TextAppearance.DeviceDefault">
         <item name="android:textColor">?android:attr/textColorSecondary</item>
         <item name="android:textSize">14sp</item>
     </style>
 
     <style name="Banner.Summary.SettingsLib"
-        parent="@*android:style/TextAppearance.DeviceDefault.Body1">
+        parent="@android:style/TextAppearance.DeviceDefault">
         <item name="android:textColor">?android:attr/textColorSecondary</item>
         <item name="android:textSize">14sp</item>
     </style>
@@ -58,4 +58,4 @@
         parent="android:Widget.DeviceDefault.Button.Borderless.Colored">
         <item name="android:textColor">?android:attr/colorAccent</item>
     </style>
-</resources>
\ No newline at end of file
+</resources>
diff --git a/packages/SettingsLib/BannerMessagePreference/res/values/styles.xml b/packages/SettingsLib/BannerMessagePreference/res/values/styles.xml
index df47c64..4c6ed58 100644
--- a/packages/SettingsLib/BannerMessagePreference/res/values/styles.xml
+++ b/packages/SettingsLib/BannerMessagePreference/res/values/styles.xml
@@ -17,14 +17,13 @@
 
 <resources>
     <style name="Banner.Text.Title"
-           parent="@android:style/TextAppearance.Material.Subhead">
+           parent="@android:style/TextAppearance.DeviceDefault.WindowTitle">
         <item name="android:textSize">16sp</item>
-        <item name="android:fontFamily">@*android:string/config_headlineFontFamilyMedium</item>
         <item name="android:textColor">?android:attr/textColorPrimary</item>
     </style>
 
     <style name="Banner.Text.Summary"
-           parent="@*android:style/TextAppearance.DeviceDefault.Body1">
+           parent="@android:style/TextAppearance.DeviceDefault">
         <item name="android:textColor">?android:attr/textColorSecondary</item>
         <item name="android:textSize">14sp</item>
     </style>
diff --git a/packages/SettingsLib/BarChartPreference/res/values/styles.xml b/packages/SettingsLib/BarChartPreference/res/values/styles.xml
index 92514ad..1c44207 100644
--- a/packages/SettingsLib/BarChartPreference/res/values/styles.xml
+++ b/packages/SettingsLib/BarChartPreference/res/values/styles.xml
@@ -87,9 +87,9 @@
     </style>
 
     <style name="BarChart.Text"
-           parent="@android:style/TextAppearance.Material.Subhead">
-        <item name="android:fontFamily">@*android:string/config_headlineFontFamilyMedium</item>
+           parent="@android:style/TextAppearance.DeviceDefault.WindowTitle">
         <item name="android:textColor">?android:attr/textColorPrimary</item>
+        <item name="android:textSize">16sp</item>
     </style>
 
     <style name="BarChart.Text.HeaderTitle">
@@ -101,7 +101,7 @@
     </style>
 
     <style name="BarChart.Text.Summary"
-           parent="@*android:style/TextAppearance.DeviceDefault.Body1">
+           parent="@android:style/TextAppearance.DeviceDefault">
         <item name="android:textColor">?android:attr/textColorSecondary</item>
         <item name="android:textSize">12sp</item>
     </style>
diff --git a/packages/SettingsLib/CollapsingToolbarBaseActivity/Android.bp b/packages/SettingsLib/CollapsingToolbarBaseActivity/Android.bp
index 2f911c4..238e65e 100644
--- a/packages/SettingsLib/CollapsingToolbarBaseActivity/Android.bp
+++ b/packages/SettingsLib/CollapsingToolbarBaseActivity/Android.bp
@@ -19,6 +19,7 @@
         "com.google.android.material_material",
         "SettingsLibSettingsTransition",
         "SettingsLibUtils",
+        "SettingsLibSettingsTheme",
     ],
     sdk_version: "system_current",
     min_sdk_version: "29",
diff --git a/packages/SettingsLib/CollapsingToolbarBaseActivity/res/layout-v31/collapsing_toolbar_base_layout.xml b/packages/SettingsLib/CollapsingToolbarBaseActivity/res/layout-v31/collapsing_toolbar_base_layout.xml
index 5950656..907863e 100644
--- a/packages/SettingsLib/CollapsingToolbarBaseActivity/res/layout-v31/collapsing_toolbar_base_layout.xml
+++ b/packages/SettingsLib/CollapsingToolbarBaseActivity/res/layout-v31/collapsing_toolbar_base_layout.xml
@@ -16,7 +16,6 @@
   -->
 <androidx.coordinatorlayout.widget.CoordinatorLayout
     xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
     xmlns:app="http://schemas.android.com/apk/res-auto"
     android:id="@+id/content_parent"
     android:layout_width="match_parent"
@@ -40,7 +39,7 @@
             android:clipToPadding="false"
             app:forceApplySystemWindowInsetTop="true"
             app:extraMultilineHeightEnabled="true"
-            app:contentScrim="?androidprv:attr/colorSurfaceHeader"
+            app:contentScrim="@color/settingslib_colorSurfaceHeader"
             app:maxLines="3"
             app:layout_scrollFlags="scroll|exitUntilCollapsed|snap"
             app:scrimAnimationDuration="50"
diff --git a/packages/SettingsLib/CollapsingToolbarBaseActivity/res/values-night/themes.xml b/packages/SettingsLib/CollapsingToolbarBaseActivity/res/values-night-v31/themes.xml
similarity index 83%
rename from packages/SettingsLib/CollapsingToolbarBaseActivity/res/values-night/themes.xml
rename to packages/SettingsLib/CollapsingToolbarBaseActivity/res/values-night-v31/themes.xml
index 878275a0..c20beaf 100644
--- a/packages/SettingsLib/CollapsingToolbarBaseActivity/res/values-night/themes.xml
+++ b/packages/SettingsLib/CollapsingToolbarBaseActivity/res/values-night-v31/themes.xml
@@ -18,7 +18,7 @@
     <style name="Theme.CollapsingToolbar.Settings" parent="@style/Theme.MaterialComponents.DayNight">
         <item name="elevationOverlayEnabled">true</item>
         <item name="elevationOverlayColor">?attr/colorPrimary</item>
-        <item name="colorPrimary">@*android:color/primary_dark_device_default_settings</item>
-        <item name="colorAccent">@*android:color/accent_device_default_dark</item>
+        <item name="colorPrimary">@color/settingslib_primary_dark_device_default_settings</item>
+        <item name="colorAccent">@color/settingslib_accent_device_default_dark</item>
     </style>
 </resources>
\ No newline at end of file
diff --git a/packages/SettingsLib/CollapsingToolbarBaseActivity/res/values/dimens.xml b/packages/SettingsLib/CollapsingToolbarBaseActivity/res/values-v31/dimens.xml
similarity index 100%
rename from packages/SettingsLib/CollapsingToolbarBaseActivity/res/values/dimens.xml
rename to packages/SettingsLib/CollapsingToolbarBaseActivity/res/values-v31/dimens.xml
diff --git a/packages/SettingsLib/CollapsingToolbarBaseActivity/res/values/styles.xml b/packages/SettingsLib/CollapsingToolbarBaseActivity/res/values-v31/styles.xml
similarity index 77%
rename from packages/SettingsLib/CollapsingToolbarBaseActivity/res/values/styles.xml
rename to packages/SettingsLib/CollapsingToolbarBaseActivity/res/values-v31/styles.xml
index 63d397c..d0b6c4d 100644
--- a/packages/SettingsLib/CollapsingToolbarBaseActivity/res/values/styles.xml
+++ b/packages/SettingsLib/CollapsingToolbarBaseActivity/res/values-v31/styles.xml
@@ -16,11 +16,13 @@
 -->
 <resources>
     <style name="CollapsingToolbarTitle.Collapsed" parent="@android:style/TextAppearance.DeviceDefault.Widget.ActionBar.Title">
-        <item name="android:fontFamily">@*android:string/config_headlineFontFamily</item>
+        <item name="android:fontFamily">@string/settingslib_config_headlineFontFamily</item>
         <item name="android:textSize">20dp</item>
+        <item name="android:textColor">@color/settingslib_text_color_primary_device_default</item>
     </style>
 
     <style name="CollapsingToolbarTitle.Expanded" parent="CollapsingToolbarTitle.Collapsed">
         <item name="android:textSize">36dp</item>
+        <item name="android:textColor">@color/settingslib_text_color_primary_device_default</item>
     </style>
 </resources>
\ No newline at end of file
diff --git a/packages/SettingsLib/CollapsingToolbarBaseActivity/res/values-night/themes.xml b/packages/SettingsLib/CollapsingToolbarBaseActivity/res/values-v31/themes.xml
similarity index 82%
copy from packages/SettingsLib/CollapsingToolbarBaseActivity/res/values-night/themes.xml
copy to packages/SettingsLib/CollapsingToolbarBaseActivity/res/values-v31/themes.xml
index 878275a0..9ecc297 100644
--- a/packages/SettingsLib/CollapsingToolbarBaseActivity/res/values-night/themes.xml
+++ b/packages/SettingsLib/CollapsingToolbarBaseActivity/res/values-v31/themes.xml
@@ -18,7 +18,7 @@
     <style name="Theme.CollapsingToolbar.Settings" parent="@style/Theme.MaterialComponents.DayNight">
         <item name="elevationOverlayEnabled">true</item>
         <item name="elevationOverlayColor">?attr/colorPrimary</item>
-        <item name="colorPrimary">@*android:color/primary_dark_device_default_settings</item>
-        <item name="colorAccent">@*android:color/accent_device_default_dark</item>
+        <item name="colorPrimary">@color/settingslib_primary_device_default_settings_light</item>
+        <item name="colorAccent">@color/settingslib_accent_device_default_light</item>
     </style>
 </resources>
\ No newline at end of file
diff --git a/packages/SettingsLib/CollapsingToolbarBaseActivity/res/values/themes.xml b/packages/SettingsLib/CollapsingToolbarBaseActivity/res/values/themes.xml
deleted file mode 100644
index 2e7a6a9..0000000
--- a/packages/SettingsLib/CollapsingToolbarBaseActivity/res/values/themes.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-  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.
--->
-<resources>
-    <style name="Theme.CollapsingToolbar.Settings" parent="@style/Theme.MaterialComponents.DayNight">
-        <item name="elevationOverlayEnabled">true</item>
-        <item name="elevationOverlayColor">?attr/colorPrimary</item>
-        <item name="colorPrimary">@*android:color/primary_device_default_settings_light</item>
-        <item name="colorAccent">@*android:color/accent_device_default_light</item>
-    </style>
-</resources>
\ No newline at end of file
diff --git a/packages/SettingsLib/FooterPreference/res/values/styles.xml b/packages/SettingsLib/FooterPreference/res/values/styles.xml
index 08dd359..5a3bada 100644
--- a/packages/SettingsLib/FooterPreference/res/values/styles.xml
+++ b/packages/SettingsLib/FooterPreference/res/values/styles.xml
@@ -17,9 +17,8 @@
 
 <resources>
     <style name="TextAppearance.Footer.Title.SettingsLib"
-           parent="@android:style/TextAppearance.DeviceDefault.Medium">
+           parent="@android:style/TextAppearance.DeviceDefault.WindowTitle">
         <item name="android:textSize">14sp</item>
-        <item name="android:fontFamily">@*android:string/config_headlineFontFamilyMedium</item>
         <item name="android:textColor">?android:attr/colorAccent</item>
     </style>
 </resources>
\ No newline at end of file
diff --git a/packages/SettingsLib/LayoutPreference/res/values/styles.xml b/packages/SettingsLib/LayoutPreference/res/values/styles.xml
index 4a99e84..2ffe6d9 100644
--- a/packages/SettingsLib/LayoutPreference/res/values/styles.xml
+++ b/packages/SettingsLib/LayoutPreference/res/values/styles.xml
@@ -24,14 +24,13 @@
     </style>
 
     <style name="TextAppearance.EntityHeaderTitle"
-           parent="@android:style/TextAppearance.Material.Subhead">
-        <item name="android:fontFamily">@*android:string/config_headlineFontFamilyMedium</item>
+           parent="@android:style/TextAppearance.DeviceDefault.WindowTitle">
         <item name="android:textColor">?android:attr/textColorPrimary</item>
         <item name="android:textSize">20sp</item>
     </style>
 
     <style name="TextAppearance.EntityHeaderSummary"
-           parent="@*android:style/TextAppearance.DeviceDefault.Body1">
+           parent="@android:style/TextAppearance.DeviceDefault">
         <item name="android:textAlignment">viewStart</item>
         <item name="android:textColor">?android:attr/textColorSecondary</item>
         <item name="android:singleLine">true</item>
diff --git a/packages/SettingsLib/MainSwitchPreference/res/drawable/settingslib_switch_bar_bg_disabled.xml b/packages/SettingsLib/MainSwitchPreference/res/drawable-v31/settingslib_switch_bar_bg_disabled.xml
similarity index 100%
rename from packages/SettingsLib/MainSwitchPreference/res/drawable/settingslib_switch_bar_bg_disabled.xml
rename to packages/SettingsLib/MainSwitchPreference/res/drawable-v31/settingslib_switch_bar_bg_disabled.xml
diff --git a/packages/SettingsLib/MainSwitchPreference/res/drawable/settingslib_switch_bar_bg_off.xml b/packages/SettingsLib/MainSwitchPreference/res/drawable-v31/settingslib_switch_bar_bg_off.xml
similarity index 100%
rename from packages/SettingsLib/MainSwitchPreference/res/drawable/settingslib_switch_bar_bg_off.xml
rename to packages/SettingsLib/MainSwitchPreference/res/drawable-v31/settingslib_switch_bar_bg_off.xml
diff --git a/packages/SettingsLib/MainSwitchPreference/res/drawable/settingslib_switch_bar_bg_on.xml b/packages/SettingsLib/MainSwitchPreference/res/drawable-v31/settingslib_switch_bar_bg_on.xml
similarity index 100%
rename from packages/SettingsLib/MainSwitchPreference/res/drawable/settingslib_switch_bar_bg_on.xml
rename to packages/SettingsLib/MainSwitchPreference/res/drawable-v31/settingslib_switch_bar_bg_on.xml
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 6e5911c..30748e6 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
@@ -50,7 +50,7 @@
             android:tint="?android:attr/colorAccent"
             android:layout_gravity="center_vertical"
             android:layout_marginEnd="@dimen/settingslib_restricted_icon_margin_end"
-            android:src="@*android:drawable/ic_info"
+            android:src="@android:drawable/ic_info"
             android:visibility="gone" />
 
         <Switch
diff --git a/packages/SettingsLib/MainSwitchPreference/res/layout/settingslib_main_switch_bar.xml b/packages/SettingsLib/MainSwitchPreference/res/layout/settingslib_main_switch_bar.xml
index 306145a..d0c2d0b 100644
--- a/packages/SettingsLib/MainSwitchPreference/res/layout/settingslib_main_switch_bar.xml
+++ b/packages/SettingsLib/MainSwitchPreference/res/layout/settingslib_main_switch_bar.xml
@@ -28,7 +28,7 @@
         android:layout_gravity="center_vertical"
         android:maxLines="2"
         android:ellipsize="end"
-        android:textAppearance="@*android:style/TextAppearance.DeviceDefault.Widget.ActionBar.Title"
+        android:textAppearance="@android:style/TextAppearance.DeviceDefault.Widget.ActionBar.Title"
         android:textSize="16sp"
         android:textColor="?android:attr/textColorPrimaryInverse"
         android:layout_marginStart="@dimen/settingslib_switchbar_subsettings_margin_start"
@@ -42,7 +42,7 @@
         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"
+        android:src="@android:drawable/ic_info"
         android:visibility="gone"/>
 
     <Switch
diff --git a/packages/SettingsLib/MainSwitchPreference/res/values-v31/dimens.xml b/packages/SettingsLib/MainSwitchPreference/res/values-v31/dimens.xml
new file mode 100644
index 0000000..2272a37
--- /dev/null
+++ b/packages/SettingsLib/MainSwitchPreference/res/values-v31/dimens.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  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.
+  -->
+
+<resources>
+
+    <!-- Size of layout margin -->
+    <dimen name="settingslib_switchbar_margin">16dp</dimen>
+
+    <!-- Size of layout margin left -->
+    <dimen name="settingslib_switchbar_padding_left">24dp</dimen>
+
+    <!-- Size of layout margin right -->
+    <dimen name="settingslib_switchbar_padding_right">16dp</dimen>
+
+    <!-- Minimum width of switch -->
+    <dimen name="settingslib_min_switch_width">52dp</dimen>
+
+    <!-- Minimum width of switch bar -->
+    <dimen name="settingslib_min_switch_bar_height">72dp</dimen>
+
+    <!-- Radius of switch bar -->
+    <dimen name="settingslib_switch_bar_radius">28dp</dimen>
+</resources>
diff --git a/packages/SettingsLib/MainSwitchPreference/res/values-v31/styles.xml b/packages/SettingsLib/MainSwitchPreference/res/values-v31/styles.xml
new file mode 100644
index 0000000..a50fc7c
--- /dev/null
+++ b/packages/SettingsLib/MainSwitchPreference/res/values-v31/styles.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+   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.
+  -->
+
+<resources>
+
+    <style name="MainSwitchText.Settingslib" parent="@android:style/TextAppearance.DeviceDefault.Widget.ActionBar.Title">
+        <item name="android:textSize">20sp</item>
+        <item name="android:fontFamily">@string/settingslib_config_headlineFontFamily</item>
+        <item name="android:textColor">@android:color/black</item>
+    </style>
+</resources>
diff --git a/packages/SettingsLib/MainSwitchPreference/res/values/dimens.xml b/packages/SettingsLib/MainSwitchPreference/res/values/dimens.xml
index 16b8af6..6362882 100644
--- a/packages/SettingsLib/MainSwitchPreference/res/values/dimens.xml
+++ b/packages/SettingsLib/MainSwitchPreference/res/values/dimens.xml
@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!--
-  Copyright (C) 2020 The Android Open Source Project
+  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.
@@ -17,30 +17,12 @@
 
 <resources>
 
-    <!-- Size of layout margin -->
-    <dimen name="settingslib_switchbar_margin">16dp</dimen>
-
-    <!-- Size of layout margin left -->
-    <dimen name="settingslib_switchbar_padding_left">24dp</dimen>
-
-    <!-- Size of layout margin right -->
-    <dimen name="settingslib_switchbar_padding_right">16dp</dimen>
-
-    <!-- Minimum width of switch -->
-    <dimen name="settingslib_min_switch_width">52dp</dimen>
-
-    <!-- Minimum width of switch bar -->
-    <dimen name="settingslib_min_switch_bar_height">72dp</dimen>
-
     <!-- Restricted icon size in switch bar -->
-    <dimen name="settingslib_restricted_icon_size">@*android:dimen/config_restrictedIconSize</dimen>
+    <dimen name="settingslib_restricted_icon_size">@android:dimen/config_restrictedIconSize</dimen>
 
     <!-- Restricted icon in switch bar -->
     <dimen name="settingslib_restricted_icon_margin_end">16dp</dimen>
 
-    <!-- Radius of switch bar -->
-    <dimen name="settingslib_switch_bar_radius">28dp</dimen>
-
     <!-- Size of title margin -->
     <dimen name="settingslib_switch_title_margin">16dp</dimen>
 
diff --git a/packages/SettingsLib/MainSwitchPreference/res/values/styles.xml b/packages/SettingsLib/MainSwitchPreference/res/values/styles.xml
index 3924e30..870812a 100644
--- a/packages/SettingsLib/MainSwitchPreference/res/values/styles.xml
+++ b/packages/SettingsLib/MainSwitchPreference/res/values/styles.xml
@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!--
-   Copyright (C) 2020 The Android Open Source Project
+   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.
@@ -17,13 +17,6 @@
 
 <resources>
 
-    <style name="MainSwitchText.Settingslib" parent="@android:style/TextAppearance.DeviceDefault.Widget.ActionBar.Title">
-        <item name="android:textSize">20sp</item>
-        <item name="android:fontFamily">@*android:string/config_headlineFontFamily</item>
-        <item name="android:textColor">@android:color/black</item>
-    </style>
-
-
     <style name="SwitchBar.Switch.Settingslib" parent="@android:style/Widget.Material.CompoundButton.Switch">
         <item name="android:trackTint">@color/settingslib_switchbar_switch_track_tint</item>
         <item name="android:thumbTint">@color/settingslib_switchbar_switch_thumb_tint</item>
diff --git a/packages/SettingsLib/SettingsTheme/res/color-v31/settingslib_surface_light.xml b/packages/SettingsLib/SettingsTheme/res/color-v31/settingslib_surface_light.xml
new file mode 100644
index 0000000..037b80a
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/color-v31/settingslib_surface_light.xml
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:color="@android:color/system_neutral1_500" android:lStar="98" />
+</selector>
\ No newline at end of file
diff --git a/packages/SettingsLib/SettingsTheme/res/values-night-v31/colors.xml b/packages/SettingsLib/SettingsTheme/res/values-night-v31/colors.xml
index 8c7c7ed..c206903 100644
--- a/packages/SettingsLib/SettingsTheme/res/values-night-v31/colors.xml
+++ b/packages/SettingsLib/SettingsTheme/res/values-night-v31/colors.xml
@@ -36,4 +36,11 @@
     <color name="settingslib_dialog_colorError">#f28b82</color> <!-- Red 300 -->
 
     <color name="settingslib_colorSurfaceVariant">@android:color/system_neutral1_700</color>
+
+    <color name="settingslib_colorSurfaceHeader">@android:color/system_neutral1_700</color>
+
+    <!-- copy from accent_primary_variant_dark_device_default-->
+    <color name="settingslib_accent_primary_variant">@android:color/system_accent1_300</color>
+
+    <color name="settingslib_text_color_primary_device_default">@android:color/system_neutral1_50</color>
 </resources>
\ No newline at end of file
diff --git a/packages/SettingsLib/SettingsTheme/res/values-v31/colors.xml b/packages/SettingsLib/SettingsTheme/res/values-v31/colors.xml
index 77f1bcd..0401098 100644
--- a/packages/SettingsLib/SettingsTheme/res/values-v31/colors.xml
+++ b/packages/SettingsLib/SettingsTheme/res/values-v31/colors.xml
@@ -37,9 +37,32 @@
     <!-- Dialog accent color -->
     <color name="settingslib_dialog_accent">@android:color/system_accent1_600</color>
     <!-- Dialog background color -->
-    <color name="settingslib_dialog_background">@*android:color/surface_light</color>
+    <color name="settingslib_dialog_background">@color/settingslib_surface_light</color>
     <!-- Dialog error color. -->
     <color name="settingslib_dialog_colorError">#d93025</color> <!-- Red 600 -->
 
     <color name="settingslib_colorSurfaceVariant">@android:color/system_neutral2_100</color>
+
+    <color name="settingslib_colorSurfaceHeader">@android:color/system_neutral1_100</color>
+
+    <color name="settingslib_accent_device_default_dark">@android:color/system_accent1_100</color>
+
+    <color name="settingslib_accent_device_default_light">@android:color/system_accent1_600</color>
+
+    <color name="settingslib_primary_dark_device_default_settings">@android:color/system_neutral1_900</color>
+
+    <color name="settingslib_primary_device_default_settings_light">@android:color/system_neutral1_50</color>
+
+    <color name="settingslib_accent_primary_device_default">@android:color/system_accent1_100</color>
+
+    <!-- copy from accent_primary_variant_light_device_default-->
+    <color name="settingslib_accent_primary_variant">@android:color/system_accent1_600</color>
+
+    <color name="settingslib_accent_secondary_device_default">@android:color/system_accent2_100</color>
+
+    <color name="settingslib_background_device_default_dark">@android:color/system_neutral1_900</color>
+
+    <color name="settingslib_background_device_default_light">@android:color/system_neutral1_50</color>
+
+    <color name="settingslib_text_color_primary_device_default">@android:color/system_neutral1_900</color>
 </resources>
diff --git a/packages/SettingsLib/SettingsTheme/res/values-v31/dimens.xml b/packages/SettingsLib/SettingsTheme/res/values-v31/dimens.xml
index ddcc83e..1c33f1a 100644
--- a/packages/SettingsLib/SettingsTheme/res/values-v31/dimens.xml
+++ b/packages/SettingsLib/SettingsTheme/res/values-v31/dimens.xml
@@ -19,4 +19,5 @@
     <dimen name="app_preference_padding_start">20dp</dimen>
     <dimen name="app_icon_min_width">52dp</dimen>
     <dimen name="settingslib_preferred_minimum_touch_target">48dp</dimen>
+    <dimen name="settingslib_dialogCornerRadius">28dp</dimen>
 </resources>
diff --git a/packages/SettingsLib/SettingsTheme/res/values-v31/strings.xml b/packages/SettingsLib/SettingsTheme/res/values-v31/strings.xml
new file mode 100644
index 0000000..6d072a9
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/values-v31/strings.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <!-- Name of a font family to use for headlines in SettingsLib. -->
+    <string name="settingslib_config_headlineFontFamily" translatable="false">
+        @*android:string/config_headlineFontFamily
+    </string>
+
+    <!-- Name of a font family to use for headlines-medium in SettingsLib. -->
+    <string name="settingslib_config_headlineFontFamilyMedium" translatable="false">
+        @*android:string/config_headlineFontFamilyMedium
+    </string>
+
+    <!-- Name of a font family to use for body in SettingsLib. -->
+    <string name="settingslib_config_bodyFontFamily" translatable="false">
+        @*android:string/config_bodyFontFamily
+    </string>
+
+    <!-- Name of a font family to use for body-medium in SettingsLib. -->
+    <string name="settingslib_config_bodyFontFamilyMedium" translatable="false">
+        @*android:string/config_bodyFontFamilyMedium
+    </string>
+</resources>
diff --git a/packages/SettingsLib/SettingsTheme/res/values-v31/styles.xml b/packages/SettingsLib/SettingsTheme/res/values-v31/styles.xml
index 46f1e03..5800636 100644
--- a/packages/SettingsLib/SettingsTheme/res/values-v31/styles.xml
+++ b/packages/SettingsLib/SettingsTheme/res/values-v31/styles.xml
@@ -16,12 +16,16 @@
   -->
 <resources>
     <style name="TextAppearance.PreferenceTitle.SettingsLib"
-           parent="@*android:style/TextAppearance.DeviceDefault.ListItem">
+           parent="@android:style/TextAppearance.Material.Subhead">
+        <item name="android:fontFamily">@string/settingslib_config_headlineFontFamily</item>
         <item name="android:textSize">20sp</item>
     </style>
 
     <style name="TextAppearance.CategoryTitle.SettingsLib"
-           parent="@*android:style/TextAppearance.DeviceDefault.Body2" />
+           parent="@android:style/TextAppearance.DeviceDefault.Medium">
+        <item name="android:textColor">?android:attr/textColorPrimary</item>
+        <item name="android:textSize">14sp</item>
+    </style>
 
     <style name="Switch.SettingsLib" parent="@android:style/Widget.Material.CompoundButton.Switch">
         <item name="android:switchMinWidth">52dp</item>
diff --git a/packages/SettingsLib/SettingsTheme/res/values-v31/themes.xml b/packages/SettingsLib/SettingsTheme/res/values-v31/themes.xml
index 8034710..6bf288b 100644
--- a/packages/SettingsLib/SettingsTheme/res/values-v31/themes.xml
+++ b/packages/SettingsLib/SettingsTheme/res/values-v31/themes.xml
@@ -50,6 +50,6 @@
         <item name="android:clipToPadding">true</item>
         <item name="android:clipChildren">true</item>
 
-        <item name="dialogCornerRadius">@*android:dimen/config_dialogCornerRadius</item>
+        <item name="dialogCornerRadius">@dimen/settingslib_dialogCornerRadius</item>
     </style>
 </resources>
\ No newline at end of file
diff --git a/packages/SettingsLib/SettingsTheme/res/values/dimens.xml b/packages/SettingsLib/SettingsTheme/res/values/dimens.xml
index 25f9514..18af1f9 100644
--- a/packages/SettingsLib/SettingsTheme/res/values/dimens.xml
+++ b/packages/SettingsLib/SettingsTheme/res/values/dimens.xml
@@ -20,4 +20,5 @@
     <dimen name="app_preference_padding_start">?android:attr/listPreferredItemPaddingStart</dimen>
     <dimen name="app_icon_min_width">56dp</dimen>
     <dimen name="two_target_min_width">72dp</dimen>
+    <dimen name="settingslib_dialogCornerRadius">8dp</dimen>
 </resources>
diff --git a/packages/SettingsLib/SettingsTheme/res/values/themes.xml b/packages/SettingsLib/SettingsTheme/res/values/themes.xml
index 6f25177..2d881d1 100644
--- a/packages/SettingsLib/SettingsTheme/res/values/themes.xml
+++ b/packages/SettingsLib/SettingsTheme/res/values/themes.xml
@@ -35,7 +35,7 @@
         <!-- TODO(b/189308264): fix the crash in Android R if set the attributes:
              <item name="colorAccent">@*android:color/accent_device_default_light</item>
              <item name="android:colorBackground">@color/settingslib_dialog_background</item>
-             <item name="dialogCornerRadius">@*android:dimen/config_dialogCornerRadius</item>
+             <item name="dialogCornerRadius">@dimen/settingslib_dialogCornerRadius</item>
         -->
         <item name="android:windowSoftInputMode">adjustResize</item>
         <item name="android:clipToPadding">true</item>
diff --git a/packages/SettingsLib/TopIntroPreference/res/values/styles.xml b/packages/SettingsLib/TopIntroPreference/res/values/styles.xml
index 65869b5..b6ca41f 100644
--- a/packages/SettingsLib/TopIntroPreference/res/values/styles.xml
+++ b/packages/SettingsLib/TopIntroPreference/res/values/styles.xml
@@ -16,8 +16,7 @@
   -->
 <resources>
     <style name="TextAppearance.TopIntroText"
-           parent="@*android:style/TextAppearance.DeviceDefault">
-        <item name="android:fontFamily">@*android:string/config_bodyFontFamily</item>
+           parent="@android:style/TextAppearance.DeviceDefault">
         <item name="android:textSize">14sp</item>
         <item name="android:textColor">?android:attr/textColorSecondary</item>
     </style>
diff --git a/packages/SystemUI/res/drawable/fingerprint_dialog_error_to_fp.xml b/packages/SystemUI/res/drawable/fingerprint_dialog_error_to_fp.xml
index 33263a9..0ae5dc7 100644
--- a/packages/SystemUI/res/drawable/fingerprint_dialog_error_to_fp.xml
+++ b/packages/SystemUI/res/drawable/fingerprint_dialog_error_to_fp.xml
@@ -1,201 +1,142 @@
-<?xml version="1.0" encoding="utf-8"?><!--
-  ~ Copyright (C) 2018 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
-  -->
+<?xml version="1.0" encoding="utf-8" ?>
+<!-- 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.
+-->
 <animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:aapt="http://schemas.android.com/aapt">
+                 xmlns:aapt="http://schemas.android.com/aapt">
     <aapt:attr name="android:drawable">
-        <vector
-            android:width="60dp"
-            android:height="60dp"
-            android:viewportHeight="60"
-            android:viewportWidth="60">
+        <vector android:height="60dp" android:width="60dp" android:viewportHeight="60"
+                android:viewportWidth="60">
             <group android:name="_R_G">
-                <group
-                    android:name="_R_G_L_1_G_N_4_T_0"
-                    android:translateX="30"
-                    android:translateY="30">
-                    <group
-                        android:name="_R_G_L_1_G"
-                        android:pivotX="114"
-                        android:pivotY="114"
-                        android:scaleX="0.42200000000000004"
-                        android:scaleY="0.42200000000000004"
-                        android:translateX="-114"
-                        android:translateY="-114">
-                        <path
-                            android:name="_R_G_L_1_G_D_0_P_0"
-                            android:pathData=" M79.63 67.24 C79.63,67.24 111.5,47.42 147.83,67.24 "
-                            android:strokeAlpha="1"
-                            android:strokeColor="@color/biometric_dialog_accent"
-                            android:strokeLineCap="round"
-                            android:strokeLineJoin="round"
-                            android:strokeWidth="5.5"
-                            android:trimPathEnd="0"
-                            android:trimPathOffset="0"
-                            android:trimPathStart="0" />
-                        <path
-                            android:name="_R_G_L_1_G_D_1_P_0"
-                            android:pathData=" M64.27 98.07 C64.27,98.07 80.13,73.02 113.98,73.02 C147.83,73.02 163.56,97.26 163.56,97.26 "
-                            android:strokeAlpha="1"
-                            android:strokeColor="@color/biometric_dialog_accent"
-                            android:strokeLineCap="round"
-                            android:strokeLineJoin="round"
-                            android:strokeWidth="5.5"
-                            android:trimPathEnd="0"
-                            android:trimPathOffset="0"
-                            android:trimPathStart="0" />
-                        <path
-                            android:name="_R_G_L_1_G_D_2_P_0"
-                            android:pathData=" M72.53 151.07 C72.53,151.07 62.46,122.89 76.16,105.55 C89.86,88.21 106.72,86.73 113.98,86.73 C121.08,86.73 153.51,90.62 158.7,125.87 C159.14,128.82 158.8,132.88 157.18,136.09 C154.88,140.63 150.62,143.63 145.85,143.97 C133.78,144.85 129.76,137.92 129.26,128.49 C128.88,121.19 122.49,115.35 113.15,115.35 C102.91,115.35 95.97,126.69 99.77,139.74 C103.57,152.78 111.33,163.85 130.32,169.13 "
-                            android:strokeAlpha="1"
-                            android:strokeColor="@color/biometric_dialog_accent"
-                            android:strokeLineCap="round"
-                            android:strokeLineJoin="round"
-                            android:strokeWidth="5.5"
-                            android:trimPathEnd="0"
-                            android:trimPathOffset="0"
-                            android:trimPathStart="0" />
-                        <path
-                            android:name="_R_G_L_1_G_D_3_P_0"
-                            android:pathData=" M100.6 167.84 C100.6,167.84 82.76,152.1 83.75,130.31 C84.75,108.53 102.58,100.7 113.73,100.7 C124.87,100.7 144.19,108.56 144.19,130.01 "
-                            android:strokeAlpha="1"
-                            android:strokeColor="@color/biometric_dialog_accent"
-                            android:strokeLineCap="round"
-                            android:strokeLineJoin="round"
-                            android:strokeWidth="5.5"
-                            android:trimPathEnd="0"
-                            android:trimPathOffset="0"
-                            android:trimPathStart="0" />
-                        <path
-                            android:name="_R_G_L_1_G_D_4_P_0"
-                            android:pathData=" M113.73 129.17 C113.73,129.17 113.15,161.33 149.15,156.58 "
-                            android:strokeAlpha="1"
-                            android:strokeColor="@color/biometric_dialog_accent"
-                            android:strokeLineCap="round"
-                            android:strokeLineJoin="round"
-                            android:strokeWidth="5.5"
-                            android:trimPathEnd="0"
-                            android:trimPathOffset="0"
-                            android:trimPathStart="0" />
+                <group android:name="_R_G_L_1_G" android:translateX="-0.05000000000000071">
+                    <group android:name="_R_G_L_1_G_D_0_P_0_G_0_T_0" android:translateX="30"
+                           android:translateY="38.75" android:scaleX="1" android:scaleY="1">
+                        <path android:name="_R_G_L_1_G_D_0_P_0"
+                              android:fillColor="@color/biometric_dialog_error"
+                              android:fillAlpha="1" android:fillType="nonZero"
+                              android:pathData=" M-1.2 -1.25 C-1.2,-1.25 1.2,-1.25 1.2,-1.25 C1.2,-1.25 1.2,1.25 1.2,1.25 C1.2,1.25 -1.2,1.25 -1.2,1.25 C-1.2,1.25 -1.2,-1.25 -1.2,-1.25c "/>
                     </group>
+                    <group android:name="_R_G_L_1_G_D_1_P_0_G_0_T_0" android:translateX="30"
+                           android:translateY="25" android:pivotX="0.002" android:pivotY="7.488"
+                           android:scaleX="1" android:scaleY="1">
+                        <path android:name="_R_G_L_1_G_D_1_P_0"
+                              android:fillColor="@color/biometric_dialog_error"
+                              android:fillAlpha="1" android:fillType="nonZero"
+                              android:pathData=" M-1.2 -7.5 C-1.2,-7.5 1.2,-7.5 1.2,-7.5 C1.2,-7.5 1.2,7.5 1.2,7.5 C1.2,7.5 -1.2,7.5 -1.2,7.5 C-1.2,7.5 -1.2,-7.5 -1.2,-7.5c "/>
+                    </group>
+                    <path android:name="_R_G_L_1_G_D_2_P_0"
+                          android:strokeColor="@color/biometric_dialog_error"
+                          android:strokeLineCap="round" android:strokeLineJoin="round"
+                          android:strokeWidth="2.5" android:strokeAlpha="1"
+                          android:trimPathStart="0" android:trimPathEnd="1"
+                          android:trimPathOffset="0"
+                          android:pathData=" M30 6.2 C16.9,6.2 6.3,16.8 6.3,30 C6.3,43.2 16.9,53.8 30,53.8 C43.1,53.8 53.8,43.2 53.8,30 C53.8,16.8 43.1,6.2 30,6.2c "/>
                 </group>
-                <group
-                    android:name="_R_G_L_0_G_N_4_T_0"
-                    android:translateX="30"
-                    android:translateY="30">
-                    <group
-                        android:name="_R_G_L_0_G"
-                        android:translateX="-30.05"
-                        android:translateY="-30">
-                        <group
-                            android:name="_R_G_L_0_G_D_0_P_0_G_0_T_0"
-                            android:scaleX="1"
-                            android:scaleY="1"
-                            android:translateX="30"
-                            android:translateY="38.75">
-                            <path
-                                android:name="_R_G_L_0_G_D_0_P_0"
-                                android:fillAlpha="1"
-                                android:fillColor="@color/biometric_dialog_error"
-                                android:fillType="nonZero"
-                                android:pathData=" M-1.2 -1.25 C-1.2,-1.25 1.2,-1.25 1.2,-1.25 C1.2,-1.25 1.2,1.25 1.2,1.25 C1.2,1.25 -1.2,1.25 -1.2,1.25 C-1.2,1.25 -1.2,-1.25 -1.2,-1.25c " />
-                        </group>
-                        <group
-                            android:name="_R_G_L_0_G_D_1_P_0_G_0_T_0"
-                            android:pivotX="0.002"
-                            android:pivotY="7.488"
-                            android:scaleX="1"
-                            android:scaleY="1"
-                            android:translateX="30"
-                            android:translateY="25">
-                            <path
-                                android:name="_R_G_L_0_G_D_1_P_0"
-                                android:fillAlpha="1"
-                                android:fillColor="@color/biometric_dialog_error"
-                                android:fillType="nonZero"
-                                android:pathData=" M-1.2 -7.5 C-1.2,-7.5 1.2,-7.5 1.2,-7.5 C1.2,-7.5 1.2,7.5 1.2,7.5 C1.2,7.5 -1.2,7.5 -1.2,7.5 C-1.2,7.5 -1.2,-7.5 -1.2,-7.5c " />
-                        </group>
-                        <path
-                            android:name="_R_G_L_0_G_D_2_P_0"
-                            android:pathData=" M30 6.2 C16.9,6.2 6.3,16.8 6.3,30 C6.3,43.2 16.9,53.8 30,53.8 C43.1,53.8 53.8,43.2 53.8,30 C53.8,16.8 43.1,6.2 30,6.2c "
-                            android:strokeAlpha="1"
-                            android:strokeColor="@color/biometric_dialog_error"
-                            android:strokeLineCap="round"
-                            android:strokeLineJoin="round"
-                            android:strokeWidth="2.5"
-                            android:trimPathEnd="1"
-                            android:trimPathOffset="0"
-                            android:trimPathStart="0" />
-                    </group>
+                <group android:name="_R_G_L_0_G" android:translateX="-10.325"
+                       android:translateY="-10.25">
+                    <path android:name="_R_G_L_0_G_D_0_P_0"
+                          android:strokeColor="@color/biometric_dialog_accent"
+                          android:strokeLineCap="round" android:strokeLineJoin="round"
+                          android:strokeWidth="2" android:strokeAlpha="1" android:trimPathStart="0"
+                          android:trimPathEnd="0" android:trimPathOffset="0"
+                          android:pathData=" M31.41 48.43 C30.78,46.69 30.78,44.91 30.78,44.91 C30.78,40.09 34.88,36.16 40.32,36.16 C45.77,36.16 49.87,40.09 49.87,44.91 C49.87,44.91 49.87,45.17 49.87,45.17 C49.87,46.97 48.41,48.43 46.61,48.43 C45.28,48.43 44.09,47.63 43.6,46.39 C43.6,46.39 42.51,43.66 42.51,43.66 C42.02,42.42 40.82,41.61 39.49,41.61 C37.69,41.61 36.23,43.07 36.23,44.87 C36.23,47.12 37.26,49.26 39.02,50.67 C39.02,50.67 39.64,51.16 39.64,51.16 "/>
+                    <path android:name="_R_G_L_0_G_D_1_P_0"
+                          android:strokeColor="@color/biometric_dialog_accent"
+                          android:strokeLineCap="round" android:strokeLineJoin="round"
+                          android:strokeWidth="2" android:strokeAlpha="1" android:trimPathStart="0"
+                          android:trimPathEnd="0" android:trimPathOffset="0"
+                          android:pathData=" M32.14 27.3 C34.5,26 37.31,25.25 40.33,25.25 C43.34,25.25 46.15,26 48.51,27.3 "/>
+                    <path android:name="_R_G_L_0_G_D_2_P_0"
+                          android:strokeColor="@color/biometric_dialog_accent"
+                          android:strokeLineCap="round" android:strokeLineJoin="round"
+                          android:strokeWidth="2" android:strokeAlpha="1" android:trimPathStart="0"
+                          android:trimPathEnd="0" android:trimPathOffset="0"
+                          android:pathData=" M29.42 36.16 C31.35,32.94 35.51,30.71 40.33,30.71 C45.14,30.71 49.3,32.94 51.23,36.16 "/>
+                    <path android:name="_R_G_L_0_G_D_3_P_0"
+                          android:strokeColor="@color/biometric_dialog_accent"
+                          android:strokeLineCap="round" android:strokeLineJoin="round"
+                          android:strokeWidth="2" android:strokeAlpha="1" android:trimPathStart="0"
+                          android:trimPathEnd="0" android:trimPathOffset="0"
+                          android:pathData=" M47.14 52.52 C45.33,54.21 42.94,55.25 40.33,55.25 C37.71,55.25 35.32,54.21 33.51,52.52 "/>
                 </group>
             </group>
-            <group android:name="time_group" />
+            <group android:name="time_group"/>
         </vector>
     </aapt:attr>
-    <target android:name="_R_G_L_1_G_D_0_P_0">
+    <target android:name="_R_G_L_1_G_D_0_P_0_G_0_T_0">
         <aapt:attr name="android:animation">
             <set android:ordering="together">
-                <objectAnimator
-                    android:duration="83"
-                    android:propertyName="trimPathEnd"
-                    android:startOffset="0"
-                    android:valueFrom="0"
-                    android:valueTo="0"
-                    android:valueType="floatType">
+                <objectAnimator android:propertyName="scaleX" android:duration="67"
+                                android:startOffset="0" android:valueFrom="1" android:valueTo="1.1"
+                                android:valueType="floatType">
                     <aapt:attr name="android:interpolator">
-                        <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.2,1 1.0,1.0" />
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.853,0 0.6,1 1.0,1.0"/>
                     </aapt:attr>
                 </objectAnimator>
-                <objectAnimator
-                    android:duration="250"
-                    android:propertyName="trimPathEnd"
-                    android:startOffset="83"
-                    android:valueFrom="0"
-                    android:valueTo="1"
-                    android:valueType="floatType">
+                <objectAnimator android:propertyName="scaleY" android:duration="67"
+                                android:startOffset="0" android:valueFrom="1" android:valueTo="1.1"
+                                android:valueType="floatType">
                     <aapt:attr name="android:interpolator">
-                        <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.2,1 1.0,1.0" />
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.853,0 0.6,1 1.0,1.0"/>
+                    </aapt:attr>
+                </objectAnimator>
+                <objectAnimator android:propertyName="scaleX" android:duration="100"
+                                android:startOffset="67" android:valueFrom="1.1" android:valueTo="0"
+                                android:valueType="floatType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.8,0 0.92,1.06 1.0,1.0"/>
+                    </aapt:attr>
+                </objectAnimator>
+                <objectAnimator android:propertyName="scaleY" android:duration="100"
+                                android:startOffset="67" android:valueFrom="1.1" android:valueTo="0"
+                                android:valueType="floatType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.8,0 0.92,1.06 1.0,1.0"/>
                     </aapt:attr>
                 </objectAnimator>
             </set>
         </aapt:attr>
     </target>
-    <target android:name="_R_G_L_1_G_D_1_P_0">
+    <target android:name="_R_G_L_1_G_D_1_P_0_G_0_T_0">
         <aapt:attr name="android:animation">
             <set android:ordering="together">
-                <objectAnimator
-                    android:duration="83"
-                    android:propertyName="trimPathEnd"
-                    android:startOffset="0"
-                    android:valueFrom="0"
-                    android:valueTo="0"
-                    android:valueType="floatType">
+                <objectAnimator android:propertyName="scaleX" android:duration="67"
+                                android:startOffset="0" android:valueFrom="1" android:valueTo="1"
+                                android:valueType="floatType">
                     <aapt:attr name="android:interpolator">
-                        <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.2,1 1.0,1.0" />
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.8,0 0.659,1 1.0,1.0"/>
                     </aapt:attr>
                 </objectAnimator>
-                <objectAnimator
-                    android:duration="250"
-                    android:propertyName="trimPathEnd"
-                    android:startOffset="83"
-                    android:valueFrom="0"
-                    android:valueTo="1"
-                    android:valueType="floatType">
+                <objectAnimator android:propertyName="scaleY" android:duration="67"
+                                android:startOffset="0" android:valueFrom="1" android:valueTo="1.1"
+                                android:valueType="floatType">
                     <aapt:attr name="android:interpolator">
-                        <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.2,1 1.0,1.0" />
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.8,0 0.6,1 1.0,1.0"/>
+                    </aapt:attr>
+                </objectAnimator>
+                <objectAnimator android:propertyName="scaleX" android:duration="100"
+                                android:startOffset="67" android:valueFrom="1" android:valueTo="1"
+                                android:valueType="floatType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.8,0 0.6,1 1.0,1.0"/>
+                    </aapt:attr>
+                </objectAnimator>
+                <objectAnimator android:propertyName="scaleY" android:duration="100"
+                                android:startOffset="67" android:valueFrom="1.1" android:valueTo="0"
+                                android:valueType="floatType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.8,0 0.92,1.096 1.0,1.0"/>
                     </aapt:attr>
                 </objectAnimator>
             </set>
@@ -204,182 +145,58 @@
     <target android:name="_R_G_L_1_G_D_2_P_0">
         <aapt:attr name="android:animation">
             <set android:ordering="together">
-                <objectAnimator
-                    android:duration="83"
-                    android:propertyName="trimPathEnd"
-                    android:startOffset="0"
-                    android:valueFrom="0"
-                    android:valueTo="0"
-                    android:valueType="floatType">
+                <objectAnimator android:propertyName="trimPathEnd" android:duration="67"
+                                android:startOffset="0" android:valueFrom="1" android:valueTo="1"
+                                android:valueType="floatType">
                     <aapt:attr name="android:interpolator">
-                        <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.2,1 1.0,1.0" />
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.2,1 1.0,1.0"/>
                     </aapt:attr>
                 </objectAnimator>
-                <objectAnimator
-                    android:duration="250"
-                    android:propertyName="trimPathEnd"
-                    android:startOffset="83"
-                    android:valueFrom="0"
-                    android:valueTo="1"
-                    android:valueType="floatType">
+                <objectAnimator android:propertyName="trimPathEnd" android:duration="133"
+                                android:startOffset="67" android:valueFrom="1" android:valueTo="0"
+                                android:valueType="floatType">
                     <aapt:attr name="android:interpolator">
-                        <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.2,1 1.0,1.0" />
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.2,1 1.0,1.0"/>
                     </aapt:attr>
                 </objectAnimator>
             </set>
         </aapt:attr>
     </target>
-    <target android:name="_R_G_L_1_G_D_3_P_0">
+    <target android:name="_R_G_L_0_G_D_0_P_0">
         <aapt:attr name="android:animation">
             <set android:ordering="together">
-                <objectAnimator
-                    android:duration="83"
-                    android:propertyName="trimPathEnd"
-                    android:startOffset="0"
-                    android:valueFrom="0"
-                    android:valueTo="0"
-                    android:valueType="floatType">
+                <objectAnimator android:propertyName="trimPathEnd" android:duration="83"
+                                android:startOffset="0" android:valueFrom="0" android:valueTo="0"
+                                android:valueType="floatType">
                     <aapt:attr name="android:interpolator">
-                        <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.2,1 1.0,1.0" />
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.2,1 1.0,1.0"/>
                     </aapt:attr>
                 </objectAnimator>
-                <objectAnimator
-                    android:duration="250"
-                    android:propertyName="trimPathEnd"
-                    android:startOffset="83"
-                    android:valueFrom="0"
-                    android:valueTo="1"
-                    android:valueType="floatType">
+                <objectAnimator android:propertyName="trimPathEnd" android:duration="250"
+                                android:startOffset="83" android:valueFrom="0" android:valueTo="1"
+                                android:valueType="floatType">
                     <aapt:attr name="android:interpolator">
-                        <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.2,1 1.0,1.0" />
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.2,1 1.0,1.0"/>
                     </aapt:attr>
                 </objectAnimator>
             </set>
         </aapt:attr>
     </target>
-    <target android:name="_R_G_L_1_G_D_4_P_0">
+    <target android:name="_R_G_L_0_G_D_1_P_0">
         <aapt:attr name="android:animation">
             <set android:ordering="together">
-                <objectAnimator
-                    android:duration="83"
-                    android:propertyName="trimPathEnd"
-                    android:startOffset="0"
-                    android:valueFrom="0"
-                    android:valueTo="0"
-                    android:valueType="floatType">
+                <objectAnimator android:propertyName="trimPathEnd" android:duration="83"
+                                android:startOffset="0" android:valueFrom="0" android:valueTo="0"
+                                android:valueType="floatType">
                     <aapt:attr name="android:interpolator">
-                        <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.2,1 1.0,1.0" />
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.2,1 1.0,1.0"/>
                     </aapt:attr>
                 </objectAnimator>
-                <objectAnimator
-                    android:duration="250"
-                    android:propertyName="trimPathEnd"
-                    android:startOffset="83"
-                    android:valueFrom="0"
-                    android:valueTo="1"
-                    android:valueType="floatType">
+                <objectAnimator android:propertyName="trimPathEnd" android:duration="250"
+                                android:startOffset="83" android:valueFrom="0" android:valueTo="1"
+                                android:valueType="floatType">
                     <aapt:attr name="android:interpolator">
-                        <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.2,1 1.0,1.0" />
-                    </aapt:attr>
-                </objectAnimator>
-            </set>
-        </aapt:attr>
-    </target>
-    <target android:name="_R_G_L_0_G_D_0_P_0_G_0_T_0">
-        <aapt:attr name="android:animation">
-            <set android:ordering="together">
-                <objectAnimator
-                    android:duration="67"
-                    android:propertyName="scaleX"
-                    android:startOffset="0"
-                    android:valueFrom="1"
-                    android:valueTo="1.1"
-                    android:valueType="floatType">
-                    <aapt:attr name="android:interpolator">
-                        <pathInterpolator android:pathData="M 0.0,0.0 c0.853,0 0.6,1 1.0,1.0" />
-                    </aapt:attr>
-                </objectAnimator>
-                <objectAnimator
-                    android:duration="67"
-                    android:propertyName="scaleY"
-                    android:startOffset="0"
-                    android:valueFrom="1"
-                    android:valueTo="1.1"
-                    android:valueType="floatType">
-                    <aapt:attr name="android:interpolator">
-                        <pathInterpolator android:pathData="M 0.0,0.0 c0.853,0 0.6,1 1.0,1.0" />
-                    </aapt:attr>
-                </objectAnimator>
-                <objectAnimator
-                    android:duration="100"
-                    android:propertyName="scaleX"
-                    android:startOffset="67"
-                    android:valueFrom="1.1"
-                    android:valueTo="0"
-                    android:valueType="floatType">
-                    <aapt:attr name="android:interpolator">
-                        <pathInterpolator android:pathData="M 0.0,0.0 c0.8,0 0.92,1.06 1.0,1.0" />
-                    </aapt:attr>
-                </objectAnimator>
-                <objectAnimator
-                    android:duration="100"
-                    android:propertyName="scaleY"
-                    android:startOffset="67"
-                    android:valueFrom="1.1"
-                    android:valueTo="0"
-                    android:valueType="floatType">
-                    <aapt:attr name="android:interpolator">
-                        <pathInterpolator android:pathData="M 0.0,0.0 c0.8,0 0.92,1.06 1.0,1.0" />
-                    </aapt:attr>
-                </objectAnimator>
-            </set>
-        </aapt:attr>
-    </target>
-    <target android:name="_R_G_L_0_G_D_1_P_0_G_0_T_0">
-        <aapt:attr name="android:animation">
-            <set android:ordering="together">
-                <objectAnimator
-                    android:duration="67"
-                    android:propertyName="scaleX"
-                    android:startOffset="0"
-                    android:valueFrom="1"
-                    android:valueTo="1"
-                    android:valueType="floatType">
-                    <aapt:attr name="android:interpolator">
-                        <pathInterpolator android:pathData="M 0.0,0.0 c0.8,0 0.659,1 1.0,1.0" />
-                    </aapt:attr>
-                </objectAnimator>
-                <objectAnimator
-                    android:duration="67"
-                    android:propertyName="scaleY"
-                    android:startOffset="0"
-                    android:valueFrom="1"
-                    android:valueTo="1.1"
-                    android:valueType="floatType">
-                    <aapt:attr name="android:interpolator">
-                        <pathInterpolator android:pathData="M 0.0,0.0 c0.8,0 0.6,1 1.0,1.0" />
-                    </aapt:attr>
-                </objectAnimator>
-                <objectAnimator
-                    android:duration="100"
-                    android:propertyName="scaleX"
-                    android:startOffset="67"
-                    android:valueFrom="1"
-                    android:valueTo="1"
-                    android:valueType="floatType">
-                    <aapt:attr name="android:interpolator">
-                        <pathInterpolator android:pathData="M 0.0,0.0 c0.8,0 0.6,1 1.0,1.0" />
-                    </aapt:attr>
-                </objectAnimator>
-                <objectAnimator
-                    android:duration="100"
-                    android:propertyName="scaleY"
-                    android:startOffset="67"
-                    android:valueFrom="1.1"
-                    android:valueTo="0"
-                    android:valueType="floatType">
-                    <aapt:attr name="android:interpolator">
-                        <pathInterpolator android:pathData="M 0.0,0.0 c0.8,0 0.92,1.096 1.0,1.0" />
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.2,1 1.0,1.0"/>
                     </aapt:attr>
                 </objectAnimator>
             </set>
@@ -388,26 +205,38 @@
     <target android:name="_R_G_L_0_G_D_2_P_0">
         <aapt:attr name="android:animation">
             <set android:ordering="together">
-                <objectAnimator
-                    android:duration="67"
-                    android:propertyName="trimPathEnd"
-                    android:startOffset="0"
-                    android:valueFrom="1"
-                    android:valueTo="1"
-                    android:valueType="floatType">
+                <objectAnimator android:propertyName="trimPathEnd" android:duration="83"
+                                android:startOffset="0" android:valueFrom="0" android:valueTo="0"
+                                android:valueType="floatType">
                     <aapt:attr name="android:interpolator">
-                        <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.2,1 1.0,1.0" />
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.2,1 1.0,1.0"/>
                     </aapt:attr>
                 </objectAnimator>
-                <objectAnimator
-                    android:duration="133"
-                    android:propertyName="trimPathEnd"
-                    android:startOffset="67"
-                    android:valueFrom="1"
-                    android:valueTo="0"
-                    android:valueType="floatType">
+                <objectAnimator android:propertyName="trimPathEnd" android:duration="250"
+                                android:startOffset="83" android:valueFrom="0" android:valueTo="1"
+                                android:valueType="floatType">
                     <aapt:attr name="android:interpolator">
-                        <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.2,1 1.0,1.0" />
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.2,1 1.0,1.0"/>
+                    </aapt:attr>
+                </objectAnimator>
+            </set>
+        </aapt:attr>
+    </target>
+    <target android:name="_R_G_L_0_G_D_3_P_0">
+        <aapt:attr name="android:animation">
+            <set android:ordering="together">
+                <objectAnimator android:propertyName="trimPathEnd" android:duration="83"
+                                android:startOffset="0" android:valueFrom="0" android:valueTo="0"
+                                android:valueType="floatType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.2,1 1.0,1.0"/>
+                    </aapt:attr>
+                </objectAnimator>
+                <objectAnimator android:propertyName="trimPathEnd" android:duration="250"
+                                android:startOffset="83" android:valueFrom="0" android:valueTo="1"
+                                android:valueType="floatType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.2,1 1.0,1.0"/>
                     </aapt:attr>
                 </objectAnimator>
             </set>
@@ -416,14 +245,10 @@
     <target android:name="time_group">
         <aapt:attr name="android:animation">
             <set android:ordering="together">
-                <objectAnimator
-                    android:duration="350"
-                    android:propertyName="translateX"
-                    android:startOffset="0"
-                    android:valueFrom="0"
-                    android:valueTo="1"
-                    android:valueType="floatType" />
+                <objectAnimator android:propertyName="translateX" android:duration="417"
+                                android:startOffset="0" android:valueFrom="0" android:valueTo="1"
+                                android:valueType="floatType"/>
             </set>
         </aapt:attr>
     </target>
-</animated-vector>
\ No newline at end of file
+</animated-vector>
diff --git a/packages/SystemUI/res/drawable/fingerprint_dialog_fp_to_error.xml b/packages/SystemUI/res/drawable/fingerprint_dialog_fp_to_error.xml
index b899828..fc2c7d0 100644
--- a/packages/SystemUI/res/drawable/fingerprint_dialog_fp_to_error.xml
+++ b/packages/SystemUI/res/drawable/fingerprint_dialog_fp_to_error.xml
@@ -1,179 +1,170 @@
-<?xml version="1.0" encoding="utf-8"?><!--
-  ~ Copyright (C) 2018 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
-  -->
+<?xml version="1.0" encoding="utf-8" ?>
+<!-- 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.
+-->
 <animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:aapt="http://schemas.android.com/aapt">
+                 xmlns:aapt="http://schemas.android.com/aapt">
     <aapt:attr name="android:drawable">
-        <vector
-            android:width="60dp"
-            android:height="60dp"
-            android:viewportHeight="60"
-            android:viewportWidth="60">
+        <vector android:height="60dp" android:width="60dp" android:viewportHeight="60"
+                android:viewportWidth="60">
             <group android:name="_R_G">
-                <group
-                    android:name="_R_G_L_1_G_N_4_T_0"
-                    android:translateX="30"
-                    android:translateY="30">
-                    <group
-                        android:name="_R_G_L_1_G"
-                        android:pivotX="114"
-                        android:pivotY="114"
-                        android:scaleX="0.42244"
-                        android:scaleY="0.42244"
-                        android:translateX="-114"
-                        android:translateY="-114">
-                        <path
-                            android:name="_R_G_L_1_G_D_0_P_0"
-                            android:pathData=" M79.63 67.24 C79.63,67.24 111.5,47.42 147.83,67.24 "
-                            android:strokeAlpha="1"
-                            android:strokeColor="@color/biometric_dialog_accent"
-                            android:strokeLineCap="round"
-                            android:strokeLineJoin="round"
-                            android:strokeWidth="5.5"
-                            android:trimPathEnd="1"
-                            android:trimPathOffset="0"
-                            android:trimPathStart="0" />
-                        <path
-                            android:name="_R_G_L_1_G_D_1_P_0"
-                            android:pathData=" M64.27 98.07 C64.27,98.07 80.13,73.02 113.98,73.02 C147.83,73.02 163.56,97.26 163.56,97.26 "
-                            android:strokeAlpha="1"
-                            android:strokeColor="@color/biometric_dialog_accent"
-                            android:strokeLineCap="round"
-                            android:strokeLineJoin="round"
-                            android:strokeWidth="5.5"
-                            android:trimPathEnd="1"
-                            android:trimPathOffset="0"
-                            android:trimPathStart="0" />
-                        <path
-                            android:name="_R_G_L_1_G_D_2_P_0"
-                            android:pathData=" M72.53 151.07 C72.53,151.07 62.46,122.89 76.16,105.55 C89.86,88.21 106.72,86.73 113.98,86.73 C121.08,86.73 153.51,90.62 158.7,125.87 C159.14,128.82 158.8,132.88 157.18,136.09 C154.88,140.63 150.62,143.63 145.85,143.97 C133.78,144.85 129.76,137.92 129.26,128.49 C128.88,121.19 122.49,115.35 113.15,115.35 C102.91,115.35 95.97,126.69 99.77,139.74 C103.57,152.78 111.33,163.85 130.32,169.13 "
-                            android:strokeAlpha="1"
-                            android:strokeColor="@color/biometric_dialog_accent"
-                            android:strokeLineCap="round"
-                            android:strokeLineJoin="round"
-                            android:strokeWidth="5.5"
-                            android:trimPathEnd="1"
-                            android:trimPathOffset="0"
-                            android:trimPathStart="0" />
-                        <path
-                            android:name="_R_G_L_1_G_D_3_P_0"
-                            android:pathData=" M100.6 167.84 C100.6,167.84 82.76,152.1 83.75,130.31 C84.75,108.53 102.58,100.7 113.73,100.7 C124.87,100.7 144.19,108.56 144.19,130.01 "
-                            android:strokeAlpha="1"
-                            android:strokeColor="@color/biometric_dialog_accent"
-                            android:strokeLineCap="round"
-                            android:strokeLineJoin="round"
-                            android:strokeWidth="5.5"
-                            android:trimPathEnd="1"
-                            android:trimPathOffset="0"
-                            android:trimPathStart="0" />
-                        <path
-                            android:name="_R_G_L_1_G_D_4_P_0"
-                            android:pathData=" M113.73 129.17 C113.73,129.17 113.15,161.33 149.15,156.58 "
-                            android:strokeAlpha="1"
-                            android:strokeColor="@color/biometric_dialog_accent"
-                            android:strokeLineCap="round"
-                            android:strokeLineJoin="round"
-                            android:strokeWidth="5.5"
-                            android:trimPathEnd="1"
-                            android:trimPathOffset="0"
-                            android:trimPathStart="0" />
+                <group android:name="_R_G_L_1_G" android:translateX="-0.05000000000000071">
+                    <group android:name="_R_G_L_1_G_D_0_P_0_G_0_T_0" android:translateX="30"
+                           android:translateY="38.75" android:scaleX="0" android:scaleY="0">
+                        <path android:name="_R_G_L_1_G_D_0_P_0"
+                              android:fillColor="@color/biometric_dialog_error"
+                              android:fillAlpha="1" android:fillType="nonZero"
+                              android:pathData=" M-1.2 -1.25 C-1.2,-1.25 1.2,-1.25 1.2,-1.25 C1.2,-1.25 1.2,1.25 1.2,1.25 C1.2,1.25 -1.2,1.25 -1.2,1.25 C-1.2,1.25 -1.2,-1.25 -1.2,-1.25c "/>
                     </group>
+                    <group android:name="_R_G_L_1_G_D_1_P_0_G_0_T_0" android:translateX="30"
+                           android:translateY="25" android:pivotX="0.002" android:pivotY="7.488"
+                           android:scaleX="1" android:scaleY="0">
+                        <path android:name="_R_G_L_1_G_D_1_P_0"
+                              android:fillColor="@color/biometric_dialog_error"
+                              android:fillAlpha="1" android:fillType="nonZero"
+                              android:pathData=" M-1.2 -7.5 C-1.2,-7.5 1.2,-7.5 1.2,-7.5 C1.2,-7.5 1.2,7.5 1.2,7.5 C1.2,7.5 -1.2,7.5 -1.2,7.5 C-1.2,7.5 -1.2,-7.5 -1.2,-7.5c "/>
+                    </group>
+                    <path android:name="_R_G_L_1_G_D_2_P_0"
+                          android:strokeColor="@color/biometric_dialog_error"
+                          android:strokeLineCap="round" android:strokeLineJoin="round"
+                          android:strokeWidth="2.5" android:strokeAlpha="1"
+                          android:trimPathStart="1" android:trimPathEnd="1"
+                          android:trimPathOffset="0"
+                          android:pathData=" M30 6.2 C16.9,6.2 6.3,16.8 6.3,30 C6.3,43.2 16.9,53.8 30,53.8 C43.1,53.8 53.8,43.2 53.8,30 C53.8,16.8 43.1,6.2 30,6.2c "/>
                 </group>
-                <group
-                    android:name="_R_G_L_0_G_N_4_T_0"
-                    android:translateX="30"
-                    android:translateY="30">
-                    <group
-                        android:name="_R_G_L_0_G"
-                        android:translateX="-30.05"
-                        android:translateY="-30">
-                        <group
-                            android:name="_R_G_L_0_G_D_0_P_0_G_0_T_0"
-                            android:scaleX="0"
-                            android:scaleY="0"
-                            android:translateX="30"
-                            android:translateY="38.75">
-                            <path
-                                android:name="_R_G_L_0_G_D_0_P_0"
-                                android:fillAlpha="1"
-                                android:fillColor="@color/biometric_dialog_error"
-                                android:fillType="nonZero"
-                                android:pathData=" M-1.2 -1.25 C-1.2,-1.25 1.2,-1.25 1.2,-1.25 C1.2,-1.25 1.2,1.25 1.2,1.25 C1.2,1.25 -1.2,1.25 -1.2,1.25 C-1.2,1.25 -1.2,-1.25 -1.2,-1.25c " />
-                        </group>
-                        <group
-                            android:name="_R_G_L_0_G_D_1_P_0_G_0_T_0"
-                            android:pivotX="0.002"
-                            android:pivotY="7.488"
-                            android:scaleX="1"
-                            android:scaleY="0"
-                            android:translateX="30"
-                            android:translateY="25">
-                            <path
-                                android:name="_R_G_L_0_G_D_1_P_0"
-                                android:fillAlpha="1"
-                                android:fillColor="@color/biometric_dialog_error"
-                                android:fillType="nonZero"
-                                android:pathData=" M-1.2 -7.5 C-1.2,-7.5 1.2,-7.5 1.2,-7.5 C1.2,-7.5 1.2,7.5 1.2,7.5 C1.2,7.5 -1.2,7.5 -1.2,7.5 C-1.2,7.5 -1.2,-7.5 -1.2,-7.5c " />
-                        </group>
-                        <path
-                            android:name="_R_G_L_0_G_D_2_P_0"
-                            android:pathData=" M30 6.2 C16.9,6.2 6.3,16.8 6.3,30 C6.3,43.2 16.9,53.8 30,53.8 C43.1,53.8 53.8,43.2 53.8,30 C53.8,16.8 43.1,6.2 30,6.2c "
-                            android:strokeAlpha="1"
-                            android:strokeColor="@color/biometric_dialog_error"
-                            android:strokeLineCap="round"
-                            android:strokeLineJoin="round"
-                            android:strokeWidth="2.5"
-                            android:trimPathEnd="1"
-                            android:trimPathOffset="0"
-                            android:trimPathStart="1" />
-                    </group>
+                <group android:name="_R_G_L_0_G" android:translateX="-10.325"
+                       android:translateY="-10.25">
+                    <path android:name="_R_G_L_0_G_D_0_P_0"
+                          android:strokeColor="@color/biometric_dialog_accent"
+                          android:strokeLineCap="round" android:strokeLineJoin="round"
+                          android:strokeWidth="2" android:strokeAlpha="1" android:trimPathStart="0"
+                          android:trimPathEnd="1" android:trimPathOffset="0"
+                          android:pathData=" M31.41 48.43 C30.78,46.69 30.78,44.91 30.78,44.91 C30.78,40.09 34.88,36.16 40.32,36.16 C45.77,36.16 49.87,40.09 49.87,44.91 C49.87,44.91 49.87,45.17 49.87,45.17 C49.87,46.97 48.41,48.43 46.61,48.43 C45.28,48.43 44.09,47.63 43.6,46.39 C43.6,46.39 42.51,43.66 42.51,43.66 C42.02,42.42 40.82,41.61 39.49,41.61 C37.69,41.61 36.23,43.07 36.23,44.87 C36.23,47.12 37.26,49.26 39.02,50.67 C39.02,50.67 39.64,51.16 39.64,51.16 "/>
+                    <path android:name="_R_G_L_0_G_D_1_P_0"
+                          android:strokeColor="@color/biometric_dialog_accent"
+                          android:strokeLineCap="round" android:strokeLineJoin="round"
+                          android:strokeWidth="2" android:strokeAlpha="1" android:trimPathStart="0"
+                          android:trimPathEnd="1" android:trimPathOffset="0"
+                          android:pathData=" M32.14 27.3 C34.5,26 37.31,25.25 40.33,25.25 C43.34,25.25 46.15,26 48.51,27.3 "/>
+                    <path android:name="_R_G_L_0_G_D_2_P_0"
+                          android:strokeColor="@color/biometric_dialog_accent"
+                          android:strokeLineCap="round" android:strokeLineJoin="round"
+                          android:strokeWidth="2" android:strokeAlpha="1" android:trimPathStart="0"
+                          android:trimPathEnd="1" android:trimPathOffset="0"
+                          android:pathData=" M29.42 36.16 C31.35,32.94 35.51,30.71 40.33,30.71 C45.14,30.71 49.3,32.94 51.23,36.16 "/>
+                    <path android:name="_R_G_L_0_G_D_3_P_0"
+                          android:strokeColor="@color/biometric_dialog_accent"
+                          android:strokeLineCap="round" android:strokeLineJoin="round"
+                          android:strokeWidth="2" android:strokeAlpha="1" android:trimPathStart="0"
+                          android:trimPathEnd="1" android:trimPathOffset="0"
+                          android:pathData=" M47.14 52.52 C45.33,54.21 42.94,55.25 40.33,55.25 C37.71,55.25 35.32,54.21 33.51,52.52 "/>
                 </group>
             </group>
-            <group android:name="time_group" />
+            <group android:name="time_group"/>
         </vector>
     </aapt:attr>
-    <target android:name="_R_G_L_1_G_D_0_P_0">
+    <target android:name="_R_G_L_1_G_D_0_P_0_G_0_T_0">
         <aapt:attr name="android:animation">
             <set android:ordering="together">
-                <objectAnimator
-                    android:duration="167"
-                    android:propertyName="trimPathStart"
-                    android:startOffset="0"
-                    android:valueFrom="0"
-                    android:valueTo="1"
-                    android:valueType="floatType">
+                <objectAnimator android:propertyName="scaleX" android:duration="167"
+                                android:startOffset="0" android:valueFrom="0" android:valueTo="0"
+                                android:valueType="floatType">
                     <aapt:attr name="android:interpolator">
-                        <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.6,1 1.0,1.0" />
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.08,0.06 0.2,1 1.0,1.0"/>
+                    </aapt:attr>
+                </objectAnimator>
+                <objectAnimator android:propertyName="scaleY" android:duration="167"
+                                android:startOffset="0" android:valueFrom="0" android:valueTo="0"
+                                android:valueType="floatType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.08,0.06 0.2,1 1.0,1.0"/>
+                    </aapt:attr>
+                </objectAnimator>
+                <objectAnimator android:propertyName="scaleX" android:duration="100"
+                                android:startOffset="167" android:valueFrom="0"
+                                android:valueTo="1.1" android:valueType="floatType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.08,0.06 0.2,1 1.0,1.0"/>
+                    </aapt:attr>
+                </objectAnimator>
+                <objectAnimator android:propertyName="scaleY" android:duration="100"
+                                android:startOffset="167" android:valueFrom="0"
+                                android:valueTo="1.1" android:valueType="floatType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.08,0.06 0.2,1 1.0,1.0"/>
+                    </aapt:attr>
+                </objectAnimator>
+                <objectAnimator android:propertyName="scaleX" android:duration="67"
+                                android:startOffset="267" android:valueFrom="1.1"
+                                android:valueTo="1" android:valueType="floatType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.147,1 1.0,1.0"/>
+                    </aapt:attr>
+                </objectAnimator>
+                <objectAnimator android:propertyName="scaleY" android:duration="67"
+                                android:startOffset="267" android:valueFrom="1.1"
+                                android:valueTo="1" android:valueType="floatType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.147,1 1.0,1.0"/>
                     </aapt:attr>
                 </objectAnimator>
             </set>
         </aapt:attr>
     </target>
-    <target android:name="_R_G_L_1_G_D_1_P_0">
+    <target android:name="_R_G_L_1_G_D_1_P_0_G_0_T_0">
         <aapt:attr name="android:animation">
             <set android:ordering="together">
-                <objectAnimator
-                    android:duration="167"
-                    android:propertyName="trimPathStart"
-                    android:startOffset="0"
-                    android:valueFrom="0"
-                    android:valueTo="1"
-                    android:valueType="floatType">
+                <objectAnimator android:propertyName="scaleX" android:duration="167"
+                                android:startOffset="0" android:valueFrom="1" android:valueTo="1"
+                                android:valueType="floatType">
                     <aapt:attr name="android:interpolator">
-                        <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.6,1 1.0,1.0" />
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.2,1 1.0,1.0"/>
+                    </aapt:attr>
+                </objectAnimator>
+                <objectAnimator android:propertyName="scaleY" android:duration="167"
+                                android:startOffset="0" android:valueFrom="0" android:valueTo="0"
+                                android:valueType="floatType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.08,0.096 0.2,1 1.0,1.0"/>
+                    </aapt:attr>
+                </objectAnimator>
+                <objectAnimator android:propertyName="scaleX" android:duration="100"
+                                android:startOffset="167" android:valueFrom="1" android:valueTo="1"
+                                android:valueType="floatType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.2,1 1.0,1.0"/>
+                    </aapt:attr>
+                </objectAnimator>
+                <objectAnimator android:propertyName="scaleY" android:duration="100"
+                                android:startOffset="167" android:valueFrom="0"
+                                android:valueTo="1.1" android:valueType="floatType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.08,0.096 0.2,1 1.0,1.0"/>
+                    </aapt:attr>
+                </objectAnimator>
+                <objectAnimator android:propertyName="scaleX" android:duration="67"
+                                android:startOffset="267" android:valueFrom="1" android:valueTo="1"
+                                android:valueType="floatType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.341,0 0.2,1 1.0,1.0"/>
+                    </aapt:attr>
+                </objectAnimator>
+                <objectAnimator android:propertyName="scaleY" android:duration="67"
+                                android:startOffset="267" android:valueFrom="1.1"
+                                android:valueTo="1" android:valueType="floatType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.2,1 1.0,1.0"/>
                     </aapt:attr>
                 </objectAnimator>
             </set>
@@ -182,193 +173,37 @@
     <target android:name="_R_G_L_1_G_D_2_P_0">
         <aapt:attr name="android:animation">
             <set android:ordering="together">
-                <objectAnimator
-                    android:duration="167"
-                    android:propertyName="trimPathStart"
-                    android:startOffset="0"
-                    android:valueFrom="0"
-                    android:valueTo="1"
-                    android:valueType="floatType">
+                <objectAnimator android:propertyName="trimPathStart" android:duration="267"
+                                android:startOffset="0" android:valueFrom="1" android:valueTo="0"
+                                android:valueType="floatType">
                     <aapt:attr name="android:interpolator">
-                        <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.6,1 1.0,1.0" />
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.2,1 1.0,1.0"/>
                     </aapt:attr>
                 </objectAnimator>
             </set>
         </aapt:attr>
     </target>
-    <target android:name="_R_G_L_1_G_D_3_P_0">
+    <target android:name="_R_G_L_0_G_D_0_P_0">
         <aapt:attr name="android:animation">
             <set android:ordering="together">
-                <objectAnimator
-                    android:duration="167"
-                    android:propertyName="trimPathStart"
-                    android:startOffset="0"
-                    android:valueFrom="0"
-                    android:valueTo="1"
-                    android:valueType="floatType">
+                <objectAnimator android:propertyName="trimPathStart" android:duration="167"
+                                android:startOffset="0" android:valueFrom="0" android:valueTo="1"
+                                android:valueType="floatType">
                     <aapt:attr name="android:interpolator">
-                        <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.6,1 1.0,1.0" />
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.2,1 1.0,1.0"/>
                     </aapt:attr>
                 </objectAnimator>
             </set>
         </aapt:attr>
     </target>
-    <target android:name="_R_G_L_1_G_D_4_P_0">
+    <target android:name="_R_G_L_0_G_D_1_P_0">
         <aapt:attr name="android:animation">
             <set android:ordering="together">
-                <objectAnimator
-                    android:duration="167"
-                    android:propertyName="trimPathStart"
-                    android:startOffset="0"
-                    android:valueFrom="0"
-                    android:valueTo="1"
-                    android:valueType="floatType">
+                <objectAnimator android:propertyName="trimPathStart" android:duration="167"
+                                android:startOffset="0" android:valueFrom="0" android:valueTo="1"
+                                android:valueType="floatType">
                     <aapt:attr name="android:interpolator">
-                        <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.6,1 1.0,1.0" />
-                    </aapt:attr>
-                </objectAnimator>
-            </set>
-        </aapt:attr>
-    </target>
-    <target android:name="_R_G_L_0_G_D_0_P_0_G_0_T_0">
-        <aapt:attr name="android:animation">
-            <set android:ordering="together">
-                <objectAnimator
-                    android:duration="167"
-                    android:propertyName="scaleX"
-                    android:startOffset="0"
-                    android:valueFrom="0"
-                    android:valueTo="0"
-                    android:valueType="floatType">
-                    <aapt:attr name="android:interpolator">
-                        <pathInterpolator android:pathData="M 0.0,0.0 c0.08,0.06 0.2,1 1.0,1.0" />
-                    </aapt:attr>
-                </objectAnimator>
-                <objectAnimator
-                    android:duration="167"
-                    android:propertyName="scaleY"
-                    android:startOffset="0"
-                    android:valueFrom="0"
-                    android:valueTo="0"
-                    android:valueType="floatType">
-                    <aapt:attr name="android:interpolator">
-                        <pathInterpolator android:pathData="M 0.0,0.0 c0.08,0.06 0.2,1 1.0,1.0" />
-                    </aapt:attr>
-                </objectAnimator>
-                <objectAnimator
-                    android:duration="100"
-                    android:propertyName="scaleX"
-                    android:startOffset="167"
-                    android:valueFrom="0"
-                    android:valueTo="1.1"
-                    android:valueType="floatType">
-                    <aapt:attr name="android:interpolator">
-                        <pathInterpolator android:pathData="M 0.0,0.0 c0.08,0.06 0.2,1 1.0,1.0" />
-                    </aapt:attr>
-                </objectAnimator>
-                <objectAnimator
-                    android:duration="100"
-                    android:propertyName="scaleY"
-                    android:startOffset="167"
-                    android:valueFrom="0"
-                    android:valueTo="1.1"
-                    android:valueType="floatType">
-                    <aapt:attr name="android:interpolator">
-                        <pathInterpolator android:pathData="M 0.0,0.0 c0.08,0.06 0.2,1 1.0,1.0" />
-                    </aapt:attr>
-                </objectAnimator>
-                <objectAnimator
-                    android:duration="67"
-                    android:propertyName="scaleX"
-                    android:startOffset="267"
-                    android:valueFrom="1.1"
-                    android:valueTo="1"
-                    android:valueType="floatType">
-                    <aapt:attr name="android:interpolator">
-                        <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.147,1 1.0,1.0" />
-                    </aapt:attr>
-                </objectAnimator>
-                <objectAnimator
-                    android:duration="67"
-                    android:propertyName="scaleY"
-                    android:startOffset="267"
-                    android:valueFrom="1.1"
-                    android:valueTo="1"
-                    android:valueType="floatType">
-                    <aapt:attr name="android:interpolator">
-                        <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.147,1 1.0,1.0" />
-                    </aapt:attr>
-                </objectAnimator>
-            </set>
-        </aapt:attr>
-    </target>
-    <target android:name="_R_G_L_0_G_D_1_P_0_G_0_T_0">
-        <aapt:attr name="android:animation">
-            <set android:ordering="together">
-                <objectAnimator
-                    android:duration="167"
-                    android:propertyName="scaleX"
-                    android:startOffset="0"
-                    android:valueFrom="1"
-                    android:valueTo="1"
-                    android:valueType="floatType">
-                    <aapt:attr name="android:interpolator">
-                        <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.2,1 1.0,1.0" />
-                    </aapt:attr>
-                </objectAnimator>
-                <objectAnimator
-                    android:duration="167"
-                    android:propertyName="scaleY"
-                    android:startOffset="0"
-                    android:valueFrom="0"
-                    android:valueTo="0"
-                    android:valueType="floatType">
-                    <aapt:attr name="android:interpolator">
-                        <pathInterpolator android:pathData="M 0.0,0.0 c0.08,0.096 0.2,1 1.0,1.0" />
-                    </aapt:attr>
-                </objectAnimator>
-                <objectAnimator
-                    android:duration="100"
-                    android:propertyName="scaleX"
-                    android:startOffset="167"
-                    android:valueFrom="1"
-                    android:valueTo="1"
-                    android:valueType="floatType">
-                    <aapt:attr name="android:interpolator">
-                        <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.2,1 1.0,1.0" />
-                    </aapt:attr>
-                </objectAnimator>
-                <objectAnimator
-                    android:duration="100"
-                    android:propertyName="scaleY"
-                    android:startOffset="167"
-                    android:valueFrom="0"
-                    android:valueTo="1.1"
-                    android:valueType="floatType">
-                    <aapt:attr name="android:interpolator">
-                        <pathInterpolator android:pathData="M 0.0,0.0 c0.08,0.096 0.2,1 1.0,1.0" />
-                    </aapt:attr>
-                </objectAnimator>
-                <objectAnimator
-                    android:duration="67"
-                    android:propertyName="scaleX"
-                    android:startOffset="267"
-                    android:valueFrom="1"
-                    android:valueTo="1"
-                    android:valueType="floatType">
-                    <aapt:attr name="android:interpolator">
-                        <pathInterpolator android:pathData="M 0.0,0.0 c0.341,0 0.2,1 1.0,1.0" />
-                    </aapt:attr>
-                </objectAnimator>
-                <objectAnimator
-                    android:duration="67"
-                    android:propertyName="scaleY"
-                    android:startOffset="267"
-                    android:valueFrom="1.1"
-                    android:valueTo="1"
-                    android:valueType="floatType">
-                    <aapt:attr name="android:interpolator">
-                        <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.2,1 1.0,1.0" />
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.2,1 1.0,1.0"/>
                     </aapt:attr>
                 </objectAnimator>
             </set>
@@ -377,15 +212,24 @@
     <target android:name="_R_G_L_0_G_D_2_P_0">
         <aapt:attr name="android:animation">
             <set android:ordering="together">
-                <objectAnimator
-                    android:duration="267"
-                    android:propertyName="trimPathStart"
-                    android:startOffset="0"
-                    android:valueFrom="1"
-                    android:valueTo="0"
-                    android:valueType="floatType">
+                <objectAnimator android:propertyName="trimPathStart" android:duration="167"
+                                android:startOffset="0" android:valueFrom="0" android:valueTo="1"
+                                android:valueType="floatType">
                     <aapt:attr name="android:interpolator">
-                        <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.2,1 1.0,1.0" />
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.2,1 1.0,1.0"/>
+                    </aapt:attr>
+                </objectAnimator>
+            </set>
+        </aapt:attr>
+    </target>
+    <target android:name="_R_G_L_0_G_D_3_P_0">
+        <aapt:attr name="android:animation">
+            <set android:ordering="together">
+                <objectAnimator android:propertyName="trimPathStart" android:duration="167"
+                                android:startOffset="0" android:valueFrom="0" android:valueTo="1"
+                                android:valueType="floatType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.2,1 1.0,1.0"/>
                     </aapt:attr>
                 </objectAnimator>
             </set>
@@ -394,14 +238,10 @@
     <target android:name="time_group">
         <aapt:attr name="android:animation">
             <set android:ordering="together">
-                <objectAnimator
-                    android:duration="350"
-                    android:propertyName="translateX"
-                    android:startOffset="0"
-                    android:valueFrom="0"
-                    android:valueTo="1"
-                    android:valueType="floatType" />
+                <objectAnimator android:propertyName="translateX" android:duration="350"
+                                android:startOffset="0" android:valueFrom="0" android:valueTo="1"
+                                android:valueType="floatType"/>
             </set>
         </aapt:attr>
     </target>
-</animated-vector>
\ No newline at end of file
+</animated-vector>
diff --git a/packages/SystemUI/res/layout/status_bar_expanded.xml b/packages/SystemUI/res/layout/status_bar_expanded.xml
index 6d5be30..8b78732 100644
--- a/packages/SystemUI/res/layout/status_bar_expanded.xml
+++ b/packages/SystemUI/res/layout/status_bar_expanded.xml
@@ -54,8 +54,7 @@
     <com.android.keyguard.LockIconView
         android:id="@+id/lock_icon_view"
         android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:layout_gravity="center">
+        android:layout_height="wrap_content">
         <!-- Background protection -->
         <ImageView
             android:id="@+id/lock_icon_bg"
@@ -71,6 +70,19 @@
             android:padding="@dimen/lock_icon_padding"
             android:layout_gravity="center"
             android:scaleType="centerCrop"/>
+
+        <!-- Fingerprint -->
+        <!-- AOD dashed fingerprint icon with moving dashes -->
+        <com.airbnb.lottie.LottieAnimationView
+            android:id="@+id/lock_udfps_aod_fp"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:padding="@dimen/lock_icon_padding"
+            android:layout_gravity="center"
+            android:scaleType="centerCrop"
+            systemui:lottie_autoPlay="false"
+            systemui:lottie_loop="true"
+            systemui:lottie_rawRes="@raw/udfps_aod_fp"/>
     </com.android.keyguard.LockIconView>
 
     <com.android.systemui.statusbar.phone.NotificationsQuickSettingsContainer
@@ -114,6 +126,7 @@
             android:layout_width="@dimen/notification_panel_width"
             android:layout_height="match_parent"
             android:layout_marginBottom="@dimen/close_handle_underlap"
+            android:importantForAccessibility="no"
             systemui:layout_constraintStart_toStartOf="parent"
             systemui:layout_constraintEnd_toEndOf="parent"
         />
diff --git a/packages/SystemUI/res/layout/udfps_enroll_view.xml b/packages/SystemUI/res/layout/udfps_enroll_view.xml
index f1ff6d6..e41a632 100644
--- a/packages/SystemUI/res/layout/udfps_enroll_view.xml
+++ b/packages/SystemUI/res/layout/udfps_enroll_view.xml
@@ -20,10 +20,23 @@
     android:layout_width="match_parent"
     android:layout_height="match_parent">
 
+    <!-- The layout height/width are placeholders, which will be overwritten by
+         FingerprintSensorPropertiesInternal. -->
+    <View
+        android:id="@+id/udfps_enroll_accessibility_view"
+        android:layout_gravity="center"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:contentDescription="@string/accessibility_fingerprint_label"/>
+
+    <ImageView
+        android:id="@+id/udfps_enroll_animation_fp_progress_view"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"/>
+
     <!-- Fingerprint -->
     <ImageView
         android:id="@+id/udfps_enroll_animation_fp_view"
         android:layout_width="match_parent"
-        android:layout_height="match_parent"
-        android:contentDescription="@string/accessibility_fingerprint_label"/>
+        android:layout_height="match_parent"/>
 </com.android.systemui.biometrics.UdfpsEnrollView>
diff --git a/packages/SystemUI/res/layout/udfps_view.xml b/packages/SystemUI/res/layout/udfps_view.xml
index 687830d..0fcbfa1 100644
--- a/packages/SystemUI/res/layout/udfps_view.xml
+++ b/packages/SystemUI/res/layout/udfps_view.xml
@@ -20,7 +20,7 @@
     android:id="@+id/udfps_view"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
-    systemui:sensorTouchAreaCoefficient="0.75"
+    systemui:sensorTouchAreaCoefficient="1.0"
     android:contentDescription="@string/accessibility_fingerprint_label">
 
     <ViewStub
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 6ad9ab9..78db2a8 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -1462,6 +1462,10 @@
     <!-- Maximum overshoot for the pulse expansion -->
     <dimen name="pulse_expansion_max_top_overshoot">32dp</dimen>
 
+    <!-- Alpha in duration in ms for the auth ripple to become fully vislble. If set to 0,
+         it is immediately visible. -->
+    <integer name="auth_ripple_alpha_in_duration">100</integer>
+
     <dimen name="people_space_widget_radius">28dp</dimen>
     <dimen name="people_space_image_radius">20dp</dimen>
     <dimen name="people_space_messages_count_radius">12dp</dimen>
diff --git a/packages/SystemUI/src/com/android/keyguard/LockIconView.java b/packages/SystemUI/src/com/android/keyguard/LockIconView.java
index 622419a8..5c34beb 100644
--- a/packages/SystemUI/src/com/android/keyguard/LockIconView.java
+++ b/packages/SystemUI/src/com/android/keyguard/LockIconView.java
@@ -22,7 +22,6 @@
 import android.graphics.RectF;
 import android.graphics.drawable.Drawable;
 import android.util.AttributeSet;
-import android.view.Gravity;
 import android.view.View;
 import android.widget.FrameLayout;
 import android.widget.ImageView;
@@ -45,7 +44,7 @@
     private int mRadius;
 
     private ImageView mLockIcon;
-    private ImageView mUnlockBgView;
+    private ImageView mBgView;
 
     private int mLockIconColor;
 
@@ -58,19 +57,19 @@
     public void onFinishInflate() {
         super.onFinishInflate();
         mLockIcon = findViewById(R.id.lock_icon);
-        mUnlockBgView = findViewById(R.id.lock_icon_bg);
+        mBgView = findViewById(R.id.lock_icon_bg);
     }
 
     void updateColorAndBackgroundVisibility(boolean useBackground) {
-        if (useBackground) {
+        if (useBackground && mLockIcon.getDrawable() != null) {
             mLockIconColor = Utils.getColorAttrDefaultColor(getContext(),
                     android.R.attr.textColorPrimary);
-            mUnlockBgView.setBackground(getContext().getDrawable(R.drawable.fingerprint_bg));
-            mUnlockBgView.setVisibility(View.VISIBLE);
+            mBgView.setBackground(getContext().getDrawable(R.drawable.fingerprint_bg));
+            mBgView.setVisibility(View.VISIBLE);
         } else {
             mLockIconColor = Utils.getColorAttrDefaultColor(getContext(),
                     R.attr.wallpaperTextColorAccent);
-            mUnlockBgView.setVisibility(View.GONE);
+            mBgView.setVisibility(View.GONE);
         }
 
         mLockIcon.setImageTintList(ColorStateList.valueOf(mLockIconColor));
@@ -78,9 +77,14 @@
 
     void setImageDrawable(Drawable drawable) {
         mLockIcon.setImageDrawable(drawable);
+        if (drawable == null) {
+            mBgView.setVisibility(View.INVISIBLE);
+        } else {
+            mBgView.setVisibility(View.VISIBLE);
+        }
     }
 
-    void setCenterLocation(@NonNull PointF center, int radius) {
+    public void setCenterLocation(@NonNull PointF center, int radius) {
         mLockIconCenter = center;
         mRadius = radius;
 
@@ -91,13 +95,11 @@
                 mLockIconCenter.x + mRadius,
                 mLockIconCenter.y + mRadius);
 
-        setX(mSensorRect.left);
-        setY(mSensorRect.top);
-
-        final FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams(
-                (int) (mSensorRect.right - mSensorRect.left),
-                (int) (mSensorRect.bottom - mSensorRect.top));
-        lp.gravity = Gravity.CENTER;
+        final FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) getLayoutParams();
+        lp.width = (int) (mSensorRect.right - mSensorRect.left);
+        lp.height = (int) (mSensorRect.bottom - mSensorRect.top);
+        lp.topMargin = (int) mSensorRect.top;
+        lp.setMarginStart((int) mSensorRect.left);
         setLayoutParams(lp);
     }
 
@@ -114,5 +116,6 @@
     public void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter pw, @NonNull String[] args) {
         pw.println("Center in px (x, y)= (" + mLockIconCenter.x + ", " + mLockIconCenter.y + ")");
         pw.println("Radius in pixels: " + mRadius);
+        pw.println("topLeft= (" + getX() + ", " + getY() + ")");
     }
 }
diff --git a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java
index 509ac8a..a41997c 100644
--- a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java
@@ -19,6 +19,8 @@
 import static android.hardware.biometrics.BiometricSourceType.FINGERPRINT;
 
 import static com.android.systemui.classifier.Classifier.LOCK_ICON;
+import static com.android.systemui.doze.util.BurnInHelperKt.getBurnInOffset;
+import static com.android.systemui.doze.util.BurnInHelperKt.getBurnInProgressOffset;
 
 import android.content.Context;
 import android.content.res.Configuration;
@@ -32,6 +34,7 @@
 import android.os.Process;
 import android.os.Vibrator;
 import android.util.DisplayMetrics;
+import android.util.MathUtils;
 import android.view.GestureDetector;
 import android.view.GestureDetector.SimpleOnGestureListener;
 import android.view.MotionEvent;
@@ -58,6 +61,8 @@
 import com.android.systemui.util.ViewController;
 import com.android.systemui.util.concurrency.DelayableExecutor;
 
+import com.airbnb.lottie.LottieAnimationView;
+
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
 import java.util.Objects;
@@ -92,6 +97,8 @@
     @NonNull private final DelayableExecutor mExecutor;
     private boolean mUdfpsEnrolled;
 
+    @NonNull private LottieAnimationView mAodFp;
+
     @NonNull private final AnimatedVectorDrawable mFpToUnlockIcon;
     @NonNull private final AnimatedVectorDrawable mLockToUnlockIcon;
     @NonNull private final Drawable mLockIcon;
@@ -109,6 +116,7 @@
     private boolean mIsKeyguardShowing;
     private boolean mUserUnlockedWithBiometric;
     private Runnable mCancelDelayedUpdateVisibilityRunnable;
+    private Runnable mOnGestureDetectedRunnable;
 
     private boolean mUdfpsSupported;
     private float mHeightPixels;
@@ -118,6 +126,12 @@
     private boolean mShowUnlockIcon;
     private boolean mShowLockIcon;
 
+    // for udfps when strong auth is required or unlocked on AOD
+    private boolean mShowAODFpIcon;
+    private final int mMaxBurnInOffsetX;
+    private final int mMaxBurnInOffsetY;
+    private float mInterpolatedDarkAmount;
+
     private boolean mDownDetected;
     private boolean mDetectedLongPress;
     private final Rect mSensorTouchLocation = new Rect();
@@ -150,6 +164,12 @@
         mVibrator = vibrator;
 
         final Context context = view.getContext();
+        mAodFp = mView.findViewById(R.id.lock_udfps_aod_fp);
+        mMaxBurnInOffsetX = context.getResources()
+                .getDimensionPixelSize(R.dimen.udfps_burn_in_offset_x);
+        mMaxBurnInOffsetY = context.getResources()
+                .getDimensionPixelSize(R.dimen.udfps_burn_in_offset_y);
+
         mUnlockIcon = mView.getContext().getResources().getDrawable(
             R.drawable.ic_unlock,
             mView.getContext().getTheme());
@@ -173,15 +193,14 @@
 
     @Override
     protected void onViewAttached() {
-        // we check this here instead of onInit since the FingerprintManager + FaceManager may not
-        // have started up yet onInit
-        mUdfpsSupported = mAuthController.getUdfpsSensorLocation() != null;
-
+        updateIsUdfpsEnrolled();
         updateConfiguration();
         updateKeyguardShowing();
         mUserUnlockedWithBiometric = false;
+
         mIsBouncerShowing = mKeyguardViewController.isBouncerShowing();
         mIsDozing = mStatusBarStateController.isDozing();
+        mInterpolatedDarkAmount = mStatusBarStateController.getDozeAmount();
         mRunningFPS = mKeyguardUpdateMonitor.isFingerprintDetectionRunning();
         mCanDismissLockScreen = mKeyguardStateController.canDismissLockScreen();
         mStatusBarState = mStatusBarStateController.getState();
@@ -189,15 +208,18 @@
         updateColors();
         mConfigurationController.addCallback(mConfigurationListener);
 
+        mAuthController.addCallback(mAuthControllerCallback);
         mKeyguardUpdateMonitor.registerCallback(mKeyguardUpdateMonitorCallback);
         mStatusBarStateController.addCallback(mStatusBarStateListener);
         mKeyguardStateController.addCallback(mKeyguardStateCallback);
         mDownDetected = false;
+        updateBurnInOffsets();
         updateVisibility();
     }
 
     @Override
     protected void onViewDetached() {
+        mAuthController.removeCallback(mAuthControllerCallback);
         mConfigurationController.removeCallback(mConfigurationListener);
         mKeyguardUpdateMonitor.removeCallback(mKeyguardUpdateMonitorCallback);
         mStatusBarStateController.removeCallback(mStatusBarStateListener);
@@ -227,7 +249,7 @@
             mCancelDelayedUpdateVisibilityRunnable = null;
         }
 
-        if (!mIsKeyguardShowing) {
+        if (!mIsKeyguardShowing && !mIsDozing) {
             mView.setVisibility(View.INVISIBLE);
             return;
         }
@@ -238,6 +260,7 @@
         mShowLockIcon = !mCanDismissLockScreen && !mUserUnlockedWithBiometric && isLockScreen()
             && (!mUdfpsEnrolled || !mRunningFPS);
         mShowUnlockIcon = mCanDismissLockScreen && isLockScreen();
+        mShowAODFpIcon = mIsDozing && mUdfpsEnrolled && !mRunningFPS;
 
         final CharSequence prevContentDescription = mView.getContentDescription();
         if (mShowLockIcon) {
@@ -260,10 +283,22 @@
             }
             mView.setVisibility(View.VISIBLE);
             mView.setContentDescription(mUnlockedLabel);
+        } else if (mShowAODFpIcon) {
+            mView.setImageDrawable(null);
+            mView.setContentDescription(null);
+            mAodFp.setVisibility(View.VISIBLE);
+            mAodFp.setContentDescription(mCanDismissLockScreen ? mUnlockedLabel : mLockedLabel);
+            mView.setVisibility(View.VISIBLE);
         } else {
             mView.setVisibility(View.INVISIBLE);
             mView.setContentDescription(null);
         }
+
+        if (!mShowAODFpIcon) {
+            mAodFp.setVisibility(View.INVISIBLE);
+            mAodFp.setContentDescription(null);
+        }
+
         if (!Objects.equals(prevContentDescription, mView.getContentDescription())
                 && mView.getContentDescription() != null) {
             mView.announceForAccessibility(mView.getContentDescription());
@@ -340,10 +375,12 @@
 
     @Override
     public void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter pw, @NonNull String[] args) {
+        pw.println("mUdfpsSupported: " + mUdfpsSupported);
         pw.println("mUdfpsEnrolled: " + mUdfpsEnrolled);
         pw.println("mIsKeyguardShowing: " + mIsKeyguardShowing);
         pw.println(" mShowUnlockIcon: " + mShowUnlockIcon);
         pw.println(" mShowLockIcon: " + mShowLockIcon);
+        pw.println(" mShowAODFpIcon: " + mShowAODFpIcon);
         pw.println("  mIsDozing: " + mIsDozing);
         pw.println("  mIsBouncerShowing: " + mIsBouncerShowing);
         pw.println("  mUserUnlockedWithBiometric: " + mUserUnlockedWithBiometric);
@@ -351,17 +388,57 @@
         pw.println("  mCanDismissLockScreen: " + mCanDismissLockScreen);
         pw.println("  mStatusBarState: " + StatusBarState.toShortString(mStatusBarState));
         pw.println("  mQsExpanded: " + mQsExpanded);
+        pw.println("  mInterpolatedDarkAmount: " + mInterpolatedDarkAmount);
 
         if (mView != null) {
             mView.dump(fd, pw, args);
         }
     }
 
+    /** Every minute, update the aod icon's burn in offset */
+    public void dozeTimeTick() {
+        updateBurnInOffsets();
+    }
+
+    private void updateBurnInOffsets() {
+        float offsetX = MathUtils.lerp(0f,
+                getBurnInOffset(mMaxBurnInOffsetX * 2, true /* xAxis */)
+                        - mMaxBurnInOffsetX, mInterpolatedDarkAmount);
+        float offsetY = MathUtils.lerp(0f,
+                getBurnInOffset(mMaxBurnInOffsetY * 2, false /* xAxis */)
+                        - mMaxBurnInOffsetY, mInterpolatedDarkAmount);
+        float progress = MathUtils.lerp(0f, getBurnInProgressOffset(), mInterpolatedDarkAmount);
+
+        mAodFp.setTranslationX(offsetX);
+        mAodFp.setTranslationY(offsetY);
+        mAodFp.setProgress(progress);
+        mAodFp.setAlpha(255 * mInterpolatedDarkAmount);
+    }
+
+    private void updateIsUdfpsEnrolled() {
+        boolean wasUdfpsSupported = mUdfpsSupported;
+        boolean wasUdfpsEnrolled = mUdfpsEnrolled;
+
+        mUdfpsSupported = mAuthController.getUdfpsSensorLocation() != null;
+        mUdfpsEnrolled = mKeyguardUpdateMonitor.isUdfpsEnrolled();
+        if (wasUdfpsSupported != mUdfpsSupported || wasUdfpsEnrolled != mUdfpsEnrolled) {
+            updateVisibility();
+        }
+    }
+
     private StatusBarStateController.StateListener mStatusBarStateListener =
             new StatusBarStateController.StateListener() {
                 @Override
+                public void onDozeAmountChanged(float linear, float eased) {
+                    mInterpolatedDarkAmount = eased;
+                    updateBurnInOffsets();
+                }
+
+                @Override
                 public void onDozingChanged(boolean isDozing) {
                     mIsDozing = isDozing;
+                    updateBurnInOffsets();
+                    updateIsUdfpsEnrolled();
                     updateVisibility();
                 }
 
@@ -435,7 +512,7 @@
                     mKeyguardUpdateMonitor.getUserUnlockedWithBiometric(
                         KeyguardUpdateMonitor.getCurrentUser());
             }
-            mUdfpsEnrolled = mKeyguardUpdateMonitor.isUdfpsEnrolled();
+            updateIsUdfpsEnrolled();
             updateVisibility();
         }
 
@@ -481,8 +558,7 @@
 
                     // intercept all following touches until we see MotionEvent.ACTION_CANCEL UP or
                     // MotionEvent.ACTION_UP (see #onTouchEvent)
-                    mDownDetected = true;
-                    if (mVibrator != null) {
+                    if (mVibrator != null && !mDownDetected) {
                         mVibrator.vibrate(
                                 Process.myUid(),
                                 getContext().getOpPackageName(),
@@ -490,6 +566,8 @@
                                 "lockIcon-onDown",
                                 VIBRATION_SONIFICATION_ATTRIBUTES);
                     }
+
+                    mDownDetected = true;
                     return true;
                 }
 
@@ -497,8 +575,10 @@
                     if (!wasClickableOnDownEvent()) {
                         return;
                     }
+                    mDetectedLongPress = true;
 
-                    if (mVibrator != null) {
+                    if (onAffordanceClick() && mVibrator != null) {
+                        // only vibrate if the click went through and wasn't intercepted by falsing
                         mVibrator.vibrate(
                                 Process.myUid(),
                                 getContext().getOpPackageName(),
@@ -506,8 +586,6 @@
                                 "lockIcon-onLongPress",
                                 VIBRATION_SONIFICATION_ATTRIBUTES);
                     }
-                    mDetectedLongPress = true;
-                    onAffordanceClick();
                 }
 
                 public boolean onSingleTapUp(MotionEvent e) {
@@ -531,15 +609,24 @@
                     return mDownDetected;
                 }
 
-                private void onAffordanceClick() {
+                /**
+                 * Whether we tried to launch the affordance.
+                 *
+                 * If falsing intercepts the click, returns false.
+                 */
+                private boolean onAffordanceClick() {
                     if (mFalsingManager.isFalseTouch(LOCK_ICON)) {
-                        return;
+                        return false;
                     }
 
                     // pre-emptively set to true to hide view
                     mIsBouncerShowing = true;
                     updateVisibility();
+                    if (mOnGestureDetectedRunnable != null) {
+                        mOnGestureDetectedRunnable.run();
+                    }
                     mKeyguardViewController.showBouncer(/* scrim */ true);
+                    return true;
                 }
             });
 
@@ -548,16 +635,18 @@
      * in a 'clickable' state
      * @return whether to intercept the touch event
      */
-    public boolean onTouchEvent(MotionEvent event) {
+    public boolean onTouchEvent(MotionEvent event, Runnable onGestureDetectedRunnable) {
         if (mSensorTouchLocation.contains((int) event.getX(), (int) event.getY())
-                && mView.getVisibility() == View.VISIBLE) {
+                && (mView.getVisibility() == View.VISIBLE
+                || mAodFp.getVisibility() == View.VISIBLE)) {
+            mOnGestureDetectedRunnable = onGestureDetectedRunnable;
             mGestureDetector.onTouchEvent(event);
         }
 
         // we continue to intercept all following touches until we see MotionEvent.ACTION_CANCEL UP
         // or MotionEvent.ACTION_UP. this is to avoid passing the touch to NPV
         // after the lock icon disappears on device entry
-        if (mDownDetected && mDetectedLongPress) {
+        if (mDownDetected) {
             if (event.getAction() == MotionEvent.ACTION_CANCEL
                     || event.getAction() == MotionEvent.ACTION_UP) {
                 mDownDetected = false;
@@ -577,4 +666,12 @@
     public void setAlpha(float alpha) {
         mView.setAlpha(alpha);
     }
+
+    private final AuthController.Callback mAuthControllerCallback = new AuthController.Callback() {
+        @Override
+        public void onAllAuthenticatorsRegistered() {
+            updateIsUdfpsEnrolled();
+            updateConfiguration();
+        }
+    };
 }
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
index 0ce1846..a4123c7 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
@@ -786,7 +786,7 @@
                 .build(sensorIds, credentialAllowed, mFpProps, mFaceProps);
     }
 
-    interface Callback {
+    public interface Callback {
         /**
          * Called when authenticators are registered. If authenticators are already
          * registered before this call, this callback will never be triggered.
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt
index 1df8ad5..2630f119 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt
@@ -16,14 +16,19 @@
 
 package com.android.systemui.biometrics
 
+import android.animation.ValueAnimator
 import android.content.Context
 import android.content.res.Configuration
 import android.graphics.PointF
 import android.hardware.biometrics.BiometricSourceType
+import android.util.Log
 import androidx.annotation.VisibleForTesting
 import com.android.keyguard.KeyguardUpdateMonitor
 import com.android.keyguard.KeyguardUpdateMonitorCallback
 import com.android.settingslib.Utils
+import com.android.systemui.R
+import com.android.systemui.animation.Interpolators
+import com.android.systemui.keyguard.WakefulnessLifecycle
 import com.android.systemui.statusbar.CircleReveal
 import com.android.systemui.statusbar.LightRevealEffect
 import com.android.systemui.statusbar.NotificationShadeWindowController
@@ -34,9 +39,14 @@
 import com.android.systemui.statusbar.phone.StatusBar
 import com.android.systemui.statusbar.phone.dagger.StatusBarComponent.StatusBarScope
 import com.android.systemui.statusbar.policy.ConfigurationController
+import com.android.systemui.statusbar.policy.KeyguardStateController
 import com.android.systemui.util.ViewController
 import java.io.PrintWriter
 import javax.inject.Inject
+import javax.inject.Provider
+import com.android.systemui.plugins.statusbar.StatusBarStateController
+
+private const val WAKE_AND_UNLOCK_FADE_DURATION = 180L
 
 /***
  * Controls the ripple effect that shows when authentication is successful.
@@ -49,37 +59,65 @@
     private val authController: AuthController,
     private val configurationController: ConfigurationController,
     private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
+    private val keyguardStateController: KeyguardStateController,
+    private val wakefulnessLifecycle: WakefulnessLifecycle,
     private val commandRegistry: CommandRegistry,
     private val notificationShadeWindowController: NotificationShadeWindowController,
     private val bypassController: KeyguardBypassController,
     private val biometricUnlockController: BiometricUnlockController,
+    private val udfpsControllerProvider: Provider<UdfpsController>,
+    private val statusBarStateController: StatusBarStateController,
     rippleView: AuthRippleView?
-) : ViewController<AuthRippleView>(rippleView) {
+) : ViewController<AuthRippleView>(rippleView), KeyguardStateController.Callback,
+    WakefulnessLifecycle.Observer {
+
+    @VisibleForTesting
+    internal var startLightRevealScrimOnKeyguardFadingAway = false
     var fingerprintSensorLocation: PointF? = null
     private var faceSensorLocation: PointF? = null
     private var circleReveal: LightRevealEffect? = null
 
+    private var udfpsController: UdfpsController? = null
+
+    private var dwellScale = 2f
+    private var expandedDwellScale = 2.5f
+    private var aodDwellScale = 1.9f
+    private var aodExpandedDwellScale = 2.3f
+    private var udfpsRadius: Float = -1f
+
+    override fun onInit() {
+        mView.setAlphaInDuration(sysuiContext.resources.getInteger(
+                R.integer.auth_ripple_alpha_in_duration).toLong())
+    }
+
     @VisibleForTesting
     public override fun onViewAttached() {
+        authController.addCallback(authControllerCallback)
         updateRippleColor()
         updateSensorLocation()
-        authController.addCallback(authControllerCallback)
+        updateUdfpsDependentParams()
+        udfpsController?.addCallback(udfpsControllerCallback)
         configurationController.addCallback(configurationChangedListener)
         keyguardUpdateMonitor.registerCallback(keyguardUpdateMonitorCallback)
+        keyguardStateController.addCallback(this)
+        wakefulnessLifecycle.addObserver(this)
         commandRegistry.registerCommand("auth-ripple") { AuthRippleCommand() }
     }
 
     @VisibleForTesting
     public override fun onViewDetached() {
+        udfpsController?.removeCallback(udfpsControllerCallback)
         authController.removeCallback(authControllerCallback)
         keyguardUpdateMonitor.removeCallback(keyguardUpdateMonitorCallback)
         configurationController.removeCallback(configurationChangedListener)
+        keyguardStateController.removeCallback(this)
+        wakefulnessLifecycle.removeObserver(this)
         commandRegistry.unregisterCommand("auth-ripple")
 
         notificationShadeWindowController.setForcePluginOpen(false, this)
     }
 
-    private fun showRipple(biometricSourceType: BiometricSourceType?) {
+    fun showRipple(biometricSourceType: BiometricSourceType?) {
         if (!keyguardUpdateMonitor.isKeyguardVisible ||
             keyguardUpdateMonitor.userNeedsStrongAuth()) {
             return
@@ -88,43 +126,61 @@
         if (biometricSourceType == BiometricSourceType.FINGERPRINT &&
             fingerprintSensorLocation != null) {
             mView.setSensorLocation(fingerprintSensorLocation!!)
-            showRipple()
+            showUnlockedRipple()
         } else if (biometricSourceType == BiometricSourceType.FACE &&
             faceSensorLocation != null) {
             if (!bypassController.canBypass()) {
                 return
             }
             mView.setSensorLocation(faceSensorLocation!!)
-            showRipple()
+            showUnlockedRipple()
         }
     }
 
-    private fun showRipple() {
+    private fun showUnlockedRipple() {
         notificationShadeWindowController.setForcePluginOpen(true, this)
-        val biometricUnlockMode = biometricUnlockController.mode
-        val useCircleReveal = circleReveal != null &&
-            (biometricUnlockMode == BiometricUnlockController.MODE_WAKE_AND_UNLOCK ||
-                biometricUnlockMode == BiometricUnlockController.MODE_WAKE_AND_UNLOCK_PULSING ||
-                biometricUnlockMode == BiometricUnlockController.MODE_WAKE_AND_UNLOCK_FROM_DREAM)
+        val useCircleReveal = circleReveal != null && biometricUnlockController.isWakeAndUnlock
         val lightRevealScrim = statusBar.lightRevealScrim
         if (useCircleReveal) {
             lightRevealScrim?.revealEffect = circleReveal!!
+            startLightRevealScrimOnKeyguardFadingAway = true
         }
 
-        mView.startRipple(
+        mView.startUnlockedRipple(
             /* end runnable */
             Runnable {
                 notificationShadeWindowController.setForcePluginOpen(false, this)
-            },
-            /* circleReveal */
-            if (useCircleReveal) {
-                lightRevealScrim
-            } else {
-                null
             }
         )
     }
 
+    override fun onKeyguardFadingAwayChanged() {
+        if (keyguardStateController.isKeyguardFadingAway) {
+            val lightRevealScrim = statusBar.lightRevealScrim
+            if (startLightRevealScrimOnKeyguardFadingAway && lightRevealScrim != null) {
+                val revealAnimator = ValueAnimator.ofFloat(.1f, 1f).apply {
+                    interpolator = Interpolators.LINEAR_OUT_SLOW_IN
+                    duration = RIPPLE_ANIMATION_DURATION
+                    startDelay = keyguardStateController.keyguardFadingAwayDelay
+                    addUpdateListener { animator ->
+                        if (lightRevealScrim.revealEffect != circleReveal) {
+                            // if the something else took over the reveal, let's do nothing.
+                            return@addUpdateListener
+                        }
+                        lightRevealScrim.revealAmount = animator.animatedValue as Float
+                    }
+                }
+                revealAnimator.start()
+                startLightRevealScrimOnKeyguardFadingAway = false
+            }
+        }
+    }
+
+    override fun onStartedGoingToSleep() {
+        // reset the light reveal start in case we were pending an unlock
+        startLightRevealScrimOnKeyguardFadingAway = false
+    }
+
     fun updateSensorLocation() {
         fingerprintSensorLocation = authController.fingerprintSensorLocation
         faceSensorLocation = authController.faceAuthSensorLocation
@@ -146,7 +202,23 @@
             Utils.getColorAttr(sysuiContext, android.R.attr.colorAccent).defaultColor)
     }
 
-    val keyguardUpdateMonitorCallback =
+    private fun showDwellRipple() {
+        if (statusBarStateController.isDozing) {
+            mView.startDwellRipple(
+                    /* startRadius */ udfpsRadius,
+                    /* endRadius */ udfpsRadius * aodDwellScale,
+                    /* expandedRadius */ udfpsRadius * aodExpandedDwellScale,
+                    /* isDozing */ true)
+        } else {
+            mView.startDwellRipple(
+                    /* startRadius */ udfpsRadius,
+                    /* endRadius */ udfpsRadius * dwellScale,
+                    /* expandedRadius */ udfpsRadius * expandedDwellScale,
+                    /* isDozing */ false)
+        }
+    }
+
+    private val keyguardUpdateMonitorCallback =
         object : KeyguardUpdateMonitorCallback() {
             override fun onBiometricAuthenticated(
                 userId: Int,
@@ -155,9 +227,13 @@
             ) {
                 showRipple(biometricSourceType)
             }
+
+        override fun onBiometricAuthFailed(biometricSourceType: BiometricSourceType?) {
+            mView.retractRipple()
+        }
     }
 
-    val configurationChangedListener =
+    private val configurationChangedListener =
         object : ConfigurationController.ConfigurationListener {
             override fun onConfigChanged(newConfig: Configuration?) {
                 updateSensorLocation()
@@ -173,14 +249,70 @@
             }
     }
 
-    private val authControllerCallback = AuthController.Callback { updateSensorLocation() }
+    private val udfpsControllerCallback =
+        object : UdfpsController.Callback {
+            override fun onFingerDown() {
+                if (fingerprintSensorLocation == null) {
+                    Log.e("AuthRipple", "fingerprintSensorLocation=null onFingerDown. " +
+                            "Skip showing dwell ripple")
+                    return
+                }
+
+                mView.setSensorLocation(fingerprintSensorLocation!!)
+                showDwellRipple()
+            }
+
+            override fun onFingerUp() {
+                mView.retractRipple()
+            }
+        }
+
+    private val authControllerCallback = AuthController.Callback {
+        updateSensorLocation()
+        updateUdfpsDependentParams()
+    }
+
+    private fun updateUdfpsDependentParams() {
+        authController.udfpsProps?.let {
+            if (it.size > 0) {
+                udfpsRadius = it[0].sensorRadius.toFloat()
+                udfpsController = udfpsControllerProvider.get()
+
+                if (mView.isAttachedToWindow) {
+                    udfpsController?.addCallback(udfpsControllerCallback)
+                }
+            }
+        }
+    }
 
     inner class AuthRippleCommand : Command {
+        fun printLockScreenDwellInfo(pw: PrintWriter) {
+            pw.println("lock screen dwell ripple: " +
+                    "\n\tsensorLocation=$fingerprintSensorLocation" +
+                    "\n\tdwellScale=$dwellScale" +
+                    "\n\tdwellExpand=$expandedDwellScale")
+        }
+
+        fun printAodDwellInfo(pw: PrintWriter) {
+            pw.println("aod dwell ripple: " +
+                    "\n\tsensorLocation=$fingerprintSensorLocation" +
+                    "\n\tdwellScale=$aodDwellScale" +
+                    "\n\tdwellExpand=$aodExpandedDwellScale")
+        }
+
         override fun execute(pw: PrintWriter, args: List<String>) {
             if (args.isEmpty()) {
                 invalidCommand(pw)
             } else {
                 when (args[0]) {
+                    "dwell" -> {
+                        showDwellRipple()
+                        if (statusBarStateController.isDozing) {
+                            printAodDwellInfo(pw)
+                        } else {
+                            printLockScreenDwellInfo(pw)
+                        }
+                    }
                     "fingerprint" -> {
                         pw.println("fingerprint ripple sensorLocation=$fingerprintSensorLocation")
                         showRipple(BiometricSourceType.FINGERPRINT)
@@ -199,7 +331,7 @@
                         pw.println("custom ripple sensorLocation=" + args[1].toFloat() + ", " +
                             args[2].toFloat())
                         mView.setSensorLocation(PointF(args[1].toFloat(), args[2].toFloat()))
-                        showRipple()
+                        showUnlockedRipple()
                     }
                     else -> invalidCommand(pw)
                 }
@@ -209,6 +341,7 @@
         override fun help(pw: PrintWriter) {
             pw.println("Usage: adb shell cmd statusbar auth-ripple <command>")
             pw.println("Available commands:")
+            pw.println("  dwell")
             pw.println("  fingerprint")
             pw.println("  face")
             pw.println("  custom <x-location: int> <y-location: int>")
@@ -219,4 +352,8 @@
             help(pw)
         }
     }
+
+    companion object {
+        const val RIPPLE_ANIMATION_DURATION: Long = 1533
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleView.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleView.kt
index 95ea8100..c6d26ff 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleView.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleView.kt
@@ -27,21 +27,40 @@
 import android.view.View
 import android.view.animation.PathInterpolator
 import com.android.internal.graphics.ColorUtils
-import com.android.systemui.statusbar.LightRevealScrim
+import com.android.systemui.animation.Interpolators
 import com.android.systemui.statusbar.charging.RippleShader
 
-private const val RIPPLE_ANIMATION_DURATION: Long = 1533
 private const val RIPPLE_SPARKLE_STRENGTH: Float = 0.4f
 
 /**
- * Expanding ripple effect on the transition from biometric authentication success to showing
+ * Expanding ripple effect
+ * - startUnlockedRipple for the transition from biometric authentication success to showing
  * launcher.
+ * - startDwellRipple for the ripple expansion out when the user has their finger down on the UDFPS
+ * sensor area
+ * - retractRipple for the ripple animation inwards to signal a failure
  */
 class AuthRippleView(context: Context?, attrs: AttributeSet?) : View(context, attrs) {
-    private var rippleInProgress: Boolean = false
+    private val retractInterpolator = PathInterpolator(.05f, .93f, .1f, 1f)
+
+    private val dwellPulseDuration = 50L
+    private val dwellAlphaDuration = dwellPulseDuration
+    private val dwellAlpha: Float = 1f
+    private val dwellExpandDuration = 1200L - dwellPulseDuration
+
+    private val aodDwellPulseDuration = 50L
+    private var aodDwellAlphaDuration = aodDwellPulseDuration
+    private var aodDwellAlpha: Float = .8f
+    private var aodDwellExpandDuration = 1200L - aodDwellPulseDuration
+
+    private val retractDuration = 400L
+    private var alphaInDuration: Long = 0
+    private var unlockedRippleInProgress: Boolean = false
     private val rippleShader = RippleShader()
     private val ripplePaint = Paint()
-    private var radius: Float = 0.0f
+    private var retractAnimator: Animator? = null
+    private var dwellPulseOutAnimator: Animator? = null
+    private var radius: Float = 0f
         set(value) {
             rippleShader.radius = value
             field = value
@@ -62,51 +81,200 @@
 
     fun setSensorLocation(location: PointF) {
         origin = location
-        radius = maxOf(location.x, location.y, width - location.x, height - location.y)
-            .toFloat()
+        radius = maxOf(location.x, location.y, width - location.x, height - location.y).toFloat()
     }
 
-    fun startRipple(onAnimationEnd: Runnable?, lightReveal: LightRevealScrim?) {
-        if (rippleInProgress) {
-            return // Ignore if ripple effect is already playing
+    fun setAlphaInDuration(duration: Long) {
+        alphaInDuration = duration
+    }
+
+    /**
+     * Animate ripple inwards back to radius 0
+     */
+    fun retractRipple() {
+        if (retractAnimator?.isRunning == true) {
+            return // let the animation finish
         }
 
-        val rippleAnimator = ValueAnimator.ofFloat(0f, 1f).apply {
-            interpolator = PathInterpolator(0.4f, 0f, 0f, 1f)
-            duration = RIPPLE_ANIMATION_DURATION
+        if (dwellPulseOutAnimator?.isRunning == true) {
+            val retractRippleAnimator = ValueAnimator.ofFloat(rippleShader.progress, 0f)
+                    .apply {
+                interpolator = retractInterpolator
+                duration = retractDuration
+                addUpdateListener { animator ->
+                    val now = animator.currentPlayTime
+                    rippleShader.progress = animator.animatedValue as Float
+                    rippleShader.time = now.toFloat()
+
+                    invalidate()
+                }
+            }
+
+            val retractAlphaAnimator = ValueAnimator.ofInt(255, 0).apply {
+                interpolator = Interpolators.LINEAR
+                duration = retractDuration
+                addUpdateListener { animator ->
+                    rippleShader.color = ColorUtils.setAlphaComponent(
+                            rippleShader.color,
+                            animator.animatedValue as Int
+                    )
+                    invalidate()
+                }
+            }
+
+            retractAnimator = AnimatorSet().apply {
+                playTogether(retractRippleAnimator, retractAlphaAnimator)
+                addListener(object : AnimatorListenerAdapter() {
+                    override fun onAnimationStart(animation: Animator?) {
+                        dwellPulseOutAnimator?.cancel()
+                        rippleShader.shouldFadeOutRipple = false
+                        visibility = VISIBLE
+                    }
+
+                    override fun onAnimationEnd(animation: Animator?) {
+                        visibility = GONE
+                        resetRippleAlpha()
+                    }
+                })
+                start()
+            }
+        }
+    }
+
+    /**
+     * Ripple that moves animates from an outer ripple ring of
+     *      startRadius => endRadius => expandedRadius
+     */
+    fun startDwellRipple(
+        startRadius: Float,
+        endRadius: Float,
+        expandedRadius: Float,
+        isDozing: Boolean
+    ) {
+        if (unlockedRippleInProgress || dwellPulseOutAnimator?.isRunning == true) {
+            return
+        }
+
+        // we divide by 4 because the desired startRadius and endRadius is for the ripple's outer
+        // ring see RippleShader
+        val startDwellProgress = startRadius / radius / 4f
+        val endInitialDwellProgress = endRadius / radius / 4f
+        val endExpandDwellProgress = expandedRadius / radius / 4f
+
+        val alpha = if (isDozing) aodDwellAlpha else dwellAlpha
+        val pulseOutEndAlpha = (255 * alpha).toInt()
+        val expandDwellEndAlpha = kotlin.math.min((255 * (alpha + .25f)).toInt(), 255)
+        val dwellPulseOutRippleAnimator = ValueAnimator.ofFloat(startDwellProgress,
+                endInitialDwellProgress).apply {
+            interpolator = Interpolators.LINEAR_OUT_SLOW_IN
+            duration = if (isDozing) aodDwellPulseDuration else dwellPulseDuration
             addUpdateListener { animator ->
                 val now = animator.currentPlayTime
                 rippleShader.progress = animator.animatedValue as Float
                 rippleShader.time = now.toFloat()
 
-                lightReveal?.revealAmount = animator.animatedValue as Float
                 invalidate()
             }
         }
 
-        val revealAnimator = ValueAnimator.ofFloat(0f, 1f).apply {
-            interpolator = rippleAnimator.interpolator
-            startDelay = 10
-            duration = rippleAnimator.duration
-            addUpdateListener { animator ->
-                lightReveal?.revealAmount = animator.animatedValue as Float
-            }
-        }
-
-        val alphaInAnimator = ValueAnimator.ofInt(0, 127).apply {
-            duration = 167
+        val dwellPulseOutAlphaAnimator = ValueAnimator.ofInt(0, pulseOutEndAlpha).apply {
+            interpolator = Interpolators.LINEAR
+            duration = if (isDozing) aodDwellAlphaDuration else dwellAlphaDuration
             addUpdateListener { animator ->
                 rippleShader.color = ColorUtils.setAlphaComponent(
-                    rippleShader.color,
-                    animator.animatedValue as Int
+                        rippleShader.color,
+                        animator.animatedValue as Int
                 )
                 invalidate()
             }
         }
 
-        val alphaOutAnimator = ValueAnimator.ofInt(127, 0).apply {
-            startDelay = 417
-            duration = 1116
+        // slowly animate outwards until we receive a call to retractRipple or startUnlockedRipple
+        val expandDwellRippleAnimator = ValueAnimator.ofFloat(endInitialDwellProgress,
+                endExpandDwellProgress).apply {
+            interpolator = Interpolators.LINEAR_OUT_SLOW_IN
+            duration = if (isDozing) aodDwellExpandDuration else dwellExpandDuration
+            addUpdateListener { animator ->
+                val now = animator.currentPlayTime
+                rippleShader.progress = animator.animatedValue as Float
+                rippleShader.time = now.toFloat()
+
+                invalidate()
+            }
+        }
+
+        val expandDwellAlphaAnimator = ValueAnimator.ofInt(pulseOutEndAlpha, expandDwellEndAlpha)
+                .apply {
+            interpolator = Interpolators.LINEAR
+            duration = if (isDozing) aodDwellExpandDuration else dwellExpandDuration
+            addUpdateListener { animator ->
+                rippleShader.color = ColorUtils.setAlphaComponent(
+                        rippleShader.color,
+                        animator.animatedValue as Int
+                )
+                invalidate()
+            }
+        }
+
+        val initialDwellPulseOutAnimator = AnimatorSet().apply {
+            playTogether(dwellPulseOutRippleAnimator, dwellPulseOutAlphaAnimator)
+        }
+        val expandDwellAnimator = AnimatorSet().apply {
+            playTogether(expandDwellRippleAnimator, expandDwellAlphaAnimator)
+        }
+
+        dwellPulseOutAnimator = AnimatorSet().apply {
+            playSequentially(
+                    initialDwellPulseOutAnimator,
+                    expandDwellAnimator
+            )
+            addListener(object : AnimatorListenerAdapter() {
+                override fun onAnimationStart(animation: Animator?) {
+                    retractAnimator?.cancel()
+                    rippleShader.shouldFadeOutRipple = false
+                    visibility = VISIBLE
+                }
+
+                override fun onAnimationEnd(animation: Animator?) {
+                    visibility = GONE
+                    resetRippleAlpha()
+                }
+            })
+            start()
+        }
+    }
+
+    /**
+     * Ripple that bursts outwards from the position of the sensor to the edges of the screen
+     */
+    fun startUnlockedRipple(onAnimationEnd: Runnable?) {
+        if (unlockedRippleInProgress) {
+            return // Ignore if ripple effect is already playing
+        }
+
+        var rippleStart = 0f
+        var alphaDuration = alphaInDuration
+        if (dwellPulseOutAnimator?.isRunning == true || retractAnimator?.isRunning == true) {
+            rippleStart = rippleShader.progress
+            alphaDuration = 0
+            dwellPulseOutAnimator?.cancel()
+            retractAnimator?.cancel()
+        }
+
+        val rippleAnimator = ValueAnimator.ofFloat(rippleStart, 1f).apply {
+            interpolator = Interpolators.LINEAR_OUT_SLOW_IN
+            duration = AuthRippleController.RIPPLE_ANIMATION_DURATION
+            addUpdateListener { animator ->
+                val now = animator.currentPlayTime
+                rippleShader.progress = animator.animatedValue as Float
+                rippleShader.time = now.toFloat()
+
+                invalidate()
+            }
+        }
+
+        val alphaInAnimator = ValueAnimator.ofInt(0, 255).apply {
+            duration = alphaDuration
             addUpdateListener { animator ->
                 rippleShader.color = ColorUtils.setAlphaComponent(
                     rippleShader.color,
@@ -119,19 +287,18 @@
         val animatorSet = AnimatorSet().apply {
             playTogether(
                 rippleAnimator,
-                revealAnimator,
-                alphaInAnimator,
-                alphaOutAnimator
+                alphaInAnimator
             )
             addListener(object : AnimatorListenerAdapter() {
                 override fun onAnimationStart(animation: Animator?) {
-                    rippleInProgress = true
+                    unlockedRippleInProgress = true
+                    rippleShader.shouldFadeOutRipple = true
                     visibility = VISIBLE
                 }
 
                 override fun onAnimationEnd(animation: Animator?) {
                     onAnimationEnd?.run()
-                    rippleInProgress = false
+                    unlockedRippleInProgress = false
                     visibility = GONE
                 }
             })
@@ -139,8 +306,16 @@
         animatorSet.start()
     }
 
+    fun resetRippleAlpha() {
+        rippleShader.color = ColorUtils.setAlphaComponent(
+                rippleShader.color,
+                255
+        )
+    }
+
     fun setColor(color: Int) {
         rippleShader.color = color
+        resetRippleAlpha()
     }
 
     override fun onDraw(canvas: Canvas?) {
@@ -148,7 +323,7 @@
         // the active effect area. Values here should be kept in sync with the
         // animation implementation in the ripple shader.
         val maskRadius = (1 - (1 - rippleShader.progress) * (1 - rippleShader.progress) *
-            (1 - rippleShader.progress)) * radius * 1.5f
+            (1 - rippleShader.progress)) * radius * 2f
         canvas?.drawCircle(origin.x, origin.y, maskRadius, ripplePaint)
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
index e612fb4..54f9321 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
@@ -77,7 +77,9 @@
 import com.android.systemui.util.concurrency.DelayableExecutor;
 import com.android.systemui.util.concurrency.Execution;
 
+import java.util.HashSet;
 import java.util.Optional;
+import java.util.Set;
 
 import javax.inject.Inject;
 
@@ -157,6 +159,7 @@
     private Runnable mAodInterruptRunnable;
     private boolean mOnFingerDown;
     private boolean mAttemptedToDismissKeyguard;
+    private Set<Callback> mCallbacks = new HashSet<>();
 
     @VisibleForTesting
     public static final AudioAttributes VIBRATION_SONIFICATION_ATTRIBUTES =
@@ -210,12 +213,14 @@
         }
 
         void onAcquiredGood() {
+            Log.d(TAG, "onAcquiredGood");
             if (mEnrollHelper != null) {
                 mEnrollHelper.animateIfLastStep();
             }
         }
 
         void onEnrollmentHelp() {
+            Log.d(TAG, "onEnrollmentHelp");
             if (mEnrollHelper != null) {
                 mEnrollHelper.onEnrollmentHelp();
             }
@@ -759,6 +764,7 @@
                 UdfpsEnrollView enrollView = (UdfpsEnrollView) mInflater.inflate(
                         R.layout.udfps_enroll_view, null);
                 mView.addView(enrollView);
+                enrollView.updateSensorLocation(mSensorProps);
                 return new UdfpsEnrollViewController(
                         enrollView,
                         mServerRequest.mEnrollHelper,
@@ -781,6 +787,7 @@
                         mKeyguardViewMediator,
                         mLockscreenShadeTransitionController,
                         mConfigurationController,
+                        mKeyguardStateController,
                         this
                 );
             case IUdfpsOverlayController.REASON_AUTH_BP:
@@ -841,6 +848,10 @@
             return;
         }
 
+        if (!mKeyguardUpdateMonitor.isFingerprintDetectionRunning()) {
+            return;
+        }
+
         mAodInterruptRunnable = () -> {
             mIsAodInterruptActive = true;
             // Since the sensor that triggers the AOD interrupt doesn't provide
@@ -860,6 +871,20 @@
     }
 
     /**
+     * Add a callback for fingerUp and fingerDown events
+     */
+    public void addCallback(Callback cb) {
+        mCallbacks.add(cb);
+    }
+
+    /**
+     * Remove callback
+     */
+    public void removeCallback(Callback cb) {
+        mCallbacks.remove(cb);
+    }
+
+    /**
      * Cancel updfs scan affordances - ability to hide the HbmSurfaceView (white circle) before
      * user explicitly lifts their finger. Generally, this should be called whenever udfps fails
      * or errors.
@@ -913,6 +938,10 @@
             mFingerprintManager.onUiReady(mSensorProps.sensorId);
             Trace.endAsyncSection("UdfpsController.e2e.startIllumination", 0);
         });
+
+        for (Callback cb : mCallbacks) {
+            cb.onFingerDown();
+        }
     }
 
     private void onFingerUp() {
@@ -925,6 +954,9 @@
         }
         if (mOnFingerDown) {
             mFingerprintManager.onPointerUp(mSensorProps.sensorId);
+            for (Callback cb : mCallbacks) {
+                cb.onFingerUp();
+            }
         }
         mOnFingerDown = false;
         if (mView.isIlluminationRequested()) {
@@ -945,4 +977,19 @@
             mView.setOnTouchListener(mOnTouchListener);
         }
     }
+
+    /**
+     * Callback for fingerUp and fingerDown events.
+     */
+    public interface Callback {
+        /**
+         * Called onFingerUp events. Will only be called if the finger was previously down.
+         */
+        void onFingerUp();
+
+        /**
+         * Called onFingerDown events.
+         */
+        void onFingerDown();
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollDrawable.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollDrawable.java
index ea69b1d..2034ff3 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollDrawable.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollDrawable.java
@@ -16,9 +16,11 @@
 
 package com.android.systemui.biometrics;
 
+import android.animation.Animator;
 import android.animation.AnimatorSet;
 import android.animation.ValueAnimator;
 import android.content.Context;
+import android.content.res.TypedArray;
 import android.graphics.Canvas;
 import android.graphics.Color;
 import android.graphics.Paint;
@@ -26,11 +28,17 @@
 import android.graphics.Rect;
 import android.graphics.RectF;
 import android.graphics.drawable.Drawable;
+import android.os.Handler;
+import android.os.Looper;
+import android.util.TypedValue;
 import android.view.animation.AccelerateDecelerateInterpolator;
+import android.view.animation.LinearInterpolator;
 
+import androidx.annotation.ColorInt;
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 
+import com.android.internal.graphics.ColorUtils;
 import com.android.systemui.R;
 
 /**
@@ -39,11 +47,20 @@
 public class UdfpsEnrollDrawable extends UdfpsDrawable {
     private static final String TAG = "UdfpsAnimationEnroll";
 
-    private static final long ANIM_DURATION = 800;
+    private static final long HINT_COLOR_ANIM_DELAY_MS = 233L;
+    private static final long HINT_COLOR_ANIM_DURATION_MS = 517L;
+    private static final long HINT_WIDTH_ANIM_DURATION_MS = 233L;
+    private static final long TARGET_ANIM_DURATION_LONG = 800L;
+    private static final long TARGET_ANIM_DURATION_SHORT = 600L;
     // 1 + SCALE_MAX is the maximum that the moving target will animate to
     private static final float SCALE_MAX = 0.25f;
 
-    @NonNull private final UdfpsEnrollProgressBarDrawable mProgressDrawable;
+    private static final float HINT_PADDING_DP = 10f;
+    private static final float HINT_MAX_WIDTH_DP = 6f;
+    private static final float HINT_ANGLE = 40f;
+
+    private final Handler mHandler = new Handler(Looper.getMainLooper());
+
     @NonNull private final Drawable mMovingTargetFpIcon;
     @NonNull private final Paint mSensorOutlinePaint;
     @NonNull private final Paint mBlueFill;
@@ -52,18 +69,41 @@
     @Nullable private UdfpsEnrollHelper mEnrollHelper;
 
     // Moving target animator set
-    @Nullable AnimatorSet mAnimatorSet;
+    @Nullable AnimatorSet mTargetAnimatorSet;
     // Moving target location
     float mCurrentX;
     float mCurrentY;
     // Moving target size
     float mCurrentScale = 1.f;
 
+    @ColorInt private final int mHintColorFaded;
+    @ColorInt private final int mHintColorHighlight;
+    private final float mHintMaxWidthPx;
+    private final float mHintPaddingPx;
+
+    @NonNull private final Animator.AnimatorListener mTargetAnimListener;
+
+    private boolean mShouldShowTipHint = false;
+    @NonNull private final Paint mTipHintPaint;
+    @Nullable private AnimatorSet mTipHintAnimatorSet;
+    @Nullable private ValueAnimator mTipHintColorAnimator;
+    @Nullable private ValueAnimator mTipHintWidthAnimator;
+    @NonNull private final ValueAnimator.AnimatorUpdateListener mTipHintColorUpdateListener;
+    @NonNull private final ValueAnimator.AnimatorUpdateListener mTipHintWidthUpdateListener;
+    @NonNull private final Animator.AnimatorListener mTipHintPulseListener;
+
+    private boolean mShouldShowEdgeHint = false;
+    @NonNull private final Paint mEdgeHintPaint;
+    @Nullable private AnimatorSet mEdgeHintAnimatorSet;
+    @Nullable private ValueAnimator mEdgeHintColorAnimator;
+    @Nullable private ValueAnimator mEdgeHintWidthAnimator;
+    @NonNull private final ValueAnimator.AnimatorUpdateListener mEdgeHintColorUpdateListener;
+    @NonNull private final ValueAnimator.AnimatorUpdateListener mEdgeHintWidthUpdateListener;
+    @NonNull private final Animator.AnimatorListener mEdgeHintPulseListener;
+
     UdfpsEnrollDrawable(@NonNull Context context) {
         super(context);
 
-        mProgressDrawable = new UdfpsEnrollProgressBarDrawable(context, this);
-
         mSensorOutlinePaint = new Paint(0 /* flags */);
         mSensorOutlinePaint.setAntiAlias(true);
         mSensorOutlinePaint.setColor(mContext.getColor(R.color.udfps_enroll_icon));
@@ -80,6 +120,117 @@
         mMovingTargetFpIcon.mutate();
 
         mFingerprintDrawable.setTint(mContext.getColor(R.color.udfps_enroll_icon));
+
+        mHintColorFaded = getHintColorFaded(context);
+        mHintColorHighlight = context.getColor(R.color.udfps_enroll_progress);
+        mHintMaxWidthPx = Utils.dpToPixels(context, HINT_MAX_WIDTH_DP);
+        mHintPaddingPx = Utils.dpToPixels(context, HINT_PADDING_DP);
+
+        mTargetAnimListener = new Animator.AnimatorListener() {
+            @Override
+            public void onAnimationStart(Animator animation) {}
+
+            @Override
+            public void onAnimationEnd(Animator animation) {
+                updateTipHintVisibility();
+            }
+
+            @Override
+            public void onAnimationCancel(Animator animation) {}
+
+            @Override
+            public void onAnimationRepeat(Animator animation) {}
+        };
+
+        mTipHintPaint = new Paint(0 /* flags */);
+        mTipHintPaint.setAntiAlias(true);
+        mTipHintPaint.setColor(mHintColorFaded);
+        mTipHintPaint.setStyle(Paint.Style.STROKE);
+        mTipHintPaint.setStrokeCap(Paint.Cap.ROUND);
+        mTipHintPaint.setStrokeWidth(0f);
+        mTipHintColorUpdateListener = animation -> {
+            mTipHintPaint.setColor((int) animation.getAnimatedValue());
+            invalidateSelf();
+        };
+        mTipHintWidthUpdateListener = animation -> {
+            mTipHintPaint.setStrokeWidth((float) animation.getAnimatedValue());
+            invalidateSelf();
+        };
+        mTipHintPulseListener = new Animator.AnimatorListener() {
+            @Override
+            public void onAnimationStart(Animator animation) {}
+
+            @Override
+            public void onAnimationEnd(Animator animation) {
+                mHandler.postDelayed(() -> {
+                    mTipHintColorAnimator =
+                            ValueAnimator.ofArgb(mTipHintPaint.getColor(), mHintColorFaded);
+                    mTipHintColorAnimator.setInterpolator(new LinearInterpolator());
+                    mTipHintColorAnimator.setDuration(HINT_COLOR_ANIM_DURATION_MS);
+                    mTipHintColorAnimator.addUpdateListener(mTipHintColorUpdateListener);
+                    mTipHintColorAnimator.start();
+                }, HINT_COLOR_ANIM_DELAY_MS);
+            }
+
+            @Override
+            public void onAnimationCancel(Animator animation) {}
+
+            @Override
+            public void onAnimationRepeat(Animator animation) {}
+        };
+
+        mEdgeHintPaint = new Paint(0 /* flags */);
+        mEdgeHintPaint.setAntiAlias(true);
+        mEdgeHintPaint.setColor(mHintColorFaded);
+        mEdgeHintPaint.setStyle(Paint.Style.STROKE);
+        mEdgeHintPaint.setStrokeCap(Paint.Cap.ROUND);
+        mEdgeHintPaint.setStrokeWidth(0f);
+        mEdgeHintColorUpdateListener = animation -> {
+            mEdgeHintPaint.setColor((int) animation.getAnimatedValue());
+            invalidateSelf();
+        };
+        mEdgeHintWidthUpdateListener = animation -> {
+            mEdgeHintPaint.setStrokeWidth((float) animation.getAnimatedValue());
+            invalidateSelf();
+        };
+        mEdgeHintPulseListener = new Animator.AnimatorListener() {
+            @Override
+            public void onAnimationStart(Animator animation) {}
+
+            @Override
+            public void onAnimationEnd(Animator animation) {
+                mHandler.postDelayed(() -> {
+                    mEdgeHintColorAnimator =
+                            ValueAnimator.ofArgb(mEdgeHintPaint.getColor(), mHintColorFaded);
+                    mEdgeHintColorAnimator.setInterpolator(new LinearInterpolator());
+                    mEdgeHintColorAnimator.setDuration(HINT_COLOR_ANIM_DURATION_MS);
+                    mEdgeHintColorAnimator.addUpdateListener(mEdgeHintColorUpdateListener);
+                    mEdgeHintColorAnimator.start();
+                }, HINT_COLOR_ANIM_DELAY_MS);
+            }
+
+            @Override
+            public void onAnimationCancel(Animator animation) {}
+
+            @Override
+            public void onAnimationRepeat(Animator animation) {}
+        };
+    }
+
+    @ColorInt
+    private static int getHintColorFaded(@NonNull Context context) {
+        final TypedValue tv = new TypedValue();
+        context.getTheme().resolveAttribute(android.R.attr.disabledAlpha, tv, true);
+        final int alpha = (int) (tv.getFloat() * 255f);
+
+        final int[] attrs = new int[] {android.R.attr.colorControlNormal};
+        final TypedArray ta = context.obtainStyledAttributes(attrs);
+        try {
+            @ColorInt final int color = ta.getColor(0, context.getColor(R.color.white_disabled));
+            return ColorUtils.setAlphaComponent(color, alpha);
+        } finally {
+            ta.recycle();
+        }
     }
 
     void setEnrollHelper(@NonNull UdfpsEnrollHelper helper) {
@@ -100,59 +251,164 @@
     }
 
     void onEnrollmentProgress(int remaining, int totalSteps) {
-        mProgressDrawable.setEnrollmentProgress(remaining, totalSteps);
+        if (mEnrollHelper == null) {
+            return;
+        }
 
-        if (mEnrollHelper.isCenterEnrollmentComplete()) {
-            if (mAnimatorSet != null && mAnimatorSet.isRunning()) {
-                mAnimatorSet.end();
+        if (!mEnrollHelper.isCenterEnrollmentStage()) {
+            if (mTargetAnimatorSet != null && mTargetAnimatorSet.isRunning()) {
+                mTargetAnimatorSet.end();
             }
 
             final PointF point = mEnrollHelper.getNextGuidedEnrollmentPoint();
+            if (mCurrentX != point.x || mCurrentY != point.y) {
+                final ValueAnimator x = ValueAnimator.ofFloat(mCurrentX, point.x);
+                x.addUpdateListener(animation -> {
+                    mCurrentX = (float) animation.getAnimatedValue();
+                    invalidateSelf();
+                });
 
-            final ValueAnimator x = ValueAnimator.ofFloat(mCurrentX, point.x);
-            x.addUpdateListener(animation -> {
-                mCurrentX = (float) animation.getAnimatedValue();
-                invalidateSelf();
-            });
+                final ValueAnimator y = ValueAnimator.ofFloat(mCurrentY, point.y);
+                y.addUpdateListener(animation -> {
+                    mCurrentY = (float) animation.getAnimatedValue();
+                    invalidateSelf();
+                });
 
-            final ValueAnimator y = ValueAnimator.ofFloat(mCurrentY, point.y);
-            y.addUpdateListener(animation -> {
-                mCurrentY = (float) animation.getAnimatedValue();
-                invalidateSelf();
-            });
+                final boolean isMovingToCenter = point.x == 0f && point.y == 0f;
+                final long duration = isMovingToCenter
+                        ? TARGET_ANIM_DURATION_SHORT
+                        : TARGET_ANIM_DURATION_LONG;
 
-            final ValueAnimator scale = ValueAnimator.ofFloat(0, (float) Math.PI);
-            scale.setDuration(ANIM_DURATION);
-            scale.addUpdateListener(animation -> {
-                // Grow then shrink
-                mCurrentScale = 1 +
-                        SCALE_MAX * (float) Math.sin((float) animation.getAnimatedValue());
-                invalidateSelf();
-            });
+                final ValueAnimator scale = ValueAnimator.ofFloat(0, (float) Math.PI);
+                scale.setDuration(duration);
+                scale.addUpdateListener(animation -> {
+                    // Grow then shrink
+                    mCurrentScale = 1
+                            + SCALE_MAX * (float) Math.sin((float) animation.getAnimatedValue());
+                    invalidateSelf();
+                });
 
-            mAnimatorSet = new AnimatorSet();
+                mTargetAnimatorSet = new AnimatorSet();
 
-            mAnimatorSet.setInterpolator(new AccelerateDecelerateInterpolator());
-            mAnimatorSet.setDuration(ANIM_DURATION);
-            mAnimatorSet.playTogether(x, y, scale);
-            mAnimatorSet.start();
+                mTargetAnimatorSet.setInterpolator(new AccelerateDecelerateInterpolator());
+                mTargetAnimatorSet.setDuration(duration);
+                mTargetAnimatorSet.addListener(mTargetAnimListener);
+                mTargetAnimatorSet.playTogether(x, y, scale);
+                mTargetAnimatorSet.start();
+            } else {
+                updateTipHintVisibility();
+            }
+        } else {
+            updateTipHintVisibility();
+        }
+
+        updateEdgeHintVisibility();
+    }
+
+    private void updateTipHintVisibility() {
+        final boolean shouldShow = mEnrollHelper != null && mEnrollHelper.isTipEnrollmentStage();
+        if (mShouldShowTipHint == shouldShow) {
+            return;
+        }
+        mShouldShowTipHint = shouldShow;
+
+        if (mTipHintWidthAnimator != null && mTipHintWidthAnimator.isRunning()) {
+            mTipHintWidthAnimator.cancel();
+        }
+
+        final float targetWidth = shouldShow ? mHintMaxWidthPx : 0f;
+        mTipHintWidthAnimator = ValueAnimator.ofFloat(mTipHintPaint.getStrokeWidth(), targetWidth);
+        mTipHintWidthAnimator.setDuration(HINT_WIDTH_ANIM_DURATION_MS);
+        mTipHintWidthAnimator.addUpdateListener(mTipHintWidthUpdateListener);
+
+        if (shouldShow) {
+            startTipHintPulseAnimation();
+        } else {
+            mTipHintWidthAnimator.start();
         }
     }
 
-    void onLastStepAcquired() {
-        mProgressDrawable.onLastStepAcquired();
+    private void updateEdgeHintVisibility() {
+        final boolean shouldShow = mEnrollHelper != null && mEnrollHelper.isEdgeEnrollmentStage();
+        if (mShouldShowEdgeHint == shouldShow) {
+            return;
+        }
+        mShouldShowEdgeHint = shouldShow;
+
+        if (mEdgeHintWidthAnimator != null && mEdgeHintWidthAnimator.isRunning()) {
+            mEdgeHintWidthAnimator.cancel();
+        }
+
+        final float targetWidth = shouldShow ? mHintMaxWidthPx : 0f;
+        mEdgeHintWidthAnimator =
+                ValueAnimator.ofFloat(mEdgeHintPaint.getStrokeWidth(), targetWidth);
+        mEdgeHintWidthAnimator.setDuration(HINT_WIDTH_ANIM_DURATION_MS);
+        mEdgeHintWidthAnimator.addUpdateListener(mEdgeHintWidthUpdateListener);
+
+        if (shouldShow) {
+            startEdgeHintPulseAnimation();
+        } else {
+            mEdgeHintWidthAnimator.start();
+        }
+    }
+
+    private void startTipHintPulseAnimation() {
+        mHandler.removeCallbacksAndMessages(null);
+        if (mTipHintAnimatorSet != null && mTipHintAnimatorSet.isRunning()) {
+            mTipHintAnimatorSet.cancel();
+        }
+        if (mTipHintColorAnimator != null && mTipHintColorAnimator.isRunning()) {
+            mTipHintColorAnimator.cancel();
+        }
+
+        mTipHintColorAnimator = ValueAnimator.ofArgb(mTipHintPaint.getColor(), mHintColorHighlight);
+        mTipHintColorAnimator.setDuration(HINT_WIDTH_ANIM_DURATION_MS);
+        mTipHintColorAnimator.addUpdateListener(mTipHintColorUpdateListener);
+        mTipHintColorAnimator.addListener(mTipHintPulseListener);
+
+        mTipHintAnimatorSet = new AnimatorSet();
+        mTipHintAnimatorSet.setInterpolator(new AccelerateDecelerateInterpolator());
+        mTipHintAnimatorSet.playTogether(mTipHintColorAnimator, mTipHintWidthAnimator);
+        mTipHintAnimatorSet.start();
+    }
+
+    private void startEdgeHintPulseAnimation() {
+        mHandler.removeCallbacksAndMessages(null);
+        if (mEdgeHintAnimatorSet != null && mEdgeHintAnimatorSet.isRunning()) {
+            mEdgeHintAnimatorSet.cancel();
+        }
+        if (mEdgeHintColorAnimator != null && mEdgeHintColorAnimator.isRunning()) {
+            mEdgeHintColorAnimator.cancel();
+        }
+
+        mEdgeHintColorAnimator =
+                ValueAnimator.ofArgb(mEdgeHintPaint.getColor(), mHintColorHighlight);
+        mEdgeHintColorAnimator.setDuration(HINT_WIDTH_ANIM_DURATION_MS);
+        mEdgeHintColorAnimator.addUpdateListener(mEdgeHintColorUpdateListener);
+        mEdgeHintColorAnimator.addListener(mEdgeHintPulseListener);
+
+        mEdgeHintAnimatorSet = new AnimatorSet();
+        mEdgeHintAnimatorSet.setInterpolator(new AccelerateDecelerateInterpolator());
+        mEdgeHintAnimatorSet.playTogether(mEdgeHintColorAnimator, mEdgeHintWidthAnimator);
+        mEdgeHintAnimatorSet.start();
+    }
+
+    private boolean isTipHintVisible() {
+        return mTipHintPaint.getStrokeWidth() > 0f;
+    }
+
+    private boolean isEdgeHintVisible() {
+        return mEdgeHintPaint.getStrokeWidth() > 0f;
     }
 
     @Override
     public void draw(@NonNull Canvas canvas) {
-        mProgressDrawable.draw(canvas);
-
         if (isIlluminationShowing()) {
             return;
         }
 
         // Draw moving target
-        if (mEnrollHelper.isCenterEnrollmentComplete()) {
+        if (mEnrollHelper != null && !mEnrollHelper.isCenterEnrollmentStage()) {
             canvas.save();
             canvas.translate(mCurrentX, mCurrentY);
 
@@ -172,11 +428,59 @@
             mFingerprintDrawable.setAlpha(mAlpha);
             mSensorOutlinePaint.setAlpha(mAlpha);
         }
-    }
 
-    @Override
-    public void onBoundsChange(@NonNull Rect rect) {
-        mProgressDrawable.setBounds(rect);
+        // Draw the finger tip or edges hint.
+        if (isTipHintVisible() || isEdgeHintVisible()) {
+            canvas.save();
+
+            // Make arcs start from the top, rather than the right.
+            canvas.rotate(-90f, mSensorRect.centerX(), mSensorRect.centerY());
+
+            final float halfSensorHeight = Math.abs(mSensorRect.bottom - mSensorRect.top) / 2f;
+            final float halfSensorWidth = Math.abs(mSensorRect.right - mSensorRect.left) / 2f;
+            final float hintXOffset = halfSensorWidth + mHintPaddingPx;
+            final float hintYOffset = halfSensorHeight + mHintPaddingPx;
+
+            if (isTipHintVisible()) {
+                canvas.drawArc(
+                        mSensorRect.centerX() - hintXOffset,
+                        mSensorRect.centerY() - hintYOffset,
+                        mSensorRect.centerX() + hintXOffset,
+                        mSensorRect.centerY() + hintYOffset,
+                        -HINT_ANGLE / 2f,
+                        HINT_ANGLE,
+                        false /* useCenter */,
+                        mTipHintPaint);
+            }
+
+            if (isEdgeHintVisible()) {
+                // Draw right edge hint.
+                canvas.rotate(-90f, mSensorRect.centerX(), mSensorRect.centerY());
+                canvas.drawArc(
+                        mSensorRect.centerX() - hintXOffset,
+                        mSensorRect.centerY() - hintYOffset,
+                        mSensorRect.centerX() + hintXOffset,
+                        mSensorRect.centerY() + hintYOffset,
+                        -HINT_ANGLE / 2f,
+                        HINT_ANGLE,
+                        false /* useCenter */,
+                        mEdgeHintPaint);
+
+                // Draw left edge hint.
+                canvas.rotate(180f, mSensorRect.centerX(), mSensorRect.centerY());
+                canvas.drawArc(
+                        mSensorRect.centerX() - hintXOffset,
+                        mSensorRect.centerY() - hintYOffset,
+                        mSensorRect.centerX() + hintXOffset,
+                        mSensorRect.centerY() + hintYOffset,
+                        -HINT_ANGLE / 2f,
+                        HINT_ANGLE,
+                        false /* useCenter */,
+                        mEdgeHintPaint);
+            }
+
+            canvas.restore();
+        }
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollHelper.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollHelper.java
index 6a918a6..8ac6df7 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollHelper.java
@@ -44,11 +44,19 @@
     private static final String NEW_COORDS_OVERRIDE =
             "com.android.systemui.biometrics.UdfpsNewCoords";
 
-    // Enroll with two center touches before going to guided enrollment
-    private static final int NUM_CENTER_TOUCHES = 2;
+    static final int ENROLL_STAGE_COUNT = 4;
+
+    // TODO(b/198928407): Consolidate with FingerprintEnrollEnrolling
+    private static final int[] STAGE_THRESHOLDS = new int[] {
+            2, // center
+            18, // guided
+            22, // fingertip
+            38, // edges
+    };
 
     interface Listener {
         void onEnrollmentProgress(int remaining, int totalSteps);
+        void onEnrollmentHelp(int remaining, int totalSteps);
         void onLastStepAcquired();
     }
 
@@ -65,6 +73,8 @@
     // interface makes no promises about monotonically increasing by one each time.
     private int mLocationsEnrolled = 0;
 
+    private int mCenterTouchCount = 0;
+
     @Nullable Listener mListener;
 
     public UdfpsEnrollHelper(@NonNull Context context, int reason) {
@@ -117,17 +127,43 @@
         }
     }
 
+    static int getStageThreshold(int index) {
+        return STAGE_THRESHOLDS[index];
+    }
+
+    static int getLastStageThreshold() {
+        return STAGE_THRESHOLDS[ENROLL_STAGE_COUNT - 1];
+    }
+
     boolean shouldShowProgressBar() {
         return mEnrollReason == IUdfpsOverlayController.REASON_ENROLL_ENROLLING;
     }
 
     void onEnrollmentProgress(int remaining) {
-        if (mTotalSteps == -1) {
-            mTotalSteps = remaining;
-        }
+        Log.d(TAG, "onEnrollmentProgress: remaining = " + remaining
+                + ", mRemainingSteps = " + mRemainingSteps
+                + ", mTotalSteps = " + mTotalSteps
+                + ", mLocationsEnrolled = " + mLocationsEnrolled
+                + ", mCenterTouchCount = " + mCenterTouchCount);
 
         if (remaining != mRemainingSteps) {
             mLocationsEnrolled++;
+            if (isCenterEnrollmentStage()) {
+                mCenterTouchCount++;
+            }
+        }
+
+        if (mTotalSteps == -1) {
+            mTotalSteps = remaining;
+
+            // Allocate (or subtract) any extra steps for the first enroll stage.
+            final int extraSteps = mTotalSteps - getLastStageThreshold();
+            if (extraSteps != 0) {
+                for (int stageIndex = 0; stageIndex < ENROLL_STAGE_COUNT; stageIndex++) {
+                    STAGE_THRESHOLDS[stageIndex] =
+                            Math.max(0, STAGE_THRESHOLDS[stageIndex] + extraSteps);
+                }
+            }
         }
 
         mRemainingSteps = remaining;
@@ -138,7 +174,9 @@
     }
 
     void onEnrollmentHelp() {
-
+        if (mListener != null) {
+            mListener.onEnrollmentHelp(mRemainingSteps, mTotalSteps);
+        }
     }
 
     void setListener(Listener listener) {
@@ -152,19 +190,39 @@
         }
     }
 
-    boolean isCenterEnrollmentComplete() {
+    boolean isCenterEnrollmentStage() {
         if (mTotalSteps == -1 || mRemainingSteps == -1) {
-            return false;
-        } else if (mAccessibilityEnabled) {
+            return true;
+        }
+        return mTotalSteps - mRemainingSteps < STAGE_THRESHOLDS[0];
+    }
+
+    boolean isGuidedEnrollmentStage() {
+        if (mAccessibilityEnabled || mTotalSteps == -1 || mRemainingSteps == -1) {
             return false;
         }
-        final int stepsEnrolled = mTotalSteps - mRemainingSteps;
-        return stepsEnrolled >= NUM_CENTER_TOUCHES;
+        final int progressSteps = mTotalSteps - mRemainingSteps;
+        return progressSteps >= STAGE_THRESHOLDS[0] && progressSteps < STAGE_THRESHOLDS[1];
+    }
+
+    boolean isTipEnrollmentStage() {
+        if (mTotalSteps == -1 || mRemainingSteps == -1) {
+            return false;
+        }
+        final int progressSteps = mTotalSteps - mRemainingSteps;
+        return progressSteps >= STAGE_THRESHOLDS[1] && progressSteps < STAGE_THRESHOLDS[2];
+    }
+
+    boolean isEdgeEnrollmentStage() {
+        if (mTotalSteps == -1 || mRemainingSteps == -1) {
+            return false;
+        }
+        return mTotalSteps - mRemainingSteps >= STAGE_THRESHOLDS[2];
     }
 
     @NonNull
     PointF getNextGuidedEnrollmentPoint() {
-        if (mAccessibilityEnabled) {
+        if (mAccessibilityEnabled || !isGuidedEnrollmentStage()) {
             return new PointF(0f, 0f);
         }
 
@@ -174,13 +232,14 @@
                     SCALE_OVERRIDE, SCALE,
                     UserHandle.USER_CURRENT);
         }
-        final int index = mLocationsEnrolled - NUM_CENTER_TOUCHES;
+        final int index = mLocationsEnrolled - mCenterTouchCount;
         final PointF originalPoint = mGuidedEnrollmentPoints
                 .get(index % mGuidedEnrollmentPoints.size());
         return new PointF(originalPoint.x * scale, originalPoint.y * scale);
     }
 
     void animateIfLastStep() {
+        Log.d(TAG, "animateIfLastStep: mRemainingSteps = " + mRemainingSteps);
         if (mListener == null) {
             Log.e(TAG, "animateIfLastStep, null listener");
             return;
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollProgressBarDrawable.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollProgressBarDrawable.java
index 4195009..b56543f 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollProgressBarDrawable.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollProgressBarDrawable.java
@@ -16,148 +16,107 @@
 
 package com.android.systemui.biometrics;
 
-import android.animation.ValueAnimator;
 import android.content.Context;
-import android.content.res.TypedArray;
 import android.graphics.Canvas;
 import android.graphics.ColorFilter;
-import android.graphics.Paint;
 import android.graphics.drawable.Drawable;
 import android.util.Log;
-import android.util.TypedValue;
 
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 
-import com.android.systemui.R;
+import java.util.ArrayList;
+import java.util.List;
 
 /**
  * UDFPS enrollment progress bar.
  */
 public class UdfpsEnrollProgressBarDrawable extends Drawable {
+    private static final String TAG = "UdfpsProgressBar";
 
-    private static final String TAG = "UdfpsEnrollProgressBarDrawable";
+    private static final float SEGMENT_GAP_ANGLE = 12f;
 
-    private static final float PROGRESS_BAR_THICKNESS_DP = 12;
+    @NonNull private final List<UdfpsEnrollProgressBarSegment> mSegments;
 
-    @NonNull private final Context mContext;
-    @NonNull private final UdfpsEnrollDrawable mParent;
-    @NonNull private final Paint mBackgroundCirclePaint;
-    @NonNull private final Paint mProgressPaint;
-
-    @Nullable private ValueAnimator mProgressAnimator;
-    private float mProgress;
-    private int mRotation; // After last step, rotate the progress bar once
-    private boolean mLastStepAcquired;
-
-    public UdfpsEnrollProgressBarDrawable(@NonNull Context context,
-            @NonNull UdfpsEnrollDrawable parent) {
-        mContext = context;
-        mParent = parent;
-
-        mBackgroundCirclePaint = new Paint();
-        mBackgroundCirclePaint.setStrokeWidth(Utils.dpToPixels(context, PROGRESS_BAR_THICKNESS_DP));
-        mBackgroundCirclePaint.setColor(context.getColor(R.color.white_disabled));
-        mBackgroundCirclePaint.setAntiAlias(true);
-        mBackgroundCirclePaint.setStyle(Paint.Style.STROKE);
-
-        // Background circle color + alpha
-        TypedArray tc = context.obtainStyledAttributes(
-                new int[] {android.R.attr.colorControlNormal});
-        int tintColor = tc.getColor(0, mBackgroundCirclePaint.getColor());
-        mBackgroundCirclePaint.setColor(tintColor);
-        tc.recycle();
-        TypedValue alpha = new TypedValue();
-        context.getTheme().resolveAttribute(android.R.attr.disabledAlpha, alpha, true);
-        mBackgroundCirclePaint.setAlpha((int) (alpha.getFloat() * 255));
-
-        // Progress should not be color extracted
-        mProgressPaint = new Paint();
-        mProgressPaint.setStrokeWidth(Utils.dpToPixels(context, PROGRESS_BAR_THICKNESS_DP));
-        mProgressPaint.setColor(context.getColor(R.color.udfps_enroll_progress));
-        mProgressPaint.setAntiAlias(true);
-        mProgressPaint.setStyle(Paint.Style.STROKE);
-        mProgressPaint.setStrokeCap(Paint.Cap.ROUND);
+    public UdfpsEnrollProgressBarDrawable(@NonNull Context context) {
+        mSegments = new ArrayList<>(UdfpsEnrollHelper.ENROLL_STAGE_COUNT);
+        float startAngle = SEGMENT_GAP_ANGLE / 2f;
+        final float sweepAngle = (360f / UdfpsEnrollHelper.ENROLL_STAGE_COUNT) - SEGMENT_GAP_ANGLE;
+        final Runnable invalidateRunnable = this::invalidateSelf;
+        for (int index = 0; index < UdfpsEnrollHelper.ENROLL_STAGE_COUNT; index++) {
+            mSegments.add(new UdfpsEnrollProgressBarSegment(context, getBounds(), startAngle,
+                    sweepAngle, SEGMENT_GAP_ANGLE, invalidateRunnable));
+            startAngle += sweepAngle + SEGMENT_GAP_ANGLE;
+        }
     }
 
     void setEnrollmentProgress(int remaining, int totalSteps) {
-        // Add one so that the first steps actually changes progress, but also so that the last
-        // step ends at 1.0
-        final float progress = (totalSteps - remaining + 1) / (float) (totalSteps + 1);
-        setEnrollmentProgress(progress);
+        if (remaining == totalSteps) {
+            // Show some progress for the initial touch.
+            setEnrollmentProgress(1);
+        } else {
+            setEnrollmentProgress(totalSteps - remaining);
+        }
     }
 
-    private void setEnrollmentProgress(float progress) {
-        if (mLastStepAcquired) {
-            return;
+    private void setEnrollmentProgress(int progressSteps) {
+        Log.d(TAG, "setEnrollmentProgress: progressSteps = " + progressSteps);
+
+        int segmentIndex = 0;
+        int prevThreshold = 0;
+        while (segmentIndex < mSegments.size()) {
+            final UdfpsEnrollProgressBarSegment segment = mSegments.get(segmentIndex);
+            final int threshold = UdfpsEnrollHelper.getStageThreshold(segmentIndex);
+
+            if (progressSteps >= threshold && !segment.isFilledOrFilling()) {
+                Log.d(TAG, "setEnrollmentProgress: segment[" + segmentIndex + "] complete");
+                segment.updateProgress(1f);
+                break;
+            } else if (progressSteps >= prevThreshold && progressSteps < threshold) {
+                final int relativeSteps = progressSteps - prevThreshold;
+                final int relativeThreshold = threshold - prevThreshold;
+                final float segmentProgress = (float) relativeSteps / (float) relativeThreshold;
+                Log.d(TAG, "setEnrollmentProgress: segment[" + segmentIndex + "] progress = "
+                        + segmentProgress);
+                segment.updateProgress(segmentProgress);
+                break;
+            }
+
+            segmentIndex++;
+            prevThreshold = threshold;
         }
 
-        long animationDuration = 150;
-
-        if (progress == 1.f) {
-            animationDuration = 400;
-            final ValueAnimator rotationAnimator = ValueAnimator.ofInt(0, 400);
-            rotationAnimator.setDuration(animationDuration);
-            rotationAnimator.addUpdateListener(animation -> {
-                Log.d(TAG, "Rotation: " + mRotation);
-                mRotation = (int) animation.getAnimatedValue();
-                mParent.invalidateSelf();
-            });
-            rotationAnimator.start();
+        if (progressSteps >= UdfpsEnrollHelper.getLastStageThreshold()) {
+            Log.d(TAG, "setEnrollmentProgress: startCompletionAnimation");
+            for (final UdfpsEnrollProgressBarSegment segment : mSegments) {
+                segment.startCompletionAnimation();
+            }
+        } else {
+            Log.d(TAG, "setEnrollmentProgress: cancelCompletionAnimation");
+            for (final UdfpsEnrollProgressBarSegment segment : mSegments) {
+                segment.cancelCompletionAnimation();
+            }
         }
-
-        if (mProgressAnimator != null && mProgressAnimator.isRunning()) {
-            mProgressAnimator.cancel();
-        }
-
-        mProgressAnimator = ValueAnimator.ofFloat(mProgress, progress);
-        mProgressAnimator.setDuration(animationDuration);
-        mProgressAnimator.addUpdateListener(animation -> {
-            mProgress = (float) animation.getAnimatedValue();
-            // Use the parent to invalidate, since it's the one that's attached as the view's
-            // drawable and has its callback set automatically. Invalidating via
-            // `this.invalidateSelf` actually does not invoke draw(), since this drawable's callback
-            // is not really set.
-            mParent.invalidateSelf();
-        });
-        mProgressAnimator.start();
     }
 
     void onLastStepAcquired() {
-        setEnrollmentProgress(1.f);
-        mLastStepAcquired = true;
+        Log.d(TAG, "setEnrollmentProgress: onLastStepAcquired");
+        setEnrollmentProgress(UdfpsEnrollHelper.getLastStageThreshold());
     }
 
     @Override
     public void draw(@NonNull Canvas canvas) {
+        Log.d(TAG, "setEnrollmentProgress: draw");
+
         canvas.save();
 
         // Progress starts from the top, instead of the right
-        canvas.rotate(-90 + mRotation, getBounds().centerX(), getBounds().centerY());
+        canvas.rotate(-90f, getBounds().centerX(), getBounds().centerY());
 
-        // Progress bar "background track"
-        final float halfPaddingPx = Utils.dpToPixels(mContext, PROGRESS_BAR_THICKNESS_DP) / 2;
-        canvas.drawArc(halfPaddingPx,
-                halfPaddingPx,
-                getBounds().right - halfPaddingPx,
-                getBounds().bottom - halfPaddingPx,
-                0,
-                360,
-                false,
-                mBackgroundCirclePaint
-        );
-
-        final float progress = 360.f * mProgress;
-        // Progress
-        canvas.drawArc(halfPaddingPx,
-                halfPaddingPx,
-                getBounds().right - halfPaddingPx,
-                getBounds().bottom - halfPaddingPx,
-                0,
-                progress,
-                false,
-                mProgressPaint
-        );
+        // Draw each of the enroll segments.
+        for (final UdfpsEnrollProgressBarSegment segment : mSegments) {
+            segment.draw(canvas);
+        }
 
         canvas.restore();
     }
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollProgressBarSegment.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollProgressBarSegment.java
new file mode 100644
index 0000000..5f24380
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollProgressBarSegment.java
@@ -0,0 +1,256 @@
+/*
+ * 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.biometrics;
+
+import android.animation.ValueAnimator;
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.Rect;
+import android.os.Handler;
+import android.os.Looper;
+import android.util.Log;
+import android.util.TypedValue;
+
+import androidx.annotation.ColorInt;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.android.systemui.R;
+
+/**
+ * A single segment of the UDFPS enrollment progress bar.
+ */
+public class UdfpsEnrollProgressBarSegment {
+    private static final String TAG = "UdfpsProgressBarSegment";
+
+    private static final long PROGRESS_ANIMATION_DURATION_MS = 400L;
+    private static final long OVER_SWEEP_ANIMATION_DELAY_MS = 200L;
+    private static final long OVER_SWEEP_ANIMATION_DURATION_MS = 200L;
+
+    private static final float STROKE_WIDTH_DP = 12f;
+
+    private final Handler mHandler = new Handler(Looper.getMainLooper());
+
+    @NonNull private final Rect mBounds;
+    @NonNull private final Runnable mInvalidateRunnable;
+    private final float mStartAngle;
+    private final float mSweepAngle;
+    private final float mMaxOverSweepAngle;
+    private final float mStrokeWidthPx;
+
+    @NonNull private final Paint mBackgroundPaint;
+    @NonNull private final Paint mProgressPaint;
+
+    private boolean mIsFilledOrFilling = false;
+
+    private float mProgress = 0f;
+    @Nullable private ValueAnimator mProgressAnimator;
+    @NonNull private final ValueAnimator.AnimatorUpdateListener mProgressUpdateListener;
+
+    private float mOverSweepAngle = 0f;
+    @Nullable private ValueAnimator mOverSweepAnimator;
+    @Nullable private ValueAnimator mOverSweepReverseAnimator;
+    @NonNull private final ValueAnimator.AnimatorUpdateListener mOverSweepUpdateListener;
+    @NonNull private final Runnable mOverSweepAnimationRunnable;
+
+    public UdfpsEnrollProgressBarSegment(@NonNull Context context, @NonNull Rect bounds,
+            float startAngle, float sweepAngle, float maxOverSweepAngle,
+            @NonNull Runnable invalidateRunnable) {
+
+        mBounds = bounds;
+        mInvalidateRunnable = invalidateRunnable;
+        mStartAngle = startAngle;
+        mSweepAngle = sweepAngle;
+        mMaxOverSweepAngle = maxOverSweepAngle;
+        mStrokeWidthPx = Utils.dpToPixels(context, STROKE_WIDTH_DP);
+
+        mBackgroundPaint = new Paint();
+        mBackgroundPaint.setStrokeWidth(mStrokeWidthPx);
+        mBackgroundPaint.setColor(context.getColor(R.color.white_disabled));
+        mBackgroundPaint.setAntiAlias(true);
+        mBackgroundPaint.setStyle(Paint.Style.STROKE);
+        mBackgroundPaint.setStrokeCap(Paint.Cap.ROUND);
+
+        // Background paint color + alpha
+        final int[] attrs = new int[] {android.R.attr.colorControlNormal};
+        final TypedArray ta = context.obtainStyledAttributes(attrs);
+        @ColorInt final int tintColor = ta.getColor(0, mBackgroundPaint.getColor());
+        mBackgroundPaint.setColor(tintColor);
+        ta.recycle();
+        TypedValue alpha = new TypedValue();
+        context.getTheme().resolveAttribute(android.R.attr.disabledAlpha, alpha, true);
+        mBackgroundPaint.setAlpha((int) (alpha.getFloat() * 255f));
+
+        // Progress should not be color extracted
+        mProgressPaint = new Paint();
+        mProgressPaint.setStrokeWidth(mStrokeWidthPx);
+        mProgressPaint.setColor(context.getColor(R.color.udfps_enroll_progress));
+        mProgressPaint.setAntiAlias(true);
+        mProgressPaint.setStyle(Paint.Style.STROKE);
+        mProgressPaint.setStrokeCap(Paint.Cap.ROUND);
+
+        mProgressUpdateListener = animation -> {
+            mProgress = (float) animation.getAnimatedValue();
+            mInvalidateRunnable.run();
+        };
+
+        mOverSweepUpdateListener = animation -> {
+            mOverSweepAngle = (float) animation.getAnimatedValue();
+            mInvalidateRunnable.run();
+        };
+        mOverSweepAnimationRunnable = () -> {
+            if (mOverSweepAnimator != null && mOverSweepAnimator.isRunning()) {
+                mOverSweepAnimator.cancel();
+            }
+            mOverSweepAnimator = ValueAnimator.ofFloat(mOverSweepAngle, mMaxOverSweepAngle);
+            mOverSweepAnimator.setDuration(OVER_SWEEP_ANIMATION_DURATION_MS);
+            mOverSweepAnimator.addUpdateListener(mOverSweepUpdateListener);
+            mOverSweepAnimator.start();
+        };
+    }
+
+    /**
+     * Draws this segment to the given canvas.
+     */
+    public void draw(@NonNull Canvas canvas) {
+        Log.d(TAG, "draw: mProgress = " + mProgress);
+
+        final float halfPaddingPx = mStrokeWidthPx / 2f;
+
+        if (mProgress < 1f) {
+            // Draw the unfilled background color of the segment.
+            canvas.drawArc(
+                    halfPaddingPx,
+                    halfPaddingPx,
+                    mBounds.right - halfPaddingPx,
+                    mBounds.bottom - halfPaddingPx,
+                    mStartAngle,
+                    mSweepAngle,
+                    false /* useCenter */,
+                    mBackgroundPaint);
+        }
+
+        if (mProgress > 0f) {
+            // Draw the filled progress portion of the segment.
+            canvas.drawArc(
+                    halfPaddingPx,
+                    halfPaddingPx,
+                    mBounds.right - halfPaddingPx,
+                    mBounds.bottom - halfPaddingPx,
+                    mStartAngle,
+                    mSweepAngle * mProgress + mOverSweepAngle,
+                    false /* useCenter */,
+                    mProgressPaint);
+        }
+    }
+
+    /**
+     * @return Whether this segment is filled or in the process of being filled.
+     */
+    public boolean isFilledOrFilling() {
+        return mIsFilledOrFilling;
+    }
+
+    /**
+     * Updates the fill progress of this segment, animating if necessary.
+     *
+     * @param progress The new fill progress, in the range [0, 1].
+     */
+    public void updateProgress(float progress) {
+        updateProgress(progress, PROGRESS_ANIMATION_DURATION_MS);
+    }
+
+    private void updateProgress(float progress, long animationDurationMs) {
+        Log.d(TAG, "updateProgress: progress = " + progress
+                + ", duration = " + animationDurationMs);
+
+        if (mProgress == progress) {
+            Log.d(TAG, "updateProgress skipped: progress == mProgress");
+            return;
+        }
+
+        mIsFilledOrFilling = progress >= 1f;
+
+        if (mProgressAnimator != null && mProgressAnimator.isRunning()) {
+            mProgressAnimator.cancel();
+        }
+
+        mProgressAnimator = ValueAnimator.ofFloat(mProgress, progress);
+        mProgressAnimator.setDuration(animationDurationMs);
+        mProgressAnimator.addUpdateListener(mProgressUpdateListener);
+        mProgressAnimator.start();
+    }
+
+    /**
+     * Queues and runs the completion animation for this segment.
+     */
+    public void startCompletionAnimation() {
+        final boolean hasCallback = mHandler.hasCallbacks(mOverSweepAnimationRunnable);
+        if (hasCallback || mOverSweepAngle >= mMaxOverSweepAngle) {
+            Log.d(TAG, "startCompletionAnimation skipped: hasCallback = " + hasCallback
+                    + ", mOverSweepAngle = " + mOverSweepAngle);
+            return;
+        }
+
+        Log.d(TAG, "startCompletionAnimation: mProgress = " + mProgress
+                + ", mOverSweepAngle = " + mOverSweepAngle);
+
+        // Reset sweep angle back to zero if the animation is being rolled back.
+        if (mOverSweepReverseAnimator != null && mOverSweepReverseAnimator.isRunning()) {
+            mOverSweepReverseAnimator.cancel();
+            mOverSweepAngle = 0f;
+        }
+
+        // Start filling the segment if it isn't already.
+        if (mProgress < 1f) {
+            updateProgress(1f, OVER_SWEEP_ANIMATION_DELAY_MS);
+        }
+
+        // Queue the animation to run after fill completes.
+        mHandler.postDelayed(mOverSweepAnimationRunnable, OVER_SWEEP_ANIMATION_DELAY_MS);
+    }
+
+    /**
+     * Cancels (and reverses, if necessary) a queued or running completion animation.
+     */
+    public void cancelCompletionAnimation() {
+        Log.d(TAG, "cancelCompletionAnimation: mProgress = " + mProgress
+                + ", mOverSweepAngle = " + mOverSweepAngle);
+
+        // Cancel the animation if it's queued or running.
+        mHandler.removeCallbacks(mOverSweepAnimationRunnable);
+        if (mOverSweepAnimator != null && mOverSweepAnimator.isRunning()) {
+            mOverSweepAnimator.cancel();
+        }
+
+        // Roll back the animation if it has at least partially run.
+        if (mOverSweepAngle > 0f) {
+            if (mOverSweepReverseAnimator != null && mOverSweepReverseAnimator.isRunning()) {
+                mOverSweepReverseAnimator.cancel();
+            }
+
+            final float completion = mOverSweepAngle / mMaxOverSweepAngle;
+            final long proratedDuration = (long) (OVER_SWEEP_ANIMATION_DURATION_MS * completion);
+            mOverSweepReverseAnimator = ValueAnimator.ofFloat(mOverSweepAngle, 0f);
+            mOverSweepReverseAnimator.setDuration(proratedDuration);
+            mOverSweepReverseAnimator.addUpdateListener(mOverSweepUpdateListener);
+            mOverSweepReverseAnimator.start();
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollView.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollView.java
index 2cdf49d..6f02c64 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollView.java
@@ -17,9 +17,12 @@
 package com.android.systemui.biometrics;
 
 import android.content.Context;
+import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
 import android.os.Handler;
 import android.os.Looper;
 import android.util.AttributeSet;
+import android.view.View;
+import android.view.ViewGroup;
 import android.widget.ImageView;
 
 import androidx.annotation.NonNull;
@@ -32,20 +35,25 @@
  */
 public class UdfpsEnrollView extends UdfpsAnimationView {
     @NonNull private final UdfpsEnrollDrawable mFingerprintDrawable;
+    @NonNull private final UdfpsEnrollProgressBarDrawable mFingerprintProgressDrawable;
     @NonNull private final Handler mHandler;
 
     @NonNull private ImageView mFingerprintView;
+    @NonNull private ImageView mFingerprintProgressView;
 
     public UdfpsEnrollView(Context context, @Nullable AttributeSet attrs) {
         super(context, attrs);
         mFingerprintDrawable = new UdfpsEnrollDrawable(mContext);
+        mFingerprintProgressDrawable = new UdfpsEnrollProgressBarDrawable(context);
         mHandler = new Handler(Looper.getMainLooper());
     }
 
     @Override
     protected void onFinishInflate() {
         mFingerprintView = findViewById(R.id.udfps_enroll_animation_fp_view);
+        mFingerprintProgressView = findViewById(R.id.udfps_enroll_animation_fp_progress_view);
         mFingerprintView.setImageDrawable(mFingerprintDrawable);
+        mFingerprintProgressView.setImageDrawable(mFingerprintProgressDrawable);
     }
 
     @Override
@@ -53,15 +61,36 @@
         return mFingerprintDrawable;
     }
 
+    void updateSensorLocation(@NonNull FingerprintSensorPropertiesInternal sensorProps) {
+        View fingerprintAccessibilityView = findViewById(R.id.udfps_enroll_accessibility_view);
+        final int sensorHeight = sensorProps.sensorRadius * 2;
+        final int sensorWidth = sensorHeight;
+        ViewGroup.LayoutParams params = fingerprintAccessibilityView.getLayoutParams();
+        params.width = sensorWidth;
+        params.height = sensorHeight;
+        fingerprintAccessibilityView.setLayoutParams(params);
+        fingerprintAccessibilityView.requestLayout();
+    }
+
     void setEnrollHelper(UdfpsEnrollHelper enrollHelper) {
         mFingerprintDrawable.setEnrollHelper(enrollHelper);
     }
 
     void onEnrollmentProgress(int remaining, int totalSteps) {
-        mHandler.post(() -> mFingerprintDrawable.onEnrollmentProgress(remaining, totalSteps));
+        mHandler.post(() -> {
+            mFingerprintProgressDrawable.setEnrollmentProgress(remaining, totalSteps);
+            mFingerprintDrawable.onEnrollmentProgress(remaining, totalSteps);
+        });
+    }
+
+    void onEnrollmentHelp(int remaining, int totalSteps) {
+        mHandler.post(
+                () -> mFingerprintProgressDrawable.setEnrollmentProgress(remaining, totalSteps));
     }
 
     void onLastStepAcquired() {
-        mHandler.post(mFingerprintDrawable::onLastStepAcquired);
+        mHandler.post(() -> {
+            mFingerprintProgressDrawable.onLastStepAcquired();
+        });
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollViewController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollViewController.java
index 3dab010..6cdd1c8b 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollViewController.java
@@ -33,16 +33,21 @@
     @NonNull private final UdfpsEnrollHelper mEnrollHelper;
     @NonNull private final UdfpsEnrollHelper.Listener mEnrollHelperListener =
             new UdfpsEnrollHelper.Listener() {
-        @Override
-        public void onEnrollmentProgress(int remaining, int totalSteps) {
-            mView.onEnrollmentProgress(remaining, totalSteps);
-        }
+                @Override
+                public void onEnrollmentProgress(int remaining, int totalSteps) {
+                    mView.onEnrollmentProgress(remaining, totalSteps);
+                }
 
-        @Override
-        public void onLastStepAcquired() {
-            mView.onLastStepAcquired();
-        }
-    };
+                @Override
+                public void onEnrollmentHelp(int remaining, int totalSteps) {
+                    mView.onEnrollmentHelp(remaining, totalSteps);
+                }
+
+                @Override
+                public void onLastStepAcquired() {
+                    mView.onLastStepAcquired();
+                }
+            };
 
     protected UdfpsEnrollViewController(
             @NonNull UdfpsEnrollView view,
@@ -74,7 +79,7 @@
     @NonNull
     @Override
     public PointF getTouchTranslation() {
-        if (!mEnrollHelper.isCenterEnrollmentComplete()) {
+        if (!mEnrollHelper.isGuidedEnrollmentStage()) {
             return new PointF(0, 0);
         } else {
             return mEnrollHelper.getNextGuidedEnrollmentPoint();
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardView.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardView.java
index d46426a..9015396 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardView.java
@@ -155,6 +155,13 @@
         updateAlpha();
     }
 
+    /**
+     * @return alpha between 0 and 255
+     */
+    int getUnpausedAlpha() {
+        return mAlpha;
+    }
+
     @Override
     protected int updateAlpha() {
         int alpha = super.updateAlpha();
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.java
index 4896305..8886723 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.java
@@ -34,12 +34,12 @@
 import com.android.systemui.statusbar.phone.StatusBar;
 import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
 import com.android.systemui.statusbar.policy.ConfigurationController;
+import com.android.systemui.statusbar.policy.KeyguardStateController;
 import com.android.systemui.util.concurrency.DelayableExecutor;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
 
-
 /**
  * Class that coordinates non-HBM animations during keyguard authentication.
  */
@@ -50,6 +50,7 @@
     @NonNull private final KeyguardViewMediator mKeyguardViewMediator;
     @NonNull private final LockscreenShadeTransitionController mLockScreenShadeTransitionController;
     @NonNull private final ConfigurationController mConfigurationController;
+    @NonNull private final KeyguardStateController mKeyguardStateController;
     @NonNull private final UdfpsController mUdfpsController;
 
     private boolean mShowingUdfpsBouncer;
@@ -60,6 +61,9 @@
     private float mTransitionToFullShadeProgress;
     private float mLastDozeAmount;
 
+    private float mStatusBarExpansion;
+    private boolean mLaunchTransitionFadingAway;
+
     /**
      * hidden amount of pin/pattern/password bouncer
      * {@link KeyguardBouncer#EXPANSION_VISIBLE} (0f) to
@@ -79,6 +83,7 @@
             @NonNull KeyguardViewMediator keyguardViewMediator,
             @NonNull LockscreenShadeTransitionController transitionController,
             @NonNull ConfigurationController configurationController,
+            @NonNull KeyguardStateController keyguardStateController,
             @NonNull UdfpsController udfpsController) {
         super(view, statusBarStateController, statusBar, dumpManager);
         mKeyguardViewManager = statusBarKeyguardViewManager;
@@ -87,6 +92,7 @@
         mKeyguardViewMediator = keyguardViewMediator;
         mLockScreenShadeTransitionController = transitionController;
         mConfigurationController = configurationController;
+        mKeyguardStateController = keyguardStateController;
         mUdfpsController = udfpsController;
     }
 
@@ -105,11 +111,14 @@
 
         mUdfpsRequested = false;
 
+        mLaunchTransitionFadingAway = mKeyguardStateController.isLaunchTransitionFadingAway();
+        mKeyguardStateController.addCallback(mKeyguardStateControllerCallback);
         mStatusBarState = mStatusBarStateController.getState();
         mQsExpanded = mKeyguardViewManager.isQsExpanded();
         mInputBouncerHiddenAmount = KeyguardBouncer.EXPANSION_HIDDEN;
         mIsBouncerVisible = mKeyguardViewManager.bouncerIsOrWillBeShowing();
         mConfigurationController.addCallback(mConfigurationListener);
+        mStatusBar.addExpansionChangedListener(mStatusBarExpansionChangedListener);
         updateAlpha();
         updatePauseAuth();
 
@@ -122,10 +131,12 @@
         super.onViewDetached();
         mFaceDetectRunning = false;
 
+        mKeyguardStateController.removeCallback(mKeyguardStateControllerCallback);
         mStatusBarStateController.removeCallback(mStateListener);
         mKeyguardViewManager.removeAlternateAuthInterceptor(mAlternateAuthInterceptor);
         mKeyguardUpdateMonitor.requestFaceAuthOnOccludingApp(false);
         mConfigurationController.removeCallback(mConfigurationListener);
+        mStatusBar.removeExpansionChangedListener(mStatusBarExpansionChangedListener);
         if (mLockScreenShadeTransitionController.getUdfpsKeyguardViewController() == this) {
             mLockScreenShadeTransitionController.setUdfpsKeyguardViewController(null);
         }
@@ -140,9 +151,11 @@
         pw.println("mQsExpanded=" + mQsExpanded);
         pw.println("mIsBouncerVisible=" + mIsBouncerVisible);
         pw.println("mInputBouncerHiddenAmount=" + mInputBouncerHiddenAmount);
-        pw.println("mAlpha=" + mView.getAlpha());
+        pw.println("mStatusBarExpansion=" + mStatusBarExpansion);
+        pw.println("unpausedAlpha=" + mView.getUnpausedAlpha());
         pw.println("mUdfpsRequested=" + mUdfpsRequested);
         pw.println("mView.mUdfpsRequested=" + mView.mUdfpsRequested);
+        pw.println("mLaunchTransitionFadingAway=" + mLaunchTransitionFadingAway);
     }
 
     /**
@@ -154,10 +167,10 @@
             return false;
         }
 
+        boolean udfpsAffordanceWasNotShowing = shouldPauseAuth();
         mShowingUdfpsBouncer = show;
-        updatePauseAuth();
         if (mShowingUdfpsBouncer) {
-            if (mStatusBarState == StatusBarState.SHADE_LOCKED) {
+            if (udfpsAffordanceWasNotShowing) {
                 mView.animateInUdfpsBouncer(null);
             }
 
@@ -170,6 +183,8 @@
         } else {
             mKeyguardUpdateMonitor.requestFaceAuthOnOccludingApp(false);
         }
+        updateAlpha();
+        updatePauseAuth();
         return true;
     }
 
@@ -189,6 +204,10 @@
             return false;
         }
 
+        if (mLaunchTransitionFadingAway) {
+            return true;
+        }
+
         if (mStatusBarState != KEYGUARD) {
             return true;
         }
@@ -237,12 +256,17 @@
     }
 
     private void updateAlpha() {
-        // fade icon on transition to showing bouncer
+        // fade icon on transitions to showing the status bar, but if mUdfpsRequested, then
+        // the keyguard is occluded by some application - so instead use the input bouncer
+        // hidden amount to determine the fade
+        float expansion = mUdfpsRequested ? mInputBouncerHiddenAmount : mStatusBarExpansion;
         int alpha = mShowingUdfpsBouncer ? 255
                 : (int) MathUtils.constrain(
-                    MathUtils.map(.5f, .9f, 0f, 255f, mInputBouncerHiddenAmount),
+                    MathUtils.map(.5f, .9f, 0f, 255f, expansion),
                     0f, 255f);
-        alpha *= (1.0f - mTransitionToFullShadeProgress);
+        if (!mShowingUdfpsBouncer) {
+            alpha *= (1.0f - mTransitionToFullShadeProgress);
+        }
         mView.setUnpausedAlpha(alpha);
     }
 
@@ -287,6 +311,7 @@
                 public void requestUdfps(boolean request, int color) {
                     mUdfpsRequested = request;
                     mView.requestUdfps(request, color);
+                    updateAlpha();
                     updatePauseAuth();
                 }
 
@@ -356,4 +381,23 @@
                     mView.updateColor();
                 }
             };
+
+    private final StatusBar.ExpansionChangedListener mStatusBarExpansionChangedListener =
+            new StatusBar.ExpansionChangedListener() {
+                @Override
+                public void onExpansionChanged(float expansion, boolean expanded) {
+                    mStatusBarExpansion = expansion;
+                    updateAlpha();
+                }
+            };
+
+    private final KeyguardStateController.Callback mKeyguardStateControllerCallback =
+            new KeyguardStateController.Callback() {
+                @Override
+                public void onLaunchTransitionFadingAwayChanged() {
+                    mLaunchTransitionFadingAway =
+                            mKeyguardStateController.isLaunchTransitionFadingAway();
+                    updatePauseAuth();
+                }
+            };
 }
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/ZigZagClassifier.java b/packages/SystemUI/src/com/android/systemui/classifier/ZigZagClassifier.java
index e1349f2..40c28fa 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/ZigZagClassifier.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/ZigZagClassifier.java
@@ -21,6 +21,7 @@
 import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.BRIGHTLINE_FALSING_ZIGZAG_Y_PRIMARY_DEVIANCE;
 import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.BRIGHTLINE_FALSING_ZIGZAG_Y_SECONDARY_DEVIANCE;
 import static com.android.systemui.classifier.Classifier.BRIGHTNESS_SLIDER;
+import static com.android.systemui.classifier.Classifier.LOCK_ICON;
 import static com.android.systemui.classifier.Classifier.SHADE_DRAG;
 
 import android.graphics.Point;
@@ -89,7 +90,9 @@
     Result calculateFalsingResult(
             @Classifier.InteractionType int interactionType,
             double historyBelief, double historyConfidence) {
-        if (interactionType == BRIGHTNESS_SLIDER || interactionType == SHADE_DRAG) {
+        if (interactionType == BRIGHTNESS_SLIDER
+                || interactionType == SHADE_DRAG
+                || interactionType == LOCK_ICON) {
             return Result.passed(0);
         }
 
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeLog.java b/packages/SystemUI/src/com/android/systemui/doze/DozeLog.java
index 4196465..657d924 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeLog.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeLog.java
@@ -214,6 +214,14 @@
     }
 
     /**
+     * Appends display state delayed by UDFPS event to the logs
+     * @param delayedDisplayState the display screen state that was delayed
+     */
+    public void traceDisplayStateDelayedByUdfps(int delayedDisplayState) {
+        mLogger.logDisplayStateDelayedByUdfps(delayedDisplayState);
+    }
+
+    /**
      * Appends display state changed event to the logs
      * @param displayState new DozeMachine state
      */
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeLogger.kt b/packages/SystemUI/src/com/android/systemui/doze/DozeLogger.kt
index 9bc74be..fe37d49 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeLogger.kt
@@ -160,6 +160,14 @@
         })
     }
 
+    fun logDisplayStateDelayedByUdfps(delayedDisplayState: Int) {
+        buffer.log(TAG, INFO, {
+            str1 = Display.stateToString(delayedDisplayState)
+        }, {
+            "Delaying display state change to: $str1 due to UDFPS activity"
+        })
+    }
+
     fun logDisplayStateChanged(displayState: Int) {
         buffer.log(TAG, INFO, {
             str1 = Display.stateToString(displayState)
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeScreenBrightness.java b/packages/SystemUI/src/com/android/systemui/doze/DozeScreenBrightness.java
index 470d2f3..8d4ac75 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeScreenBrightness.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeScreenBrightness.java
@@ -36,6 +36,7 @@
 import com.android.systemui.doze.dagger.WrappedService;
 import com.android.systemui.keyguard.WakefulnessLifecycle;
 import com.android.systemui.statusbar.phone.DozeParameters;
+import com.android.systemui.statusbar.phone.UnlockedScreenOffAnimationController;
 import com.android.systemui.util.sensors.AsyncSensorManager;
 
 import java.io.PrintWriter;
@@ -55,6 +56,16 @@
             "com.android.systemui.doze.AOD_BRIGHTNESS";
     protected static final String BRIGHTNESS_BUCKET = "brightness_bucket";
 
+    /**
+     * Just before the screen times out from user inactivity, DisplayPowerController dims the screen
+     * brightness to the lower of {@link #mScreenBrightnessDim}, or the current brightness minus
+     * DisplayPowerController#SCREEN_DIM_MINIMUM_REDUCTION_FLOAT.
+     *
+     * This value is 0.04f * 255, which converts SCREEN_DIM_MINIMUM_REDUCTION_FLOAT to the integer
+     * brightness values used by doze.
+     */
+    private static final int SCREEN_DIM_MINIMUM_REDUCTION_INT = 10;
+
     private final Context mContext;
     private final DozeMachine.Service mDozeService;
     private final DozeHost mDozeHost;
@@ -81,13 +92,16 @@
      */
     private int mDebugBrightnessBucket = -1;
 
+    private UnlockedScreenOffAnimationController mUnlockedScreenOffAnimationController;
+
     @Inject
     public DozeScreenBrightness(Context context, @WrappedService DozeMachine.Service service,
             AsyncSensorManager sensorManager,
             @BrightnessSensor Optional<Sensor> lightSensorOptional, DozeHost host, Handler handler,
             AlwaysOnDisplayPolicy alwaysOnDisplayPolicy,
             WakefulnessLifecycle wakefulnessLifecycle,
-            DozeParameters dozeParameters) {
+            DozeParameters dozeParameters,
+            UnlockedScreenOffAnimationController unlockedScreenOffAnimationController) {
         mContext = context;
         mDozeService = service;
         mSensorManager = sensorManager;
@@ -96,6 +110,7 @@
         mDozeParameters = dozeParameters;
         mDozeHost = host;
         mHandler = handler;
+        mUnlockedScreenOffAnimationController = unlockedScreenOffAnimationController;
 
         mDefaultDozeBrightness = alwaysOnDisplayPolicy.defaultDozeBrightness;
         mScreenBrightnessDim = alwaysOnDisplayPolicy.dimBrightness;
@@ -146,14 +161,15 @@
         }
     }
 
-    private void updateBrightnessAndReady(boolean force) {
+    public void updateBrightnessAndReady(boolean force) {
         if (force || mRegistered || mDebugBrightnessBucket != -1) {
             int sensorValue = mDebugBrightnessBucket == -1
                     ? mLastSensorValue : mDebugBrightnessBucket;
             int brightness = computeBrightness(sensorValue);
             boolean brightnessReady = brightness > 0;
             if (brightnessReady) {
-                mDozeService.setDozeScreenBrightness(clampToUserSetting(brightness));
+                mDozeService.setDozeScreenBrightness(
+                        clampToDimBrightnessForScreenOff(clampToUserSetting(brightness)));
             }
 
             int scrimOpacity = -1;
@@ -205,13 +221,21 @@
     /**
      * Clamp the brightness to the dim brightness value used by PowerManagerService just before the
      * device times out and goes to sleep, if we are sleeping from a timeout. This ensures that we
-     * don't raise the brightness back to the user setting before playing the screen off animation.
+     * don't raise the brightness back to the user setting before or during the screen off
+     * animation.
      */
     private int clampToDimBrightnessForScreenOff(int brightness) {
-        if (mDozeParameters.shouldControlUnlockedScreenOff()
+        if (mUnlockedScreenOffAnimationController.isScreenOffAnimationPlaying()
                 && mWakefulnessLifecycle.getLastSleepReason()
                 == PowerManager.GO_TO_SLEEP_REASON_TIMEOUT) {
-            return Math.min(mScreenBrightnessDim, brightness);
+            return Math.max(
+                    PowerManager.BRIGHTNESS_OFF,
+                    // Use the lower of either the dim brightness, or the current brightness reduced
+                    // by the minimum dim amount. This is the same logic used in
+                    // DisplayPowerController#updatePowerState to apply a minimum dim amount.
+                    Math.min(
+                            brightness - SCREEN_DIM_MINIMUM_REDUCTION_INT,
+                            mScreenBrightnessDim));
         } else {
             return brightness;
         }
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeScreenState.java b/packages/SystemUI/src/com/android/systemui/doze/DozeScreenState.java
index 8c50a16..038be48 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeScreenState.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeScreenState.java
@@ -26,6 +26,11 @@
 import android.util.Log;
 import android.view.Display;
 
+import androidx.annotation.Nullable;
+
+import com.android.keyguard.KeyguardUpdateMonitor;
+import com.android.systemui.biometrics.AuthController;
+import com.android.systemui.biometrics.UdfpsController;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.doze.dagger.DozeScope;
 import com.android.systemui.doze.dagger.WrappedService;
@@ -34,6 +39,7 @@
 import com.android.systemui.util.wakelock.WakeLock;
 
 import javax.inject.Inject;
+import javax.inject.Provider;
 
 /**
  * Controls the screen when dozing.
@@ -56,23 +62,64 @@
      */
     public static final int ENTER_DOZE_HIDE_WALLPAPER_DELAY = 2500;
 
+    /**
+     * Add an extra delay to the transition to DOZE when udfps is current activated before
+     * the display state transitions from ON => DOZE.
+     */
+    public static final int UDFPS_DISPLAY_STATE_DELAY = 1200;
+
     private final DozeMachine.Service mDozeService;
     private final Handler mHandler;
     private final Runnable mApplyPendingScreenState = this::applyPendingScreenState;
     private final DozeParameters mParameters;
     private final DozeHost mDozeHost;
+    private final AuthController mAuthController;
+    private final Provider<UdfpsController> mUdfpsControllerProvider;
+    @Nullable private UdfpsController mUdfpsController;
+    private final DozeLog mDozeLog;
+    private final DozeScreenBrightness mDozeScreenBrightness;
 
     private int mPendingScreenState = Display.STATE_UNKNOWN;
     private SettableWakeLock mWakeLock;
 
     @Inject
-    public DozeScreenState(@WrappedService DozeMachine.Service service, @Main Handler handler,
-            DozeHost host, DozeParameters parameters, WakeLock wakeLock) {
+    public DozeScreenState(
+            @WrappedService DozeMachine.Service service,
+            @Main Handler handler,
+            DozeHost host,
+            DozeParameters parameters,
+            WakeLock wakeLock,
+            AuthController authController,
+            Provider<UdfpsController> udfpsControllerProvider,
+            DozeLog dozeLog,
+            DozeScreenBrightness dozeScreenBrightness) {
         mDozeService = service;
         mHandler = handler;
         mParameters = parameters;
         mDozeHost = host;
         mWakeLock = new SettableWakeLock(wakeLock, TAG);
+        mAuthController = authController;
+        mUdfpsControllerProvider = udfpsControllerProvider;
+        mDozeLog = dozeLog;
+        mDozeScreenBrightness = dozeScreenBrightness;
+
+        updateUdfpsController();
+        if (mUdfpsController == null) {
+            mAuthController.addCallback(new AuthController.Callback() {
+                @Override
+                public void onAllAuthenticatorsRegistered() {
+                    updateUdfpsController();
+                }
+            });
+        }
+    }
+
+    private void updateUdfpsController() {
+        if (mAuthController.isUdfpsEnrolled(KeyguardUpdateMonitor.getCurrentUser())) {
+            mUdfpsController = mUdfpsControllerProvider.get();
+        } else {
+            mUdfpsController = null;
+        }
     }
 
     @Override
@@ -110,21 +157,28 @@
             mPendingScreenState = screenState;
 
             // Delay screen state transitions even longer while animations are running.
-            boolean shouldDelayTransition = newState == DOZE_AOD
+            boolean shouldDelayTransitionEnteringDoze = newState == DOZE_AOD
                     && mParameters.shouldControlScreenOff() && !turningOn;
 
-            if (shouldDelayTransition) {
+            // Delay screen state transition longer if UDFPS is actively authenticating a fp
+            boolean shouldDelayTransitionForUDFPS = newState == DOZE_AOD
+                    && mUdfpsController != null && mUdfpsController.isFingerDown();
+
+            if (shouldDelayTransitionEnteringDoze || shouldDelayTransitionForUDFPS) {
                 mWakeLock.setAcquired(true);
             }
 
             if (!messagePending) {
                 if (DEBUG) {
                     Log.d(TAG, "Display state changed to " + screenState + " delayed by "
-                            + (shouldDelayTransition ? ENTER_DOZE_DELAY : 1));
+                            + (shouldDelayTransitionEnteringDoze ? ENTER_DOZE_DELAY : 1));
                 }
 
-                if (shouldDelayTransition) {
+                if (shouldDelayTransitionEnteringDoze) {
                     mHandler.postDelayed(mApplyPendingScreenState, ENTER_DOZE_DELAY);
+                } else if (shouldDelayTransitionForUDFPS) {
+                    mDozeLog.traceDisplayStateDelayedByUdfps(mPendingScreenState);
+                    mHandler.postDelayed(mApplyPendingScreenState, UDFPS_DISPLAY_STATE_DELAY);
                 } else {
                     mHandler.post(mApplyPendingScreenState);
                 }
@@ -139,6 +193,12 @@
     }
 
     private void applyPendingScreenState() {
+        if (mUdfpsController != null && mUdfpsController.isFingerDown()) {
+            mDozeLog.traceDisplayStateDelayedByUdfps(mPendingScreenState);
+            mHandler.postDelayed(mApplyPendingScreenState, UDFPS_DISPLAY_STATE_DELAY);
+            return;
+        }
+
         applyScreenState(mPendingScreenState);
         mPendingScreenState = Display.STATE_UNKNOWN;
     }
@@ -147,6 +207,12 @@
         if (screenState != Display.STATE_UNKNOWN) {
             if (DEBUG) Log.d(TAG, "setDozeScreenState(" + screenState + ")");
             mDozeService.setDozeScreenState(screenState);
+            if (screenState == Display.STATE_DOZE) {
+                // If we're entering doze, update the doze screen brightness. We might have been
+                // clamping it to the dim brightness during the screen off animation, and we should
+                // now change it to the brightness we actually want according to the sensor.
+                mDozeScreenBrightness.updateBrightnessAndReady(false /* force */);
+            }
             mPendingScreenState = Display.STATE_UNKNOWN;
             mWakeLock.setAcquired(false);
         }
diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsImpl.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsImpl.java
index a641ad4..c4508e0 100644
--- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsImpl.java
@@ -97,7 +97,7 @@
                 int backgroundAlpha = (int) (ScrimController.BUSY_SCRIM_ALPHA * 255);
                 background.setAlpha(backgroundAlpha);
                 mBlurUtils.applyBlur(d.getWindow().getDecorView().getViewRootImpl(),
-                        mBlurUtils.blurRadiusOfRatio(1), backgroundAlpha == 255);
+                        (int) mBlurUtils.blurRadiusOfRatio(1), backgroundAlpha == 255);
             } else {
                 float backgroundAlpha = mContext.getResources().getFloat(
                         com.android.systemui.R.dimen.shutdown_scrim_behind_alpha);
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index ee3d40e..1a8af3b 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -818,6 +818,7 @@
 
     private final KeyguardStateController mKeyguardStateController;
     private final Lazy<KeyguardUnlockAnimationController> mKeyguardUnlockAnimationControllerLazy;
+    private boolean mWallpaperSupportsAmbientMode;
 
     /**
      * Injected constructor. See {@link KeyguardModule}.
@@ -2089,13 +2090,14 @@
 
             int flags = 0;
             if (mKeyguardViewControllerLazy.get().shouldDisableWindowAnimationsForUnlock()
-                    || (mWakeAndUnlocking && !mPulsing)
-                    || isAnimatingBetweenKeyguardAndSurfaceBehindOrWillBe()) {
+                    || mWakeAndUnlocking && !mWallpaperSupportsAmbientMode) {
                 flags |= WindowManagerPolicyConstants
                         .KEYGUARD_GOING_AWAY_FLAG_NO_WINDOW_ANIMATIONS;
             }
             if (mKeyguardViewControllerLazy.get().isGoingToNotificationShade()
-                    || (mWakeAndUnlocking && mPulsing)) {
+                    || mWakeAndUnlocking && mWallpaperSupportsAmbientMode) {
+                // When the wallpaper supports ambient mode, the scrim isn't fully opaque during
+                // wake and unlock and we should fade in the app on top of the wallpaper
                 flags |= WindowManagerPolicyConstants.KEYGUARD_GOING_AWAY_FLAG_TO_SHADE;
             }
             if (mKeyguardViewControllerLazy.get().isUnlockWithWallpaper()) {
@@ -2784,6 +2786,15 @@
         mPulsing = pulsing;
     }
 
+    /**
+     * Set if the wallpaper supports ambient mode. This is used to trigger the right animation.
+     * In case it does support it, we have to fade in the incoming app, otherwise we'll reveal it
+     * with the light reveal scrim.
+     */
+    public void setWallpaperSupportsAmbientMode(boolean supportsAmbientMode) {
+        mWallpaperSupportsAmbientMode = supportsAmbientMode;
+    }
+
     private static class StartKeyguardExitAnimParams {
 
         @WindowManager.TransitionOldType int mTransit;
diff --git a/packages/SystemUI/src/com/android/systemui/media/NotificationPlayer.java b/packages/SystemUI/src/com/android/systemui/media/NotificationPlayer.java
index 89786ee..a617850 100644
--- a/packages/SystemUI/src/com/android/systemui/media/NotificationPlayer.java
+++ b/packages/SystemUI/src/com/android/systemui/media/NotificationPlayer.java
@@ -139,7 +139,7 @@
                                 + " with ducking", e);
                     }
                     player.start();
-                    if (DEBUG) { Log.d(mTag, "player.start"); }
+                    if (DEBUG) { Log.d(mTag, "player.start piid:" + player.getPlayerIId()); }
                 } catch (Exception e) {
                     if (player != null) {
                         player.release();
@@ -155,7 +155,13 @@
                     mPlayer = player;
                 }
                 if (mp != null) {
-                    if (DEBUG) { Log.d(mTag, "mPlayer.release"); }
+                    if (DEBUG) {
+                        Log.d(mTag, "mPlayer.pause+release piid:" + player.getPlayerIId());
+                    }
+                    mp.pause();
+                    try {
+                        Thread.sleep(100);
+                    } catch (InterruptedException ie) { }
                     mp.release();
                 }
                 this.notify();
@@ -244,6 +250,10 @@
                         try {
                             mp.stop();
                         } catch (Exception e) { }
+                        if (DEBUG) {
+                            Log.i(mTag, "About to release MediaPlayer piid:"
+                                    + mp.getPlayerIId() + " due to notif cancelled");
+                        }
                         mp.release();
                         synchronized(mQueueAudioFocusLock) {
                             if (mAudioManagerWithAudioFocus != null) {
@@ -284,7 +294,7 @@
     public void onCompletion(MediaPlayer mp) {
         synchronized(mQueueAudioFocusLock) {
             if (mAudioManagerWithAudioFocus != null) {
-                if (DEBUG) Log.d(mTag, "onCompletion() abandonning AudioFocus");
+                if (DEBUG) Log.d(mTag, "onCompletion() abandoning AudioFocus");
                 mAudioManagerWithAudioFocus.abandonAudioFocus(null);
                 mAudioManagerWithAudioFocus = null;
             } else {
@@ -310,6 +320,10 @@
             }
         }
         if (mp != null) {
+            if (DEBUG) {
+                Log.i("NotificationPlayer", "About to release MediaPlayer piid:"
+                        + mp.getPlayerIId() + " due to onCompletion");
+            }
             mp.release();
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java b/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java
index 541ee2c..4a75810 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java
@@ -51,6 +51,7 @@
 import com.android.systemui.qs.logging.QSLogger;
 import com.android.systemui.settings.UserTracker;
 import com.android.systemui.shared.plugins.PluginManager;
+import com.android.systemui.statusbar.FeatureFlags;
 import com.android.systemui.statusbar.phone.AutoTileManager;
 import com.android.systemui.statusbar.phone.StatusBar;
 import com.android.systemui.statusbar.phone.StatusBarIconController;
@@ -95,6 +96,7 @@
     private final UiEventLogger mUiEventLogger;
     private final InstanceIdSequence mInstanceIdSequence;
     private final CustomTileStatePersister mCustomTileStatePersister;
+    private final FeatureFlags mFeatureFlags;
 
     private final List<Callback> mCallbacks = new ArrayList<>();
     private AutoTileManager mAutoTiles;
@@ -122,7 +124,8 @@
             UiEventLogger uiEventLogger,
             UserTracker userTracker,
             SecureSettings secureSettings,
-            CustomTileStatePersister customTileStatePersister
+            CustomTileStatePersister customTileStatePersister,
+            FeatureFlags featureFlags
     ) {
         mIconController = iconController;
         mContext = context;
@@ -144,6 +147,7 @@
         mUserTracker = userTracker;
         mSecureSettings = secureSettings;
         mCustomTileStatePersister = customTileStatePersister;
+        mFeatureFlags = featureFlags;
 
         mainHandler.post(() -> {
             // This is technically a hack to avoid circular dependency of
@@ -265,7 +269,7 @@
         if (newValue == null && UserManager.isDeviceInDemoMode(mContext)) {
             newValue = mContext.getResources().getString(R.string.quick_settings_tiles_retail_mode);
         }
-        final List<String> tileSpecs = loadTileSpecs(mContext, newValue);
+        final List<String> tileSpecs = loadTileSpecs(mContext, newValue, mFeatureFlags);
         int currentUser = mUserTracker.getUserId();
         if (currentUser != mCurrentUser) {
             mUserContext = mUserTracker.getUserContext();
@@ -334,7 +338,7 @@
         if (newTiles.isEmpty() && !tileSpecs.isEmpty()) {
             // If we didn't manage to create any tiles, set it to empty (default)
             Log.d(TAG, "No valid tiles on tuning changed. Setting to default.");
-            changeTiles(currentSpecs, loadTileSpecs(mContext, ""));
+            changeTiles(currentSpecs, loadTileSpecs(mContext, "", mFeatureFlags));
         } else {
             for (int i = 0; i < mCallbacks.size(); i++) {
                 mCallbacks.get(i).onTilesChanged();
@@ -389,7 +393,7 @@
 
     private void changeTileSpecs(Predicate<List<String>> changeFunction) {
         final String setting = mSecureSettings.getStringForUser(TILES_SETTING, mCurrentUser);
-        final List<String> tileSpecs = loadTileSpecs(mContext, setting);
+        final List<String> tileSpecs = loadTileSpecs(mContext, setting, mFeatureFlags);
         if (changeFunction.test(tileSpecs)) {
             saveTilesToSettings(tileSpecs);
         }
@@ -478,7 +482,8 @@
         throw new RuntimeException("Default factory didn't create view for " + tile.getTileSpec());
     }
 
-    protected static List<String> loadTileSpecs(Context context, String tileList) {
+    protected static List<String> loadTileSpecs(
+            Context context, String tileList, FeatureFlags featureFlags) {
         final Resources res = context.getResources();
 
         if (TextUtils.isEmpty(tileList)) {
@@ -511,6 +516,21 @@
                 }
             }
         }
+        if (featureFlags.isProviderModelSettingEnabled()) {
+            if (!tiles.contains("internet")) {
+                if (tiles.contains("wifi")) {
+                    // Replace the WiFi with Internet, and remove the Cell
+                    tiles.set(tiles.indexOf("wifi"), "internet");
+                    tiles.remove("cell");
+                } else if (tiles.contains("cell")) {
+                    // Replace the Cell with Internet
+                    tiles.set(tiles.indexOf("cell"), "internet");
+                }
+            } else {
+                tiles.remove("wifi");
+                tiles.remove("cell");
+            }
+        }
         return tiles;
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/customize/TileQueryHelper.java b/packages/SystemUI/src/com/android/systemui/qs/customize/TileQueryHelper.java
index 3c2f35b..f2832b3 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/customize/TileQueryHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/customize/TileQueryHelper.java
@@ -41,6 +41,7 @@
 import com.android.systemui.qs.external.CustomTile;
 import com.android.systemui.qs.tileimpl.QSTileImpl.DrawableIcon;
 import com.android.systemui.settings.UserTracker;
+import com.android.systemui.statusbar.FeatureFlags;
 import com.android.systemui.util.leak.GarbageMonitor;
 
 import java.util.ArrayList;
@@ -62,6 +63,7 @@
     private final Executor mBgExecutor;
     private final Context mContext;
     private final UserTracker mUserTracker;
+    private final FeatureFlags mFeatureFlags;
     private TileStateListener mListener;
 
     private boolean mFinished;
@@ -71,12 +73,14 @@
             Context context,
             UserTracker userTracker,
             @Main Executor mainExecutor,
-            @Background Executor bgExecutor
+            @Background Executor bgExecutor,
+            FeatureFlags featureFlags
     ) {
         mContext = context;
         mMainExecutor = mainExecutor;
         mBgExecutor = bgExecutor;
         mUserTracker = userTracker;
+        mFeatureFlags = featureFlags;
     }
 
     public void setListener(TileStateListener listener) {
@@ -117,6 +121,10 @@
         }
 
         final ArrayList<QSTile> tilesToAdd = new ArrayList<>();
+        if (mFeatureFlags.isProviderModelSettingEnabled()) {
+            possibleTiles.remove("cell");
+            possibleTiles.remove("wifi");
+        }
 
         for (String spec : possibleTiles) {
             // Only add current and stock tiles that can be created from QSFactoryImpl.
diff --git a/packages/SystemUI/src/com/android/systemui/sensorprivacy/SensorUseDialog.kt b/packages/SystemUI/src/com/android/systemui/sensorprivacy/SensorUseDialog.kt
new file mode 100644
index 0000000..c50365f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/sensorprivacy/SensorUseDialog.kt
@@ -0,0 +1,74 @@
+package com.android.systemui.sensorprivacy
+
+import android.content.Context
+import android.content.DialogInterface
+import android.content.res.Resources
+import android.text.Html
+import android.view.LayoutInflater
+import android.view.View
+import android.view.WindowManager
+import android.widget.ImageView
+import com.android.internal.widget.DialogTitle
+import com.android.systemui.R
+import com.android.systemui.statusbar.phone.SystemUIDialog
+
+class SensorUseDialog(
+    context: Context,
+    val sensor: Int,
+    val clickListener: DialogInterface.OnClickListener
+) : SystemUIDialog(context) {
+
+    // TODO move to onCreate (b/200815309)
+    init {
+        window!!.addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED)
+        window!!.addSystemFlags(
+                WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS)
+
+        val layoutInflater = LayoutInflater.from(context)
+        val customTitleView = layoutInflater.inflate(R.layout.sensor_use_started_title, null)
+        customTitleView.requireViewById<DialogTitle>(R.id.sensor_use_started_title_message)
+                .setText(when (sensor) {
+                    SensorUseStartedActivity.MICROPHONE ->
+                        R.string.sensor_privacy_start_use_mic_dialog_title
+                    SensorUseStartedActivity.CAMERA ->
+                        R.string.sensor_privacy_start_use_camera_dialog_title
+                    SensorUseStartedActivity.ALL_SENSORS ->
+                        R.string.sensor_privacy_start_use_mic_camera_dialog_title
+                    else -> Resources.ID_NULL
+                })
+        customTitleView.requireViewById<ImageView>(R.id.sensor_use_microphone_icon).visibility =
+                if (sensor == SensorUseStartedActivity.MICROPHONE ||
+                        sensor == SensorUseStartedActivity.ALL_SENSORS) {
+                    View.VISIBLE
+                } else {
+                    View.GONE
+                }
+        customTitleView.requireViewById<ImageView>(R.id.sensor_use_camera_icon).visibility =
+                if (sensor == SensorUseStartedActivity.CAMERA ||
+                        sensor == SensorUseStartedActivity.ALL_SENSORS) {
+                    View.VISIBLE
+                } else {
+                    View.GONE
+                }
+
+        setCustomTitle(customTitleView)
+        setMessage(Html.fromHtml(context.getString(when (sensor) {
+            SensorUseStartedActivity.MICROPHONE ->
+                R.string.sensor_privacy_start_use_mic_dialog_content
+            SensorUseStartedActivity.CAMERA ->
+                R.string.sensor_privacy_start_use_camera_dialog_content
+            SensorUseStartedActivity.ALL_SENSORS ->
+                R.string.sensor_privacy_start_use_mic_camera_dialog_content
+            else -> Resources.ID_NULL
+        }), 0))
+
+        setButton(BUTTON_POSITIVE,
+                context.getString(com.android.internal.R.string
+                        .sensor_privacy_start_use_dialog_turn_on_button), clickListener)
+        setButton(BUTTON_NEGATIVE,
+                context.getString(com.android.internal.R.string
+                        .cancel), clickListener)
+
+        setCancelable(false)
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/sensorprivacy/SensorUseStartedActivity.kt b/packages/SystemUI/src/com/android/systemui/sensorprivacy/SensorUseStartedActivity.kt
index f0fb5eb..b0071d9 100644
--- a/packages/SystemUI/src/com/android/systemui/sensorprivacy/SensorUseStartedActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/sensorprivacy/SensorUseStartedActivity.kt
@@ -16,33 +16,28 @@
 
 package com.android.systemui.sensorprivacy
 
+import android.app.Activity
+import android.app.AlertDialog
 import android.content.DialogInterface
+import android.content.DialogInterface.BUTTON_NEGATIVE
+import android.content.DialogInterface.BUTTON_POSITIVE
 import android.content.Intent
 import android.content.Intent.EXTRA_PACKAGE_NAME
-import android.content.pm.PackageManager
-import android.content.res.Resources
 import android.hardware.SensorPrivacyManager
 import android.hardware.SensorPrivacyManager.EXTRA_ALL_SENSORS
 import android.hardware.SensorPrivacyManager.EXTRA_SENSOR
 import android.hardware.SensorPrivacyManager.Sources.DIALOG
 import android.os.Bundle
 import android.os.Handler
-import android.text.Html
-import android.view.View.GONE
-import android.view.View.VISIBLE
-import android.widget.ImageView
-import com.android.internal.app.AlertActivity
-import com.android.internal.widget.DialogTitle
-import com.android.systemui.R
+import com.android.internal.util.FrameworkStatsLog.PRIVACY_TOGGLE_DIALOG_INTERACTION
+import com.android.internal.util.FrameworkStatsLog.PRIVACY_TOGGLE_DIALOG_INTERACTION__ACTION__CANCEL
+import com.android.internal.util.FrameworkStatsLog.PRIVACY_TOGGLE_DIALOG_INTERACTION__ACTION__ENABLE
+import com.android.internal.util.FrameworkStatsLog.write
 import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.statusbar.phone.KeyguardDismissUtil
 import com.android.systemui.statusbar.policy.IndividualSensorPrivacyController
 import com.android.systemui.statusbar.policy.KeyguardStateController
 import javax.inject.Inject
-import com.android.internal.util.FrameworkStatsLog.PRIVACY_TOGGLE_DIALOG_INTERACTION
-import com.android.internal.util.FrameworkStatsLog.PRIVACY_TOGGLE_DIALOG_INTERACTION__ACTION__ENABLE
-import com.android.internal.util.FrameworkStatsLog.PRIVACY_TOGGLE_DIALOG_INTERACTION__ACTION__CANCEL
-import com.android.internal.util.FrameworkStatsLog.write
 
 /**
  * Dialog to be shown on top of apps that are attempting to use a sensor (e.g. microphone) which is
@@ -55,7 +50,7 @@
     private val keyguardStateController: KeyguardStateController,
     private val keyguardDismissUtil: KeyguardDismissUtil,
     @Background private val bgHandler: Handler
-) : AlertActivity(), DialogInterface.OnClickListener {
+) : Activity(), DialogInterface.OnClickListener {
 
     companion object {
         private val LOG_TAG = SensorUseStartedActivity::class.java.simpleName
@@ -63,9 +58,9 @@
         private const val SUPPRESS_REMINDERS_REMOVAL_DELAY_MILLIS = 2000L
         private const val UNLOCK_DELAY_MILLIS = 200L
 
-        private const val CAMERA = SensorPrivacyManager.Sensors.CAMERA
-        private const val MICROPHONE = SensorPrivacyManager.Sensors.MICROPHONE
-        private const val ALL_SENSORS = Integer.MAX_VALUE
+        internal const val CAMERA = SensorPrivacyManager.Sensors.CAMERA
+        internal const val MICROPHONE = SensorPrivacyManager.Sensors.MICROPHONE
+        internal const val ALL_SENSORS = Integer.MAX_VALUE
     }
 
     private var sensor = -1
@@ -74,6 +69,8 @@
 
     private lateinit var sensorPrivacyListener: IndividualSensorPrivacyController.Callback
 
+    private var mDialog: AlertDialog? = null
+
     override fun onCreate(savedInstanceState: Bundle?) {
         super.onCreate(savedInstanceState)
 
@@ -91,7 +88,7 @@
                     IndividualSensorPrivacyController.Callback { _, _ ->
                         if (!sensorPrivacyController.isSensorBlocked(MICROPHONE) &&
                                 !sensorPrivacyController.isSensorBlocked(CAMERA)) {
-                            dismiss()
+                            finish()
                         }
                     }
 
@@ -109,71 +106,22 @@
                 }
             }
             sensorPrivacyListener =
-                    IndividualSensorPrivacyController.Callback {
-                        whichSensor: Int, isBlocked: Boolean ->
+                    IndividualSensorPrivacyController.Callback { whichSensor: Int,
+                                                                 isBlocked: Boolean ->
                         if (whichSensor == sensor && !isBlocked) {
-                            dismiss()
+                            finish()
                         }
                     }
             sensorPrivacyController.addCallback(sensorPrivacyListener)
 
-            sensorPrivacyController.addCallback { _, isBlocked ->
-                if (!isBlocked) {
-                    dismiss()
-                }
-            }
-        }
-
-        mAlertParams.apply {
-            try {
-                mCustomTitleView = mInflater.inflate(R.layout.sensor_use_started_title, null)
-                mCustomTitleView.findViewById<DialogTitle>(R.id.sensor_use_started_title_message)!!
-                        .setText(when (sensor) {
-                            MICROPHONE ->
-                                R.string.sensor_privacy_start_use_mic_dialog_title
-                            CAMERA ->
-                                R.string.sensor_privacy_start_use_camera_dialog_title
-                            ALL_SENSORS ->
-                                R.string.sensor_privacy_start_use_mic_camera_dialog_title
-                            else -> Resources.ID_NULL
-                        })
-
-                mCustomTitleView.findViewById<ImageView>(R.id.sensor_use_microphone_icon)!!
-                        .visibility = if (sensor == MICROPHONE || sensor == ALL_SENSORS) {
-                    VISIBLE
-                } else {
-                    GONE
-                }
-                mCustomTitleView.findViewById<ImageView>(R.id.sensor_use_camera_icon)!!
-                        .visibility = if (sensor == CAMERA || sensor == ALL_SENSORS) {
-                    VISIBLE
-                } else {
-                    GONE
-                }
-
-                mMessage = Html.fromHtml(getString(when (sensor) {
-                    MICROPHONE ->
-                        R.string.sensor_privacy_start_use_mic_dialog_content
-                    CAMERA ->
-                        R.string.sensor_privacy_start_use_camera_dialog_content
-                    ALL_SENSORS ->
-                        R.string.sensor_privacy_start_use_mic_camera_dialog_content
-                    else -> Resources.ID_NULL
-                }, packageManager.getApplicationInfo(sensorUsePackageName, 0)
-                        .loadLabel(packageManager)), 0)
-            } catch (e: PackageManager.NameNotFoundException) {
+            if (!sensorPrivacyController.isSensorBlocked(sensor)) {
                 finish()
                 return
             }
-
-            mPositiveButtonText = getString(
-                    com.android.internal.R.string.sensor_privacy_start_use_dialog_turn_on_button)
-            mNegativeButtonText = getString(android.R.string.cancel)
-            mPositiveButtonListener = this@SensorUseStartedActivity
-            mNegativeButtonListener = this@SensorUseStartedActivity
         }
 
-        setupAlert()
+        mDialog = SensorUseDialog(this, sensor, this)
+        mDialog!!.show()
     }
 
     override fun onStart() {
@@ -212,7 +160,7 @@
             }
         }
 
-        dismiss()
+        finish()
     }
 
     override fun onStop() {
@@ -229,6 +177,7 @@
 
     override fun onDestroy() {
         super.onDestroy()
+        mDialog?.dismiss()
         sensorPrivacyController.removeCallback(sensorPrivacyListener)
     }
 
@@ -263,4 +212,4 @@
                     .suppressSensorPrivacyReminders(sensor, suppressed)
         }
     }
-}
\ No newline at end of file
+}
diff --git a/packages/SystemUI/src/com/android/systemui/sensorprivacy/television/TvUnblockSensorActivity.java b/packages/SystemUI/src/com/android/systemui/sensorprivacy/television/TvUnblockSensorActivity.java
index 8cd3632..cc5cf4b 100644
--- a/packages/SystemUI/src/com/android/systemui/sensorprivacy/television/TvUnblockSensorActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/sensorprivacy/television/TvUnblockSensorActivity.java
@@ -24,6 +24,7 @@
 import android.os.Bundle;
 import android.util.Log;
 import android.view.View;
+import android.view.WindowManager;
 import android.widget.Button;
 import android.widget.ImageView;
 import android.widget.TextView;
@@ -57,6 +58,8 @@
     @Override
     public void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
+        getWindow().addSystemFlags(
+                WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS);
 
         boolean allSensors = getIntent().getBooleanExtra(SensorPrivacyManager.EXTRA_ALL_SENSORS,
                 false);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/BlurUtils.kt b/packages/SystemUI/src/com/android/systemui/statusbar/BlurUtils.kt
index dce19cf..cfbe3b2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/BlurUtils.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/BlurUtils.kt
@@ -54,22 +54,22 @@
     /**
      * Translates a ratio from 0 to 1 to a blur radius in pixels.
      */
-    fun blurRadiusOfRatio(ratio: Float): Int {
+    fun blurRadiusOfRatio(ratio: Float): Float {
         if (ratio == 0f) {
-            return 0
+            return 0f
         }
-        return MathUtils.lerp(minBlurRadius.toFloat(), maxBlurRadius.toFloat(), ratio).toInt()
+        return MathUtils.lerp(minBlurRadius.toFloat(), maxBlurRadius.toFloat(), ratio)
     }
 
     /**
      * Translates a blur radius in pixels to a ratio between 0 to 1.
      */
-    fun ratioOfBlurRadius(blur: Int): Float {
-        if (blur == 0) {
+    fun ratioOfBlurRadius(blur: Float): Float {
+        if (blur == 0f) {
             return 0f
         }
         return MathUtils.map(minBlurRadius.toFloat(), maxBlurRadius.toFloat(),
-                0f /* maxStart */, 1f /* maxStop */, blur.toFloat())
+                0f /* maxStart */, 1f /* maxStop */, blur)
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
index 503b5c0..1c93350 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
@@ -811,13 +811,8 @@
                 mStatusBarKeyguardViewManager.showBouncerMessage(message, mInitialTextColorState);
             }
         } else if (mKeyguardUpdateMonitor.isScreenOn()) {
-            if (mKeyguardUpdateMonitor.isUdfpsAvailable()) {
-                showTransientIndication(mContext.getString(R.string.keyguard_unlock_press),
-                        false /* isError */, true /* hideOnScreenOff */);
-            } else {
-                showTransientIndication(mContext.getString(R.string.keyguard_unlock),
-                        false /* isError */, true /* hideOnScreenOff */);
-            }
+            showTransientIndication(mContext.getString(R.string.keyguard_unlock),
+                    false /* isError */, true /* hideOnScreenOff */);
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt
index 002c9c7..b833427 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt
@@ -68,7 +68,7 @@
         private const val VELOCITY_SCALE = 100f
         private const val MAX_VELOCITY = 3000f
         private const val MIN_VELOCITY = -MAX_VELOCITY
-        private const val INTERACTION_BLUR_FRACTION = 0.4f
+        private const val INTERACTION_BLUR_FRACTION = 0.8f
         private const val ANIMATION_BLUR_FRACTION = 1f - INTERACTION_BLUR_FRACTION
         private const val TAG = "DepthController"
     }
@@ -92,8 +92,6 @@
     // Only for dumpsys
     private var lastAppliedBlur = 0
 
-    @VisibleForTesting
-    var shadeSpring = DepthAnimation()
     var shadeAnimation = DepthAnimation()
 
     @VisibleForTesting
@@ -101,12 +99,16 @@
     var brightnessMirrorVisible: Boolean = false
         set(value) {
             field = value
-            brightnessMirrorSpring.animateTo(if (value) blurUtils.blurRadiusOfRatio(1f)
+            brightnessMirrorSpring.animateTo(if (value) blurUtils.blurRadiusOfRatio(1f).toInt()
                 else 0)
         }
 
     var qsPanelExpansion = 0f
         set(value) {
+            if (value.isNaN()) {
+                Log.w(TAG, "Invalid qs expansion")
+                return
+            }
             if (field == value) return
             field = value
             scheduleUpdate()
@@ -134,15 +136,13 @@
             field = value
             scheduleUpdate()
 
-            if (shadeSpring.radius == 0 && shadeAnimation.radius == 0) {
+            if (shadeExpansion == 0f && shadeAnimation.radius == 0f) {
                 return
             }
             // Do not remove blurs when we're re-enabling them
             if (!value) {
                 return
             }
-            shadeSpring.animateTo(0)
-            shadeSpring.finishIfRunning()
 
             shadeAnimation.animateTo(0)
             shadeAnimation.finishIfRunning()
@@ -161,7 +161,7 @@
     /**
      * Blur radius of the wake-up animation on this frame.
      */
-    private var wakeAndUnlockBlurRadius = 0
+    private var wakeAndUnlockBlurRadius = 0f
         set(value) {
             if (field == value) return
             field = value
@@ -174,26 +174,30 @@
     @VisibleForTesting
     val updateBlurCallback = Choreographer.FrameCallback {
         updateScheduled = false
-        val normalizedBlurRadius = MathUtils.constrain(shadeAnimation.radius,
-                blurUtils.minBlurRadius, blurUtils.maxBlurRadius)
-        var combinedBlur = (shadeSpring.radius * INTERACTION_BLUR_FRACTION +
-                normalizedBlurRadius * ANIMATION_BLUR_FRACTION).toInt()
+        val animationRadius = MathUtils.constrain(shadeAnimation.radius,
+                blurUtils.minBlurRadius.toFloat(), blurUtils.maxBlurRadius.toFloat())
+        val expansionRadius = blurUtils.blurRadiusOfRatio(
+                Interpolators.getNotificationScrimAlpha(
+                        if (shouldApplyShadeBlur()) shadeExpansion else 0f, false))
+        var combinedBlur = (expansionRadius * INTERACTION_BLUR_FRACTION +
+                animationRadius * ANIMATION_BLUR_FRACTION)
         val qsExpandedRatio = qsPanelExpansion * shadeExpansion
         combinedBlur = max(combinedBlur, blurUtils.blurRadiusOfRatio(qsExpandedRatio))
         combinedBlur = max(combinedBlur, blurUtils.blurRadiusOfRatio(transitionToFullShadeProgress))
-        var shadeRadius = max(combinedBlur, wakeAndUnlockBlurRadius).toFloat()
+        var shadeRadius = max(combinedBlur, wakeAndUnlockBlurRadius)
 
         if (blursDisabledForAppLaunch) {
             shadeRadius = 0f
         }
 
+        var zoomOut = MathUtils.saturate(blurUtils.ratioOfBlurRadius(shadeRadius))
         var blur = shadeRadius.toInt()
 
         // Make blur be 0 if it is necessary to stop blur effect.
         if (scrimsVisible) {
             blur = 0
+            zoomOut = 0f
         }
-        val zoomOut = blurUtils.ratioOfBlurRadius(blur)
 
         if (!blurUtils.supportsBlursOnWindows()) {
             blur = 0
@@ -266,12 +270,11 @@
         override fun onStateChanged(newState: Int) {
             updateShadeAnimationBlur(
                     shadeExpansion, prevTracking, prevShadeVelocity, prevShadeDirection)
-            updateShadeBlur()
+            scheduleUpdate()
         }
 
         override fun onDozingChanged(isDozing: Boolean) {
             if (isDozing) {
-                shadeSpring.finishIfRunning()
                 shadeAnimation.finishIfRunning()
                 brightnessMirrorSpring.finishIfRunning()
             }
@@ -336,7 +339,7 @@
         prevTracking = tracking
         prevTimestamp = timestamp
 
-        updateShadeBlur()
+        scheduleUpdate()
     }
 
     private fun updateShadeAnimationBlur(
@@ -399,15 +402,7 @@
         }
 
         shadeAnimation.setStartVelocity(velocity)
-        shadeAnimation.animateTo(blurUtils.blurRadiusOfRatio(targetBlurNormalized))
-    }
-
-    private fun updateShadeBlur() {
-        var newBlur = 0
-        if (shouldApplyShadeBlur()) {
-            newBlur = blurUtils.blurRadiusOfRatio(shadeExpansion)
-        }
-        shadeSpring.animateTo(newBlur)
+        shadeAnimation.animateTo(blurUtils.blurRadiusOfRatio(targetBlurNormalized).toInt())
     }
 
     private fun scheduleUpdate(viewToBlur: View? = null) {
@@ -433,7 +428,8 @@
         IndentingPrintWriter(pw, "  ").let {
             it.println("StatusBarWindowBlurController:")
             it.increaseIndent()
-            it.println("shadeRadius: ${shadeSpring.radius}")
+            it.println("shadeExpansion: $shadeExpansion")
+            it.println("shouldApplyShaeBlur: ${shouldApplyShadeBlur()}")
             it.println("shadeAnimation: ${shadeAnimation.radius}")
             it.println("brightnessMirrorRadius: ${brightnessMirrorSpring.radius}")
             it.println("wakeAndUnlockBlur: $wakeAndUnlockBlurRadius")
@@ -452,7 +448,7 @@
         /**
          * Blur radius visible on the UI, in pixels.
          */
-        var radius = 0
+        var radius = 0f
 
         /**
          * Depth ratio of the current blur radius.
@@ -473,12 +469,12 @@
         private var springAnimation = SpringAnimation(this, object :
                 FloatPropertyCompat<DepthAnimation>("blurRadius") {
             override fun setValue(rect: DepthAnimation?, value: Float) {
-                radius = value.toInt()
+                radius = value
                 scheduleUpdate(view)
             }
 
             override fun getValue(rect: DepthAnimation?): Float {
-                return radius.toFloat()
+                return radius
             }
         })
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/charging/RippleShader.kt b/packages/SystemUI/src/com/android/systemui/statusbar/charging/RippleShader.kt
index 146046b..5175977 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/charging/RippleShader.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/charging/RippleShader.kt
@@ -147,8 +147,12 @@
 
             val fadeIn = subProgress(0f, 0.1f, value)
             val fadeOutNoise = subProgress(0.4f, 1f, value)
-            val fadeOutRipple = subProgress(0.3f, 1f, value)
-            val fadeCircle = subProgress(0f, 0.2f, value)
+            var fadeOutRipple = 0f
+            var fadeCircle = 0f
+            if (shouldFadeOutRipple) {
+                fadeCircle = subProgress(0f, 0.2f, value)
+                fadeOutRipple = subProgress(0.3f, 1f, value)
+            }
             setUniform("in_fadeSparkle", Math.min(fadeIn, 1 - fadeOutNoise))
             setUniform("in_fadeCircle", 1 - fadeCircle)
             setUniform("in_fadeRing", Math.min(fadeIn, 1 - fadeOutRipple))
@@ -200,4 +204,6 @@
             field = value
             setUniform("in_pixelDensity", value)
         }
+
+    var shouldFadeOutRipple: Boolean = true
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/FooterView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/FooterView.java
index 86c90c7..9eb95c4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/FooterView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/FooterView.java
@@ -16,7 +16,6 @@
 
 package com.android.systemui.statusbar.notification.row;
 
-import android.annotation.ColorInt;
 import android.content.Context;
 import android.content.res.Configuration;
 import android.content.res.Resources;
@@ -28,15 +27,12 @@
 import com.android.systemui.statusbar.notification.stack.ViewState;
 
 public class FooterView extends StackScrollerDecorView {
-    private final int mClearAllTopPadding;
     private FooterViewButton mDismissButton;
     private FooterViewButton mManageButton;
     private boolean mShowHistory;
 
     public FooterView(Context context, AttributeSet attrs) {
         super(context, attrs);
-        mClearAllTopPadding = context.getResources().getDimensionPixelSize(
-                R.dimen.clear_all_padding_top);
     }
 
     @Override
@@ -55,11 +51,6 @@
         mManageButton = findViewById(R.id.manage_text);
     }
 
-    public void setTextColor(@ColorInt int color) {
-        mManageButton.setTextColor(color);
-        mDismissButton.setTextColor(color);
-    }
-
     public void setManageButtonClickListener(OnClickListener listener) {
         mManageButton.setOnClickListener(listener);
     }
@@ -95,21 +86,25 @@
     @Override
     protected void onConfigurationChanged(Configuration newConfig) {
         super.onConfigurationChanged(newConfig);
-        int textColor = getResources().getColor(R.color.notif_pill_text);
-        Resources.Theme theme = getContext().getTheme();
-        mDismissButton.setBackground(
-                getResources().getDrawable(R.drawable.notif_footer_btn_background, theme));
-        mDismissButton.setTextColor(textColor);
-        mManageButton.setBackground(
-                getResources().getDrawable(R.drawable.notif_footer_btn_background, theme));
-        mManageButton = findViewById(R.id.manage_text);
+        updateColors();
         mDismissButton.setText(R.string.clear_all_notifications_text);
-        mManageButton.setTextColor(textColor);
         mDismissButton.setContentDescription(
                 mContext.getString(R.string.accessibility_clear_all));
         showHistory(mShowHistory);
     }
 
+    /**
+     * Update the text and background colors for the current color palette and night mode setting.
+     */
+    public void updateColors() {
+        Resources.Theme theme = mContext.getTheme();
+        int textColor = getResources().getColor(R.color.notif_pill_text, theme);
+        mDismissButton.setBackground(theme.getDrawable(R.drawable.notif_footer_btn_background));
+        mDismissButton.setTextColor(textColor);
+        mManageButton.setBackground(theme.getDrawable(R.drawable.notif_footer_btn_background));
+        mManageButton.setTextColor(textColor);
+    }
+
     @Override
     public ExpandableViewState createExpandableViewState() {
         return new FooterViewState();
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 289c32f..ae85205 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
@@ -256,6 +256,7 @@
     private boolean mExpandedInThisMotion;
     private boolean mShouldShowShelfOnly;
     protected boolean mScrollingEnabled;
+    private boolean mIsCurrentUserSetup;
     protected FooterView mFooterView;
     protected EmptyShadeView mEmptyShadeView;
     private boolean mDismissAllInProgress;
@@ -683,6 +684,7 @@
                 mController.hasActiveClearableNotifications(ROWS_ALL);
         RemoteInputController remoteInputController = mRemoteInputManager.getController();
         boolean showFooterView = (showDismissView || getVisibleNotificationCount() > 0)
+                && mIsCurrentUserSetup  // see: b/193149550
                 && mStatusBarState != StatusBarState.KEYGUARD
                 && !mUnlockedScreenOffAnimationController.isScreenOffAnimationPlaying()
                 && (remoteInputController == null || !remoteInputController.isRemoteInputActive());
@@ -4231,7 +4233,7 @@
         final @ColorInt int textColor =
                 Utils.getColorAttrDefaultColor(mContext, android.R.attr.textColorPrimary);
         mSectionsManager.setHeaderForegroundColor(textColor);
-        mFooterView.setTextColor(textColor);
+        mFooterView.updateColors();
         mEmptyShadeView.setTextColor(textColor);
     }
 
@@ -5567,6 +5569,16 @@
     }
 
     /**
+     * Sets whether the current user is set up, which is required to show the footer (b/193149550)
+     */
+    public void setCurrentUserSetup(boolean isCurrentUserSetup) {
+        if (mIsCurrentUserSetup != isCurrentUserSetup) {
+            mIsCurrentUserSetup = isCurrentUserSetup;
+            updateFooter();
+        }
+    }
+
+    /**
      * A listener that is notified when the empty space below the notifications is clicked on
      */
     @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
index 9e4adce..3ceb655 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
@@ -117,6 +117,8 @@
 import com.android.systemui.statusbar.phone.dagger.StatusBarComponent;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener;
+import com.android.systemui.statusbar.policy.DeviceProvisionedController;
+import com.android.systemui.statusbar.policy.DeviceProvisionedController.DeviceProvisionedListener;
 import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener;
 import com.android.systemui.statusbar.policy.ZenModeController;
 import com.android.systemui.tuner.TunerService;
@@ -144,6 +146,7 @@
     private final HeadsUpManagerPhone mHeadsUpManager;
     private final NotificationRoundnessManager mNotificationRoundnessManager;
     private final TunerService mTunerService;
+    private final DeviceProvisionedController mDeviceProvisionedController;
     private final DynamicPrivacyController mDynamicPrivacyController;
     private final ConfigurationController mConfigurationController;
     private final ZenModeController mZenModeController;
@@ -218,6 +221,28 @@
                 }
             };
 
+    private final DeviceProvisionedListener mDeviceProvisionedListener =
+            new DeviceProvisionedListener() {
+                @Override
+                public void onDeviceProvisionedChanged() {
+                    updateCurrentUserIsSetup();
+                }
+
+                @Override
+                public void onUserSwitched() {
+                    updateCurrentUserIsSetup();
+                }
+
+                @Override
+                public void onUserSetupChanged() {
+                    updateCurrentUserIsSetup();
+                }
+
+                private void updateCurrentUserIsSetup() {
+                    mView.setCurrentUserSetup(mDeviceProvisionedController.isCurrentUserSetup());
+                }
+            };
+
     private final DynamicPrivacyController.Listener mDynamicPrivacyControllerListener = () -> {
         if (mView.isExpanded()) {
             // The bottom might change because we're using the final actual height of the view
@@ -587,6 +612,7 @@
             HeadsUpManagerPhone headsUpManager,
             NotificationRoundnessManager notificationRoundnessManager,
             TunerService tunerService,
+            DeviceProvisionedController deviceProvisionedController,
             DynamicPrivacyController dynamicPrivacyController,
             ConfigurationController configurationController,
             SysuiStatusBarStateController statusBarStateController,
@@ -623,6 +649,7 @@
         mHeadsUpManager = headsUpManager;
         mNotificationRoundnessManager = notificationRoundnessManager;
         mTunerService = tunerService;
+        mDeviceProvisionedController = deviceProvisionedController;
         mDynamicPrivacyController = dynamicPrivacyController;
         mConfigurationController = configurationController;
         mStatusBarStateController = statusBarStateController;
@@ -759,6 +786,9 @@
             return Unit.INSTANCE;
         });
 
+        // callback is invoked synchronously, updating mView immediately
+        mDeviceProvisionedController.addCallback(mDeviceProvisionedListener);
+
         if (mView.isAttachedToWindow()) {
             mOnAttachStateChangeListener.onViewAttachedToWindow(mView);
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
index 6e201048..2c76cfe 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
@@ -116,7 +116,7 @@
 
     /**
      * Mode in which fingerprint unlocks the device or passive auth (ie face auth) unlocks the
-     * device while being requested when keyguard is occluded.
+     * device while being requested when keyguard is occluded or showing.
      */
     public static final int MODE_UNLOCK_COLLAPSING = 5;
 
@@ -425,6 +425,11 @@
                 if (!wasDeviceInteractive) {
                     mPendingShowBouncer = true;
                 } else {
+                    mShadeController.animateCollapsePanels(
+                            CommandQueue.FLAG_EXCLUDE_NONE,
+                            true /* force */,
+                            false /* delayed */,
+                            BIOMETRIC_COLLAPSE_SPEEDUP_FACTOR);
                     mPendingShowBouncer = false;
                     mKeyguardViewController.notifyKeyguardAuthenticated(
                             false /* strongAuth */);
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 15e0716..440f19c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
@@ -3655,6 +3655,7 @@
     }
 
     public void dozeTimeTick() {
+        mLockIconViewController.dozeTimeTick();
         mKeyguardBottomArea.dozeTimeTick();
         mKeyguardStatusViewController.dozeTimeTick();
         if (mInterpolatedDarkAmount > 0) {
@@ -3868,6 +3869,9 @@
     @Override
     protected TouchHandler createTouchHandler() {
         return new TouchHandler() {
+
+            private long mLastTouchDownTime = -1L;
+
             @Override
             public boolean onInterceptTouchEvent(MotionEvent event) {
                 if (mBlockTouches || mQsFullyExpanded && mQs.disallowPanelTouches()) {
@@ -3897,6 +3901,19 @@
 
             @Override
             public boolean onTouch(View v, MotionEvent event) {
+                if (event.getAction() == MotionEvent.ACTION_DOWN) {
+                    if (event.getDownTime() == mLastTouchDownTime) {
+                        // An issue can occur when swiping down after unlock, where multiple down
+                        // events are received in this handler with identical downTimes. Until the
+                        // source of the issue can be located, detect this case and ignore.
+                        // see b/193350347
+                        Log.w(TAG, "Duplicate down event detected... ignoring");
+                        return true;
+                    }
+                    mLastTouchDownTime = event.getDownTime();
+                }
+
+
                 if (mBlockTouches || (mQsFullyExpanded && mQs != null
                         && mQs.disallowPanelTouches())) {
                     return false;
@@ -3958,10 +3975,6 @@
                     mStatusBarKeyguardViewManager.updateKeyguardPosition(event.getX());
                 }
 
-                if (mLockIconViewController.onTouchEvent(event)) {
-                    return true;
-                }
-
                 handled |= super.onTouch(v, event);
                 return !mDozing || mPulsing || handled;
             }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewController.java
index b5d9bd6..66a6e72 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewController.java
@@ -35,6 +35,7 @@
 import android.view.ViewGroup;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.keyguard.LockIconViewController;
 import com.android.systemui.R;
 import com.android.systemui.classifier.FalsingCollector;
 import com.android.systemui.dock.DockManager;
@@ -88,6 +89,7 @@
     private final NotificationShadeDepthController mDepthController;
     private final NotificationStackScrollLayoutController mNotificationStackScrollLayoutController;
     private final LockscreenShadeTransitionController mLockscreenShadeTransitionController;
+    private final LockIconViewController mLockIconViewController;
     private final StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
 
     private GestureDetector mGestureDetector;
@@ -138,7 +140,8 @@
             NotificationPanelViewController notificationPanelViewController,
             SuperStatusBarViewFactory statusBarViewFactory,
             NotificationStackScrollLayoutController notificationStackScrollLayoutController,
-            StatusBarKeyguardViewManager statusBarKeyguardViewManager) {
+            StatusBarKeyguardViewManager statusBarKeyguardViewManager,
+            LockIconViewController lockIconViewController) {
         mInjectionInflationController = injectionInflationController;
         mCoordinator = coordinator;
         mPulseExpansionHandler = pulseExpansionHandler;
@@ -163,6 +166,7 @@
         mStatusBarViewFactory = statusBarViewFactory;
         mNotificationStackScrollLayoutController = notificationStackScrollLayoutController;
         mStatusBarKeyguardViewManager = statusBarKeyguardViewManager;
+        mLockIconViewController = lockIconViewController;
 
         // This view is not part of the newly inflated expanded status bar.
         mBrightnessMirror = mView.findViewById(R.id.brightness_mirror_container);
@@ -235,6 +239,7 @@
                 if (!isCancel && mService.shouldIgnoreTouch()) {
                     return false;
                 }
+
                 if (isDown) {
                     setTouchActive(true);
                     mTouchCancelled = false;
@@ -245,6 +250,7 @@
                 if (mTouchCancelled || mExpandAnimationRunning) {
                     return false;
                 }
+
                 mFalsingCollector.onTouchEvent(ev);
                 mGestureDetector.onTouchEvent(ev);
                 mStatusBarKeyguardViewManager.onTouch(ev);
@@ -260,9 +266,17 @@
                 if (isDown) {
                     mNotificationStackScrollLayoutController.closeControlsIfOutsideTouch(ev);
                 }
+
                 if (mStatusBarStateController.isDozing()) {
                     mService.mDozeScrimController.extendPulse();
                 }
+                mLockIconViewController.onTouchEvent(
+                        ev,
+                        () -> mService.wakeUpIfDozing(
+                                SystemClock.uptimeMillis(),
+                                mView,
+                                "LOCK_ICON_TOUCH"));
+
                 // In case we start outside of the view bounds (below the status bar), we need to
                 // dispatch
                 // the touch manually as the view system can't accommodate for touches outside of
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 cfcea96..7d25aee 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
@@ -111,6 +111,20 @@
      */
     private boolean mTransitioningToFullShade;
 
+    /**
+     * Is there currently an unocclusion animation running. Used to avoid bright flickers
+     * of the notification scrim.
+     */
+    private boolean mUnOcclusionAnimationRunning;
+
+    /**
+     * Set whether an unocclusion animation is currently running on the notification panel. Used
+     * to avoid bright flickers of the notification scrim.
+     */
+    public void setUnocclusionAnimationRunning(boolean unocclusionAnimationRunning) {
+        mUnOcclusionAnimationRunning = unocclusionAnimationRunning;
+    }
+
     @IntDef(prefix = {"VISIBILITY_"}, value = {
             TRANSPARENT,
             SEMI_TRANSPARENT,
@@ -418,7 +432,7 @@
         if (mKeyguardUpdateMonitor.needsSlowUnlockTransition() && mState == ScrimState.UNLOCKED) {
             mAnimationDelay = StatusBar.FADE_KEYGUARD_START_DELAY;
             scheduleUpdate();
-        } else if ((oldState == ScrimState.AOD  // leaving doze
+        } else if (((oldState == ScrimState.AOD || oldState == ScrimState.PULSING)  // leaving doze
                 && (!mDozeParameters.getAlwaysOn() || mState == ScrimState.UNLOCKED))
                 || (mState == ScrimState.AOD && !mDozeParameters.getDisplayNeedsBlanking())) {
             // Scheduling a frame isn't enough when:
@@ -466,6 +480,7 @@
 
     public void onExpandingFinished() {
         mTracking = false;
+        setUnocclusionAnimationRunning(false);
     }
 
     @VisibleForTesting
@@ -694,6 +709,11 @@
                 mNotificationsTint = mState.getNotifTint();
                 mBehindTint = behindTint;
             }
+            if (mUnOcclusionAnimationRunning && mState == ScrimState.KEYGUARD) {
+                // We're unoccluding the keyguard and don't want to have a bright flash.
+                mNotificationsAlpha = KEYGUARD_SCRIM_ALPHA;
+                mNotificationsTint = ScrimState.KEYGUARD.getNotifTint();
+            }
         }
         if (isNaN(mBehindAlpha) || isNaN(mInFrontAlpha) || isNaN(mNotificationsAlpha)) {
             throw new IllegalStateException("Scrim opacity is NaN for state: " + mState
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 0681193..2c0de62 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java
@@ -244,7 +244,8 @@
                     ? mKeyguardFadingAwayDuration
                     : StatusBar.FADE_KEYGUARD_DURATION;
 
-            mAnimateChange = !mLaunchingAffordanceWithPreview;
+            boolean fromAod = previousState == AOD || previousState == PULSING;
+            mAnimateChange = !mLaunchingAffordanceWithPreview && !fromAod;
 
             mFrontTint = Color.TRANSPARENT;
             mBehindTint = Color.BLACK;
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 394e4ad..90fc779 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
@@ -592,6 +592,7 @@
 
             mNotificationShadeWindowController.setWallpaperSupportsAmbientMode(supportsAmbientMode);
             mScrimController.setWallpaperSupportsAmbientMode(supportsAmbientMode);
+            mKeyguardViewMediator.setWallpaperSupportsAmbientMode(supportsAmbientMode);
         }
     };
 
@@ -1148,6 +1149,9 @@
                     mStatusBarView.setPanel(mNotificationPanelViewController);
                     mStatusBarView.setScrimController(mScrimController);
                     mStatusBarView.setExpansionChangedListeners(mExpansionChangedListeners);
+                    for (ExpansionChangedListener listener : mExpansionChangedListeners) {
+                        sendInitialExpansionAmount(listener);
+                    }
 
                     // CollapsedStatusBarFragment re-inflated PhoneStatusBarView and both of
                     // mStatusBarView.mExpanded and mStatusBarView.mBouncerShowing are false.
@@ -3579,6 +3583,7 @@
     public void animateKeyguardUnoccluding() {
         mNotificationPanelViewController.setExpandedFraction(0f);
         animateExpandNotificationsPanel();
+        mScrimController.setUnocclusionAnimationRunning(true);
     }
 
     /**
@@ -3909,7 +3914,8 @@
     @Override
     public void onDozeAmountChanged(float linear, float eased) {
         if (mFeatureFlags.useNewLockscreenAnimations()
-                && !(mLightRevealScrim.getRevealEffect() instanceof CircleReveal)) {
+                && !(mLightRevealScrim.getRevealEffect() instanceof CircleReveal)
+                && !mBiometricUnlockController.isWakeAndUnlock()) {
             mLightRevealScrim.setRevealAmount(1f - linear);
         }
     }
@@ -4465,10 +4471,8 @@
             ScrimState state = mStatusBarKeyguardViewManager.bouncerNeedsScrimming()
                     ? ScrimState.BOUNCER_SCRIMMED : ScrimState.BOUNCER;
             mScrimController.transitionTo(state);
-        } else if (isInLaunchTransition()
-                || mLaunchCameraWhenFinishedWaking
-                || launchingAffordanceWithPreview) {
-            // TODO(b/170133395) Investigate whether Emergency Gesture flag should be included here.
+        } else if (launchingAffordanceWithPreview) {
+            // We want to avoid animating when launching with a preview.
             mScrimController.transitionTo(ScrimState.UNLOCKED, mUnlockScrimCallback);
         } else if (mBrightnessMirrorVisible) {
             mScrimController.transitionTo(ScrimState.BRIGHTNESS_MIRROR);
@@ -4933,6 +4937,14 @@
 
     public void addExpansionChangedListener(@NonNull ExpansionChangedListener listener) {
         mExpansionChangedListeners.add(listener);
+        sendInitialExpansionAmount(listener);
+    }
+
+    private void sendInitialExpansionAmount(ExpansionChangedListener expansionChangedListener) {
+        if (mStatusBarView != null) {
+            expansionChangedListener.onExpansionChanged(mStatusBarView.getExpansionFraction(),
+                    mStatusBarView.isExpanded());
+        }
     }
 
     public void removeExpansionChangedListener(@NonNull ExpansionChangedListener listener) {
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 8a7708a..3188a52 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -194,6 +194,7 @@
     private boolean mLastGesturalNav;
     private boolean mLastIsDocked;
     private boolean mLastPulsing;
+    private boolean mLastAnimatedToSleep;
     private int mLastBiometricMode;
     private boolean mQsExpanded;
     private boolean mAnimatedToSleep;
@@ -990,6 +991,7 @@
         mLastBiometricMode = mBiometricUnlockController.getMode();
         mLastGesturalNav = mGesturalNav;
         mLastIsDocked = mIsDocked;
+        mLastAnimatedToSleep = mAnimatedToSleep;
         mStatusBar.onKeyguardViewManagerStatesUpdated();
     }
 
@@ -1033,7 +1035,7 @@
         boolean hideWhileDozing = mLastDozing && mLastBiometricMode != MODE_WAKE_AND_UNLOCK_PULSING;
         boolean keyguardWithGestureNav = (keyguardShowing && !mLastDozing
                 || mLastPulsing && !mLastIsDocked) && mLastGesturalNav;
-        return (!keyguardShowing && !hideWhileDozing || mLastBouncerShowing
+        return (!mLastAnimatedToSleep && !keyguardShowing && !hideWhileDozing || mLastBouncerShowing
                 || mLastRemoteInputActive || keyguardWithGestureNav
                 || mLastGlobalActionsVisible);
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateController.java
index fcfc967..7057618 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateController.java
@@ -245,5 +245,11 @@
          * animation.
          */
         default void onKeyguardDismissAmountChanged() {}
+
+        /**
+         * Triggered when the notification panel is starting or has finished
+         * fading away on transition to an app.
+         */
+        default void onLaunchTransitionFadingAwayChanged() {}
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java
index 64750bd..f787ecf 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java
@@ -343,6 +343,7 @@
     @Override
     public void setLaunchTransitionFadingAway(boolean fadingAway) {
         mLaunchTransitionFadingAway = fadingAway;
+        new ArrayList<>(mCallbacks).forEach(Callback::onLaunchTransitionFadingAwayChanged);
     }
 
     @Override
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthRippleControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthRippleControllerTest.kt
index d87a26b..f2f0029 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthRippleControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthRippleControllerTest.kt
@@ -19,16 +19,23 @@
 import android.graphics.PointF
 import android.hardware.biometrics.BiometricSourceType
 import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper.RunWithLooper
 import androidx.test.filters.SmallTest
 import com.android.keyguard.KeyguardUpdateMonitor
 import com.android.keyguard.KeyguardUpdateMonitorCallback
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.keyguard.WakefulnessLifecycle
+import com.android.systemui.statusbar.LightRevealScrim
 import com.android.systemui.statusbar.NotificationShadeWindowController
 import com.android.systemui.statusbar.commandline.CommandRegistry
 import com.android.systemui.statusbar.phone.BiometricUnlockController
 import com.android.systemui.statusbar.phone.KeyguardBypassController
 import com.android.systemui.statusbar.phone.StatusBar
 import com.android.systemui.statusbar.policy.ConfigurationController
+import com.android.systemui.statusbar.policy.KeyguardStateController
+import org.junit.Assert.assertFalse
+import org.junit.Assert.assertTrue
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -42,6 +49,8 @@
 import org.mockito.Mockito.verify
 import org.mockito.MockitoAnnotations
 
+import javax.inject.Provider
+
 @SmallTest
 @RunWith(AndroidTestingRunner::class)
 class AuthRippleControllerTest : SysuiTestCase() {
@@ -52,26 +61,39 @@
     @Mock private lateinit var configurationController: ConfigurationController
     @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
     @Mock private lateinit var authController: AuthController
+    @Mock private lateinit var keyguardStateController: KeyguardStateController
+    @Mock private lateinit var wakefulnessLifecycle: WakefulnessLifecycle
     @Mock private lateinit var notificationShadeWindowController: NotificationShadeWindowController
     @Mock private lateinit var bypassController: KeyguardBypassController
     @Mock private lateinit var biometricUnlockController: BiometricUnlockController
+    @Mock private lateinit var udfpsControllerProvider: Provider<UdfpsController>
+    @Mock private lateinit var udfpsController: UdfpsController
+    @Mock private lateinit var statusBarStateController: StatusBarStateController
+    @Mock private lateinit var lightRevealScrim: LightRevealScrim
 
     @Before
     fun setUp() {
         MockitoAnnotations.initMocks(this)
+        `when`(udfpsControllerProvider.get()).thenReturn(udfpsController)
+
         controller = AuthRippleController(
             statusBar,
             context,
             authController,
             configurationController,
             keyguardUpdateMonitor,
+            keyguardStateController,
+            wakefulnessLifecycle,
             commandRegistry,
             notificationShadeWindowController,
             bypassController,
             biometricUnlockController,
+            udfpsControllerProvider,
+            statusBarStateController,
             rippleView
         )
         controller.init()
+        `when`(statusBar.lightRevealScrim).thenReturn(lightRevealScrim)
     }
 
     @Test
@@ -93,7 +115,7 @@
 
         // THEN update sensor location and show ripple
         verify(rippleView).setSensorLocation(fpsLocation)
-        verify(rippleView).startRipple(any(), any())
+        verify(rippleView).startUnlockedRipple(any())
     }
 
     @Test
@@ -114,7 +136,7 @@
             false /* isStrongBiometric */)
 
         // THEN no ripple
-        verify(rippleView, never()).startRipple(any(), any())
+        verify(rippleView, never()).startUnlockedRipple(any())
     }
 
     @Test
@@ -135,7 +157,7 @@
             false /* isStrongBiometric */)
 
         // THEN no ripple
-        verify(rippleView, never()).startRipple(any(), any())
+        verify(rippleView, never()).startUnlockedRipple(any())
     }
 
     @Test
@@ -159,7 +181,7 @@
 
         // THEN show ripple
         verify(rippleView).setSensorLocation(faceLocation)
-        verify(rippleView).startRipple(any(), any())
+        verify(rippleView).startUnlockedRipple(any())
     }
 
     @Test
@@ -179,7 +201,7 @@
             false /* isStrongBiometric */)
 
         // THEN no ripple
-        verify(rippleView, never()).startRipple(any(), any())
+        verify(rippleView, never()).startUnlockedRipple(any())
     }
 
     @Test
@@ -194,7 +216,7 @@
             0 /* userId */,
             BiometricSourceType.FACE /* type */,
             false /* isStrongBiometric */)
-        verify(rippleView, never()).startRipple(any(), any())
+        verify(rippleView, never()).startUnlockedRipple(any())
     }
 
     @Test
@@ -209,7 +231,39 @@
             0 /* userId */,
             BiometricSourceType.FINGERPRINT /* type */,
             false /* isStrongBiometric */)
-        verify(rippleView, never()).startRipple(any(), any())
+        verify(rippleView, never()).startUnlockedRipple(any())
+    }
+
+    @Test
+    fun registersAndDeregisters() {
+        controller.onViewAttached()
+        val captor = ArgumentCaptor
+            .forClass(KeyguardStateController.Callback::class.java)
+        verify(keyguardStateController).addCallback(captor.capture())
+        val captor2 = ArgumentCaptor
+            .forClass(WakefulnessLifecycle.Observer::class.java)
+        verify(wakefulnessLifecycle).addObserver(captor2.capture())
+        controller.onViewDetached()
+        verify(keyguardStateController).removeCallback(any())
+        verify(wakefulnessLifecycle).removeObserver(any())
+    }
+
+    @Test
+    @RunWithLooper(setAsMainLooper = true)
+    fun testAnimatorRunWhenWakeAndUnlock() {
+        val fpsLocation = PointF(5f, 5f)
+        `when`(authController.fingerprintSensorLocation).thenReturn(fpsLocation)
+        controller.onViewAttached()
+        `when`(keyguardUpdateMonitor.isKeyguardVisible).thenReturn(true)
+        `when`(biometricUnlockController.isWakeAndUnlock).thenReturn(true)
+
+        controller.showRipple(BiometricSourceType.FINGERPRINT)
+        assertTrue("reveal didn't start on keyguardFadingAway",
+            controller.startLightRevealScrimOnKeyguardFadingAway)
+        `when`(keyguardStateController.isKeyguardFadingAway).thenReturn(true)
+        controller.onKeyguardFadingAwayChanged()
+        assertFalse("reveal triggers multiple times",
+            controller.startLightRevealScrimOnKeyguardFadingAway)
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
index 2120b0e..1a39017 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
@@ -456,11 +456,12 @@
 
     @Test
     public void aodInterrupt() throws RemoteException {
-        // GIVEN that the overlay is showing and screen is on
+        // GIVEN that the overlay is showing and screen is on and fp is running
         mOverlayController.showUdfpsOverlay(TEST_UDFPS_SENSOR_ID,
                 IUdfpsOverlayController.REASON_AUTH_FPM_KEYGUARD, mUdfpsOverlayControllerCallback);
         mScreenObserver.onScreenTurnedOn();
         mFgExecutor.runAllReady();
+        when(mKeyguardUpdateMonitor.isFingerprintDetectionRunning()).thenReturn(true);
         // WHEN fingerprint is requested because of AOD interrupt
         mUdfpsController.onAodInterrupt(0, 0, 2f, 3f);
         // THEN illumination begins
@@ -478,6 +479,7 @@
                 IUdfpsOverlayController.REASON_AUTH_FPM_KEYGUARD, mUdfpsOverlayControllerCallback);
         mScreenObserver.onScreenTurnedOn();
         mFgExecutor.runAllReady();
+        when(mKeyguardUpdateMonitor.isFingerprintDetectionRunning()).thenReturn(true);
         mUdfpsController.onAodInterrupt(0, 0, 0f, 0f);
         when(mUdfpsView.isIlluminationRequested()).thenReturn(true);
         // WHEN it is cancelled
@@ -493,6 +495,7 @@
                 IUdfpsOverlayController.REASON_AUTH_FPM_KEYGUARD, mUdfpsOverlayControllerCallback);
         mScreenObserver.onScreenTurnedOn();
         mFgExecutor.runAllReady();
+        when(mKeyguardUpdateMonitor.isFingerprintDetectionRunning()).thenReturn(true);
         mUdfpsController.onAodInterrupt(0, 0, 0f, 0f);
         when(mUdfpsView.isIlluminationRequested()).thenReturn(true);
         // WHEN it times out
@@ -511,6 +514,23 @@
         mFgExecutor.runAllReady();
 
         // WHEN aod interrupt is received
+        when(mKeyguardUpdateMonitor.isFingerprintDetectionRunning()).thenReturn(true);
+        mUdfpsController.onAodInterrupt(0, 0, 0f, 0f);
+
+        // THEN no illumination because screen is off
+        verify(mUdfpsView, never()).startIllumination(any());
+    }
+
+    @Test
+    public void aodInterrupt_fingerprintNotRunning() throws RemoteException {
+        // GIVEN showing overlay
+        mOverlayController.showUdfpsOverlay(TEST_UDFPS_SENSOR_ID,
+                IUdfpsOverlayController.REASON_AUTH_FPM_KEYGUARD, mUdfpsOverlayControllerCallback);
+        mScreenObserver.onScreenTurnedOn();
+        mFgExecutor.runAllReady();
+
+        // WHEN aod interrupt is received when the fingerprint service isn't running
+        when(mKeyguardUpdateMonitor.isFingerprintDetectionRunning()).thenReturn(false);
         mUdfpsController.onAodInterrupt(0, 0, 0f, 0f);
 
         // THEN no illumination because screen is off
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 0c03a51..0fbf9af 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerTest.java
@@ -20,6 +20,11 @@
 import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.Mockito.atLeast;
+
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
@@ -30,7 +35,6 @@
 import androidx.test.filters.SmallTest;
 
 import com.android.keyguard.KeyguardUpdateMonitor;
-import com.android.keyguard.KeyguardUpdateMonitorCallback;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.keyguard.KeyguardViewMediator;
@@ -40,6 +44,7 @@
 import com.android.systemui.statusbar.phone.StatusBar;
 import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
 import com.android.systemui.statusbar.policy.ConfigurationController;
+import com.android.systemui.statusbar.policy.KeyguardStateController;
 import com.android.systemui.util.concurrency.DelayableExecutor;
 
 import org.junit.Before;
@@ -50,6 +55,8 @@
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
+import java.util.List;
+
 
 @SmallTest
 @RunWith(AndroidTestingRunner.class)
@@ -75,6 +82,8 @@
     @Mock
     private KeyguardUpdateMonitor mKeyguardUpdateMonitor;
     @Mock
+    private KeyguardStateController mKeyguardStateController;
+    @Mock
     private KeyguardViewMediator mKeyguardViewMediator;
     @Mock
     private ConfigurationController mConfigurationController;
@@ -88,14 +97,15 @@
     private StatusBarStateController.StateListener mStatusBarStateListener;
 
     @Captor private ArgumentCaptor<StatusBar.ExpansionChangedListener> mExpansionListenerCaptor;
-    private StatusBar.ExpansionChangedListener mExpansionListener;
+    private List<StatusBar.ExpansionChangedListener> mExpansionListeners;
 
     @Captor private ArgumentCaptor<StatusBarKeyguardViewManager.AlternateAuthInterceptor>
             mAltAuthInterceptorCaptor;
     private StatusBarKeyguardViewManager.AlternateAuthInterceptor mAltAuthInterceptor;
 
-    @Captor private ArgumentCaptor<KeyguardUpdateMonitorCallback> mUpdateMonitorCallbackCaptor;
-    private KeyguardUpdateMonitorCallback mKeyguardUpdateMonitorCallback;
+    @Captor private ArgumentCaptor<KeyguardStateController.Callback>
+            mKeyguardStateControllerCallbackCaptor;
+    private KeyguardStateController.Callback mKeyguardStateControllerCallback;
 
     @Before
     public void setUp() {
@@ -114,13 +124,14 @@
                 mKeyguardViewMediator,
                 mLockscreenShadeTransitionController,
                 mConfigurationController,
+                mKeyguardStateController,
                 mUdfpsController);
     }
 
     @Test
     public void testRegistersExpansionChangedListenerOnAttached() {
         mController.onViewAttached();
-        captureExpansionListener();
+        captureExpansionListeners();
     }
 
     @Test
@@ -149,11 +160,15 @@
     public void testListenersUnregisteredOnDetached() {
         mController.onViewAttached();
         captureStatusBarStateListeners();
-        captureExpansionListener();
+        captureExpansionListeners();
+        captureKeyguardStateControllerCallback();
         mController.onViewDetached();
 
         verify(mStatusBarStateController).removeCallback(mStatusBarStateListener);
-        verify(mStatusBar).removeExpansionChangedListener(mExpansionListener);
+        for (StatusBar.ExpansionChangedListener listener : mExpansionListeners) {
+            verify(mStatusBar).removeExpansionChangedListener(listener);
+        }
+        verify(mKeyguardStateController).removeCallback(mKeyguardStateControllerCallback);
     }
 
     @Test
@@ -172,7 +187,6 @@
     public void testShouldPauseAuthBouncerShowing() {
         mController.onViewAttached();
         captureStatusBarStateListeners();
-        captureExpansionListener();
 
         sendStatusBarStateChanged(StatusBarState.KEYGUARD);
 
@@ -183,7 +197,6 @@
     public void testShouldNotPauseAuthOnKeyguard() {
         mController.onViewAttached();
         captureStatusBarStateListeners();
-        captureExpansionListener();
 
         sendStatusBarStateChanged(StatusBarState.KEYGUARD);
 
@@ -191,10 +204,25 @@
     }
 
     @Test
+    public void testShouldPauseAuthIsLaunchTransitionFadingAway() {
+        // GIVEN view is attached and we're on the keyguard (see testShouldNotPauseAuthOnKeyguard)
+        mController.onViewAttached();
+        captureStatusBarStateListeners();
+        sendStatusBarStateChanged(StatusBarState.KEYGUARD);
+
+        // WHEN isLaunchTransitionFadingAway=true
+        captureKeyguardStateControllerCallback();
+        when(mKeyguardStateController.isLaunchTransitionFadingAway()).thenReturn(true);
+        mKeyguardStateControllerCallback.onLaunchTransitionFadingAwayChanged();
+
+        // THEN pause auth
+        assertTrue(mController.shouldPauseAuth());
+    }
+
+    @Test
     public void testShouldPauseAuthOnShadeLocked() {
         mController.onViewAttached();
         captureStatusBarStateListeners();
-        captureExpansionListener();
 
         sendStatusBarStateChanged(StatusBarState.SHADE_LOCKED);
 
@@ -205,7 +233,6 @@
     public void testShouldPauseAuthOnShade() {
         mController.onViewAttached();
         captureStatusBarStateListeners();
-        captureExpansionListener();
 
         // WHEN not on keyguard yet (shade = home)
         sendStatusBarStateChanged(StatusBarState.SHADE);
@@ -218,7 +245,6 @@
     public void testShouldPauseAuthAnimatingScreenOffFromShade() {
         mController.onViewAttached();
         captureStatusBarStateListeners();
-        captureExpansionListener();
 
         // WHEN transitioning from home/shade => keyguard + animating screen off
         mStatusBarStateListener.onStatePreChange(StatusBarState.SHADE, StatusBarState.KEYGUARD);
@@ -232,7 +258,6 @@
     public void testDoNotPauseAuthAnimatingScreenOffFromLS() {
         mController.onViewAttached();
         captureStatusBarStateListeners();
-        captureExpansionListener();
 
         // WHEN animating screen off transition from LS => AOD
         sendStatusBarStateChanged(StatusBarState.KEYGUARD);
@@ -273,6 +298,74 @@
         verify(mStatusBarKeyguardViewManager).removeAlternateAuthInterceptor(mAltAuthInterceptor);
     }
 
+    @Test
+    public void testFadeInWithStatusBarExpansion() {
+        // GIVEN view is attached
+        mController.onViewAttached();
+        captureExpansionListeners();
+        captureKeyguardStateControllerCallback();
+        reset(mView);
+
+        // WHEN status bar expansion is 0
+        updateStatusBarExpansion(0, true);
+
+        // THEN alpha is 0
+        verify(mView).setUnpausedAlpha(0);
+    }
+
+    @Test
+    public void testShowUdfpsBouncer() {
+        // GIVEN view is attached and status bar expansion is 0
+        mController.onViewAttached();
+        captureExpansionListeners();
+        captureKeyguardStateControllerCallback();
+        captureAltAuthInterceptor();
+        updateStatusBarExpansion(0, true);
+        reset(mView);
+        when(mView.getContext()).thenReturn(mResourceContext);
+        when(mResourceContext.getString(anyInt())).thenReturn("test string");
+
+        // WHEN status bar expansion is 0 but udfps bouncer is requested
+        mAltAuthInterceptor.showAlternateAuthBouncer();
+
+        // THEN alpha is 255
+        verify(mView).setUnpausedAlpha(255);
+    }
+
+    @Test
+    public void testTransitionToFullShadeProgress() {
+        // GIVEN view is attached and status bar expansion is 1f
+        mController.onViewAttached();
+        captureExpansionListeners();
+        updateStatusBarExpansion(1f, true);
+        reset(mView);
+
+        // WHEN we're transitioning to the full shade
+        float transitionProgress = .6f;
+        mController.setTransitionToFullShadeProgress(transitionProgress);
+
+        // THEN alpha is between 0 and 255
+        verify(mView).setUnpausedAlpha((int) ((1f - transitionProgress) * 255));
+    }
+
+    @Test
+    public void testShowUdfpsBouncer_transitionToFullShadeProgress() {
+        // GIVEN view is attached and status bar expansion is 1f
+        mController.onViewAttached();
+        captureExpansionListeners();
+        captureKeyguardStateControllerCallback();
+        captureAltAuthInterceptor();
+        updateStatusBarExpansion(1f, true);
+        mAltAuthInterceptor.showAlternateAuthBouncer();
+        reset(mView);
+
+        // WHEN we're transitioning to the full shade
+        mController.setTransitionToFullShadeProgress(1.0f);
+
+        // THEN alpha is 255 (b/c udfps bouncer is requested)
+        verify(mView).setUnpausedAlpha(255);
+    }
+
     private void sendStatusBarStateChanged(int statusBarState) {
         mStatusBarStateListener.onStateChanged(statusBarState);
     }
@@ -282,9 +375,18 @@
         mStatusBarStateListener = mStateListenerCaptor.getValue();
     }
 
-    private void captureExpansionListener() {
-        verify(mStatusBar).addExpansionChangedListener(mExpansionListenerCaptor.capture());
-        mExpansionListener = mExpansionListenerCaptor.getValue();
+    private void captureExpansionListeners() {
+        verify(mStatusBar, times(2))
+                .addExpansionChangedListener(mExpansionListenerCaptor.capture());
+        // first (index=0) is from super class, UdfpsAnimationViewController.
+        // second (index=1) is from UdfpsKeyguardViewController
+        mExpansionListeners = mExpansionListenerCaptor.getAllValues();
+    }
+
+    private void updateStatusBarExpansion(float expansion, boolean expanded) {
+        for (StatusBar.ExpansionChangedListener listener : mExpansionListeners) {
+            listener.onExpansionChanged(expansion, expanded);
+        }
     }
 
     private void captureAltAuthInterceptor() {
@@ -293,8 +395,9 @@
         mAltAuthInterceptor = mAltAuthInterceptorCaptor.getValue();
     }
 
-    private void captureKeyguardUpdateMonitorCallback() {
-        verify(mKeyguardUpdateMonitor).registerCallback(mUpdateMonitorCallbackCaptor.capture());
-        mKeyguardUpdateMonitorCallback = mUpdateMonitorCallbackCaptor.getValue();
+    private void captureKeyguardStateControllerCallback() {
+        verify(mKeyguardStateController).addCallback(
+                mKeyguardStateControllerCallbackCaptor.capture());
+        mKeyguardStateControllerCallback = mKeyguardStateControllerCallbackCaptor.getValue();
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenBrightnessTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenBrightnessTest.java
index 4e8b59c..cb55efa 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenBrightnessTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenBrightnessTest.java
@@ -49,6 +49,7 @@
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.keyguard.WakefulnessLifecycle;
 import com.android.systemui.statusbar.phone.DozeParameters;
+import com.android.systemui.statusbar.phone.UnlockedScreenOffAnimationController;
 import com.android.systemui.util.concurrency.FakeExecutor;
 import com.android.systemui.util.concurrency.FakeThreadFactory;
 import com.android.systemui.util.sensors.AsyncSensorManager;
@@ -82,6 +83,8 @@
     WakefulnessLifecycle mWakefulnessLifecycle;
     @Mock
     DozeParameters mDozeParameters;
+    @Mock
+    private UnlockedScreenOffAnimationController mUnlockedScreenOffAnimationController;
     private FakeExecutor mFakeExecutor = new FakeExecutor(new FakeSystemClock());
     private FakeThreadFactory mFakeThreadFactory = new FakeThreadFactory(mFakeExecutor);
 
@@ -109,8 +112,8 @@
         mSensor = fakeSensorManager.getFakeLightSensor();
         mScreen = new DozeScreenBrightness(mContext, mServiceFake, mSensorManager,
                 Optional.of(mSensor.getSensor()), mDozeHost, null /* handler */,
-                mAlwaysOnDisplayPolicy, mWakefulnessLifecycle, mDozeParameters);
-
+                mAlwaysOnDisplayPolicy, mWakefulnessLifecycle, mDozeParameters,
+                mUnlockedScreenOffAnimationController);
         mScreen.onScreenState(Display.STATE_ON);
     }
 
@@ -175,7 +178,8 @@
     public void testPulsing_withoutLightSensor_setsAoDDimmingScrimTransparent() throws Exception {
         mScreen = new DozeScreenBrightness(mContext, mServiceFake, mSensorManager,
                 Optional.empty() /* sensor */, mDozeHost, null /* handler */,
-                mAlwaysOnDisplayPolicy, mWakefulnessLifecycle, mDozeParameters);
+                mAlwaysOnDisplayPolicy, mWakefulnessLifecycle, mDozeParameters,
+                mUnlockedScreenOffAnimationController);
         mScreen.transitionTo(UNINITIALIZED, INITIALIZED);
         mScreen.transitionTo(INITIALIZED, DOZE);
         reset(mDozeHost);
@@ -216,7 +220,8 @@
     public void testNullSensor() throws Exception {
         mScreen = new DozeScreenBrightness(mContext, mServiceFake, mSensorManager,
                 Optional.empty() /* sensor */, mDozeHost, null /* handler */,
-                mAlwaysOnDisplayPolicy, mWakefulnessLifecycle, mDozeParameters);
+                mAlwaysOnDisplayPolicy, mWakefulnessLifecycle, mDozeParameters,
+                mUnlockedScreenOffAnimationController);
 
         mScreen.transitionTo(UNINITIALIZED, INITIALIZED);
         mScreen.transitionTo(INITIALIZED, DOZE_AOD);
@@ -296,13 +301,14 @@
         when(mWakefulnessLifecycle.getLastSleepReason()).thenReturn(
                 PowerManager.GO_TO_SLEEP_REASON_TIMEOUT);
         when(mDozeParameters.shouldControlUnlockedScreenOff()).thenReturn(true);
+        when(mUnlockedScreenOffAnimationController.isScreenOffAnimationPlaying()).thenReturn(true);
 
         mScreen.transitionTo(UNINITIALIZED, INITIALIZED);
         mScreen.transitionTo(INITIALIZED, DOZE);
 
         // If we're dozing after a timeout, and playing the unlocked screen animation, we should
-        // stay at dim brightness, because the screen dims just before timeout.
-        assertEquals(mServiceFake.screenBrightness, DIM_BRIGHTNESS);
+        // stay at or below dim brightness, because the screen dims just before timeout.
+        assertTrue(mServiceFake.screenBrightness <= DIM_BRIGHTNESS);
     }
 
     @Test
@@ -310,6 +316,7 @@
         when(mWakefulnessLifecycle.getLastSleepReason()).thenReturn(
                 PowerManager.GO_TO_SLEEP_REASON_POWER_BUTTON);
         when(mDozeParameters.shouldControlUnlockedScreenOff()).thenReturn(true);
+        when(mUnlockedScreenOffAnimationController.isScreenOffAnimationPlaying()).thenReturn(true);
 
         mScreen.transitionTo(UNINITIALIZED, INITIALIZED);
         mScreen.transitionTo(INITIALIZED, DOZE);
@@ -324,6 +331,7 @@
         when(mWakefulnessLifecycle.getLastSleepReason()).thenReturn(
                 PowerManager.GO_TO_SLEEP_REASON_TIMEOUT);
         when(mDozeParameters.shouldControlUnlockedScreenOff()).thenReturn(false);
+        when(mUnlockedScreenOffAnimationController.isScreenOffAnimationPlaying()).thenReturn(false);
 
         mScreen.transitionTo(UNINITIALIZED, INITIALIZED);
         mScreen.transitionTo(INITIALIZED, DOZE);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenStateTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenStateTest.java
index 41d7fd6..3e19cc4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenStateTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenStateTest.java
@@ -21,6 +21,7 @@
 import static com.android.systemui.doze.DozeMachine.State.DOZE_AOD_DOCKED;
 import static com.android.systemui.doze.DozeMachine.State.DOZE_AOD_PAUSED;
 import static com.android.systemui.doze.DozeMachine.State.DOZE_AOD_PAUSING;
+import static com.android.systemui.doze.DozeMachine.State.DOZE_PULSE_DONE;
 import static com.android.systemui.doze.DozeMachine.State.DOZE_PULSING;
 import static com.android.systemui.doze.DozeMachine.State.DOZE_REQUEST_PULSE;
 import static com.android.systemui.doze.DozeMachine.State.FINISH;
@@ -34,6 +35,7 @@
 import static org.junit.Assert.assertThat;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.anyInt;
 import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
@@ -45,6 +47,8 @@
 import androidx.test.runner.AndroidJUnit4;
 
 import com.android.systemui.SysuiTestCase;
+import com.android.systemui.biometrics.AuthController;
+import com.android.systemui.biometrics.UdfpsController;
 import com.android.systemui.statusbar.phone.DozeParameters;
 import com.android.systemui.util.wakelock.WakeLockFake;
 import com.android.systemui.utils.os.FakeHandler;
@@ -56,6 +60,8 @@
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
+import javax.inject.Provider;
+
 @RunWith(AndroidJUnit4.class)
 @SmallTest
 public class DozeScreenStateTest extends SysuiTestCase {
@@ -68,17 +74,32 @@
     private DozeParameters mDozeParameters;
     private WakeLockFake mWakeLock;
     private DozeScreenState mScreen;
+    @Mock
+    private Provider<UdfpsController> mUdfpsControllerProvider;
+    @Mock
+    private AuthController mAuthController;
+    @Mock
+    private UdfpsController mUdfpsController;
+    @Mock
+    private DozeLog mDozeLog;
+    @Mock
+    private DozeScreenBrightness mDozeScreenBrightness;
 
     @Before
     public void setUp() throws Exception {
         MockitoAnnotations.initMocks(this);
         when(mDozeParameters.getDisplayNeedsBlanking()).thenReturn(true);
         when(mDozeParameters.getAlwaysOn()).thenReturn(true);
+        when(mUdfpsControllerProvider.get()).thenReturn(mUdfpsController);
+        when(mAuthController.isUdfpsEnrolled(anyInt())).thenReturn(true);
+        when(mUdfpsController.isFingerDown()).thenReturn(false);
+
         mServiceFake = new DozeServiceFake();
         mHandlerFake = new FakeHandler(Looper.getMainLooper());
         mWakeLock = new WakeLockFake();
         mScreen = new DozeScreenState(mServiceFake, mHandlerFake, mDozeHost, mDozeParameters,
-                mWakeLock);
+                mWakeLock, mAuthController, mUdfpsControllerProvider, mDozeLog,
+                mDozeScreenBrightness);
     }
 
     @Test
@@ -233,4 +254,56 @@
         assertEquals(Display.STATE_OFF, mServiceFake.screenState);
     }
 
+    @Test
+    public void testDelayEnterDozeScreenState_whenUdfpsFingerDown() {
+        // GIVEN AOD is initialized
+        when(mDozeParameters.shouldControlScreenOff()).thenReturn(true);
+        mHandlerFake.setMode(QUEUEING);
+        mScreen.transitionTo(UNINITIALIZED, INITIALIZED);
+        mHandlerFake.dispatchQueuedMessages();
+
+        mScreen.transitionTo(INITIALIZED, DOZE_AOD);
+
+        // WHEN udfps is activated (fingerDown)
+        when(mUdfpsController.isFingerDown()).thenReturn(true);
+        mHandlerFake.dispatchQueuedMessages();
+
+        // THEN the display screen state doesn't immediately change
+        assertEquals(Display.STATE_ON, mServiceFake.screenState);
+
+        // WHEN udfpsController finger is no longer down and the queued messages are run
+        when(mUdfpsController.isFingerDown()).thenReturn(false);
+        mHandlerFake.dispatchQueuedMessages();
+
+        // THEN the display screen state will change
+        assertEquals(Display.STATE_DOZE_SUSPEND, mServiceFake.screenState);
+    }
+
+    @Test
+    public void testDelayExitPulsingScreenState_whenUdfpsFingerDown() {
+        // GIVEN we're pulsing
+        when(mDozeParameters.shouldControlScreenOff()).thenReturn(true);
+        mHandlerFake.setMode(QUEUEING);
+        mScreen.transitionTo(UNINITIALIZED, INITIALIZED);
+        mScreen.transitionTo(INITIALIZED, DOZE_AOD);
+        mScreen.transitionTo(DOZE_AOD, DOZE_REQUEST_PULSE);
+        mScreen.transitionTo(DOZE_REQUEST_PULSE, DOZE_PULSING);
+        mScreen.transitionTo(DOZE_PULSING, DOZE_PULSE_DONE);
+        mHandlerFake.dispatchQueuedMessages();
+
+        // WHEN udfps is activated while are transitioning back to DOZE_AOD
+        mScreen.transitionTo(DOZE_PULSE_DONE, DOZE_AOD);
+        when(mUdfpsController.isFingerDown()).thenReturn(true);
+        mHandlerFake.dispatchQueuedMessages();
+
+        // THEN the display screen state doesn't immediately change
+        assertEquals(Display.STATE_ON, mServiceFake.screenState);
+
+        // WHEN udfpsController finger is no longer down and the queued messages are run
+        when(mUdfpsController.isFingerDown()).thenReturn(false);
+        mHandlerFake.dispatchQueuedMessages();
+
+        // THEN the display screen state will change
+        assertEquals(Display.STATE_DOZE_SUSPEND, mServiceFake.screenState);
+    }
 }
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/LockIconViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/LockIconViewControllerTest.java
new file mode 100644
index 0000000..9c3016c
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/LockIconViewControllerTest.java
@@ -0,0 +1,195 @@
+/*
+ * 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.keyguard;
+
+import static junit.framework.Assert.assertEquals;
+
+import static org.mockito.Mockito.anyInt;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.PointF;
+import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
+import android.os.Vibrator;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+import android.util.DisplayMetrics;
+import android.view.View;
+import android.view.accessibility.AccessibilityManager;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.keyguard.KeyguardUpdateMonitor;
+import com.android.keyguard.KeyguardViewController;
+import com.android.keyguard.LockIconView;
+import com.android.keyguard.LockIconViewController;
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.biometrics.AuthController;
+import com.android.systemui.biometrics.AuthRippleController;
+import com.android.systemui.dump.DumpManager;
+import com.android.systemui.plugins.FalsingManager;
+import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.statusbar.policy.ConfigurationController;
+import com.android.systemui.statusbar.policy.KeyguardStateController;
+import com.android.systemui.util.concurrency.DelayableExecutor;
+
+import com.airbnb.lottie.LottieAnimationView;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
+public class LockIconViewControllerTest extends SysuiTestCase {
+    private @Mock LockIconView mLockIconView;
+    private @Mock Context mContext;
+    private @Mock Resources mResources;
+    private @Mock DisplayMetrics mDisplayMetrics;
+    private @Mock StatusBarStateController mStatusBarStateController;
+    private @Mock KeyguardUpdateMonitor mKeyguardUpdateMonitor;
+    private @Mock KeyguardViewController mKeyguardViewController;
+    private @Mock KeyguardStateController mKeyguardStateController;
+    private @Mock FalsingManager mFalsingManager;
+    private @Mock AuthController mAuthController;
+    private @Mock DumpManager mDumpManager;
+    private @Mock AccessibilityManager mAccessibilityManager;
+    private @Mock ConfigurationController mConfigurationController;
+    private @Mock DelayableExecutor mDelayableExecutor;
+    private @Mock Vibrator mVibrator;
+    private @Mock AuthRippleController mAuthRippleController;
+    private @Mock LottieAnimationView mAodFp;
+
+    private LockIconViewController mLockIconViewController;
+
+    // Capture listeners so that they can be used to send events
+    @Captor private ArgumentCaptor<View.OnAttachStateChangeListener> mAttachCaptor =
+            ArgumentCaptor.forClass(View.OnAttachStateChangeListener.class);
+    private View.OnAttachStateChangeListener mAttachListener;
+
+    @Captor private ArgumentCaptor<AuthController.Callback> mAuthControllerCallbackCaptor;
+    private AuthController.Callback mAuthControllerCallback;
+
+    @Captor private ArgumentCaptor<PointF> mPointCaptor;
+
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+
+        when(mLockIconView.getResources()).thenReturn(mResources);
+        when(mLockIconView.getContext()).thenReturn(mContext);
+        when(mContext.getResources()).thenReturn(mResources);
+        when(mResources.getDisplayMetrics()).thenReturn(mDisplayMetrics);
+        when(mLockIconView.findViewById(anyInt())).thenReturn(mAodFp);
+
+        mLockIconViewController = new LockIconViewController(
+                mLockIconView,
+                mStatusBarStateController,
+                mKeyguardUpdateMonitor,
+                mKeyguardViewController,
+                mKeyguardStateController,
+                mFalsingManager,
+                mAuthController,
+                mDumpManager,
+                mAccessibilityManager,
+                mConfigurationController,
+                mDelayableExecutor,
+                mVibrator
+        );
+    }
+
+    @Test
+    public void testUpdateFingerprintLocationOnInit() {
+        // GIVEN fp sensor location is available pre-attached
+        final PointF udfpsLocation = new PointF(50, 75);
+        final int radius = 33;
+        final FingerprintSensorPropertiesInternal fpProps =
+                new FingerprintSensorPropertiesInternal(
+                        /* sensorId */ 0,
+                        /* strength */ 0,
+                        /* max enrollments per user */ 5,
+                        /* component info */ new ArrayList<>(),
+                        /* sensorType */ 3,
+                        /* resetLockoutRequiresHwToken */ false,
+                        (int) udfpsLocation.x, (int) udfpsLocation.y, radius);
+        when(mAuthController.getUdfpsSensorLocation()).thenReturn(udfpsLocation);
+        when(mAuthController.getUdfpsProps()).thenReturn(List.of(fpProps));
+
+        // WHEN lock icon view controller is initialized and attached
+        mLockIconViewController.init();
+        captureAttachListener();
+        mAttachListener.onViewAttachedToWindow(mLockIconView);
+
+        // THEN lock icon view location is updated with the same coordinates as fpProps
+        verify(mLockIconView).setCenterLocation(mPointCaptor.capture(), eq(radius));
+        assertEquals(udfpsLocation, mPointCaptor.getValue());
+    }
+
+    @Test
+    public void testUpdateFingerprintLocationOnAuthenticatorsRegistered() {
+        // GIVEN fp sensor location is not available pre-init
+        when(mAuthController.getFingerprintSensorLocation()).thenReturn(null);
+        when(mAuthController.getUdfpsProps()).thenReturn(null);
+        mLockIconViewController.init();
+        captureAttachListener();
+        mAttachListener.onViewAttachedToWindow(mLockIconView);
+
+        // GIVEN fp sensor location is available post-atttached
+        captureAuthControllerCallback();
+        final PointF udfpsLocation = new PointF(50, 75);
+        final int radius = 33;
+        final FingerprintSensorPropertiesInternal fpProps =
+                new FingerprintSensorPropertiesInternal(
+                        /* sensorId */ 0,
+                        /* strength */ 0,
+                        /* max enrollments per user */ 5,
+                        /* component info */ new ArrayList<>(),
+                        /* sensorType */ 3,
+                        /* resetLockoutRequiresHwToken */ false,
+                        (int) udfpsLocation.x, (int) udfpsLocation.y, radius);
+        when(mAuthController.getUdfpsSensorLocation()).thenReturn(udfpsLocation);
+        when(mAuthController.getUdfpsProps()).thenReturn(List.of(fpProps));
+
+        // WHEN all authenticators are registered
+        mAuthControllerCallback.onAllAuthenticatorsRegistered();
+
+        // THEN lock icon view location is updated with the same coordinates as fpProps
+        verify(mLockIconView).setCenterLocation(mPointCaptor.capture(), eq(radius));
+        assertEquals(udfpsLocation, mPointCaptor.getValue());
+    }
+
+    private void captureAuthControllerCallback() {
+        verify(mAuthController).addCallback(mAuthControllerCallbackCaptor.capture());
+        mAuthControllerCallback = mAuthControllerCallbackCaptor.getValue();
+    }
+
+    private void captureAttachListener() {
+        verify(mLockIconView).addOnAttachStateChangeListener(mAttachCaptor.capture());
+        mAttachListener = mAttachCaptor.getValue();
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java
index 3ee3e55..7f89b26 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java
@@ -139,7 +139,7 @@
                 () -> mock(AutoTileManager.class), mock(DumpManager.class),
                 mock(BroadcastDispatcher.class), Optional.of(mock(StatusBar.class)),
                 mock(QSLogger.class), mock(UiEventLogger.class), mock(UserTracker.class),
-                mock(SecureSettings.class), mock(CustomTileStatePersister.class));
+                mock(SecureSettings.class), mock(CustomTileStatePersister.class), mFeatureFlags);
         qs.setHost(host);
 
         qs.setListening(true);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java
index 9e97f80..4cbad5f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java
@@ -63,6 +63,7 @@
 import com.android.systemui.qs.tileimpl.QSTileImpl;
 import com.android.systemui.settings.UserTracker;
 import com.android.systemui.shared.plugins.PluginManager;
+import com.android.systemui.statusbar.FeatureFlags;
 import com.android.systemui.statusbar.phone.AutoTileManager;
 import com.android.systemui.statusbar.phone.StatusBar;
 import com.android.systemui.statusbar.phone.StatusBarIconController;
@@ -124,6 +125,8 @@
     private SecureSettings mSecureSettings;
     @Mock
     private CustomTileStatePersister mCustomTileStatePersister;
+    @Mock
+    private FeatureFlags mFeatureFlags;
 
     private Handler mHandler;
     private TestableLooper mLooper;
@@ -137,9 +140,9 @@
         mQSTileHost = new TestQSTileHost(mContext, mIconController, mDefaultFactory, mHandler,
                 mLooper.getLooper(), mPluginManager, mTunerService, mAutoTiles, mDumpManager,
                 mBroadcastDispatcher, mStatusBar, mQSLogger, mUiEventLogger, mUserTracker,
-                mSecureSettings, mCustomTileStatePersister);
+                mSecureSettings, mCustomTileStatePersister, mFeatureFlags);
         setUpTileFactory();
-
+        when(mFeatureFlags.isProviderModelSettingEnabled()).thenReturn(false);
         when(mSecureSettings.getStringForUser(eq(QSTileHost.TILES_SETTING), anyInt()))
                 .thenReturn("");
     }
@@ -169,13 +172,13 @@
 
     @Test
     public void testLoadTileSpecs_emptySetting() {
-        List<String> tiles = QSTileHost.loadTileSpecs(mContext, "");
+        List<String> tiles = QSTileHost.loadTileSpecs(mContext, "", mFeatureFlags);
         assertFalse(tiles.isEmpty());
     }
 
     @Test
     public void testLoadTileSpecs_nullSetting() {
-        List<String> tiles = QSTileHost.loadTileSpecs(mContext, null);
+        List<String> tiles = QSTileHost.loadTileSpecs(mContext, null, mFeatureFlags);
         assertFalse(tiles.isEmpty());
     }
 
@@ -189,6 +192,55 @@
     }
 
     @Test
+    public void testRemoveWifiAndCellularWithoutInternet() {
+        when(mFeatureFlags.isProviderModelSettingEnabled()).thenReturn(true);
+        mQSTileHost.onTuningChanged(QSTileHost.TILES_SETTING, "wifi, spec1, cell, spec2");
+
+        assertEquals("internet", mQSTileHost.mTileSpecs.get(0));
+        assertEquals("spec1", mQSTileHost.mTileSpecs.get(1));
+        assertEquals("spec2", mQSTileHost.mTileSpecs.get(2));
+    }
+
+    @Test
+    public void testRemoveWifiAndCellularWithInternet() {
+        when(mFeatureFlags.isProviderModelSettingEnabled()).thenReturn(true);
+        mQSTileHost.onTuningChanged(QSTileHost.TILES_SETTING, "wifi, spec1, cell, spec2, internet");
+
+        assertEquals("spec1", mQSTileHost.mTileSpecs.get(0));
+        assertEquals("spec2", mQSTileHost.mTileSpecs.get(1));
+        assertEquals("internet", mQSTileHost.mTileSpecs.get(2));
+    }
+
+    @Test
+    public void testRemoveWifiWithoutInternet() {
+        when(mFeatureFlags.isProviderModelSettingEnabled()).thenReturn(true);
+        mQSTileHost.onTuningChanged(QSTileHost.TILES_SETTING, "spec1, wifi, spec2");
+
+        assertEquals("spec1", mQSTileHost.mTileSpecs.get(0));
+        assertEquals("internet", mQSTileHost.mTileSpecs.get(1));
+        assertEquals("spec2", mQSTileHost.mTileSpecs.get(2));
+    }
+
+    @Test
+    public void testRemoveCellWithInternet() {
+        when(mFeatureFlags.isProviderModelSettingEnabled()).thenReturn(true);
+        mQSTileHost.onTuningChanged(QSTileHost.TILES_SETTING, "spec1, spec2, cell, internet");
+
+        assertEquals("spec1", mQSTileHost.mTileSpecs.get(0));
+        assertEquals("spec2", mQSTileHost.mTileSpecs.get(1));
+        assertEquals("internet", mQSTileHost.mTileSpecs.get(2));
+    }
+
+    @Test
+    public void testNoWifiNoCellularNoInternet() {
+        when(mFeatureFlags.isProviderModelSettingEnabled()).thenReturn(true);
+        mQSTileHost.onTuningChanged(QSTileHost.TILES_SETTING, "spec1,spec2");
+
+        assertEquals("spec1", mQSTileHost.mTileSpecs.get(0));
+        assertEquals("spec2", mQSTileHost.mTileSpecs.get(1));
+    }
+
+    @Test
     public void testSpecWithInvalidDoesNotUseDefault() {
         mContext.getOrCreateTestableResources()
                 .addOverride(R.string.quick_settings_tiles, "spec1,spec2");
@@ -321,7 +373,7 @@
 
     @Test
     public void testLoadTileSpec_repeated() {
-        List<String> specs = QSTileHost.loadTileSpecs(mContext, "spec1,spec1,spec2");
+        List<String> specs = QSTileHost.loadTileSpecs(mContext, "spec1,spec1,spec2", mFeatureFlags);
 
         assertEquals(2, specs.size());
         assertEquals("spec1", specs.get(0));
@@ -332,7 +384,7 @@
     public void testLoadTileSpec_repeatedInDefault() {
         mContext.getOrCreateTestableResources()
                 .addOverride(R.string.quick_settings_tiles_default, "spec1,spec1");
-        List<String> specs = QSTileHost.loadTileSpecs(mContext, "default");
+        List<String> specs = QSTileHost.loadTileSpecs(mContext, "default", mFeatureFlags);
 
         // Remove spurious tiles, like dbg:mem
         specs.removeIf(spec -> !"spec1".equals(spec));
@@ -343,7 +395,7 @@
     public void testLoadTileSpec_repeatedDefaultAndSetting() {
         mContext.getOrCreateTestableResources()
                 .addOverride(R.string.quick_settings_tiles_default, "spec1");
-        List<String> specs = QSTileHost.loadTileSpecs(mContext, "default,spec1");
+        List<String> specs = QSTileHost.loadTileSpecs(mContext, "default,spec1", mFeatureFlags);
 
         // Remove spurious tiles, like dbg:mem
         specs.removeIf(spec -> !"spec1".equals(spec));
@@ -371,11 +423,12 @@
                 Provider<AutoTileManager> autoTiles, DumpManager dumpManager,
                 BroadcastDispatcher broadcastDispatcher, StatusBar statusBar, QSLogger qsLogger,
                 UiEventLogger uiEventLogger, UserTracker userTracker,
-                SecureSettings secureSettings, CustomTileStatePersister customTileStatePersister) {
+                SecureSettings secureSettings, CustomTileStatePersister customTileStatePersister,
+                FeatureFlags featureFlags) {
             super(context, iconController, defaultFactory, mainHandler, bgLooper, pluginManager,
                     tunerService, autoTiles, dumpManager, broadcastDispatcher,
                     Optional.of(statusBar), qsLogger, uiEventLogger, userTracker, secureSettings,
-                    customTileStatePersister);
+                    customTileStatePersister, featureFlags);
         }
 
         @Override
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/customize/TileQueryHelperTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/customize/TileQueryHelperTest.java
index 4efcc5c..c5b6709 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/customize/TileQueryHelperTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/customize/TileQueryHelperTest.java
@@ -58,6 +58,7 @@
 import com.android.systemui.plugins.qs.QSTile;
 import com.android.systemui.qs.QSTileHost;
 import com.android.systemui.settings.UserTracker;
+import com.android.systemui.statusbar.FeatureFlags;
 import com.android.systemui.util.concurrency.FakeExecutor;
 import com.android.systemui.util.time.FakeSystemClock;
 
@@ -108,6 +109,8 @@
     private PackageManager mPackageManager;
     @Mock
     private UserTracker mUserTracker;
+    @Mock
+    private FeatureFlags mFeatureFlags;
     @Captor
     private ArgumentCaptor<List<TileQueryHelper.TileInfo>> mCaptor;
 
@@ -133,12 +136,12 @@
                     }
                 }
         ).when(mQSTileHost).createTile(anyString());
-
+        when(mFeatureFlags.isProviderModelSettingEnabled()).thenReturn(false);
         FakeSystemClock clock = new FakeSystemClock();
         mMainExecutor = new FakeExecutor(clock);
         mBgExecutor = new FakeExecutor(clock);
         mTileQueryHelper = new TileQueryHelper(
-                mContext, mUserTracker, mMainExecutor, mBgExecutor);
+                mContext, mUserTracker, mMainExecutor, mBgExecutor, mFeatureFlags);
         mTileQueryHelper.setListener(mListener);
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServicesTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServicesTest.java
index 2b18404..01fa222 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServicesTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServicesTest.java
@@ -48,6 +48,7 @@
 import com.android.systemui.qs.tileimpl.QSFactoryImpl;
 import com.android.systemui.settings.UserTracker;
 import com.android.systemui.shared.plugins.PluginManager;
+import com.android.systemui.statusbar.FeatureFlags;
 import com.android.systemui.statusbar.phone.AutoTileManager;
 import com.android.systemui.statusbar.phone.StatusBar;
 import com.android.systemui.statusbar.phone.StatusBarIconController;
@@ -98,6 +99,8 @@
     private UserTracker mUserTracker;
     @Mock
     private SecureSettings  mSecureSettings;
+    @Mock
+    private FeatureFlags mFeatureFlags;
 
     @Before
     public void setUp() throws Exception {
@@ -119,7 +122,8 @@
                 mUiEventLogger,
                 mUserTracker,
                 mSecureSettings,
-                mock(CustomTileStatePersister.class));
+                mock(CustomTileStatePersister.class),
+                mFeatureFlags);
         mTileService = new TestTileServices(host, Looper.getMainLooper(), mBroadcastDispatcher,
                 mUserTracker);
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationShadeDepthControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationShadeDepthControllerTest.kt
index 7c045c1..a7b1446 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationShadeDepthControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationShadeDepthControllerTest.kt
@@ -69,7 +69,6 @@
     @Mock private lateinit var root: View
     @Mock private lateinit var viewRootImpl: ViewRootImpl
     @Mock private lateinit var windowToken: IBinder
-    @Mock private lateinit var shadeSpring: NotificationShadeDepthController.DepthAnimation
     @Mock private lateinit var shadeAnimation: NotificationShadeDepthController.DepthAnimation
     @Mock private lateinit var brightnessSpring: NotificationShadeDepthController.DepthAnimation
     @Mock private lateinit var listener: NotificationShadeDepthController.DepthListener
@@ -89,10 +88,10 @@
         `when`(root.isAttachedToWindow).thenReturn(true)
         `when`(statusBarStateController.state).then { statusBarState }
         `when`(blurUtils.blurRadiusOfRatio(anyFloat())).then { answer ->
-            (answer.arguments[0] as Float * maxBlur).toInt()
+            answer.arguments[0] as Float * maxBlur.toFloat()
         }
-        `when`(blurUtils.ratioOfBlurRadius(anyInt())).then { answer ->
-            answer.arguments[0] as Int / maxBlur.toFloat()
+        `when`(blurUtils.ratioOfBlurRadius(anyFloat())).then { answer ->
+            answer.arguments[0] as Float / maxBlur.toFloat()
         }
         `when`(blurUtils.supportsBlursOnWindows()).thenReturn(true)
         `when`(blurUtils.maxBlurRadius).thenReturn(maxBlur)
@@ -102,7 +101,6 @@
                 statusBarStateController, blurUtils, biometricUnlockController,
                 keyguardStateController, choreographer, wallpaperManager,
                 notificationShadeWindowController, dozeParameters, dumpManager)
-        notificationShadeDepthController.shadeSpring = shadeSpring
         notificationShadeDepthController.shadeAnimation = shadeAnimation
         notificationShadeDepthController.brightnessMirrorSpring = brightnessSpring
         notificationShadeDepthController.root = root
@@ -123,7 +121,6 @@
     fun onPanelExpansionChanged_apliesBlur_ifShade() {
         notificationShadeDepthController.onPanelExpansionChanged(1f /* expansion */,
                 false /* tracking */)
-        verify(shadeSpring).animateTo(eq(maxBlur), any())
         verify(shadeAnimation).animateTo(eq(maxBlur), any())
     }
 
@@ -172,12 +169,10 @@
     @Test
     fun onStateChanged_reevalutesBlurs_ifSameRadiusAndNewState() {
         onPanelExpansionChanged_apliesBlur_ifShade()
-        clearInvocations(shadeSpring)
-        clearInvocations(shadeAnimation)
+        clearInvocations(choreographer)
 
         statusBarState = StatusBarState.KEYGUARD
         statusBarStateListener.onStateChanged(statusBarState)
-        verify(shadeSpring).animateTo(eq(0), any())
         verify(shadeAnimation).animateTo(eq(0), any())
     }
 
@@ -186,7 +181,7 @@
         notificationShadeDepthController.qsPanelExpansion = 1f
         notificationShadeDepthController.onPanelExpansionChanged(0.5f, tracking = false)
         notificationShadeDepthController.updateBlurCallback.doFrame(0)
-        verify(blurUtils).applyBlur(any(), eq(maxBlur / 2), eq(false))
+        verify(blurUtils).applyBlur(any(), anyInt(), eq(false))
     }
 
     @Test
@@ -207,10 +202,10 @@
     fun setFullShadeTransition_appliesBlur_onlyIfSupported() {
         reset(blurUtils)
         `when`(blurUtils.blurRadiusOfRatio(anyFloat())).then { answer ->
-            (answer.arguments[0] as Float * maxBlur).toInt()
+            answer.arguments[0] as Float * maxBlur
         }
-        `when`(blurUtils.ratioOfBlurRadius(anyInt())).then { answer ->
-            answer.arguments[0] as Int / maxBlur.toFloat()
+        `when`(blurUtils.ratioOfBlurRadius(anyFloat())).then { answer ->
+            answer.arguments[0] as Float / maxBlur.toFloat()
         }
         `when`(blurUtils.maxBlurRadius).thenReturn(maxBlur)
         `when`(blurUtils.maxBlurRadius).thenReturn(maxBlur)
@@ -239,16 +234,16 @@
 
     @Test
     fun updateBlurCallback_setsBlur_whenExpanded() {
-        `when`(shadeSpring.radius).thenReturn(maxBlur)
-        `when`(shadeAnimation.radius).thenReturn(maxBlur)
+        notificationShadeDepthController.onPanelExpansionChanged(1f, false)
+        `when`(shadeAnimation.radius).thenReturn(maxBlur.toFloat())
         notificationShadeDepthController.updateBlurCallback.doFrame(0)
         verify(blurUtils).applyBlur(any(), eq(maxBlur), eq(false))
     }
 
     @Test
     fun updateBlurCallback_ignoreShadeBlurUntilHidden_overridesZoom() {
-        `when`(shadeSpring.radius).thenReturn(maxBlur)
-        `when`(shadeAnimation.radius).thenReturn(maxBlur)
+        notificationShadeDepthController.onPanelExpansionChanged(1f, false)
+        `when`(shadeAnimation.radius).thenReturn(maxBlur.toFloat())
         notificationShadeDepthController.blursDisabledForAppLaunch = true
         notificationShadeDepthController.updateBlurCallback.doFrame(0)
         verify(blurUtils).applyBlur(any(), eq(0), eq(false))
@@ -293,8 +288,8 @@
         // Brightness mirror is fully visible
         `when`(brightnessSpring.ratio).thenReturn(1f)
         // And shade is blurred
-        `when`(shadeSpring.radius).thenReturn(maxBlur)
-        `when`(shadeAnimation.radius).thenReturn(maxBlur)
+        notificationShadeDepthController.onPanelExpansionChanged(1f, false)
+        `when`(shadeAnimation.radius).thenReturn(maxBlur.toFloat())
 
         notificationShadeDepthController.updateBlurCallback.doFrame(0)
         verify(notificationShadeWindowController).setBackgroundBlurRadius(eq(0))
@@ -304,10 +299,8 @@
 
     @Test
     fun ignoreShadeBlurUntilHidden_whennNull_ignoresIfShadeHasNoBlur() {
-        `when`(shadeSpring.radius).thenReturn(0)
-        `when`(shadeAnimation.radius).thenReturn(0)
+        `when`(shadeAnimation.radius).thenReturn(0f)
         notificationShadeDepthController.blursDisabledForAppLaunch = true
-        verify(shadeSpring, never()).animateTo(anyInt(), any())
         verify(shadeAnimation, never()).animateTo(anyInt(), any())
     }
 }
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
index b03df880..4151ab2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
@@ -290,6 +290,8 @@
     @Test
     public void testUpdateFooter_noNotifications() {
         setBarStateForTest(StatusBarState.SHADE);
+        mStackScroller.setCurrentUserSetup(true);
+
         FooterView view = mock(FooterView.class);
         mStackScroller.setFooterView(view);
         mStackScroller.updateFooter();
@@ -299,6 +301,7 @@
     @Test
     public void testUpdateFooter_remoteInput() {
         setBarStateForTest(StatusBarState.SHADE);
+        mStackScroller.setCurrentUserSetup(true);
 
         ExpandableNotificationRow row = mock(ExpandableNotificationRow.class);
         when(row.canViewBeDismissed()).thenReturn(true);
@@ -318,6 +321,7 @@
     @Test
     public void testUpdateFooter_oneClearableNotification() {
         setBarStateForTest(StatusBarState.SHADE);
+        mStackScroller.setCurrentUserSetup(true);
 
         when(mEmptyShadeView.getVisibility()).thenReturn(GONE);
         when(mStackScrollLayoutController.hasActiveClearableNotifications(ROWS_ALL))
@@ -331,8 +335,25 @@
     }
 
     @Test
+    public void testUpdateFooter_oneClearableNotification_beforeUserSetup() {
+        setBarStateForTest(StatusBarState.SHADE);
+        mStackScroller.setCurrentUserSetup(false);
+
+        when(mEmptyShadeView.getVisibility()).thenReturn(GONE);
+        when(mStackScrollLayoutController.hasActiveClearableNotifications(ROWS_ALL))
+                .thenReturn(true);
+        when(mStackScrollLayoutController.hasActiveNotifications()).thenReturn(true);
+
+        FooterView view = mock(FooterView.class);
+        mStackScroller.setFooterView(view);
+        mStackScroller.updateFooter();
+        verify(mStackScroller).updateFooterView(false, true, true);
+    }
+
+    @Test
     public void testUpdateFooter_oneNonClearableNotification() {
         setBarStateForTest(StatusBarState.SHADE);
+        mStackScroller.setCurrentUserSetup(true);
 
         ExpandableNotificationRow row = mock(ExpandableNotificationRow.class);
         when(row.canViewBeDismissed()).thenReturn(false);
@@ -351,6 +372,8 @@
 
     @Test
     public void testUpdateFooter_atEnd() {
+        mStackScroller.setCurrentUserSetup(true);
+
         // add footer
         mStackScroller.inflateFooterView();
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollerControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollerControllerTest.java
index f376e88..42f3889 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollerControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollerControllerTest.java
@@ -74,6 +74,7 @@
 import com.android.systemui.statusbar.phone.ShadeController;
 import com.android.systemui.statusbar.phone.StatusBar;
 import com.android.systemui.statusbar.policy.ConfigurationController;
+import com.android.systemui.statusbar.policy.DeviceProvisionedController;
 import com.android.systemui.statusbar.policy.ZenModeController;
 import com.android.systemui.tuner.TunerService;
 
@@ -98,6 +99,7 @@
     @Mock private HeadsUpManagerPhone mHeadsUpManager;
     @Mock private NotificationRoundnessManager mNotificationRoundnessManager;
     @Mock private TunerService mTunerService;
+    @Mock private DeviceProvisionedController mDeviceProvisionedController;
     @Mock private DynamicPrivacyController mDynamicPrivacyController;
     @Mock private ConfigurationController mConfigurationController;
     @Mock private NotificationStackScrollLayout mNotificationStackScrollLayout;
@@ -153,6 +155,7 @@
                 mHeadsUpManager,
                 mNotificationRoundnessManager,
                 mTunerService,
+                mDeviceProvisionedController,
                 mDynamicPrivacyController,
                 mConfigurationController,
                 mSysuiStatusBarStateController,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java
index 60f0b68..4276f7c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java
@@ -274,6 +274,26 @@
     }
 
     @Test
+    public void onBiometricAuthenticated_onLockScreen() {
+        // GIVEN not dozing
+        when(mUpdateMonitor.isDeviceInteractive()).thenReturn(true);
+
+        // WHEN we want to unlock collapse
+        mBiometricUnlockController.startWakeAndUnlock(
+                BiometricUnlockController.MODE_UNLOCK_COLLAPSING);
+
+        // THEN we collpase the panels and notify authenticated
+        verify(mShadeController).animateCollapsePanels(
+                /* flags */ anyInt(),
+                /* force */ eq(true),
+                /* delayed */ eq(false),
+                /* speedUpFactor */ anyFloat()
+        );
+        verify(mStatusBarKeyguardViewManager).notifyKeyguardAuthenticated(
+                /* strongAuth */ eq(false));
+    }
+
+    @Test
     public void onBiometricAuthenticated_whenFace_noBypass_encrypted_doNothing() {
         reset(mUpdateMonitor);
         mBiometricUnlockController.setKeyguardViewController(mStatusBarKeyguardViewManager);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewTest.java
index 6c1a3c9..9b7c78f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewTest.java
@@ -27,6 +27,7 @@
 
 import androidx.test.filters.SmallTest;
 
+import com.android.keyguard.LockIconViewController;
 import com.android.systemui.R;
 import com.android.systemui.SystemUIFactory;
 import com.android.systemui.SysuiTestCase;
@@ -90,6 +91,7 @@
     @Mock private NotificationStackScrollLayoutController mNotificationStackScrollLayoutController;
     @Mock private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
     @Mock private LockscreenShadeTransitionController mLockscreenShadeTransitionController;
+    @Mock private LockIconViewController mLockIconViewController;
 
     @Before
     public void setUp() {
@@ -131,7 +133,8 @@
                 mNotificationPanelViewController,
                 mStatusBarViewFactory,
                 mNotificationStackScrollLayoutController,
-                mStatusBarKeyguardViewManager);
+                mStatusBarKeyguardViewManager,
+                mLockIconViewController);
         mController.setupExpandedStatusBar();
         mController.setService(mStatusBar, mNotificationShadeWindowController);
         mController.setDragDownHelper(mDragDownHelper);
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 678b193..6de5866 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
@@ -16,10 +16,13 @@
 
 package com.android.systemui.statusbar.phone;
 
+import static com.android.systemui.statusbar.phone.ScrimController.KEYGUARD_SCRIM_ALPHA;
 import static com.android.systemui.statusbar.phone.ScrimController.OPAQUE;
 import static com.android.systemui.statusbar.phone.ScrimController.SEMI_TRANSPARENT;
 import static com.android.systemui.statusbar.phone.ScrimController.TRANSPARENT;
 
+import static com.google.common.truth.Truth.assertThat;
+
 import static org.junit.Assert.assertEquals;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyFloat;
@@ -734,20 +737,13 @@
     }
 
     @Test
-    public void transitionToUnlockedFromAod() {
-        // Simulate unlock with fingerprint
-        mScrimController.transitionTo(ScrimState.AOD);
+    public void transitionToUnlockedFromOff() {
+        // Simulate unlock with fingerprint without AOD
+        mScrimController.transitionTo(ScrimState.OFF);
         mScrimController.setPanelExpansion(0f);
         finishAnimationsImmediately();
         mScrimController.transitionTo(ScrimState.UNLOCKED);
 
-        // Immediately tinted black after the transition starts
-        assertScrimTinted(Map.of(
-                mScrimInFront, true,
-                mScrimBehind, true,
-                mScrimForBubble, true
-        ));
-
         finishAnimationsImmediately();
 
         // All scrims should be transparent at the end of fade transition.
@@ -765,6 +761,28 @@
     }
 
     @Test
+    public void transitionToUnlockedFromAod() {
+        // Simulate unlock with fingerprint
+        mScrimController.transitionTo(ScrimState.AOD);
+        mScrimController.setPanelExpansion(0f);
+        finishAnimationsImmediately();
+        mScrimController.transitionTo(ScrimState.UNLOCKED);
+
+        finishAnimationsImmediately();
+
+        // All scrims should be transparent at the end of fade transition.
+        assertScrimAlpha(Map.of(
+                mScrimInFront, TRANSPARENT,
+                mScrimBehind, TRANSPARENT));
+
+        // Make sure at the very end of the animation, we're reset to transparent
+        assertScrimTinted(Map.of(
+                mScrimInFront, false,
+                mScrimBehind, true
+        ));
+    }
+
+    @Test
     public void scrimBlanksBeforeLeavingAod() {
         // Simulate unlock with fingerprint
         mScrimController.transitionTo(ScrimState.AOD);
@@ -1081,6 +1099,26 @@
     }
 
     @Test
+    public void testDoesntAnimate_whenUnlocking() {
+        // LightRevealScrim will animate the transition, we should only hide the keyguard scrims.
+        ScrimState.UNLOCKED.prepare(ScrimState.KEYGUARD);
+        assertThat(ScrimState.UNLOCKED.getAnimateChange()).isTrue();
+        ScrimState.UNLOCKED.prepare(ScrimState.PULSING);
+        assertThat(ScrimState.UNLOCKED.getAnimateChange()).isFalse();
+
+        ScrimState.UNLOCKED.prepare(ScrimState.KEYGUARD);
+        assertThat(ScrimState.UNLOCKED.getAnimateChange()).isTrue();
+        ScrimState.UNLOCKED.prepare(ScrimState.AOD);
+        assertThat(ScrimState.UNLOCKED.getAnimateChange()).isFalse();
+
+        // LightRevealScrim doesn't animate when AOD is disabled. We need to use the legacy anim.
+        ScrimState.UNLOCKED.prepare(ScrimState.KEYGUARD);
+        assertThat(ScrimState.UNLOCKED.getAnimateChange()).isTrue();
+        ScrimState.UNLOCKED.prepare(ScrimState.OFF);
+        assertThat(ScrimState.UNLOCKED.getAnimateChange()).isTrue();
+    }
+
+    @Test
     public void testScrimsVisible_whenShadeVisible_clippingQs() {
         mScrimController.setClipsQsScrim(true);
         mScrimController.transitionTo(ScrimState.UNLOCKED);
@@ -1138,6 +1176,21 @@
     }
 
     @Test
+    public void testNotificationTransparency_unnocclusion() {
+        mScrimController.transitionTo(ScrimState.KEYGUARD);
+        mScrimController.setUnocclusionAnimationRunning(true);
+
+        assertAlphaAfterExpansion(mNotificationsScrim, /* alpha */ KEYGUARD_SCRIM_ALPHA,
+                /* expansion */ 0.0f);
+        assertAlphaAfterExpansion(mNotificationsScrim, /* alpha */ KEYGUARD_SCRIM_ALPHA,
+                /* expansion */ 1.0f);
+
+        // Verify normal behavior after
+        mScrimController.setUnocclusionAnimationRunning(false);
+        assertAlphaAfterExpansion(mNotificationsScrim, /* alpha */ 0.2f, /* expansion */ 0.4f);
+    }
+
+    @Test
     public void testNotificationTransparency_inKeyguardState() {
         mScrimController.transitionTo(ScrimState.KEYGUARD);
 
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 2c2833a..bd9835c 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
@@ -812,6 +812,30 @@
     }
 
     @Test
+    public void testTransitionLaunch_goesToUnlocked() {
+        mStatusBar.setBarStateForTest(StatusBarState.KEYGUARD);
+        mStatusBar.showKeyguardImpl();
+
+        // Starting a pulse should change the scrim controller to the pulsing state
+        when(mNotificationPanelViewController.isLaunchTransitionRunning()).thenReturn(true);
+        when(mNotificationPanelViewController.isLaunchingAffordanceWithPreview()).thenReturn(true);
+        mStatusBar.updateScrimController();
+        verify(mScrimController).transitionTo(eq(ScrimState.UNLOCKED), any());
+    }
+
+    @Test
+    public void testTransitionLaunch_noPreview_doesntGoUnlocked() {
+        mStatusBar.setBarStateForTest(StatusBarState.KEYGUARD);
+        mStatusBar.showKeyguardImpl();
+
+        // Starting a pulse should change the scrim controller to the pulsing state
+        when(mNotificationPanelViewController.isLaunchTransitionRunning()).thenReturn(true);
+        when(mNotificationPanelViewController.isLaunchingAffordanceWithPreview()).thenReturn(false);
+        mStatusBar.updateScrimController();
+        verify(mScrimController).transitionTo(eq(ScrimState.KEYGUARD));
+    }
+
+    @Test
     public void testSetOccluded_propagatesToScrimController() {
         mStatusBar.setOccluded(true);
         verify(mScrimController).setKeyguardOccluded(eq(true));
diff --git a/services/core/java/com/android/server/RescueParty.java b/services/core/java/com/android/server/RescueParty.java
index c3543e7..c1c9fbb 100644
--- a/services/core/java/com/android/server/RescueParty.java
+++ b/services/core/java/com/android/server/RescueParty.java
@@ -327,18 +327,23 @@
         }
     }
 
-    private static int getMaxRescueLevel() {
-        return SystemProperties.getBoolean(PROP_DISABLE_FACTORY_RESET_FLAG, false)
-                ? LEVEL_RESET_SETTINGS_TRUSTED_DEFAULTS : LEVEL_FACTORY_RESET;
+    private static int getMaxRescueLevel(boolean mayPerformFactoryReset) {
+        if (!mayPerformFactoryReset
+                || SystemProperties.getBoolean(PROP_DISABLE_FACTORY_RESET_FLAG, false)) {
+            return LEVEL_RESET_SETTINGS_TRUSTED_DEFAULTS;
+        }
+        return LEVEL_FACTORY_RESET;
     }
 
     /**
      * Get the rescue level to perform if this is the n-th attempt at mitigating failure.
      *
      * @param mitigationCount: the mitigation attempt number (1 = first attempt etc.)
+     * @param mayPerformFactoryReset: whether or not a factory reset may be performed for the given
+     *                              failure.
      * @return the rescue level for the n-th mitigation attempt.
      */
-    private static int getRescueLevel(int mitigationCount) {
+    private static int getRescueLevel(int mitigationCount, boolean mayPerformFactoryReset) {
         if (mitigationCount == 1) {
             return LEVEL_RESET_SETTINGS_UNTRUSTED_DEFAULTS;
         } else if (mitigationCount == 2) {
@@ -346,9 +351,9 @@
         } else if (mitigationCount == 3) {
             return LEVEL_RESET_SETTINGS_TRUSTED_DEFAULTS;
         } else if (mitigationCount == 4) {
-            return Math.min(getMaxRescueLevel(), LEVEL_WARM_REBOOT);
+            return Math.min(getMaxRescueLevel(mayPerformFactoryReset), LEVEL_WARM_REBOOT);
         } else if (mitigationCount >= 5) {
-            return Math.min(getMaxRescueLevel(), LEVEL_FACTORY_RESET);
+            return Math.min(getMaxRescueLevel(mayPerformFactoryReset), LEVEL_FACTORY_RESET);
         } else {
             Slog.w(TAG, "Expected positive mitigation count, was " + mitigationCount);
             return LEVEL_NONE;
@@ -614,7 +619,8 @@
                 @FailureReasons int failureReason, int mitigationCount) {
             if (!isDisabled() && (failureReason == PackageWatchdog.FAILURE_REASON_APP_CRASH
                     || failureReason == PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING)) {
-                return mapRescueLevelToUserImpact(getRescueLevel(mitigationCount));
+                return mapRescueLevelToUserImpact(getRescueLevel(mitigationCount,
+                        mayPerformFactoryReset(failedPackage)));
             } else {
                 return PackageHealthObserverImpact.USER_IMPACT_NONE;
             }
@@ -628,7 +634,8 @@
             }
             if (failureReason == PackageWatchdog.FAILURE_REASON_APP_CRASH
                     || failureReason == PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING) {
-                final int level = getRescueLevel(mitigationCount);
+                final int level = getRescueLevel(mitigationCount,
+                        mayPerformFactoryReset(failedPackage));
                 executeRescueLevel(mContext,
                         failedPackage == null ? null : failedPackage.getPackageName(), level);
                 return true;
@@ -653,12 +660,7 @@
             } catch (PackageManager.NameNotFoundException ignore) {
             }
 
-            try {
-                ApplicationInfo info = pm.getApplicationInfo(packageName, 0);
-                return (info.flags & PERSISTENT_MASK) == PERSISTENT_MASK;
-            } catch (PackageManager.NameNotFoundException e) {
-                return false;
-            }
+            return isPersistentSystemApp(packageName);
         }
 
         @Override
@@ -666,7 +668,7 @@
             if (isDisabled()) {
                 return PackageHealthObserverImpact.USER_IMPACT_NONE;
             }
-            return mapRescueLevelToUserImpact(getRescueLevel(mitigationCount));
+            return mapRescueLevelToUserImpact(getRescueLevel(mitigationCount, true));
         }
 
         @Override
@@ -674,7 +676,8 @@
             if (isDisabled()) {
                 return false;
             }
-            executeRescueLevel(mContext, /*failedPackage=*/ null, getRescueLevel(mitigationCount));
+            executeRescueLevel(mContext, /*failedPackage=*/ null,
+                    getRescueLevel(mitigationCount, true));
             return true;
         }
 
@@ -683,6 +686,29 @@
             return NAME;
         }
 
+        /**
+         * Returns {@code true} if the failing package is non-null and performing a reboot or
+         * prompting a factory reset is an acceptable mitigation strategy for the package's
+         * failure, {@code false} otherwise.
+         */
+        private boolean mayPerformFactoryReset(@Nullable VersionedPackage failingPackage) {
+            if (failingPackage == null) {
+                return false;
+            }
+
+            return isPersistentSystemApp(failingPackage.getPackageName());
+        }
+
+        private boolean isPersistentSystemApp(@NonNull String packageName) {
+            PackageManager pm = mContext.getPackageManager();
+            try {
+                ApplicationInfo info = pm.getApplicationInfo(packageName, 0);
+                return (info.flags & PERSISTENT_MASK) == PERSISTENT_MASK;
+            } catch (PackageManager.NameNotFoundException e) {
+                return false;
+            }
+        }
+
         private synchronized void recordDeviceConfigAccess(@NonNull String callingPackage,
                 @NonNull String namespace) {
             // Record it in calling packages to namespace map
diff --git a/services/core/java/com/android/server/SensorPrivacyService.java b/services/core/java/com/android/server/SensorPrivacyService.java
index 91b2440..75d0796 100644
--- a/services/core/java/com/android/server/SensorPrivacyService.java
+++ b/services/core/java/com/android/server/SensorPrivacyService.java
@@ -708,6 +708,16 @@
         @Override
         public void setIndividualSensorPrivacy(@UserIdInt int userId,
                 @SensorPrivacyManager.Sources.Source int source, int sensor, boolean enable) {
+            if (DEBUG) {
+                Log.d(TAG, "callingUid=" + Binder.getCallingUid()
+                        + " callingPid=" + Binder.getCallingPid()
+                        + " setIndividualSensorPrivacy("
+                        + "userId=" + userId
+                        + " source=" + source
+                        + " sensor=" + sensor
+                        + " enable=" + enable
+                        + ")");
+            }
             enforceManageSensorPrivacyPermission();
             if (userId == UserHandle.USER_CURRENT) {
                 userId = mCurrentUser;
@@ -892,6 +902,14 @@
 
         @Override
         public boolean isIndividualSensorPrivacyEnabled(@UserIdInt int userId, int sensor) {
+            if (DEBUG) {
+                Log.d(TAG, "callingUid=" + Binder.getCallingUid()
+                        + " callingPid=" + Binder.getCallingPid()
+                        + " isIndividualSensorPrivacyEnabled("
+                        + "userId=" + userId
+                        + " sensor=" + sensor
+                        + ")");
+            }
             enforceObserveSensorPrivacyPermission();
             if (userId == UserHandle.USER_CURRENT) {
                 userId = mCurrentUser;
diff --git a/services/core/java/com/android/server/TelephonyRegistry.java b/services/core/java/com/android/server/TelephonyRegistry.java
index edf832f..b4413a4 100644
--- a/services/core/java/com/android/server/TelephonyRegistry.java
+++ b/services/core/java/com/android/server/TelephonyRegistry.java
@@ -1889,11 +1889,12 @@
                         try {
                             if (!mConfigurationProvider.isDisplayInfoNrAdvancedSupported(
                                     r.callingPackage, Binder.getCallingUserHandle())) {
-                                telephonyDisplayInfo =
+                                r.callback.onDisplayInfoChanged(
                                         getBackwardCompatibleTelephonyDisplayInfo(
-                                                telephonyDisplayInfo);
+                                                telephonyDisplayInfo));
+                            } else {
+                                r.callback.onDisplayInfoChanged(telephonyDisplayInfo);
                             }
-                            r.callback.onDisplayInfoChanged(telephonyDisplayInfo);
                         } catch (RemoteException ex) {
                             mRemoveList.add(r.binder);
                         }
diff --git a/services/core/java/com/android/server/accounts/AccountManagerService.java b/services/core/java/com/android/server/accounts/AccountManagerService.java
index 731f7fe..ab2147d 100644
--- a/services/core/java/com/android/server/accounts/AccountManagerService.java
+++ b/services/core/java/com/android/server/accounts/AccountManagerService.java
@@ -1834,6 +1834,11 @@
                                 + ", skipping since the account already exists");
                         return false;
                     }
+                    if (accounts.accountsDb.findAllDeAccounts().size() > 100) {
+                        Log.w(TAG, "insertAccountIntoDatabase: " + account.toSafeString()
+                                + ", skipping since more than 50 accounts on device exist");
+                        return false;
+                    }
                     long accountId = accounts.accountsDb.insertCeAccount(account, password);
                     if (accountId < 0) {
                         Log.w(TAG, "insertAccountIntoDatabase: " + account.toSafeString()
diff --git a/services/core/java/com/android/server/am/CachedAppOptimizer.java b/services/core/java/com/android/server/am/CachedAppOptimizer.java
index f32aa22..6230919 100644
--- a/services/core/java/com/android/server/am/CachedAppOptimizer.java
+++ b/services/core/java/com/android/server/am/CachedAppOptimizer.java
@@ -157,6 +157,9 @@
     static final int SYNC_RECEIVED_WHILE_FROZEN = 1;
     static final int ASYNC_RECEIVED_WHILE_FROZEN = 2;
 
+    // Bitfield values for sync transactions received by frozen binder threads
+    static final int TXNS_PENDING_WHILE_FROZEN = 4;
+
     /**
      * This thread must be moved to the system background cpuset.
      * If that doesn't happen, it's probably going to draw a lot of power.
@@ -611,8 +614,9 @@
      * binder for the specificed pid.
      *
      * @throws RuntimeException in case a flush/freeze operation could not complete successfully.
+     * @return 0 if success, or -EAGAIN indicating there's pending transaction.
      */
-    private static native void freezeBinder(int pid, boolean freeze);
+    private static native int freezeBinder(int pid, boolean freeze);
 
     /**
      * Retrieves binder freeze info about a process.
@@ -641,21 +645,25 @@
             char state = (char) fr.read();
 
             if (state == '1' || state == '0') {
+                // Also check freezer binder ioctl
+                getBinderFreezeInfo(Process.myPid());
                 supported = true;
             } else {
                 Slog.e(TAG_AM, "unexpected value in cgroup.freeze");
             }
         } catch (java.io.FileNotFoundException e) {
-            Slog.d(TAG_AM, "cgroup.freeze not present");
+            Slog.w(TAG_AM, "cgroup.freeze not present");
+        } catch (RuntimeException e) {
+            Slog.w(TAG_AM, "unable to read freezer info");
         } catch (Exception e) {
-            Slog.d(TAG_AM, "unable to read cgroup.freeze: " + e.toString());
+            Slog.w(TAG_AM, "unable to read cgroup.freeze: " + e.toString());
         }
 
         if (fr != null) {
             try {
                 fr.close();
             } catch (java.io.IOException e) {
-                Slog.e(TAG_AM, "Exception closing freezer.killable: " + e.toString());
+                Slog.e(TAG_AM, "Exception closing cgroup.freeze: " + e.toString());
             }
         }
 
@@ -948,7 +956,7 @@
             int freezeInfo = getBinderFreezeInfo(pid);
 
             if ((freezeInfo & SYNC_RECEIVED_WHILE_FROZEN) != 0) {
-                Slog.d(TAG_AM, "pid " + pid + " " + app.processName + " "
+                Slog.d(TAG_AM, "pid " + pid + " " + app.processName
                         + " received sync transactions while frozen, killing");
                 app.killLocked("Sync transaction while in frozen state",
                         ApplicationExitInfo.REASON_OTHER,
@@ -956,8 +964,8 @@
                 processKilled = true;
             }
 
-            if ((freezeInfo & ASYNC_RECEIVED_WHILE_FROZEN) != 0) {
-                Slog.d(TAG_AM, "pid " + pid + " " + app.processName + " "
+            if ((freezeInfo & ASYNC_RECEIVED_WHILE_FROZEN) != 0 && DEBUG_FREEZER) {
+                Slog.d(TAG_AM, "pid " + pid + " " + app.processName
                         + " received async transactions while frozen");
             }
         } catch (Exception e) {
@@ -1292,7 +1300,9 @@
         public void handleMessage(Message msg) {
             switch (msg.what) {
                 case SET_FROZEN_PROCESS_MSG:
-                    freezeProcess((ProcessRecord) msg.obj);
+                    synchronized (mAm) {
+                        freezeProcess((ProcessRecord) msg.obj);
+                    }
                     break;
                 case REPORT_UNFREEZE_MSG:
                     int pid = msg.arg1;
@@ -1306,6 +1316,15 @@
             }
         }
 
+        @GuardedBy({"mAm", "mProcLock"})
+        private void rescheduleFreeze(final ProcessRecord proc, final String reason) {
+            Slog.d(TAG_AM, "Reschedule freeze for process " + proc.getPid()
+                    + " " + proc.processName + " (" + reason + ")");
+            unfreezeAppLSP(proc);
+            freezeAppAsyncLSP(proc);
+        }
+
+        @GuardedBy({"mAm"})
         private void freezeProcess(final ProcessRecord proc) {
             int pid = proc.getPid(); // Unlocked intentionally
             final String name = proc.processName;
@@ -1355,10 +1374,15 @@
                     return;
                 }
 
+                Slog.d(TAG_AM, "freezing " + pid + " " + name);
+
                 // Freeze binder interface before the process, to flush any
                 // transactions that might be pending.
                 try {
-                    freezeBinder(pid, true);
+                    if (freezeBinder(pid, true) != 0) {
+                        rescheduleFreeze(proc, "outstanding txns");
+                        return;
+                    }
                 } catch (RuntimeException e) {
                     Slog.e(TAG_AM, "Unable to freeze binder for " + pid + " " + name);
                     mFreezeHandler.post(() -> {
@@ -1404,24 +1428,36 @@
 
             try {
                 // post-check to prevent races
+                int freezeInfo = getBinderFreezeInfo(pid);
+
+                if ((freezeInfo & TXNS_PENDING_WHILE_FROZEN) != 0) {
+                    synchronized (mProcLock) {
+                        rescheduleFreeze(proc, "new pending txns");
+                    }
+                    return;
+                }
+            } catch (RuntimeException e) {
+                Slog.e(TAG_AM, "Unable to freeze binder for " + pid + " " + name);
+                mFreezeHandler.post(() -> {
+                    synchronized (mAm) {
+                        proc.killLocked("Unable to freeze binder interface",
+                                ApplicationExitInfo.REASON_OTHER,
+                                ApplicationExitInfo.SUBREASON_FREEZER_BINDER_IOCTL, true);
+                    }
+                });
+            }
+
+            try {
+                // post-check to prevent races
                 if (mProcLocksReader.hasFileLocks(pid)) {
                     if (DEBUG_FREEZER) {
                         Slog.d(TAG_AM, name + " (" + pid + ") holds file locks, reverting freeze");
                     }
-
-                    synchronized (mAm) {
-                        synchronized (mProcLock) {
-                            unfreezeAppLSP(proc);
-                        }
-                    }
+                    unfreezeAppLSP(proc);
                 }
             } catch (Exception e) {
                 Slog.e(TAG_AM, "Unable to check file locks for " + name + "(" + pid + "): " + e);
-                synchronized (mAm) {
-                    synchronized (mProcLock) {
-                        unfreezeAppLSP(proc);
-                    }
-                }
+                unfreezeAppLSP(proc);
             }
         }
 
diff --git a/services/core/java/com/android/server/am/OWNERS b/services/core/java/com/android/server/am/OWNERS
index a5cfc4a..c4efbd7 100644
--- a/services/core/java/com/android/server/am/OWNERS
+++ b/services/core/java/com/android/server/am/OWNERS
@@ -19,6 +19,7 @@
 
 # Permissions & Packages
 svetoslavganov@google.com
+toddke@google.com
 patb@google.com
 
 # Battery Stats
diff --git a/services/core/java/com/android/server/biometrics/sensors/BaseClientMonitor.java b/services/core/java/com/android/server/biometrics/sensors/BaseClientMonitor.java
index e5e1385..3eb6f4a 100644
--- a/services/core/java/com/android/server/biometrics/sensors/BaseClientMonitor.java
+++ b/services/core/java/com/android/server/biometrics/sensors/BaseClientMonitor.java
@@ -206,7 +206,6 @@
             }
             mToken = null;
         }
-        mListener = null;
     }
 
     @Override
diff --git a/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java b/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java
index b20316e..feb9e2a 100644
--- a/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java
+++ b/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java
@@ -295,6 +295,7 @@
         @Override
         public void onClientFinished(@NonNull BaseClientMonitor clientMonitor, boolean success) {
             mHandler.post(() -> {
+                clientMonitor.destroy();
                 if (mCurrentOperation == null) {
                     Slog.e(getTag(), "[Finishing] " + clientMonitor
                             + " but current operation is null, success: " + success
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java
index 37ee76a..4400834 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java
@@ -164,13 +164,17 @@
     @Override
     protected void stopHalOperation() {
         UdfpsHelper.hideUdfpsOverlay(getSensorId(), mUdfpsOverlayController);
-        try {
-            mCancellationSignal.cancel();
-        } catch (RemoteException e) {
-            Slog.e(TAG, "Remote exception", e);
-            onError(BiometricFingerprintConstants.FINGERPRINT_ERROR_HW_UNAVAILABLE,
-                    0 /* vendorCode */);
-            mCallback.onClientFinished(this, false /* success */);
+        if (mCancellationSignal != null) {
+            try {
+                mCancellationSignal.cancel();
+            } catch (RemoteException e) {
+                Slog.e(TAG, "Remote exception", e);
+                onError(BiometricFingerprintConstants.FINGERPRINT_ERROR_HW_UNAVAILABLE,
+                        0 /* vendorCode */);
+                mCallback.onClientFinished(this, false /* success */);
+            }
+        } else {
+            Slog.e(TAG, "cancellation signal was null");
         }
     }
 
diff --git a/services/core/java/com/android/server/devicestate/OWNERS b/services/core/java/com/android/server/devicestate/OWNERS
index 7708505..ae79fc0 100644
--- a/services/core/java/com/android/server/devicestate/OWNERS
+++ b/services/core/java/com/android/server/devicestate/OWNERS
@@ -1,2 +1,3 @@
 ogunwale@google.com
 akulian@google.com
+darryljohnson@google.com
diff --git a/services/core/java/com/android/server/display/DisplayModeDirector.java b/services/core/java/com/android/server/display/DisplayModeDirector.java
index d66d7ee..5fc301e 100644
--- a/services/core/java/com/android/server/display/DisplayModeDirector.java
+++ b/services/core/java/com/android/server/display/DisplayModeDirector.java
@@ -36,9 +36,14 @@
 import android.hardware.fingerprint.IUdfpsHbmListener;
 import android.net.Uri;
 import android.os.Handler;
+import android.os.IThermalEventListener;
+import android.os.IThermalService;
 import android.os.Looper;
 import android.os.Message;
+import android.os.RemoteException;
+import android.os.ServiceManager;
 import android.os.SystemClock;
+import android.os.Temperature;
 import android.os.UserHandle;
 import android.provider.DeviceConfig;
 import android.provider.Settings;
@@ -48,6 +53,7 @@
 import android.util.Slog;
 import android.util.SparseArray;
 import android.util.SparseBooleanArray;
+import android.util.SparseIntArray;
 import android.view.Display;
 import android.view.DisplayInfo;
 
@@ -72,7 +78,6 @@
 import java.util.Locale;
 import java.util.Objects;
 
-
 /**
  * The DisplayModeDirector is responsible for determining what modes are allowed to be automatically
  * picked by the system based on system-wide and display-specific configuration.
@@ -87,6 +92,8 @@
     private static final int MSG_REFRESH_RATE_IN_LOW_ZONE_CHANGED = 4;
     private static final int MSG_REFRESH_RATE_IN_HIGH_ZONE_CHANGED = 5;
     private static final int MSG_HIGH_BRIGHTNESS_THRESHOLDS_CHANGED = 6;
+    private static final int MSG_REFRESH_RATE_IN_HBM_SUNLIGHT_CHANGED = 7;
+    private static final int MSG_REFRESH_RATE_IN_HBM_HDR_CHANGED = 8;
 
     // Special ID used to indicate that given vote is to be applied globally, rather than to a
     // specific display.
@@ -108,6 +115,7 @@
     private final UdfpsObserver mUdfpsObserver;
     private final SensorObserver mSensorObserver;
     private final HbmObserver mHbmObserver;
+    private final SkinThermalStatusObserver mSkinThermalStatusObserver;
     private final DeviceConfigInterface mDeviceConfig;
     private final DeviceConfigDisplaySettings mDeviceConfigDisplaySettings;
 
@@ -155,8 +163,10 @@
             }
         };
         mSensorObserver = new SensorObserver(context, ballotBox, injector);
-        mHbmObserver = new HbmObserver(injector, ballotBox, BackgroundThread.getHandler());
+        mSkinThermalStatusObserver = new SkinThermalStatusObserver(injector, ballotBox);
         mDeviceConfigDisplaySettings = new DeviceConfigDisplaySettings();
+        mHbmObserver = new HbmObserver(injector, ballotBox, BackgroundThread.getHandler(),
+                mDeviceConfigDisplaySettings);
         mDeviceConfig = injector.getDeviceConfig();
         mAlwaysRespectAppRequest = false;
     }
@@ -174,6 +184,7 @@
         mBrightnessObserver.observe(sensorManager);
         mSensorObserver.observe();
         mHbmObserver.observe();
+        mSkinThermalStatusObserver.observe();
         synchronized (mLock) {
             // We may have a listener already registered before the call to start, so go ahead and
             // notify them to pick up our newly initialized state.
@@ -606,6 +617,7 @@
             mUdfpsObserver.dumpLocked(pw);
             mSensorObserver.dumpLocked(pw);
             mHbmObserver.dumpLocked(pw);
+            mSkinThermalStatusObserver.dumpLocked(pw);
         }
     }
 
@@ -714,6 +726,10 @@
         return mUdfpsObserver;
     }
 
+    @VisibleForTesting
+    HbmObserver getHbmObserver() {
+        return mHbmObserver;
+    }
 
     @VisibleForTesting
     DesiredDisplayModeSpecs getDesiredDisplayModeSpecsWithInjectedFpsSettings(
@@ -784,6 +800,19 @@
                             (DesiredDisplayModeSpecsListener) msg.obj;
                     desiredDisplayModeSpecsListener.onDesiredDisplayModeSpecsChanged();
                     break;
+
+                case MSG_REFRESH_RATE_IN_HBM_SUNLIGHT_CHANGED: {
+                    int refreshRateInHbmSunlight = msg.arg1;
+                    mHbmObserver.onDeviceConfigRefreshRateInHbmSunlightChanged(
+                            refreshRateInHbmSunlight);
+                    break;
+                }
+
+                case MSG_REFRESH_RATE_IN_HBM_HDR_CHANGED: {
+                    int refreshRateInHbmHdr = msg.arg1;
+                    mHbmObserver.onDeviceConfigRefreshRateInHbmHdrChanged(refreshRateInHbmHdr);
+                    break;
+                }
             }
         }
     }
@@ -910,16 +939,19 @@
         // result is a range.
         public static final int PRIORITY_FLICKER_REFRESH_RATE = 1;
 
+        // High-brightness-mode may need a specific range of refresh-rates to function properly.
+        public static final int PRIORITY_HIGH_BRIGHTNESS_MODE = 2;
+
         // SETTING_MIN_REFRESH_RATE is used to propose a lower bound of display refresh rate.
         // It votes [MIN_REFRESH_RATE, Float.POSITIVE_INFINITY]
-        public static final int PRIORITY_USER_SETTING_MIN_REFRESH_RATE = 2;
+        public static final int PRIORITY_USER_SETTING_MIN_REFRESH_RATE = 3;
 
         // APP_REQUEST_REFRESH_RATE_RANGE is used to for internal apps to limit the refresh
         // rate in certain cases, mostly to preserve power.
         // @see android.view.WindowManager.LayoutParams#preferredMinRefreshRate
         // @see android.view.WindowManager.LayoutParams#preferredMaxRefreshRate
         // It votes to [preferredMinRefreshRate, preferredMaxRefreshRate].
-        public static final int PRIORITY_APP_REQUEST_REFRESH_RATE_RANGE = 3;
+        public static final int PRIORITY_APP_REQUEST_REFRESH_RATE_RANGE = 4;
 
         // We split the app request into different priorities in case we can satisfy one desire
         // without the other.
@@ -934,32 +966,32 @@
         // The preferred refresh rate is set on the main surface of the app outside of
         // DisplayModeDirector.
         // @see com.android.server.wm.WindowState#updateFrameRateSelectionPriorityIfNeeded
-        public static final int PRIORITY_APP_REQUEST_BASE_MODE_REFRESH_RATE = 4;
-        public static final int PRIORITY_APP_REQUEST_SIZE = 5;
+        public static final int PRIORITY_APP_REQUEST_BASE_MODE_REFRESH_RATE = 5;
+        public static final int PRIORITY_APP_REQUEST_SIZE = 6;
 
         // SETTING_PEAK_REFRESH_RATE has a high priority and will restrict the bounds of the rest
         // of low priority voters. It votes [0, max(PEAK, MIN)]
-        public static final int PRIORITY_USER_SETTING_PEAK_REFRESH_RATE = 6;
+        public static final int PRIORITY_USER_SETTING_PEAK_REFRESH_RATE = 7;
 
         // LOW_POWER_MODE force display to [0, 60HZ] if Settings.Global.LOW_POWER_MODE is on.
-        public static final int PRIORITY_LOW_POWER_MODE = 7;
+        public static final int PRIORITY_LOW_POWER_MODE = 8;
 
         // PRIORITY_FLICKER_REFRESH_RATE_SWITCH votes for disabling refresh rate switching. If the
         // higher priority voters' result is a range, it will fix the rate to a single choice.
         // It's used to avoid refresh rate switches in certain conditions which may result in the
         // user seeing the display flickering when the switches occur.
-        public static final int PRIORITY_FLICKER_REFRESH_RATE_SWITCH = 8;
+        public static final int PRIORITY_FLICKER_REFRESH_RATE_SWITCH = 9;
 
-        // High-brightness-mode may need a specific range of refresh-rates to function properly.
-        public static final int PRIORITY_HIGH_BRIGHTNESS_MODE = 9;
+        // Force display to [0, 60HZ] if skin temperature is at or above CRITICAL.
+        public static final int PRIORITY_SKIN_TEMPERATURE = 10;
 
         // The proximity sensor needs the refresh rate to be locked in order to function, so this is
         // set to a high priority.
-        public static final int PRIORITY_PROXIMITY = 10;
+        public static final int PRIORITY_PROXIMITY = 11;
 
         // The Under-Display Fingerprint Sensor (UDFPS) needs the refresh rate to be locked in order
         // to function, so this needs to be the highest priority of all votes.
-        public static final int PRIORITY_UDFPS = 11;
+        public static final int PRIORITY_UDFPS = 12;
 
         // Whenever a new priority is added, remember to update MIN_PRIORITY, MAX_PRIORITY, and
         // APP_REQUEST_REFRESH_RATE_RANGE_PRIORITY_CUTOFF, as well as priorityToString.
@@ -1054,6 +1086,8 @@
                     return "PRIORITY_PROXIMITY";
                 case PRIORITY_LOW_POWER_MODE:
                     return "PRIORITY_LOW_POWER_MODE";
+                case PRIORITY_SKIN_TEMPERATURE:
+                    return "PRIORITY_SKIN_TEMPERATURE";
                 case PRIORITY_UDFPS:
                     return "PRIORITY_UDFPS";
                 case PRIORITY_USER_SETTING_MIN_REFRESH_RATE:
@@ -2245,33 +2279,78 @@
      * HBM that are associated with that display. Restrictions are retrieved from
      * DisplayManagerInternal but originate in the display-device-config file.
      */
-    private static class HbmObserver implements DisplayManager.DisplayListener {
+    public static class HbmObserver implements DisplayManager.DisplayListener {
         private final BallotBox mBallotBox;
         private final Handler mHandler;
-        private final SparseBooleanArray mHbmEnabled = new SparseBooleanArray();
+        private final SparseIntArray mHbmMode = new SparseIntArray();
         private final Injector mInjector;
+        private final DeviceConfigDisplaySettings mDeviceConfigDisplaySettings;
+        private int mRefreshRateInHbmSunlight;
+        private int mRefreshRateInHbmHdr;
 
         private DisplayManagerInternal mDisplayManagerInternal;
 
-        HbmObserver(Injector injector, BallotBox ballotBox, Handler handler) {
+        HbmObserver(Injector injector, BallotBox ballotBox, Handler handler,
+                DeviceConfigDisplaySettings displaySettings) {
             mInjector = injector;
             mBallotBox = ballotBox;
             mHandler = handler;
+            mDeviceConfigDisplaySettings = displaySettings;
         }
 
         public void observe() {
+            mRefreshRateInHbmSunlight = mDeviceConfigDisplaySettings.getRefreshRateInHbmSunlight();
+            mRefreshRateInHbmHdr = mDeviceConfigDisplaySettings.getRefreshRateInHbmHdr();
+
             mDisplayManagerInternal = LocalServices.getService(DisplayManagerInternal.class);
             mInjector.registerDisplayListener(this, mHandler,
                     DisplayManager.EVENT_FLAG_DISPLAY_BRIGHTNESS
                     | DisplayManager.EVENT_FLAG_DISPLAY_REMOVED);
         }
 
+        /**
+         * @return the refresh to lock to when the device is in high brightness mode for Sunlight.
+         */
+        @VisibleForTesting
+        int getRefreshRateInHbmSunlight() {
+            return mRefreshRateInHbmSunlight;
+        }
+
+        /**
+         * @return the refresh to lock to when the device is in high brightness mode for HDR.
+         */
+        @VisibleForTesting
+        int getRefreshRateInHbmHdr() {
+            return mRefreshRateInHbmHdr;
+        }
+
+        /**
+         * Recalculates the HBM vote when the device config has been changed.
+         */
+        public void onDeviceConfigRefreshRateInHbmSunlightChanged(int refreshRate) {
+            if (refreshRate != mRefreshRateInHbmSunlight) {
+                mRefreshRateInHbmSunlight = refreshRate;
+                onDeviceConfigRefreshRateInHbmChanged();
+            }
+        }
+
+        /**
+         * Recalculates the HBM vote when the device config has been changed.
+         */
+        public void onDeviceConfigRefreshRateInHbmHdrChanged(int refreshRate) {
+            if (refreshRate != mRefreshRateInHbmHdr) {
+                mRefreshRateInHbmHdr = refreshRate;
+                onDeviceConfigRefreshRateInHbmChanged();
+            }
+        }
+
         @Override
         public void onDisplayAdded(int displayId) {}
 
         @Override
         public void onDisplayRemoved(int displayId) {
             mBallotBox.vote(displayId, Vote.PRIORITY_HIGH_BRIGHTNESS_MODE, null);
+            mHbmMode.delete(displayId);
         }
 
         @Override
@@ -2281,31 +2360,102 @@
                 // Display no longer there. Assume we'll get an onDisplayRemoved very soon.
                 return;
             }
-            final boolean isHbmEnabled =
-                    info.highBrightnessMode != BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF;
-            if (isHbmEnabled == mHbmEnabled.get(displayId)) {
+            final int hbmMode = info.highBrightnessMode;
+            if (hbmMode == mHbmMode.get(displayId)) {
                 // no change, ignore.
                 return;
             }
+            mHbmMode.put(displayId, hbmMode);
+            recalculateVotesForDisplay(displayId);
+        }
+
+        private void onDeviceConfigRefreshRateInHbmChanged() {
+            final int[] displayIds = mHbmMode.copyKeys();
+            if (displayIds != null) {
+                for (int id : displayIds) {
+                    recalculateVotesForDisplay(id);
+                }
+            }
+        }
+
+        private void recalculateVotesForDisplay(int displayId) {
+            final int hbmMode = mHbmMode.get(displayId, BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF);
             Vote vote = null;
-            mHbmEnabled.put(displayId, isHbmEnabled);
-            if (isHbmEnabled) {
-                final List<RefreshRateLimitation> limits =
+            if (hbmMode == BrightnessInfo.HIGH_BRIGHTNESS_MODE_SUNLIGHT) {
+                // Device resource properties take priority over DisplayDeviceConfig
+                if (mRefreshRateInHbmSunlight > 0) {
+                    vote = Vote.forRefreshRates(mRefreshRateInHbmSunlight,
+                            mRefreshRateInHbmSunlight);
+                } else {
+                    final List<RefreshRateLimitation> limits =
                         mDisplayManagerInternal.getRefreshRateLimitations(displayId);
-                for (int i = 0; limits != null && i < limits.size(); i++) {
-                    final RefreshRateLimitation limitation = limits.get(i);
-                    if (limitation.type == REFRESH_RATE_LIMIT_HIGH_BRIGHTNESS_MODE) {
-                        vote = Vote.forRefreshRates(limitation.range.min, limitation.range.max);
-                        break;
+                    for (int i = 0; limits != null && i < limits.size(); i++) {
+                        final RefreshRateLimitation limitation = limits.get(i);
+                        if (limitation.type == REFRESH_RATE_LIMIT_HIGH_BRIGHTNESS_MODE) {
+                            vote = Vote.forRefreshRates(limitation.range.min, limitation.range.max);
+                            break;
+                        }
                     }
                 }
             }
+            if (hbmMode == BrightnessInfo.HIGH_BRIGHTNESS_MODE_HDR
+                    && mRefreshRateInHbmHdr > 0) {
+                vote = Vote.forRefreshRates(mRefreshRateInHbmHdr, mRefreshRateInHbmHdr);
+            }
             mBallotBox.vote(displayId, Vote.PRIORITY_HIGH_BRIGHTNESS_MODE, vote);
         }
 
         void dumpLocked(PrintWriter pw) {
             pw.println("   HbmObserver");
-            pw.println("     mHbmEnabled: " + mHbmEnabled);
+            pw.println("     mHbmMode: " + mHbmMode);
+            pw.println("     mRefreshRateInHbmSunlight: " + mRefreshRateInHbmSunlight);
+            pw.println("     mRefreshRateInHbmHdr: " + mRefreshRateInHbmHdr);
+        }
+    }
+
+    private final class SkinThermalStatusObserver extends IThermalEventListener.Stub {
+        private final BallotBox mBallotBox;
+        private final Injector mInjector;
+
+        private @Temperature.ThrottlingStatus int mStatus = -1;
+
+        SkinThermalStatusObserver(Injector injector, BallotBox ballotBox) {
+            mInjector = injector;
+            mBallotBox = ballotBox;
+        }
+
+        @Override
+        public void notifyThrottling(Temperature temp) {
+            mStatus = temp.getStatus();
+            if (mLoggingEnabled) {
+                Slog.d(TAG, "New thermal throttling status "
+                        + ", current thermal status = " + mStatus);
+            }
+            final Vote vote;
+            if (mStatus >= Temperature.THROTTLING_CRITICAL) {
+                vote = Vote.forRefreshRates(0f, 60f);
+            } else {
+                vote = null;
+            }
+            mBallotBox.vote(GLOBAL_ID, Vote.PRIORITY_SKIN_TEMPERATURE, vote);
+        }
+
+        public void observe() {
+            IThermalService thermalService = mInjector.getThermalService();
+            if (thermalService == null) {
+                Slog.w(TAG, "Could not observe thermal status. Service not available");
+                return;
+            }
+            try {
+                thermalService.registerThermalEventListenerWithType(this, Temperature.TYPE_SKIN);
+            } catch (RemoteException e) {
+                Slog.e(TAG, "Failed to register thermal status listener", e);
+            }
+        }
+
+        void dumpLocked(PrintWriter writer) {
+            writer.println("  SkinThermalStatusObserver:");
+            writer.println("    mStatus: " + mStatus);
         }
     }
 
@@ -2378,6 +2528,29 @@
             return refreshRate;
         }
 
+        public int getRefreshRateInHbmSunlight() {
+            final int defaultRefreshRateInHbmSunlight =
+                    mContext.getResources().getInteger(
+                            R.integer.config_defaultRefreshRateInHbmSunlight);
+
+            final int refreshRate = mDeviceConfig.getInt(DeviceConfig.NAMESPACE_DISPLAY_MANAGER,
+                    DisplayManager.DeviceConfig.KEY_REFRESH_RATE_IN_HBM_SUNLIGHT,
+                    defaultRefreshRateInHbmSunlight);
+
+            return refreshRate;
+        }
+
+        public int getRefreshRateInHbmHdr() {
+            final int defaultRefreshRateInHbmHdr =
+                    mContext.getResources().getInteger(R.integer.config_defaultRefreshRateInHbmHdr);
+
+            final int refreshRate = mDeviceConfig.getInt(DeviceConfig.NAMESPACE_DISPLAY_MANAGER,
+                    DisplayManager.DeviceConfig.KEY_REFRESH_RATE_IN_HBM_HDR,
+                    defaultRefreshRateInHbmHdr);
+
+            return refreshRate;
+        }
+
         /*
          * Return null if no such property
          */
@@ -2417,6 +2590,15 @@
                     .sendToTarget();
             mHandler.obtainMessage(MSG_REFRESH_RATE_IN_HIGH_ZONE_CHANGED, refreshRateInHighZone, 0)
                     .sendToTarget();
+
+            final int refreshRateInHbmSunlight = getRefreshRateInHbmSunlight();
+            mHandler.obtainMessage(MSG_REFRESH_RATE_IN_HBM_SUNLIGHT_CHANGED,
+                    refreshRateInHbmSunlight, 0)
+                    .sendToTarget();
+
+            final int refreshRateInHbmHdr = getRefreshRateInHbmHdr();
+            mHandler.obtainMessage(MSG_REFRESH_RATE_IN_HBM_HDR_CHANGED, refreshRateInHbmHdr, 0)
+                    .sendToTarget();
         }
 
         private int[] getIntArrayProperty(String prop) {
@@ -2470,6 +2652,8 @@
         BrightnessInfo getBrightnessInfo(int displayId);
 
         boolean isDozeState(Display d);
+
+        IThermalService getThermalService();
     }
 
     @VisibleForTesting
@@ -2530,6 +2714,12 @@
             return Display.isDozeState(d.getState());
         }
 
+        @Override
+        public IThermalService getThermalService() {
+            return IThermalService.Stub.asInterface(
+                    ServiceManager.getService(Context.THERMAL_SERVICE));
+        }
+
         private DisplayManager getDisplayManager() {
             if (mDisplayManager == null) {
                 mDisplayManager = mContext.getSystemService(DisplayManager.class);
diff --git a/services/core/java/com/android/server/display/DisplayPowerState.java b/services/core/java/com/android/server/display/DisplayPowerState.java
index 6af1923..147050c 100644
--- a/services/core/java/com/android/server/display/DisplayPowerState.java
+++ b/services/core/java/com/android/server/display/DisplayPowerState.java
@@ -411,7 +411,7 @@
      * Updates the state of the screen and backlight asynchronously on a separate thread.
      */
     private final class PhotonicModulator extends Thread {
-        private static final int INITIAL_SCREEN_STATE = Display.STATE_OFF; // unknown, assume off
+        private static final int INITIAL_SCREEN_STATE = Display.STATE_UNKNOWN;
         private static final float INITIAL_BACKLIGHT_FLOAT = PowerManager.BRIGHTNESS_INVALID_FLOAT;
 
         private final Object mLock = new Object();
@@ -494,7 +494,9 @@
                     if (!backlightChanged) {
                         mBacklightChangeInProgress = false;
                     }
-                    if (!stateChanged && !backlightChanged) {
+                    boolean valid = state != Display.STATE_UNKNOWN && !Float.isNaN(brightnessState);
+                    boolean changed = stateChanged || backlightChanged;
+                    if (!valid || !changed) {
                         try {
                             mLock.wait();
                         } catch (InterruptedException ex) {
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index dc95533..3e52f5e 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -2582,14 +2582,12 @@
         }
 
         if (mCurToken != null) {
-            try {
-                if (DEBUG) {
-                    Slog.v(TAG, "Removing window token: " + mCurToken + " for display: "
-                            + mCurTokenDisplayId);
-                }
-                mIWindowManager.removeWindowToken(mCurToken, mCurTokenDisplayId);
-            } catch (RemoteException e) {
+            if (DEBUG) {
+                Slog.v(TAG, "Removing window token: " + mCurToken + " for display: "
+                        + mCurTokenDisplayId);
             }
+            mWindowManagerInternal.removeWindowToken(mCurToken, false /* removeWindows */,
+                    false /* animateExit */, mCurTokenDisplayId);
             // Set IME window status as invisible when unbind current method.
             mImeWindowVis = 0;
             mBackDisposition = InputMethodService.BACK_DISPOSITION_DEFAULT;
diff --git a/services/core/java/com/android/server/pm/DefaultCrossProfileIntentFiltersUtils.java b/services/core/java/com/android/server/pm/DefaultCrossProfileIntentFiltersUtils.java
index 3019439..5643873 100644
--- a/services/core/java/com/android/server/pm/DefaultCrossProfileIntentFiltersUtils.java
+++ b/services/core/java/com/android/server/pm/DefaultCrossProfileIntentFiltersUtils.java
@@ -215,7 +215,7 @@
     private static final DefaultCrossProfileIntentFilter RECOGNIZE_SPEECH =
             new DefaultCrossProfileIntentFilter.Builder(
                     DefaultCrossProfileIntentFilter.Direction.TO_PARENT,
-                    /* flags= */0,
+                    /* flags= */ ONLY_IF_NO_MATCH_FOUND,
                     /* letsPersonalDataIntoProfile= */ false)
                     .addAction(ACTION_RECOGNIZE_SPEECH)
                     .addCategory(Intent.CATEGORY_DEFAULT)
diff --git a/services/core/java/com/android/server/pm/ShortcutService.java b/services/core/java/com/android/server/pm/ShortcutService.java
index fcbf40e..62d6717 100644
--- a/services/core/java/com/android/server/pm/ShortcutService.java
+++ b/services/core/java/com/android/server/pm/ShortcutService.java
@@ -1664,6 +1664,19 @@
         mContext.enforceCallingPermission(permission, message);
     }
 
+    private void verifyCallerUserId(@UserIdInt int userId) {
+        if (isCallerSystem()) {
+            return; // no check
+        }
+
+        final int callingUid = injectBinderCallingUid();
+
+        // Otherwise, make sure the arguments are valid.
+        if (UserHandle.getUserId(callingUid) != userId) {
+            throw new SecurityException("Invalid user-ID");
+        }
+    }
+
     private void verifyCaller(@NonNull String packageName, @UserIdInt int userId) {
         Preconditions.checkStringNotEmpty(packageName, "packageName");
 
@@ -2847,6 +2860,8 @@
 
     @Override
     public boolean isRequestPinItemSupported(int callingUserId, int requestType) {
+        verifyCallerUserId(callingUserId);
+
         final long token = injectClearCallingIdentity();
         try {
             return mShortcutRequestPinProcessor
diff --git a/services/core/java/com/android/server/pm/permission/Permission.java b/services/core/java/com/android/server/pm/permission/Permission.java
index cda4806..94e551a 100644
--- a/services/core/java/com/android/server/pm/permission/Permission.java
+++ b/services/core/java/com/android/server/pm/permission/Permission.java
@@ -480,9 +480,10 @@
             r.append("DUP:");
             r.append(permissionInfo.name);
         }
-        if (permission.isRuntime() && (ownerChanged || wasNonRuntime)) {
-            // If this is a runtime permission and the owner has changed, or this wasn't a runtime
-            // permission, then permission state should be cleaned up
+        if ((permission.isInternal() && ownerChanged)
+                || (permission.isRuntime() && (ownerChanged || wasNonRuntime))) {
+            // If this is an internal/runtime permission and the owner has changed, or this wasn't a
+            // runtime permission, then permission state should be cleaned up.
             permission.mDefinitionChanged = true;
         }
         if (PackageManagerService.DEBUG_PACKAGE_SCANNING && r != null) {
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
index 1133faa..54a6c67 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
@@ -1215,6 +1215,7 @@
 
     private boolean checkExistsAndEnforceCannotModifyImmutablyRestrictedPermission(
             @NonNull String permName) {
+        final String permissionPackageName;
         final boolean isImmutablyRestrictedPermission;
         synchronized (mLock) {
             final Permission bp = mRegistry.getPermission(permName);
@@ -1222,15 +1223,25 @@
                 Slog.w(TAG, "No such permissions: " + permName);
                 return false;
             }
+            permissionPackageName = bp.getPackageName();
             isImmutablyRestrictedPermission = bp.isHardOrSoftRestricted()
                     && bp.isImmutablyRestricted();
         }
+
+        final int callingUid = getCallingUid();
+        final int callingUserId = UserHandle.getUserId(callingUid);
+        if (mPackageManagerInt.filterAppAccess(permissionPackageName, callingUid, callingUserId)) {
+            EventLog.writeEvent(0x534e4554, "186404356", callingUid, permName);
+            return false;
+        }
+
         if (isImmutablyRestrictedPermission && mContext.checkCallingOrSelfPermission(
                 Manifest.permission.WHITELIST_RESTRICTED_PERMISSIONS)
                 != PackageManager.PERMISSION_GRANTED) {
             throw new SecurityException("Cannot modify allowlisting of an immutably "
                     + "restricted permission: " + permName);
         }
+
         return true;
     }
 
@@ -1643,7 +1654,8 @@
             isRolePermission = permission.isRole();
         }
         final boolean mayRevokeRolePermission = isRolePermission
-                && mayManageRolePermission(callingUid);
+                // Allow ourselves to revoke role permissions due to definition changes.
+                && (callingUid == Process.myUid() || mayManageRolePermission(callingUid));
 
         final boolean isRuntimePermission;
         synchronized (mLock) {
@@ -2321,11 +2333,13 @@
 
         for (int permNum = 0; permNum < numPermissions; permNum++) {
             final String permName = permissionsToRevoke.get(permNum);
+            final boolean isInternalPermission;
             synchronized (mLock) {
                 final Permission bp = mRegistry.getPermission(permName);
-                if (bp == null || !bp.isRuntime()) {
+                if (bp == null || !(bp.isInternal() || bp.isRuntime())) {
                     continue;
                 }
+                isInternalPermission = bp.isInternal();
             }
             mPackageManagerInt.forEachPackage(pkg -> {
                 final String packageName = pkg.getPackageName();
@@ -2345,12 +2359,18 @@
                     if (permissionState == PackageManager.PERMISSION_GRANTED
                             && (flags & flagMask) == 0) {
                         final int uid = UserHandle.getUid(userId, appId);
-                        EventLog.writeEvent(0x534e4554, "154505240", uid,
-                                "Revoking permission " + permName + " from package "
-                                        + packageName + " due to definition change");
-                        EventLog.writeEvent(0x534e4554, "168319670", uid,
-                                "Revoking permission " + permName + " from package "
-                                        + packageName + " due to definition change");
+                        if (isInternalPermission) {
+                            EventLog.writeEvent(0x534e4554, "195338390", uid,
+                                    "Revoking permission " + permName + " from package "
+                                            + packageName + " due to definition change");
+                        } else {
+                            EventLog.writeEvent(0x534e4554, "154505240", uid,
+                                    "Revoking permission " + permName + " from package "
+                                            + packageName + " due to definition change");
+                            EventLog.writeEvent(0x534e4554, "168319670", uid,
+                                    "Revoking permission " + permName + " from package "
+                                            + packageName + " due to definition change");
+                        }
                         Slog.e(TAG, "Revoking permission " + permName + " from package "
                                 + packageName + " due to definition change");
                         try {
diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java
index d982336..73bce31 100644
--- a/services/core/java/com/android/server/power/PowerManagerService.java
+++ b/services/core/java/com/android/server/power/PowerManagerService.java
@@ -129,6 +129,7 @@
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
+import java.util.NoSuchElementException;
 import java.util.Objects;
 
 /**
@@ -1495,7 +1496,11 @@
                 mRequestWaitForNegativeProximity = true;
             }
 
-            wakeLock.mLock.unlinkToDeath(wakeLock, 0);
+            try {
+                wakeLock.mLock.unlinkToDeath(wakeLock, 0);
+            } catch (NoSuchElementException e) {
+                Slog.wtf(TAG, "Failed to unlink wakelock", e);
+            }
             removeWakeLockLocked(wakeLock, index);
         }
     }
diff --git a/services/core/java/com/android/server/powerstats/PowerStatsDataStorage.java b/services/core/java/com/android/server/powerstats/PowerStatsDataStorage.java
index 2a95416..06253a0 100644
--- a/services/core/java/com/android/server/powerstats/PowerStatsDataStorage.java
+++ b/services/core/java/com/android/server/powerstats/PowerStatsDataStorage.java
@@ -124,12 +124,8 @@
         @Override
         public void read(InputStream in) throws IOException {
             while (in.available() > 0) {
-                try {
-                    DataElement dataElement = new DataElement(in);
-                    mCallback.onReadDataElement(dataElement.getData());
-                } catch (IOException e) {
-                    Slog.e(TAG, "Failed to read from storage. " + e.getMessage());
-                }
+                DataElement dataElement = new DataElement(in);
+                mCallback.onReadDataElement(dataElement.getData());
             }
         }
     }
diff --git a/services/core/java/com/android/server/tv/TvInputManagerService.java b/services/core/java/com/android/server/tv/TvInputManagerService.java
index 36a854e..2894708 100755
--- a/services/core/java/com/android/server/tv/TvInputManagerService.java
+++ b/services/core/java/com/android/server/tv/TvInputManagerService.java
@@ -2304,10 +2304,9 @@
         public void requestChannelBrowsable(Uri channelUri, int userId)
                 throws RemoteException {
             final String callingPackageName = getCallingPackageName();
+            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(),
+                    Binder.getCallingUid(), userId, "requestChannelBrowsable");
             final long identity = Binder.clearCallingIdentity();
-            final int callingUid = Binder.getCallingUid();
-            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
-                userId, "requestChannelBrowsable");
             try {
                 Intent intent = new Intent(TvContract.ACTION_CHANNEL_BROWSABLE_REQUESTED);
                 List<ResolveInfo> list = getContext().getPackageManager()
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 308df2f..c2cfe0b 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -656,6 +656,12 @@
     boolean mLastImeShown;
 
     /**
+     * When set to true, the IME insets will be frozen until the next app becomes IME input target.
+     * @see InsetsPolicy#adjustVisibilityForIme
+     */
+    boolean mImeInsetsFrozenUntilStartInput;
+
+    /**
      * A flag to determine if this AR is in the process of closing or entering PIP. This is needed
      * to help AR know that the app is in the process of closing but hasn't yet started closing on
      * the WM side.
@@ -1363,6 +1369,7 @@
         }
         if (newTask != null && isState(RESUMED)) {
             newTask.setResumedActivity(this, "onParentChanged");
+            mImeInsetsFrozenUntilStartInput = false;
         }
 
         if (rootTask != null && rootTask.topRunningActivity() == this) {
@@ -4759,6 +4766,7 @@
                     && imeInputTarget.getWindow().mActivityRecord == this
                     && mDisplayContent.mInputMethodWindow != null
                     && mDisplayContent.mInputMethodWindow.isVisible();
+            mImeInsetsFrozenUntilStartInput = true;
         }
 
         final DisplayContent displayContent = getDisplayContent();
@@ -5877,6 +5885,14 @@
             // closing activity having to wait until idle timeout to be stopped or destroyed if the
             // next activity won't report idle (e.g. repeated view animation).
             mTaskSupervisor.scheduleProcessStoppingAndFinishingActivitiesIfNeeded();
+
+            // If the activity is visible, but no windows are eligible to start input, unfreeze
+            // to avoid permanently frozen IME insets.
+            if (mImeInsetsFrozenUntilStartInput && getWindow(
+                    win -> WindowManager.LayoutParams.mayUseInputMethod(win.mAttrs.flags))
+                    == null) {
+                mImeInsetsFrozenUntilStartInput = false;
+            }
         }
     }
 
@@ -7792,6 +7808,13 @@
         }
     }
 
+    @Override
+    void onResize() {
+        // Reset freezing IME insets flag when the activity resized.
+        mImeInsetsFrozenUntilStartInput = false;
+        super.onResize();
+    }
+
     /** Returns true if the configuration is compatible with this activity. */
     boolean isConfigurationCompatible(Configuration config) {
         final int orientation = getRequestedOrientation();
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index dbc1116..9335846 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -1165,10 +1165,10 @@
         }
     }
 
-    WindowToken removeWindowToken(IBinder binder) {
+    WindowToken removeWindowToken(IBinder binder, boolean animateExit) {
         final WindowToken token = mTokenMap.remove(binder);
         if (token != null && token.asActivityRecord() == null) {
-            token.setExiting();
+            token.setExiting(animateExit);
         }
         return token;
     }
@@ -1252,7 +1252,7 @@
     }
 
     void removeAppToken(IBinder binder) {
-        final WindowToken token = removeWindowToken(binder);
+        final WindowToken token = removeWindowToken(binder, true /* animateExit */);
         if (token == null) {
             Slog.w(TAG_WM, "removeAppToken: Attempted to remove non-existing token: " + binder);
             return;
@@ -3971,6 +3971,9 @@
     void updateImeInputAndControlTarget(WindowState target) {
         if (mImeInputTarget != target) {
             ProtoLog.i(WM_DEBUG_IME, "setInputMethodInputTarget %s", target);
+            if (target != null && target.mActivityRecord != null) {
+                target.mActivityRecord.mImeInsetsFrozenUntilStartInput = false;
+            }
             setImeInputTarget(target);
             updateImeControlTarget();
         }
diff --git a/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java b/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java
index da47328..ed1e784 100644
--- a/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java
+++ b/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java
@@ -90,6 +90,24 @@
         onSourceChanged();
     }
 
+    @Override
+    protected boolean updateClientVisibility(InsetsControlTarget caller) {
+        boolean changed = super.updateClientVisibility(caller);
+        if (changed && caller.getRequestedVisibility(mSource.getType())) {
+            reportImeDrawnForOrganizer(caller);
+        }
+        return changed;
+    }
+
+    private void reportImeDrawnForOrganizer(InsetsControlTarget caller) {
+        if (caller.getWindow() != null && caller.getWindow().getTask() != null) {
+            if (caller.getWindow().getTask().isOrganized()) {
+                mWin.mWmService.mAtmService.mTaskOrganizerController.reportImeDrawnOnTask(
+                        caller.getWindow().getTask());
+            }
+        }
+    }
+
     private void onSourceChanged() {
         if (mLastSource.equals(mSource)) {
             return;
diff --git a/services/core/java/com/android/server/wm/InsetsPolicy.java b/services/core/java/com/android/server/wm/InsetsPolicy.java
index f2f1926..a8e1c1c 100644
--- a/services/core/java/com/android/server/wm/InsetsPolicy.java
+++ b/services/core/java/com/android/server/wm/InsetsPolicy.java
@@ -211,7 +211,7 @@
     InsetsState getInsetsForWindow(WindowState target) {
         final InsetsState originalState = mStateController.getInsetsForWindow(target);
         final InsetsState state = adjustVisibilityForTransientTypes(originalState);
-        return target.mIsImWindow ? adjustVisibilityForIme(state, state == originalState) : state;
+        return adjustVisibilityForIme(target, state, state == originalState);
     }
 
     /**
@@ -241,16 +241,37 @@
         return state;
     }
 
-    // Navigation bar insets is always visible to IME.
-    private static InsetsState adjustVisibilityForIme(InsetsState originalState,
+    private InsetsState adjustVisibilityForIme(WindowState w, InsetsState originalState,
             boolean copyState) {
-        final InsetsSource originalNavSource = originalState.peekSource(ITYPE_NAVIGATION_BAR);
-        if (originalNavSource != null && !originalNavSource.isVisible()) {
-            final InsetsState state = copyState ? new InsetsState(originalState) : originalState;
-            final InsetsSource navSource = new InsetsSource(originalNavSource);
-            navSource.setVisible(true);
-            state.addSource(navSource);
-            return state;
+        if (w.mIsImWindow) {
+            // Navigation bar insets is always visible to IME.
+            final InsetsSource originalNavSource = originalState.peekSource(ITYPE_NAVIGATION_BAR);
+            if (originalNavSource != null && !originalNavSource.isVisible()) {
+                final InsetsState state = copyState ? new InsetsState(originalState)
+                        : originalState;
+                final InsetsSource navSource = new InsetsSource(originalNavSource);
+                navSource.setVisible(true);
+                state.addSource(navSource);
+                return state;
+            }
+        } else if (w.mActivityRecord != null && w.mActivityRecord.mImeInsetsFrozenUntilStartInput) {
+            // During switching tasks with gestural navigation, if the IME is attached to
+            // one app window on that time, even the next app window is behind the IME window,
+            // conceptually the window should not receive the IME insets if the next window is
+            // not eligible IME requester and ready to show IME on top of it.
+            final boolean shouldImeAttachedToApp = mDisplayContent.shouldImeAttachedToApp();
+            final InsetsSource originalImeSource = originalState.peekSource(ITYPE_IME);
+
+            if (shouldImeAttachedToApp && originalImeSource != null) {
+                final boolean imeVisibility =
+                        w.mActivityRecord.mLastImeShown || w.getRequestedVisibility(ITYPE_IME);
+                final InsetsState state = copyState ? new InsetsState(originalState)
+                        : originalState;
+                final InsetsSource imeSource = new InsetsSource(originalImeSource);
+                imeSource.setVisible(imeVisibility);
+                state.addSource(imeSource);
+                return state;
+            }
         }
         return originalState;
     }
diff --git a/services/core/java/com/android/server/wm/KeyguardController.java b/services/core/java/com/android/server/wm/KeyguardController.java
index fe1020c..cf0f973 100644
--- a/services/core/java/com/android/server/wm/KeyguardController.java
+++ b/services/core/java/com/android/server/wm/KeyguardController.java
@@ -106,13 +106,13 @@
     }
 
     /**
-     * @return {@code true} for default display when AOD is showing. Otherwise, same as
-     *         {@link #isKeyguardOrAodShowing(int)}
+     * @return {@code true} for default display when AOD is showing, not going away. Otherwise, same
+     *         as {@link #isKeyguardOrAodShowing(int)}
      * TODO(b/125198167): Replace isKeyguardOrAodShowing() by this logic.
      */
     boolean isKeyguardUnoccludedOrAodShowing(int displayId) {
         if (displayId == DEFAULT_DISPLAY && mAodShowing) {
-            return true;
+            return !mKeyguardGoingAway;
         }
         return isKeyguardOrAodShowing(displayId);
     }
@@ -477,7 +477,7 @@
         final KeyguardDisplayState state = getDisplayState(displayId);
         if (isKeyguardUnoccludedOrAodShowing(displayId)) {
             state.mSleepTokenAcquirer.acquire(displayId);
-        } else if (!isKeyguardUnoccludedOrAodShowing(displayId)) {
+        } else {
             state.mSleepTokenAcquirer.release(displayId);
         }
     }
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 516be55..c068fd1 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -5144,7 +5144,7 @@
     /**
      * @return true if the task is currently focused.
      */
-    private boolean isFocused() {
+    boolean isFocused() {
         if (mDisplayContent == null || mDisplayContent.mCurrentFocus == null) {
             return false;
         }
diff --git a/services/core/java/com/android/server/wm/TaskOrganizerController.java b/services/core/java/com/android/server/wm/TaskOrganizerController.java
index 09c5581..88467ba 100644
--- a/services/core/java/com/android/server/wm/TaskOrganizerController.java
+++ b/services/core/java/com/android/server/wm/TaskOrganizerController.java
@@ -733,6 +733,17 @@
         mPendingTaskEvents.clear();
     }
 
+    void reportImeDrawnOnTask(Task task) {
+        final TaskOrganizerState state = mTaskOrganizerStates.get(task.mTaskOrganizer.asBinder());
+        if (state != null) {
+            try {
+                state.mOrganizer.mTaskOrganizer.onImeDrawnOnTask(task.mTaskId);
+            } catch (RemoteException e) {
+                Slog.e(TAG, "Exception sending onImeDrawnOnTask callback", e);
+            }
+        }
+    }
+
     void onTaskInfoChanged(Task task, boolean force) {
         if (!task.mTaskAppearedSent) {
             // Skip if task still not appeared.
diff --git a/services/core/java/com/android/server/wm/WallpaperWindowToken.java b/services/core/java/com/android/server/wm/WallpaperWindowToken.java
index 194f48f..b54e8b7 100644
--- a/services/core/java/com/android/server/wm/WallpaperWindowToken.java
+++ b/services/core/java/com/android/server/wm/WallpaperWindowToken.java
@@ -66,8 +66,8 @@
     }
 
     @Override
-    void setExiting() {
-        super.setExiting();
+    void setExiting(boolean animateExit) {
+        super.setExiting(animateExit);
         mDisplayContent.mWallpaperController.removeWallpaperToken(this);
     }
 
diff --git a/services/core/java/com/android/server/wm/WindowManagerInternal.java b/services/core/java/com/android/server/wm/WindowManagerInternal.java
index 47087cf..4fac05c 100644
--- a/services/core/java/com/android/server/wm/WindowManagerInternal.java
+++ b/services/core/java/com/android/server/wm/WindowManagerInternal.java
@@ -445,8 +445,21 @@
      * @param removeWindows Whether to also remove the windows associated with the token.
      * @param displayId The display to remove the token from.
      */
+    public final void removeWindowToken(android.os.IBinder token, boolean removeWindows,
+            int displayId) {
+        removeWindowToken(token, removeWindows, true /* animateExit */, displayId);
+    }
+
+    /**
+     * Removes a window token.
+     *
+     * @param token The toke to remove.
+     * @param removeWindows Whether to also remove the windows associated with the token.
+     * @param animateExit Whether to play the windows exit animation after the token removal.
+     * @param displayId The display to remove the token from.
+     */
     public abstract void removeWindowToken(android.os.IBinder token, boolean removeWindows,
-            int displayId);
+            boolean animateExit, int displayId);
 
     /**
      * Registers a listener to be notified about app transition events.
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 1ec9187..9caef70 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -2816,6 +2816,31 @@
 
     }
 
+    void removeWindowToken(IBinder binder, boolean removeWindows, boolean animateExit,
+            int displayId) {
+        synchronized (mGlobalLock) {
+            final DisplayContent dc = mRoot.getDisplayContent(displayId);
+
+            if (dc == null) {
+                ProtoLog.w(WM_ERROR, "removeWindowToken: Attempted to remove token: %s"
+                        + " for non-exiting displayId=%d", binder, displayId);
+                return;
+            }
+            final WindowToken token = dc.removeWindowToken(binder, animateExit);
+            if (token == null) {
+                ProtoLog.w(WM_ERROR,
+                        "removeWindowToken: Attempted to remove non-existing token: %s",
+                        binder);
+                return;
+            }
+
+            if (removeWindows) {
+                token.removeAllWindowsIfPossible();
+            }
+            dc.getInputMonitor().updateInputWindowsLw(true /* force */);
+        }
+    }
+
     @Override
     public void removeWindowToken(IBinder binder, int displayId) {
         if (!checkCallingPermission(MANAGE_APP_TOKENS, "removeWindowToken()")) {
@@ -2823,23 +2848,7 @@
         }
         final long origId = Binder.clearCallingIdentity();
         try {
-            synchronized (mGlobalLock) {
-                final DisplayContent dc = mRoot.getDisplayContent(displayId);
-
-                if (dc == null) {
-                    ProtoLog.w(WM_ERROR, "removeWindowToken: Attempted to remove token: %s"
-                            + " for non-exiting displayId=%d", binder, displayId);
-                    return;
-                }
-                final WindowToken token = dc.removeWindowToken(binder);
-                if (token == null) {
-                    ProtoLog.w(WM_ERROR,
-                            "removeWindowToken: Attempted to remove non-existing token: %s",
-                            binder);
-                    return;
-                }
-                dc.getInputMonitor().updateInputWindowsLw(true /*force*/);
-            }
+            removeWindowToken(binder, false /* removeWindows */, true /* animateExit */, displayId);
         } finally {
             Binder.restoreCallingIdentity(origId);
         }
@@ -7536,28 +7545,10 @@
         }
 
         @Override
-        public void removeWindowToken(IBinder binder, boolean removeWindows, int displayId) {
-            synchronized (mGlobalLock) {
-                if (removeWindows) {
-                    final DisplayContent dc = mRoot.getDisplayContent(displayId);
-                    if (dc == null) {
-                        ProtoLog.w(WM_ERROR, "removeWindowToken: Attempted to remove token: %s"
-                                + " for non-exiting displayId=%d", binder, displayId);
-                        return;
-                    }
-
-                    final WindowToken token = dc.removeWindowToken(binder);
-                    if (token == null) {
-                        ProtoLog.w(WM_ERROR,
-                                "removeWindowToken: Attempted to remove non-existing token: %s",
-                                binder);
-                        return;
-                    }
-
-                    token.removeAllWindowsIfPossible();
-                }
-                WindowManagerService.this.removeWindowToken(binder, displayId);
-            }
+        public void removeWindowToken(IBinder binder, boolean removeWindows, boolean animateExit,
+                int displayId) {
+            WindowManagerService.this.removeWindowToken(binder, removeWindows, animateExit,
+                    displayId);
         }
 
         @Override
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index c3fc995..5e042ef 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -2180,11 +2180,18 @@
         }
     }
 
-    boolean onSetAppExiting() {
+    boolean onSetAppExiting(boolean animateExit) {
         final DisplayContent displayContent = getDisplayContent();
         boolean changed = false;
 
-        if (isVisibleNow()) {
+        if (!animateExit) {
+            // Hide the window permanently if no window exist animation is performed, so we can
+            // avoid the window surface becoming visible again unexpectedly during the next
+            // relayout.
+            mPermanentlyHidden = true;
+            hide(false /* doAnimation */, false /* requestAnim */);
+        }
+        if (isVisibleNow() && animateExit) {
             mWinAnimator.applyAnimationLocked(TRANSIT_EXIT, false);
             if (mWmService.mAccessibilityController != null) {
                 mWmService.mAccessibilityController.onWindowTransition(this, TRANSIT_EXIT);
@@ -2197,7 +2204,7 @@
 
         for (int i = mChildren.size() - 1; i >= 0; --i) {
             final WindowState c = mChildren.get(i);
-            changed |= c.onSetAppExiting();
+            changed |= c.onSetAppExiting(animateExit);
         }
 
         return changed;
diff --git a/services/core/java/com/android/server/wm/WindowToken.java b/services/core/java/com/android/server/wm/WindowToken.java
index fbfa400..3cbc67c 100644
--- a/services/core/java/com/android/server/wm/WindowToken.java
+++ b/services/core/java/com/android/server/wm/WindowToken.java
@@ -24,6 +24,7 @@
 import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_APP_TRANSITIONS;
 import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_FOCUS;
 import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_WINDOW_MOVEMENT;
+import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_WINDOW_ANIMATION;
 import static com.android.server.wm.WindowContainer.AnimationFlags.CHILDREN;
 import static com.android.server.wm.WindowContainer.AnimationFlags.PARENTS;
 import static com.android.server.wm.WindowContainer.AnimationFlags.TRANSITION;
@@ -232,7 +233,7 @@
         }
     }
 
-    void setExiting() {
+    void setExiting(boolean animateExit) {
         if (isEmpty()) {
             super.removeImmediately();
             return;
@@ -247,11 +248,12 @@
 
         final int count = mChildren.size();
         boolean changed = false;
-        final boolean delayed = isAnimating(TRANSITION | PARENTS | CHILDREN);
+        final boolean delayed = isAnimating(TRANSITION | PARENTS)
+                || (isAnimating(CHILDREN, ANIMATION_TYPE_WINDOW_ANIMATION) && animateExit);
 
         for (int i = 0; i < count; i++) {
             final WindowState win = mChildren.get(i);
-            changed |= win.onSetAppExiting();
+            changed |= win.onSetAppExiting(animateExit);
         }
 
         final ActivityRecord app = asActivityRecord();
@@ -353,7 +355,7 @@
     @Override
     void removeImmediately() {
         if (mDisplayContent != null) {
-            mDisplayContent.removeWindowToken(token);
+            mDisplayContent.removeWindowToken(token, true /* animateExit */);
         }
         // Needs to occur after the token is removed from the display above to avoid attempt at
         // duplicate removal of this window container from it's parent.
diff --git a/services/core/jni/com_android_server_am_CachedAppOptimizer.cpp b/services/core/jni/com_android_server_am_CachedAppOptimizer.cpp
index e319e3f..4190a91 100644
--- a/services/core/jni/com_android_server_am_CachedAppOptimizer.cpp
+++ b/services/core/jni/com_android_server_am_CachedAppOptimizer.cpp
@@ -55,6 +55,7 @@
 
 #define SYNC_RECEIVED_WHILE_FROZEN (1)
 #define ASYNC_RECEIVED_WHILE_FROZEN (2)
+#define TXNS_PENDING_WHILE_FROZEN (4)
 
 namespace android {
 
@@ -232,17 +233,20 @@
     compactProcessOrFallback(pid, compactionFlags);
 }
 
-static void com_android_server_am_CachedAppOptimizer_freezeBinder(
+static jint com_android_server_am_CachedAppOptimizer_freezeBinder(
         JNIEnv *env, jobject clazz, jint pid, jboolean freeze) {
 
-    if (IPCThreadState::freeze(pid, freeze, 100 /* timeout [ms] */) != 0) {
+    jint retVal = IPCThreadState::freeze(pid, freeze, 100 /* timeout [ms] */);
+    if (retVal != 0 && retVal != -EAGAIN) {
         jniThrowException(env, "java/lang/RuntimeException", "Unable to freeze/unfreeze binder");
     }
+
+    return retVal;
 }
 
 static jint com_android_server_am_CachedAppOptimizer_getBinderFreezeInfo(JNIEnv *env,
         jobject clazz, jint pid) {
-    bool syncReceived = false, asyncReceived = false;
+    uint32_t syncReceived = 0, asyncReceived = 0;
 
     int error = IPCThreadState::getProcessFreezeInfo(pid, &syncReceived, &asyncReceived);
 
@@ -252,13 +256,12 @@
 
     jint retVal = 0;
 
-    if(syncReceived) {
-        retVal |= SYNC_RECEIVED_WHILE_FROZEN;;
-    }
-
-    if(asyncReceived) {
-        retVal |= ASYNC_RECEIVED_WHILE_FROZEN;
-    }
+    // bit 0 of sync_recv goes to bit 0 of retVal
+    retVal |= syncReceived & SYNC_RECEIVED_WHILE_FROZEN;
+    // bit 0 of async_recv goes to bit 1 of retVal
+    retVal |= (asyncReceived << 1) & ASYNC_RECEIVED_WHILE_FROZEN;
+    // bit 1 of sync_recv goes to bit 2 of retVal
+    retVal |= (syncReceived << 1) & TXNS_PENDING_WHILE_FROZEN;
 
     return retVal;
 }
@@ -278,7 +281,7 @@
         /* name, signature, funcPtr */
         {"compactSystem", "()V", (void*)com_android_server_am_CachedAppOptimizer_compactSystem},
         {"compactProcess", "(II)V", (void*)com_android_server_am_CachedAppOptimizer_compactProcess},
-        {"freezeBinder", "(IZ)V", (void*)com_android_server_am_CachedAppOptimizer_freezeBinder},
+        {"freezeBinder", "(IZ)I", (void*)com_android_server_am_CachedAppOptimizer_freezeBinder},
         {"getBinderFreezeInfo", "(I)I",
          (void*)com_android_server_am_CachedAppOptimizer_getBinderFreezeInfo},
         {"getFreezerCheckPath", "()Ljava/lang/String;",
diff --git a/services/core/jni/stats/OWNERS b/services/core/jni/stats/OWNERS
index 552cc0d..2611e5b 100644
--- a/services/core/jni/stats/OWNERS
+++ b/services/core/jni/stats/OWNERS
@@ -1,6 +1,8 @@
 jeffreyhuang@google.com
 jtnguyen@google.com
 muhammadq@google.com
+sharaieko@google.com
 singhtejinder@google.com
 tsaichristine@google.com
 yaochen@google.com
+yro@google.com
diff --git a/services/tests/mockingservicestests/src/com/android/server/DeviceIdleControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/DeviceIdleControllerTest.java
index acf50b45..e472b06 100644
--- a/services/tests/mockingservicestests/src/com/android/server/DeviceIdleControllerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/DeviceIdleControllerTest.java
@@ -323,6 +323,7 @@
         when(mPowerManager.newWakeLock(anyInt(), anyString())).thenReturn(mWakeLock);
         doNothing().when(mWakeLock).acquire();
         doNothing().when(mAlarmManager).set(anyInt(), anyLong(), anyString(), any(), any());
+        doNothing().when(mAlarmManager).setExact(anyInt(), anyLong(), anyString(), any(), any());
         doNothing().when(mAlarmManager)
                 .setWindow(anyInt(), anyLong(), anyLong(), anyString(), any(), any());
         doReturn(mock(Sensor.class)).when(mSensorManager)
diff --git a/services/tests/mockingservicestests/src/com/android/server/RescuePartyTest.java b/services/tests/mockingservicestests/src/com/android/server/RescuePartyTest.java
index fd364ae7..51fa851 100644
--- a/services/tests/mockingservicestests/src/com/android/server/RescuePartyTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/RescuePartyTest.java
@@ -37,6 +37,8 @@
 
 import android.content.ContentResolver;
 import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
 import android.content.pm.VersionedPackage;
 import android.os.Bundle;
 import android.os.RecoverySystem;
@@ -83,6 +85,8 @@
     private static final String CALLING_PACKAGE1 = "com.package.name1";
     private static final String CALLING_PACKAGE2 = "com.package.name2";
     private static final String CALLING_PACKAGE3 = "com.package.name3";
+    private static final String PERSISTENT_PACKAGE = "com.persistent.package";
+    private static final String NON_PERSISTENT_PACKAGE = "com.nonpersistent.package";
     private static final String NAMESPACE1 = "namespace1";
     private static final String NAMESPACE2 = "namespace2";
     private static final String NAMESPACE3 = "namespace3";
@@ -103,6 +107,8 @@
     private PackageWatchdog mMockPackageWatchdog;
     @Mock(answer = Answers.RETURNS_DEEP_STUBS)
     private ContentResolver mMockContentResolver;
+    @Mock(answer = Answers.RETURNS_DEEP_STUBS)
+    private PackageManager mPackageManager;
 
     @Captor
     private ArgumentCaptor<RemoteCallback> mMonitorCallbackCaptor;
@@ -129,6 +135,17 @@
         mNamespacesWiped = new HashSet<>();
 
         when(mMockContext.getContentResolver()).thenReturn(mMockContentResolver);
+        when(mMockContext.getPackageManager()).thenReturn(mPackageManager);
+        ApplicationInfo persistentApplicationInfo = new ApplicationInfo();
+        persistentApplicationInfo.flags |=
+                ApplicationInfo.FLAG_SYSTEM | ApplicationInfo.FLAG_PERSISTENT;
+
+        // If the package name is PERSISTENT_PACKAGE, then set the flags to be persistent and
+        // system. Don't set any flags otherwise.
+        when(mPackageManager.getApplicationInfo(eq(PERSISTENT_PACKAGE),
+                anyInt())).thenReturn(persistentApplicationInfo);
+        when(mPackageManager.getApplicationInfo(eq(NON_PERSISTENT_PACKAGE),
+                anyInt())).thenReturn(new ApplicationInfo());
         // Reset observer instance to get new mock context on every run
         RescuePartyObserver.reset();
 
@@ -241,29 +258,53 @@
 
     @Test
     public void testPersistentAppCrashDetectionWithExecutionForAllRescueLevels() {
-        notePersistentAppCrash(1);
+        noteAppCrash(1, true);
 
         verifySettingsResets(Settings.RESET_MODE_UNTRUSTED_DEFAULTS, /*resetNamespaces=*/ null,
                 /*configResetVerifiedTimesMap=*/ null);
 
-        notePersistentAppCrash(2);
+        noteAppCrash(2, true);
 
         verifySettingsResets(Settings.RESET_MODE_UNTRUSTED_CHANGES, /*resetNamespaces=*/ null,
                 /*configResetVerifiedTimesMap=*/ null);
 
-        notePersistentAppCrash(3);
+        noteAppCrash(3, true);
 
         verifySettingsResets(Settings.RESET_MODE_TRUSTED_DEFAULTS, /*resetNamespaces=*/ null,
                 /*configResetVerifiedTimesMap=*/ null);
 
-        notePersistentAppCrash(4);
+        noteAppCrash(4, true);
         assertTrue(RescueParty.isRebootPropertySet());
 
-        notePersistentAppCrash(5);
+        noteAppCrash(5, true);
         assertTrue(RescueParty.isFactoryResetPropertySet());
     }
 
     @Test
+    public void testNonPersistentAppOnlyPerformsFlagResets() {
+        noteAppCrash(1, false);
+
+        verifySettingsResets(Settings.RESET_MODE_UNTRUSTED_DEFAULTS, /*resetNamespaces=*/ null,
+                /*configResetVerifiedTimesMap=*/ null);
+
+        noteAppCrash(2, false);
+
+        verifySettingsResets(Settings.RESET_MODE_UNTRUSTED_CHANGES, /*resetNamespaces=*/ null,
+                /*configResetVerifiedTimesMap=*/ null);
+
+        noteAppCrash(3, false);
+
+        verifySettingsResets(Settings.RESET_MODE_TRUSTED_DEFAULTS, /*resetNamespaces=*/ null,
+                /*configResetVerifiedTimesMap=*/ null);
+
+        noteAppCrash(4, false);
+        assertFalse(RescueParty.isRebootPropertySet());
+
+        noteAppCrash(5, false);
+        assertFalse(RescueParty.isFactoryResetPropertySet());
+    }
+
+    @Test
     public void testNonPersistentAppCrashDetectionWithScopedResets() {
         RescueParty.onSettingsProviderPublished(mMockContext);
         verify(() -> Settings.Config.registerMonitorCallback(eq(mMockContentResolver),
@@ -311,11 +352,11 @@
 
         observer.execute(new VersionedPackage(
                 CALLING_PACKAGE1, 1), PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING, 4);
-        assertTrue(RescueParty.isRebootPropertySet());
+        assertFalse(RescueParty.isRebootPropertySet());
 
         observer.execute(new VersionedPackage(
                 CALLING_PACKAGE1, 1), PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING, 5);
-        assertTrue(RescueParty.isFactoryResetPropertySet());
+        assertFalse(RescueParty.isFactoryResetPropertySet());
     }
 
     @Test
@@ -376,11 +417,11 @@
 
         observer.execute(new VersionedPackage(
                 CALLING_PACKAGE1, 1), PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING, 4);
-        assertTrue(RescueParty.isRebootPropertySet());
+        assertFalse(RescueParty.isRebootPropertySet());
 
         observer.execute(new VersionedPackage(
                 CALLING_PACKAGE1, 1), PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING, 5);
-        assertTrue(RescueParty.isFactoryResetPropertySet());
+        assertFalse(RescueParty.isFactoryResetPropertySet());
     }
 
     @Test
@@ -627,9 +668,10 @@
         RescuePartyObserver.getInstance(mMockContext).executeBootLoopMitigation(mitigationCount);
     }
 
-    private void notePersistentAppCrash(int mitigationCount) {
+    private void noteAppCrash(int mitigationCount, boolean isPersistent) {
+        String packageName = isPersistent ? PERSISTENT_PACKAGE : NON_PERSISTENT_PACKAGE;
         RescuePartyObserver.getInstance(mMockContext).execute(new VersionedPackage(
-                "com.package.name", 1), PackageWatchdog.FAILURE_REASON_APP_CRASH, mitigationCount);
+                packageName, 1), PackageWatchdog.FAILURE_REASON_APP_CRASH, mitigationCount);
     }
 
     private Bundle getConfigAccessBundle(String callingPackage, String namespace) {
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerTest.java
index f4d1499..a41f79e 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerTest.java
@@ -348,6 +348,17 @@
         verify((Interruptable) interruptableMonitor, never()).cancel();
     }
 
+    @Test
+    public void testClientDestroyed_afterFinish() {
+        final HalClientMonitor.LazyDaemon<Object> nonNullDaemon = () -> mock(Object.class);
+        final TestClientMonitor client =
+                new TestClientMonitor(mContext, mToken, nonNullDaemon);
+        mScheduler.scheduleClientMonitor(client);
+        client.mCallback.onClientFinished(client, true /* success */);
+        waitForIdle();
+        assertTrue(client.wasDestroyed());
+    }
+
     private BiometricSchedulerProto getDump(boolean clearSchedulerBuffer) throws Exception {
         return BiometricSchedulerProto.parseFrom(mScheduler.dumpProtoState(clearSchedulerBuffer));
     }
@@ -437,6 +448,7 @@
     private static class TestClientMonitor extends HalClientMonitor<Object> {
         private boolean mUnableToStart;
         private boolean mStarted;
+        private boolean mDestroyed;
 
         public TestClientMonitor(@NonNull Context context, @NonNull IBinder token,
                 @NonNull LazyDaemon<Object> lazyDaemon) {
@@ -475,6 +487,11 @@
 
         }
 
+        @Override
+        public void destroy() {
+            mDestroyed = true;
+        }
+
         public boolean wasUnableToStart() {
             return mUnableToStart;
         }
@@ -482,6 +499,11 @@
         public boolean hasStarted() {
             return mStarted;
         }
+
+        public boolean wasDestroyed() {
+            return mDestroyed;
+        }
+
     }
 
     private static void waitForIdle() {
diff --git a/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java b/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java
index 1ac28ab..0dd5c61 100644
--- a/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java
@@ -20,6 +20,8 @@
 import static android.hardware.display.DisplayManager.DeviceConfig.KEY_FIXED_REFRESH_RATE_HIGH_DISPLAY_BRIGHTNESS_THRESHOLDS;
 import static android.hardware.display.DisplayManager.DeviceConfig.KEY_FIXED_REFRESH_RATE_LOW_AMBIENT_BRIGHTNESS_THRESHOLDS;
 import static android.hardware.display.DisplayManager.DeviceConfig.KEY_FIXED_REFRESH_RATE_LOW_DISPLAY_BRIGHTNESS_THRESHOLDS;
+import static android.hardware.display.DisplayManager.DeviceConfig.KEY_REFRESH_RATE_IN_HBM_SUNLIGHT;
+import static android.hardware.display.DisplayManager.DeviceConfig.KEY_REFRESH_RATE_IN_HBM_HDR;
 import static android.hardware.display.DisplayManager.DeviceConfig.KEY_REFRESH_RATE_IN_HIGH_ZONE;
 import static android.hardware.display.DisplayManager.DeviceConfig.KEY_REFRESH_RATE_IN_LOW_ZONE;
 
@@ -56,7 +58,11 @@
 import android.hardware.display.DisplayManagerInternal.RefreshRateRange;
 import android.hardware.fingerprint.IUdfpsHbmListener;
 import android.os.Handler;
+import android.os.IThermalEventListener;
+import android.os.IThermalService;
 import android.os.Looper;
+import android.os.RemoteException;
+import android.os.Temperature;
 import android.provider.DeviceConfig;
 import android.provider.Settings;
 import android.test.mock.MockContentResolver;
@@ -116,6 +122,8 @@
     public SensorManagerInternal mSensorManagerInternalMock;
     @Mock
     public DisplayManagerInternal mDisplayManagerInternalMock;
+    @Mock
+    public IThermalService mThermalServiceMock;
 
     @Before
     public void setUp() throws Exception {
@@ -124,6 +132,7 @@
         final MockContentResolver resolver = mSettingsProviderRule.mockContentResolver(mContext);
         when(mContext.getContentResolver()).thenReturn(resolver);
         mInjector = spy(new FakesInjector());
+        when(mInjector.getThermalService()).thenReturn(mThermalServiceMock);
         mHandler = new Handler(Looper.getMainLooper());
 
         LocalServices.removeServiceForTest(StatusBarManagerInternal.class);
@@ -1401,6 +1410,12 @@
     public void testHbmVoting_forHdr() {
         DisplayModeDirector director =
                 createDirectorFromRefreshRateArray(new float[] {60.0f, 90.0f}, 0);
+        final int hbmRefreshRate = 72;
+
+        // Specify limitation before starting DisplayModeDirector to avoid waiting on property
+        // propagation
+        mInjector.getDeviceConfig().setRefreshRateInHbmHdr(hbmRefreshRate);
+
         director.start(createMockSensorManager());
 
         ArgumentCaptor<DisplayListener> captor =
@@ -1425,7 +1440,7 @@
                 new BrightnessInfo(0.45f, 0.0f, 1.0f, BrightnessInfo.HIGH_BRIGHTNESS_MODE_HDR));
         listener.onDisplayChanged(DISPLAY_ID);
         vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_HIGH_BRIGHTNESS_MODE);
-        assertVoteForRefreshRate(vote, 60.f);
+        assertVoteForRefreshRate(vote, hbmRefreshRate);
 
         // Turn off HBM
         when(mInjector.getBrightnessInfo(DISPLAY_ID)).thenReturn(
@@ -1436,6 +1451,44 @@
     }
 
     @Test
+    public void testHbmObserverGetsUpdatedRefreshRateInHbmSunlight() {
+        DisplayModeDirector director =
+                createDirectorFromRefreshRateArray(new float[] {60.f, 90.f}, /* baseModeId= */ 0);
+
+        final int initialRefreshRate = 60;
+        mInjector.getDeviceConfig().setRefreshRateInHbmSunlight(initialRefreshRate);
+        director.start(createMockSensorManager());
+        assertThat(director.getHbmObserver().getRefreshRateInHbmSunlight())
+                .isEqualTo(initialRefreshRate);
+
+        final int updatedRefreshRate = 90;
+        mInjector.getDeviceConfig().setRefreshRateInHbmSunlight(updatedRefreshRate);
+        // Need to wait for the property change to propagate to the main thread.
+        waitForIdleSync();
+        assertThat(director.getHbmObserver().getRefreshRateInHbmSunlight())
+                .isEqualTo(updatedRefreshRate);
+    }
+
+    @Test
+    public void testHbmObserverGetsUpdatedRefreshRateInHbmHdr() {
+        DisplayModeDirector director =
+                createDirectorFromRefreshRateArray(new float[] {60.f, 90.f}, /* baseModeId= */ 0);
+
+        final int initialRefreshRate = 60;
+        mInjector.getDeviceConfig().setRefreshRateInHbmHdr(initialRefreshRate);
+        director.start(createMockSensorManager());
+        assertThat(director.getHbmObserver().getRefreshRateInHbmHdr())
+                .isEqualTo(initialRefreshRate);
+
+        final int updatedRefreshRate = 90;
+        mInjector.getDeviceConfig().setRefreshRateInHbmHdr(updatedRefreshRate);
+        // Need to wait for the property change to propagate to the main thread.
+        waitForIdleSync();
+        assertThat(director.getHbmObserver().getRefreshRateInHbmHdr())
+                .isEqualTo(updatedRefreshRate);
+    }
+
+    @Test
     public void testHbmVoting_forSunlight() {
         DisplayModeDirector director =
                 createDirectorFromRefreshRateArray(new float[] {60.0f, 90.0f}, 0);
@@ -1448,11 +1501,12 @@
                   | DisplayManager.EVENT_FLAG_DISPLAY_REMOVED));
         DisplayListener listener = captor.getValue();
 
+        final int initialRefreshRate = 60;
         // Specify Limitation
         when(mDisplayManagerInternalMock.getRefreshRateLimitations(DISPLAY_ID)).thenReturn(
                 List.of(new RefreshRateLimitation(
                         DisplayManagerInternal.REFRESH_RATE_LIMIT_HIGH_BRIGHTNESS_MODE,
-                        60.f, 60.f)));
+                        initialRefreshRate, initialRefreshRate)));
 
         // Verify that there is no HBM vote initially
         Vote vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_HIGH_BRIGHTNESS_MODE);
@@ -1463,7 +1517,39 @@
                 new BrightnessInfo(1.0f, 0.0f, 1.0f, BrightnessInfo.HIGH_BRIGHTNESS_MODE_SUNLIGHT));
         listener.onDisplayChanged(DISPLAY_ID);
         vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_HIGH_BRIGHTNESS_MODE);
-        assertVoteForRefreshRate(vote, 60.f);
+        assertVoteForRefreshRate(vote, initialRefreshRate);
+
+        // Change refresh rate vote value through DeviceConfig, ensure it takes precedence
+        final int updatedRefreshRate = 90;
+        mInjector.getDeviceConfig().setRefreshRateInHbmSunlight(updatedRefreshRate);
+        // Need to wait for the property change to propagate to the main thread.
+        waitForIdleSync();
+        assertThat(director.getHbmObserver().getRefreshRateInHbmSunlight())
+                .isEqualTo(updatedRefreshRate);
+        vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_HIGH_BRIGHTNESS_MODE);
+        assertVoteForRefreshRate(vote, updatedRefreshRate);
+
+        // Turn off HBM
+        when(mInjector.getBrightnessInfo(DISPLAY_ID)).thenReturn(
+                new BrightnessInfo(0.43f, 0.1f, 0.8f, BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF));
+        listener.onDisplayChanged(DISPLAY_ID);
+        vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_HIGH_BRIGHTNESS_MODE);
+        assertNull(vote);
+
+        // Turn HBM on again and ensure the updated vote value stuck
+        when(mInjector.getBrightnessInfo(DISPLAY_ID)).thenReturn(
+                new BrightnessInfo(1.0f, 0.0f, 1.0f, BrightnessInfo.HIGH_BRIGHTNESS_MODE_SUNLIGHT));
+        listener.onDisplayChanged(DISPLAY_ID);
+        vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_HIGH_BRIGHTNESS_MODE);
+        assertVoteForRefreshRate(vote, updatedRefreshRate);
+
+        // Reset DeviceConfig refresh rate, ensure vote falls back to the initial value
+        mInjector.getDeviceConfig().setRefreshRateInHbmSunlight(0);
+        // Need to wait for the property change to propagate to the main thread.
+        waitForIdleSync();
+        assertThat(director.getHbmObserver().getRefreshRateInHbmSunlight()).isEqualTo(0);
+        vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_HIGH_BRIGHTNESS_MODE);
+        assertVoteForRefreshRate(vote, initialRefreshRate);
 
         // Turn off HBM
         when(mInjector.getBrightnessInfo(DISPLAY_ID)).thenReturn(
@@ -1511,6 +1597,63 @@
         assertNull(vote);
     }
 
+    private void setHbmAndAssertRefreshRate(
+            DisplayModeDirector director, DisplayListener listener, int mode, float rr) {
+        when(mInjector.getBrightnessInfo(DISPLAY_ID))
+                .thenReturn(new BrightnessInfo(1.0f, 0.0f, 1.0f, mode));
+        listener.onDisplayChanged(DISPLAY_ID);
+
+        final Vote vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_HIGH_BRIGHTNESS_MODE);
+        if (Float.isNaN(rr)) {
+            assertNull(vote);
+        } else {
+            assertVoteForRefreshRate(vote, rr);
+        }
+    }
+
+    @Test
+    public void testHbmVoting_forSunlightAndHdr() {
+        DisplayModeDirector director =
+                createDirectorFromRefreshRateArray(new float[] {60.0f, 90.0f}, 0);
+
+        // Specify HDR limitation before starting DisplayModeDirector to avoid waiting on property
+        // propagation
+        final int hdrRr = 60;
+        mInjector.getDeviceConfig().setRefreshRateInHbmHdr(hdrRr);
+        director.start(createMockSensorManager());
+
+        ArgumentCaptor<DisplayListener> captor = ArgumentCaptor.forClass(DisplayListener.class);
+        verify(mInjector).registerDisplayListener(captor.capture(), any(Handler.class),
+                eq(DisplayManager.EVENT_FLAG_DISPLAY_BRIGHTNESS
+                        | DisplayManager.EVENT_FLAG_DISPLAY_REMOVED));
+        DisplayListener listener = captor.getValue();
+
+        // Specify Sunlight limitations
+        final float sunlightRr = 90.0f;
+        when(mDisplayManagerInternalMock.getRefreshRateLimitations(DISPLAY_ID))
+                .thenReturn(List.of(new RefreshRateLimitation(
+                        DisplayManagerInternal.REFRESH_RATE_LIMIT_HIGH_BRIGHTNESS_MODE, sunlightRr,
+                        sunlightRr)));
+
+        // Verify that there is no HBM vote initially
+        Vote vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_HIGH_BRIGHTNESS_MODE);
+        assertNull(vote);
+
+        // Verify all state transitions
+        setHbmAndAssertRefreshRate(
+                director, listener, BrightnessInfo.HIGH_BRIGHTNESS_MODE_SUNLIGHT, sunlightRr);
+        setHbmAndAssertRefreshRate(
+                director, listener, BrightnessInfo.HIGH_BRIGHTNESS_MODE_HDR, hdrRr);
+        setHbmAndAssertRefreshRate(
+                director, listener, BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF, Float.NaN);
+        setHbmAndAssertRefreshRate(
+                director, listener, BrightnessInfo.HIGH_BRIGHTNESS_MODE_HDR, hdrRr);
+        setHbmAndAssertRefreshRate(
+                director, listener, BrightnessInfo.HIGH_BRIGHTNESS_MODE_SUNLIGHT, sunlightRr);
+        setHbmAndAssertRefreshRate(
+                director, listener, BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF, Float.NaN);
+    }
+
     @Test
     public void testHbmVoting_RemovedDisplay() {
         DisplayModeDirector director =
@@ -1547,12 +1690,52 @@
         assertNull(vote);
     }
 
+    @Test
+    public void testSkinTemperature() throws RemoteException {
+        DisplayModeDirector director =
+                createDirectorFromRefreshRateArray(new float[] {60.0f, 90.0f}, 0);
+        director.start(createMockSensorManager());
+
+        ArgumentCaptor<IThermalEventListener> thermalEventListener =
+                ArgumentCaptor.forClass(IThermalEventListener.class);
+
+        verify(mThermalServiceMock).registerThermalEventListenerWithType(
+            thermalEventListener.capture(), eq(Temperature.TYPE_SKIN));
+        final IThermalEventListener listener = thermalEventListener.getValue();
+
+        // Verify that there is no skin temperature vote initially.
+        Vote vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_SKIN_TEMPERATURE);
+        assertNull(vote);
+
+        // Set the skin temperature to critical and verify that we added a vote.
+        listener.notifyThrottling(getSkinTemp(Temperature.THROTTLING_CRITICAL));
+        vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_SKIN_TEMPERATURE);
+        assertVoteForRefreshRateRange(vote, 0f, 60.f);
+
+        // Set the skin temperature to severe and verify that the vote is gone.
+        listener.notifyThrottling(getSkinTemp(Temperature.THROTTLING_SEVERE));
+        vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_SKIN_TEMPERATURE);
+        assertNull(vote);
+    }
+
+    private Temperature getSkinTemp(@Temperature.ThrottlingStatus int status) {
+        return new Temperature(30.0f, Temperature.TYPE_SKIN, "test_skin_temp", status);
+    }
+
     private void assertVoteForRefreshRate(Vote vote, float refreshRate) {
         assertThat(vote).isNotNull();
         final RefreshRateRange expectedRange = new RefreshRateRange(refreshRate, refreshRate);
         assertThat(vote.refreshRateRange).isEqualTo(expectedRange);
     }
 
+    private void assertVoteForRefreshRateRange(
+            Vote vote, float refreshRateLow, float refreshRateHigh) {
+        assertThat(vote).isNotNull();
+        final RefreshRateRange expectedRange =
+                new RefreshRateRange(refreshRateLow, refreshRateHigh);
+        assertThat(vote.refreshRateRange).isEqualTo(expectedRange);
+    }
+
     public static class FakeDeviceConfig extends FakeDeviceConfigInterface {
         @Override
         public String getProperty(String namespace, String name) {
@@ -1575,6 +1758,16 @@
                     String.valueOf(fps));
         }
 
+        void setRefreshRateInHbmSunlight(int fps) {
+            putPropertyAndNotify(DeviceConfig.NAMESPACE_DISPLAY_MANAGER,
+                    KEY_REFRESH_RATE_IN_HBM_SUNLIGHT, String.valueOf(fps));
+        }
+
+        void setRefreshRateInHbmHdr(int fps) {
+            putPropertyAndNotify(DeviceConfig.NAMESPACE_DISPLAY_MANAGER,
+                    KEY_REFRESH_RATE_IN_HBM_HDR, String.valueOf(fps));
+        }
+
         void setLowDisplayBrightnessThresholds(int[] brightnessThresholds) {
             String thresholds = toPropertyValue(brightnessThresholds);
 
@@ -1748,6 +1941,11 @@
             return false;
         }
 
+        @Override
+        public IThermalService getThermalService() {
+            return null;
+        }
+
         void notifyPeakRefreshRateChanged() {
             if (mPeakRefreshRateObserver != null) {
                 mPeakRefreshRateObserver.dispatchChange(false /*selfChange*/,
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
index 293e862a..6f04f17 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
@@ -39,6 +39,7 @@
 import static android.view.Display.DEFAULT_DISPLAY;
 import static android.view.WindowManager.LayoutParams.FIRST_APPLICATION_WINDOW;
 import static android.view.WindowManager.LayoutParams.FIRST_SUB_WINDOW;
+import static android.view.WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM;
 import static android.view.WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD;
 import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED;
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
@@ -2816,6 +2817,73 @@
         assertFalse(activity.mDisplayContent.mClosingApps.contains(activity));
     }
 
+    @Test
+    public void testImeInsetsFrozenFlag_resetWhenReparented() {
+        final ActivityRecord activity = createActivityWithTask();
+        final WindowState app = createWindow(null, TYPE_APPLICATION, activity, "app");
+        final WindowState imeWindow = createWindow(null, TYPE_APPLICATION, "imeWindow");
+        final Task newTask = new TaskBuilder(mSupervisor).build();
+        makeWindowVisible(app, imeWindow);
+        mDisplayContent.mInputMethodWindow = imeWindow;
+        mDisplayContent.setImeLayeringTarget(app);
+        mDisplayContent.setImeInputTarget(app);
+
+        // Simulate app is closing and expect the last IME is shown and IME insets is frozen.
+        app.mActivityRecord.commitVisibility(false, false);
+        assertTrue(app.mActivityRecord.mLastImeShown);
+        assertTrue(app.mActivityRecord.mImeInsetsFrozenUntilStartInput);
+
+        // Expect IME insets frozen state will reset when the activity is reparent to the new task.
+        activity.setState(RESUMED, "test");
+        activity.reparent(newTask, 0 /* top */, "test");
+        assertFalse(app.mActivityRecord.mImeInsetsFrozenUntilStartInput);
+    }
+
+    @UseTestDisplay(addWindows = W_INPUT_METHOD)
+    @Test
+    public void testImeInsetsFrozenFlag_resetWhenResized() {
+        final WindowState app = createWindow(null, TYPE_APPLICATION, "app");
+        makeWindowVisibleAndDrawn(app, mImeWindow);
+        mDisplayContent.setImeLayeringTarget(app);
+        mDisplayContent.setImeInputTarget(app);
+
+        // Simulate app is closing and expect the last IME is shown and IME insets is frozen.
+        app.mActivityRecord.commitVisibility(false, false);
+        assertTrue(app.mActivityRecord.mLastImeShown);
+        assertTrue(app.mActivityRecord.mImeInsetsFrozenUntilStartInput);
+
+        // Expect IME insets frozen state will reset when the activity is reparent to the new task.
+        app.mActivityRecord.onResize();
+        assertFalse(app.mActivityRecord.mImeInsetsFrozenUntilStartInput);
+    }
+
+    @UseTestDisplay(addWindows = W_INPUT_METHOD)
+    @Test
+    public void testImeInsetsFrozenFlag_resetWhenNoImeFocusableInActivity() {
+        final WindowState app = createWindow(null, TYPE_APPLICATION, "app");
+        makeWindowVisibleAndDrawn(app, mImeWindow);
+        mDisplayContent.setImeLayeringTarget(app);
+        mDisplayContent.setImeInputTarget(app);
+
+        // Simulate app is closing and expect the last IME is shown and IME insets is frozen.
+        app.mActivityRecord.commitVisibility(false, false);
+        app.mActivityRecord.onWindowsGone();
+
+        assertTrue(app.mActivityRecord.mLastImeShown);
+        assertTrue(app.mActivityRecord.mImeInsetsFrozenUntilStartInput);
+
+        // Expect IME insets frozen state will reset when the activity has no IME focusable window.
+        app.mActivityRecord.forAllWindowsUnchecked(w -> {
+            w.mAttrs.flags |= FLAG_ALT_FOCUSABLE_IM;
+            return true;
+        }, true);
+
+        app.mActivityRecord.commitVisibility(true, false);
+        app.mActivityRecord.onWindowsVisible();
+
+        assertFalse(app.mActivityRecord.mImeInsetsFrozenUntilStartInput);
+    }
+
     private void assertHasStartingWindow(ActivityRecord atoken) {
         assertNotNull(atoken.mStartingSurface);
         assertNotNull(atoken.mStartingData);
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
index 12fc2f4..5a6581f 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
@@ -2166,6 +2166,21 @@
     }
 
     @Test
+    public void testKeyguardGoingAwayWhileAodShown() {
+        mDisplayContent.getDisplayPolicy().setAwake(true);
+
+        final WindowState appWin = createWindow(null, TYPE_APPLICATION, mDisplayContent, "appWin");
+        final ActivityRecord activity = appWin.mActivityRecord;
+
+        mAtm.mKeyguardController.setKeyguardShown(true /* keyguardShowing */,
+                true /* aodShowing */);
+        assertFalse(activity.isVisibleRequested());
+
+        mAtm.mKeyguardController.keyguardGoingAway(0 /* flags */);
+        assertTrue(activity.isVisibleRequested());
+    }
+
+    @Test
     public void testRemoveRootTaskInWindowingModes() {
         removeRootTaskTests(() -> mRootWindowContainer.removeRootTasksInWindowingModes(
                 WINDOWING_MODE_FULLSCREEN));
diff --git a/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java b/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java
index a8e1753..8e7ba4bc 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java
@@ -258,6 +258,7 @@
         final ActivityManagerInternal amInternal = mAmService.mInternal;
         spyOn(amInternal);
         doNothing().when(amInternal).trimApplications();
+        doNothing().when(amInternal).scheduleAppGcs();
         doNothing().when(amInternal).updateCpuStats();
         doNothing().when(amInternal).updateOomAdj();
         doNothing().when(amInternal).updateBatteryStats(any(), anyInt(), anyInt(), anyBoolean());
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
index d6a8401..ab496cf 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
@@ -797,6 +797,9 @@
         public void onBackPressedOnTaskRoot(RunningTaskInfo taskInfo) {
         }
         @Override
+        public void onImeDrawnOnTask(int taskId) throws RemoteException {
+        }
+        @Override
         public void onAppSplashScreenViewRemoved(int taskId) {
         }
     };
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
index 92b670e..d88ac25 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
@@ -891,6 +891,40 @@
         assertTrue(mAppWindow.getInsetsState().getSourceOrDefaultVisibility(ITYPE_NAVIGATION_BAR));
     }
 
+    @Test
+    public void testAdjustImeInsetsVisibilityWhenSwitchingApps() {
+        final WindowState app = createWindow(null, TYPE_APPLICATION, "app");
+        final WindowState app2 = createWindow(null, TYPE_APPLICATION, "app2");
+        final WindowState imeWindow = createWindow(null, TYPE_APPLICATION, "imeWindow");
+        spyOn(imeWindow);
+        doReturn(true).when(imeWindow).isVisible();
+        mDisplayContent.mInputMethodWindow = imeWindow;
+
+        final InsetsStateController controller = mDisplayContent.getInsetsStateController();
+        controller.getImeSourceProvider().setWindow(imeWindow, null, null);
+
+        // Simulate app requests IME with updating all windows Insets State when IME is above app.
+        mDisplayContent.setImeLayeringTarget(app);
+        mDisplayContent.setImeInputTarget(app);
+        assertTrue(mDisplayContent.shouldImeAttachedToApp());
+        controller.getImeSourceProvider().scheduleShowImePostLayout(app);
+        controller.getImeSourceProvider().getSource().setVisible(true);
+        controller.updateAboveInsetsState(imeWindow, false);
+
+        // Expect all app windows behind IME can receive IME insets visible.
+        assertTrue(app.getInsetsState().getSource(ITYPE_IME).isVisible());
+        assertTrue(app2.getInsetsState().getSource(ITYPE_IME).isVisible());
+
+        // Simulate app plays closing transition to app2.
+        app.mActivityRecord.commitVisibility(false, false);
+        assertTrue(app.mActivityRecord.mLastImeShown);
+        assertTrue(app.mActivityRecord.mImeInsetsFrozenUntilStartInput);
+
+        // Verify the IME insets is visible on app, but not for app2 during app task switching.
+        assertTrue(app.getInsetsState().getSource(ITYPE_IME).isVisible());
+        assertFalse(app2.getInsetsState().getSource(ITYPE_IME).isVisible());
+    }
+
     @UseTestDisplay(addWindows = { W_ACTIVITY })
     @Test
     public void testUpdateImeControlTargetWhenLeavingMultiWindow() {
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTokenTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowTokenTests.java
index d048f1842..589f913 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowTokenTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowTokenTests.java
@@ -24,6 +24,7 @@
 import static android.view.WindowManager.LayoutParams.TYPE_TOAST;
 
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
+import static com.android.server.policy.WindowManagerPolicy.TRANSIT_EXIT;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
@@ -44,6 +45,7 @@
 
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.Mockito;
 
 import java.util.function.BiFunction;
 
@@ -126,7 +128,7 @@
         final WindowState window1 = createWindow(null, TYPE_TOAST, token, "window1");
         final WindowState window2 = createWindow(null, TYPE_TOAST, token, "window2");
 
-        mDisplayContent.removeWindowToken(token.token);
+        mDisplayContent.removeWindowToken(token.token, true /* animateExit */);
         // Verify that the token is no longer mapped on the display
         assertNull(mDisplayContent.getWindowToken(token.token));
         // Verify that the token is still attached to its parent
@@ -261,4 +263,29 @@
         assertNotNull(app.getFrozenInsetsState());
         assertNull(mDisplayContent.mInputMethodWindow.getFrozenInsetsState());
     }
+
+    @Test
+    public void testRemoveWindowToken_noAnimateExitWhenSet() {
+        final TestWindowToken token = createTestWindowToken(0, mDisplayContent);
+        final WindowState win = createWindow(null, TYPE_APPLICATION, token, "win");
+        makeWindowVisible(win);
+        assertTrue(win.isOnScreen());
+        spyOn(win);
+        spyOn(win.mWinAnimator);
+        spyOn(win.mToken);
+
+        // Invoking removeWindowToken with setting no window exit animation and not remove window
+        // immediately. verify the window will hide without applying exit animation.
+        mWm.removeWindowToken(win.mToken.token, false /* removeWindows */, false /* animateExit */,
+                mDisplayContent.mDisplayId);
+        verify(win).onSetAppExiting(Mockito.eq(false) /* animateExit */);
+        verify(win).hide(false /* doAnimation */, false /* requestAnim */);
+        assertFalse(win.isOnScreen());
+        verify(win.mWinAnimator, Mockito.never()).applyAnimationLocked(TRANSIT_EXIT, false);
+        assertTrue(win.mToken.hasChild());
+
+        // Even though the window is being removed afterwards, it won't apply exit animation.
+        win.removeIfPossible();
+        verify(win.mWinAnimator, Mockito.never()).applyAnimationLocked(TRANSIT_EXIT, false);
+    }
 }
diff --git a/services/usb/java/com/android/server/usb/UsbDeviceManager.java b/services/usb/java/com/android/server/usb/UsbDeviceManager.java
index 7f24c36..85f16eb 100644
--- a/services/usb/java/com/android/server/usb/UsbDeviceManager.java
+++ b/services/usb/java/com/android/server/usb/UsbDeviceManager.java
@@ -1193,7 +1193,8 @@
                 } else if (mCurrentFunctions == UsbManager.FUNCTION_MIDI) {
                     titleRes = com.android.internal.R.string.usb_midi_notification_title;
                     id = SystemMessage.NOTE_USB_MIDI;
-                } else if (mCurrentFunctions == UsbManager.FUNCTION_RNDIS) {
+                } else if ((mCurrentFunctions == UsbManager.FUNCTION_RNDIS)
+                        || (mCurrentFunctions == UsbManager.FUNCTION_NCM)) {
                     titleRes = com.android.internal.R.string.usb_tether_notification_title;
                     id = SystemMessage.NOTE_USB_TETHER;
                 } else if (mCurrentFunctions == UsbManager.FUNCTION_ACCESSORY) {
diff --git a/telecomm/java/android/telecom/Connection.java b/telecomm/java/android/telecom/Connection.java
index d06fe45..9fec96b 100644
--- a/telecomm/java/android/telecom/Connection.java
+++ b/telecomm/java/android/telecom/Connection.java
@@ -781,6 +781,21 @@
             "android.telecom.extra.REMOTE_PHONE_ACCOUNT_HANDLE";
 
     /**
+     * The Telecom call ID of the conference an existing connection should be added to.  This is
+     * required when {@link com.android.services.telephony.TelephonyConnectionService} adds a
+     * {@link Conference} to Telecom using the
+     * {@link ConnectionService#addExistingConnection(PhoneAccountHandle, Connection, Conference)}
+     * API.  That API specifies a parent conference associated with the new existing connection
+     * being added, and there is no equivalent as part of the {@link RemoteConnectionService} API.
+     * This extra key is used to stack the ID of the conference to which the existing connection
+     * will be added so that Telecom can link it up correctly when the {@link RemoteConference}
+     * is added to Telecom by the connection manager.
+     * @hide
+     */
+    public static final String EXTRA_ADD_TO_CONFERENCE_ID =
+            "android.telecom.extra.ADD_TO_CONFERENCE_ID";
+
+    /**
      * Extra key set from a {@link ConnectionService} when using the remote connection APIs
      * (e.g. {@link RemoteConnectionService#createRemoteConnection(PhoneAccountHandle,
      * ConnectionRequest, boolean)}) to create a remote connection.  Provides the receiving
diff --git a/telecomm/java/android/telecom/RemoteConnectionService.java b/telecomm/java/android/telecom/RemoteConnectionService.java
index bf6a6ef7..efe35d2 100644
--- a/telecomm/java/android/telecom/RemoteConnectionService.java
+++ b/telecomm/java/android/telecom/RemoteConnectionService.java
@@ -239,13 +239,9 @@
                     conference.addConnection(c);
                 }
             }
-            if (conference.getConnections().size() == 0) {
-                // A conference was created, but none of its connections are ones that have been
-                // created by, and therefore being tracked by, this remote connection service. It
-                // is of no interest to us.
-                Log.d(this, "addConferenceCall - skipping");
-                return;
-            }
+            // We used to skip adding empty conferences; however in the world of IMS conference
+            // calls we need to add them to the remote connection service because they will always
+            // start with no participants.
 
             conference.setState(parcel.getState());
             conference.setConnectionCapabilities(parcel.getConnectionCapabilities());
@@ -379,6 +375,8 @@
         @Override
         public void addExistingConnection(String callId, ParcelableConnection connection,
                 Session.Info sessionInfo) {
+            Log.i(RemoteConnectionService.this, "addExistingConnection: callId=%s, conn=%s", callId,
+                    connection);
             String callingPackage = mOurConnectionServiceImpl.getApplicationContext().
                     getOpPackageName();
             int callingTargetSdkVersion = mOurConnectionServiceImpl.getApplicationInfo()
@@ -390,6 +388,20 @@
             Bundle newExtras = new Bundle();
             newExtras.putParcelable(Connection.EXTRA_REMOTE_PHONE_ACCOUNT_HANDLE,
                     connection.getPhoneAccount());
+            if (connection.getParentCallId() != null) {
+                RemoteConference parentConf = mConferenceById.get(connection.getParentCallId());
+                // If there is a parent being set, we need to stash the conference ID here.
+                // Telephony can add an existing connection while specifying a parent conference.
+                // There is no equivalent version of that operation as part of the remote connection
+                // API, so we will stash the pre-defined parent's ID in the extras.  When the
+                // connectionmanager copies over the extras from the remote connection to the
+                // actual one, it'll get passed to Telecom so that it can make the association.
+                if (parentConf != null) {
+                    newExtras.putString(Connection.EXTRA_ADD_TO_CONFERENCE_ID, parentConf.getId());
+                    Log.i(this, "addExistingConnection: stash parent of %s as %s",
+                            connection.getParentCallId(), parentConf.getId());
+                }
+            }
             remoteConnection.putExtras(newExtras);
             mConnectionById.put(callId, remoteConnection);
             remoteConnection.registerCallback(new RemoteConnection.Callback() {