Merge "AudioService: properties for configuring ring/notif steps/default vol" into tm-qpr-dev
diff --git a/apct-tests/perftests/windowmanager/src/android/wm/RelayoutPerfTest.java b/apct-tests/perftests/windowmanager/src/android/wm/RelayoutPerfTest.java
index c92c634..fb62920 100644
--- a/apct-tests/perftests/windowmanager/src/android/wm/RelayoutPerfTest.java
+++ b/apct-tests/perftests/windowmanager/src/android/wm/RelayoutPerfTest.java
@@ -153,9 +153,9 @@
             final IWindowSession session = WindowManagerGlobal.getWindowSession();
             while (state.keepRunning()) {
                 session.relayout(mWindow, mParams, mWidth, mHeight,
-                        mViewVisibility.getAsInt(), mFlags, mOutFrames,
-                        mOutMergedConfiguration, mOutSurfaceControl, mOutInsetsState, mOutControls,
-                        new Bundle());
+                        mViewVisibility.getAsInt(), mFlags, 0 /* seq */, 0 /* lastSyncSeqId */,
+                        mOutFrames, mOutMergedConfiguration, mOutSurfaceControl, mOutInsetsState,
+                        mOutControls, new Bundle());
             }
         }
     }
diff --git a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
index c94cc8f..37ce0d2 100644
--- a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
@@ -5262,13 +5262,15 @@
                                         + TareBill.getName(bill) + " changed to " + canAfford);
                     }
 
-                    ArrayMap<EconomyManagerInternal.ActionBill, Boolean> actionAffordability =
-                            mAffordabilityCache.get(userId, packageName);
-                    if (actionAffordability == null) {
-                        actionAffordability = new ArrayMap<>();
-                        mAffordabilityCache.add(userId, packageName, actionAffordability);
+                    synchronized (mLock) {
+                        ArrayMap<EconomyManagerInternal.ActionBill, Boolean> actionAffordability =
+                                mAffordabilityCache.get(userId, packageName);
+                        if (actionAffordability == null) {
+                            actionAffordability = new ArrayMap<>();
+                            mAffordabilityCache.add(userId, packageName, actionAffordability);
+                        }
+                        actionAffordability.put(bill, canAfford);
                     }
-                    actionAffordability.put(bill, canAfford);
 
                     mHandler.obtainMessage(AlarmHandler.TARE_AFFORDABILITY_CHANGED, userId,
                             canAfford ? 1 : 0, packageName)
diff --git a/cmds/uiautomator/library/core-src/com/android/uiautomator/core/UiDevice.java b/cmds/uiautomator/library/core-src/com/android/uiautomator/core/UiDevice.java
index b1b432b..6fd2bf2 100644
--- a/cmds/uiautomator/library/core-src/com/android/uiautomator/core/UiDevice.java
+++ b/cmds/uiautomator/library/core-src/com/android/uiautomator/core/UiDevice.java
@@ -380,7 +380,7 @@
         Tracer.trace();
         Display display = getAutomatorBridge().getDefaultDisplay();
         Point p = new Point();
-        display.getSize(p);
+        display.getRealSize(p);
         return p.x;
     }
 
@@ -394,7 +394,7 @@
         Tracer.trace();
         Display display = getAutomatorBridge().getDefaultDisplay();
         Point p = new Point();
-        display.getSize(p);
+        display.getRealSize(p);
         return p.y;
     }
 
diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java
index 8e67705..ce7b5e1 100644
--- a/core/java/android/inputmethodservice/InputMethodService.java
+++ b/core/java/android/inputmethodservice/InputMethodService.java
@@ -148,6 +148,7 @@
 import com.android.internal.inputmethod.InputMethodNavButtonFlags;
 import com.android.internal.inputmethod.InputMethodPrivilegedOperations;
 import com.android.internal.inputmethod.InputMethodPrivilegedOperationsRegistry;
+import com.android.internal.inputmethod.SoftInputShowHideReason;
 import com.android.internal.util.RingBuffer;
 import com.android.internal.view.IInlineSuggestionsRequestCallback;
 import com.android.internal.view.IInputContext;
@@ -2962,9 +2963,13 @@
      * @param flags Provides additional operating flags.
      */
     public void requestHideSelf(int flags) {
+        requestHideSelf(flags, SoftInputShowHideReason.HIDE_SOFT_INPUT_FROM_IME);
+    }
+
+    private void requestHideSelf(int flags, @SoftInputShowHideReason int reason) {
         ImeTracing.getInstance().triggerServiceDump("InputMethodService#requestHideSelf", mDumper,
                 null /* icProto */);
-        mPrivOps.hideMySoftInput(flags);
+        mPrivOps.hideMySoftInput(flags, reason);
     }
 
     /**
@@ -2985,7 +2990,9 @@
         if (mShowInputRequested) {
             // If the soft input area is shown, back closes it and we
             // consume the back key.
-            if (doIt) requestHideSelf(0);
+            if (doIt) {
+                requestHideSelf(0 /* flags */, SoftInputShowHideReason.HIDE_SOFT_INPUT_BY_BACK_KEY);
+            }
             return true;
         } else if (mDecorViewVisible) {
             if (mCandidatesVisibility == View.VISIBLE) {
@@ -3136,7 +3143,8 @@
     private void onToggleSoftInput(int showFlags, int hideFlags) {
         if (DEBUG) Log.v(TAG, "toggleSoftInput()");
         if (isInputViewShown()) {
-            requestHideSelf(hideFlags);
+            requestHideSelf(
+                    hideFlags, SoftInputShowHideReason.HIDE_SOFT_INPUT_IME_TOGGLE_SOFT_INPUT);
         } else {
             requestShowSelf(showFlags);
         }
@@ -3571,7 +3579,8 @@
      */
     public void onExtractingInputChanged(EditorInfo ei) {
         if (ei.inputType == InputType.TYPE_NULL) {
-            requestHideSelf(InputMethodManager.HIDE_NOT_ALWAYS);
+            requestHideSelf(InputMethodManager.HIDE_NOT_ALWAYS,
+                    SoftInputShowHideReason.HIDE_SOFT_INPUT_EXTRACT_INPUT_CHANGED);
         }
     }
 
diff --git a/core/java/android/service/dreams/IDreamManager.aidl b/core/java/android/service/dreams/IDreamManager.aidl
index 3e0deeb..53ae657 100644
--- a/core/java/android/service/dreams/IDreamManager.aidl
+++ b/core/java/android/service/dreams/IDreamManager.aidl
@@ -35,6 +35,8 @@
     void testDream(int userId, in ComponentName componentName);
     @UnsupportedAppUsage
     boolean isDreaming();
+    @UnsupportedAppUsage
+    boolean isDreamingOrInPreview();
     void finishSelf(in IBinder token, boolean immediate);
     void startDozing(in IBinder token, int screenState, int screenBrightness);
     void stopDozing(in IBinder token);
diff --git a/core/java/android/service/selectiontoolbar/DefaultSelectionToolbarRenderService.java b/core/java/android/service/selectiontoolbar/DefaultSelectionToolbarRenderService.java
index f028ed3..ad73a53 100644
--- a/core/java/android/service/selectiontoolbar/DefaultSelectionToolbarRenderService.java
+++ b/core/java/android/service/selectiontoolbar/DefaultSelectionToolbarRenderService.java
@@ -69,7 +69,7 @@
 
         if (mToolbarCache.indexOfKey(callingUid) < 0) {
             RemoteSelectionToolbar toolbar = new RemoteSelectionToolbar(this,
-                    widgetToken, showInfo.getHostInputToken(),
+                    widgetToken, showInfo,
                     callbackWrapper, this::transferTouch);
             mToolbarCache.put(callingUid, new Pair<>(widgetToken, toolbar));
         }
diff --git a/core/java/android/service/selectiontoolbar/RemoteSelectionToolbar.java b/core/java/android/service/selectiontoolbar/RemoteSelectionToolbar.java
index d75fbc0..95bcda5 100644
--- a/core/java/android/service/selectiontoolbar/RemoteSelectionToolbar.java
+++ b/core/java/android/service/selectiontoolbar/RemoteSelectionToolbar.java
@@ -22,7 +22,6 @@
 import android.animation.ObjectAnimator;
 import android.animation.ValueAnimator;
 import android.content.Context;
-import android.content.res.TypedArray;
 import android.graphics.Point;
 import android.graphics.Rect;
 import android.graphics.drawable.AnimatedVectorDrawable;
@@ -162,15 +161,14 @@
     private final Rect mTempContentRectForRoot = new Rect();
     private final int[] mTempCoords = new int[2];
 
-    RemoteSelectionToolbar(Context context, long selectionToolbarToken, IBinder hostInputToken,
+    RemoteSelectionToolbar(Context context, long selectionToolbarToken, ShowInfo showInfo,
             SelectionToolbarRenderService.RemoteCallbackWrapper callbackWrapper,
             SelectionToolbarRenderService.TransferTouchListener transferTouchListener) {
-        mContext = applyDefaultTheme(context);
+        mContext = applyDefaultTheme(context, showInfo.isIsLightTheme());
         mSelectionToolbarToken = selectionToolbarToken;
         mCallbackWrapper = callbackWrapper;
         mTransferTouchListener = transferTouchListener;
-        mHostInputToken = hostInputToken;
-
+        mHostInputToken = showInfo.getHostInputToken();
         mContentContainer = createContentContainer(mContext);
         mMarginHorizontal = mContext.getResources()
                 .getDimensionPixelSize(R.dimen.floating_toolbar_horizontal_margin);
@@ -1359,12 +1357,9 @@
     /**
      * Returns a re-themed context with controlled look and feel for views.
      */
-    private static Context applyDefaultTheme(Context originalContext) {
-        TypedArray a = originalContext.obtainStyledAttributes(new int[]{R.attr.isLightTheme});
-        boolean isLightTheme = a.getBoolean(0, true);
+    private static Context applyDefaultTheme(Context originalContext, boolean isLightTheme) {
         int themeId =
                 isLightTheme ? R.style.Theme_DeviceDefault_Light : R.style.Theme_DeviceDefault;
-        a.recycle();
         return new ContextThemeWrapper(originalContext, themeId);
     }
 
diff --git a/core/java/android/service/wallpaper/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java
index 90e4e94..14cfe6a 100644
--- a/core/java/android/service/wallpaper/WallpaperService.java
+++ b/core/java/android/service/wallpaper/WallpaperService.java
@@ -1154,8 +1154,8 @@
                         mLayout.surfaceInsets.set(0, 0, 0, 0);
                     }
                     final int relayoutResult = mSession.relayout(mWindow, mLayout, mWidth, mHeight,
-                            View.VISIBLE, 0, mWinFrames, mMergedConfiguration, mSurfaceControl,
-                            mInsetsState, mTempControls, mSyncSeqIdBundle);
+                            View.VISIBLE, 0, 0, 0, mWinFrames, mMergedConfiguration,
+                            mSurfaceControl, mInsetsState, mTempControls, mSyncSeqIdBundle);
 
                     final int transformHint = SurfaceControl.rotationToBufferTransform(
                             (mDisplayInstallOrientation + mDisplay.getRotation()) % 4);
diff --git a/core/java/android/view/IWindowSession.aidl b/core/java/android/view/IWindowSession.aidl
index 3016473..afcec66 100644
--- a/core/java/android/view/IWindowSession.aidl
+++ b/core/java/android/view/IWindowSession.aidl
@@ -75,41 +75,42 @@
      * @param requestedWidth The width the window wants to be.
      * @param requestedHeight The height the window wants to be.
      * @param viewVisibility Window root view's visibility.
-     * @param flags Request flags: {@link WindowManagerGlobal#RELAYOUT_INSETS_PENDING},
-     * {@link WindowManagerGlobal#RELAYOUT_DEFER_SURFACE_DESTROY}.
-     * @param outFrame Rect in which is placed the new position/size on
-     * screen.
-     * @param outContentInsets Rect in which is placed the offsets from
-     * <var>outFrame</var> in which the content of the window should be
-     * placed.  This can be used to modify the window layout to ensure its
-     * contents are visible to the user, taking into account system windows
-     * like the status bar or a soft keyboard.
-     * @param outVisibleInsets Rect in which is placed the offsets from
-     * <var>outFrame</var> in which the window is actually completely visible
-     * to the user.  This can be used to temporarily scroll the window's
-     * contents to make sure the user can see it.  This is different than
-     * <var>outContentInsets</var> in that these insets change transiently,
-     * so complex relayout of the window should not happen based on them.
-     * @param outOutsets Rect in which is placed the dead area of the screen that we would like to
-     * treat as real display. Example of such area is a chin in some models of wearable devices.
-     * @param outBackdropFrame Rect which is used draw the resizing background during a resize
-     * operation.
+     * @param flags Request flags: {@link WindowManagerGlobal#RELAYOUT_INSETS_PENDING}.
+     * @param seq The calling sequence of {@link #relayout} and {@link #relayoutAsync}.
+     * @param lastSyncSeqId The last SyncSeqId that the client applied.
+     * @param outFrames The window frames used by the client side for layout.
      * @param outMergedConfiguration New config container that holds global, override and merged
-     * config for window, if it is now becoming visible and the merged configuration has changed
-     * since it was last displayed.
-     * @param outSurface Object in which is placed the new display surface.
+     *                               config for window, if it is now becoming visible and the merged
+     *                               config has changed since it was last displayed.
+     * @param outSurfaceControl Object in which is placed the new display surface.
      * @param insetsState The current insets state in the system.
-     *
-     * @return int Result flags: {@link WindowManagerGlobal#RELAYOUT_SHOW_FOCUS},
-     * {@link WindowManagerGlobal#RELAYOUT_FIRST_TIME}.
+     * @param activeControls Objects which allow controlling {@link InsetsSource}s.
+     * @param bundle A temporary object to obtain the latest SyncSeqId.
+     * @return int Result flags, defined in {@link WindowManagerGlobal}.
      */
     int relayout(IWindow window, in WindowManager.LayoutParams attrs,
             int requestedWidth, int requestedHeight, int viewVisibility,
-            int flags, out ClientWindowFrames outFrames,
+            int flags, int seq, int lastSyncSeqId, out ClientWindowFrames outFrames,
             out MergedConfiguration outMergedConfiguration, out SurfaceControl outSurfaceControl,
             out InsetsState insetsState, out InsetsSourceControl[] activeControls,
             out Bundle bundle);
 
+    /**
+     * Similar to {@link #relayout} but this is an oneway method which doesn't return anything.
+     *
+     * @param window The window being modified.
+     * @param attrs If non-null, new attributes to apply to the window.
+     * @param requestedWidth The width the window wants to be.
+     * @param requestedHeight The height the window wants to be.
+     * @param viewVisibility Window root view's visibility.
+     * @param flags Request flags: {@link WindowManagerGlobal#RELAYOUT_INSETS_PENDING}.
+     * @param seq The calling sequence of {@link #relayout} and {@link #relayoutAsync}.
+     * @param lastSyncSeqId The last SyncSeqId that the client applied.
+     */
+    oneway void relayoutAsync(IWindow window, in WindowManager.LayoutParams attrs,
+            int requestedWidth, int requestedHeight, int viewVisibility, int flags, int seq,
+            int lastSyncSeqId);
+
     /*
      * Notify the window manager that an application is relaunching and
      * windows should be prepared for replacement.
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index fff6c60..af57f3b 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -75,6 +75,7 @@
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_LAYOUT_SIZE_EXTENDED_BY_CUTOUT;
 import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE;
 import static android.view.WindowManager.LayoutParams.SOFT_INPUT_MASK_ADJUST;
+import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING;
 import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD;
 import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR_ADDITIONAL;
 import static android.view.WindowManager.LayoutParams.TYPE_SYSTEM_ALERT;
@@ -706,6 +707,8 @@
     final Rect mPendingBackDropFrame = new Rect();
 
     boolean mPendingAlwaysConsumeSystemBars;
+    private int mRelayoutSeq;
+    private final Rect mWinFrameInScreen = new Rect();
     private final InsetsState mTempInsets = new InsetsState();
     private final InsetsSourceControl[] mTempControls = new InsetsSourceControl[SIZE];
     private final WindowConfiguration mTempWinConfig = new WindowConfiguration();
@@ -3333,20 +3336,6 @@
                 }
             }
         } else {
-            // If a relayout isn't going to happen, we still need to check if this window can draw
-            // when mCheckIfCanDraw is set. This is because it means we had a sync in the past, but
-            // have not been told by WMS that the sync is complete and that we can continue to draw
-            if (mCheckIfCanDraw) {
-                try {
-                    cancelDraw = mWindowSession.cancelDraw(mWindow);
-                    cancelReason = "wm_sync";
-                    if (DEBUG_BLAST) {
-                        Log.d(mTag, "cancelDraw returned " + cancelDraw);
-                    }
-                } catch (RemoteException e) {
-                }
-            }
-
             // Not the first pass and no window/insets/visibility change but the window
             // may have moved and we need check that and if so to update the left and right
             // in the attach info. We translate only the window frame since on window move
@@ -3355,6 +3344,20 @@
             maybeHandleWindowMove(frame);
         }
 
+        if (!mRelayoutRequested && mCheckIfCanDraw) {
+            // We had a sync previously, but we didn't call IWindowSession#relayout in this
+            // traversal. So we don't know if the sync is complete that we can continue to draw.
+            // Here invokes cancelDraw to obtain the information.
+            try {
+                cancelDraw = mWindowSession.cancelDraw(mWindow);
+                cancelReason = "wm_sync";
+                if (DEBUG_BLAST) {
+                    Log.d(mTag, "cancelDraw returned " + cancelDraw);
+                }
+            } catch (RemoteException e) {
+            }
+        }
+
         if (surfaceSizeChanged || surfaceReplaced || surfaceCreated || windowAttributesChanged) {
             // If the surface has been replaced, there's a chance the bounds layer is not parented
             // to the new layer. When updating bounds layer, also reparent to the main VRI
@@ -8082,7 +8085,43 @@
 
     private int relayoutWindow(WindowManager.LayoutParams params, int viewVisibility,
             boolean insetsPending) throws RemoteException {
-        mRelayoutRequested = true;
+        final WindowConfiguration winConfigFromAm = getConfiguration().windowConfiguration;
+        final WindowConfiguration winConfigFromWm =
+                mLastReportedMergedConfiguration.getGlobalConfiguration().windowConfiguration;
+        final WindowConfiguration winConfig = getCompatWindowConfiguration();
+        final int measuredWidth = mView.getMeasuredWidth();
+        final int measuredHeight = mView.getMeasuredHeight();
+        final boolean relayoutAsync;
+        if (LOCAL_LAYOUT && !mFirst && viewVisibility == mViewVisibility
+                && mWindowAttributes.type != TYPE_APPLICATION_STARTING
+                && mSyncSeqId <= mLastSyncSeqId
+                && winConfigFromAm.diff(winConfigFromWm, false /* compareUndefined */) == 0) {
+            final InsetsState state = mInsetsController.getState();
+            final Rect displayCutoutSafe = mTempRect;
+            state.getDisplayCutoutSafe(displayCutoutSafe);
+            mWindowLayout.computeFrames(mWindowAttributes.forRotation(winConfig.getRotation()),
+                    state, displayCutoutSafe, winConfig.getBounds(), winConfig.getWindowingMode(),
+                    measuredWidth, measuredHeight, mInsetsController.getRequestedVisibilities(),
+                    1f /* compatScale */, mTmpFrames);
+            mWinFrameInScreen.set(mTmpFrames.frame);
+            if (mTranslator != null) {
+                mTranslator.translateRectInAppWindowToScreen(mWinFrameInScreen);
+            }
+
+            // If the position and the size of the frame are both changed, it will trigger a BLAST
+            // sync, and we still need to call relayout to obtain the syncSeqId. Otherwise, we just
+            // need to send attributes via relayoutAsync.
+            final Rect oldFrame = mWinFrame;
+            final Rect newFrame = mTmpFrames.frame;
+            final boolean positionChanged =
+                    newFrame.top != oldFrame.top || newFrame.left != oldFrame.left;
+            final boolean sizeChanged =
+                    newFrame.width() != oldFrame.width() || newFrame.height() != oldFrame.height();
+            relayoutAsync = !positionChanged || !sizeChanged;
+        } else {
+            relayoutAsync = false;
+        }
+
         float appScale = mAttachInfo.mApplicationScale;
         boolean restore = false;
         if (params != null && mTranslator != null) {
@@ -8104,26 +8143,47 @@
             }
         }
 
-        final int requestedWidth = (int) (mView.getMeasuredWidth() * appScale + 0.5f);
-        final int requestedHeight = (int) (mView.getMeasuredHeight() * appScale + 0.5f);
+        final int requestedWidth = (int) (measuredWidth * appScale + 0.5f);
+        final int requestedHeight = (int) (measuredHeight * appScale + 0.5f);
+        int relayoutResult = 0;
+        mRelayoutSeq++;
+        if (relayoutAsync) {
+            mWindowSession.relayoutAsync(mWindow, params,
+                    requestedWidth, requestedHeight, viewVisibility,
+                    insetsPending ? WindowManagerGlobal.RELAYOUT_INSETS_PENDING : 0, mRelayoutSeq,
+                    mLastSyncSeqId);
+        } else {
+            relayoutResult = mWindowSession.relayout(mWindow, params,
+                    requestedWidth, requestedHeight, viewVisibility,
+                    insetsPending ? WindowManagerGlobal.RELAYOUT_INSETS_PENDING : 0, mRelayoutSeq,
+                    mLastSyncSeqId, mTmpFrames, mPendingMergedConfiguration, mSurfaceControl,
+                    mTempInsets, mTempControls, mRelayoutBundle);
+            mRelayoutRequested = true;
+            final int maybeSyncSeqId = mRelayoutBundle.getInt("seqid");
+            if (maybeSyncSeqId > 0) {
+                mSyncSeqId = maybeSyncSeqId;
+            }
+            mWinFrameInScreen.set(mTmpFrames.frame);
+            if (mTranslator != null) {
+                mTranslator.translateRectInScreenToAppWindow(mTmpFrames.frame);
+                mTranslator.translateRectInScreenToAppWindow(mTmpFrames.displayFrame);
+                mTranslator.translateRectInScreenToAppWindow(mTmpFrames.attachedFrame);
+                mTranslator.translateInsetsStateInScreenToAppWindow(mTempInsets);
+                mTranslator.translateSourceControlsInScreenToAppWindow(mTempControls);
+            }
+            mInvSizeCompatScale = 1f / mTmpFrames.sizeCompatScale;
+            mInsetsController.onStateChanged(mTempInsets);
+            mInsetsController.onControlsChanged(mTempControls);
 
-        int relayoutResult = mWindowSession.relayout(mWindow, params,
-                requestedWidth, requestedHeight, viewVisibility,
-                insetsPending ? WindowManagerGlobal.RELAYOUT_INSETS_PENDING : 0,
-                mTmpFrames, mPendingMergedConfiguration, mSurfaceControl, mTempInsets,
-                mTempControls, mRelayoutBundle);
-        final int maybeSyncSeqId = mRelayoutBundle.getInt("seqid");
-        if (maybeSyncSeqId > 0) {
-            mSyncSeqId = maybeSyncSeqId;
+            mPendingAlwaysConsumeSystemBars =
+                    (relayoutResult & RELAYOUT_RES_CONSUME_ALWAYS_SYSTEM_BARS) != 0;
         }
-        mInvSizeCompatScale = 1f / mTmpFrames.sizeCompatScale;
 
         final int transformHint = SurfaceControl.rotationToBufferTransform(
                 (mDisplayInstallOrientation + mDisplay.getRotation()) % 4);
 
-        final WindowConfiguration winConfig = getCompatWindowConfiguration();
         WindowLayout.computeSurfaceSize(mWindowAttributes, winConfig.getMaxBounds(), requestedWidth,
-                requestedHeight, mTmpFrames.frame, mPendingDragResizing, mSurfaceSize);
+                requestedHeight, mWinFrameInScreen, mPendingDragResizing, mSurfaceSize);
 
         final boolean transformHintChanged = transformHint != mLastTransformHint;
         final boolean sizeChanged = !mLastSurfaceSize.equals(mSurfaceSize);
@@ -8170,23 +8230,11 @@
             destroySurface();
         }
 
-        mPendingAlwaysConsumeSystemBars =
-                (relayoutResult & RELAYOUT_RES_CONSUME_ALWAYS_SYSTEM_BARS) != 0;
-
         if (restore) {
             params.restore();
         }
 
-        if (mTranslator != null) {
-            mTranslator.translateRectInScreenToAppWindow(mTmpFrames.frame);
-            mTranslator.translateRectInScreenToAppWindow(mTmpFrames.displayFrame);
-            mTranslator.translateRectInScreenToAppWindow(mTmpFrames.attachedFrame);
-            mTranslator.translateInsetsStateInScreenToAppWindow(mTempInsets);
-            mTranslator.translateSourceControlsInScreenToAppWindow(mTempControls);
-        }
         setFrame(mTmpFrames.frame);
-        mInsetsController.onStateChanged(mTempInsets);
-        mInsetsController.onControlsChanged(mTempControls);
         return relayoutResult;
     }
 
diff --git a/core/java/android/view/WindowlessWindowManager.java b/core/java/android/view/WindowlessWindowManager.java
index d55c838..1ec17d0 100644
--- a/core/java/android/view/WindowlessWindowManager.java
+++ b/core/java/android/view/WindowlessWindowManager.java
@@ -286,10 +286,11 @@
 
     @Override
     public int relayout(IWindow window, WindowManager.LayoutParams inAttrs,
-            int requestedWidth, int requestedHeight, int viewFlags, int flags,
-            ClientWindowFrames outFrames, MergedConfiguration mergedConfiguration,
-            SurfaceControl outSurfaceControl, InsetsState outInsetsState,
-            InsetsSourceControl[] outActiveControls, Bundle outSyncSeqIdBundle) {
+            int requestedWidth, int requestedHeight, int viewFlags, int flags, int seq,
+            int lastSyncSeqId, ClientWindowFrames outFrames,
+            MergedConfiguration outMergedConfiguration, SurfaceControl outSurfaceControl,
+            InsetsState outInsetsState, InsetsSourceControl[] outActiveControls,
+            Bundle outSyncSeqIdBundle) {
         final State state;
         synchronized (this) {
             state = mStateForWindow.get(window.asBinder());
@@ -309,15 +310,23 @@
 
         if (viewFlags == View.VISIBLE) {
             t.setOpaque(sc, isOpaque(attrs)).show(sc).apply();
-            outSurfaceControl.copyFrom(sc, "WindowlessWindowManager.relayout");
+            if (outSurfaceControl != null) {
+                outSurfaceControl.copyFrom(sc, "WindowlessWindowManager.relayout");
+            }
         } else {
             t.hide(sc).apply();
-            outSurfaceControl.release();
+            if (outSurfaceControl != null) {
+                outSurfaceControl.release();
+            }
         }
-        outFrames.frame.set(0, 0, attrs.width, attrs.height);
-        outFrames.displayFrame.set(outFrames.frame);
+        if (outFrames != null) {
+            outFrames.frame.set(0, 0, attrs.width, attrs.height);
+            outFrames.displayFrame.set(outFrames.frame);
+        }
 
-        mergedConfiguration.setConfiguration(mConfiguration, mConfiguration);
+        if (outMergedConfiguration != null) {
+            outMergedConfiguration.setConfiguration(mConfiguration, mConfiguration);
+        }
 
         if ((attrChanges & WindowManager.LayoutParams.FLAGS_CHANGED) != 0
                 && state.mInputChannelToken != null) {
@@ -335,7 +344,7 @@
             }
         }
 
-        if (mInsetsState != null) {
+        if (outInsetsState != null && mInsetsState != null) {
             outInsetsState.set(mInsetsState);
         }
 
@@ -343,6 +352,16 @@
     }
 
     @Override
+    public void relayoutAsync(IWindow window, WindowManager.LayoutParams inAttrs,
+            int requestedWidth, int requestedHeight, int viewFlags, int flags, int seq,
+            int lastSyncSeqId) {
+        relayout(window, inAttrs, requestedWidth, requestedHeight, viewFlags, flags, seq,
+                lastSyncSeqId, null /* outFrames */, null /* outMergedConfiguration */,
+                null /* outSurfaceControl */, null /* outInsetsState */,
+                null /* outActiveControls */, null /* outSyncSeqIdBundle */);
+    }
+
+    @Override
     public void prepareToReplaceWindows(android.os.IBinder appToken, boolean childrenOnly) {
     }
 
diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java
index a0a3b4f..cae4868 100644
--- a/core/java/android/view/inputmethod/InputMethodManager.java
+++ b/core/java/android/view/inputmethod/InputMethodManager.java
@@ -2596,7 +2596,7 @@
                 try {
                     mService.hideSoftInput(mClient, windowToken, 0 /* flags */,
                             null /* resultReceiver */,
-                            SoftInputShowHideReason.HIDE_SOFT_INPUT);
+                            SoftInputShowHideReason.HIDE_SOFT_INPUT_BY_INSETS_API);
                 } catch (RemoteException e) {
                     throw e.rethrowFromSystemServer();
                 }
@@ -2989,7 +2989,8 @@
      */
     @Deprecated
     public void hideSoftInputFromInputMethod(IBinder token, int flags) {
-        InputMethodPrivilegedOperationsRegistry.get(token).hideMySoftInput(flags);
+        InputMethodPrivilegedOperationsRegistry.get(token).hideMySoftInput(
+                flags, SoftInputShowHideReason.HIDE_SOFT_INPUT_IMM_DEPRECATION);
     }
 
     /**
diff --git a/core/java/android/view/selectiontoolbar/ShowInfo.java b/core/java/android/view/selectiontoolbar/ShowInfo.java
index 594b6bc..08d6db5 100644
--- a/core/java/android/view/selectiontoolbar/ShowInfo.java
+++ b/core/java/android/view/selectiontoolbar/ShowInfo.java
@@ -75,6 +75,11 @@
     @NonNull
     private final IBinder mHostInputToken;
 
+    /**
+     * If the host application uses light theme.
+     */
+    private final boolean mIsLightTheme;
+
 
 
     // Code below generated by codegen v1.0.23.
@@ -109,6 +114,8 @@
      * @param hostInputToken
      *   The host application's input token, this allows the remote render service to transfer
      *   the touch focus to the host application.
+     * @param isLightTheme
+     *   If the host application uses light theme.
      */
     @DataClass.Generated.Member
     public ShowInfo(
@@ -118,7 +125,8 @@
             @NonNull Rect contentRect,
             int suggestedWidth,
             @NonNull Rect viewPortOnScreen,
-            @NonNull IBinder hostInputToken) {
+            @NonNull IBinder hostInputToken,
+            boolean isLightTheme) {
         this.mWidgetToken = widgetToken;
         this.mLayoutRequired = layoutRequired;
         this.mMenuItems = menuItems;
@@ -134,6 +142,7 @@
         this.mHostInputToken = hostInputToken;
         com.android.internal.util.AnnotationValidations.validate(
                 NonNull.class, null, mHostInputToken);
+        this.mIsLightTheme = isLightTheme;
 
         // onConstructed(); // You can define this method to get a callback
     }
@@ -196,6 +205,14 @@
         return mHostInputToken;
     }
 
+    /**
+     * If the host application uses light theme.
+     */
+    @DataClass.Generated.Member
+    public boolean isIsLightTheme() {
+        return mIsLightTheme;
+    }
+
     @Override
     @DataClass.Generated.Member
     public String toString() {
@@ -209,7 +226,8 @@
                 "contentRect = " + mContentRect + ", " +
                 "suggestedWidth = " + mSuggestedWidth + ", " +
                 "viewPortOnScreen = " + mViewPortOnScreen + ", " +
-                "hostInputToken = " + mHostInputToken +
+                "hostInputToken = " + mHostInputToken + ", " +
+                "isLightTheme = " + mIsLightTheme +
         " }";
     }
 
@@ -232,7 +250,8 @@
                 && java.util.Objects.equals(mContentRect, that.mContentRect)
                 && mSuggestedWidth == that.mSuggestedWidth
                 && java.util.Objects.equals(mViewPortOnScreen, that.mViewPortOnScreen)
-                && java.util.Objects.equals(mHostInputToken, that.mHostInputToken);
+                && java.util.Objects.equals(mHostInputToken, that.mHostInputToken)
+                && mIsLightTheme == that.mIsLightTheme;
     }
 
     @Override
@@ -249,6 +268,7 @@
         _hash = 31 * _hash + mSuggestedWidth;
         _hash = 31 * _hash + java.util.Objects.hashCode(mViewPortOnScreen);
         _hash = 31 * _hash + java.util.Objects.hashCode(mHostInputToken);
+        _hash = 31 * _hash + Boolean.hashCode(mIsLightTheme);
         return _hash;
     }
 
@@ -258,9 +278,10 @@
         // You can override field parcelling by defining methods like:
         // void parcelFieldName(Parcel dest, int flags) { ... }
 
-        byte flg = 0;
+        int flg = 0;
         if (mLayoutRequired) flg |= 0x2;
-        dest.writeByte(flg);
+        if (mIsLightTheme) flg |= 0x80;
+        dest.writeInt(flg);
         dest.writeLong(mWidgetToken);
         dest.writeParcelableList(mMenuItems, flags);
         dest.writeTypedObject(mContentRect, flags);
@@ -280,8 +301,9 @@
         // You can override field unparcelling by defining methods like:
         // static FieldType unparcelFieldName(Parcel in) { ... }
 
-        byte flg = in.readByte();
+        int flg = in.readInt();
         boolean layoutRequired = (flg & 0x2) != 0;
+        boolean isLightTheme = (flg & 0x80) != 0;
         long widgetToken = in.readLong();
         List<ToolbarMenuItem> menuItems = new java.util.ArrayList<>();
         in.readParcelableList(menuItems, ToolbarMenuItem.class.getClassLoader());
@@ -305,6 +327,7 @@
         this.mHostInputToken = hostInputToken;
         com.android.internal.util.AnnotationValidations.validate(
                 NonNull.class, null, mHostInputToken);
+        this.mIsLightTheme = isLightTheme;
 
         // onConstructed(); // You can define this method to get a callback
     }
@@ -324,10 +347,10 @@
     };
 
     @DataClass.Generated(
-            time = 1643186262604L,
+            time = 1645108384245L,
             codegenVersion = "1.0.23",
             sourceFile = "frameworks/base/core/java/android/view/selectiontoolbar/ShowInfo.java",
-            inputSignatures = "private final  long mWidgetToken\nprivate final  boolean mLayoutRequired\nprivate final @android.annotation.NonNull java.util.List<android.view.selectiontoolbar.ToolbarMenuItem> mMenuItems\nprivate final @android.annotation.NonNull android.graphics.Rect mContentRect\nprivate final  int mSuggestedWidth\nprivate final @android.annotation.NonNull android.graphics.Rect mViewPortOnScreen\nprivate final @android.annotation.NonNull android.os.IBinder mHostInputToken\nclass ShowInfo extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genToString=true, genEqualsHashCode=true)")
+            inputSignatures = "private final  long mWidgetToken\nprivate final  boolean mLayoutRequired\nprivate final @android.annotation.NonNull java.util.List<android.view.selectiontoolbar.ToolbarMenuItem> mMenuItems\nprivate final @android.annotation.NonNull android.graphics.Rect mContentRect\nprivate final  int mSuggestedWidth\nprivate final @android.annotation.NonNull android.graphics.Rect mViewPortOnScreen\nprivate final @android.annotation.NonNull android.os.IBinder mHostInputToken\nprivate final  boolean mIsLightTheme\nclass ShowInfo extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genToString=true, genEqualsHashCode=true)")
     @Deprecated
     private void __metadata() {}
 
diff --git a/core/java/android/view/translation/TranslationManager.java b/core/java/android/view/translation/TranslationManager.java
index db1c606..fbaf711 100644
--- a/core/java/android/view/translation/TranslationManager.java
+++ b/core/java/android/view/translation/TranslationManager.java
@@ -40,11 +40,11 @@
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.util.SyncResultReceiver;
 
+import java.security.SecureRandom;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.Map;
 import java.util.Objects;
-import java.util.Random;
 import java.util.Set;
 import java.util.concurrent.Executor;
 import java.util.concurrent.TimeoutException;
@@ -92,7 +92,8 @@
     private final Map<Consumer<TranslationCapability>, IRemoteCallback> mCapabilityCallbacks =
             new ArrayMap<>();
 
-    private static final Random ID_GENERATOR = new Random();
+    // TODO(b/158778794): make the session ids truly globally unique across processes
+    private static final SecureRandom ID_GENERATOR = new SecureRandom();
     private final Object mLock = new Object();
 
     @NonNull
diff --git a/core/java/com/android/internal/inputmethod/IInputMethodPrivilegedOperations.aidl b/core/java/com/android/internal/inputmethod/IInputMethodPrivilegedOperations.aidl
index 2ee47b6..4babb70 100644
--- a/core/java/com/android/internal/inputmethod/IInputMethodPrivilegedOperations.aidl
+++ b/core/java/com/android/internal/inputmethod/IInputMethodPrivilegedOperations.aidl
@@ -34,7 +34,7 @@
     void setInputMethod(String id, in AndroidFuture future /* T=Void */);
     void setInputMethodAndSubtype(String id, in InputMethodSubtype subtype,
             in AndroidFuture future /* T=Void */);
-    void hideMySoftInput(int flags, in AndroidFuture future /* T=Void */);
+    void hideMySoftInput(int flags, int reason, in AndroidFuture future /* T=Void */);
     void showMySoftInput(int flags, in AndroidFuture future /* T=Void */);
     void updateStatusIconAsync(String packageName, int iconId);
     void switchToPreviousInputMethod(in AndroidFuture future /* T=Boolean */);
diff --git a/core/java/com/android/internal/inputmethod/InputMethodDebug.java b/core/java/com/android/internal/inputmethod/InputMethodDebug.java
index d669768..97ad084 100644
--- a/core/java/com/android/internal/inputmethod/InputMethodDebug.java
+++ b/core/java/com/android/internal/inputmethod/InputMethodDebug.java
@@ -194,12 +194,12 @@
                 return "SHOW_SOFT_INPUT";
             case SoftInputShowHideReason.ATTACH_NEW_INPUT:
                 return "ATTACH_NEW_INPUT";
-            case SoftInputShowHideReason.SHOW_MY_SOFT_INPUT:
-                return "SHOW_MY_SOFT_INPUT";
+            case SoftInputShowHideReason.SHOW_SOFT_INPUT_FROM_IME:
+                return "SHOW_SOFT_INPUT_FROM_IME";
             case SoftInputShowHideReason.HIDE_SOFT_INPUT:
                 return "HIDE_SOFT_INPUT";
-            case SoftInputShowHideReason.HIDE_MY_SOFT_INPUT:
-                return "HIDE_MY_SOFT_INPUT";
+            case SoftInputShowHideReason.HIDE_SOFT_INPUT_FROM_IME:
+                return "HIDE_SOFT_INPUT_FROM_IME";
             case SoftInputShowHideReason.SHOW_AUTO_EDITOR_FORWARD_NAV:
                 return "SHOW_AUTO_EDITOR_FORWARD_NAV";
             case SoftInputShowHideReason.SHOW_STATE_VISIBLE_FORWARD_NAV:
@@ -242,6 +242,16 @@
                 return "SHOW_SOFT_INPUT_BY_INSETS_API";
             case SoftInputShowHideReason.HIDE_DISPLAY_IME_POLICY_HIDE:
                 return "HIDE_DISPLAY_IME_POLICY_HIDE";
+            case SoftInputShowHideReason.HIDE_SOFT_INPUT_BY_INSETS_API:
+                return "HIDE_SOFT_INPUT_BY_INSETS_API";
+            case SoftInputShowHideReason.HIDE_SOFT_INPUT_BY_BACK_KEY:
+                return "HIDE_SOFT_INPUT_BY_BACK_KEY";
+            case SoftInputShowHideReason.HIDE_SOFT_INPUT_IME_TOGGLE_SOFT_INPUT:
+                return "HIDE_SOFT_INPUT_IME_TOGGLE_SOFT_INPUT";
+            case SoftInputShowHideReason.HIDE_SOFT_INPUT_EXTRACT_INPUT_CHANGED:
+                return "HIDE_SOFT_INPUT_EXTRACT_INPUT_CHANGED";
+            case SoftInputShowHideReason.HIDE_SOFT_INPUT_IMM_DEPRECATION:
+                return "HIDE_SOFT_INPUT_IMM_DEPRECATION";
             default:
                 return "Unknown=" + reason;
         }
diff --git a/core/java/com/android/internal/inputmethod/InputMethodPrivilegedOperations.java b/core/java/com/android/internal/inputmethod/InputMethodPrivilegedOperations.java
index 15d7acf..67c2103 100644
--- a/core/java/com/android/internal/inputmethod/InputMethodPrivilegedOperations.java
+++ b/core/java/com/android/internal/inputmethod/InputMethodPrivilegedOperations.java
@@ -253,18 +253,19 @@
      * Calls {@link IInputMethodPrivilegedOperations#hideMySoftInput(int, IVoidResultCallback)}
      *
      * @param flags additional operating flags
+     * @param reason the reason to hide soft input
      * @see android.view.inputmethod.InputMethodManager#HIDE_IMPLICIT_ONLY
      * @see android.view.inputmethod.InputMethodManager#HIDE_NOT_ALWAYS
      */
     @AnyThread
-    public void hideMySoftInput(int flags) {
+    public void hideMySoftInput(int flags, @SoftInputShowHideReason int reason) {
         final IInputMethodPrivilegedOperations ops = mOps.getAndWarnIfNull();
         if (ops == null) {
             return;
         }
         try {
             final AndroidFuture<Void> future = new AndroidFuture<>();
-            ops.hideMySoftInput(flags, future);
+            ops.hideMySoftInput(flags, reason, future);
             CompletableFutureUtil.getResult(future);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
diff --git a/core/java/com/android/internal/inputmethod/SoftInputShowHideReason.java b/core/java/com/android/internal/inputmethod/SoftInputShowHideReason.java
index 9e57762..97ad5cb 100644
--- a/core/java/com/android/internal/inputmethod/SoftInputShowHideReason.java
+++ b/core/java/com/android/internal/inputmethod/SoftInputShowHideReason.java
@@ -19,8 +19,11 @@
 import static java.lang.annotation.RetentionPolicy.SOURCE;
 
 import android.annotation.IntDef;
+import android.os.IBinder;
 import android.view.WindowManager;
 import android.view.WindowManager.LayoutParams;
+import android.view.inputmethod.EditorInfo;
+import android.view.inputmethod.InputMethodManager;
 
 import java.lang.annotation.Retention;
 
@@ -31,9 +34,9 @@
 @IntDef(value = {
         SoftInputShowHideReason.SHOW_SOFT_INPUT,
         SoftInputShowHideReason.ATTACH_NEW_INPUT,
-        SoftInputShowHideReason.SHOW_MY_SOFT_INPUT,
+        SoftInputShowHideReason.SHOW_SOFT_INPUT_FROM_IME,
         SoftInputShowHideReason.HIDE_SOFT_INPUT,
-        SoftInputShowHideReason.HIDE_MY_SOFT_INPUT,
+        SoftInputShowHideReason.HIDE_SOFT_INPUT_FROM_IME,
         SoftInputShowHideReason.SHOW_AUTO_EDITOR_FORWARD_NAV,
         SoftInputShowHideReason.SHOW_STATE_VISIBLE_FORWARD_NAV,
         SoftInputShowHideReason.SHOW_STATE_ALWAYS_VISIBLE,
@@ -55,7 +58,12 @@
         SoftInputShowHideReason.SHOW_TOGGLE_SOFT_INPUT,
         SoftInputShowHideReason.HIDE_TOGGLE_SOFT_INPUT,
         SoftInputShowHideReason.SHOW_SOFT_INPUT_BY_INSETS_API,
-        SoftInputShowHideReason.HIDE_DISPLAY_IME_POLICY_HIDE})
+        SoftInputShowHideReason.HIDE_DISPLAY_IME_POLICY_HIDE,
+        SoftInputShowHideReason.HIDE_SOFT_INPUT_BY_INSETS_API,
+        SoftInputShowHideReason.HIDE_SOFT_INPUT_BY_BACK_KEY,
+        SoftInputShowHideReason.HIDE_SOFT_INPUT_IME_TOGGLE_SOFT_INPUT,
+        SoftInputShowHideReason.HIDE_SOFT_INPUT_EXTRACT_INPUT_CHANGED,
+        SoftInputShowHideReason.HIDE_SOFT_INPUT_IMM_DEPRECATION})
 public @interface SoftInputShowHideReason {
     /** Show soft input by {@link android.view.inputmethod.InputMethodManager#showSoftInput}. */
     int SHOW_SOFT_INPUT = 0;
@@ -63,8 +71,12 @@
     /** Show soft input when {@code InputMethodManagerService#attachNewInputLocked} called. */
     int ATTACH_NEW_INPUT = 1;
 
-    /** Show soft input by {@code InputMethodManagerService#showMySoftInput}. */
-    int SHOW_MY_SOFT_INPUT = 2;
+    /** Show soft input by {@code InputMethodManagerService#showMySoftInput}. This is triggered when
+     *  the IME process try to show the keyboard.
+     *
+     * @see android.inputmethodservice.InputMethodService#requestShowSelf(int)
+     */
+    int SHOW_SOFT_INPUT_FROM_IME = 2;
 
     /**
      * Hide soft input by
@@ -72,8 +84,11 @@
      */
     int HIDE_SOFT_INPUT = 3;
 
-    /** Hide soft input by {@code InputMethodManagerService#hideMySoftInput}. */
-    int HIDE_MY_SOFT_INPUT = 4;
+    /**
+     * Hide soft input by
+     * {@link android.inputmethodservice.InputMethodService#requestHideSelf(int)}.
+     */
+    int HIDE_SOFT_INPUT_FROM_IME = 4;
 
     /**
      * Show soft input when navigated forward to the window (with
@@ -203,4 +218,32 @@
      * See also {@code InputMethodManagerService#mImeHiddenByDisplayPolicy}.
      */
     int HIDE_DISPLAY_IME_POLICY_HIDE = 26;
+
+    /**
+     * Hide soft input by {@link android.view.InsetsController#hide(int)}.
+     */
+    int HIDE_SOFT_INPUT_BY_INSETS_API = 27;
+
+    /**
+     * Hide soft input by {@link android.inputmethodservice.InputMethodService#handleBack(boolean)}.
+     */
+    int HIDE_SOFT_INPUT_BY_BACK_KEY = 28;
+
+    /**
+     * Hide soft input by
+     * {@link android.inputmethodservice.InputMethodService#onToggleSoftInput(int, int)}.
+     */
+    int HIDE_SOFT_INPUT_IME_TOGGLE_SOFT_INPUT = 29;
+
+    /**
+     * Hide soft input by
+     * {@link android.inputmethodservice.InputMethodService#onExtractingInputChanged(EditorInfo)})}.
+     */
+    int HIDE_SOFT_INPUT_EXTRACT_INPUT_CHANGED = 30;
+
+    /**
+     * Hide soft input by the deprecated
+     * {@link InputMethodManager#hideSoftInputFromInputMethod(IBinder, int)}.
+     */
+    int HIDE_SOFT_INPUT_IMM_DEPRECATION = 31;
 }
diff --git a/core/java/com/android/internal/policy/DecorContext.java b/core/java/com/android/internal/policy/DecorContext.java
index 5e34c15..134a917 100644
--- a/core/java/com/android/internal/policy/DecorContext.java
+++ b/core/java/com/android/internal/policy/DecorContext.java
@@ -137,4 +137,13 @@
         }
         return false;
     }
+
+    @Override
+    public boolean isConfigurationContext() {
+        Context context = mContext.get();
+        if (context != null) {
+            return context.isConfigurationContext();
+        }
+        return false;
+    }
 }
diff --git a/core/java/com/android/internal/widget/LocalImageResolver.java b/core/java/com/android/internal/widget/LocalImageResolver.java
index b866723..b11ea29 100644
--- a/core/java/com/android/internal/widget/LocalImageResolver.java
+++ b/core/java/com/android/internal/widget/LocalImageResolver.java
@@ -25,6 +25,7 @@
 import android.graphics.drawable.Drawable;
 import android.graphics.drawable.Icon;
 import android.net.Uri;
+import android.text.TextUtils;
 import android.util.Log;
 import android.util.Size;
 
@@ -108,6 +109,12 @@
                 }
                 break;
             case Icon.TYPE_RESOURCE:
+                if (!(TextUtils.isEmpty(icon.getResPackage())
+                        || context.getPackageName().equals(icon.getResPackage()))) {
+                    // We can't properly resolve icons from other packages here, so fall back.
+                    return icon.loadDrawable(context);
+                }
+
                 Drawable result = resolveImage(icon.getResId(), context, maxWidth, maxHeight);
                 if (result != null) {
                     return tintDrawable(icon, result);
diff --git a/core/java/com/android/internal/widget/floatingtoolbar/RemoteFloatingToolbarPopup.java b/core/java/com/android/internal/widget/floatingtoolbar/RemoteFloatingToolbarPopup.java
index 8c2eb10..8787c39 100644
--- a/core/java/com/android/internal/widget/floatingtoolbar/RemoteFloatingToolbarPopup.java
+++ b/core/java/com/android/internal/widget/floatingtoolbar/RemoteFloatingToolbarPopup.java
@@ -23,6 +23,7 @@
 import android.annotation.Nullable;
 import android.annotation.UiThread;
 import android.content.Context;
+import android.content.res.TypedArray;
 import android.graphics.Bitmap;
 import android.graphics.Canvas;
 import android.graphics.Color;
@@ -107,6 +108,7 @@
     private int mSuggestedWidth;
     private final Rect mScreenViewPort = new Rect();
     private boolean mWidthChanged = true;
+    private final boolean mIsLightTheme;
 
     private final int[] mCoordsOnScreen = new int[2];
     private final int[] mCoordsOnWindow = new int[2];
@@ -116,9 +118,17 @@
         mPopupWindow = createPopupWindow(context);
         mSelectionToolbarManager = context.getSystemService(SelectionToolbarManager.class);
         mSelectionToolbarCallback = new SelectionToolbarCallbackImpl(this);
+        mIsLightTheme = isLightTheme(context);
         mFloatingToolbarToken = NO_TOOLBAR_ID;
     }
 
+    private boolean isLightTheme(Context context) {
+        TypedArray a = context.obtainStyledAttributes(new int[]{R.attr.isLightTheme});
+        boolean isLightTheme = a.getBoolean(0, true);
+        a.recycle();
+        return isLightTheme;
+    }
+
     @UiThread
     @Override
     public void show(List<MenuItem> menuItems,
@@ -155,7 +165,7 @@
                 contentRect,
                 suggestWidth,
                 mScreenViewPort,
-                mParent.getViewRootImpl().getInputToken());
+                mParent.getViewRootImpl().getInputToken(), mIsLightTheme);
         if (DEBUG) {
             Log.v(FloatingToolbar.FLOATING_TOOLBAR_TAG,
                     "RemoteFloatingToolbarPopup.show() for " + showInfo);
diff --git a/core/proto/android/server/peopleservice.proto b/core/proto/android/server/peopleservice.proto
index c465233..a96ec41 100644
--- a/core/proto/android/server/peopleservice.proto
+++ b/core/proto/android/server/peopleservice.proto
@@ -62,7 +62,10 @@
   // The timestamp of the last event in millis.
   optional int64 last_event_timestamp = 9;
 
-  // Next tag: 10
+  // The timestamp this conversation was created in millis.
+  optional int64 creation_timestamp = 10;
+
+  // Next tag: 11
 }
 
 // On disk data of events.
diff --git a/core/proto/android/server/vibrator/vibratormanagerservice.proto b/core/proto/android/server/vibrator/vibratormanagerservice.proto
index 2a625b027..25a1f68 100644
--- a/core/proto/android/server/vibrator/vibratormanagerservice.proto
+++ b/core/proto/android/server/vibrator/vibratormanagerservice.proto
@@ -86,7 +86,7 @@
     optional int32 flags = 3;
 }
 
-// Next id: 8
+// Next Tag: 9
 message VibrationProto {
     option (.android.msg_privacy).dest = DEST_AUTOMATIC;
     optional int64 start_time = 1;
@@ -94,11 +94,43 @@
     optional CombinedVibrationEffectProto effect = 3;
     optional CombinedVibrationEffectProto original_effect = 4;
     optional VibrationAttributesProto attributes = 5;
-    optional int32 status = 6;
     optional int64 duration_ms = 7;
+    optional Status status = 8;
+    reserved 6; // prev int32 status
+
+    // Also used by VibrationReported from frameworks/proto_logging/stats/atoms.proto.
+    // Next Tag: 26
+    enum Status {
+        UNKNOWN = 0;
+        RUNNING = 1;
+        FINISHED = 2;
+        FINISHED_UNEXPECTED = 3;  // Didn't terminate in the usual way.
+        FORWARDED_TO_INPUT_DEVICES = 4;
+        CANCELLED_BINDER_DIED = 5;
+        CANCELLED_BY_SCREEN_OFF = 6;
+        CANCELLED_BY_SETTINGS_UPDATE = 7;
+        CANCELLED_BY_USER = 8;
+        CANCELLED_BY_UNKNOWN_REASON = 9;
+        CANCELLED_SUPERSEDED = 10;
+        IGNORED_ERROR_APP_OPS = 11;
+        IGNORED_ERROR_CANCELLING = 12;
+        IGNORED_ERROR_SCHEDULING = 13;
+        IGNORED_ERROR_TOKEN= 14;
+        IGNORED_APP_OPS = 15;
+        IGNORED_BACKGROUND = 16;
+        IGNORED_UNKNOWN_VIBRATION = 17;
+        IGNORED_UNSUPPORTED = 18;
+        IGNORED_FOR_EXTERNAL = 19;
+        IGNORED_FOR_HIGHER_IMPORTANCE = 20;
+        IGNORED_FOR_ONGOING = 21;
+        IGNORED_FOR_POWER = 22;
+        IGNORED_FOR_RINGER_MODE = 23;
+        IGNORED_FOR_SETTINGS = 24;
+        IGNORED_SUPERSEDED = 25;
+    }
 }
 
-// Next id: 25
+// Next Tag: 25
 message VibratorManagerServiceDumpProto {
     option (.android.msg_privacy).dest = DEST_AUTOMATIC;
     repeated int32 vibrator_ids = 1;
diff --git a/core/res/res/layout/side_fps_toast.xml b/core/res/res/layout/side_fps_toast.xml
index 58b8cc9..96860b0 100644
--- a/core/res/res/layout/side_fps_toast.xml
+++ b/core/res/res/layout/side_fps_toast.xml
@@ -20,13 +20,14 @@
               android:layout_height="wrap_content"
               android:minWidth="350dp"
               android:layout_gravity="center"
-              android:theme="?attr/alertDialogTheme">
+              android:background="@color/side_fps_toast_background">
     <TextView
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
         android:text="@string/fp_power_button_enrollment_title"
         android:singleLine="true"
         android:ellipsize="end"
+        android:textColor="@color/side_fps_text_color"
         android:paddingLeft="20dp"/>
     <Space
         android:layout_width="wrap_content"
@@ -39,5 +40,6 @@
         android:text="@string/fp_power_button_enrollment_button_text"
         android:paddingRight="20dp"
         style="?android:attr/buttonBarNegativeButtonStyle"
+        android:textColor="@color/side_fps_button_color"
         android:maxLines="1"/>
 </LinearLayout>
\ No newline at end of file
diff --git a/core/res/res/values-night/colors.xml b/core/res/res/values-night/colors.xml
index 33c9b95..ffaccd3 100644
--- a/core/res/res/values-night/colors.xml
+++ b/core/res/res/values-night/colors.xml
@@ -37,4 +37,10 @@
     <color name="user_icon_6">#ff4ecde6</color><!-- cyan -->
     <color name="user_icon_7">#fffbbc04</color><!-- yellow -->
     <color name="user_icon_8">#fffa903e</color><!-- orange -->
+
+    <!-- Color for side fps toast dark theme-->
+    <color name="side_fps_toast_background">#2E3132</color>
+    <color name="side_fps_text_color">#EFF1F2</color>
+    <color name="side_fps_button_color">#33B9DB</color>
+
 </resources>
diff --git a/core/res/res/values/colors.xml b/core/res/res/values/colors.xml
index 9fd894d..d5875f5 100644
--- a/core/res/res/values/colors.xml
+++ b/core/res/res/values/colors.xml
@@ -447,4 +447,10 @@
     <!-- Color of camera light when camera is in use -->
     <color name="camera_privacy_light_day">#FFFFFF</color>
     <color name="camera_privacy_light_night">#FFFFFF</color>
+
+    <!-- Color for side fps toast light theme -->
+    <color name="side_fps_toast_background">#F7F9FA</color>
+    <color name="side_fps_text_color">#191C1D</color>
+    <color name="side_fps_button_color">#00677E</color>
+
 </resources>
diff --git a/core/tests/coretests/src/com/android/internal/widget/LocalImageResolverTest.java b/core/tests/coretests/src/com/android/internal/widget/LocalImageResolverTest.java
index c63d18b..0cee526 100644
--- a/core/tests/coretests/src/com/android/internal/widget/LocalImageResolverTest.java
+++ b/core/tests/coretests/src/com/android/internal/widget/LocalImageResolverTest.java
@@ -270,4 +270,13 @@
         assertThat(bd.getBitmap().getHeight()).isEqualTo(originalHeight);
 
     }
+
+    @Test
+    public void resolveImage_iconWithOtherPackageResource_usesPackageContextDefinition()
+            throws IOException {
+        Icon icon = Icon.createWithResource("this_is_invalid", R.drawable.test32x24);
+        Drawable d = LocalImageResolver.resolveImage(icon, mContext);
+        // This drawable must not be loaded - if it was, the code ignored the package specification.
+        assertThat(d).isNull();
+    }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragLayout.java
index ff3c083..497a6f6 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragLayout.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragLayout.java
@@ -105,6 +105,10 @@
                 MATCH_PARENT));
         ((LayoutParams) mDropZoneView1.getLayoutParams()).weight = 1;
         ((LayoutParams) mDropZoneView2.getLayoutParams()).weight = 1;
+        int orientation = getResources().getConfiguration().orientation;
+        setOrientation(orientation == Configuration.ORIENTATION_LANDSCAPE
+                ? LinearLayout.HORIZONTAL
+                : LinearLayout.VERTICAL);
         updateContainerMargins(getResources().getConfiguration().orientation);
     }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipContentOverlay.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipContentOverlay.java
index 0e32663..7096a64 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipContentOverlay.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipContentOverlay.java
@@ -111,9 +111,6 @@
         private final TaskSnapshot mSnapshot;
         private final Rect mSourceRectHint;
 
-        private float mTaskSnapshotScaleX;
-        private float mTaskSnapshotScaleY;
-
         public PipSnapshotOverlay(TaskSnapshot snapshot, Rect sourceRectHint) {
             mSnapshot = snapshot;
             mSourceRectHint = new Rect(sourceRectHint);
@@ -125,16 +122,16 @@
 
         @Override
         public void attach(SurfaceControl.Transaction tx, SurfaceControl parentLeash) {
-            mTaskSnapshotScaleX = (float) mSnapshot.getTaskSize().x
+            final float taskSnapshotScaleX = (float) mSnapshot.getTaskSize().x
                     / mSnapshot.getHardwareBuffer().getWidth();
-            mTaskSnapshotScaleY = (float) mSnapshot.getTaskSize().y
+            final float taskSnapshotScaleY = (float) mSnapshot.getTaskSize().y
                     / mSnapshot.getHardwareBuffer().getHeight();
             tx.show(mLeash);
             tx.setLayer(mLeash, Integer.MAX_VALUE);
             tx.setBuffer(mLeash, mSnapshot.getHardwareBuffer());
             // Relocate the content to parentLeash's coordinates.
             tx.setPosition(mLeash, -mSourceRectHint.left, -mSourceRectHint.top);
-            tx.setScale(mLeash, mTaskSnapshotScaleX, mTaskSnapshotScaleY);
+            tx.setScale(mLeash, taskSnapshotScaleX, taskSnapshotScaleY);
             tx.reparent(mLeash, parentLeash);
             tx.apply();
         }
@@ -146,20 +143,6 @@
 
         @Override
         public void onAnimationEnd(SurfaceControl.Transaction atomicTx, Rect destinationBounds) {
-            // Work around to make sure the snapshot overlay is aligned with PiP window before
-            // the atomicTx is committed along with the final WindowContainerTransaction.
-            final SurfaceControl.Transaction nonAtomicTx = new SurfaceControl.Transaction();
-            final float scaleX = (float) destinationBounds.width()
-                    / mSourceRectHint.width();
-            final float scaleY = (float) destinationBounds.height()
-                    / mSourceRectHint.height();
-            final float scale = Math.max(
-                    scaleX * mTaskSnapshotScaleX, scaleY * mTaskSnapshotScaleY);
-            nonAtomicTx.setScale(mLeash, scale, scale);
-            nonAtomicTx.setPosition(mLeash,
-                    -scale * mSourceRectHint.left / mTaskSnapshotScaleX,
-                    -scale * mSourceRectHint.top / mTaskSnapshotScaleY);
-            nonAtomicTx.apply();
             atomicTx.remove(mLeash);
         }
     }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipDismissTargetHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipDismissTargetHandler.java
index a0e2201..7619646 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipDismissTargetHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipDismissTargetHandler.java
@@ -288,8 +288,10 @@
 
         if (mTargetViewContainer.getVisibility() != View.VISIBLE) {
             mTargetViewContainer.getViewTreeObserver().addOnPreDrawListener(this);
-            mTargetViewContainer.show();
         }
+        // always invoke show, since the target might still be VISIBLE while playing hide animation,
+        // so we want to ensure it will show back again
+        mTargetViewContainer.show();
     }
 
     /** Animates the magnetic dismiss target out and then sets it to GONE. */
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenShellCommandHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenShellCommandHandler.java
index 681d964..7fd03a9 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenShellCommandHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenShellCommandHandler.java
@@ -56,7 +56,7 @@
             return false;
         }
         final int taskId = new Integer(args[1]);
-        final int sideStagePosition = args.length > 3
+        final int sideStagePosition = args.length > 2
                 ? new Integer(args[2]) : SPLIT_POSITION_BOTTOM_OR_RIGHT;
         mController.moveToSideStage(taskId, sideStagePosition);
         return true;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
index 4bc8e91..7e83d2f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
@@ -1374,21 +1374,13 @@
                 }
             }
         } else if (isSideStage && hasChildren && !mMainStage.isActive()) {
-            if (mFocusingTaskInfo != null && !isValidToEnterSplitScreen(mFocusingTaskInfo)) {
-                final WindowContainerTransaction wct = new WindowContainerTransaction();
-                mSideStage.removeAllTasks(wct, true);
-                wct.reorder(mRootTaskInfo.token, false /* onTop */);
-                mTaskOrganizer.applyTransaction(wct);
-                Slog.i(TAG, "cancel entering split screen, reason = "
-                        + exitReasonToString(EXIT_REASON_APP_DOES_NOT_SUPPORT_MULTIWINDOW));
-            } else {
-                final WindowContainerTransaction wct = new WindowContainerTransaction();
-                mSplitLayout.init();
-                prepareEnterSplitScreen(wct);
-                mSyncQueue.queue(wct);
-                mSyncQueue.runInSync(t ->
-                        updateSurfaceBounds(mSplitLayout, t, false /* applyResizingOffset */));
-            }
+            // TODO (b/238697912) : Add the validation to prevent entering non-recovered status
+            final WindowContainerTransaction wct = new WindowContainerTransaction();
+            mSplitLayout.init();
+            prepareEnterSplitScreen(wct);
+            mSyncQueue.queue(wct);
+            mSyncQueue.runInSync(t ->
+                    updateSurfaceBounds(mSplitLayout, t, false /* applyResizingOffset */));
         }
         if (mMainStageListener.mHasChildren && mSideStageListener.mHasChildren) {
             mShouldUpdateRecents = true;
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 b70bde3..7b498e4 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
@@ -246,7 +246,7 @@
         window.setOuter(snapshotSurface);
         try {
             Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "TaskSnapshot#relayout");
-            session.relayout(window, layoutParams, -1, -1, View.VISIBLE, 0,
+            session.relayout(window, layoutParams, -1, -1, View.VISIBLE, 0, 0, 0,
                     tmpFrames, tmpMergedConfiguration, surfaceControl, tmpInsetsState,
                     tmpControls, new Bundle());
             Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
diff --git a/packages/SettingsLib/ButtonPreference/src/com/android/settingslib/widget/ButtonPreference.java b/packages/SettingsLib/ButtonPreference/src/com/android/settingslib/widget/ButtonPreference.java
index 56d2967..f46de06 100644
--- a/packages/SettingsLib/ButtonPreference/src/com/android/settingslib/widget/ButtonPreference.java
+++ b/packages/SettingsLib/ButtonPreference/src/com/android/settingslib/widget/ButtonPreference.java
@@ -133,6 +133,11 @@
     }
 
     @Override
+    public CharSequence getTitle() {
+        return mTitle;
+    }
+
+    @Override
     public void setIcon(Drawable icon) {
         mIcon = icon;
         if (mButton == null || icon == null) {
diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml
index 85f91fe..11cb9c1 100644
--- a/packages/SettingsLib/res/values/strings.xml
+++ b/packages/SettingsLib/res/values/strings.xml
@@ -264,12 +264,12 @@
 
     <!-- Bluetooth settings.  The user-visible string that is used whenever referring to the Hearing Aid profile. -->
     <string name="bluetooth_profile_hearing_aid">Hearing Aids</string>
-    <!-- Bluetooth settings.  The user-visible string that is used whenever referring to the LE_AUDIO profile. -->
-    <string name="bluetooth_profile_le_audio">LE_AUDIO</string>
+    <!-- Bluetooth settings.  The user-visible string that is used whenever referring to the LE audio profile. -->
+    <string name="bluetooth_profile_le_audio">LE audio</string>
     <!-- Bluetooth settings.  Connection options screen.  The summary for the Hearing Aid checkbox preference when Hearing Aid is connected. -->
     <string name="bluetooth_hearing_aid_profile_summary_connected">Connected to Hearing Aids</string>
-    <!-- Bluetooth settings.  Connection options screen.  The summary for the LE_AUDIO checkbox preference when LE_AUDIO is connected. -->
-    <string name="bluetooth_le_audio_profile_summary_connected">Connected to LE_AUDIO</string>
+    <!-- Bluetooth settings.  Connection options screen.  The summary for the LE audio checkbox preference when LE audio is connected. -->
+    <string name="bluetooth_le_audio_profile_summary_connected">Connected to LE audio</string>
 
     <!-- Bluetooth settings.  Connection options screen.  The summary for the A2DP checkbox preference when A2DP is connected. -->
     <string name="bluetooth_a2dp_profile_summary_connected">Connected to media audio</string>
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java
index 7fbd100..cd3242a 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java
@@ -297,6 +297,9 @@
                     mCachedDevices.remove(i);
                 }
             }
+
+            // To clear the SetMemberPair flag when the Bluetooth is turning off.
+            mOngoingSetMemberPair = null;
         }
     }
 
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/ButtonPreferenceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/ButtonPreferenceTest.java
index 625b214..d78f8e7 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/ButtonPreferenceTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/ButtonPreferenceTest.java
@@ -63,6 +63,7 @@
 
         final Button button = mPreference.getButton();
         assertThat(button.getText().toString()).isEqualTo(testTitle);
+        assertThat(mPreference.getTitle().toString()).isEqualTo(testTitle);
     }
 
     @Test
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/TextInterpolator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/TextInterpolator.kt
index ff64c78..d427a57 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/TextInterpolator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/TextInterpolator.kt
@@ -485,7 +485,13 @@
         val out = mutableListOf<List<PositionedGlyphs>>()
         for (lineNo in 0 until layout.lineCount) { // Shape all lines.
             val lineStart = layout.getLineStart(lineNo)
-            val count = layout.getLineEnd(lineNo) - lineStart
+            var count = layout.getLineEnd(lineNo) - lineStart
+            // Do not render the last character in the line if it's a newline and unprintable
+            val last = lineStart + count - 1
+            if (last > lineStart && last < layout.text.length && layout.text[last] == '\n') {
+                count--
+            }
+
             val runs = mutableListOf<PositionedGlyphs>()
             TextShaper.shapeText(layout.text, lineStart, count, layout.textDirectionHeuristic,
                     paint) { _, _, glyphs, _ ->
diff --git a/packages/SystemUI/res/drawable/fingerprint_dialog_error_to_fp.xml b/packages/SystemUI/res/drawable/fingerprint_dialog_error_to_fp.xml
deleted file mode 100644
index 5084ca4..0000000
--- a/packages/SystemUI/res/drawable/fingerprint_dialog_error_to_fp.xml
+++ /dev/null
@@ -1 +0,0 @@
-<animated-vector xmlns:android="http://schemas.android.com/apk/res/android" xmlns:aapt="http://schemas.android.com/aapt"><aapt:attr name="android:drawable"><vector android:height="80dp" android:width="80dp" android:viewportHeight="80" android:viewportWidth="80"><group android:name="_R_G"><group android:name="_R_G_L_3_G" android:translateX="-0.25" android:translateY="-0.25"><path android:name="_R_G_L_3_G_D_0_P_0" android:fillColor="#474747" android:fillAlpha="0" android:fillType="nonZero" android:pathData=" M40.21 0.25 C18.13,0.25 0.25,18.17 0.25,40.25 C0.25,62.33 18.13,80.25 40.21,80.25 C62.33,80.25 80.25,62.33 80.25,40.25 C80.25,18.17 62.33,0.25 40.21,0.25c "/></group><group android:name="_R_G_L_2_G" android:translateX="-0.25" android:translateY="-0.25" android:pivotX="40.25" android:pivotY="40.25" android:scaleX="0.975" android:scaleY="0.975"><path android:name="_R_G_L_2_G_D_0_P_0" android:strokeColor="#f2b8b5" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="2.5" android:strokeAlpha="1" android:pathData=" M40.21 0.25 C18.13,0.25 0.25,18.17 0.25,40.25 C0.25,62.33 18.13,80.25 40.21,80.25 C62.33,80.25 80.25,62.33 80.25,40.25 C80.25,18.17 62.33,0.25 40.21,0.25c "/></group><group android:name="_R_G_L_1_G" android:translateX="9.950000000000003" android:translateY="10" android:pivotX="30" android:pivotY="30" android:scaleX="1.2" android:scaleY="1.2"><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="#f2b8b5" 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="#f2b8b5" 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></group><group android:name="_R_G_L_0_G" android:translateX="20.659" android:translateY="15.75"><path android:name="_R_G_L_0_G_D_0_P_0" android:strokeColor="#d3e3fd" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="2" android:strokeAlpha="1" android:trimPathStart="0" android:trimPathEnd="0" android:trimPathOffset="0" android:pathData=" M27.52 38.98 C26.49,39.95 25.29,40.73 23.98,41.29 C23.17,41.65 22.31,41.91 21.41,42.07 C20.74,42.19 20.05,42.25 19.34,42.25 C18.44,42.25 17.56,42.15 16.72,41.96 C15.93,41.77 15.16,41.51 14.43,41.18 C13.23,40.63 12.13,39.88 11.16,38.98 "/><path android:name="_R_G_L_0_G_D_1_P_0" android:strokeColor="#d3e3fd" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="2" android:strokeAlpha="1" android:trimPathStart="0" android:trimPathEnd="0" android:trimPathOffset="0" android:pathData=" M8.64 34.07 C7.89,31.97 7.89,29.85 7.89,29.85 C7.89,24.05 12.81,19.34 19.34,19.34 C25.87,19.34 30.8,24.05 30.8,29.85 C30.8,29.85 30.8,30.16 30.8,30.16 C30.8,32.32 29.04,34.07 26.89,34.07 C25.28,34.07 23.86,33.1 23.27,31.61 C23.27,31.61 21.96,28.34 21.96,28.34 C21.37,26.85 19.93,25.89 18.34,25.89 C16.18,25.89 14.43,27.64 14.43,29.8 C14.43,31.42 14.87,32.99 15.68,34.36 C16.22,35.26 16.93,36.08 17.77,36.75 C17.77,36.75 18.52,37.34 18.52,37.34 "/><path android:name="_R_G_L_0_G_D_2_P_0" android:strokeColor="#d3e3fd" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="2" android:strokeAlpha="1" android:trimPathStart="0" android:trimPathEnd="0" android:trimPathOffset="0" android:pathData=" M6.25 19.34 C7.48,17.3 9.46,15.58 11.9,14.42 C12.93,13.94 14.03,13.55 15.2,13.27 C16.51,12.96 17.9,12.8 19.34,12.8 C20.77,12.8 22.14,12.96 23.45,13.26 C24.9,13.6 26.26,14.12 27.48,14.78 C29.6,15.92 31.32,17.5 32.43,19.34 "/><path android:name="_R_G_L_0_G_D_3_P_0" android:strokeColor="#d3e3fd" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="2" android:strokeAlpha="1" android:trimPathStart="0" android:trimPathEnd="0" android:trimPathOffset="0" android:pathData=" M9.52 8.7 C10.98,7.91 12.58,7.28 14.28,6.86 C15.89,6.46 17.58,6.25 19.34,6.25 C21.06,6.25 22.72,6.45 24.3,6.83 C26.04,7.25 27.67,7.89 29.16,8.7 "/></group></group><group android:name="time_group"/></vector></aapt:attr><target android:name="_R_G_L_3_G_D_0_P_0"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="fillAlpha" android:duration="67" 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.167,0.167 0.833,0.833 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="fillAlpha" android:duration="167" android:startOffset="67" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_2_G_D_0_P_0"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="strokeWidth" android:duration="167" android:startOffset="0" android:valueFrom="2.5" android:valueTo="0" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.333,0 0.667,1 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_2_G_D_0_P_0"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="strokeAlpha" android:duration="83" 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.167,0.167 0.833,0.833 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="strokeAlpha" android:duration="83" android:startOffset="83" android:valueFrom="1" android:valueTo="0" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_2_G"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="scaleY" android:duration="0" android:startOffset="0" android:valueFrom="0" android:valueTo="0.975" android:valueType="floatType"/></set></aapt:attr></target><target android:name="_R_G_L_2_G"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="scaleY" android:duration="0" android:startOffset="167" android:valueFrom="0.975" android:valueTo="0" android:valueType="floatType"/></set></aapt:attr></target><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: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.853,0 0.6,1 1.0,1.0"/></aapt:attr></objectAnimator><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.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_G_0_T_0"><aapt:attr name="android:animation"><set android:ordering="together"><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.8,0 0.659,1 1.0,1.0"/></aapt:attr></objectAnimator><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.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></aapt:attr></target><target android:name="_R_G_L_0_G_D_0_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.543,0 0.299,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.543,0 0.299,1 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_0_G_D_1_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.543,0 0.299,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.543,0 0.299,1 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_0_G_D_2_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.543,0 0.299,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.543,0 0.299,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.543,0 0.299,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.543,0 0.299,1 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="time_group"><aapt:attr name="android:animation"><set android:ordering="together"><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
diff --git a/packages/SystemUI/res/drawable/fingerprint_dialog_error_to_unlock.xml b/packages/SystemUI/res/drawable/fingerprint_dialog_error_to_unlock.xml
deleted file mode 100644
index c4f8181..0000000
--- a/packages/SystemUI/res/drawable/fingerprint_dialog_error_to_unlock.xml
+++ /dev/null
@@ -1 +0,0 @@
-<animated-vector xmlns:android="http://schemas.android.com/apk/res/android" xmlns:aapt="http://schemas.android.com/aapt"><aapt:attr name="android:drawable"><vector android:height="80dp" android:width="80dp" android:viewportHeight="80" android:viewportWidth="80"><group android:name="_R_G"><group android:name="_R_G_L_3_G" android:translateX="-0.25" android:translateY="-0.25"><path android:name="_R_G_L_3_G_D_0_P_0" android:fillColor="#474747" android:fillAlpha="0" android:fillType="nonZero" android:pathData=" M40.21 0.25 C18.13,0.25 0.25,18.17 0.25,40.25 C0.25,62.33 18.13,80.25 40.21,80.25 C62.33,80.25 80.25,62.33 80.25,40.25 C80.25,18.17 62.33,0.25 40.21,0.25c "/></group><group android:name="_R_G_L_2_G" android:translateX="-0.25" android:translateY="-0.25" android:pivotX="40.25" android:pivotY="40.25" android:scaleX="0.975" android:scaleY="0.975"><path android:name="_R_G_L_2_G_D_0_P_0" android:strokeColor="#f2b8b5" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="2.5" android:strokeAlpha="1" android:pathData=" M40.21 0.25 C18.13,0.25 0.25,18.17 0.25,40.25 C0.25,62.33 18.13,80.25 40.21,80.25 C62.33,80.25 80.25,62.33 80.25,40.25 C80.25,18.17 62.33,0.25 40.21,0.25c "/></group><group android:name="_R_G_L_1_G" android:translateX="9.950000000000003" android:translateY="10" android:pivotX="30" android:pivotY="30" android:scaleX="1.2" android:scaleY="1.2"><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="#f2b8b5" 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="#f2b8b5" 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></group><group android:name="_R_G_L_0_G" android:translateX="20.75" android:translateY="15.75" android:pivotX="19.341" android:pivotY="24.25" android:scaleX="0.5" android:scaleY="0"><path android:name="_R_G_L_0_G_D_0_P_0" android:strokeColor="#d3e3fd" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="2" android:strokeAlpha="1" android:pathData=" M30.64 30.14 C30.64,30.14 30.64,38.14 30.64,38.14 C30.64,38.77 30.36,39.32 29.91,39.69 C29.57,39.97 29.12,40.14 28.64,40.14 C28.64,40.14 10.14,40.14 10.14,40.14 C9.04,40.14 8.14,39.25 8.14,38.14 C8.14,38.14 8.14,30.14 8.14,30.14 "/><path android:name="_R_G_L_0_G_D_1_P_0" android:strokeColor="#d3e3fd" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="2" android:strokeAlpha="1" android:pathData=" M19.42 31.53 C18.15,31.52 18.11,30.33 18.11,30.33 C18.11,29.59 18.66,28.98 19.4,28.98 C20.13,28.98 20.69,29.59 20.69,30.33 C20.69,30.33 20.69,30.37 20.69,30.37 C20.69,30.64 20.49,30.87 20.25,30.87 C20.07,30.87 19.91,30.74 19.84,30.55 C19.84,30.55 19.69,30.14 19.69,30.14 C19.63,29.94 19.46,29.82 19.28,29.82 C19.04,29.82 18.61,30.02 18.61,30.29 C18.61,30.43 18.6,30.75 18.76,31.03 C18.87,31.21 19.21,31.77 19.96,31.41 C20.69,31.01 20.69,30.34 20.69,30.34 "/><path android:name="_R_G_L_0_G_D_2_P_0" android:strokeColor="#d3e3fd" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="2" android:strokeAlpha="1" android:pathData=" M8.14 30.22 C8.14,30.22 8.14,22.22 8.14,22.22 C8.14,21.71 8.33,21.25 8.64,20.9 C9,20.48 9.54,20.22 10.14,20.22 C10.14,20.22 28.64,20.22 28.64,20.22 C29.75,20.22 30.64,21.11 30.64,22.22 C30.64,22.22 30.64,30.14 30.64,30.14 "/><path android:name="_R_G_L_0_G_D_3_P_0" android:strokeColor="#d3e3fd" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="2" android:strokeAlpha="1" android:pathData=" M13.12 20.04 C13.12,20.04 13.11,14.15 13.11,14.15 C13.11,10.77 15.91,8.04 19.36,8.04 C22.81,8.04 25.61,10.77 25.61,14.15 C25.61,14.15 25.62,20.04 25.62,20.04 "/></group></group><group android:name="time_group"/></vector></aapt:attr><target android:name="_R_G_L_3_G_D_0_P_0"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="fillAlpha" android:duration="67" 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.167,0.167 0.833,0.833 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="fillAlpha" android:duration="167" android:startOffset="67" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_2_G_D_0_P_0"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="strokeWidth" android:duration="167" android:startOffset="0" android:valueFrom="2.5" android:valueTo="0" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.333,0 0.667,1 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_2_G_D_0_P_0"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="strokeAlpha" android:duration="83" 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.167,0.167 0.833,0.833 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="strokeAlpha" android:duration="83" android:startOffset="83" android:valueFrom="1" android:valueTo="0" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_2_G"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="scaleY" android:duration="0" android:startOffset="0" android:valueFrom="0" android:valueTo="0.975" android:valueType="floatType"/></set></aapt:attr></target><target android:name="_R_G_L_2_G"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="scaleY" android:duration="0" android:startOffset="167" android:valueFrom="0.975" android:valueTo="0" android:valueType="floatType"/></set></aapt:attr></target><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: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.853,0 0.6,1 1.0,1.0"/></aapt:attr></objectAnimator><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.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_G_0_T_0"><aapt:attr name="android:animation"><set android:ordering="together"><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.8,0 0.659,1 1.0,1.0"/></aapt:attr></objectAnimator><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.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></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="pathData" android:duration="233" android:startOffset="0" android:valueFrom="M13.12 20.04 C13.12,20.04 13.11,14.15 13.11,14.15 C13.11,10.77 15.91,8.04 19.36,8.04 C22.81,8.04 25.61,10.77 25.61,14.15 C25.61,14.15 25.62,20.04 25.62,20.04 " android:valueTo="M13.12 20.04 C13.12,20.04 13.11,14.15 13.11,14.15 C13.11,10.77 15.91,8.04 19.36,8.04 C22.81,8.04 25.61,10.77 25.61,14.15 C25.61,14.15 25.62,20.04 25.62,20.04 " android:valueType="pathType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.2,0 0,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="pathData" android:duration="267" android:startOffset="233" android:valueFrom="M13.12 20.04 C13.12,20.04 13.11,14.15 13.11,14.15 C13.11,10.77 15.91,8.04 19.36,8.04 C22.81,8.04 25.61,10.77 25.61,14.15 C25.61,14.15 25.62,20.04 25.62,20.04 " android:valueTo="M37.91 20.05 C37.91,20.05 37.89,14.16 37.89,14.16 C37.89,10.79 35.15,8.05 31.86,8.03 C28.46,8.01 25.61,10.77 25.61,14.15 C25.61,14.15 25.62,20.04 25.62,20.04 " android:valueType="pathType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.2,0 0,1 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_0_G"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="scaleX" android:duration="167" android:startOffset="0" android:valueFrom="0.5" android:valueTo="0.5" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.05,1.4 0.1,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="scaleY" android:duration="167" android:startOffset="0" android:valueFrom="0.5" android:valueTo="0.5" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.05,1.4 0.1,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="scaleX" android:duration="333" android:startOffset="167" android:valueFrom="0.5" android:valueTo="1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.05,1.4 0.1,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="scaleY" android:duration="333" android:startOffset="167" android:valueFrom="0.5" android:valueTo="1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.05,1.4 0.1,1 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_0_G"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="scaleY" android:duration="0" android:startOffset="167" android:valueFrom="0" android:valueTo="0.5" android:valueType="floatType"/></set></aapt:attr></target><target android:name="time_group"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="translateX" android:duration="683" android:startOffset="0" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"/></set></aapt:attr></target></animated-vector>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/fingerprint_dialog_fp_to_error.xml b/packages/SystemUI/res/drawable/fingerprint_dialog_fp_to_error.xml
deleted file mode 100644
index c05a8d5..0000000
--- a/packages/SystemUI/res/drawable/fingerprint_dialog_fp_to_error.xml
+++ /dev/null
@@ -1 +0,0 @@
-<animated-vector xmlns:android="http://schemas.android.com/apk/res/android" xmlns:aapt="http://schemas.android.com/aapt"><aapt:attr name="android:drawable"><vector android:height="80dp" android:width="80dp" android:viewportHeight="80" android:viewportWidth="80"><group android:name="_R_G"><group android:name="_R_G_L_3_G" android:translateX="-0.25" android:translateY="-0.25"><path android:name="_R_G_L_3_G_D_0_P_0" android:fillColor="#474747" android:fillAlpha="1" android:fillType="nonZero" android:pathData=" M40.21 0.25 C18.13,0.25 0.25,18.17 0.25,40.25 C0.25,62.33 18.13,80.25 40.21,80.25 C62.33,80.25 80.25,62.33 80.25,40.25 C80.25,18.17 62.33,0.25 40.21,0.25c "/></group><group android:name="_R_G_L_2_G" android:translateX="-0.25" android:translateY="-0.25" android:pivotX="40.25" android:pivotY="40.25" android:scaleX="0.975" android:scaleY="0"><path android:name="_R_G_L_2_G_D_0_P_0" android:strokeColor="#f2b8b5" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="0" android:strokeAlpha="0" android:pathData=" M40.21 0.25 C18.13,0.25 0.25,18.17 0.25,40.25 C0.25,62.33 18.13,80.25 40.21,80.25 C62.33,80.25 80.25,62.33 80.25,40.25 C80.25,18.17 62.33,0.25 40.21,0.25c "/></group><group android:name="_R_G_L_1_G" android:translateX="9.950000000000003" android:translateY="10" android:pivotX="30" android:pivotY="30" android:scaleX="1.2" android:scaleY="1.2"><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="#f2b8b5" 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="#f2b8b5" 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></group><group android:name="_R_G_L_0_G" android:translateX="20.659" android:translateY="15.75"><path android:name="_R_G_L_0_G_D_0_P_0" android:strokeColor="#d3e3fd" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="2" android:strokeAlpha="1" android:trimPathStart="0" android:trimPathEnd="1" android:trimPathOffset="0" android:pathData=" M27.52 38.98 C26.49,39.95 25.29,40.73 23.98,41.29 C23.17,41.65 22.31,41.91 21.41,42.07 C20.74,42.19 20.05,42.25 19.34,42.25 C18.44,42.25 17.56,42.15 16.72,41.96 C15.93,41.77 15.16,41.51 14.43,41.18 C13.23,40.63 12.13,39.88 11.16,38.98 "/><path android:name="_R_G_L_0_G_D_1_P_0" android:strokeColor="#d3e3fd" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="2" android:strokeAlpha="1" android:trimPathStart="0" android:trimPathEnd="1" android:trimPathOffset="0" android:pathData=" M8.64 34.07 C7.89,31.97 7.89,29.85 7.89,29.85 C7.89,24.05 12.81,19.34 19.34,19.34 C25.87,19.34 30.8,24.05 30.8,29.85 C30.8,29.85 30.8,30.16 30.8,30.16 C30.8,32.32 29.04,34.07 26.89,34.07 C25.28,34.07 23.86,33.1 23.27,31.61 C23.27,31.61 21.96,28.34 21.96,28.34 C21.37,26.85 19.93,25.89 18.34,25.89 C16.18,25.89 14.43,27.64 14.43,29.8 C14.43,31.42 14.87,32.99 15.68,34.36 C16.22,35.26 16.93,36.08 17.77,36.75 C17.77,36.75 18.52,37.34 18.52,37.34 "/><path android:name="_R_G_L_0_G_D_2_P_0" android:strokeColor="#d3e3fd" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="2" android:strokeAlpha="1" android:trimPathStart="0" android:trimPathEnd="1" android:trimPathOffset="0" android:pathData=" M6.25 19.34 C7.48,17.3 9.46,15.58 11.9,14.42 C12.93,13.94 14.03,13.55 15.2,13.27 C16.51,12.96 17.9,12.8 19.34,12.8 C20.77,12.8 22.14,12.96 23.45,13.26 C24.9,13.6 26.26,14.12 27.48,14.78 C29.6,15.92 31.32,17.5 32.43,19.34 "/><path android:name="_R_G_L_0_G_D_3_P_0" android:strokeColor="#d3e3fd" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="2" android:strokeAlpha="1" android:trimPathStart="0" android:trimPathEnd="1" android:trimPathOffset="0" android:pathData=" M9.52 8.7 C10.98,7.91 12.58,7.28 14.28,6.86 C15.89,6.46 17.58,6.25 19.34,6.25 C21.06,6.25 22.72,6.45 24.3,6.83 C26.04,7.25 27.67,7.89 29.16,8.7 "/></group></group><group android:name="time_group"/></vector></aapt:attr><target android:name="_R_G_L_3_G_D_0_P_0"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="fillAlpha" android:duration="83" 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.167,0.167 0.833,0.833 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_2_G_D_0_P_0"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="strokeWidth" android:duration="67" 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.333,0 0.667,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="strokeWidth" android:duration="233" android:startOffset="67" android:valueFrom="0" android:valueTo="2.5" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.333,0 0.667,1 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_2_G_D_0_P_0"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="strokeAlpha" android:duration="67" 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.167,0.167 0.833,0.833 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="strokeAlpha" android:duration="83" android:startOffset="67" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_2_G"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="scaleY" android:duration="0" android:startOffset="67" android:valueFrom="0" android:valueTo="0.975" android:valueType="floatType"/></set></aapt:attr></target><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: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.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_G_0_T_0"><aapt:attr name="android:animation"><set android:ordering="together"><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.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></aapt:attr></target><target android:name="_R_G_L_0_G_D_0_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.2,0 0,1 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_0_G_D_1_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.2,0 0,1 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_0_G_D_2_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.2,0 0,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.2,0 0,1 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="time_group"><aapt:attr name="android:animation"><set android:ordering="together"><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
diff --git a/packages/SystemUI/res/drawable/fingerprint_dialog_fp_to_unlock.xml b/packages/SystemUI/res/drawable/fingerprint_dialog_fp_to_unlock.xml
deleted file mode 100644
index 1694429..0000000
--- a/packages/SystemUI/res/drawable/fingerprint_dialog_fp_to_unlock.xml
+++ /dev/null
@@ -1 +0,0 @@
-<animated-vector xmlns:android="http://schemas.android.com/apk/res/android" xmlns:aapt="http://schemas.android.com/aapt"><aapt:attr name="android:drawable"><vector android:height="80dp" android:width="80dp" android:viewportHeight="80" android:viewportWidth="80"><group android:name="_R_G"><group android:name="_R_G_L_2_G" android:translateX="-0.25" android:translateY="-0.25"><path android:name="_R_G_L_2_G_D_0_P_0" android:fillColor="#474747" android:fillAlpha="1" android:fillType="nonZero" android:pathData=" M40.21 0.25 C18.13,0.25 0.25,18.17 0.25,40.25 C0.25,62.33 18.13,80.25 40.21,80.25 C62.33,80.25 80.25,62.33 80.25,40.25 C80.25,18.17 62.33,0.25 40.21,0.25c "/></group><group android:name="_R_G_L_1_G" android:translateX="20.75" android:translateY="15.75"><path android:name="_R_G_L_1_G_D_0_P_0" android:strokeColor="#d3e3fd" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="2" android:strokeAlpha="1" android:pathData=" M27.52 38.98 C26.49,39.95 25.29,40.73 23.98,41.29 C23.17,41.65 22.31,41.91 21.41,42.07 C20.74,42.19 20.05,42.25 19.34,42.25 C18.44,42.25 17.56,42.15 16.72,41.96 C15.93,41.77 15.16,41.51 14.43,41.18 C13.23,40.63 12.13,39.88 11.16,38.98 "/><path android:name="_R_G_L_1_G_D_1_P_0" android:strokeColor="#d3e3fd" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="2" android:strokeAlpha="1" android:pathData=" M8.64 34.07 C7.89,31.97 7.89,29.85 7.89,29.85 C7.89,24.05 12.81,19.34 19.34,19.34 C25.87,19.34 30.8,24.05 30.8,29.85 C30.8,29.85 30.8,30.16 30.8,30.16 C30.8,32.32 29.04,34.07 26.89,34.07 C25.28,34.07 23.86,33.1 23.27,31.61 C23.27,31.61 21.96,28.34 21.96,28.34 C21.37,26.85 19.93,25.89 18.34,25.89 C16.18,25.89 14.43,27.64 14.43,29.8 C14.43,31.42 14.87,32.99 15.68,34.36 C16.22,35.26 16.93,36.08 17.77,36.75 C17.77,36.75 18.52,37.34 18.52,37.34 "/><path android:name="_R_G_L_1_G_D_2_P_0" android:strokeColor="#d3e3fd" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="2" android:strokeAlpha="1" android:pathData=" M6.25 19.34 C7.48,17.3 9.46,15.58 11.9,14.42 C12.93,13.94 14.03,13.55 15.2,13.27 C16.51,12.96 17.9,12.8 19.34,12.8 C20.77,12.8 22.14,12.96 23.45,13.26 C24.9,13.6 26.26,14.12 27.48,14.78 C29.6,15.92 31.32,17.5 32.43,19.34 "/><path android:name="_R_G_L_1_G_D_3_P_0" android:strokeColor="#d3e3fd" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="2" android:strokeAlpha="1" android:pathData=" M9.52 8.7 C10.98,7.91 12.58,7.28 14.28,6.86 C15.89,6.46 17.58,6.25 19.34,6.25 C21.06,6.25 22.72,6.45 24.3,6.83 C26.04,7.25 27.67,7.89 29.16,8.7 "/></group><group android:name="_R_G_L_0_G" android:translateX="37.357" android:translateY="43.25" android:pivotX="2.75" android:pivotY="2.75" android:scaleX="1.41866" android:scaleY="1.41866"><path android:name="_R_G_L_0_G_D_0_P_0" android:fillColor="#d3e3fd" android:fillAlpha="0" android:fillType="nonZero" android:pathData=" M2.75 5.25 C4.13,5.25 5.25,4.13 5.25,2.75 C5.25,1.37 4.13,0.25 2.75,0.25 C1.37,0.25 0.25,1.37 0.25,2.75 C0.25,4.13 1.37,5.25 2.75,5.25c "/></group></group><group android:name="time_group"/></vector></aapt:attr><target android:name="_R_G_L_1_G_D_0_P_0"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="pathData" android:duration="107" android:startOffset="0" android:valueFrom="M27.52 38.98 C26.49,39.95 25.29,40.73 23.98,41.29 C23.17,41.65 22.31,41.91 21.41,42.07 C20.74,42.19 20.05,42.25 19.34,42.25 C18.44,42.25 17.56,42.15 16.72,41.96 C15.93,41.77 15.16,41.51 14.43,41.18 C13.23,40.63 12.13,39.88 11.16,38.98 " android:valueTo="M30.81 32.26 C30.56,32.49 30.27,38.76 29.96,38.9 C29.77,39.46 29.13,39.94 28.57,40.26 C28.15,40.51 26.93,40.65 26.4,40.65 C26.18,40.65 11.91,40.62 11.71,40.58 C10.68,40.53 9.06,39.79 8.89,38.88 C8.6,38.74 8.34,32.48 8.1,32.27 " android:valueType="pathType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.541,0 0.833,0.767 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="pathData" android:duration="143" android:startOffset="107" android:valueFrom="M30.81 32.26 C30.56,32.49 30.27,38.76 29.96,38.9 C29.77,39.46 29.13,39.94 28.57,40.26 C28.15,40.51 26.93,40.65 26.4,40.65 C26.18,40.65 11.91,40.62 11.71,40.58 C10.68,40.53 9.06,39.79 8.89,38.88 C8.6,38.74 8.34,32.48 8.1,32.27 " android:valueTo="M30.64 30.14 C30.64,30.14 30.64,38.14 30.64,38.14 C30.64,38.77 30.36,39.32 29.91,39.69 C29.57,39.97 29.12,40.14 28.64,40.14 C28.64,40.14 10.14,40.14 10.14,40.14 C9.04,40.14 8.14,39.25 8.14,38.14 C8.14,38.14 8.14,30.14 8.14,30.14 " android:valueType="pathType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.233 0.331,1 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_1_G_D_1_P_0"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="strokeAlpha" android:duration="140" 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.167,0.167 0.833,0.833 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="strokeAlpha" android:duration="50" android:startOffset="140" android:valueFrom="1" android:valueTo="0" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_1_G_D_1_P_0"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="pathData" android:duration="107" android:startOffset="0" android:valueFrom="M8.64 34.07 C7.89,31.97 7.89,29.85 7.89,29.85 C7.89,24.05 12.81,19.34 19.34,19.34 C25.87,19.34 30.8,24.05 30.8,29.85 C30.8,29.85 30.8,30.16 30.8,30.16 C30.8,32.32 29.04,34.07 26.89,34.07 C25.28,34.07 23.86,33.1 23.27,31.61 C23.27,31.61 21.96,28.34 21.96,28.34 C21.37,26.85 19.93,25.89 18.34,25.89 C16.18,25.89 14.43,27.64 14.43,29.8 C14.43,31.42 14.87,32.99 15.68,34.36 C16.22,35.26 16.93,36.08 17.77,36.75 C17.77,36.75 18.52,37.34 18.52,37.34 " android:valueTo="M18.93 32.18 C17.11,32.68 16.62,30.26 16.62,30.26 C16.62,28.78 17.81,27.59 19.39,27.59 C20.96,27.59 22.15,28.78 22.15,30.26 C22.15,30.26 22.15,30.34 22.15,30.34 C22.15,30.89 21.11,32.54 19.57,32.19 C19.19,32.1 20.48,31.09 20.34,30.71 C20.34,30.71 20.02,29.88 20.02,29.88 C19.88,29.5 19.53,29.25 19.15,29.25 C18.63,29.25 18,29.67 18,30.22 C18,30.57 18.06,31.08 18.32,31.51 C18.49,31.8 19.02,32.25 19.79,32.04 C20.41,31.7 20.38,31.36 20.38,31.36 " android:valueType="pathType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.541,0 0.833,0.767 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="pathData" android:duration="107" android:startOffset="107" android:valueFrom="M18.93 32.18 C17.11,32.68 16.62,30.26 16.62,30.26 C16.62,28.78 17.81,27.59 19.39,27.59 C20.96,27.59 22.15,28.78 22.15,30.26 C22.15,30.26 22.15,30.34 22.15,30.34 C22.15,30.89 21.11,32.54 19.57,32.19 C19.19,32.1 20.48,31.09 20.34,30.71 C20.34,30.71 20.02,29.88 20.02,29.88 C19.88,29.5 19.53,29.25 19.15,29.25 C18.63,29.25 18,29.67 18,30.22 C18,30.57 18.06,31.08 18.32,31.51 C18.49,31.8 19.02,32.25 19.79,32.04 C20.41,31.7 20.38,31.36 20.38,31.36 " android:valueTo="M19.42 31.53 C18.15,31.52 18.11,30.33 18.11,30.33 C18.11,29.59 18.66,28.98 19.4,28.98 C20.13,28.98 20.69,29.59 20.69,30.33 C20.69,30.33 20.69,30.37 20.69,30.37 C20.69,30.64 20.49,30.87 20.25,30.87 C20.07,30.87 19.91,30.74 19.84,30.55 C19.84,30.55 19.69,30.14 19.69,30.14 C19.63,29.94 19.46,29.82 19.28,29.82 C19.04,29.82 18.61,30.02 18.61,30.29 C18.61,30.43 18.6,30.75 18.76,31.03 C18.87,31.21 19.21,31.77 19.96,31.41 C20.69,31.01 20.69,30.34 20.69,30.34 " android:valueType="pathType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.233 0,1 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_1_G_D_2_P_0"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="pathData" android:duration="250" android:startOffset="0" android:valueFrom="M6.25 19.34 C7.48,17.3 9.46,15.58 11.9,14.42 C12.93,13.94 14.03,13.55 15.2,13.27 C16.51,12.96 17.9,12.8 19.34,12.8 C20.77,12.8 22.14,12.96 23.45,13.26 C24.9,13.6 26.26,14.12 27.48,14.78 C29.6,15.92 31.32,17.5 32.43,19.34 " android:valueTo="M8.14 30.22 C8.14,30.22 8.14,22.22 8.14,22.22 C8.14,21.71 8.33,21.25 8.64,20.9 C9,20.48 9.54,20.22 10.14,20.22 C10.14,20.22 28.64,20.22 28.64,20.22 C29.75,20.22 30.64,21.11 30.64,22.22 C30.64,22.22 30.64,30.14 30.64,30.14 " android:valueType="pathType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.541,0 0.189,1 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_1_G_D_3_P_0"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="pathData" android:duration="95" android:startOffset="0" android:valueFrom="M9.52 8.7 C10.98,7.91 12.58,7.28 14.28,6.86 C15.89,6.46 17.58,6.25 19.34,6.25 C21.06,6.25 22.72,6.45 24.3,6.83 C26.04,7.25 27.67,7.89 29.16,8.7 " android:valueTo="M11.47 14.84 C11.47,14.84 12.21,11.43 13.54,9.84 C14.84,8.28 16.68,7.22 19.35,7.22 C22.01,7.22 23.98,8.4 25.19,10.18 C26.39,11.96 27.25,14.84 27.25,14.84 " android:valueType="pathType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.541,0 0.833,0.767 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="pathData" android:duration="24" android:startOffset="95" android:valueFrom="M11.47 14.84 C11.47,14.84 12.21,11.43 13.54,9.84 C14.84,8.28 16.68,7.22 19.35,7.22 C22.01,7.22 23.98,8.4 25.19,10.18 C26.39,11.96 27.25,14.84 27.25,14.84 " android:valueTo="M12.11 16.85 C12.11,16.85 12.82,12.71 13.37,11.5 C14.17,9.24 16.38,7.53 19.35,7.53 C22.32,7.53 24.61,9.32 25.35,11.72 C25.61,12.64 26.62,16.85 26.62,16.85 " android:valueType="pathType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.233 0.833,0.767 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="pathData" android:duration="81" android:startOffset="119" android:valueFrom="M12.11 16.85 C12.11,16.85 12.82,12.71 13.37,11.5 C14.17,9.24 16.38,7.53 19.35,7.53 C22.32,7.53 24.61,9.32 25.35,11.72 C25.61,12.64 26.62,16.85 26.62,16.85 " android:valueTo="M13.12 20.04 C13.12,20.04 13.11,14.15 13.11,14.15 C13.11,10.77 15.91,8.04 19.36,8.04 C22.81,8.04 25.61,10.77 25.61,14.15 C25.61,14.15 25.62,20.04 25.62,20.04 " android:valueType="pathType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.233 0.261,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="pathData" android:duration="233" android:startOffset="200" android:valueFrom="M13.12 20.04 C13.12,20.04 13.11,14.15 13.11,14.15 C13.11,10.77 15.91,8.04 19.36,8.04 C22.81,8.04 25.61,10.77 25.61,14.15 C25.61,14.15 25.62,20.04 25.62,20.04 " android:valueTo="M37.91 20.05 C37.91,20.05 37.89,14.16 37.89,14.16 C37.89,10.79 35.15,8.05 31.86,8.03 C28.46,8.01 25.61,10.77 25.61,14.15 C25.61,14.15 25.62,20.04 25.62,20.04 " android:valueType="pathType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.123,0 0.23,1 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_0_G_D_0_P_0"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="fillAlpha" android:duration="120" 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.167,0.167 0.833,0.833 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="fillAlpha" android:duration="20" android:startOffset="120" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_0_G"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="scaleX" android:duration="120" android:startOffset="0" android:valueFrom="1.4186600000000003" android:valueTo="1.4186600000000003" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.001,0 0.43,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="scaleY" android:duration="120" android:startOffset="0" android:valueFrom="1.4186600000000003" android:valueTo="1.4186600000000003" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.001,0 0.43,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="scaleX" android:duration="130" android:startOffset="120" android:valueFrom="1.4186600000000003" android:valueTo="1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.001,0 0.43,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="scaleY" android:duration="130" android:startOffset="120" android:valueFrom="1.4186600000000003" android:valueTo="1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.001,0 0.43,1 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="time_group"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="translateX" android:duration="517" android:startOffset="0" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"/></set></aapt:attr></target></animated-vector>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/auth_biometric_contents.xml b/packages/SystemUI/res/layout/auth_biometric_contents.xml
index e1b294f..d633803 100644
--- a/packages/SystemUI/res/layout/auth_biometric_contents.xml
+++ b/packages/SystemUI/res/layout/auth_biometric_contents.xml
@@ -49,11 +49,11 @@
 
     <FrameLayout
         android:id="@+id/biometric_icon_frame"
-        android:layout_width="match_parent"
+        android:layout_width="wrap_content"
         android:layout_height="wrap_content"
         android:layout_gravity="center_horizontal">
 
-        <ImageView
+        <com.airbnb.lottie.LottieAnimationView
             android:id="@+id/biometric_icon"
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
diff --git a/packages/SystemUI/res/layout/auth_biometric_fingerprint_view.xml b/packages/SystemUI/res/layout/auth_biometric_fingerprint_view.xml
index ce53e27..01ea31f 100644
--- a/packages/SystemUI/res/layout/auth_biometric_fingerprint_view.xml
+++ b/packages/SystemUI/res/layout/auth_biometric_fingerprint_view.xml
@@ -17,7 +17,7 @@
 <com.android.systemui.biometrics.AuthBiometricFingerprintView
     xmlns:android="http://schemas.android.com/apk/res/android"
     android:id="@+id/contents"
-    android:layout_width="match_parent"
+    android:layout_width="wrap_content"
     android:layout_height="wrap_content"
     android:orientation="vertical">
 
diff --git a/packages/SystemUI/res/raw/fingerprint_dialogue_error_to_fingerprint_lottie.json b/packages/SystemUI/res/raw/fingerprint_dialogue_error_to_fingerprint_lottie.json
new file mode 100644
index 0000000..cc68a83
--- /dev/null
+++ b/packages/SystemUI/res/raw/fingerprint_dialogue_error_to_fingerprint_lottie.json
@@ -0,0 +1 @@
+{"v":"5.9.0","fr":60,"ip":0,"op":25,"w":80,"h":80,"nm":"error_to_fingerprint","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":".colorAccentPrimary","cl":"colorAccentPrimary","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[40,40,0],"ix":2,"l":2},"a":{"a":0,"k":[19.341,24.25,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[-1.701,0.42],[-1.757,0],[-1.577,-0.381],[-1.485,-0.816]],"o":[[1.455,-0.799],[1.608,-0.397],[1.719,0],[1.739,0.42],[0,0]],"v":[[-9.818,1.227],[-5.064,-0.618],[0,-1.227],[4.96,-0.643],[9.818,1.227]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.827450990677,0.890196084976,0.992156863213,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":2,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[19.341,7.477],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Top","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[-2.446,1.161],[-1.168,0.275],[-1.439,0],[-1.301,-0.304],[-1.225,-0.66],[-1.11,-1.844]],"o":[[1.23,-2.044],[1.024,-0.486],[1.312,-0.31],[1.425,0],[1.454,0.34],[2.122,1.143],[0,0]],"v":[[-13.091,3.273],[-7.438,-1.646],[-4.14,-2.797],[0,-3.273],[4.104,-2.805],[8.141,-1.29],[13.091,3.273]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.827450980392,0.890196078431,0.992156862745,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":2,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[19.341,16.069],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Mid Top","np":2,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[-6.53,0],[0,-5.793],[0,0],[2.159,0],[0.59,1.489],[0,0],[1.587,0],[0,-2.16],[-0.81,-1.363],[-0.844,-0.674],[0,0]],"o":[[-0.753,-2.095],[0,-5.793],[6.529,0],[0,0],[0,2.16],[-1.604,0],[0,0],[-0.589,-1.489],[-2.161,0],[0,1.62],[0.54,0.909],[0,0],[0,0]],"v":[[-10.702,5.728],[-11.454,1.506],[0.001,-9],[11.454,1.506],[11.454,1.817],[7.544,5.728],[3.926,3.273],[2.618,0],[-0.997,-2.454],[-4.91,1.457],[-3.657,6.014],[-1.57,8.412],[-0.818,9]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.827450980392,0.890196078431,0.992156862745,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":2,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[19.341,28.341],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Inside to dot ","np":2,"cix":2,"bm":0,"ix":3,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[1.307,-0.561],[0.894,-0.16],[0.706,0],[0.844,0.193],[0.728,0.334],[0.967,0.901]],"o":[[-1.038,0.967],[-0.817,0.351],[-0.673,0.12],[-0.9,0],[-0.794,-0.182],[-1.203,-0.551],[0,0]],"v":[[8.182,-1.636],[4.642,0.681],[2.07,1.453],[-0.001,1.636],[-2.621,1.341],[-4.909,0.563],[-8.182,-1.636]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.827450980392,0.890196078431,0.992156862745,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":2,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[19.341,40.614],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Bottom","np":2,"cix":2,"bm":0,"ix":4,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":0,"k":0,"ix":1},"e":{"a":1,"k":[{"i":{"x":[0.299],"y":[1]},"o":{"x":[0.543],"y":[0]},"t":5,"s":[0]},{"t":20,"s":[100]}],"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":5,"nm":"Trim Paths 1","mn":"ADBE Vector Filter - Trim","hd":false}],"ip":0,"op":600,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":".red200","cl":"red200","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[39.95,40,0],"ix":2,"l":2},"a":{"a":0,"k":[30,30,0],"ix":1,"l":2},"s":{"a":0,"k":[120,120,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[-1.2,-7.5],[1.2,-7.5],[1.2,7.5],[-1.2,7.5]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.949019607843,0.721568627451,0.709803921569,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[30.002,32.488],"ix":2},"a":{"a":0,"k":[0.002,7.488],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.659,0.6],"y":[1,1]},"o":{"x":[0.8,0.8],"y":[0,0]},"t":0,"s":[100,100]},{"i":{"x":[0.6,0.92],"y":[1,1.096]},"o":{"x":[0.8,0.8],"y":[0,0]},"t":4,"s":[100,110]},{"t":10,"s":[100,0]}],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Top!","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[-1.2,-1.25],[1.2,-1.25],[1.2,1.25],[-1.2,1.25]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.949019607843,0.721568627451,0.709803921569,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[30,38.75],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.6,0.6],"y":[1,1]},"o":{"x":[0.853,0.853],"y":[0,0]},"t":0,"s":[100,100]},{"i":{"x":[0.92,0.92],"y":[1.06,1.06]},"o":{"x":[0.8,0.8],"y":[0,0]},"t":4,"s":[110,110]},{"t":10,"s":[0,0]}],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Bottom!","np":2,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":25,"st":-30,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":".red200","cl":"red200","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":5,"s":[100]},{"t":10,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[40,40,0],"ix":2,"l":2},"a":{"a":0,"k":[40.25,40.25,0],"ix":1,"l":2},"s":{"a":0,"k":[97.5,97.5,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[22.12,0],[0,-22.08],[-22.08,0],[0,22.08]],"o":[[-22.08,0],[0,22.08],[22.12,0],[0,-22.08]],"v":[[-0.04,-40],[-40,0],[-0.04,40],[40,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tr","p":{"a":0,"k":[40.25,40.25],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":5,"s":[100]},{"t":10,"s":[0]}],"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 2","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.949019607843,0.721568627451,0.709803921569,1],"ix":3},"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":5,"s":[100]},{"t":10,"s":[0]}],"ix":4},"w":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":0,"s":[2.5]},{"t":10,"s":[0]}],"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false}],"ip":0,"op":10,"st":0,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":".grey700","cl":"grey700","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[40,40,0],"ix":2,"l":2},"a":{"a":0,"k":[40.25,40.25,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[22.12,0],[0,-22.08],[-22.08,0],[0,22.08]],"o":[[-22.08,0],[0,22.08],[22.12,0],[0,-22.08]],"v":[[-0.04,-40],[-40,0],[-0.04,40],[40,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.278431385756,0.278431385756,0.278431385756,1],"ix":4},"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":4,"s":[0]},{"t":14,"s":[100]}],"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[40.25,40.25],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 2","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":600,"st":0,"bm":0}],"markers":[]}
\ No newline at end of file
diff --git a/packages/SystemUI/res/raw/fingerprint_dialogue_error_to_unlock_lottie.json b/packages/SystemUI/res/raw/fingerprint_dialogue_error_to_unlock_lottie.json
new file mode 100644
index 0000000..aaf7e58
--- /dev/null
+++ b/packages/SystemUI/res/raw/fingerprint_dialogue_error_to_unlock_lottie.json
@@ -0,0 +1 @@
+{"v":"5.9.0","fr":60,"ip":0,"op":41,"w":80,"h":80,"nm":"error_to_unlock","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":".colorAccentPrimary","cl":"colorAccentPrimary","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[40.091,40,0],"ix":2,"l":2},"a":{"a":0,"k":[19.341,24.25,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.1,0.1,0.1],"y":[1,1,1]},"o":{"x":[0.05,0.05,0.05],"y":[1.4,1.4,0]},"t":10,"s":[50,50,100]},{"t":30,"s":[100,100,100]}],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0,"y":1},"o":{"x":0.2,"y":0},"t":14,"s":[{"i":[[0,0],[0,0],[-3.452,0],[0,-3.375],[0,0]],"o":[[0,0],[0,-3.375],[3.452,0],[0,0],[0,0]],"v":[[-6.217,12.558],[-6.234,6.669],[0.016,0.558],[6.266,6.669],[6.283,12.558]],"c":false}]},{"t":30,"s":[{"i":[[0,0],[0,0],[3.292,0.021],[0,-3.375],[0,0]],"o":[[0,0],[0,-3.375],[-3.393,-0.022],[0,0],[0,0]],"v":[[18.568,12.573],[18.552,6.684],[12.516,0.553],[6.266,6.669],[6.283,12.558]],"c":false}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.827450990677,0.890196084976,0.992156863213,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":2,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[19.341,7.477],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Top","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[-0.307,0.352],[-0.601,0],[0,0],[0,-1.104],[0,0]],"o":[[0,0],[0,-0.503],[0.367,-0.42],[0,0],[1.104,0],[0,0],[0,0]],"v":[[-11.2,14.15],[-11.198,6.146],[-10.705,4.831],[-9.198,4.146],[9.302,4.146],[11.302,6.146],[11.3,14.07]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.827450980392,0.890196078431,0.992156862745,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":2,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[19.341,16.069],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Mid Top","np":2,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[-0.736,0],[0,-0.741],[0,0],[0.243,0],[0.066,0.191],[0,0],[0.179,0],[0,-0.276],[-0.162,-0.273],[-0.755,0.357],[0,0]],"o":[[-1.273,-0.008],[0,-0.741],[0.736,0],[0,0],[0,0.276],[-0.181,0],[0,0],[-0.066,-0.191],[-0.243,0],[-0.002,0.139],[0.109,0.182],[0.727,-0.402],[0,0]],"v":[[0.082,3.187],[-1.235,1.986],[0.055,0.642],[1.346,1.986],[1.346,2.026],[0.905,2.527],[0.498,2.212],[0.35,1.794],[-0.057,1.479],[-0.733,1.951],[-0.58,2.686],[0.619,3.071],[1.351,2]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.827450980392,0.890196078431,0.992156862745,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":2,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[19.341,28.341],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Inside to dot ","np":2,"cix":2,"bm":0,"ix":3,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0.446,-0.367],[0.481,0],[0,0],[0,1.104],[0,0]],"o":[[0,0],[0,0.623],[-0.345,0.284],[0,0],[-1.104,0],[0,0],[0,0]],"v":[[11.302,-10.469],[11.302,-2.469],[10.57,-0.923],[9.302,-0.469],[-9.198,-0.469],[-11.198,-2.469],[-11.198,-10.469]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.827450980392,0.890196078431,0.992156862745,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":2,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[19.341,40.614],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Bottom","np":2,"cix":2,"bm":0,"ix":4,"mn":"ADBE Vector Group","hd":false}],"ip":10,"op":600,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":".red200","cl":"red200","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[39.95,40,0],"ix":2,"l":2},"a":{"a":0,"k":[30,30,0],"ix":1,"l":2},"s":{"a":0,"k":[120,120,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[-1.2,-7.5],[1.2,-7.5],[1.2,7.5],[-1.2,7.5]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.949019607843,0.721568627451,0.709803921569,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[30.002,32.488],"ix":2},"a":{"a":0,"k":[0.002,7.488],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.659,0.6],"y":[1,1]},"o":{"x":[0.8,0.8],"y":[0,0]},"t":0,"s":[100,100]},{"i":{"x":[0.6,0.92],"y":[1,1.096]},"o":{"x":[0.8,0.8],"y":[0,0]},"t":4,"s":[100,110]},{"t":10,"s":[100,0]}],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Top!","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[-1.2,-1.25],[1.2,-1.25],[1.2,1.25],[-1.2,1.25]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.949019607843,0.721568627451,0.709803921569,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[30,38.75],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.6,0.6],"y":[1,1]},"o":{"x":[0.853,0.853],"y":[0,0]},"t":0,"s":[100,100]},{"i":{"x":[0.92,0.92],"y":[1.06,1.06]},"o":{"x":[0.8,0.8],"y":[0,0]},"t":4,"s":[110,110]},{"t":10,"s":[0,0]}],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Bottom!","np":2,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":86,"st":-30,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":".red200","cl":"red200","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":5,"s":[100]},{"t":10,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[40,40,0],"ix":2,"l":2},"a":{"a":0,"k":[40.25,40.25,0],"ix":1,"l":2},"s":{"a":0,"k":[97.5,97.5,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[22.12,0],[0,-22.08],[-22.08,0],[0,22.08]],"o":[[-22.08,0],[0,22.08],[22.12,0],[0,-22.08]],"v":[[-0.04,-40],[-40,0],[-0.04,40],[40,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tr","p":{"a":0,"k":[40.25,40.25],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":5,"s":[100]},{"t":10,"s":[0]}],"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 2","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.949019607843,0.721568627451,0.709803921569,1],"ix":3},"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":5,"s":[100]},{"t":10,"s":[0]}],"ix":4},"w":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":0,"s":[2.5]},{"t":10,"s":[0]}],"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false}],"ip":0,"op":10,"st":0,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":".grey700","cl":"grey700","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[40,40,0],"ix":2,"l":2},"a":{"a":0,"k":[40.25,40.25,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[22.12,0],[0,-22.08],[-22.08,0],[0,22.08]],"o":[[-22.08,0],[0,22.08],[22.12,0],[0,-22.08]],"v":[[-0.04,-40],[-40,0],[-0.04,40],[40,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.278431385756,0.278431385756,0.278431385756,1],"ix":4},"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":4,"s":[0]},{"t":14,"s":[100]}],"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[40.25,40.25],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 2","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":600,"st":0,"bm":0}],"markers":[]}
\ No newline at end of file
diff --git a/packages/SystemUI/res/raw/fingerprint_dialogue_fingerprint_to_error_lottie.json b/packages/SystemUI/res/raw/fingerprint_dialogue_fingerprint_to_error_lottie.json
new file mode 100644
index 0000000..78bccba
--- /dev/null
+++ b/packages/SystemUI/res/raw/fingerprint_dialogue_fingerprint_to_error_lottie.json
@@ -0,0 +1 @@
+{"v":"5.9.0","fr":60,"ip":0,"op":21,"w":80,"h":80,"nm":"fingerprint_to_error","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":".colorAccentPrimary","cl":"colorAccentPrimary","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[40,40,0],"ix":2,"l":2},"a":{"a":0,"k":[19.341,24.25,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[-1.701,0.42],[-1.757,0],[-1.577,-0.381],[-1.485,-0.816]],"o":[[1.455,-0.799],[1.608,-0.397],[1.719,0],[1.739,0.42],[0,0]],"v":[[-9.818,1.227],[-5.064,-0.618],[0,-1.227],[4.96,-0.643],[9.818,1.227]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.827450980392,0.890196078431,0.992156862745,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":2,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[19.341,7.477],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Top","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[-2.446,1.161],[-1.168,0.275],[-1.439,0],[-1.301,-0.304],[-1.225,-0.66],[-1.11,-1.844]],"o":[[1.23,-2.044],[1.024,-0.486],[1.312,-0.31],[1.425,0],[1.454,0.34],[2.122,1.143],[0,0]],"v":[[-13.091,3.273],[-7.438,-1.646],[-4.14,-2.797],[0,-3.273],[4.104,-2.805],[8.141,-1.29],[13.091,3.273]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.827450980392,0.890196078431,0.992156862745,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":2,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[19.341,16.069],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Mid Top","np":2,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[-6.53,0],[0,-5.793],[0,0],[2.159,0],[0.59,1.489],[0,0],[1.587,0],[0,-2.16],[-0.81,-1.363],[-0.844,-0.674],[0,0]],"o":[[-0.753,-2.095],[0,-5.793],[6.529,0],[0,0],[0,2.16],[-1.604,0],[0,0],[-0.589,-1.489],[-2.161,0],[0,1.62],[0.54,0.909],[0,0],[0,0]],"v":[[-10.702,5.728],[-11.454,1.506],[0.001,-9],[11.454,1.506],[11.454,1.817],[7.544,5.728],[3.926,3.273],[2.618,0],[-0.997,-2.454],[-4.91,1.457],[-3.657,6.014],[-1.57,8.412],[-0.818,9]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.827450980392,0.890196078431,0.992156862745,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":2,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[19.341,28.341],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Inside to dot ","np":2,"cix":2,"bm":0,"ix":3,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[1.307,-0.561],[0.894,-0.16],[0.706,0],[0.844,0.193],[0.728,0.334],[0.967,0.901]],"o":[[-1.038,0.967],[-0.817,0.351],[-0.673,0.12],[-0.9,0],[-0.794,-0.182],[-1.203,-0.551],[0,0]],"v":[[8.182,-1.636],[4.642,0.681],[2.07,1.453],[-0.001,1.636],[-2.621,1.341],[-4.909,0.563],[-8.182,-1.636]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.827450990677,0.890196084976,0.992156863213,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":2,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[19.341,40.614],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Bottom","np":2,"cix":2,"bm":0,"ix":4,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":1,"k":[{"i":{"x":[0],"y":[1]},"o":{"x":[0.2],"y":[0]},"t":0,"s":[0]},{"t":10,"s":[100]}],"ix":1},"e":{"a":0,"k":100,"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":5,"nm":"Trim Paths 1","mn":"ADBE Vector Filter - Trim","hd":false}],"ip":0,"op":600,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":".red200","cl":"red200","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[39.95,40,0],"ix":2,"l":2},"a":{"a":0,"k":[30,30,0],"ix":1,"l":2},"s":{"a":0,"k":[120,120,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[-1.2,-7.5],[1.2,-7.5],[1.2,7.5],[-1.2,7.5]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.949019610882,0.721568644047,0.709803938866,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[30.002,32.488],"ix":2},"a":{"a":0,"k":[0.002,7.488],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.2,0.2],"y":[1,1]},"o":{"x":[0.4,0.08],"y":[0,0.096]},"t":10,"s":[100,0]},{"i":{"x":[0.2,0.2],"y":[1,1]},"o":{"x":[0.341,0.4],"y":[0,0]},"t":16,"s":[100,110]},{"t":20,"s":[100,100]}],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Top!","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[-1.2,-1.25],[1.2,-1.25],[1.2,1.25],[-1.2,1.25]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.949019610882,0.721568644047,0.709803938866,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[30,38.75],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.2,0.2],"y":[1,1]},"o":{"x":[0.08,0.08],"y":[0.06,0.06]},"t":10,"s":[0,0]},{"i":{"x":[0.147,0.147],"y":[1,1]},"o":{"x":[0.4,0.4],"y":[0,0]},"t":16,"s":[110,110]},{"t":20,"s":[100,100]}],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Bottom!","np":2,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":21,"st":0,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":".red200","cl":"red200","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":4,"s":[0]},{"t":9,"s":[100]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[40,40,0],"ix":2,"l":2},"a":{"a":0,"k":[40.25,40.25,0],"ix":1,"l":2},"s":{"a":0,"k":[97.5,97.5,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[22.12,0],[0,-22.08],[-22.08,0],[0,22.08]],"o":[[-22.08,0],[0,22.08],[22.12,0],[0,-22.08]],"v":[[-0.04,-40],[-40,0],[-0.04,40],[40,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tr","p":{"a":0,"k":[40.25,40.25],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":4,"s":[0]},{"t":9,"s":[100]}],"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 2","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.949019607843,0.721568627451,0.709803921569,1],"ix":3},"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":4,"s":[0]},{"t":9,"s":[100]}],"ix":4},"w":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":4,"s":[0]},{"t":18,"s":[2.5]}],"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false}],"ip":4,"op":600,"st":0,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":".grey700","cl":"grey700","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[40,40,0],"ix":2,"l":2},"a":{"a":0,"k":[40.25,40.25,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[22.12,0],[0,-22.08],[-22.08,0],[0,22.08]],"o":[[-22.08,0],[0,22.08],[22.12,0],[0,-22.08]],"v":[[-0.04,-40],[-40,0],[-0.04,40],[40,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.278431385756,0.278431385756,0.278431385756,1],"ix":4},"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[100]},{"t":5,"s":[0]}],"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[40.25,40.25],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 2","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":600,"st":0,"bm":0}],"markers":[]}
\ No newline at end of file
diff --git a/packages/SystemUI/res/raw/fingerprint_dialogue_fingerprint_to_unlock_lottie.json b/packages/SystemUI/res/raw/fingerprint_dialogue_fingerprint_to_unlock_lottie.json
new file mode 100644
index 0000000..313c6c5
--- /dev/null
+++ b/packages/SystemUI/res/raw/fingerprint_dialogue_fingerprint_to_unlock_lottie.json
@@ -0,0 +1 @@
+{"v":"5.9.0","fr":60,"ip":0,"op":31,"w":80,"h":80,"nm":"fingerprint_to_unlock","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":2,"ty":4,"nm":".colorAccentPrimary","cl":"colorAccentPrimary","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[40.107,46,0],"ix":2,"l":2},"a":{"a":0,"k":[2.75,2.75,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.43,0.43,0.2],"y":[1,1,1]},"o":{"x":[0.001,0.001,0.001],"y":[0,0,0]},"t":7.199,"s":[141.866,141.866,100]},{"t":15,"s":[100,100,100]}],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-1.381,0],[0,1.381],[1.381,0],[0,-1.381]],"o":[[1.381,0],[0,-1.381],[-1.381,0],[0,1.381]],"v":[[0,2.5],[2.5,0],[0,-2.5],[-2.5,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.827450980392,0.890196078431,0.992156862745,1],"ix":4},"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":7.199,"s":[0]},{"t":8.400390625,"s":[100]}],"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[2.75,2.75],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":600,"st":0,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":".colorAccentPrimary","cl":"colorAccentPrimary","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[40.091,40,0],"ix":2,"l":2},"a":{"a":0,"k":[19.341,24.25,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.767},"o":{"x":0.541,"y":0},"t":0,"s":[{"i":[[0,0],[-1.701,0.42],[-1.757,0],[-1.577,-0.381],[-1.485,-0.816]],"o":[[1.455,-0.799],[1.608,-0.397],[1.719,0],[1.739,0.42],[0,0]],"v":[[-9.818,1.227],[-5.064,-0.618],[0,-1.227],[4.96,-0.643],[9.818,1.227]],"c":false}]},{"i":{"x":0.833,"y":0.767},"o":{"x":0.167,"y":0.233},"t":5.715,"s":[{"i":[[0,0],[-1.323,1.591],[-2.674,0],[-1.207,-1.781],[0,0]],"o":[[0,0],[1.298,-1.562],[2.657,0],[1.206,1.781],[0,0]],"v":[[-7.87,7.358],[-5.804,2.36],[0.009,-0.261],[5.845,2.706],[7.905,7.358]],"c":false}]},{"i":{"x":0.261,"y":1},"o":{"x":0.167,"y":0.233},"t":7.143,"s":[{"i":[[0,0],[-0.549,1.21],[-2.975,0],[-0.74,-2.398],[0,0]],"o":[[0,0],[0.796,-2.263],[2.964,0],[0.258,0.927],[0,0]],"v":[[-7.231,9.37],[-5.97,4.027],[0.012,0.056],[6.008,4.239],[7.277,9.37]],"c":false}]},{"i":{"x":0.23,"y":1},"o":{"x":0.123,"y":0},"t":12,"s":[{"i":[[0,0],[0,0],[-3.452,0],[0,-3.375],[0,0]],"o":[[0,0],[0,-3.375],[3.452,0],[0,0],[0,0]],"v":[[-6.217,12.558],[-6.234,6.669],[0.016,0.558],[6.266,6.669],[6.283,12.558]],"c":false}]},{"t":26,"s":[{"i":[[0,0],[0,0],[3.292,0.021],[0,-3.375],[0,0]],"o":[[0,0],[0,-3.375],[-3.393,-0.022],[0,0],[0,0]],"v":[[18.568,12.573],[18.552,6.684],[12.516,0.553],[6.266,6.669],[6.283,12.558]],"c":false}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.827450980392,0.890196078431,0.992156862745,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":2,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[19.341,7.477],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Top","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.189,"y":1},"o":{"x":0.541,"y":0},"t":0,"s":[{"i":[[0,0],[-2.446,1.161],[-1.168,0.275],[-1.439,0],[-1.301,-0.304],[-1.225,-0.66],[-1.11,-1.844]],"o":[[1.23,-2.044],[1.024,-0.486],[1.312,-0.31],[1.425,0],[1.454,0.34],[2.122,1.143],[0,0]],"v":[[-13.091,3.273],[-7.438,-1.646],[-4.14,-2.797],[0,-3.273],[4.104,-2.805],[8.141,-1.29],[13.091,3.273]],"c":false}]},{"t":15,"s":[{"i":[[0,0],[0,0],[-0.307,0.352],[-0.601,0],[0,0],[0,-1.104],[0,0]],"o":[[0,0],[0,-0.503],[0.367,-0.42],[0,0],[1.104,0],[0,0],[0,0]],"v":[[-11.2,14.15],[-11.198,6.146],[-10.705,4.831],[-9.198,4.146],[9.302,4.146],[11.302,6.146],[11.3,14.07]],"c":false}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.827450980392,0.890196078431,0.992156862745,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":2,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[19.341,16.069],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Mid Top","np":2,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.767},"o":{"x":0.541,"y":0},"t":0,"s":[{"i":[[0,0],[0,0],[-6.53,0],[0,-5.793],[0,0],[2.159,0],[0.59,1.489],[0,0],[1.587,0],[0,-2.16],[-0.81,-1.363],[-0.844,-0.674],[0,0]],"o":[[-0.753,-2.095],[0,-5.793],[6.529,0],[0,0],[0,2.16],[-1.604,0],[0,0],[-0.589,-1.489],[-2.161,0],[0,1.62],[0.54,0.909],[0,0],[0,0]],"v":[[-10.702,5.728],[-11.454,1.506],[0.001,-9],[11.454,1.506],[11.454,1.817],[7.544,5.728],[3.926,3.273],[2.618,0],[-0.997,-2.454],[-4.91,1.457],[-3.657,6.014],[-1.57,8.412],[-0.818,9]],"c":false}]},{"i":{"x":0,"y":1},"o":{"x":0.167,"y":0.233},"t":6.428,"s":[{"i":[[0,0],[0,0],[-1.576,0],[0,-1.474],[0,0],[1.541,0.347],[0.142,0.379],[0,0],[0.383,0],[0,-0.549],[-0.256,-0.431],[-0.768,0.207],[0,0]],"o":[[-1.823,0.497],[0,-1.474],[1.576,0],[0,0],[0,0.549],[-0.378,-0.085],[0,0],[-0.142,-0.379],[-0.521,0],[-0.002,0.353],[0.171,0.288],[0.622,-0.344],[0,0]],"v":[[-0.41,3.841],[-2.717,1.917],[0.047,-0.756],[2.811,1.917],[2.811,1.996],[0.225,3.848],[0.995,2.366],[0.679,1.534],[-0.193,0.909],[-1.338,1.879],[-1.026,3.169],[0.445,3.702],[1.036,3.015]],"c":false}]},{"t":12.857421875,"s":[{"i":[[0,0],[0,0],[-0.736,0],[0,-0.741],[0,0],[0.243,0],[0.066,0.191],[0,0],[0.179,0],[0,-0.276],[-0.162,-0.273],[-0.755,0.357],[0,0]],"o":[[-1.273,-0.008],[0,-0.741],[0.736,0],[0,0],[0,0.276],[-0.181,0],[0,0],[-0.066,-0.191],[-0.243,0],[-0.002,0.139],[0.109,0.182],[0.727,-0.402],[0,0]],"v":[[0.082,3.187],[-1.235,1.986],[0.055,0.642],[1.346,1.986],[1.346,2.026],[0.905,2.527],[0.498,2.212],[0.35,1.794],[-0.057,1.479],[-0.733,1.951],[-0.58,2.686],[0.619,3.071],[1.351,2]],"c":false}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.827450980392,0.890196078431,0.992156862745,1],"ix":3},"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":8.4,"s":[100]},{"t":11.3984375,"s":[0]}],"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":2,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[19.341,28.341],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Inside to dot ","np":2,"cix":2,"bm":0,"ix":3,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.767},"o":{"x":0.541,"y":0},"t":0,"s":[{"i":[[0,0],[1.307,-0.561],[0.894,-0.16],[0.706,0],[0.844,0.193],[0.728,0.334],[0.967,0.901]],"o":[[-1.038,0.967],[-0.817,0.351],[-0.673,0.12],[-0.9,0],[-0.794,-0.182],[-1.203,-0.551],[0,0]],"v":[[8.182,-1.636],[4.642,0.681],[2.07,1.453],[-0.001,1.636],[-2.621,1.341],[-4.909,0.563],[-8.182,-1.636]],"c":false}]},{"i":{"x":0.331,"y":1},"o":{"x":0.167,"y":0.233},"t":6.428,"s":[{"i":[[0,0],[0.313,-0.134],[0.554,-0.317],[0.535,0],[0.203,0.046],[0.175,0.919],[0.232,0.216]],"o":[[-0.249,0.232],[-0.196,0.557],[-0.424,0.245],[-0.216,0],[-1.03,-0.044],[-0.288,-0.132],[0,0]],"v":[[11.468,-8.353],[10.62,-1.716],[9.232,-0.353],[7.057,0.034],[-7.634,-0.037],[-10.453,-1.739],[-11.238,-8.347]],"c":false}]},{"t":15,"s":[{"i":[[0,0],[0,0],[0.446,-0.367],[0.481,0],[0,0],[0,1.104],[0,0]],"o":[[0,0],[0,0.623],[-0.345,0.284],[0,0],[-1.104,0],[0,0],[0,0]],"v":[[11.302,-10.469],[11.302,-2.469],[10.57,-0.923],[9.302,-0.469],[-9.198,-0.469],[-11.198,-2.469],[-11.198,-10.469]],"c":false}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.827450980392,0.890196078431,0.992156862745,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":2,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[19.341,40.614],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Bottom","np":2,"cix":2,"bm":0,"ix":4,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":600,"st":0,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":".grey700","cl":"grey700","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[40,40,0],"ix":2,"l":2},"a":{"a":0,"k":[40.25,40.25,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[22.12,0],[0,-22.08],[-22.08,0],[0,22.08]],"o":[[-22.08,0],[0,22.08],[22.12,0],[0,-22.08]],"v":[[-0.04,-40],[-40,0],[-0.04,40],[40,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.278431385756,0.278431385756,0.278431385756,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[40.25,40.25],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 2","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":600,"st":0,"bm":0}],"markers":[{"tm":30,"cm":"1","dr":0},{"tm":51,"cm":"350ms\r","dr":0},{"tm":69,"cm":"650ms\r","dr":0}]}
\ No newline at end of file
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 8ea2c0c..f344721 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -937,7 +937,8 @@
 
     <!-- Biometric Dialog values -->
     <dimen name="biometric_dialog_face_icon_size">64dp</dimen>
-    <dimen name="biometric_dialog_fingerprint_icon_size">80dp</dimen>
+    <dimen name="biometric_dialog_fingerprint_icon_width">80dp</dimen>
+    <dimen name="biometric_dialog_fingerprint_icon_height">80dp</dimen>
     <dimen name="biometric_dialog_button_negative_max_width">160dp</dimen>
     <dimen name="biometric_dialog_button_positive_max_width">136dp</dimen>
     <dimen name="biometric_dialog_corner_size">4dp</dimen>
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index 6bac7dc..6976e3e 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -2580,8 +2580,8 @@
     }
 
     private boolean isOnlyFaceEnrolled() {
-        return isFaceAuthEnabledForUser(getCurrentUser())
-                && !isUnlockWithFingerprintPossible(getCurrentUser());
+        return isFaceEnrolled()
+                && !getCachedIsUnlockWithFingerprintPossible(sCurrentUser);
     }
 
     private void maybeLogListenerModelData(KeyguardListenModel model) {
@@ -2696,7 +2696,9 @@
         return isUnlockWithFacePossible(userId) || isUnlockWithFingerprintPossible(userId);
     }
 
-    private boolean isUnlockWithFingerprintPossible(int userId) {
+    @VisibleForTesting
+    boolean isUnlockWithFingerprintPossible(int userId) {
+        // TODO (b/242022358), make this rely on onEnrollmentChanged event and update it only once.
         mIsUnlockWithFingerprintPossible.put(userId, mFpm != null && mFpm.isHardwareDetected()
                 && !isFingerprintDisabled(userId) && mFpm.hasEnrolledTemplates(userId));
         return mIsUnlockWithFingerprintPossible.get(userId);
@@ -2718,6 +2720,7 @@
      * If face hardware is available, user has enrolled and enabled auth via setting.
      */
     public boolean isFaceAuthEnabledForUser(int userId) {
+        // TODO (b/242022358), make this rely on onEnrollmentChanged event and update it only once.
         updateFaceEnrolled(userId);
         return mIsFaceEnrolled;
     }
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFaceIconController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFaceIconController.kt
index 55611f7..e60d4e1 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFaceIconController.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFaceIconController.kt
@@ -18,7 +18,7 @@
 import android.content.Context
 import android.graphics.drawable.Drawable
 import android.util.Log
-import android.widget.ImageView
+import com.airbnb.lottie.LottieAnimationView
 import com.android.systemui.R
 import com.android.systemui.biometrics.AuthBiometricView.BiometricState
 import com.android.systemui.biometrics.AuthBiometricView.STATE_AUTHENTICATED
@@ -33,8 +33,8 @@
 
 /** Face only icon animator for BiometricPrompt. */
 class AuthBiometricFaceIconController(
-    context: Context,
-    iconView: ImageView
+        context: Context,
+        iconView: LottieAnimationView
 ) : AuthIconController(context, iconView) {
 
     // false = dark to light, true = light to dark
@@ -76,44 +76,44 @@
         if (newState == STATE_AUTHENTICATING_ANIMATING_IN) {
             showStaticDrawable(R.drawable.face_dialog_pulse_dark_to_light)
             iconView.contentDescription = context.getString(
-                R.string.biometric_dialog_face_icon_description_authenticating
+                    R.string.biometric_dialog_face_icon_description_authenticating
             )
         } else if (newState == STATE_AUTHENTICATING) {
             startPulsing()
             iconView.contentDescription = context.getString(
-                R.string.biometric_dialog_face_icon_description_authenticating
+                    R.string.biometric_dialog_face_icon_description_authenticating
             )
         } else if (oldState == STATE_PENDING_CONFIRMATION && newState == STATE_AUTHENTICATED) {
             animateIconOnce(R.drawable.face_dialog_dark_to_checkmark)
             iconView.contentDescription = context.getString(
-                R.string.biometric_dialog_face_icon_description_confirmed
+                    R.string.biometric_dialog_face_icon_description_confirmed
             )
         } else if (lastStateIsErrorIcon && newState == STATE_IDLE) {
             animateIconOnce(R.drawable.face_dialog_error_to_idle)
             iconView.contentDescription = context.getString(
-                R.string.biometric_dialog_face_icon_description_idle
+                    R.string.biometric_dialog_face_icon_description_idle
             )
         } else if (lastStateIsErrorIcon && newState == STATE_AUTHENTICATED) {
             animateIconOnce(R.drawable.face_dialog_dark_to_checkmark)
             iconView.contentDescription = context.getString(
-                R.string.biometric_dialog_face_icon_description_authenticated
+                    R.string.biometric_dialog_face_icon_description_authenticated
             )
         } else if (newState == STATE_ERROR && oldState != STATE_ERROR) {
             animateIconOnce(R.drawable.face_dialog_dark_to_error)
         } else if (oldState == STATE_AUTHENTICATING && newState == STATE_AUTHENTICATED) {
             animateIconOnce(R.drawable.face_dialog_dark_to_checkmark)
             iconView.contentDescription = context.getString(
-                R.string.biometric_dialog_face_icon_description_authenticated
+                    R.string.biometric_dialog_face_icon_description_authenticated
             )
         } else if (newState == STATE_PENDING_CONFIRMATION) {
             animateIconOnce(R.drawable.face_dialog_wink_from_dark)
             iconView.contentDescription = context.getString(
-                R.string.biometric_dialog_face_icon_description_authenticated
+                    R.string.biometric_dialog_face_icon_description_authenticated
             )
         } else if (newState == STATE_IDLE) {
             showStaticDrawable(R.drawable.face_dialog_idle_static)
             iconView.contentDescription = context.getString(
-                R.string.biometric_dialog_face_icon_description_idle
+                    R.string.biometric_dialog_face_icon_description_idle
             )
         } else {
             Log.w(TAG, "Unhandled state: $newState")
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintAndFaceIconController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintAndFaceIconController.kt
index 3e4e573..40d1eff 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintAndFaceIconController.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintAndFaceIconController.kt
@@ -16,42 +16,43 @@
 
 package com.android.systemui.biometrics
 
+import android.annotation.RawRes
 import android.content.Context
-import android.graphics.drawable.Drawable
-import android.widget.ImageView
+import com.airbnb.lottie.LottieAnimationView
 import com.android.systemui.R
 import com.android.systemui.biometrics.AuthBiometricView.BiometricState
-import com.android.systemui.biometrics.AuthBiometricView.STATE_PENDING_CONFIRMATION
 import com.android.systemui.biometrics.AuthBiometricView.STATE_AUTHENTICATED
 import com.android.systemui.biometrics.AuthBiometricView.STATE_ERROR
 import com.android.systemui.biometrics.AuthBiometricView.STATE_HELP
+import com.android.systemui.biometrics.AuthBiometricView.STATE_PENDING_CONFIRMATION
 
 /** Face/Fingerprint combined icon animator for BiometricPrompt. */
 class AuthBiometricFingerprintAndFaceIconController(
-    context: Context,
-    iconView: ImageView
+        context: Context,
+        iconView: LottieAnimationView
 ) : AuthBiometricFingerprintIconController(context, iconView) {
 
     override val actsAsConfirmButton: Boolean = true
 
     override fun shouldAnimateForTransition(
-        @BiometricState oldState: Int,
-        @BiometricState newState: Int
+            @BiometricState oldState: Int,
+            @BiometricState newState: Int
     ): Boolean = when (newState) {
         STATE_PENDING_CONFIRMATION -> true
         STATE_AUTHENTICATED -> false
         else -> super.shouldAnimateForTransition(oldState, newState)
     }
 
+    @RawRes
     override fun getAnimationForTransition(
-        @BiometricState oldState: Int,
-        @BiometricState newState: Int
-    ): Drawable? = when (newState) {
+            @BiometricState oldState: Int,
+            @BiometricState newState: Int
+    ): Int? = when (newState) {
         STATE_PENDING_CONFIRMATION -> {
             if (oldState == STATE_ERROR || oldState == STATE_HELP) {
-                context.getDrawable(R.drawable.fingerprint_dialog_error_to_unlock)
+                R.raw.fingerprint_dialogue_error_to_unlock_lottie
             } else {
-                context.getDrawable(R.drawable.fingerprint_dialog_fp_to_unlock)
+                R.raw.fingerprint_dialogue_fingerprint_to_unlock_lottie
             }
         }
         STATE_AUTHENTICATED -> null
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintIconController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintIconController.kt
index 606a73a..589ec0e 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintIconController.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintIconController.kt
@@ -16,10 +16,9 @@
 
 package com.android.systemui.biometrics
 
+import android.annotation.RawRes
 import android.content.Context
-import android.graphics.drawable.AnimatedVectorDrawable
-import android.graphics.drawable.Drawable
-import android.widget.ImageView
+import com.airbnb.lottie.LottieAnimationView
 import com.android.systemui.R
 import com.android.systemui.biometrics.AuthBiometricView.BiometricState
 import com.android.systemui.biometrics.AuthBiometricView.STATE_AUTHENTICATED
@@ -32,42 +31,42 @@
 
 /** Fingerprint only icon animator for BiometricPrompt.  */
 open class AuthBiometricFingerprintIconController(
-    context: Context,
-    iconView: ImageView
+        context: Context,
+        iconView: LottieAnimationView
 ) : AuthIconController(context, iconView) {
 
-    var iconLayoutParamsSize = 0
+    var iconLayoutParamSize: Pair<Int, Int> = Pair(1, 1)
         set(value) {
             if (field == value) {
                 return
             }
-            iconView.layoutParams.width = value
-            iconView.layoutParams.height = value
+            iconView.layoutParams.width = value.first
+            iconView.layoutParams.height = value.second
             field = value
         }
 
     init {
-        iconLayoutParamsSize = context.resources.getDimensionPixelSize(
-            R.dimen.biometric_dialog_fingerprint_icon_size
-        )
+        iconLayoutParamSize = Pair(context.resources.getDimensionPixelSize(
+                R.dimen.biometric_dialog_fingerprint_icon_width),
+                context.resources.getDimensionPixelSize(
+                        R.dimen.biometric_dialog_fingerprint_icon_height))
     }
 
     override fun updateIcon(@BiometricState lastState: Int, @BiometricState newState: Int) {
         val icon = getAnimationForTransition(lastState, newState) ?: return
 
-        iconView.setImageDrawable(icon)
+        if (!(lastState == STATE_AUTHENTICATING_ANIMATING_IN && newState == STATE_AUTHENTICATING)) {
+            iconView.setAnimation(icon)
+        }
 
         val iconContentDescription = getIconContentDescription(newState)
         if (iconContentDescription != null) {
             iconView.contentDescription = iconContentDescription
         }
 
-        (icon as? AnimatedVectorDrawable)?.apply {
-            reset()
-            if (shouldAnimateForTransition(lastState, newState)) {
-                forceAnimationOnUI()
-                start()
-            }
+        iconView.frame = 0
+        if (shouldAnimateForTransition(lastState, newState)) {
+            iconView.playAnimation()
         }
     }
 
@@ -86,8 +85,8 @@
     }
 
     protected open fun shouldAnimateForTransition(
-        @BiometricState oldState: Int,
-        @BiometricState newState: Int
+            @BiometricState oldState: Int,
+            @BiometricState newState: Int
     ) = when (newState) {
         STATE_HELP,
         STATE_ERROR -> true
@@ -97,24 +96,27 @@
         else -> false
     }
 
+    @RawRes
     protected open fun getAnimationForTransition(
-        @BiometricState oldState: Int,
-        @BiometricState newState: Int
-    ): Drawable? {
+            @BiometricState oldState: Int,
+            @BiometricState newState: Int
+    ): Int? {
         val id = when (newState) {
             STATE_HELP,
-            STATE_ERROR -> R.drawable.fingerprint_dialog_fp_to_error
+            STATE_ERROR -> {
+                R.raw.fingerprint_dialogue_fingerprint_to_error_lottie
+            }
             STATE_AUTHENTICATING_ANIMATING_IN,
             STATE_AUTHENTICATING -> {
                 if (oldState == STATE_ERROR || oldState == STATE_HELP) {
-                    R.drawable.fingerprint_dialog_error_to_fp
+                    R.raw.fingerprint_dialogue_error_to_fingerprint_lottie
                 } else {
-                    R.drawable.fingerprint_dialog_fp_to_error
+                    R.raw.fingerprint_dialogue_fingerprint_to_error_lottie
                 }
             }
-            STATE_AUTHENTICATED -> R.drawable.fingerprint_dialog_fp_to_error
+            STATE_AUTHENTICATED -> R.raw.fingerprint_dialogue_fingerprint_to_error_lottie
             else -> return null
         }
-        return if (id != null) context.getDrawable(id) else null
+        return if (id != null) return id else null
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintView.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintView.kt
index 24046f0..31baa0f 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintView.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintView.kt
@@ -90,8 +90,9 @@
 
     fun updateOverrideIconLayoutParamsSize() {
         udfpsAdapter?.let {
-            (mIconController as? AuthBiometricFingerprintIconController)?.iconLayoutParamsSize =
-                    it.getSensorDiameter(scaleFactorProvider?.provide() ?: 1.0f)
+            val sensorDiameter = it.getSensorDiameter(scaleFactorProvider?.provide() ?: 1.0f)
+            (mIconController as? AuthBiometricFingerprintIconController)?.iconLayoutParamSize =
+                    Pair(sensorDiameter, sensorDiameter)
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricIconController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricIconController.kt
index ce5e600..15f487b 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricIconController.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricIconController.kt
@@ -22,15 +22,15 @@
 import android.graphics.drawable.AnimatedVectorDrawable
 import android.graphics.drawable.Drawable
 import android.util.Log
-import android.widget.ImageView
+import com.airbnb.lottie.LottieAnimationView
 import com.android.systemui.biometrics.AuthBiometricView.BiometricState
 
 private const val TAG = "AuthIconController"
 
 /** Controller for animating the BiometricPrompt icon/affordance. */
 abstract class AuthIconController(
-    protected val context: Context,
-    protected val iconView: ImageView
+        protected val context: Context,
+        protected val iconView: LottieAnimationView
 ) : Animatable2.AnimationCallback() {
 
     /** If this controller should ignore events and pause. */
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricView.java
index d7ae9ef..e866b9c 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricView.java
@@ -41,14 +41,14 @@
 import android.view.ViewGroup;
 import android.view.accessibility.AccessibilityManager;
 import android.widget.Button;
-import android.widget.ImageView;
 import android.widget.LinearLayout;
 import android.widget.TextView;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.widget.LockPatternUtils;
 import com.android.systemui.R;
-import com.android.systemui.util.LargeScreenUtils;
+
+import com.airbnb.lottie.LottieAnimationView;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -133,7 +133,7 @@
     private TextView mSubtitleView;
     private TextView mDescriptionView;
     private View mIconHolderView;
-    protected ImageView mIconView;
+    protected LottieAnimationView mIconView;
     protected TextView mIndicatorView;
 
     @VisibleForTesting @NonNull AuthIconController mIconController;
@@ -824,25 +824,12 @@
         return new AuthDialog.LayoutParams(width, totalHeight);
     }
 
-    private boolean isLargeDisplay() {
-        return LargeScreenUtils.shouldUseSplitNotificationShade(getResources());
-    }
-
     @Override
     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
         final int width = MeasureSpec.getSize(widthMeasureSpec);
         final int height = MeasureSpec.getSize(heightMeasureSpec);
 
-        final boolean isLargeDisplay = isLargeDisplay();
-
-        final int newWidth;
-        if (isLargeDisplay) {
-            // TODO(b/201811580): Unless we can come up with a one-size-fits-all equation, we may
-            //  want to consider moving this to an overlay.
-            newWidth = 2 * Math.min(width, height) / 3;
-        } else {
-            newWidth = Math.min(width, height);
-        }
+        final int newWidth = Math.min(width, height);
 
         // Use "newWidth" instead, so the landscape dialog width is the same as the portrait
         // width.
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelper.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelper.kt
index 8757904..00b0ff9 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelper.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelper.kt
@@ -68,7 +68,7 @@
                     .addFeature("feature")
             val useAppIcon = !(args.size >= 3 && args[2] == "useAppIcon=false")
             if (useAppIcon) {
-                routeInfo.setPackageName(TEST_PACKAGE_NAME)
+                routeInfo.setClientPackageName(TEST_PACKAGE_NAME)
             }
 
             statusBarManager.updateMediaTapToTransferSenderDisplay(
@@ -134,7 +134,7 @@
                 .addFeature("feature")
             val useAppIcon = !(args.size >= 2 && args[1] == "useAppIcon=false")
             if (useAppIcon) {
-                routeInfo.setPackageName(TEST_PACKAGE_NAME)
+                routeInfo.setClientPackageName(TEST_PACKAGE_NAME)
             }
 
             statusBarManager.updateMediaTapToTransferReceiverDisplay(
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt
index 0f1ae00..196ea22 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt
@@ -143,7 +143,7 @@
         super.updateChipView(newChipInfo, currentChipView)
         setIcon(
                 currentChipView,
-                newChipInfo.routeInfo.packageName,
+                newChipInfo.routeInfo.clientPackageName,
                 newChipInfo.appIconDrawableOverride,
                 newChipInfo.appNameOverride
         )
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSender.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSender.kt
index b94b8bf..92d9ea8 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSender.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSender.kt
@@ -122,7 +122,7 @@
         val chipState = newChipInfo.state
 
         // App icon
-        val iconName = setIcon(currentChipView, newChipInfo.routeInfo.packageName)
+        val iconName = setIcon(currentChipView, newChipInfo.routeInfo.clientPackageName)
 
         // Text
         val otherDeviceName = newChipInfo.routeInfo.name.toString()
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/TargetSdkResolver.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/TargetSdkResolver.kt
index c71eade..0c49713 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/TargetSdkResolver.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/TargetSdkResolver.kt
@@ -16,7 +16,9 @@
 
 package com.android.systemui.statusbar.notification.collection
 
+import android.app.Notification
 import android.content.Context
+import android.content.pm.ApplicationInfo
 import android.content.pm.PackageManager
 import android.service.notification.StatusBarNotification
 import android.util.Log
@@ -39,17 +41,28 @@
     }
 
     private fun resolveNotificationSdk(sbn: StatusBarNotification): Int {
+        val applicationInfo = getApplicationInfoFromExtras(sbn.notification)
+                ?: getApplicationInfoFromPackageManager(sbn)
+
+        return applicationInfo?.targetSdkVersion ?: 0
+    }
+
+    private fun getApplicationInfoFromExtras(notification: Notification): ApplicationInfo? =
+            notification.extras.getParcelable(
+                    Notification.EXTRA_BUILDER_APPLICATION_INFO,
+                    ApplicationInfo::class.java
+            )
+
+    private fun getApplicationInfoFromPackageManager(sbn: StatusBarNotification): ApplicationInfo? {
         val pmUser = CentralSurfaces.getPackageManagerForUser(context, sbn.user.identifier)
-        var targetSdk = 0
-        // Extract target SDK version.
-        try {
-            val info = pmUser.getApplicationInfo(sbn.packageName, 0)
-            targetSdk = info.targetSdkVersion
+
+        return try {
+            pmUser.getApplicationInfo(sbn.packageName, 0)
         } catch (ex: PackageManager.NameNotFoundException) {
             Log.e(TAG, "Failed looking up ApplicationInfo for " + sbn.packageName, ex)
+            null
         }
-        return targetSdk
     }
 
     private val TAG = "TargetSdkResolver"
-}
\ No newline at end of file
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/FeedbackInfo.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/FeedbackInfo.java
index 2b782b6..3f4fd50 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/FeedbackInfo.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/FeedbackInfo.java
@@ -165,7 +165,7 @@
     }
 
     private void positiveFeedback(View v) {
-        mGutsContainer.closeControls(v, false);
+        mGutsContainer.closeControls(v, /* save= */ false);
         handleFeedback(true);
     }
 
@@ -176,7 +176,7 @@
             menuItem = mMenuRowPlugin.getLongpressMenuItem(mContext);
         }
 
-        mGutsContainer.closeControls(v, false);
+        mGutsContainer.closeControls(v, /* save= */ false);
         mNotificationGutsManager.openGuts(mExpandableNotificationRow, 0, 0, menuItem);
         handleFeedback(false);
     }
@@ -203,7 +203,7 @@
     }
 
     private void closeControls(View v) {
-        mGutsContainer.closeControls(v, false);
+        mGutsContainer.closeControls(v, /* save= */ false);
     }
 
     @Override
@@ -232,7 +232,7 @@
     }
 
     @Override
-    public boolean shouldBeSaved() {
+    public boolean shouldBeSavedOnClose() {
         return false;
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfo.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfo.java
index 7120fe5..0ce9656 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfo.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfo.java
@@ -157,7 +157,7 @@
             mShadeController.animateCollapsePanels();
             mPeopleSpaceWidgetManager.requestPinAppWidget(mShortcutInfo, new Bundle());
         }
-        mGutsContainer.closeControls(v, true);
+        mGutsContainer.closeControls(v, /* save= */ true);
     };
 
     public NotificationConversationInfo(Context context, AttributeSet attrs) {
@@ -186,7 +186,6 @@
     }
 
     public void bindNotification(
-            @Action int selectedAction,
             ShortcutManager shortcutManager,
             PackageManager pm,
             PeopleSpaceWidgetManager peopleSpaceWidgetManager,
@@ -205,8 +204,6 @@
             OnConversationSettingsClickListener onConversationSettingsClickListener,
             Optional<BubblesManager> bubblesManagerOptional,
             ShadeController shadeController) {
-        mPressedApply = false;
-        mSelectedAction = selectedAction;
         mINotificationManager = iNotificationManager;
         mPeopleSpaceWidgetManager = peopleSpaceWidgetManager;
         mOnUserInteractionCallback = onUserInteractionCallback;
@@ -417,9 +414,7 @@
     }
 
     @Override
-    public void onFinishedClosing() {
-        mSelectedAction = -1;
-    }
+    public void onFinishedClosing() { }
 
     @Override
     public boolean needsFalsingProtection() {
@@ -564,7 +559,7 @@
     }
 
     @Override
-    public boolean shouldBeSaved() {
+    public boolean shouldBeSavedOnClose() {
         return mPressedApply;
     }
 
@@ -578,6 +573,12 @@
         if (save && mSelectedAction > -1) {
             updateChannel();
         }
+
+        // Clear the selected importance when closing, so when when we open again,
+        // we starts from a clean state.
+        mSelectedAction = -1;
+        mPressedApply = false;
+
         return false;
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGuts.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGuts.java
index fc296e1..93f0812 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGuts.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGuts.java
@@ -76,7 +76,7 @@
 
                     switch (action) {
                         case AccessibilityNodeInfo.ACTION_LONG_CLICK:
-                            closeControls(host, false);
+                            closeControls(host, /* save= */ false);
                             return true;
                     }
 
@@ -123,7 +123,7 @@
         /**
          * Return whether something changed and needs to be saved, possibly requiring a bouncer.
          */
-        boolean shouldBeSaved();
+        boolean shouldBeSavedOnClose();
 
         /**
          * Called when the guts view has finished its close animation.
@@ -259,7 +259,7 @@
         if (mGutsContent != null) {
             if ((mGutsContent.isLeavebehind() && leavebehinds)
                     || (!mGutsContent.isLeavebehind() && controls)) {
-                closeControls(x, y, mGutsContent.shouldBeSaved(), force);
+                closeControls(x, y, mGutsContent.shouldBeSavedOnClose(), force);
             }
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java
index 7b0b0ce..ea12b82 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java
@@ -463,7 +463,6 @@
                         R.dimen.notification_guts_conversation_icon_size));
 
         notificationInfoView.bindNotification(
-                notificationInfoView.getSelectedAction(),
                 mShortcutManager,
                 pmUser,
                 mPeopleSpaceWidgetManager,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInfo.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInfo.java
index 8b01a47..ea0060a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInfo.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInfo.java
@@ -158,7 +158,7 @@
     // used by standard ui
     private OnClickListener mOnDismissSettings = v -> {
         mPressedApply = true;
-        mGutsContainer.closeControls(v, true);
+        mGutsContainer.closeControls(v, /* save= */ true);
     };
 
     public NotificationInfo(Context context, AttributeSet attrs) {
@@ -541,10 +541,6 @@
 
     @Override
     public void onFinishedClosing() {
-        if (mChosenImportance != null) {
-            mStartingChannelImportance = mChosenImportance;
-        }
-
         bindInlineControls();
 
         logUiEvent(NotificationControlsEvent.NOTIFICATION_CONTROLS_CLOSE);
@@ -604,7 +600,7 @@
     }
 
     @Override
-    public boolean shouldBeSaved() {
+    public boolean shouldBeSavedOnClose() {
         return mPressedApply;
     }
 
@@ -627,6 +623,12 @@
         if (save) {
             saveImportance();
         }
+
+        // Clear the selected importance when closing, so when when we open again,
+        // we starts from a clean state.
+        mChosenImportance = null;
+        mPressedApply = false;
+
         return false;
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationSnooze.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationSnooze.java
index 512b049..adbfa75 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationSnooze.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationSnooze.java
@@ -384,7 +384,7 @@
     private void undoSnooze(View v) {
         mSelectedOption = null;
         showSnoozeOptions(false);
-        mGutsContainer.closeControls(v, false);
+        mGutsContainer.closeControls(v, /* save= */ false);
     }
 
     @Override
@@ -433,7 +433,7 @@
     }
 
     @Override
-    public boolean shouldBeSaved() {
+    public boolean shouldBeSavedOnClose() {
         return true;
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/PartialConversationInfo.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/PartialConversationInfo.java
index 186ffa6..ac97e77 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/PartialConversationInfo.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/PartialConversationInfo.java
@@ -16,22 +16,13 @@
 
 package com.android.systemui.statusbar.notification.row;
 
-import static android.app.Notification.EXTRA_IS_GROUP_CONVERSATION;
-
-import static java.lang.annotation.RetentionPolicy.SOURCE;
-
-import android.annotation.IntDef;
 import android.app.INotificationManager;
-import android.app.Notification;
 import android.app.NotificationChannel;
 import android.app.NotificationChannelGroup;
 import android.content.Context;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
 import android.graphics.drawable.Drawable;
-import android.graphics.drawable.Icon;
-import android.os.Bundle;
-import android.os.Parcelable;
 import android.os.RemoteException;
 import android.service.notification.StatusBarNotification;
 import android.text.TextUtils;
@@ -46,8 +37,6 @@
 import com.android.systemui.R;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 
-import java.lang.annotation.Retention;
-import java.util.List;
 import java.util.Set;
 
 /**
@@ -71,8 +60,6 @@
     private Set<NotificationChannel> mUniqueChannelsInRow;
     private Drawable mPkgIcon;
 
-    private @Action int mSelectedAction = -1;
-    private boolean mPressedApply;
     private boolean mPresentingChannelEditorDialog = false;
 
     private NotificationInfo.OnSettingsClickListener mOnSettingsClickListener;
@@ -82,14 +69,8 @@
     @VisibleForTesting
     boolean mSkipPost = false;
 
-    @Retention(SOURCE)
-    @IntDef({ACTION_SETTINGS})
-    private @interface Action {}
-    static final int ACTION_SETTINGS = 5;
-
     private OnClickListener mOnDone = v -> {
-        mPressedApply = true;
-        mGutsContainer.closeControls(v, true);
+        mGutsContainer.closeControls(v, /* save= */ false);
     };
 
     public PartialConversationInfo(Context context, AttributeSet attrs) {
@@ -107,7 +88,6 @@
             NotificationInfo.OnSettingsClickListener onSettingsClick,
             boolean isDeviceProvisioned,
             boolean isNonBlockable) {
-        mSelectedAction = -1;
         mINotificationManager = iNotificationManager;
         mPackageName = pkg;
         mSbn = entry.getSbn();
@@ -286,8 +266,8 @@
     }
 
     @Override
-    public boolean shouldBeSaved() {
-        return mPressedApply;
+    public boolean shouldBeSavedOnClose() {
+        return false;
     }
 
     @Override
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
index c677371..c281965 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
@@ -581,6 +581,7 @@
     @Test
     public void testTriesToAuthenticate_whenBouncer() {
         fingerprintIsNotEnrolled();
+        faceAuthEnabled();
         setKeyguardBouncerVisibility(true);
 
         verify(mFaceManager).authenticate(any(), any(), any(), any(), anyInt(), anyBoolean());
@@ -1218,6 +1219,7 @@
     public void testShouldListenForFace_whenFaceIsAlreadyAuthenticated_returnsFalse()
             throws RemoteException {
         // Face auth should run when the following is true.
+        faceAuthEnabled();
         bouncerFullyVisibleAndNotGoingToSleep();
         fingerprintIsNotEnrolled();
         keyguardNotGoingAway();
@@ -1284,6 +1286,7 @@
     public void testShouldListenForFace_whenBiometricsDisabledForUser_returnsFalse()
             throws RemoteException {
         // Preconditions for face auth to run
+        faceAuthEnabled();
         keyguardNotGoingAway();
         bouncerFullyVisibleAndNotGoingToSleep();
         fingerprintIsNotEnrolled();
@@ -1307,6 +1310,7 @@
     public void testShouldListenForFace_whenUserCurrentlySwitching_returnsFalse()
             throws RemoteException {
         // Preconditions for face auth to run
+        faceAuthEnabled();
         keyguardNotGoingAway();
         bouncerFullyVisibleAndNotGoingToSleep();
         fingerprintIsNotEnrolled();
@@ -1329,6 +1333,7 @@
     public void testShouldListenForFace_whenSecureCameraLaunched_returnsFalse()
             throws RemoteException {
         // Preconditions for face auth to run
+        faceAuthEnabled();
         keyguardNotGoingAway();
         bouncerFullyVisibleAndNotGoingToSleep();
         fingerprintIsNotEnrolled();
@@ -1374,6 +1379,7 @@
     public void testShouldListenForFace_whenBouncerShowingAndDeviceIsAwake_returnsTrue()
             throws RemoteException {
         // Preconditions for face auth to run
+        faceAuthEnabled();
         keyguardNotGoingAway();
         currentUserIsPrimary();
         currentUserDoesNotHaveTrust();
@@ -1539,8 +1545,19 @@
         assertThat(mKeyguardUpdateMonitor.shouldListenForFingerprint(anyBoolean())).isEqualTo(true);
     }
 
+    private void faceAuthEnabled() {
+        // this ensures KeyguardUpdateMonitor updates the cached mIsFaceEnrolled flag using the
+        // face manager mock wire-up in setup()
+        mKeyguardUpdateMonitor.isFaceAuthEnabledForUser(mCurrentUserId);
+    }
+
     private void fingerprintIsNotEnrolled() {
         when(mFingerprintManager.hasEnrolledTemplates(mCurrentUserId)).thenReturn(false);
+        // This updates the cached fingerprint state.
+        // There is no straightforward API to update the fingerprint state.
+        // It currently works updates after enrollment changes because something else invokes
+        // startListeningForFingerprint(), which internally calls this method.
+        mKeyguardUpdateMonitor.isUnlockWithFingerprintPossible(mCurrentUserId);
     }
 
     private void statusBarShadeIsNotLocked() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiverTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiverTest.kt
index dbc5f7c..171d893 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiverTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiverTest.kt
@@ -241,5 +241,5 @@
 
 private val routeInfo = MediaRoute2Info.Builder("id", "Test route name")
     .addFeature("feature")
-    .setPackageName(PACKAGE_NAME)
+    .setClientPackageName(PACKAGE_NAME)
     .build()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSenderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSenderTest.kt
index cd8ee73..1061e3c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSenderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSenderTest.kt
@@ -686,5 +686,5 @@
 
 private val routeInfo = MediaRoute2Info.Builder("id", OTHER_DEVICE_NAME)
     .addFeature("feature")
-    .setPackageName(PACKAGE_NAME)
+    .setClientPackageName(PACKAGE_NAME)
     .build()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/TargetSdkResolverTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/TargetSdkResolverTest.kt
new file mode 100644
index 0000000..8275c0c
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/TargetSdkResolverTest.kt
@@ -0,0 +1,132 @@
+/*
+ *  Copyright (C) 2022 The Android Open Source Project
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+
+package com.android.systemui.statusbar.notification.collection
+
+import android.app.Notification
+import android.app.NotificationManager
+import android.content.pm.ApplicationInfo
+import android.content.pm.PackageManager
+import android.os.Bundle
+import android.os.UserHandle
+import android.service.notification.NotificationListenerService.Ranking
+import android.service.notification.StatusBarNotification
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection
+import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.withArgCaptor
+import junit.framework.Assert.assertEquals
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mockito.anyInt
+import org.mockito.Mockito.anyString
+import org.mockito.Mockito.eq
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.verifyZeroInteractions
+import org.mockito.Mockito.`when` as whenever
+
+private const val SDK_VERSION = 33
+private const val PACKAGE = "pkg"
+private const val USER_ID = -1
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class TargetSdkResolverTest : SysuiTestCase() {
+    private val packageManager: PackageManager = mock()
+    private val applicationInfo = ApplicationInfo().apply { targetSdkVersion = SDK_VERSION }
+
+    private lateinit var targetSdkResolver: TargetSdkResolver
+    private lateinit var notifListener: NotifCollectionListener
+
+    @Before
+    fun setUp() {
+        targetSdkResolver = TargetSdkResolver(mContext)
+        mContext.setMockPackageManager(packageManager)
+
+        val notifCollection: CommonNotifCollection = mock()
+        targetSdkResolver.initialize(notifCollection)
+        notifListener = withArgCaptor {
+            verify(notifCollection).addCollectionListener(capture())
+        }
+    }
+
+    @Test
+    fun resolveFromNotificationExtras() {
+        val extras = Bundle().apply {
+            putParcelable(Notification.EXTRA_BUILDER_APPLICATION_INFO, applicationInfo)
+        }
+        val notification = Notification().apply { this.extras = extras }
+        val sbn = createSbn(notification)
+        val entry = createNotificationEntry(sbn)
+
+        notifListener.onEntryBind(entry, sbn)
+
+        assertEquals(SDK_VERSION, entry.targetSdk)
+        verifyZeroInteractions(packageManager)
+    }
+
+    @Test
+    fun resolveFromPackageManager() {
+        val sbn = createSbn(Notification())
+        val entry = createNotificationEntry(sbn)
+        whenever(packageManager.getApplicationInfo(anyString(), anyInt()))
+                .thenReturn(applicationInfo)
+
+        notifListener.onEntryBind(entry, sbn)
+
+        assertEquals(SDK_VERSION, entry.targetSdk)
+        verify(packageManager).getApplicationInfo(eq(PACKAGE), anyInt())
+    }
+
+    @Test
+    fun resolveFromPackageManager_andPackageManagerCrashes() {
+        val sbn = createSbn(Notification())
+        val entry = createNotificationEntry(sbn)
+        whenever(packageManager.getApplicationInfo(anyString(), anyInt()))
+                .thenThrow(PackageManager.NameNotFoundException())
+
+        notifListener.onEntryBind(entry, sbn)
+
+        assertEquals(0, entry.targetSdk)
+        verify(packageManager).getApplicationInfo(eq(PACKAGE), anyInt())
+    }
+
+    private fun createSbn(notification: Notification) = StatusBarNotification(
+            PACKAGE, "opPkg", 0, "tag", 0, 0,
+            notification, UserHandle(USER_ID), "", 0
+    )
+
+    private fun createNotificationEntry(sbn: StatusBarNotification) =
+            NotificationEntry(sbn, createRanking(sbn.key), 0)
+
+    private fun createRanking(key: String) = Ranking().apply {
+        populate(
+                key,
+                0,
+                false,
+                0,
+                0,
+                NotificationManager.IMPORTANCE_DEFAULT,
+                null, null,
+                null, null, null, true, 0, false, -1, false, null, null, false, false,
+                false, null, 0, false)
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfoTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfoTest.java
index 381d72f..90adabf 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfoTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfoTest.java
@@ -236,7 +236,6 @@
     @Test
     public void testBindNotification_SetsShortcutIcon() {
         mNotificationInfo.bindNotification(
-                -1,
                 mShortcutManager,
                 mMockPackageManager,
                 mPeopleSpaceWidgetManager,
@@ -261,7 +260,6 @@
     public void testBindNotification_SetsTextApplicationName() {
         when(mMockPackageManager.getApplicationLabel(any())).thenReturn("App Name");
         mNotificationInfo.bindNotification(
-                -1,
                 mShortcutManager,
                 mMockPackageManager,
                 mPeopleSpaceWidgetManager,
@@ -314,7 +312,6 @@
         mConversationChannel.setGroup(group.getId());
 
         mNotificationInfo.bindNotification(
-                -1,
                 mShortcutManager,
                 mMockPackageManager,
                 mPeopleSpaceWidgetManager,
@@ -340,7 +337,6 @@
     @Test
     public void testBindNotification_GroupNameHiddenIfNoGroup() {
         mNotificationInfo.bindNotification(
-                -1,
                 mShortcutManager,
                 mMockPackageManager,
                 mPeopleSpaceWidgetManager,
@@ -365,7 +361,6 @@
     @Test
     public void testBindNotification_noDelegate() {
         mNotificationInfo.bindNotification(
-                -1,
                 mShortcutManager,
                 mMockPackageManager,
                 mPeopleSpaceWidgetManager,
@@ -401,7 +396,6 @@
                 .setShortcutInfo(mShortcutInfo)
                 .build();
         mNotificationInfo.bindNotification(
-                -1,
                 mShortcutManager,
                 mMockPackageManager,
                 mPeopleSpaceWidgetManager,
@@ -427,7 +421,6 @@
     public void testBindNotification_SetsOnClickListenerForSettings() {
         final CountDownLatch latch = new CountDownLatch(1);
         mNotificationInfo.bindNotification(
-                -1,
                 mShortcutManager,
                 mMockPackageManager,
                 mPeopleSpaceWidgetManager,
@@ -457,7 +450,6 @@
     @Test
     public void testBindNotification_SettingsButtonInvisibleWhenNoClickListener() {
         mNotificationInfo.bindNotification(
-                -1,
                 mShortcutManager,
                 mMockPackageManager,
                 mPeopleSpaceWidgetManager,
@@ -482,7 +474,6 @@
     public void testBindNotification_SettingsButtonInvisibleWhenDeviceUnprovisioned() {
         final CountDownLatch latch = new CountDownLatch(1);
         mNotificationInfo.bindNotification(
-                -1,
                 mShortcutManager,
                 mMockPackageManager,
                 mPeopleSpaceWidgetManager,
@@ -511,7 +502,6 @@
         mConversationChannel.setImportance(IMPORTANCE_LOW);
         mConversationChannel.setImportantConversation(true);
         mNotificationInfo.bindNotification(
-                -1,
                 mShortcutManager,
                 mMockPackageManager,
                 mPeopleSpaceWidgetManager,
@@ -540,7 +530,6 @@
         mConversationChannel.setImportantConversation(false);
         mConversationChannel.setAllowBubbles(true);
         mNotificationInfo.bindNotification(
-                -1,
                 mShortcutManager,
                 mMockPackageManager,
                 mPeopleSpaceWidgetManager,
@@ -572,7 +561,6 @@
         mConversationChannel.setImportantConversation(false);
         mConversationChannel.setAllowBubbles(true);
         mNotificationInfo.bindNotification(
-                -1,
                 mShortcutManager,
                 mMockPackageManager,
                 mPeopleSpaceWidgetManager,
@@ -610,7 +598,6 @@
         mConversationChannel.setImportantConversation(false);
         mConversationChannel.setAllowBubbles(false);
         mNotificationInfo.bindNotification(
-                -1,
                 mShortcutManager,
                 mMockPackageManager,
                 mPeopleSpaceWidgetManager,
@@ -639,7 +626,6 @@
         mConversationChannel.setImportantConversation(false);
         mConversationChannel.setAllowBubbles(false);
         mNotificationInfo.bindNotification(
-                -1,
                 mShortcutManager,
                 mMockPackageManager,
                 mPeopleSpaceWidgetManager,
@@ -675,7 +661,6 @@
         mConversationChannel.setImportantConversation(false);
         mConversationChannel.setAllowBubbles(true);
         mNotificationInfo.bindNotification(
-                -1,
                 mShortcutManager,
                 mMockPackageManager,
                 mPeopleSpaceWidgetManager,
@@ -704,7 +689,6 @@
         mConversationChannel.setImportantConversation(false);
 
         mNotificationInfo.bindNotification(
-                -1,
                 mShortcutManager,
                 mMockPackageManager,
                 mPeopleSpaceWidgetManager,
@@ -735,7 +719,7 @@
                 .isEqualTo(GONE);
 
         // no changes until hit done
-        assertFalse(mNotificationInfo.shouldBeSaved());
+        assertFalse(mNotificationInfo.shouldBeSavedOnClose());
         verify(mMockINotificationManager, never()).updateNotificationChannelForPackage(
                 anyString(), anyInt(), any());
         assertFalse(mConversationChannel.isImportantConversation());
@@ -749,7 +733,6 @@
         mConversationChannel.setImportance(IMPORTANCE_LOW);
         mConversationChannel.setImportantConversation(false);
         mNotificationInfo.bindNotification(
-                -1,
                 mShortcutManager,
                 mMockPackageManager,
                 mPeopleSpaceWidgetManager,
@@ -779,7 +762,7 @@
                 .isEqualTo(GONE);
 
         // no changes until hit done
-        assertFalse(mNotificationInfo.shouldBeSaved());
+        assertFalse(mNotificationInfo.shouldBeSavedOnClose());
         verify(mMockINotificationManager, never()).updateNotificationChannelForPackage(
                 anyString(), anyInt(), any());
         assertFalse(mConversationChannel.isImportantConversation());
@@ -793,7 +776,6 @@
         mConversationChannel.setImportantConversation(false);
 
         mNotificationInfo.bindNotification(
-                -1,
                 mShortcutManager,
                 mMockPackageManager,
                 mPeopleSpaceWidgetManager,
@@ -825,7 +807,7 @@
                 .isEqualTo(VISIBLE);
 
         // no changes until save
-        assertFalse(mNotificationInfo.shouldBeSaved());
+        assertFalse(mNotificationInfo.shouldBeSavedOnClose());
         verify(mMockINotificationManager, never()).updateNotificationChannelForPackage(
                 anyString(), anyInt(), any());
         assertEquals(IMPORTANCE_DEFAULT, mConversationChannel.getImportance());
@@ -838,7 +820,6 @@
         mConversationChannel.setImportantConversation(false);
 
         mNotificationInfo.bindNotification(
-                -1,
                 mShortcutManager,
                 mMockPackageManager,
                 mPeopleSpaceWidgetManager,
@@ -868,6 +849,7 @@
         assertTrue(captor.getValue().isImportantConversation());
         assertTrue(captor.getValue().canBubble());
         assertEquals(IMPORTANCE_DEFAULT, captor.getValue().getImportance());
+        assertFalse(mNotificationInfo.shouldBeSavedOnClose());
     }
 
     @Test
@@ -876,7 +858,6 @@
         mConversationChannel.setImportance(9);
 
         mNotificationInfo.bindNotification(
-                -1,
                 mShortcutManager,
                 mMockPackageManager,
                 mPeopleSpaceWidgetManager,
@@ -913,7 +894,6 @@
         mConversationChannel.setImportantConversation(true);
 
         mNotificationInfo.bindNotification(
-                -1,
                 mShortcutManager,
                 mMockPackageManager,
                 mPeopleSpaceWidgetManager,
@@ -954,7 +934,6 @@
 
         // WHEN we indicate no selected action
         mNotificationInfo.bindNotification(
-                -1, // no action selected by default
                 mShortcutManager,
                 mMockPackageManager,
                 mPeopleSpaceWidgetManager,
@@ -984,8 +963,8 @@
         mConversationChannel.setImportantConversation(false);
 
         // WHEN we indicate the selected action should be "Favorite"
+        mNotificationInfo.setSelectedAction(NotificationConversationInfo.ACTION_FAVORITE);
         mNotificationInfo.bindNotification(
-                NotificationConversationInfo.ACTION_FAVORITE, // "Favorite" selected by default
                 mShortcutManager,
                 mMockPackageManager,
                 mPeopleSpaceWidgetManager,
@@ -1015,7 +994,6 @@
         mConversationChannel.setOriginalImportance(IMPORTANCE_HIGH);
         mConversationChannel.setImportantConversation(true);
         mNotificationInfo.bindNotification(
-                -1,
                 mShortcutManager,
                 mMockPackageManager,
                 mPeopleSpaceWidgetManager,
@@ -1044,6 +1022,7 @@
         assertFalse(captor.getValue().isImportantConversation());
         assertFalse(captor.getValue().canBubble());
         assertEquals(IMPORTANCE_HIGH, captor.getValue().getImportance());
+        assertFalse(mNotificationInfo.shouldBeSavedOnClose());
     }
 
     @Test
@@ -1052,7 +1031,6 @@
         mConversationChannel.setOriginalImportance(IMPORTANCE_HIGH);
         mConversationChannel.setImportantConversation(false);
         mNotificationInfo.bindNotification(
-                -1,
                 mShortcutManager,
                 mMockPackageManager,
                 mPeopleSpaceWidgetManager,
@@ -1089,7 +1067,6 @@
         mConversationChannel.setOriginalImportance(IMPORTANCE_HIGH);
 
         mNotificationInfo.bindNotification(
-                -1,
                 mShortcutManager,
                 mMockPackageManager,
                 mPeopleSpaceWidgetManager,
@@ -1125,7 +1102,6 @@
         mConversationChannel.setAllowBubbles(true);
 
         mNotificationInfo.bindNotification(
-                -1,
                 mShortcutManager,
                 mMockPackageManager,
                 mPeopleSpaceWidgetManager,
@@ -1155,12 +1131,46 @@
         assertFalse(captor.getValue().isImportantConversation());
         assertFalse(captor.getValue().canBubble());
         assertEquals(IMPORTANCE_LOW, captor.getValue().getImportance());
+        assertFalse(mNotificationInfo.shouldBeSavedOnClose());
+    }
+
+    @Test
+    public void testSilence_closeGutsThenTryToSave() {
+        mConversationChannel.setImportance(IMPORTANCE_DEFAULT);
+        mConversationChannel.setImportantConversation(true);
+        mConversationChannel.setAllowBubbles(true);
+
+        mNotificationInfo.bindNotification(
+                mShortcutManager,
+                mMockPackageManager,
+                mPeopleSpaceWidgetManager,
+                mMockINotificationManager,
+                mOnUserInteractionCallback,
+                TEST_PACKAGE_NAME,
+                mNotificationChannel,
+                mEntry,
+                mBubbleMetadata,
+                null,
+                mIconFactory,
+                mContext,
+                true,
+                mTestHandler,
+                mTestHandler, null, Optional.of(mBubblesManager),
+                mShadeController);
+
+        mNotificationInfo.findViewById(R.id.silence).performClick();
+        mNotificationInfo.handleCloseControls(false, false);
+        mNotificationInfo.findViewById(R.id.done).performClick();
+
+        mTestableLooper.processAllMessages();
+
+        assertEquals(IMPORTANCE_DEFAULT, mConversationChannel.getImportance());
+        assertFalse(mNotificationInfo.shouldBeSavedOnClose());
     }
 
     @Test
     public void testBindNotification_createsNewChannel() throws Exception {
         mNotificationInfo.bindNotification(
-                -1,
                 mShortcutManager,
                 mMockPackageManager,
                 mPeopleSpaceWidgetManager,
@@ -1186,7 +1196,6 @@
     public void testBindNotification_doesNotCreateNewChannelIfExists() throws Exception {
         mNotificationChannel.setConversationId("", CONVERSATION_ID);
         mNotificationInfo.bindNotification(
-                -1,
                 mShortcutManager,
                 mMockPackageManager,
                 mPeopleSpaceWidgetManager,
@@ -1213,7 +1222,6 @@
         //WHEN channel is default importance
         mNotificationChannel.setImportantConversation(false);
         mNotificationInfo.bindNotification(
-                -1,
                 mShortcutManager,
                 mMockPackageManager,
                 mPeopleSpaceWidgetManager,
@@ -1244,7 +1252,6 @@
     @Test
     public void testSelectDefaultDoesNotRequestPinPeopleTile() {
         mNotificationInfo.bindNotification(
-                -1,
                 mShortcutManager,
                 mMockPackageManager,
                 mPeopleSpaceWidgetManager,
@@ -1279,7 +1286,6 @@
         mConversationChannel.setImportantConversation(true);
 
         mNotificationInfo.bindNotification(
-                -1,
                 mShortcutManager,
                 mMockPackageManager,
                 mPeopleSpaceWidgetManager,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsTest.kt
new file mode 100644
index 0000000..e696c87
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsTest.kt
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.row
+
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper.RunWithLooper
+import android.testing.ViewUtils
+import android.view.LayoutInflater
+import android.view.View
+import androidx.test.filters.SmallTest
+import com.android.systemui.R
+import com.android.systemui.SysuiTestCase
+import org.junit.After
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when` as whenever
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@RunWithLooper
+class NotificationGutsTest : SysuiTestCase() {
+
+    private lateinit var guts: NotificationGuts
+    private lateinit var gutsContentView: View
+
+    @Mock
+    private lateinit var gutsContent: NotificationGuts.GutsContent
+
+    @Mock
+    private lateinit var gutsClosedListener: NotificationGuts.OnGutsClosedListener
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+
+        val layoutInflater = LayoutInflater.from(mContext)
+        guts = layoutInflater.inflate(R.layout.notification_guts, null) as NotificationGuts
+        gutsContentView = View(mContext)
+
+        whenever(gutsContent.contentView).thenReturn(gutsContentView)
+
+        ViewUtils.attachView(guts)
+    }
+
+    @After
+    fun tearDown() {
+        ViewUtils.detachView(guts)
+    }
+
+    @Test
+    fun setGutsContent() {
+        guts.gutsContent = gutsContent
+
+        verify(gutsContent).setGutsParent(guts)
+    }
+
+    @Test
+    fun openControls() {
+        guts.gutsContent = gutsContent
+
+        guts.openControls(true, 0, 0, false, null)
+    }
+
+    @Test
+    fun closeControlsWithSave() {
+        guts.gutsContent = gutsContent
+        guts.setClosedListener(gutsClosedListener)
+
+        guts.closeControls(gutsContentView, true)
+
+        verify(gutsContent).handleCloseControls(true, false)
+        verify(gutsClosedListener).onGutsClosed(guts)
+    }
+
+    @Test
+    fun closeControlsWithoutSave() {
+        guts.gutsContent = gutsContent
+        guts.setClosedListener(gutsClosedListener)
+
+        guts.closeControls(gutsContentView, false)
+
+        verify(gutsContent).handleCloseControls(false, false)
+        verify(gutsClosedListener).onGutsClosed(guts)
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationInfoTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationInfoTest.java
index b1f1075..80a81a5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationInfoTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationInfoTest.java
@@ -50,6 +50,7 @@
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
 import android.graphics.drawable.Drawable;
+import android.os.RemoteException;
 import android.os.UserHandle;
 import android.service.notification.StatusBarNotification;
 import android.telecom.TelecomManager;
@@ -1090,6 +1091,7 @@
                 mUiEventLogger.eventId(0));
         assertEquals(NotificationControlsEvent.NOTIFICATION_CONTROLS_SAVE_IMPORTANCE.getId(),
                 mUiEventLogger.eventId(1));
+        assertFalse(mNotificationInfo.shouldBeSavedOnClose());
     }
 
     @Test
@@ -1124,6 +1126,7 @@
         assertTrue((updated.getValue().getUserLockedFields()
                 & USER_LOCKED_IMPORTANCE) != 0);
         assertEquals(IMPORTANCE_DEFAULT, updated.getValue().getImportance());
+        assertFalse(mNotificationInfo.shouldBeSavedOnClose());
     }
 
     @Test
@@ -1156,6 +1159,7 @@
         verify(mMockINotificationManager, times(1)).unlockNotificationChannel(
                 anyString(), eq(TEST_UID), any());
         assertEquals(IMPORTANCE_DEFAULT, mNotificationChannel.getImportance());
+        assertFalse(mNotificationInfo.shouldBeSavedOnClose());
     }
 
     @Test
@@ -1191,6 +1195,7 @@
         assertTrue((updated.getValue().getUserLockedFields()
                 & USER_LOCKED_IMPORTANCE) != 0);
         assertEquals(IMPORTANCE_LOW, updated.getValue().getImportance());
+        assertFalse(mNotificationInfo.shouldBeSavedOnClose());
     }
 
     @Test
@@ -1229,6 +1234,37 @@
                 anyString(), eq(TEST_UID), updated.capture());
         assertTrue((updated.getValue().getUserLockedFields() & USER_LOCKED_IMPORTANCE) != 0);
         assertEquals(IMPORTANCE_MIN, updated.getValue().getImportance());
+        assertFalse(mNotificationInfo.shouldBeSavedOnClose());
+    }
+
+    @Test
+    public void testSilence_closeGutsThenTryToSave() throws RemoteException {
+        mNotificationChannel.setImportance(IMPORTANCE_DEFAULT);
+        mNotificationInfo.bindNotification(
+                mMockPackageManager,
+                mMockINotificationManager,
+                mOnUserInteractionCallback,
+                mChannelEditorDialogController,
+                TEST_PACKAGE_NAME,
+                mNotificationChannel,
+                mNotificationChannelSet,
+                mEntry,
+                null,
+                null,
+                mUiEventLogger,
+                true,
+                false,
+                false,
+                mAssistantFeedbackController);
+
+        mNotificationInfo.findViewById(R.id.silence).performClick();
+        mNotificationInfo.handleCloseControls(false, false);
+        mNotificationInfo.handleCloseControls(true, false);
+
+        mTestableLooper.processAllMessages();
+
+        assertEquals(IMPORTANCE_DEFAULT, mNotificationChannel.getImportance());
+        assertFalse(mNotificationInfo.shouldBeSavedOnClose());
     }
 
     @Test
@@ -1267,6 +1303,7 @@
                 anyString(), eq(TEST_UID), updated.capture());
         assertTrue((updated.getValue().getUserLockedFields() & USER_LOCKED_IMPORTANCE) != 0);
         assertEquals(IMPORTANCE_DEFAULT, updated.getValue().getImportance());
+        assertFalse(mNotificationInfo.shouldBeSavedOnClose());
     }
 
     @Test
@@ -1294,6 +1331,7 @@
         mNotificationInfo.handleCloseControls(true, false);
 
         verify(mOnUserInteractionCallback).onImportanceChanged(mEntry);
+        assertFalse(mNotificationInfo.shouldBeSavedOnClose());
     }
 
     @Test
@@ -1360,6 +1398,7 @@
         assertTrue((updated.getValue().getUserLockedFields()
                 & USER_LOCKED_IMPORTANCE) != 0);
         assertEquals(IMPORTANCE_DEFAULT, updated.getValue().getImportance());
+        assertFalse(mNotificationInfo.shouldBeSavedOnClose());
     }
 
     @Test
@@ -1450,7 +1489,7 @@
 
         mNotificationInfo.findViewById(R.id.alert).performClick();
 
-        assertFalse(mNotificationInfo.shouldBeSaved());
+        assertFalse(mNotificationInfo.shouldBeSavedOnClose());
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/PartialConversationInfoTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/PartialConversationInfoTest.java
index 43aa8fe..12c8fd5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/PartialConversationInfoTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/PartialConversationInfoTest.java
@@ -16,7 +16,6 @@
 
 package com.android.systemui.statusbar.notification.row;
 
-import static android.app.Notification.EXTRA_IS_GROUP_CONVERSATION;
 import static android.app.NotificationManager.IMPORTANCE_LOW;
 import static android.print.PrintManager.PRINT_SPOOLER_PACKAGE_NAME;
 import static android.view.View.GONE;
@@ -25,7 +24,6 @@
 import static junit.framework.Assert.assertEquals;
 import static junit.framework.Assert.assertTrue;
 
-import static org.mockito.ArgumentMatchers.anyObject;
 import static org.mockito.Mockito.any;
 import static org.mockito.Mockito.anyBoolean;
 import static org.mockito.Mockito.anyInt;
@@ -36,8 +34,6 @@
 import android.app.INotificationManager;
 import android.app.Notification;
 import android.app.NotificationChannel;
-import android.app.NotificationChannelGroup;
-import android.app.Person;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
diff --git a/services/core/java/com/android/server/audio/AudioDeviceBroker.java b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
index 03dcc8d..82fe6c6 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceBroker.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
@@ -1914,7 +1914,7 @@
         return null;
     }
 
-    UUID getDeviceSensorUuid(AudioDeviceAttributes device) {
+    @Nullable UUID getDeviceSensorUuid(AudioDeviceAttributes device) {
         synchronized (mDeviceStateLock) {
             return mDeviceInventory.getDeviceSensorUuid(device);
         }
diff --git a/services/core/java/com/android/server/audio/AudioDeviceInventory.java b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
index dbe4fb8..82e68d9 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceInventory.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
@@ -16,6 +16,7 @@
 package com.android.server.audio;
 
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.bluetooth.BluetoothAdapter;
 import android.bluetooth.BluetoothDevice;
 import android.bluetooth.BluetoothProfile;
@@ -1495,7 +1496,7 @@
         mDevRoleCapturePresetDispatchers.finishBroadcast();
     }
 
-    UUID getDeviceSensorUuid(AudioDeviceAttributes device) {
+    @Nullable UUID getDeviceSensorUuid(AudioDeviceAttributes device) {
         final String key = DeviceInfo.makeDeviceListKey(device.getInternalType(),
                 device.getAddress());
         synchronized (mDevicesLock) {
diff --git a/services/core/java/com/android/server/audio/SpatializerHelper.java b/services/core/java/com/android/server/audio/SpatializerHelper.java
index e27fb11..8356134 100644
--- a/services/core/java/com/android/server/audio/SpatializerHelper.java
+++ b/services/core/java/com/android/server/audio/SpatializerHelper.java
@@ -353,6 +353,14 @@
         mASA.getDevicesForAttributes(
                 DEFAULT_ATTRIBUTES, false /* forVolume */).toArray(ROUTING_DEVICES);
 
+        // check validity of routing information
+        if (ROUTING_DEVICES[0] == null) {
+            logloge("onRoutingUpdated: device is null, no Spatial Audio");
+            setDispatchAvailableState(false);
+            // not changing the spatializer level as this is likely a transient state
+            return;
+        }
+
         // is media routed to a new device?
         if (isWireless(ROUTING_DEVICES[0].getType())) {
             addWirelessDeviceIfNew(ROUTING_DEVICES[0]);
@@ -1098,7 +1106,7 @@
         logDeviceState(deviceState, "setHeadTrackerEnabled");
 
         // check current routing to see if it affects the headtracking mode
-        if (ROUTING_DEVICES[0].getType() == ada.getType()
+        if (ROUTING_DEVICES[0] != null && ROUTING_DEVICES[0].getType() == ada.getType()
                 && ROUTING_DEVICES[0].getAddress().equals(ada.getAddress())) {
             setDesiredHeadTrackingMode(enabled ? mDesiredHeadTrackingModeWhenEnabled
                     : Spatializer.HEAD_TRACKING_MODE_DISABLED);
@@ -1633,7 +1641,11 @@
 
     private int getHeadSensorHandleUpdateTracker() {
         int headHandle = -1;
-        UUID routingDeviceUuid = mAudioService.getDeviceSensorUuid(ROUTING_DEVICES[0]);
+        final AudioDeviceAttributes currentDevice = ROUTING_DEVICES[0];
+        if (currentDevice == null) {
+            return headHandle;
+        }
+        UUID routingDeviceUuid = mAudioService.getDeviceSensorUuid(currentDevice);
         // We limit only to Sensor.TYPE_HEAD_TRACKER here to avoid confusion
         // with gaming sensors. (Note that Sensor.TYPE_ROTATION_VECTOR
         // and Sensor.TYPE_GAME_ROTATION_VECTOR are supported internally by
@@ -1644,7 +1656,7 @@
             final UUID uuid = sensor.getUuid();
             if (uuid.equals(routingDeviceUuid)) {
                 headHandle = sensor.getHandle();
-                if (!setHasHeadTracker(ROUTING_DEVICES[0])) {
+                if (!setHasHeadTracker(currentDevice)) {
                     headHandle = -1;
                 }
                 break;
diff --git a/services/core/java/com/android/server/broadcastradio/BroadcastRadioService.java b/services/core/java/com/android/server/broadcastradio/BroadcastRadioService.java
index 23f0ffb..351a1e9 100644
--- a/services/core/java/com/android/server/broadcastradio/BroadcastRadioService.java
+++ b/services/core/java/com/android/server/broadcastradio/BroadcastRadioService.java
@@ -39,7 +39,6 @@
 
 public class BroadcastRadioService extends SystemService {
     private static final String TAG = "BcRadioSrv";
-    private static final boolean DEBUG = false;
 
     private final ServiceImpl mServiceImpl = new ServiceImpl();
 
@@ -74,6 +73,7 @@
 
         @Override
         public List<RadioManager.ModuleProperties> listModules() {
+            Slog.v(TAG, "Listing HIDL modules");
             enforcePolicyAccess();
             List<RadioManager.ModuleProperties> modules = new ArrayList<>();
             modules.addAll(mV1Modules);
@@ -84,7 +84,7 @@
         @Override
         public ITuner openTuner(int moduleId, RadioManager.BandConfig bandConfig,
                 boolean withAudio, ITunerCallback callback) throws RemoteException {
-            if (DEBUG) Slog.i(TAG, "Opening module " + moduleId);
+            Slog.v(TAG, "Opening module " + moduleId);
             enforcePolicyAccess();
             if (callback == null) {
                 throw new IllegalArgumentException("Callback must not be empty");
@@ -101,16 +101,14 @@
         @Override
         public ICloseHandle addAnnouncementListener(int[] enabledTypes,
                 IAnnouncementListener listener) {
-            if (DEBUG) {
-                Slog.i(TAG, "Adding announcement listener for " + Arrays.toString(enabledTypes));
-            }
+            Slog.v(TAG, "Adding announcement listener for " + Arrays.toString(enabledTypes));
             Objects.requireNonNull(enabledTypes);
             Objects.requireNonNull(listener);
             enforcePolicyAccess();
 
             synchronized (mLock) {
                 if (!mHal2.hasAnyModules()) {
-                    Slog.i(TAG, "There are no HAL 2.x modules registered");
+                    Slog.i(TAG, "There are no HAL 2.0 modules registered");
                     return new AnnouncementAggregator(listener, mLock);
                 }
 
diff --git a/services/core/java/com/android/server/broadcastradio/hal2/BroadcastRadioService.java b/services/core/java/com/android/server/broadcastradio/hal2/BroadcastRadioService.java
index 5c07f76..534e828 100644
--- a/services/core/java/com/android/server/broadcastradio/hal2/BroadcastRadioService.java
+++ b/services/core/java/com/android/server/broadcastradio/hal2/BroadcastRadioService.java
@@ -132,6 +132,7 @@
     }
 
     public @NonNull Collection<RadioManager.ModuleProperties> listModules() {
+        Slog.v(TAG, "List HIDL 2.0 modules");
         synchronized (mLock) {
             return mModules.values().stream().map(module -> module.mProperties)
                     .collect(Collectors.toList());
@@ -152,10 +153,11 @@
 
     public ITuner openSession(int moduleId, @Nullable RadioManager.BandConfig legacyConfig,
         boolean withAudio, @NonNull ITunerCallback callback) throws RemoteException {
+        Slog.v(TAG, "Open HIDL 2.0 session");
         Objects.requireNonNull(callback);
 
         if (!withAudio) {
-            throw new IllegalArgumentException("Non-audio sessions not supported with HAL 2.x");
+            throw new IllegalArgumentException("Non-audio sessions not supported with HAL 2.0");
         }
 
         RadioModule module = null;
@@ -175,6 +177,7 @@
 
     public ICloseHandle addAnnouncementListener(@NonNull int[] enabledTypes,
             @NonNull IAnnouncementListener listener) {
+        Slog.v(TAG, "Add announcementListener");
         AnnouncementAggregator aggregator = new AnnouncementAggregator(listener, mLock);
         boolean anySupported = false;
         synchronized (mLock) {
diff --git a/services/core/java/com/android/server/broadcastradio/hal2/RadioModule.java b/services/core/java/com/android/server/broadcastradio/hal2/RadioModule.java
index ef7f4c9..aeaa678 100644
--- a/services/core/java/com/android/server/broadcastradio/hal2/RadioModule.java
+++ b/services/core/java/com/android/server/broadcastradio/hal2/RadioModule.java
@@ -142,8 +142,12 @@
     public static @Nullable RadioModule tryLoadingModule(int idx, @NonNull String fqName,
             Object lock) {
         try {
+            Slog.i(TAG, "Try loading module for idx " + idx + ", fqName " + fqName);
             IBroadcastRadio service = IBroadcastRadio.getService(fqName);
-            if (service == null) return null;
+            if (service == null) {
+                Slog.w(TAG, "No service found for fqName " + fqName);
+                return null;
+            }
 
             Mutable<AmFmRegionConfig> amfmConfig = new Mutable<>();
             service.getAmFmRegionConfig(false, (result, config) -> {
@@ -160,7 +164,7 @@
 
             return new RadioModule(service, prop, lock);
         } catch (RemoteException ex) {
-            Slog.e(TAG, "failed to load module " + fqName, ex);
+            Slog.e(TAG, "Failed to load module " + fqName, ex);
             return null;
         }
     }
@@ -171,6 +175,7 @@
 
     public @NonNull TunerSession openSession(@NonNull android.hardware.radio.ITunerCallback userCb)
             throws RemoteException {
+        Slog.i(TAG, "Open TunerSession");
         synchronized (mLock) {
             if (mHalTunerSession == null) {
                 Mutable<ITunerSession> hwSession = new Mutable<>();
@@ -201,6 +206,7 @@
         // Copy the contents of mAidlTunerSessions into a local array because TunerSession.close()
         // must be called without mAidlTunerSessions locked because it can call
         // onTunerSessionClosed().
+        Slog.i(TAG, "Close TunerSessions");
         TunerSession[] tunerSessions;
         synchronized (mLock) {
             tunerSessions = new TunerSession[mAidlTunerSessions.size()];
@@ -313,7 +319,7 @@
         }
         onTunerSessionProgramListFilterChanged(null);
         if (mAidlTunerSessions.isEmpty() && mHalTunerSession != null) {
-            Slog.v(TAG, "closing HAL tuner session");
+            Slog.i(TAG, "Closing HAL tuner session");
             try {
                 mHalTunerSession.close();
             } catch (RemoteException ex) {
@@ -365,6 +371,7 @@
 
     public android.hardware.radio.ICloseHandle addAnnouncementListener(@NonNull int[] enabledTypes,
             @NonNull android.hardware.radio.IAnnouncementListener listener) throws RemoteException {
+        Slog.i(TAG, "Add AnnouncementListener");
         ArrayList<Byte> enabledList = new ArrayList<>();
         for (int type : enabledTypes) {
             enabledList.add((byte)type);
@@ -401,6 +408,7 @@
     }
 
     Bitmap getImage(int id) {
+        Slog.i(TAG, "Get image for id " + id);
         if (id == 0) throw new IllegalArgumentException("Image ID is missing");
 
         byte[] rawImage;
diff --git a/services/core/java/com/android/server/broadcastradio/hal2/TunerSession.java b/services/core/java/com/android/server/broadcastradio/hal2/TunerSession.java
index d476fd6..c13216b 100644
--- a/services/core/java/com/android/server/broadcastradio/hal2/TunerSession.java
+++ b/services/core/java/com/android/server/broadcastradio/hal2/TunerSession.java
@@ -27,6 +27,7 @@
 import android.hardware.radio.ProgramSelector;
 import android.hardware.radio.RadioManager;
 import android.os.RemoteException;
+import android.util.Log;
 import android.util.MutableBoolean;
 import android.util.MutableInt;
 import android.util.Slog;
@@ -61,8 +62,13 @@
         mLock = Objects.requireNonNull(lock);
     }
 
+    private boolean isDebugEnabled() {
+        return Log.isLoggable(TAG, Log.DEBUG);
+    }
+
     @Override
     public void close() {
+        if (isDebugEnabled()) Slog.d(TAG, "Close");
         close(null);
     }
 
@@ -74,6 +80,7 @@
      * @param error Optional error to send to client before session is closed.
      */
     public void close(@Nullable Integer error) {
+        if (isDebugEnabled()) Slog.d(TAG, "Close on error " + error);
         synchronized (mLock) {
             if (mIsClosed) return;
             if (error != null) {
@@ -104,7 +111,7 @@
         synchronized (mLock) {
             checkNotClosedLocked();
             mDummyConfig = Objects.requireNonNull(config);
-            Slog.i(TAG, "Ignoring setConfiguration - not applicable for broadcastradio HAL 2.x");
+            Slog.i(TAG, "Ignoring setConfiguration - not applicable for broadcastradio HAL 2.0");
             mModule.fanoutAidlCallback(cb -> cb.onConfigurationChanged(config));
         }
     }
@@ -137,6 +144,10 @@
 
     @Override
     public void step(boolean directionDown, boolean skipSubChannel) throws RemoteException {
+        if (isDebugEnabled()) {
+            Slog.d(TAG, "Step with directionDown " + directionDown
+                    + " skipSubChannel " + skipSubChannel);
+        }
         synchronized (mLock) {
             checkNotClosedLocked();
             int halResult = mHwSession.step(!directionDown);
@@ -146,6 +157,10 @@
 
     @Override
     public void scan(boolean directionDown, boolean skipSubChannel) throws RemoteException {
+        if (isDebugEnabled()) {
+            Slog.d(TAG, "Scan with directionDown " + directionDown
+                    + " skipSubChannel " + skipSubChannel);
+        }
         synchronized (mLock) {
             checkNotClosedLocked();
             int halResult = mHwSession.scan(!directionDown, skipSubChannel);
@@ -155,6 +170,7 @@
 
     @Override
     public void tune(ProgramSelector selector) throws RemoteException {
+        if (isDebugEnabled()) Slog.d(TAG, "Tune with selector " + selector);
         synchronized (mLock) {
             checkNotClosedLocked();
             int halResult = mHwSession.tune(Convert.programSelectorToHal(selector));
@@ -164,6 +180,7 @@
 
     @Override
     public void cancel() {
+        Slog.i(TAG, "Cancel");
         synchronized (mLock) {
             checkNotClosedLocked();
             Utils.maybeRethrow(mHwSession::cancel);
@@ -172,23 +189,25 @@
 
     @Override
     public void cancelAnnouncement() {
-        Slog.i(TAG, "Announcements control doesn't involve cancelling at the HAL level in 2.x");
+        Slog.i(TAG, "Announcements control doesn't involve cancelling at the HAL level in HAL 2.0");
     }
 
     @Override
     public Bitmap getImage(int id) {
+        if (isDebugEnabled()) Slog.d(TAG, "Get image for " + id);
         return mModule.getImage(id);
     }
 
     @Override
     public boolean startBackgroundScan() {
-        Slog.i(TAG, "Explicit background scan trigger is not supported with HAL 2.x");
+        Slog.i(TAG, "Explicit background scan trigger is not supported with HAL 2.0");
         mModule.fanoutAidlCallback(cb -> cb.onBackgroundScanComplete());
         return true;
     }
 
     @Override
     public void startProgramListUpdates(ProgramList.Filter filter) throws RemoteException {
+        if (isDebugEnabled()) Slog.d(TAG, "start programList updates " + filter);
         // If the AIDL client provides a null filter, it wants all updates, so use the most broad
         // filter.
         if (filter == null) {
@@ -247,6 +266,7 @@
 
     @Override
     public void stopProgramListUpdates() throws RemoteException {
+        if (isDebugEnabled()) Slog.d(TAG, "Stop programList updates");
         synchronized (mLock) {
             checkNotClosedLocked();
             mProgramInfoCache = null;
@@ -270,7 +290,7 @@
 
     @Override
     public boolean isConfigFlagSet(int flag) {
-        Slog.v(TAG, "isConfigFlagSet " + ConfigFlag.toString(flag));
+        if (isDebugEnabled()) Slog.d(TAG, "Is ConfigFlagSet for " + ConfigFlag.toString(flag));
         synchronized (mLock) {
             checkNotClosedLocked();
 
@@ -292,7 +312,9 @@
 
     @Override
     public void setConfigFlag(int flag, boolean value) throws RemoteException {
-        Slog.v(TAG, "setConfigFlag " + ConfigFlag.toString(flag) + " = " + value);
+        if (isDebugEnabled()) {
+            Slog.d(TAG, "Set ConfigFlag " + ConfigFlag.toString(flag) + " = " + value);
+        }
         synchronized (mLock) {
             checkNotClosedLocked();
             int halResult = mHwSession.setConfigFlag(flag, value);
diff --git a/services/core/java/com/android/server/display/AutomaticBrightnessController.java b/services/core/java/com/android/server/display/AutomaticBrightnessController.java
index 72464be..db646df 100644
--- a/services/core/java/com/android/server/display/AutomaticBrightnessController.java
+++ b/services/core/java/com/android/server/display/AutomaticBrightnessController.java
@@ -993,8 +993,9 @@
                     final String packageName = info.topActivity.getPackageName();
                     // If the app didn't change, there's nothing to do. Otherwise, we have to
                     // update the category and re-apply the brightness correction.
-                    if (mForegroundAppPackageName != null
-                            && mForegroundAppPackageName.equals(packageName)) {
+                    String currentForegroundAppPackageName = mForegroundAppPackageName;
+                    if (currentForegroundAppPackageName != null
+                            && currentForegroundAppPackageName.equals(packageName)) {
                         return;
                     }
                     mPendingForegroundAppPackageName = packageName;
diff --git a/services/core/java/com/android/server/dreams/DreamManagerService.java b/services/core/java/com/android/server/dreams/DreamManagerService.java
index 7b60345..4e0489a 100644
--- a/services/core/java/com/android/server/dreams/DreamManagerService.java
+++ b/services/core/java/com/android/server/dreams/DreamManagerService.java
@@ -218,6 +218,7 @@
         }, pw, "", 200);
     }
 
+    /** Whether a real dream is occurring. */
     private boolean isDreamingInternal() {
         synchronized (mLock) {
             return mCurrentDreamToken != null && !mCurrentDreamIsPreview
@@ -225,6 +226,13 @@
         }
     }
 
+    /** Whether a real dream, or a dream preview is occurring. */
+    private boolean isDreamingOrInPreviewInternal() {
+        synchronized (mLock) {
+            return mCurrentDreamToken != null && !mCurrentDreamIsWaking;
+        }
+    }
+
     protected void requestStartDreamFromShell() {
         requestDreamInternal();
     }
@@ -695,6 +703,19 @@
         }
 
         @Override // Binder call
+        public boolean isDreamingOrInPreview() {
+            checkPermission(android.Manifest.permission.READ_DREAM_STATE);
+
+            final long ident = Binder.clearCallingIdentity();
+            try {
+                return isDreamingOrInPreviewInternal();
+            } finally {
+                Binder.restoreCallingIdentity(ident);
+            }
+        }
+
+
+        @Override // Binder call
         public void dream() {
             checkPermission(android.Manifest.permission.WRITE_DREAM_STATE);
 
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index 9d15ed3..faa219e 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -4855,7 +4855,8 @@
     }
 
     @BinderThread
-    private void hideMySoftInput(@NonNull IBinder token, int flags) {
+    private void hideMySoftInput(@NonNull IBinder token, int flags,
+            @SoftInputShowHideReason int reason) {
         Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "IMMS.hideMySoftInput");
         synchronized (ImfLock.class) {
             if (!calledWithValidTokenLocked(token)) {
@@ -4863,10 +4864,7 @@
             }
             final long ident = Binder.clearCallingIdentity();
             try {
-                hideCurrentInputLocked(
-                        mLastImeTargetWindow, flags, null,
-                        SoftInputShowHideReason.HIDE_MY_SOFT_INPUT);
-
+                hideCurrentInputLocked(mLastImeTargetWindow, flags, null, reason);
             } finally {
                 Binder.restoreCallingIdentity(ident);
             }
@@ -4884,7 +4882,7 @@
             final long ident = Binder.clearCallingIdentity();
             try {
                 showCurrentInputLocked(mLastImeTargetWindow, flags, null,
-                        SoftInputShowHideReason.SHOW_MY_SOFT_INPUT);
+                        SoftInputShowHideReason.SHOW_SOFT_INPUT_FROM_IME);
             } finally {
                 Binder.restoreCallingIdentity(ident);
             }
@@ -6698,11 +6696,12 @@
 
         @BinderThread
         @Override
-        public void hideMySoftInput(int flags, AndroidFuture future /* T=Void */) {
+        public void hideMySoftInput(int flags, @SoftInputShowHideReason int reason,
+                AndroidFuture future /* T=Void */) {
             @SuppressWarnings("unchecked")
             final AndroidFuture<Void> typedFuture = future;
             try {
-                mImms.hideMySoftInput(mToken, flags);
+                mImms.hideMySoftInput(mToken, flags, reason);
                 typedFuture.complete(null);
             } catch (Throwable e) {
                 typedFuture.completeExceptionally(e);
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 793f592d..7771a40 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -5171,7 +5171,8 @@
                     extras,
                     mRankingHelper.findExtractor(ValidateNotificationPeople.class),
                     MATCHES_CALL_FILTER_CONTACTS_TIMEOUT_MS,
-                    MATCHES_CALL_FILTER_TIMEOUT_AFFINITY);
+                    MATCHES_CALL_FILTER_TIMEOUT_AFFINITY,
+                    Binder.getCallingUid());
         }
 
         @Override
diff --git a/services/core/java/com/android/server/notification/ZenLog.java b/services/core/java/com/android/server/notification/ZenLog.java
index 7d7f3a9..c0bc474 100644
--- a/services/core/java/com/android/server/notification/ZenLog.java
+++ b/services/core/java/com/android/server/notification/ZenLog.java
@@ -66,6 +66,8 @@
     private static final int TYPE_SET_NOTIFICATION_POLICY = 16;
     private static final int TYPE_SET_CONSOLIDATED_ZEN_POLICY = 17;
     private static final int TYPE_MATCHES_CALL_FILTER = 18;
+    private static final int TYPE_RECORD_CALLER = 19;
+    private static final int TYPE_CHECK_REPEAT_CALLER = 20;
 
     private static int sNext;
     private static int sSize;
@@ -167,11 +169,28 @@
             + hintsToString(newHints) + ",listeners=" + listenerCount);
     }
 
-    /*
+    /**
      * Trace calls to matchesCallFilter with the result of the call and the reason for the result.
      */
-    public static void traceMatchesCallFilter(boolean result, String reason) {
-        append(TYPE_MATCHES_CALL_FILTER, "result=" + result + ", reason=" + reason);
+    public static void traceMatchesCallFilter(boolean result, String reason, int callingUid) {
+        append(TYPE_MATCHES_CALL_FILTER, "result=" + result + ", reason=" + reason
+                + ", calling uid=" + callingUid);
+    }
+
+    /**
+     * Trace what information is available about an incoming call when it's recorded
+     */
+    public static void traceRecordCaller(boolean hasPhone, boolean hasUri) {
+        append(TYPE_RECORD_CALLER, "has phone number=" + hasPhone + ", has uri=" + hasUri);
+    }
+
+    /**
+     * Trace what information was provided about a caller when checking whether it is from a repeat
+     * caller
+     */
+    public static void traceCheckRepeatCaller(boolean found, boolean hasPhone, boolean hasUri) {
+        append(TYPE_CHECK_REPEAT_CALLER, "res=" + found + ", given phone number=" + hasPhone
+                + ", given uri=" + hasUri);
     }
 
     private static String subscribeResult(IConditionProvider provider, RemoteException e) {
@@ -198,6 +217,8 @@
             case TYPE_SET_NOTIFICATION_POLICY: return "set_notification_policy";
             case TYPE_SET_CONSOLIDATED_ZEN_POLICY: return "set_consolidated_policy";
             case TYPE_MATCHES_CALL_FILTER: return "matches_call_filter";
+            case TYPE_RECORD_CALLER: return "record_caller";
+            case TYPE_CHECK_REPEAT_CALLER: return "check_repeat_caller";
             default: return "unknown";
         }
     }
diff --git a/services/core/java/com/android/server/notification/ZenModeFiltering.java b/services/core/java/com/android/server/notification/ZenModeFiltering.java
index b0d40ef..7e36aed 100644
--- a/services/core/java/com/android/server/notification/ZenModeFiltering.java
+++ b/services/core/java/com/android/server/notification/ZenModeFiltering.java
@@ -101,23 +101,24 @@
      */
     public static boolean matchesCallFilter(Context context, int zen, NotificationManager.Policy
             consolidatedPolicy, UserHandle userHandle, Bundle extras,
-            ValidateNotificationPeople validator, int contactsTimeoutMs, float timeoutAffinity) {
+            ValidateNotificationPeople validator, int contactsTimeoutMs, float timeoutAffinity,
+            int callingUid) {
         if (zen == Global.ZEN_MODE_NO_INTERRUPTIONS) {
-            ZenLog.traceMatchesCallFilter(false, "no interruptions");
+            ZenLog.traceMatchesCallFilter(false, "no interruptions", callingUid);
             return false; // nothing gets through
         }
         if (zen == Global.ZEN_MODE_ALARMS) {
-            ZenLog.traceMatchesCallFilter(false, "alarms only");
+            ZenLog.traceMatchesCallFilter(false, "alarms only", callingUid);
             return false; // not an alarm
         }
         if (zen == Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS) {
             if (consolidatedPolicy.allowRepeatCallers()
                     && REPEAT_CALLERS.isRepeat(context, extras, null)) {
-                ZenLog.traceMatchesCallFilter(true, "repeat caller");
+                ZenLog.traceMatchesCallFilter(true, "repeat caller", callingUid);
                 return true;
             }
             if (!consolidatedPolicy.allowCalls()) {
-                ZenLog.traceMatchesCallFilter(false, "calls not allowed");
+                ZenLog.traceMatchesCallFilter(false, "calls not allowed", callingUid);
                 return false; // no other calls get through
             }
             if (validator != null) {
@@ -125,11 +126,12 @@
                         contactsTimeoutMs, timeoutAffinity);
                 boolean match =
                         audienceMatches(consolidatedPolicy.allowCallsFrom(), contactAffinity);
-                ZenLog.traceMatchesCallFilter(match, "contact affinity " + contactAffinity);
+                ZenLog.traceMatchesCallFilter(match, "contact affinity " + contactAffinity,
+                        callingUid);
                 return match;
             }
         }
-        ZenLog.traceMatchesCallFilter(true, "no restrictions");
+        ZenLog.traceMatchesCallFilter(true, "no restrictions", callingUid);
         return true;
     }
 
@@ -419,6 +421,7 @@
 
         private synchronized void recordCallers(String[] people, ArraySet<String> phoneNumbers,
                 long now) {
+            boolean recorded = false, hasTel = false, hasOther = false;
             for (int i = 0; i < people.length; i++) {
                 String person = people[i];
                 if (person == null) continue;
@@ -427,10 +430,16 @@
                     // while ideally we should not need to decode this, sometimes we have seen tel
                     // numbers given in an encoded format
                     String tel = Uri.decode(uri.getSchemeSpecificPart());
-                    if (tel != null) mTelCalls.put(tel, now);
+                    if (tel != null) {
+                        mTelCalls.put(tel, now);
+                        recorded = true;
+                        hasTel = true;
+                    }
                 } else {
                     // for non-tel calls, store the entire string, uri-component and all
                     mOtherCalls.put(person, now);
+                    recorded = true;
+                    hasOther = true;
                 }
             }
 
@@ -438,9 +447,16 @@
             // provided; these are in the format of just a phone number string
             if (phoneNumbers != null) {
                 for (String num : phoneNumbers) {
-                    if (num != null) mTelCalls.put(num, now);
+                    if (num != null) {
+                        mTelCalls.put(num, now);
+                        recorded = true;
+                        hasTel = true;
+                    }
                 }
             }
+            if (recorded) {
+                ZenLog.traceRecordCaller(hasTel, hasOther);
+            }
         }
 
         // helper function to check mTelCalls array for a number, and also check its decoded
@@ -468,6 +484,8 @@
         // previously recorded phone call.
         private synchronized boolean checkCallers(Context context, String[] people,
                 ArraySet<String> phoneNumbers) {
+            boolean found = false, checkedTel = false, checkedOther = false;
+
             // get the default country code for checking telephone numbers
             final String defaultCountryCode =
                     context.getSystemService(TelephonyManager.class).getNetworkCountryIso();
@@ -477,12 +495,14 @@
                 final Uri uri = Uri.parse(person);
                 if ("tel".equals(uri.getScheme())) {
                     String number = uri.getSchemeSpecificPart();
+                    checkedTel = true;
                     if (checkForNumber(number, defaultCountryCode)) {
-                        return true;
+                        found = true;
                     }
                 } else {
+                    checkedOther = true;
                     if (mOtherCalls.containsKey(person)) {
-                        return true;
+                        found = true;
                     }
                 }
             }
@@ -490,14 +510,16 @@
             // also check any passed-in phone numbers
             if (phoneNumbers != null) {
                 for (String num : phoneNumbers) {
+                    checkedTel = true;
                     if (checkForNumber(num, defaultCountryCode)) {
-                        return true;
+                        found = true;
                     }
                 }
             }
 
             // no matches
-            return false;
+            ZenLog.traceCheckRepeatCaller(found, checkedTel, checkedOther);
+            return found;
         }
     }
 
diff --git a/services/core/java/com/android/server/notification/ZenModeHelper.java b/services/core/java/com/android/server/notification/ZenModeHelper.java
index 6135fe8..d426679 100644
--- a/services/core/java/com/android/server/notification/ZenModeHelper.java
+++ b/services/core/java/com/android/server/notification/ZenModeHelper.java
@@ -177,10 +177,12 @@
     }
 
     public boolean matchesCallFilter(UserHandle userHandle, Bundle extras,
-            ValidateNotificationPeople validator, int contactsTimeoutMs, float timeoutAffinity) {
+            ValidateNotificationPeople validator, int contactsTimeoutMs, float timeoutAffinity,
+            int callingUid) {
         synchronized (mConfig) {
             return ZenModeFiltering.matchesCallFilter(mContext, mZenMode, mConsolidatedPolicy,
-                    userHandle, extras, validator, contactsTimeoutMs, timeoutAffinity);
+                    userHandle, extras, validator, contactsTimeoutMs, timeoutAffinity,
+                    callingUid);
         }
     }
 
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 85b0149..0c4273f 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -7051,7 +7051,8 @@
             mResolveActivity.processName = pkg.getProcessName();
             mResolveActivity.launchMode = ActivityInfo.LAUNCH_MULTIPLE;
             mResolveActivity.flags = ActivityInfo.FLAG_EXCLUDE_FROM_RECENTS
-                    | ActivityInfo.FLAG_FINISH_ON_CLOSE_SYSTEM_DIALOGS;
+                    | ActivityInfo.FLAG_FINISH_ON_CLOSE_SYSTEM_DIALOGS
+                    | ActivityInfo.FLAG_CAN_DISPLAY_ON_REMOTE_DEVICES;
             mResolveActivity.theme = 0;
             mResolveActivity.exported = true;
             mResolveActivity.enabled = true;
@@ -7084,7 +7085,8 @@
                 mResolveActivity.launchMode = ActivityInfo.LAUNCH_MULTIPLE;
                 mResolveActivity.documentLaunchMode = ActivityInfo.DOCUMENT_LAUNCH_NEVER;
                 mResolveActivity.flags = ActivityInfo.FLAG_EXCLUDE_FROM_RECENTS
-                        | ActivityInfo.FLAG_RELINQUISH_TASK_IDENTITY;
+                        | ActivityInfo.FLAG_RELINQUISH_TASK_IDENTITY
+                        | ActivityInfo.FLAG_CAN_DISPLAY_ON_REMOTE_DEVICES;
                 mResolveActivity.theme = R.style.Theme_Material_Dialog_Alert;
                 mResolveActivity.exported = true;
                 mResolveActivity.enabled = true;
diff --git a/services/core/java/com/android/server/vibrator/AbstractVibratorStep.java b/services/core/java/com/android/server/vibrator/AbstractVibratorStep.java
index 12e68b1..eebd046 100644
--- a/services/core/java/com/android/server/vibrator/AbstractVibratorStep.java
+++ b/services/core/java/com/android/server/vibrator/AbstractVibratorStep.java
@@ -96,6 +96,7 @@
                     "Turning off vibrator " + getVibratorId());
         }
         controller.off();
+        getVibration().stats().reportVibratorOff();
     }
 
     protected void changeAmplitude(float amplitude) {
@@ -104,6 +105,7 @@
                     "Amplitude changed on vibrator " + getVibratorId() + " to " + amplitude);
         }
         controller.setAmplitude(amplitude);
+        getVibration().stats().reportSetAmplitude();
     }
 
     /**
@@ -147,6 +149,8 @@
         if (nextSegmentIndex >= effectSize && repeatIndex >= 0) {
             // Count the loops that were played.
             int loopSize = effectSize - repeatIndex;
+            int loopSegmentsPlayed = nextSegmentIndex - repeatIndex;
+            getVibration().stats().reportRepetition(loopSegmentsPlayed / loopSize);
             nextSegmentIndex = repeatIndex + ((nextSegmentIndex - effectSize) % loopSize);
         }
         Step nextStep = conductor.nextVibrateStep(nextStartTime, controller, effect,
diff --git a/services/core/java/com/android/server/vibrator/ComposePrimitivesVibratorStep.java b/services/core/java/com/android/server/vibrator/ComposePrimitivesVibratorStep.java
index 3bc11c8..f8b9926 100644
--- a/services/core/java/com/android/server/vibrator/ComposePrimitivesVibratorStep.java
+++ b/services/core/java/com/android/server/vibrator/ComposePrimitivesVibratorStep.java
@@ -67,9 +67,10 @@
                 Slog.d(VibrationThread.TAG, "Compose " + primitives + " primitives on vibrator "
                         + controller.getVibratorInfo().getId());
             }
-            mVibratorOnResult = controller.on(
-                    primitives.toArray(new PrimitiveSegment[primitives.size()]),
-                    getVibration().id);
+            PrimitiveSegment[] primitivesArray =
+                    primitives.toArray(new PrimitiveSegment[primitives.size()]);
+            mVibratorOnResult = controller.on(primitivesArray, getVibration().id);
+            getVibration().stats().reportComposePrimitives(mVibratorOnResult, primitivesArray);
 
             return nextSteps(/* segmentsPlayed= */ primitives.size());
         } finally {
diff --git a/services/core/java/com/android/server/vibrator/ComposePwleVibratorStep.java b/services/core/java/com/android/server/vibrator/ComposePwleVibratorStep.java
index 919f1be..81f52c9 100644
--- a/services/core/java/com/android/server/vibrator/ComposePwleVibratorStep.java
+++ b/services/core/java/com/android/server/vibrator/ComposePwleVibratorStep.java
@@ -68,8 +68,9 @@
                 Slog.d(VibrationThread.TAG, "Compose " + pwles + " PWLEs on vibrator "
                         + controller.getVibratorInfo().getId());
             }
-            mVibratorOnResult = controller.on(pwles.toArray(new RampSegment[pwles.size()]),
-                    getVibration().id);
+            RampSegment[] pwlesArray = pwles.toArray(new RampSegment[pwles.size()]);
+            mVibratorOnResult = controller.on(pwlesArray, getVibration().id);
+            getVibration().stats().reportComposePwle(mVibratorOnResult, pwlesArray);
 
             return nextSteps(/* segmentsPlayed= */ pwles.size());
         } finally {
diff --git a/services/core/java/com/android/server/vibrator/PerformPrebakedVibratorStep.java b/services/core/java/com/android/server/vibrator/PerformPrebakedVibratorStep.java
index 601ae97..419021478 100644
--- a/services/core/java/com/android/server/vibrator/PerformPrebakedVibratorStep.java
+++ b/services/core/java/com/android/server/vibrator/PerformPrebakedVibratorStep.java
@@ -62,6 +62,7 @@
 
             VibrationEffect fallback = getVibration().getFallback(prebaked.getEffectId());
             mVibratorOnResult = controller.on(prebaked, getVibration().id);
+            getVibration().stats().reportPerformEffect(mVibratorOnResult, prebaked);
 
             if (mVibratorOnResult == 0 && prebaked.shouldFallback()
                     && (fallback instanceof VibrationEffect.Composed)) {
diff --git a/services/core/java/com/android/server/vibrator/SetAmplitudeVibratorStep.java b/services/core/java/com/android/server/vibrator/SetAmplitudeVibratorStep.java
index 1f0d2d7..6fb9111 100644
--- a/services/core/java/com/android/server/vibrator/SetAmplitudeVibratorStep.java
+++ b/services/core/java/com/android/server/vibrator/SetAmplitudeVibratorStep.java
@@ -148,7 +148,9 @@
                     "Turning on vibrator " + controller.getVibratorInfo().getId() + " for "
                             + duration + "ms");
         }
-        return controller.on(duration, getVibration().id);
+        long vibratorOnResult = controller.on(duration, getVibration().id);
+        getVibration().stats().reportVibratorOn(vibratorOnResult);
+        return vibratorOnResult;
     }
 
     /**
diff --git a/services/core/java/com/android/server/vibrator/StartSequentialEffectStep.java b/services/core/java/com/android/server/vibrator/StartSequentialEffectStep.java
index 080a36c..2c6fbbc9 100644
--- a/services/core/java/com/android/server/vibrator/StartSequentialEffectStep.java
+++ b/services/core/java/com/android/server/vibrator/StartSequentialEffectStep.java
@@ -93,10 +93,8 @@
             }
 
             mVibratorsOnMaxDuration = startVibrating(effectMapping, nextSteps);
-            if (mVibratorsOnMaxDuration > 0) {
-                conductor.vibratorManagerHooks.noteVibratorOn(conductor.getVibration().uid,
-                        mVibratorsOnMaxDuration);
-            }
+            conductor.vibratorManagerHooks.noteVibratorOn(conductor.getVibration().uid,
+                    mVibratorsOnMaxDuration);
         } finally {
             if (mVibratorsOnMaxDuration >= 0) {
                 // It least one vibrator was started then add a finish step to wait for all
diff --git a/services/core/java/com/android/server/vibrator/Vibration.java b/services/core/java/com/android/server/vibrator/Vibration.java
index d79837b..a375d0a 100644
--- a/services/core/java/com/android/server/vibrator/Vibration.java
+++ b/services/core/java/com/android/server/vibrator/Vibration.java
@@ -16,10 +16,10 @@
 
 package com.android.server.vibrator;
 
+import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.os.CombinedVibration;
 import android.os.IBinder;
-import android.os.SystemClock;
 import android.os.VibrationAttributes;
 import android.os.VibrationEffect;
 import android.os.vibrator.PrebakedSegment;
@@ -30,48 +30,60 @@
 import android.util.SparseArray;
 import android.util.proto.ProtoOutputStream;
 
+import com.android.internal.util.FrameworkStatsLog;
+
 import java.text.SimpleDateFormat;
 import java.util.Date;
 import java.util.List;
+import java.util.Objects;
 import java.util.concurrent.CountDownLatch;
 import java.util.function.Function;
 
 /** Represents a vibration request to the vibrator service. */
 final class Vibration {
-    private static final String TAG = "Vibration";
     private static final SimpleDateFormat DEBUG_DATE_FORMAT =
             new SimpleDateFormat("MM-dd HH:mm:ss.SSS");
 
+    /** Vibration status with reference to values from vibratormanagerservice.proto for logging. */
     enum Status {
-        RUNNING,
-        FINISHED,
-        FINISHED_UNEXPECTED,  // Didn't terminate in the usual way.
-        FORWARDED_TO_INPUT_DEVICES,
-        CANCELLED_BINDER_DIED,
-        CANCELLED_BY_SCREEN_OFF,
-        CANCELLED_BY_SETTINGS_UPDATE,
-        CANCELLED_BY_USER,
-        CANCELLED_BY_UNKNOWN_REASON,
-        CANCELLED_SUPERSEDED,
-        IGNORED_ERROR_APP_OPS,
-        IGNORED_ERROR_CANCELLING,
-        IGNORED_ERROR_SCHEDULING,
-        IGNORED_ERROR_TOKEN,
-        IGNORED_APP_OPS,
-        IGNORED_BACKGROUND,
-        IGNORED_UNKNOWN_VIBRATION,
-        IGNORED_UNSUPPORTED,
-        IGNORED_FOR_EXTERNAL,
-        IGNORED_FOR_HIGHER_IMPORTANCE,
-        IGNORED_FOR_ONGOING,
-        IGNORED_FOR_POWER,
-        IGNORED_FOR_RINGER_MODE,
-        IGNORED_FOR_SETTINGS,
-        IGNORED_SUPERSEDED,
+        UNKNOWN(VibrationProto.UNKNOWN),
+        RUNNING(VibrationProto.RUNNING),
+        FINISHED(VibrationProto.FINISHED),
+        FINISHED_UNEXPECTED(VibrationProto.FINISHED_UNEXPECTED),
+        FORWARDED_TO_INPUT_DEVICES(VibrationProto.FORWARDED_TO_INPUT_DEVICES),
+        CANCELLED_BINDER_DIED(VibrationProto.CANCELLED_BINDER_DIED),
+        CANCELLED_BY_SCREEN_OFF(VibrationProto.CANCELLED_BY_SCREEN_OFF),
+        CANCELLED_BY_SETTINGS_UPDATE(VibrationProto.CANCELLED_BY_SETTINGS_UPDATE),
+        CANCELLED_BY_USER(VibrationProto.CANCELLED_BY_USER),
+        CANCELLED_BY_UNKNOWN_REASON(VibrationProto.CANCELLED_BY_UNKNOWN_REASON),
+        CANCELLED_SUPERSEDED(VibrationProto.CANCELLED_SUPERSEDED),
+        IGNORED_ERROR_APP_OPS(VibrationProto.IGNORED_ERROR_APP_OPS),
+        IGNORED_ERROR_CANCELLING(VibrationProto.IGNORED_ERROR_CANCELLING),
+        IGNORED_ERROR_SCHEDULING(VibrationProto.IGNORED_ERROR_SCHEDULING),
+        IGNORED_ERROR_TOKEN(VibrationProto.IGNORED_ERROR_TOKEN),
+        IGNORED_APP_OPS(VibrationProto.IGNORED_APP_OPS),
+        IGNORED_BACKGROUND(VibrationProto.IGNORED_BACKGROUND),
+        IGNORED_UNKNOWN_VIBRATION(VibrationProto.IGNORED_UNKNOWN_VIBRATION),
+        IGNORED_UNSUPPORTED(VibrationProto.IGNORED_UNSUPPORTED),
+        IGNORED_FOR_EXTERNAL(VibrationProto.IGNORED_FOR_EXTERNAL),
+        IGNORED_FOR_HIGHER_IMPORTANCE(VibrationProto.IGNORED_FOR_HIGHER_IMPORTANCE),
+        IGNORED_FOR_ONGOING(VibrationProto.IGNORED_FOR_ONGOING),
+        IGNORED_FOR_POWER(VibrationProto.IGNORED_FOR_POWER),
+        IGNORED_FOR_RINGER_MODE(VibrationProto.IGNORED_FOR_RINGER_MODE),
+        IGNORED_FOR_SETTINGS(VibrationProto.IGNORED_FOR_SETTINGS),
+        IGNORED_SUPERSEDED(VibrationProto.IGNORED_SUPERSEDED);
+
+        private final int mProtoEnumValue;
+
+        Status(int value) {
+            mProtoEnumValue = value;
+        }
+
+        public int getProtoEnumValue() {
+            return mProtoEnumValue;
+        }
     }
 
-    /** Start time using {@link SystemClock#uptimeMillis()}, for calculations. */
-    public final long startUptimeMillis;
     public final VibrationAttributes attrs;
     public final long id;
     public final int uid;
@@ -91,17 +103,11 @@
     @Nullable
     private CombinedVibration mOriginalEffect;
 
-    /**
-     * Start/end times in unix epoch time. Only to be used for debugging purposes and to correlate
-     * with other system events, any duration calculations should be done use
-     * {@link #startUptimeMillis} so as not to be affected by discontinuities created by RTC
-     * adjustments.
-     */
-    private final long mStartTimeDebug;
-    private long mEndTimeDebug;
-    /** End time using {@link SystemClock#uptimeMillis()}, for calculations. */
-    private long mEndUptimeMillis;
-    private Status mStatus;
+    /** Vibration status. */
+    private Vibration.Status mStatus;
+
+    /** Vibration runtime stats. */
+    private final VibrationStats mStats = new VibrationStats();
 
     /** A {@link CountDownLatch} to enable waiting for completion. */
     private final CountDownLatch mCompletionLatch = new CountDownLatch(1);
@@ -111,34 +117,35 @@
         this.token = token;
         this.mEffect = effect;
         this.id = id;
-        this.startUptimeMillis = SystemClock.uptimeMillis();
         this.attrs = attrs;
         this.uid = uid;
         this.opPkg = opPkg;
         this.reason = reason;
-        mStartTimeDebug = System.currentTimeMillis();
-        mStatus = Status.RUNNING;
+        mStatus = Vibration.Status.RUNNING;
+    }
+
+    VibrationStats stats() {
+        return mStats;
     }
 
     /**
-     * Set the {@link Status} of this vibration and the current system time as this
+     * Set the {@link Status} of this vibration and reports the current system time as this
      * vibration end time, for debugging purposes.
      *
      * <p>This method will only accept given value if the current status is {@link
      * Status#RUNNING}.
      */
-    public void end(Status status) {
+    public void end(EndInfo info) {
         if (hasEnded()) {
             // Vibration already ended, keep first ending status set and ignore this one.
             return;
         }
-        mStatus = status;
-        mEndUptimeMillis = SystemClock.uptimeMillis();
-        mEndTimeDebug = System.currentTimeMillis();
+        mStatus = info.status;
+        mStats.reportEnded(info.endedByUid, info.endedByUsage);
         mCompletionLatch.countDown();
     }
 
-    /** Waits indefinitely until another thread calls {@link #end(Status)} on this vibration. */
+    /** Waits indefinitely until another thread calls {@link #end} on this vibration. */
     public void waitForEnd() throws InterruptedException {
         mCompletionLatch.await();
     }
@@ -228,16 +235,69 @@
 
     /** Return {@link Vibration.DebugInfo} with read-only debug information about this vibration. */
     public Vibration.DebugInfo getDebugInfo() {
-        long durationMs = hasEnded() ? mEndUptimeMillis - startUptimeMillis : -1;
-        return new Vibration.DebugInfo(
-                mStartTimeDebug, mEndTimeDebug, durationMs, mEffect, mOriginalEffect,
-                /* scale= */ 0, attrs, uid, opPkg, reason, mStatus);
+        return new Vibration.DebugInfo(mStatus, mStats, mEffect, mOriginalEffect, /* scale= */ 0,
+                attrs, uid, opPkg, reason);
+    }
+
+    /** Return {@link VibrationStats.StatsInfo} with read-only metrics about this vibration. */
+    public VibrationStats.StatsInfo getStatsInfo(long completionUptimeMillis) {
+        int vibrationType = isRepeating()
+                ? FrameworkStatsLog.VIBRATION_REPORTED__VIBRATION_TYPE__REPEATED
+                : FrameworkStatsLog.VIBRATION_REPORTED__VIBRATION_TYPE__SINGLE;
+        return new VibrationStats.StatsInfo(
+                uid, vibrationType, attrs.getUsage(), mStatus, mStats, completionUptimeMillis);
+    }
+
+    /** Immutable info passed as a signal to end a vibration. */
+    static final class EndInfo {
+        /** The {@link Status} to be set to the vibration when it ends with this info. */
+        @NonNull
+        public final Status status;
+        /** The UID that triggered the vibration that ended this, or -1 if undefined. */
+        public final int endedByUid;
+        /** The VibrationAttributes.USAGE_* of the vibration that ended this, or -1 if undefined. */
+        public final int endedByUsage;
+
+        EndInfo(@NonNull Vibration.Status status) {
+            this(status, /* endedByUid= */ -1, /* endedByUsage= */ -1);
+        }
+
+        EndInfo(@NonNull Vibration.Status status, int endedByUid, int endedByUsage) {
+            this.status = status;
+            this.endedByUid = endedByUid;
+            this.endedByUsage = endedByUsage;
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (this == o) return true;
+            if (!(o instanceof EndInfo)) return false;
+            EndInfo that = (EndInfo) o;
+            return endedByUid == that.endedByUid
+                    && endedByUsage == that.endedByUsage
+                    && status == that.status;
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(status, endedByUid, endedByUsage);
+        }
+
+        @Override
+        public String toString() {
+            return "EndInfo{"
+                    + "status=" + status
+                    + ", endedByUid=" + endedByUid
+                    + ", endedByUsage=" + endedByUsage
+                    + '}';
+        }
     }
 
     /** Debug information about vibrations. */
     static final class DebugInfo {
-        private final long mStartTimeDebug;
-        private final long mEndTimeDebug;
+        private final long mCreateTime;
+        private final long mStartTime;
+        private final long mEndTime;
         private final long mDurationMs;
         private final CombinedVibration mEffect;
         private final CombinedVibration mOriginalEffect;
@@ -248,12 +308,13 @@
         private final String mReason;
         private final Status mStatus;
 
-        DebugInfo(long startTimeDebug, long endTimeDebug, long durationMs,
-                CombinedVibration effect, CombinedVibration originalEffect, float scale,
-                VibrationAttributes attrs, int uid, String opPkg, String reason, Status status) {
-            mStartTimeDebug = startTimeDebug;
-            mEndTimeDebug = endTimeDebug;
-            mDurationMs = durationMs;
+        DebugInfo(Status status, VibrationStats stats, @Nullable CombinedVibration effect,
+                @Nullable CombinedVibration originalEffect, float scale, VibrationAttributes attrs,
+                int uid, String opPkg, String reason) {
+            mCreateTime = stats.getCreateTimeDebug();
+            mStartTime = stats.getStartTimeDebug();
+            mEndTime = stats.getEndTimeDebug();
+            mDurationMs = stats.getDurationDebug();
             mEffect = effect;
             mOriginalEffect = originalEffect;
             mScale = scale;
@@ -267,11 +328,13 @@
         @Override
         public String toString() {
             return new StringBuilder()
-                    .append("startTime: ")
-                    .append(DEBUG_DATE_FORMAT.format(new Date(mStartTimeDebug)))
+                    .append("createTime: ")
+                    .append(DEBUG_DATE_FORMAT.format(new Date(mCreateTime)))
+                    .append(", startTime: ")
+                    .append(DEBUG_DATE_FORMAT.format(new Date(mStartTime)))
                     .append(", endTime: ")
-                    .append(mEndTimeDebug == 0 ? null
-                            : DEBUG_DATE_FORMAT.format(new Date(mEndTimeDebug)))
+                    .append(mEndTime == 0 ? null
+                            : DEBUG_DATE_FORMAT.format(new Date(mEndTime)))
                     .append(", durationMs: ")
                     .append(mDurationMs)
                     .append(", status: ")
@@ -296,8 +359,8 @@
         /** Write this info into given {@code fieldId} on {@link ProtoOutputStream}. */
         public void dumpProto(ProtoOutputStream proto, long fieldId) {
             final long token = proto.start(fieldId);
-            proto.write(VibrationProto.START_TIME, mStartTimeDebug);
-            proto.write(VibrationProto.END_TIME, mEndTimeDebug);
+            proto.write(VibrationProto.START_TIME, mStartTime);
+            proto.write(VibrationProto.END_TIME, mEndTime);
             proto.write(VibrationProto.DURATION_MS, mDurationMs);
             proto.write(VibrationProto.STATUS, mStatus.ordinal());
 
@@ -421,4 +484,5 @@
             proto.end(token);
         }
     }
+
 }
diff --git a/services/core/java/com/android/server/vibrator/VibrationStats.java b/services/core/java/com/android/server/vibrator/VibrationStats.java
new file mode 100644
index 0000000..931be1d
--- /dev/null
+++ b/services/core/java/com/android/server/vibrator/VibrationStats.java
@@ -0,0 +1,395 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.vibrator;
+
+import android.os.SystemClock;
+import android.os.vibrator.PrebakedSegment;
+import android.os.vibrator.PrimitiveSegment;
+import android.os.vibrator.RampSegment;
+import android.util.Slog;
+import android.util.SparseBooleanArray;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.FrameworkStatsLog;
+
+/** Holds basic stats about the vibration playback and interaction with the vibrator HAL. */
+final class VibrationStats {
+    static final String TAG = "VibrationStats";
+
+    // Milestone timestamps, using SystemClock.uptimeMillis(), for calculations.
+    // - Create: time a vibration object was created, which is closer to when the service receives a
+    //           vibrate request.
+    // - Start: time a vibration started to play, which is closer to the time that the
+    //          VibrationEffect started playing the very first segment.
+    // - End: time a vibration ended, even if it never started to play. This can be as soon as the
+    //        vibrator HAL reports it has finished the last command, or before it has even started
+    //        when the vibration is ignored or cancelled.
+    // Create and end times set by VibratorManagerService only, guarded by its lock.
+    // Start times set by VibrationThread only (single-threaded).
+    private long mCreateUptimeMillis;
+    private long mStartUptimeMillis;
+    private long mEndUptimeMillis;
+
+    // Milestone timestamps, using unix epoch time, only to be used for debugging purposes and
+    // to correlate with other system events. Any duration calculations should be done with the
+    // {create/start/end}UptimeMillis counterparts so as not to be affected by discontinuities
+    // created by RTC adjustments.
+    // Set together with the *UptimeMillis counterparts.
+    private long mCreateTimeDebug;
+    private long mStartTimeDebug;
+    private long mEndTimeDebug;
+
+    // Vibration interruption tracking.
+    // Set by VibratorManagerService only, guarded by its lock.
+    private int mEndedByUid;
+    private int mEndedByUsage;
+    private int mInterruptedUsage;
+
+    // All following counters are set by VibrationThread only (single-threaded):
+    // Counts how many times the VibrationEffect was repeated.
+    private int mRepeatCount;
+    // Total duration, in milliseconds, the vibrator was active with non-zero amplitude.
+    private int mVibratorOnTotalDurationMillis;
+    // Total number of primitives used in compositions.
+    private int mVibrationCompositionTotalSize;
+    private int mVibrationPwleTotalSize;
+    // Counts how many times each IVibrator method was triggered by this vibration.
+    private int mVibratorOnCount;
+    private int mVibratorOffCount;
+    private int mVibratorSetAmplitudeCount;
+    private int mVibratorSetExternalControlCount;
+    private int mVibratorPerformCount;
+    private int mVibratorComposeCount;
+    private int mVibratorComposePwleCount;
+
+    // Ids of vibration effects and primitives used by this vibration, with support flag.
+    // Set by VibrationThread only (single-threaded).
+    private SparseBooleanArray mVibratorEffectsUsed = new SparseBooleanArray();
+    private SparseBooleanArray mVibratorPrimitivesUsed = new SparseBooleanArray();
+
+    VibrationStats() {
+        mCreateUptimeMillis = SystemClock.uptimeMillis();
+        mCreateTimeDebug = System.currentTimeMillis();
+        // Set invalid UID and VibrationAttributes.USAGE values to indicate fields are unset.
+        mEndedByUid = -1;
+        mEndedByUsage = -1;
+        mInterruptedUsage = -1;
+    }
+
+    long getCreateUptimeMillis() {
+        return mCreateUptimeMillis;
+    }
+
+    long getStartUptimeMillis() {
+        return mStartUptimeMillis;
+    }
+
+    long getEndUptimeMillis() {
+        return mEndUptimeMillis;
+    }
+
+    long getCreateTimeDebug() {
+        return mCreateTimeDebug;
+    }
+
+    long getStartTimeDebug() {
+        return mStartTimeDebug;
+    }
+
+    long getEndTimeDebug() {
+        return mEndTimeDebug;
+    }
+
+    /**
+     * Duration calculated for debugging purposes, between the creation of a vibration and the
+     * end time being reported, or -1 if the vibration has not ended.
+     */
+    long getDurationDebug() {
+        return hasEnded() ? (mEndUptimeMillis - mCreateUptimeMillis) : -1;
+    }
+
+    /** Return true if vibration reported it has ended. */
+    boolean hasEnded() {
+        return mEndUptimeMillis > 0;
+    }
+
+    /** Return true if vibration reported it has started triggering the vibrator. */
+    boolean hasStarted() {
+        return mStartUptimeMillis > 0;
+    }
+
+    /**
+     * Set the current system time as this vibration start time, for debugging purposes.
+     *
+     * <p>This indicates the vibration has started to interact with the vibrator HAL and the
+     * device may start vibrating after this point.
+     *
+     * <p>This method will only accept given value if the start timestamp was never set.
+     */
+    void reportStarted() {
+        if (hasEnded() || (mStartUptimeMillis != 0)) {
+            // Vibration already started or ended, keep first time set and ignore this one.
+            return;
+        }
+        mStartUptimeMillis = SystemClock.uptimeMillis();
+        mStartTimeDebug = System.currentTimeMillis();
+    }
+
+    /**
+     * Set status and end cause for this vibration to end, and the current system time as this
+     * vibration end time, for debugging purposes.
+     *
+     * <p>This might be triggered before {@link #reportStarted()}, which indicates this
+     * vibration was cancelled or ignored before it started triggering the vibrator.
+     *
+     * @return true if the status was accepted. This method will only accept given values if
+     * the end timestamp was never set.
+     */
+    boolean reportEnded(int endedByUid, int endedByUsage) {
+        if (hasEnded()) {
+            // Vibration already ended, keep first ending stats set and ignore this one.
+            return false;
+        }
+        mEndedByUid = endedByUid;
+        mEndedByUsage = endedByUsage;
+        mEndUptimeMillis = SystemClock.uptimeMillis();
+        mEndTimeDebug = System.currentTimeMillis();
+        return true;
+    }
+
+    /**
+     * Report this vibration has interrupted another vibration.
+     *
+     * <p>This method will only accept the first value as the one that was interrupted by this
+     * vibration, and will ignore all successive calls.
+     */
+    void reportInterruptedAnotherVibration(int interruptedUsage) {
+        if (mInterruptedUsage < 0) {
+            mInterruptedUsage = interruptedUsage;
+        }
+    }
+
+    /** Report the vibration has looped a few more times. */
+    void reportRepetition(int loops) {
+        mRepeatCount += loops;
+    }
+
+    /** Report a call to vibrator method to turn on for given duration. */
+    void reportVibratorOn(long halResult) {
+        mVibratorOnCount++;
+
+        if (halResult > 0) {
+            // If HAL result is positive then it represents the actual duration it will be ON.
+            mVibratorOnTotalDurationMillis += (int) halResult;
+        }
+    }
+
+    /** Report a call to vibrator method to turn off. */
+    void reportVibratorOff() {
+        mVibratorOffCount++;
+    }
+
+    /** Report a call to vibrator method to change the vibration amplitude. */
+    void reportSetAmplitude() {
+        mVibratorSetAmplitudeCount++;
+    }
+
+    /** Report a call to vibrator method to trigger a vibration effect. */
+    void reportPerformEffect(long halResult, PrebakedSegment prebaked) {
+        mVibratorPerformCount++;
+
+        if (halResult > 0) {
+            // If HAL result is positive then it represents the actual duration of the vibration.
+            mVibratorEffectsUsed.put(prebaked.getEffectId(), true);
+            mVibratorOnTotalDurationMillis += (int) halResult;
+        } else {
+            // Effect unsupported or request failed.
+            mVibratorEffectsUsed.put(prebaked.getEffectId(), false);
+        }
+    }
+
+    /** Report a call to vibrator method to trigger a vibration as a composition of primitives. */
+    void reportComposePrimitives(long halResult, PrimitiveSegment[] primitives) {
+        mVibratorComposeCount++;
+        mVibrationCompositionTotalSize += primitives.length;
+
+        if (halResult > 0) {
+            // If HAL result is positive then it represents the actual duration of the vibration.
+            // Remove the requested delays to update the total time the vibrator was ON.
+            for (PrimitiveSegment primitive : primitives) {
+                halResult -= primitive.getDelay();
+                mVibratorPrimitivesUsed.put(primitive.getPrimitiveId(), true);
+            }
+            if (halResult > 0) {
+                mVibratorOnTotalDurationMillis += (int) halResult;
+            }
+        } else {
+            // One or more primitives were unsupported, or request failed.
+            for (PrimitiveSegment primitive : primitives) {
+                mVibratorPrimitivesUsed.put(primitive.getPrimitiveId(), false);
+            }
+        }
+    }
+
+    /** Report a call to vibrator method to trigger a vibration as a PWLE. */
+    void reportComposePwle(long halResult, RampSegment[] segments) {
+        mVibratorComposePwleCount++;
+        mVibrationPwleTotalSize += segments.length;
+
+        if (halResult > 0) {
+            // If HAL result is positive then it represents the actual duration of the vibration.
+            // Remove the zero-amplitude segments to update the total time the vibrator was ON.
+            for (RampSegment ramp : segments) {
+                if ((ramp.getStartAmplitude() == 0) && (ramp.getEndAmplitude() == 0)) {
+                    halResult -= ramp.getDuration();
+                }
+            }
+            if (halResult > 0) {
+                mVibratorOnTotalDurationMillis += (int) halResult;
+            }
+        }
+    }
+
+    /**
+     * Increment the stats for total number of times the {@code setExternalControl} method was
+     * triggered in the vibrator HAL.
+     */
+    void reportSetExternalControl() {
+        mVibratorSetExternalControlCount++;
+    }
+
+    /**
+     * Immutable metrics about this vibration, to be kept in memory until it can be pushed through
+     * {@link com.android.internal.util.FrameworkStatsLog} as a
+     * {@link com.android.internal.util.FrameworkStatsLog#VIBRATION_REPORTED}.
+     */
+    static final class StatsInfo {
+        public final int uid;
+        public final int vibrationType;
+        public final int usage;
+        public final int status;
+        public final boolean endedBySameUid;
+        public final int endedByUsage;
+        public final int interruptedUsage;
+        public final int repeatCount;
+        public final int totalDurationMillis;
+        public final int vibratorOnMillis;
+        public final int startLatencyMillis;
+        public final int endLatencyMillis;
+        public final int halComposeCount;
+        public final int halComposePwleCount;
+        public final int halOnCount;
+        public final int halOffCount;
+        public final int halPerformCount;
+        public final int halSetAmplitudeCount;
+        public final int halSetExternalControlCount;
+        public final int halCompositionSize;
+        public final int halPwleSize;
+        public final int[] halSupportedCompositionPrimitivesUsed;
+        public final int[] halSupportedEffectsUsed;
+        public final int[] halUnsupportedCompositionPrimitivesUsed;
+        public final int[] halUnsupportedEffectsUsed;
+        private boolean mIsWritten;
+
+        StatsInfo(int uid, int vibrationType, int usage, Vibration.Status status,
+                VibrationStats stats, long completionUptimeMillis) {
+            this.uid = uid;
+            this.vibrationType = vibrationType;
+            this.usage = usage;
+            this.status = status.getProtoEnumValue();
+            endedBySameUid = (uid == stats.mEndedByUid);
+            endedByUsage = stats.mEndedByUsage;
+            interruptedUsage = stats.mInterruptedUsage;
+            repeatCount = stats.mRepeatCount;
+
+            // This duration goes from the time this object was created until the time it was
+            // completed. We can use latencies to detect the times between first and last
+            // interaction with vibrator.
+            totalDurationMillis =
+                    (int) Math.max(0,  completionUptimeMillis - stats.mCreateUptimeMillis);
+            vibratorOnMillis = stats.mVibratorOnTotalDurationMillis;
+
+            if (stats.hasStarted()) {
+                // We only measure latencies for vibrations that actually triggered the vibrator.
+                startLatencyMillis =
+                        (int) Math.max(0, stats.mStartUptimeMillis - stats.mCreateUptimeMillis);
+                endLatencyMillis =
+                        (int) Math.max(0, completionUptimeMillis - stats.mEndUptimeMillis);
+            } else {
+                startLatencyMillis = endLatencyMillis = 0;
+            }
+
+            halComposeCount = stats.mVibratorComposeCount;
+            halComposePwleCount = stats.mVibratorComposePwleCount;
+            halOnCount = stats.mVibratorOnCount;
+            halOffCount = stats.mVibratorOffCount;
+            halPerformCount = stats.mVibratorPerformCount;
+            halSetAmplitudeCount = stats.mVibratorSetAmplitudeCount;
+            halSetExternalControlCount = stats.mVibratorSetExternalControlCount;
+            halCompositionSize = stats.mVibrationCompositionTotalSize;
+            halPwleSize = stats.mVibrationPwleTotalSize;
+            halSupportedCompositionPrimitivesUsed =
+                    filteredKeys(stats.mVibratorPrimitivesUsed, /* supported= */ true);
+            halSupportedEffectsUsed =
+                    filteredKeys(stats.mVibratorEffectsUsed, /* supported= */ true);
+            halUnsupportedCompositionPrimitivesUsed =
+                    filteredKeys(stats.mVibratorPrimitivesUsed, /* supported= */ false);
+            halUnsupportedEffectsUsed =
+                    filteredKeys(stats.mVibratorEffectsUsed, /* supported= */ false);
+        }
+
+        @VisibleForTesting
+        boolean isWritten() {
+            return mIsWritten;
+        }
+
+        void writeVibrationReported() {
+            if (mIsWritten) {
+                Slog.wtf(TAG, "Writing same vibration stats multiple times for uid=" + uid);
+            }
+            mIsWritten = true;
+            // Mapping from this MetricInfo representation and the atom proto VibrationReported.
+            FrameworkStatsLog.write_non_chained(
+                    FrameworkStatsLog.VIBRATION_REPORTED,
+                    uid, null, vibrationType, usage, status, endedBySameUid, endedByUsage,
+                    interruptedUsage, repeatCount, totalDurationMillis, vibratorOnMillis,
+                    startLatencyMillis, endLatencyMillis, halComposeCount, halComposePwleCount,
+                    halOnCount, halOffCount, halPerformCount, halSetAmplitudeCount,
+                    halSetExternalControlCount, halSupportedCompositionPrimitivesUsed,
+                    halSupportedEffectsUsed, halUnsupportedCompositionPrimitivesUsed,
+                    halUnsupportedEffectsUsed, halCompositionSize, halPwleSize);
+        }
+
+        private static int[] filteredKeys(SparseBooleanArray supportArray, boolean supported) {
+            int count = 0;
+            for (int i = 0; i < supportArray.size(); i++) {
+                if (supportArray.valueAt(i) == supported) count++;
+            }
+            if (count == 0) {
+                return null;
+            }
+            int pos = 0;
+            int[] res = new int[count];
+            for (int i = 0; i < supportArray.size(); i++) {
+                if (supportArray.valueAt(i) == supported) {
+                    res[pos++] = supportArray.keyAt(i);
+                }
+            }
+            return res;
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/vibrator/VibrationStepConductor.java b/services/core/java/com/android/server/vibrator/VibrationStepConductor.java
index e3d8067..0799b95 100644
--- a/services/core/java/com/android/server/vibrator/VibrationStepConductor.java
+++ b/services/core/java/com/android/server/vibrator/VibrationStepConductor.java
@@ -81,12 +81,12 @@
     private final IntArray mSignalVibratorsComplete;
     @Nullable
     @GuardedBy("mLock")
-    private Vibration.Status mSignalCancelStatus = null;
+    private Vibration.EndInfo mSignalCancel = null;
     @GuardedBy("mLock")
     private boolean mSignalCancelImmediate = false;
 
     @Nullable
-    private Vibration.Status mCancelStatus = null;
+    private Vibration.EndInfo mCancelledVibrationEndInfo = null;
     private boolean mCancelledImmediately = false;  // hard stop
     private int mPendingVibrateSteps;
     private int mRemainingStartSequentialEffectSteps;
@@ -153,6 +153,9 @@
         // This count is decremented at the completion of the step, so we don't subtract one.
         mRemainingStartSequentialEffectSteps = sequentialEffect.getEffects().size();
         mNextSteps.offer(new StartSequentialEffectStep(this, sequentialEffect));
+        // Vibration will start playing in the Vibrator, following the effect timings and delays.
+        // Report current time as the vibration start time, for debugging.
+        mVibration.stats().reportStarted();
     }
 
     public Vibration getVibration() {
@@ -182,24 +185,25 @@
      * Calculate the {@link Vibration.Status} based on the current queue state and the expected
      * number of {@link StartSequentialEffectStep} to be played.
      */
-    public Vibration.Status calculateVibrationStatus() {
+    @Nullable
+    public Vibration.EndInfo calculateVibrationEndInfo() {
         if (Build.IS_DEBUGGABLE) {
             expectIsVibrationThread(true);
         }
 
-        if (mCancelStatus != null) {
-            return mCancelStatus;
+        if (mCancelledVibrationEndInfo != null) {
+            return mCancelledVibrationEndInfo;
         }
-        if (mPendingVibrateSteps > 0
-                || mRemainingStartSequentialEffectSteps > 0) {
-            return Vibration.Status.RUNNING;
+        if (mPendingVibrateSteps > 0 || mRemainingStartSequentialEffectSteps > 0) {
+            // Vibration still running.
+            return null;
         }
         // No pending steps, and something happened.
         if (mSuccessfulVibratorOnSteps > 0) {
-            return Vibration.Status.FINISHED;
+            return new Vibration.EndInfo(Vibration.Status.FINISHED);
         }
         // If no step was able to turn the vibrator ON successfully.
-        return Vibration.Status.IGNORED_UNSUPPORTED;
+        return new Vibration.EndInfo(Vibration.Status.IGNORED_UNSUPPORTED);
     }
 
     /**
@@ -305,45 +309,50 @@
         if (DEBUG) {
             Slog.d(TAG, "Binder died, cancelling vibration...");
         }
-        notifyCancelled(Vibration.Status.CANCELLED_BINDER_DIED, /* immediate= */ false);
+        notifyCancelled(new Vibration.EndInfo(Vibration.Status.CANCELLED_BINDER_DIED),
+                /* immediate= */ false);
     }
 
     /**
      * Notify the execution that cancellation is requested. This will be acted upon
      * asynchronously in the VibrationThread.
      *
+     * <p>Only the first cancel signal will be used to end a cancelled vibration, but subsequent
+     * calls with {@code immediate} flag set to true can still force the first cancel signal to
+     * take effect urgently.
+     *
      * @param immediate indicates whether cancellation should abort urgently and skip cleanup steps.
      */
-    public void notifyCancelled(@NonNull Vibration.Status cancelStatus, boolean immediate) {
+    public void notifyCancelled(@NonNull Vibration.EndInfo cancelInfo, boolean immediate) {
         if (Build.IS_DEBUGGABLE) {
             expectIsVibrationThread(false);
         }
         if (DEBUG) {
-            Slog.d(TAG, "Vibration cancel requested with status=" + cancelStatus
+            Slog.d(TAG, "Vibration cancel requested with signal=" + cancelInfo
                     + ", immediate=" + immediate);
         }
-        if ((cancelStatus == null) || !cancelStatus.name().startsWith("CANCEL")) {
-            Slog.w(TAG, "Vibration cancel requested with bad status=" + cancelStatus
+        if ((cancelInfo == null) || !cancelInfo.status.name().startsWith("CANCEL")) {
+            Slog.w(TAG, "Vibration cancel requested with bad signal=" + cancelInfo
                     + ", using CANCELLED_UNKNOWN_REASON to ensure cancellation.");
-            cancelStatus = Vibration.Status.CANCELLED_BY_UNKNOWN_REASON;
+            cancelInfo = new Vibration.EndInfo(Vibration.Status.CANCELLED_BY_UNKNOWN_REASON);
         }
         synchronized (mLock) {
-            if (immediate && mSignalCancelImmediate || (mSignalCancelStatus != null)) {
+            if ((immediate && mSignalCancelImmediate) || (mSignalCancel != null)) {
                 if (DEBUG) {
                     Slog.d(TAG, "Vibration cancel request ignored as the vibration "
-                            + mVibration.id + "is already being cancelled with status="
-                            + mSignalCancelStatus + ", immediate=" + mSignalCancelImmediate);
+                            + mVibration.id + "is already being cancelled with signal="
+                            + mSignalCancel + ", immediate=" + mSignalCancelImmediate);
                 }
                 return;
             }
             mSignalCancelImmediate |= immediate;
-            if (mSignalCancelStatus == null) {
-                mSignalCancelStatus = cancelStatus;
+            if (mSignalCancel == null) {
+                mSignalCancel = cancelInfo;
             } else {
                 if (DEBUG) {
-                    Slog.d(TAG, "Vibration cancel request new status=" + cancelStatus
-                            + " ignored as the vibration was already cancelled with status="
-                            + mSignalCancelStatus + ", but immediate flag was updated to "
+                    Slog.d(TAG, "Vibration cancel request new signal=" + cancelInfo
+                            + " ignored as the vibration was already cancelled with signal="
+                            + mSignalCancel + ", but immediate flag was updated to "
                             + mSignalCancelImmediate);
                 }
             }
@@ -401,9 +410,9 @@
         if (Build.IS_DEBUGGABLE) {
             expectIsVibrationThread(true);  // Reads VibrationThread variables as well as signals.
         }
-        return (mSignalCancelStatus != mCancelStatus)
-            || (mSignalCancelImmediate && !mCancelledImmediately)
-            || (mSignalVibratorsComplete.size() > 0);
+        return (mSignalCancel != null && mCancelledVibrationEndInfo == null)
+                || (mSignalCancelImmediate && !mCancelledImmediately)
+                || (mSignalVibratorsComplete.size() > 0);
     }
 
     /**
@@ -416,7 +425,7 @@
         }
 
         int[] vibratorsToProcess = null;
-        Vibration.Status doCancelStatus = null;
+        Vibration.EndInfo doCancelInfo = null;
         boolean doCancelImmediate = false;
         // Collect signals to process, but don't keep the lock while processing them.
         synchronized (mLock) {
@@ -426,10 +435,10 @@
                 }
                 // This should only happen once.
                 doCancelImmediate = true;
-                doCancelStatus = mSignalCancelStatus;
+                doCancelInfo = mSignalCancel;
             }
-            if (mSignalCancelStatus != mCancelStatus) {
-                doCancelStatus = mSignalCancelStatus;
+            if ((mSignalCancel != null) && (mCancelledVibrationEndInfo == null)) {
+                doCancelInfo = mSignalCancel;
             }
             if (!doCancelImmediate && mSignalVibratorsComplete.size() > 0) {
                 // Swap out the queue of completions to process.
@@ -443,11 +452,11 @@
         // completion signals that were collected in this call, but we won't process them
         // anyway as all steps are cancelled.
         if (doCancelImmediate) {
-            processCancelImmediately(doCancelStatus);
+            processCancelImmediately(doCancelInfo);
             return;
         }
-        if (doCancelStatus != null) {
-            processCancel(doCancelStatus);
+        if (doCancelInfo != null) {
+            processCancel(doCancelInfo);
         }
         if (vibratorsToProcess != null) {
             processVibratorsComplete(vibratorsToProcess);
@@ -460,12 +469,12 @@
      * <p>This will remove all steps and replace them with respective results of
      * {@link Step#cancel()}.
      */
-    public void processCancel(Vibration.Status cancelStatus) {
+    public void processCancel(Vibration.EndInfo cancelInfo) {
         if (Build.IS_DEBUGGABLE) {
             expectIsVibrationThread(true);
         }
 
-        mCancelStatus = cancelStatus;
+        mCancelledVibrationEndInfo = cancelInfo;
         // Vibrator callbacks should wait until all steps from the queue are properly cancelled
         // and clean up steps are added back to the queue, so they can handle the callback.
         List<Step> cleanUpSteps = new ArrayList<>();
@@ -483,13 +492,13 @@
      *
      * <p>This will remove and trigger {@link Step#cancelImmediately()} in all steps, in order.
      */
-    public void processCancelImmediately(Vibration.Status cancelStatus) {
+    public void processCancelImmediately(Vibration.EndInfo cancelInfo) {
         if (Build.IS_DEBUGGABLE) {
             expectIsVibrationThread(true);
         }
 
         mCancelledImmediately = true;
-        mCancelStatus = cancelStatus;
+        mCancelledVibrationEndInfo = cancelInfo;
         Step step;
         while ((step = pollNext()) != null) {
             step.cancelImmediately();
diff --git a/services/core/java/com/android/server/vibrator/VibrationThread.java b/services/core/java/com/android/server/vibrator/VibrationThread.java
index cecc5c0..e824db10 100644
--- a/services/core/java/com/android/server/vibrator/VibrationThread.java
+++ b/services/core/java/com/android/server/vibrator/VibrationThread.java
@@ -76,7 +76,7 @@
          * cleanup tasks, and should not be given new work until {@link #onVibrationThreadReleased}
          * is called.
          */
-        void onVibrationCompleted(long vibrationId, Vibration.Status status);
+        void onVibrationCompleted(long vibrationId, @NonNull Vibration.EndInfo vibrationEndInfo);
 
         /**
          * Tells the manager that the VibrationThread is finished with the previous vibration and
@@ -237,7 +237,8 @@
             try {
                 runCurrentVibrationWithWakeLockAndDeathLink();
             } finally {
-                clientVibrationCompleteIfNotAlready(Vibration.Status.FINISHED_UNEXPECTED);
+                clientVibrationCompleteIfNotAlready(
+                        new Vibration.EndInfo(Vibration.Status.FINISHED_UNEXPECTED));
             }
         } finally {
             mWakeLock.release();
@@ -255,7 +256,8 @@
             vibrationBinderToken.linkToDeath(mExecutingConductor, 0);
         } catch (RemoteException e) {
             Slog.e(TAG, "Error linking vibration to token death", e);
-            clientVibrationCompleteIfNotAlready(Vibration.Status.IGNORED_ERROR_TOKEN);
+            clientVibrationCompleteIfNotAlready(
+                    new Vibration.EndInfo(Vibration.Status.IGNORED_ERROR_TOKEN));
             return;
         }
         // Ensure that the unlink always occurs now.
@@ -274,11 +276,11 @@
     // Indicate that the vibration is complete. This can be called multiple times only for
     // convenience of handling error conditions - an error after the client is complete won't
     // affect the status.
-    private void clientVibrationCompleteIfNotAlready(Vibration.Status completedStatus) {
+    private void clientVibrationCompleteIfNotAlready(@NonNull Vibration.EndInfo vibrationEndInfo) {
         if (!mCalledVibrationCompleteCallback) {
             mCalledVibrationCompleteCallback = true;
             mVibratorManagerHooks.onVibrationCompleted(
-                    mExecutingConductor.getVibration().id, completedStatus);
+                    mExecutingConductor.getVibration().id, vibrationEndInfo);
         }
     }
 
@@ -298,12 +300,15 @@
                     mExecutingConductor.runNextStep();
                 }
 
-                Vibration.Status status = mExecutingConductor.calculateVibrationStatus();
-                // This block can only run once due to mCalledVibrationCompleteCallback.
-                if (status != Vibration.Status.RUNNING && !mCalledVibrationCompleteCallback) {
-                    // First time vibration stopped running, start clean-up tasks and notify
-                    // callback immediately.
-                    clientVibrationCompleteIfNotAlready(status);
+                if (!mCalledVibrationCompleteCallback) {
+                    // This block can only run once due to mCalledVibrationCompleteCallback.
+                    Vibration.EndInfo vibrationEndInfo =
+                            mExecutingConductor.calculateVibrationEndInfo();
+                    if (vibrationEndInfo != null) {
+                        // First time vibration stopped running, start clean-up tasks and notify
+                        // callback immediately.
+                        clientVibrationCompleteIfNotAlready(vibrationEndInfo);
+                    }
                 }
             }
         } finally {
diff --git a/services/core/java/com/android/server/vibrator/VibratorFrameworkStatsLogger.java b/services/core/java/com/android/server/vibrator/VibratorFrameworkStatsLogger.java
new file mode 100644
index 0000000..f600a29
--- /dev/null
+++ b/services/core/java/com/android/server/vibrator/VibratorFrameworkStatsLogger.java
@@ -0,0 +1,140 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.vibrator;
+
+import android.os.Handler;
+import android.os.SystemClock;
+import android.util.Slog;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.FrameworkStatsLog;
+
+import java.util.ArrayDeque;
+import java.util.Queue;
+
+/** Helper class for async write of atoms to {@link FrameworkStatsLog} using a given Handler. */
+public class VibratorFrameworkStatsLogger {
+    private static final String TAG = "VibratorFrameworkStatsLogger";
+
+    // VibrationReported pushed atom needs to be throttled to at most one every 10ms.
+    private static final int VIBRATION_REPORTED_MIN_INTERVAL_MILLIS = 10;
+    // We accumulate events that should take 3s to write and drop excessive metrics.
+    private static final int VIBRATION_REPORTED_MAX_QUEUE_SIZE = 300;
+    // Warning about dropping entries after this amount of atoms were dropped by the throttle.
+    private static final int VIBRATION_REPORTED_WARNING_QUEUE_SIZE = 200;
+
+    private final Object mLock = new Object();
+    private final Handler mHandler;
+    private final long mVibrationReportedLogIntervalMillis;
+    private final long mVibrationReportedQueueMaxSize;
+    private final Runnable mConsumeVibrationStatsQueueRunnable =
+            () -> writeVibrationReportedFromQueue();
+
+    @GuardedBy("mLock")
+    private long mLastVibrationReportedLogUptime;
+    @GuardedBy("mLock")
+    private Queue<VibrationStats.StatsInfo> mVibrationStatsQueue = new ArrayDeque<>();
+
+    VibratorFrameworkStatsLogger(Handler handler) {
+        this(handler, VIBRATION_REPORTED_MIN_INTERVAL_MILLIS, VIBRATION_REPORTED_MAX_QUEUE_SIZE);
+    }
+
+    @VisibleForTesting
+    VibratorFrameworkStatsLogger(Handler handler, int vibrationReportedLogIntervalMillis,
+            int vibrationReportedQueueMaxSize) {
+        mHandler = handler;
+        mVibrationReportedLogIntervalMillis = vibrationReportedLogIntervalMillis;
+        mVibrationReportedQueueMaxSize = vibrationReportedQueueMaxSize;
+    }
+
+    /** Writes {@link FrameworkStatsLog#VIBRATOR_STATE_CHANGED} for state ON. */
+    public void writeVibratorStateOnAsync(int uid, long duration) {
+        mHandler.post(
+                () -> FrameworkStatsLog.write_non_chained(
+                        FrameworkStatsLog.VIBRATOR_STATE_CHANGED, uid, null,
+                        FrameworkStatsLog.VIBRATOR_STATE_CHANGED__STATE__ON, duration));
+    }
+
+    /** Writes {@link FrameworkStatsLog#VIBRATOR_STATE_CHANGED} for state OFF. */
+    public void writeVibratorStateOffAsync(int uid) {
+        mHandler.post(
+                () -> FrameworkStatsLog.write_non_chained(
+                        FrameworkStatsLog.VIBRATOR_STATE_CHANGED, uid, null,
+                        FrameworkStatsLog.VIBRATOR_STATE_CHANGED__STATE__OFF,
+                        /* duration= */ 0));
+    }
+
+    /**
+     *  Writes {@link FrameworkStatsLog#VIBRATION_REPORTED} for given vibration.
+     *
+     *  <p>This atom is throttled to be pushed once every 10ms, so this logger can keep a queue of
+     *  {@link VibrationStats.StatsInfo} entries to slowly write to statsd.
+     */
+    public void writeVibrationReportedAsync(VibrationStats.StatsInfo metrics) {
+        boolean needsScheduling;
+        long scheduleDelayMs;
+        int queueSize;
+
+        synchronized (mLock) {
+            queueSize = mVibrationStatsQueue.size();
+            needsScheduling = (queueSize == 0);
+
+            if (queueSize < mVibrationReportedQueueMaxSize) {
+                mVibrationStatsQueue.offer(metrics);
+            }
+
+            long nextLogUptime =
+                    mLastVibrationReportedLogUptime + mVibrationReportedLogIntervalMillis;
+            scheduleDelayMs = Math.max(0, nextLogUptime - SystemClock.uptimeMillis());
+        }
+
+        if ((queueSize + 1) == VIBRATION_REPORTED_WARNING_QUEUE_SIZE) {
+            Slog.w(TAG, " Approaching vibration metrics queue limit, events might be dropped.");
+        }
+
+        if (needsScheduling) {
+            mHandler.postDelayed(mConsumeVibrationStatsQueueRunnable, scheduleDelayMs);
+        }
+    }
+
+    /** Writes next {@link FrameworkStatsLog#VIBRATION_REPORTED} from the queue. */
+    private void writeVibrationReportedFromQueue() {
+        boolean needsScheduling;
+        VibrationStats.StatsInfo stats;
+
+        synchronized (mLock) {
+            stats = mVibrationStatsQueue.poll();
+            needsScheduling = !mVibrationStatsQueue.isEmpty();
+
+            if (stats != null) {
+                mLastVibrationReportedLogUptime = SystemClock.uptimeMillis();
+            }
+        }
+
+        if (stats == null) {
+            Slog.w(TAG, "Unexpected vibration metric flush with empty queue. Ignoring.");
+        } else {
+            stats.writeVibrationReported();
+        }
+
+        if (needsScheduling) {
+            mHandler.postDelayed(mConsumeVibrationStatsQueueRunnable,
+                    mVibrationReportedLogIntervalMillis);
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/vibrator/VibratorManagerService.java b/services/core/java/com/android/server/vibrator/VibratorManagerService.java
index 5ac2f4f..2f12a82 100644
--- a/services/core/java/com/android/server/vibrator/VibratorManagerService.java
+++ b/services/core/java/com/android/server/vibrator/VibratorManagerService.java
@@ -129,6 +129,7 @@
     private final Context mContext;
     private final PowerManager.WakeLock mWakeLock;
     private final IBatteryStats mBatteryStatsService;
+    private final VibratorFrameworkStatsLogger mFrameworkStatsLogger;
     private final Handler mHandler;
     private final VibrationThread mVibrationThread;
     private final AppOpsManager mAppOps;
@@ -163,10 +164,12 @@
                     // When the system is entering a non-interactive state, we want to cancel
                     // vibrations in case a misbehaving app has abandoned them.
                     if (shouldCancelOnScreenOffLocked(mNextVibration)) {
-                        clearNextVibrationLocked(Vibration.Status.CANCELLED_BY_SCREEN_OFF);
+                        clearNextVibrationLocked(
+                                new Vibration.EndInfo(Vibration.Status.CANCELLED_BY_SCREEN_OFF));
                     }
                     if (shouldCancelOnScreenOffLocked(mCurrentVibration)) {
-                        mCurrentVibration.notifyCancelled(Vibration.Status.CANCELLED_BY_SCREEN_OFF,
+                        mCurrentVibration.notifyCancelled(
+                                new Vibration.EndInfo(Vibration.Status.CANCELLED_BY_SCREEN_OFF),
                                 /* immediate= */ false);
                     }
                 }
@@ -207,6 +210,7 @@
         mVibratorManagerRecords = new VibratorManagerRecords(dumpLimit);
 
         mBatteryStatsService = injector.getBatteryStatsService();
+        mFrameworkStatsLogger = injector.getFrameworkStatsLogger(mHandler);
 
         mAppOps = mContext.getSystemService(AppOpsManager.class);
 
@@ -384,7 +388,8 @@
      * The Vibration is only returned if it is ongoing after this method returns.
      */
     @Nullable
-    private Vibration vibrateInternal(int uid, String opPkg, @NonNull CombinedVibration effect,
+    @VisibleForTesting
+    Vibration vibrateInternal(int uid, String opPkg, @NonNull CombinedVibration effect,
             @Nullable VibrationAttributes attrs, String reason, IBinder token) {
         Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "vibrate, reason = " + reason);
         try {
@@ -399,6 +404,7 @@
                 return null;
             }
             attrs = fixupVibrationAttributes(attrs, effect);
+            // Create Vibration.Stats as close to the received request as possible, for tracking.
             Vibration vib = new Vibration(token, mNextVibrationId.getAndIncrement(), effect, attrs,
                     uid, opPkg, reason);
             fillVibrationFallbacks(vib, effect);
@@ -413,32 +419,56 @@
                 if (DEBUG) {
                     Slog.d(TAG, "Starting vibrate for vibration  " + vib.id);
                 }
-                Vibration.Status ignoreStatus = shouldIgnoreVibrationLocked(
-                        vib.uid, vib.opPkg, vib.attrs);
+                int ignoredByUid = -1;
+                int ignoredByUsage = -1;
+                Vibration.Status status = null;
 
-                if (ignoreStatus == null) {
-                    ignoreStatus = shouldIgnoreVibrationForOngoingLocked(vib);
+                // Check if user settings or DnD is set to ignore this vibration.
+                status = shouldIgnoreVibrationLocked(vib.uid, vib.opPkg, vib.attrs);
+
+                // Check if something has external control, assume it's more important.
+                if ((status == null) && (mCurrentExternalVibration != null)) {
+                    status = Vibration.Status.IGNORED_FOR_EXTERNAL;
+                    ignoredByUid = mCurrentExternalVibration.externalVibration.getUid();
+                    ignoredByUsage = mCurrentExternalVibration.externalVibration
+                            .getVibrationAttributes().getUsage();
                 }
 
-                if (ignoreStatus != null) {
-                    endVibrationLocked(vib, ignoreStatus);
-                    return vib;
-                }
-
-                final long ident = Binder.clearCallingIdentity();
-                try {
-                    if (mCurrentVibration != null) {
-                        mCurrentVibration.notifyCancelled(Vibration.Status.CANCELLED_SUPERSEDED,
-                                /* immediate= */ false);
+                // Check if ongoing vibration is more important than this vibration.
+                if (status == null) {
+                    status = shouldIgnoreVibrationForOngoingLocked(vib);
+                    if (status != null) {
+                        ignoredByUid = mCurrentVibration.getVibration().uid;
+                        ignoredByUsage = mCurrentVibration.getVibration().attrs.getUsage();
                     }
-                    Vibration.Status status = startVibrationLocked(vib);
-                    if (status != Vibration.Status.RUNNING) {
-                        endVibrationLocked(vib, status);
-                    }
-                    return vib;
-                } finally {
-                    Binder.restoreCallingIdentity(ident);
                 }
+
+                // If not ignored so far then try to start this vibration.
+                if (status == null) {
+                    final long ident = Binder.clearCallingIdentity();
+                    try {
+                        if (mCurrentVibration != null) {
+                            vib.stats().reportInterruptedAnotherVibration(
+                                    mCurrentVibration.getVibration().attrs.getUsage());
+                            mCurrentVibration.notifyCancelled(
+                                    new Vibration.EndInfo(
+                                            Vibration.Status.CANCELLED_SUPERSEDED, vib.uid,
+                                            vib.attrs.getUsage()),
+                                    /* immediate= */ false);
+                        }
+                        status = startVibrationLocked(vib);
+                    } finally {
+                        Binder.restoreCallingIdentity(ident);
+                    }
+                }
+
+                // Ignored or failed to start the vibration, end it and report metrics right away.
+                if (status != Vibration.Status.RUNNING) {
+                    endVibrationLocked(vib,
+                            new Vibration.EndInfo(status, ignoredByUid, ignoredByUsage),
+                            /* shouldWriteStats= */ true);
+                }
+                return vib;
             }
         } finally {
             Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
@@ -457,26 +487,28 @@
                 if (DEBUG) {
                     Slog.d(TAG, "Canceling vibration");
                 }
+                Vibration.EndInfo cancelledByUserInfo =
+                        new Vibration.EndInfo(Vibration.Status.CANCELLED_BY_USER);
                 final long ident = Binder.clearCallingIdentity();
                 try {
                     if (mNextVibration != null
                             && shouldCancelVibration(mNextVibration.getVibration(),
                             usageFilter, token)) {
-                        clearNextVibrationLocked(Vibration.Status.CANCELLED_BY_USER);
+                        clearNextVibrationLocked(cancelledByUserInfo);
                     }
                     if (mCurrentVibration != null
                             && shouldCancelVibration(mCurrentVibration.getVibration(),
                             usageFilter, token)) {
-                        mCurrentVibration.notifyCancelled(Vibration.Status.CANCELLED_BY_USER,
-                                /* immediate= */false);
+                        mCurrentVibration.notifyCancelled(
+                                cancelledByUserInfo, /* immediate= */false);
                     }
                     if (mCurrentExternalVibration != null
                             && shouldCancelVibration(
                             mCurrentExternalVibration.externalVibration.getVibrationAttributes(),
                             usageFilter)) {
-                        mCurrentExternalVibration.externalVibration.mute();
-                        endExternalVibrateLocked(Vibration.Status.CANCELLED_BY_USER,
-                                /* continueExternalControl= */ false);
+                        mCurrentExternalVibration.mute();
+                        endExternalVibrateLocked(
+                                cancelledByUserInfo, /* continueExternalControl= */ false);
                     }
                 } finally {
                     Binder.restoreCallingIdentity(ident);
@@ -604,15 +636,17 @@
                     Slog.d(TAG, "Canceling vibration because settings changed: "
                             + (inputDevicesChanged ? "input devices changed" : ignoreStatus));
                 }
-                mCurrentVibration.notifyCancelled(Vibration.Status.CANCELLED_BY_SETTINGS_UPDATE,
+                mCurrentVibration.notifyCancelled(
+                        new Vibration.EndInfo(Vibration.Status.CANCELLED_BY_SETTINGS_UPDATE),
                         /* immediate= */ false);
             }
         }
     }
 
-    private void setExternalControl(boolean externalControl) {
+    private void setExternalControl(boolean externalControl, VibrationStats vibrationStats) {
         for (int i = 0; i < mVibrators.size(); i++) {
             mVibrators.valueAt(i).setExternalControl(externalControl);
+            vibrationStats.reportSetExternalControl();
         }
     }
 
@@ -654,7 +688,9 @@
             }
             // If there's already a vibration queued (waiting for the previous one to finish
             // cancelling), end it cleanly and replace it with the new one.
-            clearNextVibrationLocked(Vibration.Status.IGNORED_SUPERSEDED);
+            clearNextVibrationLocked(
+                    new Vibration.EndInfo(Vibration.Status.IGNORED_SUPERSEDED,
+                            vib.uid, vib.attrs.getUsage()));
             mNextVibration = conductor;
             return Vibration.Status.RUNNING;
         } finally {
@@ -671,6 +707,7 @@
             switch (mode) {
                 case AppOpsManager.MODE_ALLOWED:
                     Trace.asyncTraceBegin(Trace.TRACE_TAG_VIBRATOR, "vibration", 0);
+                    // Make sure mCurrentVibration is set while triggering the VibrationThread.
                     mCurrentVibration = conductor;
                     if (!mVibrationThread.runVibrationOnVibrationThread(mCurrentVibration)) {
                         // Shouldn't happen. The method call already logs a wtf.
@@ -690,18 +727,26 @@
     }
 
     @GuardedBy("mLock")
-    private void endVibrationLocked(Vibration vib, Vibration.Status status) {
-        vib.end(status);
-        logVibrationStatus(vib.uid, vib.attrs, status);
+    private void endVibrationLocked(Vibration vib, Vibration.EndInfo vibrationEndInfo,
+            boolean shouldWriteStats) {
+        vib.end(vibrationEndInfo);
+        logVibrationStatus(vib.uid, vib.attrs, vibrationEndInfo.status);
         mVibratorManagerRecords.record(vib);
+        if (shouldWriteStats) {
+            mFrameworkStatsLogger.writeVibrationReportedAsync(
+                    vib.getStatsInfo(/* completionUptimeMillis= */ SystemClock.uptimeMillis()));
+        }
     }
 
     @GuardedBy("mLock")
-    private void endVibrationLocked(ExternalVibrationHolder vib, Vibration.Status status) {
-        vib.end(status);
+    private void endVibrationAndWriteStatsLocked(ExternalVibrationHolder vib,
+            Vibration.EndInfo vibrationEndInfo) {
+        vib.end(vibrationEndInfo);
         logVibrationStatus(vib.externalVibration.getUid(),
-                vib.externalVibration.getVibrationAttributes(), status);
+                vib.externalVibration.getVibrationAttributes(), vibrationEndInfo.status);
         mVibratorManagerRecords.record(vib);
+        mFrameworkStatsLogger.writeVibrationReportedAsync(
+                vib.getStatsInfo(/* completionUptimeMillis= */ SystemClock.uptimeMillis()));
     }
 
     private void logVibrationStatus(int uid, VibrationAttributes attrs, Vibration.Status status) {
@@ -744,15 +789,17 @@
     }
 
     @GuardedBy("mLock")
-    private void reportFinishedVibrationLocked(Vibration.Status status) {
+    private void reportFinishedVibrationLocked(Vibration.EndInfo vibrationEndInfo) {
         Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "reportFinishVibrationLocked");
         Trace.asyncTraceEnd(Trace.TRACE_TAG_VIBRATOR, "vibration", 0);
         try {
             Vibration vib = mCurrentVibration.getVibration();
             if (DEBUG) {
-                Slog.d(TAG, "Reporting vibration " + vib.id + " finished with status " + status);
+                Slog.d(TAG, "Reporting vibration " + vib.id + " finished with " + vibrationEndInfo);
             }
-            endVibrationLocked(vib, status);
+            // DO NOT write metrics at this point, wait for the VibrationThread to report the
+            // vibration was released, after all cleanup. The metrics will be reported then.
+            endVibrationLocked(vib, vibrationEndInfo, /* shouldWriteStats= */ false);
             finishAppOpModeLocked(vib.uid, vib.opPkg);
         } finally {
             Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
@@ -791,11 +838,6 @@
     @GuardedBy("mLock")
     @Nullable
     private Vibration.Status shouldIgnoreVibrationForOngoingLocked(Vibration vib) {
-        if (mCurrentExternalVibration != null) {
-            // If something has external control of the vibrator, assume that it's more important.
-            return Vibration.Status.IGNORED_FOR_EXTERNAL;
-        }
-
         if (mCurrentVibration == null || vib.isRepeating()) {
             // Incoming repeating vibrations always take precedence over ongoing vibrations.
             return null;
@@ -1122,7 +1164,7 @@
         }
         Vibration vib = conductor.getVibration();
         return mVibrationSettings.shouldCancelVibrationOnScreenOff(
-                vib.uid, vib.opPkg, vib.attrs.getUsage(), vib.startUptimeMillis);
+                vib.uid, vib.opPkg, vib.attrs.getUsage(), vib.stats().getCreateUptimeMillis());
     }
 
     @GuardedBy("mLock")
@@ -1158,6 +1200,10 @@
                     BatteryStats.SERVICE_NAME));
         }
 
+        VibratorFrameworkStatsLogger getFrameworkStatsLogger(Handler handler) {
+            return new VibratorFrameworkStatsLogger(handler);
+        }
+
         VibratorController createVibratorController(int vibratorId,
                 VibratorController.OnVibrationCompleteListener listener) {
             return new VibratorController(vibratorId, listener);
@@ -1197,6 +1243,10 @@
         public void noteVibratorOn(int uid, long duration) {
             try {
                 if (duration <= 0) {
+                    // Tried to turn vibrator ON and got:
+                    // duration == 0: Unsupported effect/method or zero-amplitude segment.
+                    // duration < 0: Unexpected error triggering the vibrator.
+                    // Skip battery stats and atom metric for VibratorStageChanged to ON.
                     return;
                 }
                 if (duration == Long.MAX_VALUE) {
@@ -1205,10 +1255,9 @@
                     duration = BATTERY_STATS_REPEATING_VIBRATION_DURATION;
                 }
                 mBatteryStatsService.noteVibratorOn(uid, duration);
-                FrameworkStatsLog.write_non_chained(FrameworkStatsLog.VIBRATOR_STATE_CHANGED,
-                        uid, null, FrameworkStatsLog.VIBRATOR_STATE_CHANGED__STATE__ON,
-                        duration);
+                mFrameworkStatsLogger.writeVibratorStateOnAsync(uid, duration);
             } catch (RemoteException e) {
+                Slog.e(TAG, "Error logging VibratorStateChanged to ON", e);
             }
         }
 
@@ -1216,22 +1265,21 @@
         public void noteVibratorOff(int uid) {
             try {
                 mBatteryStatsService.noteVibratorOff(uid);
-                FrameworkStatsLog.write_non_chained(FrameworkStatsLog.VIBRATOR_STATE_CHANGED,
-                        uid, null, FrameworkStatsLog.VIBRATOR_STATE_CHANGED__STATE__OFF,
-                        /* duration= */ 0);
+                mFrameworkStatsLogger.writeVibratorStateOffAsync(uid);
             } catch (RemoteException e) {
+                Slog.e(TAG, "Error logging VibratorStateChanged to OFF", e);
             }
         }
 
         @Override
-        public void onVibrationCompleted(long vibrationId, Vibration.Status status) {
+        public void onVibrationCompleted(long vibrationId, Vibration.EndInfo vibrationEndInfo) {
             if (DEBUG) {
-                Slog.d(TAG, "Vibration " + vibrationId + " finished with status " + status);
+                Slog.d(TAG, "Vibration " + vibrationId + " finished with " + vibrationEndInfo);
             }
             synchronized (mLock) {
                 if (mCurrentVibration != null
                         && mCurrentVibration.getVibration().id == vibrationId) {
-                    reportFinishedVibrationLocked(status);
+                    reportFinishedVibrationLocked(vibrationEndInfo);
                 }
             }
         }
@@ -1251,13 +1299,21 @@
                             "VibrationId mismatch on release. expected=%d, released=%d",
                             mCurrentVibration.getVibration().id, vibrationId));
                 }
-                mCurrentVibration = null;
+                if (mCurrentVibration != null) {
+                    // This is when we consider the current vibration complete, so report metrics.
+                    mFrameworkStatsLogger.writeVibrationReportedAsync(
+                            mCurrentVibration.getVibration().getStatsInfo(
+                                    /* completionUptimeMillis= */ SystemClock.uptimeMillis()));
+                    mCurrentVibration = null;
+                }
                 if (mNextVibration != null) {
                     VibrationStepConductor nextConductor = mNextVibration;
                     mNextVibration = null;
                     Vibration.Status status = startVibrationOnThreadLocked(nextConductor);
                     if (status != Vibration.Status.RUNNING) {
-                        endVibrationLocked(nextConductor.getVibration(), status);
+                        // Failed to start the vibration, end it and report metrics right away.
+                        endVibrationLocked(nextConductor.getVibration(),
+                                new Vibration.EndInfo(status), /* shouldWriteStats= */ true);
                     }
                 }
             }
@@ -1325,31 +1381,48 @@
     private final class ExternalVibrationHolder implements IBinder.DeathRecipient {
 
         public final ExternalVibration externalVibration;
+        public final VibrationStats stats = new VibrationStats();
         public int scale;
 
-        private final long mStartUptimeMillis;
-        private final long mStartTimeDebug;
-
-        private long mEndUptimeMillis;
-        private long mEndTimeDebug;
         private Vibration.Status mStatus;
 
         private ExternalVibrationHolder(ExternalVibration externalVibration) {
             this.externalVibration = externalVibration;
             this.scale = IExternalVibratorService.SCALE_NONE;
-            mStartUptimeMillis = SystemClock.uptimeMillis();
-            mStartTimeDebug = System.currentTimeMillis();
             mStatus = Vibration.Status.RUNNING;
         }
 
-        public void end(Vibration.Status status) {
+        public void mute() {
+            externalVibration.mute();
+        }
+
+        public void linkToDeath() {
+            externalVibration.linkToDeath(this);
+        }
+
+        public void unlinkToDeath() {
+            externalVibration.unlinkToDeath(this);
+        }
+
+        public boolean isHoldingSameVibration(ExternalVibration externalVibration) {
+            return this.externalVibration.equals(externalVibration);
+        }
+
+        public void end(Vibration.EndInfo info) {
             if (mStatus != Vibration.Status.RUNNING) {
-                // Vibration already ended, keep first ending status set and ignore this one.
+                // Already ended, ignore this call
                 return;
             }
-            mStatus = status;
-            mEndUptimeMillis = SystemClock.uptimeMillis();
-            mEndTimeDebug = System.currentTimeMillis();
+            mStatus = info.status;
+            stats.reportEnded(info.endedByUid, info.endedByUsage);
+
+            if (stats.hasStarted()) {
+                // External vibration doesn't have feedback from total time the vibrator was playing
+                // with non-zero amplitude, so we use the duration between start and end times of
+                // the vibration as the time the vibrator was ON, since the haptic channels are
+                // open for this duration and can receive vibration waveform data.
+                stats.reportVibratorOn(stats.getEndUptimeMillis() - stats.getStartUptimeMillis());
+            }
         }
 
         public void binderDied() {
@@ -1358,19 +1431,26 @@
                     if (DEBUG) {
                         Slog.d(TAG, "External vibration finished because binder died");
                     }
-                    endExternalVibrateLocked(Vibration.Status.CANCELLED_BINDER_DIED,
+                    endExternalVibrateLocked(
+                            new Vibration.EndInfo(Vibration.Status.CANCELLED_BINDER_DIED),
                             /* continueExternalControl= */ false);
                 }
             }
         }
 
         public Vibration.DebugInfo getDebugInfo() {
-            long durationMs = mEndUptimeMillis == 0 ? -1 : mEndUptimeMillis - mStartUptimeMillis;
             return new Vibration.DebugInfo(
-                    mStartTimeDebug, mEndTimeDebug, durationMs,
-                    /* effect= */ null, /* originalEffect= */ null, scale,
+                    mStatus, stats, /* effect= */ null, /* originalEffect= */ null, scale,
                     externalVibration.getVibrationAttributes(), externalVibration.getUid(),
-                    externalVibration.getPackage(), /* reason= */ null, mStatus);
+                    externalVibration.getPackage(), /* reason= */ null);
+        }
+
+        public VibrationStats.StatsInfo getStatsInfo(long completionUptimeMillis) {
+            return new VibrationStats.StatsInfo(
+                    externalVibration.getUid(),
+                    FrameworkStatsLog.VIBRATION_REPORTED__VIBRATION_TYPE__EXTERNAL,
+                    externalVibration.getVibrationAttributes().getUsage(), mStatus, stats,
+                    completionUptimeMillis);
         }
     }
 
@@ -1500,9 +1580,11 @@
 
     /** Clears mNextVibration if set, ending it cleanly */
     @GuardedBy("mLock")
-    private void clearNextVibrationLocked(Vibration.Status endStatus) {
+    private void clearNextVibrationLocked(Vibration.EndInfo vibrationEndInfo) {
         if (mNextVibration != null) {
-            endVibrationLocked(mNextVibration.getVibration(), endStatus);
+            // Clearing next vibration before playing it, end it and report metrics right away.
+            endVibrationLocked(mNextVibration.getVibration(), vibrationEndInfo,
+                    /* shouldWriteStats= */ true);
             mNextVibration = null;
         }
     }
@@ -1510,25 +1592,25 @@
     /**
      * Ends the external vibration, and clears related service state.
      *
-     * @param status the status to end the associated Vibration with
+     * @param vibrationEndInfo the status and related info to end the associated Vibration with
      * @param continueExternalControl indicates whether external control will continue. If not, the
      *                                HAL will have external control turned off.
      */
     @GuardedBy("mLock")
-    private void endExternalVibrateLocked(Vibration.Status status,
+    private void endExternalVibrateLocked(Vibration.EndInfo vibrationEndInfo,
             boolean continueExternalControl) {
         Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "endExternalVibrateLocked");
         try {
             if (mCurrentExternalVibration == null) {
                 return;
             }
-            endVibrationLocked(mCurrentExternalVibration, status);
-            mCurrentExternalVibration.externalVibration.unlinkToDeath(
-                    mCurrentExternalVibration);
-            mCurrentExternalVibration = null;
+            mCurrentExternalVibration.unlinkToDeath();
             if (!continueExternalControl) {
-                setExternalControl(false);
+                setExternalControl(false, mCurrentExternalVibration.stats);
             }
+            // The external control was turned off, end it and report metrics right away.
+            endVibrationAndWriteStatsLocked(mCurrentExternalVibration, vibrationEndInfo);
+            mCurrentExternalVibration = null;
         } finally {
             Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
         }
@@ -1552,6 +1634,8 @@
                 return IExternalVibratorService.SCALE_MUTE;
             }
 
+            // Create Vibration.Stats as close to the received request as possible, for tracking.
+            ExternalVibrationHolder vibHolder = new ExternalVibrationHolder(vib);
             VibrationAttributes attrs = fixupVibrationAttributes(vib.getVibrationAttributes(),
                     /* effect= */ null);
             if (attrs.isFlagSet(VibrationAttributes.FLAG_INVALIDATE_SETTINGS_CACHE)) {
@@ -1562,18 +1646,17 @@
 
             boolean alreadyUnderExternalControl = false;
             boolean waitForCompletion = false;
-            int scale;
             synchronized (mLock) {
                 Vibration.Status ignoreStatus = shouldIgnoreVibrationLocked(
                         vib.getUid(), vib.getPackage(), attrs);
                 if (ignoreStatus != null) {
-                    ExternalVibrationHolder vibHolder = new ExternalVibrationHolder(vib);
                     vibHolder.scale = IExternalVibratorService.SCALE_MUTE;
-                    endVibrationLocked(vibHolder, ignoreStatus);
+                    // Failed to start the vibration, end it and report metrics right away.
+                    endVibrationAndWriteStatsLocked(vibHolder, new Vibration.EndInfo(ignoreStatus));
                     return vibHolder.scale;
                 }
                 if (mCurrentExternalVibration != null
-                        && mCurrentExternalVibration.externalVibration.equals(vib)) {
+                        && mCurrentExternalVibration.isHoldingSameVibration(vib)) {
                     // We are already playing this external vibration, so we can return the same
                     // scale calculated in the previous call to this method.
                     return mCurrentExternalVibration.scale;
@@ -1582,8 +1665,14 @@
                     // If we're not under external control right now, then cancel any normal
                     // vibration that may be playing and ready the vibrator for external control.
                     if (mCurrentVibration != null) {
-                        clearNextVibrationLocked(Vibration.Status.IGNORED_FOR_EXTERNAL);
-                        mCurrentVibration.notifyCancelled(Vibration.Status.CANCELLED_SUPERSEDED,
+                        vibHolder.stats.reportInterruptedAnotherVibration(
+                                mCurrentVibration.getVibration().attrs.getUsage());
+                        clearNextVibrationLocked(
+                                new Vibration.EndInfo(Vibration.Status.IGNORED_FOR_EXTERNAL,
+                                        vib.getUid(), attrs.getUsage()));
+                        mCurrentVibration.notifyCancelled(
+                                new Vibration.EndInfo(Vibration.Status.CANCELLED_SUPERSEDED,
+                                        vib.getUid(), attrs.getUsage()),
                                 /* immediate= */ true);
                         waitForCompletion = true;
                     }
@@ -1597,22 +1686,27 @@
                     // Note that this doesn't support multiple concurrent external controls, as we
                     // would need to mute the old one still if it came from a different controller.
                     alreadyUnderExternalControl = true;
-                    mCurrentExternalVibration.externalVibration.mute();
-                    endExternalVibrateLocked(Vibration.Status.CANCELLED_SUPERSEDED,
+                    mCurrentExternalVibration.mute();
+                    vibHolder.stats.reportInterruptedAnotherVibration(
+                            mCurrentExternalVibration.externalVibration
+                                    .getVibrationAttributes().getUsage());
+                    endExternalVibrateLocked(
+                            new Vibration.EndInfo(Vibration.Status.CANCELLED_SUPERSEDED,
+                                    vib.getUid(), attrs.getUsage()),
                             /* continueExternalControl= */ true);
                 }
-                mCurrentExternalVibration = new ExternalVibrationHolder(vib);
-                vib.linkToDeath(mCurrentExternalVibration);
-                mCurrentExternalVibration.scale = mVibrationScaler.getExternalVibrationScale(
-                        attrs.getUsage());
-                scale = mCurrentExternalVibration.scale;
+                mCurrentExternalVibration = vibHolder;
+                vibHolder.linkToDeath();
+                vibHolder.scale = mVibrationScaler.getExternalVibrationScale(attrs.getUsage());
             }
 
             if (waitForCompletion) {
                 if (!mVibrationThread.waitForThreadIdle(VIBRATION_CANCEL_WAIT_MILLIS)) {
                     Slog.e(TAG, "Timed out waiting for vibration to cancel");
                     synchronized (mLock) {
-                        endExternalVibrateLocked(Vibration.Status.IGNORED_ERROR_CANCELLING,
+                        // Trigger endExternalVibrateLocked to unlink to death recipient.
+                        endExternalVibrateLocked(
+                                new Vibration.EndInfo(Vibration.Status.IGNORED_ERROR_CANCELLING),
                                 /* continueExternalControl= */ false);
                     }
                     return IExternalVibratorService.SCALE_MUTE;
@@ -1622,23 +1716,27 @@
                 if (DEBUG) {
                     Slog.d(TAG, "Vibrator going under external control.");
                 }
-                setExternalControl(true);
+                setExternalControl(true, vibHolder.stats);
             }
             if (DEBUG) {
                 Slog.e(TAG, "Playing external vibration: " + vib);
             }
-            return scale;
+            // Vibrator will start receiving data from external channels after this point.
+            // Report current time as the vibration start time, for debugging.
+            vibHolder.stats.reportStarted();
+            return vibHolder.scale;
         }
 
         @Override
         public void onExternalVibrationStop(ExternalVibration vib) {
             synchronized (mLock) {
                 if (mCurrentExternalVibration != null
-                        && mCurrentExternalVibration.externalVibration.equals(vib)) {
+                        && mCurrentExternalVibration.isHoldingSameVibration(vib)) {
                     if (DEBUG) {
                         Slog.e(TAG, "Stopping external vibration" + vib);
                     }
-                    endExternalVibrateLocked(Vibration.Status.FINISHED,
+                    endExternalVibrateLocked(
+                            new Vibration.EndInfo(Vibration.Status.FINISHED),
                             /* continueExternalControl= */ false);
                 }
             }
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 3a2c9c8..208450d 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -5649,7 +5649,7 @@
         ProtoLog.v(WM_DEBUG_ADD_REMOVE, "notifyAppResumed: wasStopped=%b %s",
                 wasStopped, this);
         mAppStopped = false;
-        // Allow the window to turn the screen on once the app is resumed again.
+        // Allow the window to turn the screen on once the app is started and resumed.
         if (mAtmService.getActivityStartController().isInExecution()) {
             setCurrentLaunchCanTurnScreenOn(true);
         }
diff --git a/services/core/java/com/android/server/wm/ActivityStartController.java b/services/core/java/com/android/server/wm/ActivityStartController.java
index 9be9340..7c25b53 100644
--- a/services/core/java/com/android/server/wm/ActivityStartController.java
+++ b/services/core/java/com/android/server/wm/ActivityStartController.java
@@ -94,6 +94,7 @@
 
     boolean mCheckedForSetup = false;
 
+    /** Whether an {@link ActivityStarter} is currently executing (starting an Activity). */
     private boolean mInExecution = false;
 
     /**
@@ -129,7 +130,7 @@
         return mFactory.obtain().setIntent(intent).setReason(reason);
     }
 
-    void onExecutionStarted(ActivityStarter starter) {
+    void onExecutionStarted() {
         mInExecution = true;
     }
 
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index 99b34c7..f9e59c8 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -131,6 +131,7 @@
 import com.android.internal.app.HeavyWeightSwitcherActivity;
 import com.android.internal.app.IVoiceInteractor;
 import com.android.internal.protolog.common.ProtoLog;
+import com.android.internal.util.FrameworkStatsLog;
 import com.android.server.am.PendingIntentRecord;
 import com.android.server.pm.InstantAppResolver;
 import com.android.server.power.ShutdownCheckPoints;
@@ -1268,7 +1269,7 @@
     }
 
     private void onExecutionStarted() {
-        mController.onExecutionStarted(this);
+        mController.onExecutionStarted();
     }
 
     private boolean isHomeApp(int uid, @Nullable String packageName) {
@@ -2100,6 +2101,49 @@
             }
         }
 
+        // Log activity starts which violate one of the following rules of the
+        // activity security model (ASM):
+        // 1. Only the top activity on a task can start activities on that task
+        // 2. Only the top activity on the top task can create new (top) tasks
+        // We don't currently block, but these checks may later become blocks
+        // TODO(b/236234252): Shift to BackgroundActivityStartController once
+        // class is ready
+        if (mSourceRecord != null) {
+            int callerUid = mSourceRecord.getUid();
+            ActivityRecord targetTopActivity =
+                    targetTask != null ? targetTask.getTopNonFinishingActivity() : null;
+            boolean passesAsmChecks = newTask
+                    ? mService.mVisibleActivityProcessTracker.hasResumedActivity(callerUid)
+                    : targetTopActivity != null && targetTopActivity.getUid() == callerUid;
+
+            if (!passesAsmChecks) {
+                Slog.i(TAG, "Launching r: " + r
+                        + " from background: " + mSourceRecord
+                        + ". New task: " + newTask);
+                boolean newOrEmptyTask = newTask || (targetTopActivity == null);
+                FrameworkStatsLog.write(FrameworkStatsLog.ACTIVITY_ACTION_BLOCKED,
+                        /* caller_uid */
+                        callerUid,
+                        /* caller_activity_class_name */
+                        mSourceRecord.info.name,
+                        /* target_task_top_activity_uid */
+                        newOrEmptyTask ? -1 : targetTopActivity.getUid(),
+                        /* target_task_top_activity_class_name */
+                        newOrEmptyTask ? null : targetTopActivity.info.name,
+                        /* target_task_is_different */
+                        newTask || !mSourceRecord.getTask().equals(targetTask),
+                        /* target_activity_uid */
+                        r.getUid(),
+                        /* target_activity_class_name */
+                        r.info.name,
+                        /* target_intent_action */
+                        r.intent.getAction(),
+                        /* target_intent_flags */
+                        r.intent.getFlags()
+                );
+            }
+        }
+
         return START_SUCCESS;
     }
 
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index cbe56eb..ecb9fe3 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -53,7 +53,6 @@
 import static android.view.Surface.ROTATION_270;
 import static android.view.Surface.ROTATION_90;
 import static android.view.View.GONE;
-import static android.view.ViewRootImpl.LOCAL_LAYOUT;
 import static android.view.WindowInsets.Type.displayCutout;
 import static android.view.WindowInsets.Type.ime;
 import static android.view.WindowInsets.Type.systemBars;
@@ -2707,25 +2706,22 @@
         mCurrentPrivacyIndicatorBounds =
                 mCurrentPrivacyIndicatorBounds.updateStaticBounds(staticBounds);
         if (!Objects.equals(oldBounds, mCurrentPrivacyIndicatorBounds)) {
-            updateDisplayFrames(false /* insetsSourceMayChange */, true /* notifyInsetsChange */);
+            updateDisplayFrames(true /* notifyInsetsChange */);
         }
     }
 
     void onDisplayInfoChanged() {
-        updateDisplayFrames(LOCAL_LAYOUT, LOCAL_LAYOUT);
+        updateDisplayFrames(false /* notifyInsetsChange */);
         mMinSizeOfResizeableTaskDp = getMinimalTaskSizeDp();
         mInputMonitor.layoutInputConsumers(mDisplayInfo.logicalWidth, mDisplayInfo.logicalHeight);
         mDisplayPolicy.onDisplayInfoChanged(mDisplayInfo);
     }
 
-    private void updateDisplayFrames(boolean insetsSourceMayChange, boolean notifyInsetsChange) {
+    private void updateDisplayFrames(boolean notifyInsetsChange) {
         if (mDisplayFrames.update(mDisplayInfo,
                 calculateDisplayCutoutForRotation(mDisplayInfo.rotation),
                 calculateRoundedCornersForRotation(mDisplayInfo.rotation),
                 calculatePrivacyIndicatorBoundsForRotation(mDisplayInfo.rotation))) {
-            if (insetsSourceMayChange) {
-                mDisplayPolicy.updateInsetsSourceFramesExceptIme(mDisplayFrames);
-            }
             mInsetsStateController.onDisplayFramesUpdated(notifyInsetsChange);
         }
     }
diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java
index 0769406..5221072 100644
--- a/services/core/java/com/android/server/wm/DisplayPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayPolicy.java
@@ -1576,19 +1576,6 @@
         }
     }
 
-    void updateInsetsSourceFramesExceptIme(DisplayFrames displayFrames) {
-        sTmpClientFrames.attachedFrame = null;
-        for (int i = mInsetsSourceWindowsExceptIme.size() - 1; i >= 0; i--) {
-            final WindowState win = mInsetsSourceWindowsExceptIme.valueAt(i);
-            mWindowLayout.computeFrames(win.mAttrs.forRotation(displayFrames.mRotation),
-                    displayFrames.mInsetsState, displayFrames.mDisplayCutoutSafe,
-                    displayFrames.mUnrestricted, win.getWindowingMode(), UNSPECIFIED_LENGTH,
-                    UNSPECIFIED_LENGTH, win.getRequestedVisibilities(), win.mGlobalScale,
-                    sTmpClientFrames);
-            win.updateSourceFrame(sTmpClientFrames.frame);
-        }
-    }
-
     void onDisplayInfoChanged(DisplayInfo info) {
         mSystemGestures.onDisplayInfoChanged(info);
     }
diff --git a/services/core/java/com/android/server/wm/Session.java b/services/core/java/com/android/server/wm/Session.java
index 0128c18..fb68fe6 100644
--- a/services/core/java/com/android/server/wm/Session.java
+++ b/services/core/java/com/android/server/wm/Session.java
@@ -242,16 +242,17 @@
 
     @Override
     public int relayout(IWindow window, WindowManager.LayoutParams attrs,
-            int requestedWidth, int requestedHeight, int viewFlags, int flags,
-            ClientWindowFrames outFrames, MergedConfiguration mergedConfiguration,
-            SurfaceControl outSurfaceControl, InsetsState outInsetsState,
-            InsetsSourceControl[] outActiveControls, Bundle outSyncSeqIdBundle) {
+            int requestedWidth, int requestedHeight, int viewFlags, int flags, int seq,
+            int lastSyncSeqId, ClientWindowFrames outFrames,
+            MergedConfiguration mergedConfiguration, SurfaceControl outSurfaceControl,
+            InsetsState outInsetsState, InsetsSourceControl[] outActiveControls,
+            Bundle outSyncSeqIdBundle) {
         if (false) Slog.d(TAG_WM, ">>>>>> ENTERED relayout from "
                 + Binder.getCallingPid());
         Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, mRelayoutTag);
         int res = mService.relayoutWindow(this, window, attrs,
-                requestedWidth, requestedHeight, viewFlags, flags,
-                outFrames, mergedConfiguration, outSurfaceControl, outInsetsState,
+                requestedWidth, requestedHeight, viewFlags, flags, seq,
+                lastSyncSeqId, outFrames, mergedConfiguration, outSurfaceControl, outInsetsState,
                 outActiveControls, outSyncSeqIdBundle);
         Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
         if (false) Slog.d(TAG_WM, "<<<<<< EXITING relayout to "
@@ -260,6 +261,16 @@
     }
 
     @Override
+    public void relayoutAsync(IWindow window, WindowManager.LayoutParams attrs,
+            int requestedWidth, int requestedHeight, int viewFlags, int flags, int seq,
+            int lastSyncSeqId) {
+        relayout(window, attrs, requestedWidth, requestedHeight, viewFlags, flags, seq,
+                lastSyncSeqId, null /* outFrames */, null /* mergedConfiguration */,
+                null /* outSurfaceControl */, null /* outInsetsState */,
+                null /* outActiveControls */, null /* outSyncIdBundle */);
+    }
+
+    @Override
     public boolean outOfMemory(IWindow window) {
         return mService.outOfMemoryWindow(this, window);
     }
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 1e79b03..1397977 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -2246,11 +2246,14 @@
     }
 
     public int relayoutWindow(Session session, IWindow client, LayoutParams attrs,
-            int requestedWidth, int requestedHeight, int viewVisibility, int flags,
-            ClientWindowFrames outFrames, MergedConfiguration mergedConfiguration,
-            SurfaceControl outSurfaceControl, InsetsState outInsetsState,
-            InsetsSourceControl[] outActiveControls, Bundle outSyncIdBundle) {
-        Arrays.fill(outActiveControls, null);
+            int requestedWidth, int requestedHeight, int viewVisibility, int flags, int seq,
+            int lastSyncSeqId, ClientWindowFrames outFrames,
+            MergedConfiguration outMergedConfiguration, SurfaceControl outSurfaceControl,
+            InsetsState outInsetsState, InsetsSourceControl[] outActiveControls,
+            Bundle outSyncIdBundle) {
+        if (outActiveControls != null) {
+            Arrays.fill(outActiveControls, null);
+        }
         int result = 0;
         boolean configChanged;
         final int pid = Binder.getCallingPid();
@@ -2261,8 +2264,15 @@
             if (win == null) {
                 return 0;
             }
+            if (win.mRelayoutSeq < seq) {
+                win.mRelayoutSeq = seq;
+            } else if (win.mRelayoutSeq > seq) {
+                return 0;
+            }
 
-            if (win.cancelAndRedraw() && win.mPrepareSyncSeqId <= win.mLastSeqIdSentToRelayout) {
+            if (win.cancelAndRedraw() && win.mPrepareSyncSeqId <= lastSyncSeqId) {
+                // The client has reported the sync draw, but we haven't finished it yet.
+                // Don't let the client perform a non-sync draw at this time.
                 result |= RELAYOUT_RES_CANCEL_AND_REDRAW;
             }
 
@@ -2431,7 +2441,7 @@
 
             // Create surfaceControl before surface placement otherwise layout will be skipped
             // (because WS.isGoneForLayout() is true when there is no surface.
-            if (shouldRelayout) {
+            if (shouldRelayout && outSurfaceControl != null) {
                 try {
                     result = createSurfaceControl(outSurfaceControl, result, win, winAnimator);
                 } catch (Exception e) {
@@ -2470,22 +2480,25 @@
                 winAnimator.mEnterAnimationPending = false;
                 winAnimator.mEnteringAnimation = false;
 
-                if (viewVisibility == View.VISIBLE && winAnimator.hasSurface()) {
-                    // We already told the client to go invisible, but the message may not be
-                    // handled yet, or it might want to draw a last frame. If we already have a
-                    // surface, let the client use that, but don't create new surface at this point.
-                    Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "relayoutWindow: getSurface");
-                    winAnimator.mSurfaceController.getSurfaceControl(outSurfaceControl);
-                    Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
-                } else {
-                    if (DEBUG_VISIBILITY) Slog.i(TAG_WM, "Releasing surface in: " + win);
-
-                    try {
-                        Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "wmReleaseOutSurface_"
-                                + win.mAttrs.getTitle());
-                        outSurfaceControl.release();
-                    } finally {
+                if (outSurfaceControl != null) {
+                    if (viewVisibility == View.VISIBLE && winAnimator.hasSurface()) {
+                        // We already told the client to go invisible, but the message may not be
+                        // handled yet, or it might want to draw a last frame. If we already have a
+                        // surface, let the client use that, but don't create new surface at this
+                        // point.
+                        Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "relayoutWindow: getSurface");
+                        winAnimator.mSurfaceController.getSurfaceControl(outSurfaceControl);
                         Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
+                    } else {
+                        if (DEBUG_VISIBILITY) Slog.i(TAG_WM, "Releasing surface in: " + win);
+
+                        try {
+                            Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "wmReleaseOutSurface_"
+                                    + win.mAttrs.getTitle());
+                            outSurfaceControl.release();
+                        } finally {
+                            Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
+                        }
                     }
                 }
 
@@ -2538,20 +2551,16 @@
                 win.mResizedWhileGone = false;
             }
 
-            win.fillClientWindowFramesAndConfiguration(outFrames, mergedConfiguration,
-                    false /* useLatestConfig */, shouldRelayout);
+            if (outFrames != null && outMergedConfiguration != null) {
+                win.fillClientWindowFramesAndConfiguration(outFrames, outMergedConfiguration,
+                        false /* useLatestConfig */, shouldRelayout);
 
-            // Set resize-handled here because the values are sent back to the client.
-            win.onResizeHandled();
+                // Set resize-handled here because the values are sent back to the client.
+                win.onResizeHandled();
+            }
 
-            outInsetsState.set(win.getCompatInsetsState(), win.isClientLocal());
-            if (DEBUG) {
-                Slog.v(TAG_WM, "Relayout given client " + client.asBinder()
-                        + ", requestedWidth=" + requestedWidth
-                        + ", requestedHeight=" + requestedHeight
-                        + ", viewVisibility=" + viewVisibility
-                        + "\nRelayout returning frame=" + outFrames.frame
-                        + ", surface=" + outSurfaceControl);
+            if (outInsetsState != null) {
+                outInsetsState.set(win.getCompatInsetsState(), win.isClientLocal());
             }
 
             ProtoLog.v(WM_DEBUG_FOCUS, "Relayout of %s: focusMayChange=%b",
@@ -2562,14 +2571,16 @@
             }
             win.mInRelayout = false;
 
-            if (mUseBLASTSync && win.useBLASTSync() && viewVisibility != View.GONE
-                    && (win.mSyncSeqId > win.mLastSeqIdSentToRelayout)) {
-                win.markRedrawForSyncReported();
-
-                win.mLastSeqIdSentToRelayout = win.mSyncSeqId;
-                outSyncIdBundle.putInt("seqid", win.mSyncSeqId);
-            } else {
-                outSyncIdBundle.putInt("seqid", -1);
+            if (outSyncIdBundle != null) {
+                final int maybeSyncSeqId;
+                if (mUseBLASTSync && win.useBLASTSync() && viewVisibility != View.GONE
+                        && win.mSyncSeqId > lastSyncSeqId) {
+                    maybeSyncSeqId = win.mSyncSeqId;
+                    win.markRedrawForSyncReported();
+                } else {
+                    maybeSyncSeqId = -1;
+                }
+                outSyncIdBundle.putInt("seqid", maybeSyncSeqId);
             }
 
             if (configChanged) {
@@ -2578,7 +2589,9 @@
                 displayContent.sendNewConfiguration();
                 Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
             }
-            getInsetsSourceControls(win, outActiveControls);
+            if (outActiveControls != null) {
+                getInsetsSourceControls(win, outActiveControls);
+            }
         }
 
         Binder.restoreCallingIdentity(origId);
@@ -2614,27 +2627,32 @@
             transit = WindowManagerPolicy.TRANSIT_PREVIEW_DONE;
         }
 
-        String reason = null;
-        if (win.isWinVisibleLw() && winAnimator.applyAnimationLocked(transit, false)) {
-            reason = "applyAnimation";
-            focusMayChange = true;
-            win.mAnimatingExit = true;
-        } else if (win.mDisplayContent.okToAnimate() && win.isExitAnimationRunningSelfOrParent()) {
-            // Currently in a hide animation... turn this into
-            // an exit.
-            win.mAnimatingExit = true;
-        } else if (win.mDisplayContent.okToAnimate()
-                && win.mDisplayContent.mWallpaperController.isWallpaperTarget(win)
-                && win.mAttrs.type != TYPE_NOTIFICATION_SHADE) {
-            reason = "isWallpaperTarget";
-            // If the wallpaper is currently behind this app window, we need to change both of them
-            // inside of a transaction to avoid artifacts.
-            // For NotificationShade, sysui is in charge of running window animation and it updates
-            // the client view visibility only after both NotificationShade and the wallpaper are
-            // hidden. So we don't need to care about exit animation, but can destroy its surface
-            // immediately.
-            win.mAnimatingExit = true;
-        } else {
+        if (win.isWinVisibleLw() && win.mDisplayContent.okToAnimate()) {
+            String reason = null;
+            if (winAnimator.applyAnimationLocked(transit, false)) {
+                reason = "applyAnimation";
+                focusMayChange = true;
+                win.mAnimatingExit = true;
+            } else if (win.isExitAnimationRunningSelfOrParent()) {
+                reason = "animating";
+                win.mAnimatingExit = true;
+            } else if (win.mDisplayContent.mWallpaperController.isWallpaperTarget(win)
+                    && win.mAttrs.type != TYPE_NOTIFICATION_SHADE) {
+                reason = "isWallpaperTarget";
+                // If the wallpaper is currently behind this app window, they should be updated
+                // in a transaction to avoid artifacts.
+                // For NotificationShade, sysui is in charge of running window animation and it
+                // updates the client view visibility only after both NotificationShade and the
+                // wallpaper are hidden. So the exit animation is not needed and can destroy its
+                // surface immediately.
+                win.mAnimatingExit = true;
+            }
+            if (reason != null) {
+                ProtoLog.d(WM_DEBUG_ANIM,
+                        "Set animatingExit: reason=startExitingAnimation/%s win=%s", reason, win);
+            }
+        }
+        if (!win.mAnimatingExit) {
             boolean stopped = win.mActivityRecord == null || win.mActivityRecord.mAppStopped;
             // We set mDestroying=true so ActivityRecord#notifyAppStopped in-to destroy surfaces
             // will later actually destroy the surface if we do not do so here. Normally we leave
@@ -2642,10 +2660,6 @@
             win.mDestroying = true;
             win.destroySurface(false, stopped);
         }
-        if (reason != null) {
-            ProtoLog.d(WM_DEBUG_ANIM, "Set animatingExit: reason=startExitingAnimation/%s win=%s",
-                    reason, win);
-        }
         if (mAccessibilityController.hasCallbacks()) {
             mAccessibilityController.onWindowTransition(win, transit);
         }
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 86fa356..41bcbf6 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -390,7 +390,6 @@
      * examine the git commit message introducing this comment and variable.2
      */
     int mSyncSeqId = 0;
-    int mLastSeqIdSentToRelayout = 0;
 
     /** The last syncId associated with a prepareSync or 0 when no sync is active. */
     int mPrepareSyncSeqId = 0;
@@ -426,6 +425,7 @@
     boolean mHaveFrame;
     boolean mObscured;
 
+    int mRelayoutSeq = -1;
     int mLayoutSeq = -1;
 
     /**
@@ -1350,29 +1350,15 @@
         final WindowFrames windowFrames = mWindowFrames;
         mTmpRect.set(windowFrames.mParentFrame);
 
-        if (LOCAL_LAYOUT) {
-            windowFrames.mCompatFrame.set(clientWindowFrames.frame);
+        windowFrames.mDisplayFrame.set(clientWindowFrames.displayFrame);
+        windowFrames.mParentFrame.set(clientWindowFrames.parentFrame);
+        windowFrames.mFrame.set(clientWindowFrames.frame);
 
-            windowFrames.mFrame.set(clientWindowFrames.frame);
-            windowFrames.mDisplayFrame.set(clientWindowFrames.displayFrame);
-            windowFrames.mParentFrame.set(clientWindowFrames.parentFrame);
-            if (mGlobalScale != 1f) {
-                // The frames sent from the client need to be adjusted to the real coordinate space.
-                windowFrames.mFrame.scale(mGlobalScale);
-                windowFrames.mDisplayFrame.scale(mGlobalScale);
-                windowFrames.mParentFrame.scale(mGlobalScale);
-            }
-        } else {
-            windowFrames.mDisplayFrame.set(clientWindowFrames.displayFrame);
-            windowFrames.mParentFrame.set(clientWindowFrames.parentFrame);
-            windowFrames.mFrame.set(clientWindowFrames.frame);
-
-            windowFrames.mCompatFrame.set(windowFrames.mFrame);
-            if (mInvGlobalScale != 1f) {
-                // Also, the scaled frame that we report to the app needs to be adjusted to be in
-                // its coordinate space.
-                windowFrames.mCompatFrame.scale(mInvGlobalScale);
-            }
+        windowFrames.mCompatFrame.set(windowFrames.mFrame);
+        if (mInvGlobalScale != 1f) {
+            // Also, the scaled frame that we report to the app needs to be adjusted to be in
+            // its coordinate space.
+            windowFrames.mCompatFrame.scale(mInvGlobalScale);
         }
         windowFrames.setParentFrameWasClippedByDisplayCutout(
                 clientWindowFrames.isParentFrameClippedByDisplayCutout);
@@ -1416,13 +1402,6 @@
 
         updateSourceFrame(windowFrames.mFrame);
 
-        if (LOCAL_LAYOUT) {
-            if (!mHaveFrame) {
-                // The first frame should not be considered as moved.
-                updateLastFrames();
-            }
-        }
-
         if (mActivityRecord != null && !mIsChildWindow) {
             mActivityRecord.layoutLetterbox(this);
         }
diff --git a/services/people/java/com/android/server/people/data/ConversationInfo.java b/services/people/java/com/android/server/people/data/ConversationInfo.java
index 16c4c29..6ead44a 100644
--- a/services/people/java/com/android/server/people/data/ConversationInfo.java
+++ b/services/people/java/com/android/server/people/data/ConversationInfo.java
@@ -26,6 +26,7 @@
 import android.content.pm.ShortcutInfo.ShortcutFlags;
 import android.net.Uri;
 import android.text.TextUtils;
+import android.util.Log;
 import android.util.Slog;
 import android.util.proto.ProtoInputStream;
 import android.util.proto.ProtoOutputStream;
@@ -37,6 +38,7 @@
 import java.io.ByteArrayOutputStream;
 import java.io.DataInputStream;
 import java.io.DataOutputStream;
+import java.io.EOFException;
 import java.io.IOException;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -50,6 +52,11 @@
  * Represents a conversation that is provided by the app based on {@link ShortcutInfo}.
  */
 public class ConversationInfo {
+    private static final boolean DEBUG = false;
+
+    // Schema version for the backup payload. Must be incremented whenever fields are added in
+    // backup payload.
+    private static final int VERSION = 1;
 
     private static final String TAG = ConversationInfo.class.getSimpleName();
 
@@ -100,6 +107,8 @@
 
     private long mLastEventTimestamp;
 
+    private long mCreationTimestamp;
+
     @ShortcutFlags
     private int mShortcutFlags;
 
@@ -116,6 +125,7 @@
         mNotificationChannelId = builder.mNotificationChannelId;
         mParentNotificationChannelId = builder.mParentNotificationChannelId;
         mLastEventTimestamp = builder.mLastEventTimestamp;
+        mCreationTimestamp = builder.mCreationTimestamp;
         mShortcutFlags = builder.mShortcutFlags;
         mConversationFlags = builder.mConversationFlags;
         mCurrStatuses = builder.mCurrStatuses;
@@ -170,6 +180,13 @@
         return mLastEventTimestamp;
     }
 
+    /**
+     * Timestamp of the creation of the conversationInfo.
+     */
+    long getCreationTimestamp() {
+        return mCreationTimestamp;
+    }
+
     /** Whether the shortcut for this conversation is set long-lived by the app. */
     public boolean isShortcutLongLived() {
         return hasShortcutFlags(ShortcutInfo.FLAG_LONG_LIVED);
@@ -241,6 +258,7 @@
                 && Objects.equals(mNotificationChannelId, other.mNotificationChannelId)
                 && Objects.equals(mParentNotificationChannelId, other.mParentNotificationChannelId)
                 && Objects.equals(mLastEventTimestamp, other.mLastEventTimestamp)
+                && mCreationTimestamp == other.mCreationTimestamp
                 && mShortcutFlags == other.mShortcutFlags
                 && mConversationFlags == other.mConversationFlags
                 && Objects.equals(mCurrStatuses, other.mCurrStatuses);
@@ -250,7 +268,7 @@
     public int hashCode() {
         return Objects.hash(mShortcutId, mLocusId, mContactUri, mContactPhoneNumber,
                 mNotificationChannelId, mParentNotificationChannelId, mLastEventTimestamp,
-                mShortcutFlags, mConversationFlags, mCurrStatuses);
+                mCreationTimestamp, mShortcutFlags, mConversationFlags, mCurrStatuses);
     }
 
     @Override
@@ -264,6 +282,7 @@
         sb.append(", notificationChannelId=").append(mNotificationChannelId);
         sb.append(", parentNotificationChannelId=").append(mParentNotificationChannelId);
         sb.append(", lastEventTimestamp=").append(mLastEventTimestamp);
+        sb.append(", creationTimestamp=").append(mCreationTimestamp);
         sb.append(", statuses=").append(mCurrStatuses);
         sb.append(", shortcutFlags=0x").append(Integer.toHexString(mShortcutFlags));
         sb.append(" [");
@@ -329,6 +348,7 @@
                     mParentNotificationChannelId);
         }
         protoOutputStream.write(ConversationInfoProto.LAST_EVENT_TIMESTAMP, mLastEventTimestamp);
+        protoOutputStream.write(ConversationInfoProto.CREATION_TIMESTAMP, mCreationTimestamp);
         protoOutputStream.write(ConversationInfoProto.SHORTCUT_FLAGS, mShortcutFlags);
         protoOutputStream.write(ConversationInfoProto.CONVERSATION_FLAGS, mConversationFlags);
         if (mContactPhoneNumber != null) {
@@ -352,6 +372,8 @@
             out.writeUTF(mContactPhoneNumber != null ? mContactPhoneNumber : "");
             out.writeUTF(mParentNotificationChannelId != null ? mParentNotificationChannelId : "");
             out.writeLong(mLastEventTimestamp);
+            out.writeInt(VERSION);
+            out.writeLong(mCreationTimestamp);
             // ConversationStatus is a transient object and not persisted
         } catch (IOException e) {
             Slog.e(TAG, "Failed to write fields to backup payload.", e);
@@ -399,6 +421,9 @@
                     builder.setLastEventTimestamp(protoInputStream.readLong(
                             ConversationInfoProto.LAST_EVENT_TIMESTAMP));
                     break;
+                case (int) ConversationInfoProto.CREATION_TIMESTAMP:
+                    builder.setCreationTimestamp(protoInputStream.readLong(
+                            ConversationInfoProto.CREATION_TIMESTAMP));
                 case (int) ConversationInfoProto.SHORTCUT_FLAGS:
                     builder.setShortcutFlags(protoInputStream.readInt(
                             ConversationInfoProto.SHORTCUT_FLAGS));
@@ -448,6 +473,10 @@
                 builder.setParentNotificationChannelId(parentNotificationChannelId);
             }
             builder.setLastEventTimestamp(in.readLong());
+            int payloadVersion = maybeReadVersion(in);
+            if (payloadVersion == 1) {
+                builder.setCreationTimestamp(in.readLong());
+            }
         } catch (IOException e) {
             Slog.e(TAG, "Failed to read conversation info fields from backup payload.", e);
             return null;
@@ -455,6 +484,16 @@
         return builder.build();
     }
 
+    private static int maybeReadVersion(DataInputStream in) throws IOException {
+        try {
+            return in.readInt();
+        } catch (EOFException eofException) {
+            // EOF is expected if using old backup payload protocol.
+            if (DEBUG) Log.d(TAG, "Eof reached for data stream, missing version number");
+            return 0;
+        }
+    }
+
     /**
      * Builder class for {@link ConversationInfo} objects.
      */
@@ -479,6 +518,8 @@
 
         private long mLastEventTimestamp;
 
+        private long mCreationTimestamp;
+
         @ShortcutFlags
         private int mShortcutFlags;
 
@@ -502,6 +543,7 @@
             mNotificationChannelId = conversationInfo.mNotificationChannelId;
             mParentNotificationChannelId = conversationInfo.mParentNotificationChannelId;
             mLastEventTimestamp = conversationInfo.mLastEventTimestamp;
+            mCreationTimestamp = conversationInfo.mCreationTimestamp;
             mShortcutFlags = conversationInfo.mShortcutFlags;
             mConversationFlags = conversationInfo.mConversationFlags;
             mCurrStatuses = conversationInfo.mCurrStatuses;
@@ -542,6 +584,11 @@
             return this;
         }
 
+        Builder setCreationTimestamp(long creationTimestamp) {
+            mCreationTimestamp = creationTimestamp;
+            return this;
+        }
+
         Builder setShortcutFlags(@ShortcutFlags int shortcutFlags) {
             mShortcutFlags = shortcutFlags;
             return this;
diff --git a/services/people/java/com/android/server/people/data/DataManager.java b/services/people/java/com/android/server/people/data/DataManager.java
index d305fc5..693f3a0 100644
--- a/services/people/java/com/android/server/people/data/DataManager.java
+++ b/services/people/java/com/android/server/people/data/DataManager.java
@@ -816,10 +816,18 @@
     }
 
     private boolean isCachedRecentConversation(ConversationInfo conversationInfo) {
+        return isEligibleForCleanUp(conversationInfo)
+                && conversationInfo.getLastEventTimestamp() > 0L;
+    }
+
+    /**
+     * Conversations that are cached and not customized are eligible for clean-up, even if they
+     * don't have an associated notification event with them.
+     */
+    private boolean isEligibleForCleanUp(ConversationInfo conversationInfo) {
         return conversationInfo.isShortcutCachedForNotification()
                 && Objects.equals(conversationInfo.getNotificationChannelId(),
-                conversationInfo.getParentNotificationChannelId())
-                && conversationInfo.getLastEventTimestamp() > 0L;
+                conversationInfo.getParentNotificationChannelId());
     }
 
     private boolean hasActiveNotifications(String packageName, @UserIdInt int userId,
@@ -842,14 +850,14 @@
         }
         // pair of <package name, conversation info>
         List<Pair<String, ConversationInfo>> cachedConvos = new ArrayList<>();
-        userData.forAllPackages(packageData ->
+        userData.forAllPackages(packageData -> {
                 packageData.forAllConversations(conversationInfo -> {
-                    if (isCachedRecentConversation(conversationInfo)) {
+                    if (isEligibleForCleanUp(conversationInfo)) {
                         cachedConvos.add(
                                 Pair.create(packageData.getPackageName(), conversationInfo));
                     }
-                })
-        );
+                });
+        });
         if (cachedConvos.size() <= targetCachedCount) {
             return;
         }
@@ -858,7 +866,9 @@
         PriorityQueue<Pair<String, ConversationInfo>> maxHeap = new PriorityQueue<>(
                 numToUncache + 1,
                 Comparator.comparingLong((Pair<String, ConversationInfo> pair) ->
-                        pair.second.getLastEventTimestamp()).reversed());
+                        Math.max(
+                            pair.second.getLastEventTimestamp(),
+                            pair.second.getCreationTimestamp())).reversed());
         for (Pair<String, ConversationInfo> cached : cachedConvos) {
             if (hasActiveNotifications(cached.first, userId, cached.second.getShortcutId())) {
                 continue;
@@ -893,7 +903,7 @@
         }
         ConversationInfo.Builder builder = oldConversationInfo != null
                 ? new ConversationInfo.Builder(oldConversationInfo)
-                : new ConversationInfo.Builder();
+                : new ConversationInfo.Builder().setCreationTimestamp(System.currentTimeMillis());
 
         builder.setShortcutId(shortcutInfo.getId());
         builder.setLocusId(shortcutInfo.getLocusId());
@@ -1326,7 +1336,8 @@
         }
     }
 
-    private void updateConversationStoreThenNotifyListeners(ConversationStore cs,
+    @VisibleForTesting
+    void updateConversationStoreThenNotifyListeners(ConversationStore cs,
             ConversationInfo modifiedConv,
             String packageName, int userId) {
         cs.addOrUpdate(modifiedConv);
diff --git a/services/tests/servicestests/src/com/android/server/people/data/ConversationInfoTest.java b/services/tests/servicestests/src/com/android/server/people/data/ConversationInfoTest.java
index 8139310..c90064e 100644
--- a/services/tests/servicestests/src/com/android/server/people/data/ConversationInfoTest.java
+++ b/services/tests/servicestests/src/com/android/server/people/data/ConversationInfoTest.java
@@ -58,6 +58,7 @@
                 .setNotificationChannelId(NOTIFICATION_CHANNEL_ID)
                 .setParentNotificationChannelId(PARENT_NOTIFICATION_CHANNEL_ID)
                 .setLastEventTimestamp(100L)
+                .setCreationTimestamp(200L)
                 .setShortcutFlags(ShortcutInfo.FLAG_LONG_LIVED
                         | ShortcutInfo.FLAG_CACHED_NOTIFICATIONS)
                 .setImportant(true)
@@ -79,6 +80,7 @@
         assertEquals(PARENT_NOTIFICATION_CHANNEL_ID,
                 conversationInfo.getParentNotificationChannelId());
         assertEquals(100L, conversationInfo.getLastEventTimestamp());
+        assertEquals(200L, conversationInfo.getCreationTimestamp());
         assertTrue(conversationInfo.isShortcutLongLived());
         assertTrue(conversationInfo.isShortcutCachedForNotification());
         assertTrue(conversationInfo.isImportant());
@@ -105,6 +107,7 @@
         assertNull(conversationInfo.getNotificationChannelId());
         assertNull(conversationInfo.getParentNotificationChannelId());
         assertEquals(0L, conversationInfo.getLastEventTimestamp());
+        assertEquals(0L, conversationInfo.getCreationTimestamp());
         assertFalse(conversationInfo.isShortcutLongLived());
         assertFalse(conversationInfo.isShortcutCachedForNotification());
         assertFalse(conversationInfo.isImportant());
@@ -131,6 +134,7 @@
                 .setNotificationChannelId(NOTIFICATION_CHANNEL_ID)
                 .setParentNotificationChannelId(PARENT_NOTIFICATION_CHANNEL_ID)
                 .setLastEventTimestamp(100L)
+                .setCreationTimestamp(200L)
                 .setShortcutFlags(ShortcutInfo.FLAG_LONG_LIVED)
                 .setImportant(true)
                 .setNotificationSilenced(true)
@@ -154,6 +158,7 @@
         assertEquals(NOTIFICATION_CHANNEL_ID, destination.getNotificationChannelId());
         assertEquals(PARENT_NOTIFICATION_CHANNEL_ID, destination.getParentNotificationChannelId());
         assertEquals(100L, destination.getLastEventTimestamp());
+        assertEquals(200L, destination.getCreationTimestamp());
         assertTrue(destination.isShortcutLongLived());
         assertFalse(destination.isImportant());
         assertTrue(destination.isNotificationSilenced());
@@ -164,4 +169,105 @@
         assertThat(destination.getStatuses()).contains(cs);
         assertThat(destination.getStatuses()).contains(cs2);
     }
+
+    @Test
+    public void testBuildFromAnotherConversation_identicalConversation() {
+        ConversationStatus cs = new ConversationStatus.Builder("id", ACTIVITY_ANNIVERSARY).build();
+        ConversationStatus cs2 = new ConversationStatus.Builder("id2", ACTIVITY_GAME).build();
+
+        ConversationInfo source = new ConversationInfo.Builder()
+                .setShortcutId(SHORTCUT_ID)
+                .setLocusId(LOCUS_ID)
+                .setContactUri(CONTACT_URI)
+                .setContactPhoneNumber(PHONE_NUMBER)
+                .setNotificationChannelId(NOTIFICATION_CHANNEL_ID)
+                .setParentNotificationChannelId(PARENT_NOTIFICATION_CHANNEL_ID)
+                .setLastEventTimestamp(100L)
+                .setCreationTimestamp(200L)
+                .setShortcutFlags(ShortcutInfo.FLAG_LONG_LIVED)
+                .setImportant(true)
+                .setNotificationSilenced(true)
+                .setBubbled(true)
+                .setPersonImportant(true)
+                .setPersonBot(true)
+                .setContactStarred(true)
+                .addOrUpdateStatus(cs)
+                .addOrUpdateStatus(cs2)
+                .build();
+
+        ConversationInfo destination = new ConversationInfo.Builder(source).build();
+
+        assertEquals(SHORTCUT_ID, destination.getShortcutId());
+        assertEquals(LOCUS_ID, destination.getLocusId());
+        assertEquals(CONTACT_URI, destination.getContactUri());
+        assertEquals(PHONE_NUMBER, destination.getContactPhoneNumber());
+        assertEquals(NOTIFICATION_CHANNEL_ID, destination.getNotificationChannelId());
+        assertEquals(PARENT_NOTIFICATION_CHANNEL_ID, destination.getParentNotificationChannelId());
+        assertEquals(100L, destination.getLastEventTimestamp());
+        assertEquals(200L, destination.getCreationTimestamp());
+        assertTrue(destination.isShortcutLongLived());
+        assertTrue(destination.isImportant());
+        assertTrue(destination.isNotificationSilenced());
+        assertTrue(destination.isBubbled());
+        assertTrue(destination.isPersonImportant());
+        assertTrue(destination.isPersonBot());
+        assertTrue(destination.isContactStarred());
+        assertThat(destination.getStatuses()).contains(cs);
+        assertThat(destination.getStatuses()).contains(cs2);
+        // Also check equals() implementation
+        assertTrue(source.equals(destination));
+        assertTrue(destination.equals(source));
+    }
+
+    @Test
+    public void testBuildFromBackupPayload() {
+        ConversationStatus cs = new ConversationStatus.Builder("id", ACTIVITY_ANNIVERSARY).build();
+        ConversationStatus cs2 = new ConversationStatus.Builder("id2", ACTIVITY_GAME).build();
+
+        ConversationInfo conversationInfo = new ConversationInfo.Builder()
+                .setShortcutId(SHORTCUT_ID)
+                .setLocusId(LOCUS_ID)
+                .setContactUri(CONTACT_URI)
+                .setContactPhoneNumber(PHONE_NUMBER)
+                .setNotificationChannelId(NOTIFICATION_CHANNEL_ID)
+                .setParentNotificationChannelId(PARENT_NOTIFICATION_CHANNEL_ID)
+                .setLastEventTimestamp(100L)
+                .setCreationTimestamp(200L)
+                .setShortcutFlags(ShortcutInfo.FLAG_LONG_LIVED
+                        | ShortcutInfo.FLAG_CACHED_NOTIFICATIONS)
+                .setImportant(true)
+                .setNotificationSilenced(true)
+                .setBubbled(true)
+                .setDemoted(true)
+                .setPersonImportant(true)
+                .setPersonBot(true)
+                .setContactStarred(true)
+                .addOrUpdateStatus(cs)
+                .addOrUpdateStatus(cs2)
+                .build();
+
+        ConversationInfo conversationInfoFromBackup =
+                ConversationInfo.readFromBackupPayload(conversationInfo.getBackupPayload());
+
+        assertEquals(SHORTCUT_ID, conversationInfoFromBackup.getShortcutId());
+        assertEquals(LOCUS_ID, conversationInfoFromBackup.getLocusId());
+        assertEquals(CONTACT_URI, conversationInfoFromBackup.getContactUri());
+        assertEquals(PHONE_NUMBER, conversationInfoFromBackup.getContactPhoneNumber());
+        assertEquals(
+                NOTIFICATION_CHANNEL_ID, conversationInfoFromBackup.getNotificationChannelId());
+        assertEquals(PARENT_NOTIFICATION_CHANNEL_ID,
+                conversationInfoFromBackup.getParentNotificationChannelId());
+        assertEquals(100L, conversationInfoFromBackup.getLastEventTimestamp());
+        assertEquals(200L, conversationInfoFromBackup.getCreationTimestamp());
+        assertTrue(conversationInfoFromBackup.isShortcutLongLived());
+        assertTrue(conversationInfoFromBackup.isShortcutCachedForNotification());
+        assertTrue(conversationInfoFromBackup.isImportant());
+        assertTrue(conversationInfoFromBackup.isNotificationSilenced());
+        assertTrue(conversationInfoFromBackup.isBubbled());
+        assertTrue(conversationInfoFromBackup.isDemoted());
+        assertTrue(conversationInfoFromBackup.isPersonImportant());
+        assertTrue(conversationInfoFromBackup.isPersonBot());
+        assertTrue(conversationInfoFromBackup.isContactStarred());
+        // ConversationStatus is a transient object and not persisted
+    }
 }
diff --git a/services/tests/servicestests/src/com/android/server/people/data/DataManagerTest.java b/services/tests/servicestests/src/com/android/server/people/data/DataManagerTest.java
index 2a4896a..66c3f07 100644
--- a/services/tests/servicestests/src/com/android/server/people/data/DataManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/people/data/DataManagerTest.java
@@ -291,7 +291,8 @@
                 mShortcutChangeCallbackCaptor.capture());
         mShortcutChangeCallback = mShortcutChangeCallbackCaptor.getValue();
 
-        verify(mContext, times(2)).registerReceiver(any(), any());
+        verify(mContext, times(1)).registerReceiver(any(), any());
+        verify(mContext, times(1)).registerReceiver(any(), any(), anyInt());
     }
 
     @After
@@ -1163,6 +1164,76 @@
     }
 
     @Test
+    public void testUncacheOldestCachedShortcut_missingNotificationEvents() {
+        mDataManager.onUserUnlocked(USER_ID_PRIMARY);
+
+        for (int i = 0; i < DataManager.MAX_CACHED_RECENT_SHORTCUTS + 1; i++) {
+            String shortcutId = TEST_SHORTCUT_ID + i;
+            ShortcutInfo shortcut = buildShortcutInfo(TEST_PKG_NAME, USER_ID_PRIMARY, shortcutId,
+                    buildPerson());
+            shortcut.setCached(ShortcutInfo.FLAG_CACHED_NOTIFICATIONS);
+            mShortcutChangeCallback.onShortcutsAddedOrUpdated(
+                    TEST_PKG_NAME,
+                    Collections.singletonList(shortcut),
+                    UserHandle.of(USER_ID_PRIMARY));
+            mLooper.dispatchAll();
+        }
+
+        // Only the shortcut #0 is uncached, all the others are not.
+        verify(mShortcutServiceInternal).uncacheShortcuts(
+                anyInt(), any(), eq(TEST_PKG_NAME),
+                eq(Collections.singletonList(TEST_SHORTCUT_ID + 0)), eq(USER_ID_PRIMARY),
+                eq(ShortcutInfo.FLAG_CACHED_NOTIFICATIONS));
+        for (int i = 1; i < DataManager.MAX_CACHED_RECENT_SHORTCUTS + 1; i++) {
+            verify(mShortcutServiceInternal, never()).uncacheShortcuts(
+                    anyInt(), anyString(), anyString(),
+                    eq(Collections.singletonList(TEST_SHORTCUT_ID + i)), anyInt(),
+                    eq(ShortcutInfo.FLAG_CACHED_NOTIFICATIONS));
+        }
+    }
+
+    @Test
+    public void testUncacheOldestCachedShortcut_legacyConversation() {
+        mDataManager.onUserUnlocked(USER_ID_PRIMARY);
+
+        // Add an extra conversation with a legacy type (no creationTime)
+        ConversationStore conversationStore = mDataManager
+                .getUserDataForTesting(USER_ID_PRIMARY)
+                .getOrCreatePackageData(TEST_PKG_NAME)
+                .getConversationStore();
+        ConversationInfo.Builder builder = new ConversationInfo.Builder();
+        builder.setShortcutId(TEST_SHORTCUT_ID + 0);
+        builder.setShortcutFlags(ShortcutInfo.FLAG_CACHED_NOTIFICATIONS);
+        mDataManager.updateConversationStoreThenNotifyListeners(
+                conversationStore,
+                builder.build(),
+                TEST_PKG_NAME, USER_ID_PRIMARY);
+        for (int i = 1; i < DataManager.MAX_CACHED_RECENT_SHORTCUTS + 1; i++) {
+            String shortcutId = TEST_SHORTCUT_ID + i;
+            ShortcutInfo shortcut = buildShortcutInfo(TEST_PKG_NAME, USER_ID_PRIMARY, shortcutId,
+                    buildPerson());
+            shortcut.setCached(ShortcutInfo.FLAG_CACHED_NOTIFICATIONS);
+            mShortcutChangeCallback.onShortcutsAddedOrUpdated(
+                    TEST_PKG_NAME,
+                    Collections.singletonList(shortcut),
+                    UserHandle.of(USER_ID_PRIMARY));
+            mLooper.dispatchAll();
+        }
+
+        // Only the shortcut #0 is uncached, all the others are not.
+        verify(mShortcutServiceInternal).uncacheShortcuts(
+                anyInt(), any(), eq(TEST_PKG_NAME),
+                eq(Collections.singletonList(TEST_SHORTCUT_ID + 0)), eq(USER_ID_PRIMARY),
+                eq(ShortcutInfo.FLAG_CACHED_NOTIFICATIONS));
+        for (int i = 1; i < DataManager.MAX_CACHED_RECENT_SHORTCUTS + 1; i++) {
+            verify(mShortcutServiceInternal, never()).uncacheShortcuts(
+                    anyInt(), anyString(), anyString(),
+                    eq(Collections.singletonList(TEST_SHORTCUT_ID + i)), anyInt(),
+                    eq(ShortcutInfo.FLAG_CACHED_NOTIFICATIONS));
+        }
+    }
+
+    @Test
     public void testBackupAndRestoration()
             throws IntentFilter.MalformedMimeTypeException {
         mDataManager.onUserUnlocked(USER_ID_PRIMARY);
diff --git a/services/tests/servicestests/src/com/android/server/vibrator/FakeVibratorControllerProvider.java b/services/tests/servicestests/src/com/android/server/vibrator/FakeVibratorControllerProvider.java
index fa3fcd9..235849c 100644
--- a/services/tests/servicestests/src/com/android/server/vibrator/FakeVibratorControllerProvider.java
+++ b/services/tests/servicestests/src/com/android/server/vibrator/FakeVibratorControllerProvider.java
@@ -127,9 +127,17 @@
         }
 
         @Override
-        public long compose(PrimitiveSegment[] effects, long vibrationId) {
+        public long compose(PrimitiveSegment[] primitives, long vibrationId) {
+            if (mSupportedPrimitives == null) {
+                return 0;
+            }
+            for (PrimitiveSegment primitive : primitives) {
+                if (Arrays.binarySearch(mSupportedPrimitives, primitive.getPrimitiveId()) < 0) {
+                    return 0;
+                }
+            }
             long duration = 0;
-            for (PrimitiveSegment primitive : effects) {
+            for (PrimitiveSegment primitive : primitives) {
                 duration += EFFECT_DURATION + primitive.getDelay();
                 recordEffectSegment(vibrationId, primitive);
             }
diff --git a/services/tests/servicestests/src/com/android/server/vibrator/VibrationTest.java b/services/tests/servicestests/src/com/android/server/vibrator/VibrationTest.java
new file mode 100644
index 0000000..b469299
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/vibrator/VibrationTest.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.vibrator;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static java.util.stream.Collectors.toList;
+
+import android.platform.test.annotations.Presubmit;
+
+import org.junit.Test;
+
+import java.util.Arrays;
+
+/**
+ * Tests for {@link Vibration}.
+ *
+ * Build/Install/Run:
+ * atest FrameworksServicesTests:VibrationTest
+ */
+@Presubmit
+public class VibrationTest {
+
+    @Test
+    public void status_hasUniqueProtoEnumValues() {
+        assertThat(
+                Arrays.stream(Vibration.Status.values())
+                        .map(Vibration.Status::getProtoEnumValue)
+                        .collect(toList()))
+                .containsNoDuplicates();
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/vibrator/VibrationThreadTest.java b/services/tests/servicestests/src/com/android/server/vibrator/VibrationThreadTest.java
index de5f6ed..ca162ef 100644
--- a/services/tests/servicestests/src/com/android/server/vibrator/VibrationThreadTest.java
+++ b/services/tests/servicestests/src/com/android/server/vibrator/VibrationThreadTest.java
@@ -257,13 +257,18 @@
         assertTrue(mThread.isRunningVibrationId(vibrationId));
         assertTrue(mControllers.get(VIBRATOR_ID).isVibrating());
 
-        conductor.notifyCancelled(Vibration.Status.CANCELLED_SUPERSEDED, /* immediate= */ false);
+        Vibration.EndInfo cancelVibrationInfo = new Vibration.EndInfo(
+                Vibration.Status.CANCELLED_SUPERSEDED, /* endedByUid= */ 1,
+                /* endedByUsage= */ VibrationAttributes.USAGE_ALARM);
+        conductor.notifyCancelled(
+                cancelVibrationInfo,
+                /* immediate= */ false);
         waitForCompletion();
         assertFalse(mThread.isRunningVibrationId(vibrationId));
 
         verify(mManagerHooks).noteVibratorOn(eq(UID), anyLong());
         verify(mManagerHooks).noteVibratorOff(eq(UID));
-        verifyCallbacksTriggered(vibrationId, Vibration.Status.CANCELLED_SUPERSEDED);
+        verifyCallbacksTriggered(vibrationId, cancelVibrationInfo);
         assertFalse(mControllers.get(VIBRATOR_ID).isVibrating());
 
         List<Float> playedAmplitudes = fakeVibrator.getAmplitudes();
@@ -288,7 +293,9 @@
         VibrationStepConductor conductor = startThreadAndDispatcher(vibrationId, effect);
 
         assertTrue(waitUntil(() -> !fakeVibrator.getAmplitudes().isEmpty(), TEST_TIMEOUT_MILLIS));
-        conductor.notifyCancelled(Vibration.Status.CANCELLED_BY_USER, /* immediate= */ false);
+        conductor.notifyCancelled(
+                new Vibration.EndInfo(Vibration.Status.CANCELLED_BY_USER),
+                /* immediate= */ false);
         waitForCompletion();
 
         verifyCallbacksTriggered(vibrationId, Vibration.Status.CANCELLED_BY_USER);
@@ -319,7 +326,9 @@
 
         assertTrue(waitUntil(() -> !fakeVibrator.getEffectSegments(vibrationId).isEmpty(),
                 TEST_TIMEOUT_MILLIS));
-        conductor.notifyCancelled(Vibration.Status.CANCELLED_BY_USER, /* immediate= */ false);
+        conductor.notifyCancelled(
+                new Vibration.EndInfo(Vibration.Status.CANCELLED_BY_USER),
+                /* immediate= */ false);
         waitForCompletion();
 
         // PWLE size max was used to generate a single vibrate call with 10 segments.
@@ -348,11 +357,13 @@
 
         assertTrue(waitUntil(() -> !fakeVibrator.getEffectSegments(vibrationId).isEmpty(),
                 TEST_TIMEOUT_MILLIS));
-        conductor.notifyCancelled(Vibration.Status.CANCELLED_SUPERSEDED, /* immediate= */ false);
+        conductor.notifyCancelled(
+                new Vibration.EndInfo(Vibration.Status.CANCELLED_BY_SCREEN_OFF),
+                /* immediate= */ false);
         waitForCompletion();
 
         // Composition size max was used to generate a single vibrate call with 10 primitives.
-        verifyCallbacksTriggered(vibrationId, Vibration.Status.CANCELLED_SUPERSEDED);
+        verifyCallbacksTriggered(vibrationId, Vibration.Status.CANCELLED_BY_SCREEN_OFF);
         assertFalse(mControllers.get(VIBRATOR_ID).isVibrating());
         assertEquals(10, fakeVibrator.getEffectSegments(vibrationId).size());
     }
@@ -370,7 +381,9 @@
         VibrationStepConductor conductor = startThreadAndDispatcher(vibrationId, effect);
 
         assertTrue(waitUntil(() -> !fakeVibrator.getAmplitudes().isEmpty(), TEST_TIMEOUT_MILLIS));
-        conductor.notifyCancelled(Vibration.Status.CANCELLED_BY_USER, /* immediate= */ false);
+        conductor.notifyCancelled(
+                new Vibration.EndInfo(Vibration.Status.CANCELLED_BY_USER),
+                /* immediate= */ false);
         waitForCompletion();
 
         verifyCallbacksTriggered(vibrationId, Vibration.Status.CANCELLED_BY_USER);
@@ -394,7 +407,9 @@
 
         assertTrue(waitUntil(() -> fakeVibrator.getEffectSegments(vibrationId).size() > 1,
                 5000 + TEST_TIMEOUT_MILLIS));
-        conductor.notifyCancelled(Vibration.Status.CANCELLED_BY_USER, /* immediate= */ false);
+        conductor.notifyCancelled(
+                new Vibration.EndInfo(Vibration.Status.CANCELLED_BY_USER),
+                /* immediate= */ false);
         waitForCompletion();
 
         verifyCallbacksTriggered(vibrationId, Vibration.Status.CANCELLED_BY_USER);
@@ -414,6 +429,8 @@
     public void vibrate_singleVibratorPredefinedCancel_cancelsVibrationImmediately()
             throws Exception {
         mVibratorProviders.get(VIBRATOR_ID).setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS);
+        mVibratorProviders.get(VIBRATOR_ID).setSupportedPrimitives(
+                VibrationEffect.Composition.PRIMITIVE_CLICK);
 
         long vibrationId = 1;
         VibrationEffect effect = VibrationEffect.startComposition()
@@ -431,7 +448,9 @@
         // fail at waitForCompletion(vibrationThread) if the vibration not cancelled immediately.
         Thread cancellingThread =
                 new Thread(() -> conductor.notifyCancelled(
-                        Vibration.Status.CANCELLED_BY_SETTINGS_UPDATE, /* immediate= */ false));
+                        new Vibration.EndInfo(
+                                Vibration.Status.CANCELLED_BY_SETTINGS_UPDATE),
+                        /* immediate= */ false));
         cancellingThread.start();
 
         waitForCompletion(/* timeout= */ 50);
@@ -458,7 +477,9 @@
         // fail at waitForCompletion(vibrationThread) if the vibration not cancelled immediately.
         Thread cancellingThread =
                 new Thread(() -> conductor.notifyCancelled(
-                        Vibration.Status.CANCELLED_BY_SCREEN_OFF, /* immediate= */ false));
+                        new Vibration.EndInfo(
+                                Vibration.Status.CANCELLED_BY_SCREEN_OFF),
+                        /* immediate= */ false));
         cancellingThread.start();
 
         waitForCompletion(/* timeout= */ 50);
@@ -519,7 +540,7 @@
         startThreadAndDispatcher(vibrationId, effect);
         waitForCompletion();
 
-        verify(mManagerHooks, never()).noteVibratorOn(eq(UID), anyLong());
+        verify(mManagerHooks).noteVibratorOn(eq(UID), eq(0L));
         verify(mManagerHooks, never()).noteVibratorOff(eq(UID));
         verify(mControllerCallbacks, never()).onComplete(eq(VIBRATOR_ID), eq(vibrationId));
         verifyCallbacksTriggered(vibrationId, Vibration.Status.IGNORED_UNSUPPORTED);
@@ -530,6 +551,8 @@
     public void vibrate_singleVibratorComposed_runsVibration() throws Exception {
         FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(VIBRATOR_ID);
         fakeVibrator.setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS);
+        fakeVibrator.setSupportedPrimitives(VibrationEffect.Composition.PRIMITIVE_CLICK,
+                VibrationEffect.Composition.PRIMITIVE_TICK);
 
         long vibrationId = 1;
         VibrationEffect effect = VibrationEffect.startComposition()
@@ -559,7 +582,7 @@
         startThreadAndDispatcher(vibrationId, effect);
         waitForCompletion();
 
-        verify(mManagerHooks, never()).noteVibratorOn(eq(UID), anyLong());
+        verify(mManagerHooks).noteVibratorOn(eq(UID), eq(0L));
         verify(mManagerHooks, never()).noteVibratorOff(eq(UID));
         verify(mControllerCallbacks, never()).onComplete(eq(VIBRATOR_ID), eq(vibrationId));
         verifyCallbacksTriggered(vibrationId, Vibration.Status.IGNORED_UNSUPPORTED);
@@ -570,6 +593,10 @@
     public void vibrate_singleVibratorLargeComposition_splitsVibratorComposeCalls() {
         FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(VIBRATOR_ID);
         fakeVibrator.setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS);
+        fakeVibrator.setSupportedPrimitives(
+                VibrationEffect.Composition.PRIMITIVE_CLICK,
+                VibrationEffect.Composition.PRIMITIVE_TICK,
+                VibrationEffect.Composition.PRIMITIVE_SPIN);
         fakeVibrator.setCompositionSizeMax(2);
 
         long vibrationId = 1;
@@ -809,6 +836,8 @@
         mVibratorProviders.get(2).setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
         mVibratorProviders.get(3).setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
         mVibratorProviders.get(4).setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS);
+        mVibratorProviders.get(4).setSupportedPrimitives(
+                VibrationEffect.Composition.PRIMITIVE_CLICK);
 
         long vibrationId = 1;
         VibrationEffect composed = VibrationEffect.startComposition()
@@ -854,6 +883,8 @@
         mockVibrators(1, 2, 3);
         mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
         mVibratorProviders.get(2).setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS);
+        mVibratorProviders.get(2).setSupportedPrimitives(
+                VibrationEffect.Composition.PRIMITIVE_CLICK);
         mVibratorProviders.get(3).setSupportedEffects(VibrationEffect.EFFECT_CLICK);
 
         long vibrationId = 1;
@@ -902,7 +933,11 @@
         long vibrationId = 1;
         mockVibrators(vibratorIds);
         mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS);
+        mVibratorProviders.get(1).setSupportedPrimitives(
+                VibrationEffect.Composition.PRIMITIVE_CLICK);
         mVibratorProviders.get(2).setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS);
+        mVibratorProviders.get(2).setSupportedPrimitives(
+                VibrationEffect.Composition.PRIMITIVE_CLICK);
         when(mManagerHooks.prepareSyncedVibration(anyLong(), eq(vibratorIds))).thenReturn(true);
         when(mManagerHooks.triggerSyncedVibration(eq(vibrationId))).thenReturn(true);
 
@@ -939,6 +974,8 @@
         mockVibrators(vibratorIds);
         mVibratorProviders.get(1).setSupportedEffects(VibrationEffect.EFFECT_CLICK);
         mVibratorProviders.get(4).setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS);
+        mVibratorProviders.get(4).setSupportedPrimitives(
+                VibrationEffect.Composition.PRIMITIVE_CLICK);
         when(mManagerHooks.prepareSyncedVibration(anyLong(), any())).thenReturn(true);
         when(mManagerHooks.triggerSyncedVibration(anyLong())).thenReturn(true);
 
@@ -1125,7 +1162,9 @@
         // fail at waitForCompletion(cancellingThread).
         Thread cancellingThread = new Thread(
                 () -> conductor.notifyCancelled(
-                        Vibration.Status.CANCELLED_BY_USER, /* immediate= */ false));
+                        new Vibration.EndInfo(
+                                Vibration.Status.CANCELLED_BY_USER),
+                        /* immediate= */ false));
         cancellingThread.start();
 
         // Cancelling the vibration should be fast and return right away, even if the thread is
@@ -1143,6 +1182,8 @@
         mockVibrators(1, 2);
         mVibratorProviders.get(1).setSupportedEffects(VibrationEffect.EFFECT_CLICK);
         mVibratorProviders.get(2).setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS);
+        mVibratorProviders.get(2).setSupportedPrimitives(
+                VibrationEffect.Composition.PRIMITIVE_CLICK);
 
         long vibrationId = 1;
         CombinedVibration effect = CombinedVibration.startParallel()
@@ -1163,13 +1204,15 @@
         // fail at waitForCompletion(vibrationThread) if the vibration not cancelled immediately.
         Thread cancellingThread = new Thread(
                 () -> conductor.notifyCancelled(
-                        Vibration.Status.CANCELLED_SUPERSEDED, /* immediate= */ false));
+                        new Vibration.EndInfo(
+                                Vibration.Status.CANCELLED_BY_SCREEN_OFF),
+                        /* immediate= */ false));
         cancellingThread.start();
 
         waitForCompletion(/* timeout= */ 50);
         cancellingThread.join();
 
-        verifyCallbacksTriggered(vibrationId, Vibration.Status.CANCELLED_SUPERSEDED);
+        verifyCallbacksTriggered(vibrationId, Vibration.Status.CANCELLED_BY_SCREEN_OFF);
         assertFalse(mControllers.get(1).isVibrating());
         assertFalse(mControllers.get(2).isVibrating());
     }
@@ -1195,9 +1238,11 @@
 
         // Run cancel in a separate thread so if VibrationThread.cancel blocks then this test should
         // fail at waitForCompletion(vibrationThread) if the vibration not cancelled immediately.
-        Thread cancellingThread =
-                new Thread(() -> conductor.notifyCancelled(
-                        Vibration.Status.CANCELLED_BY_SCREEN_OFF, /* immediate= */ false));
+        Thread cancellingThread = new Thread(
+                () -> conductor.notifyCancelled(
+                        new Vibration.EndInfo(
+                                Vibration.Status.CANCELLED_BY_SCREEN_OFF),
+                        /* immediate= */ false));
         cancellingThread.start();
 
         waitForCompletion(/* timeout= */ 50);
@@ -1266,7 +1311,7 @@
 
         // Vibration completed but vibrator not yet released.
         verify(mManagerHooks, timeout(TEST_TIMEOUT_MILLIS)).onVibrationCompleted(eq(vibrationId),
-                eq(Vibration.Status.FINISHED));
+                eq(new Vibration.EndInfo(Vibration.Status.FINISHED)));
         verify(mManagerHooks, never()).onVibrationThreadReleased(anyLong());
 
         // Thread still running ramp down.
@@ -1278,12 +1323,13 @@
 
         // Will stop the ramp down right away.
         conductor.notifyCancelled(
-                Vibration.Status.CANCELLED_BY_SETTINGS_UPDATE, /* immediate= */ true);
+                new Vibration.EndInfo(Vibration.Status.CANCELLED_BY_SETTINGS_UPDATE),
+                /* immediate= */ true);
         waitForCompletion();
 
         // Does not cancel already finished vibration, but releases vibrator.
         verify(mManagerHooks, never()).onVibrationCompleted(eq(vibrationId),
-                eq(Vibration.Status.CANCELLED_BY_SETTINGS_UPDATE));
+                eq(new Vibration.EndInfo(Vibration.Status.CANCELLED_BY_SETTINGS_UPDATE)));
         verify(mManagerHooks).onVibrationThreadReleased(vibrationId);
     }
 
@@ -1299,7 +1345,9 @@
         VibrationStepConductor conductor = startThreadAndDispatcher(vibrationId, effect);
         assertTrue(waitUntil(() -> mControllers.get(VIBRATOR_ID).isVibrating(),
                 TEST_TIMEOUT_MILLIS));
-        conductor.notifyCancelled(Vibration.Status.CANCELLED_BY_USER, /* immediate= */ false);
+        conductor.notifyCancelled(
+                new Vibration.EndInfo(Vibration.Status.CANCELLED_BY_USER),
+                /* immediate= */ false);
         waitForCompletion();
 
         verifyCallbacksTriggered(vibrationId, Vibration.Status.CANCELLED_BY_USER);
@@ -1422,7 +1470,9 @@
         VibrationStepConductor conductor2 = startThreadAndDispatcher(vibrationId2, effect2);
         // Effect2 won't complete on its own. Cancel it after a couple of repeats.
         Thread.sleep(150);  // More than two TICKs.
-        conductor2.notifyCancelled(Vibration.Status.CANCELLED_BY_USER, /* immediate= */ false);
+        conductor2.notifyCancelled(
+                new Vibration.EndInfo(Vibration.Status.CANCELLED_BY_USER),
+                /* immediate= */ false);
         waitForCompletion();
 
         startThreadAndDispatcher(vibrationId3, effect3);
@@ -1431,7 +1481,9 @@
         // Effect4 is a long oneshot, but it gets cancelled as fast as possible.
         long start4 = System.currentTimeMillis();
         VibrationStepConductor conductor4 = startThreadAndDispatcher(vibrationId4, effect4);
-        conductor4.notifyCancelled(Vibration.Status.CANCELLED_SUPERSEDED, /* immediate= */ true);
+        conductor4.notifyCancelled(
+                new Vibration.EndInfo(Vibration.Status.CANCELLED_BY_SCREEN_OFF),
+                /* immediate= */ true);
         waitForCompletion();
         long duration4 = System.currentTimeMillis() - start4;
 
@@ -1469,7 +1521,7 @@
                 fakeVibrator.getEffectSegments(vibrationId3));
 
         // Effect4: cancelled quickly.
-        verifyCallbacksTriggered(vibrationId4, Vibration.Status.CANCELLED_SUPERSEDED);
+        verifyCallbacksTriggered(vibrationId4, Vibration.Status.CANCELLED_BY_SCREEN_OFF);
         assertTrue("Tested duration=" + duration4, duration4 < 2000);
 
         // Effect5: normal oneshot. Don't worry about amplitude, as effect4 may or may not have
@@ -1580,7 +1632,11 @@
     }
 
     private void verifyCallbacksTriggered(long vibrationId, Vibration.Status expectedStatus) {
-        verify(mManagerHooks).onVibrationCompleted(eq(vibrationId), eq(expectedStatus));
+        verifyCallbacksTriggered(vibrationId, new Vibration.EndInfo(expectedStatus));
+    }
+
+    private void verifyCallbacksTriggered(long vibrationId, Vibration.EndInfo expectedEndInfo) {
+        verify(mManagerHooks).onVibrationCompleted(eq(vibrationId), eq(expectedEndInfo));
         verify(mManagerHooks).onVibrationThreadReleased(vibrationId);
     }
 
diff --git a/services/tests/servicestests/src/com/android/server/vibrator/VibratorFrameworkStatsLoggerTest.java b/services/tests/servicestests/src/com/android/server/vibrator/VibratorFrameworkStatsLoggerTest.java
new file mode 100644
index 0000000..c1ab1db
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/vibrator/VibratorFrameworkStatsLoggerTest.java
@@ -0,0 +1,127 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.vibrator;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import android.os.Handler;
+import android.os.test.TestLooper;
+import android.platform.test.annotations.Presubmit;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+/**
+ * Tests for {@link VibratorFrameworkStatsLogger}.
+ *
+ * Build/Install/Run:
+ * atest FrameworksServicesTests:VibratorFrameworkStatsLoggerTest
+ */
+@Presubmit
+public class VibratorFrameworkStatsLoggerTest {
+
+    @Rule public MockitoRule rule = MockitoJUnit.rule();
+
+    private TestLooper mTestLooper;
+    private VibratorFrameworkStatsLogger mLogger;
+
+    @Before
+    public void setUp() {
+        mTestLooper = new TestLooper();
+    }
+
+    @Test
+    public void writeVibrationReportedAsync_afterMinInterval_writesRightAway() {
+        setUpLogger(/* minIntervalMillis= */ 10, /* queueMaxSize= */ 10);
+
+        VibrationStats.StatsInfo firstStats = newEmptyStatsInfo();
+        assertFalse(firstStats.isWritten());
+
+        mLogger.writeVibrationReportedAsync(firstStats);
+        mTestLooper.dispatchAll();
+        assertTrue(firstStats.isWritten());
+    }
+
+    @Test
+    public void writeVibrationReportedAsync_rightAfterLogging_schedulesToRunAfterRemainingDelay() {
+        setUpLogger(/* minIntervalMillis= */ 100, /* queueMaxSize= */ 10);
+
+        VibrationStats.StatsInfo firstStats = newEmptyStatsInfo();
+        VibrationStats.StatsInfo secondStats = newEmptyStatsInfo();
+        assertFalse(firstStats.isWritten());
+        assertFalse(secondStats.isWritten());
+
+        // Write first message at current SystemClock.uptimeMillis
+        mLogger.writeVibrationReportedAsync(firstStats);
+        mTestLooper.dispatchAll();
+        assertTrue(firstStats.isWritten());
+
+        // Second message is not written right away, it needs to wait the configured interval.
+        mLogger.writeVibrationReportedAsync(secondStats);
+        mTestLooper.dispatchAll();
+        assertFalse(secondStats.isWritten());
+
+        // Second message is written after delay passes.
+        mTestLooper.moveTimeForward(100);
+        mTestLooper.dispatchAll();
+        assertTrue(secondStats.isWritten());
+    }
+
+    @Test
+    public void writeVibrationReportedAsync_tooFast_logsUsingIntervalAndDropsMessagesFromQueue() {
+        setUpLogger(/* minIntervalMillis= */ 100, /* queueMaxSize= */ 2);
+
+        VibrationStats.StatsInfo firstStats = newEmptyStatsInfo();
+        VibrationStats.StatsInfo secondStats = newEmptyStatsInfo();
+        VibrationStats.StatsInfo thirdStats = newEmptyStatsInfo();
+
+        mLogger.writeVibrationReportedAsync(firstStats);
+        mLogger.writeVibrationReportedAsync(secondStats);
+        mLogger.writeVibrationReportedAsync(thirdStats);
+
+        // Only first message is logged.
+        mTestLooper.dispatchAll();
+        assertTrue(firstStats.isWritten());
+        assertFalse(secondStats.isWritten());
+        assertFalse(thirdStats.isWritten());
+
+        // Wait one interval to check only the second one is logged.
+        mTestLooper.moveTimeForward(100);
+        mTestLooper.dispatchAll();
+        assertTrue(secondStats.isWritten());
+        assertFalse(thirdStats.isWritten());
+
+        // Wait a long interval to check the third one was dropped and will never be logged.
+        mTestLooper.moveTimeForward(1_000);
+        mTestLooper.dispatchAll();
+        assertFalse(thirdStats.isWritten());
+    }
+
+    private void setUpLogger(int minIntervalMillis, int queueMaxSize) {
+        mLogger = new VibratorFrameworkStatsLogger(new Handler(mTestLooper.getLooper()),
+                minIntervalMillis, queueMaxSize);
+    }
+
+    private static VibrationStats.StatsInfo newEmptyStatsInfo() {
+        return new VibrationStats.StatsInfo(
+                0, 0, 0, Vibration.Status.FINISHED, new VibrationStats(), 0L);
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/vibrator/VibratorManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/vibrator/VibratorManagerServiceTest.java
index 8a96feb..36bec75 100644
--- a/services/tests/servicestests/src/com/android/server/vibrator/VibratorManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/vibrator/VibratorManagerServiceTest.java
@@ -34,6 +34,7 @@
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.timeout;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
@@ -75,11 +76,13 @@
 import android.os.vibrator.VibrationEffectSegment;
 import android.platform.test.annotations.Presubmit;
 import android.provider.Settings;
+import android.util.SparseBooleanArray;
 import android.view.InputDevice;
 
 import androidx.test.InstrumentationRegistry;
 
 import com.android.internal.app.IBatteryStats;
+import com.android.internal.util.FrameworkStatsLog;
 import com.android.internal.util.test.FakeSettingsProvider;
 import com.android.internal.util.test.FakeSettingsProviderRule;
 import com.android.server.LocalServices;
@@ -148,6 +151,8 @@
     private IInputManager mIInputManagerMock;
     @Mock
     private IBatteryStats mBatteryStatsMock;
+    @Mock
+    private VibratorFrameworkStatsLogger mVibratorFrameworkStatsLoggerMock;
 
     private final Map<Integer, FakeVibratorControllerProvider> mVibratorProviders = new HashMap<>();
 
@@ -233,6 +238,11 @@
                     }
 
                     @Override
+                    VibratorFrameworkStatsLogger getFrameworkStatsLogger(Handler handler) {
+                        return mVibratorFrameworkStatsLoggerMock;
+                    }
+
+                    @Override
                     VibratorController createVibratorController(int vibratorId,
                             VibratorController.OnVibrationCompleteListener listener) {
                         return mVibratorProviders.get(vibratorId)
@@ -806,11 +816,11 @@
                 service, TEST_TIMEOUT_MILLIS));
 
         VibrationEffect repeatingEffect = VibrationEffect.createWaveform(
-                new long[]{10_000, 10_000}, new int[]{128, 255}, 1);
+                new long[]{10, 10}, new int[]{128, 255}, 1);
         vibrate(service, repeatingEffect, NOTIFICATION_ATTRS);
 
         // VibrationThread will start this vibration async, so wait before checking it started.
-        assertTrue(waitUntil(s -> mVibratorProviders.get(1).getAllEffectSegments().size() > 1,
+        assertTrue(waitUntil(s -> mVibratorProviders.get(1).getAllEffectSegments().size() > 2,
                 service, TEST_TIMEOUT_MILLIS));
 
         // The second vibration should have recorded that the vibrators were turned on.
@@ -916,7 +926,11 @@
         mockCapabilities(IVibratorManager.CAP_SYNC, IVibratorManager.CAP_PREPARE_COMPOSE);
         mockVibrators(1, 2);
         mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS);
+        mVibratorProviders.get(1).setSupportedPrimitives(
+                VibrationEffect.Composition.PRIMITIVE_CLICK);
         mVibratorProviders.get(2).setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS);
+        mVibratorProviders.get(2).setSupportedPrimitives(
+                VibrationEffect.Composition.PRIMITIVE_CLICK);
         // Mock alarm intensity equals to default value to avoid scaling in this test.
         setUserSetting(Settings.System.ALARM_VIBRATION_INTENSITY,
                 mVibrator.getDefaultVibrationIntensity(VibrationAttributes.USAGE_ALARM));
@@ -1078,6 +1092,8 @@
         FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(1);
         fakeVibrator.setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL,
                 IVibrator.CAP_COMPOSE_EFFECTS);
+        fakeVibrator.setSupportedPrimitives(VibrationEffect.Composition.PRIMITIVE_CLICK,
+                VibrationEffect.Composition.PRIMITIVE_TICK);
         VibratorManagerService service = createSystemReadyService();
 
         vibrate(service, VibrationEffect.startComposition()
@@ -1380,6 +1396,373 @@
         assertEquals(IExternalVibratorService.SCALE_MUTE, scale);
     }
 
+    @Test
+    public void frameworkStats_externalVibration_reportsAllMetrics() throws Exception {
+        mockVibrators(1);
+        mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_EXTERNAL_CONTROL);
+        createSystemReadyService();
+
+        AudioAttributes audioAttrs = new AudioAttributes.Builder()
+                .setUsage(AudioAttributes.USAGE_ALARM)
+                .build();
+
+        ExternalVibration vib = new ExternalVibration(UID, PACKAGE_NAME, audioAttrs,
+                mock(IExternalVibrationController.class));
+        mExternalVibratorService.onExternalVibrationStart(vib);
+
+        Thread.sleep(10);
+        mExternalVibratorService.onExternalVibrationStop(vib);
+
+        ArgumentCaptor<VibrationStats.StatsInfo> argumentCaptor =
+                ArgumentCaptor.forClass(VibrationStats.StatsInfo.class);
+        verify(mVibratorFrameworkStatsLoggerMock, timeout(TEST_TIMEOUT_MILLIS))
+                .writeVibrationReportedAsync(argumentCaptor.capture());
+
+        VibrationStats.StatsInfo statsInfo = argumentCaptor.getValue();
+        assertEquals(UID, statsInfo.uid);
+        assertEquals(FrameworkStatsLog.VIBRATION_REPORTED__VIBRATION_TYPE__EXTERNAL,
+                statsInfo.vibrationType);
+        assertEquals(VibrationAttributes.USAGE_ALARM, statsInfo.usage);
+        assertEquals(Vibration.Status.FINISHED.getProtoEnumValue(), statsInfo.status);
+        assertTrue(statsInfo.totalDurationMillis > 0);
+        assertTrue(
+                "Expected vibrator ON for at least 10ms, got " + statsInfo.vibratorOnMillis + "ms",
+                statsInfo.vibratorOnMillis >= 10);
+        assertEquals(2, statsInfo.halSetExternalControlCount);
+    }
+
+    @Test
+    public void frameworkStats_waveformVibration_reportsAllMetrics() throws Exception {
+        mockVibrators(1);
+        mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
+
+        VibratorManagerService service = createSystemReadyService();
+        vibrateAndWaitUntilFinished(service,
+                VibrationEffect.createWaveform(new long[] {0, 10, 20, 10}, -1), RINGTONE_ATTRS);
+
+        verify(mVibratorFrameworkStatsLoggerMock, timeout(TEST_TIMEOUT_MILLIS))
+                .writeVibratorStateOnAsync(eq(UID), anyLong());
+        verify(mVibratorFrameworkStatsLoggerMock, timeout(TEST_TIMEOUT_MILLIS))
+                .writeVibratorStateOffAsync(eq(UID));
+
+        ArgumentCaptor<VibrationStats.StatsInfo> argumentCaptor =
+                ArgumentCaptor.forClass(VibrationStats.StatsInfo.class);
+        verify(mVibratorFrameworkStatsLoggerMock, timeout(TEST_TIMEOUT_MILLIS))
+                .writeVibrationReportedAsync(argumentCaptor.capture());
+
+        VibrationStats.StatsInfo metrics = argumentCaptor.getValue();
+        assertEquals(UID, metrics.uid);
+        assertEquals(FrameworkStatsLog.VIBRATION_REPORTED__VIBRATION_TYPE__SINGLE,
+                metrics.vibrationType);
+        assertEquals(VibrationAttributes.USAGE_RINGTONE, metrics.usage);
+        assertEquals(Vibration.Status.FINISHED.getProtoEnumValue(), metrics.status);
+        assertTrue("Total duration was too low, " + metrics.totalDurationMillis + "ms",
+                metrics.totalDurationMillis >= 20);
+        assertTrue("Vibrator ON duration was too low, " + metrics.vibratorOnMillis + "ms",
+                metrics.vibratorOnMillis >= 20);
+
+        // All unrelated metrics are empty.
+        assertEquals(0, metrics.repeatCount);
+        assertEquals(0, metrics.halComposeCount);
+        assertEquals(0, metrics.halComposePwleCount);
+        assertEquals(0, metrics.halPerformCount);
+        assertEquals(0, metrics.halSetExternalControlCount);
+        assertEquals(0, metrics.halCompositionSize);
+        assertEquals(0, metrics.halPwleSize);
+        assertNull(metrics.halSupportedCompositionPrimitivesUsed);
+        assertNull(metrics.halSupportedEffectsUsed);
+        assertNull(metrics.halUnsupportedCompositionPrimitivesUsed);
+        assertNull(metrics.halUnsupportedEffectsUsed);
+
+        // Accommodate for ramping off config that might add extra setAmplitudes.
+        assertEquals(2, metrics.halOnCount);
+        assertTrue(metrics.halOffCount > 0);
+        assertTrue(metrics.halSetAmplitudeCount >= 2);
+    }
+
+    @Test
+    public void frameworkStats_repeatingVibration_reportsAllMetrics() throws Exception {
+        mockVibrators(1);
+        mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
+
+        VibratorManagerService service = createSystemReadyService();
+        vibrate(service, VibrationEffect.createWaveform(new long[] {10, 100}, 1), RINGTONE_ATTRS);
+
+        verify(mVibratorFrameworkStatsLoggerMock, timeout(TEST_TIMEOUT_MILLIS))
+                .writeVibratorStateOnAsync(eq(UID), anyLong());
+
+        // Wait for at least one loop before cancelling it.
+        Thread.sleep(100);
+        service.cancelVibrate(VibrationAttributes.USAGE_RINGTONE, service);
+
+        verify(mVibratorFrameworkStatsLoggerMock, timeout(TEST_TIMEOUT_MILLIS))
+                .writeVibratorStateOffAsync(eq(UID));
+
+        ArgumentCaptor<VibrationStats.StatsInfo> argumentCaptor =
+                ArgumentCaptor.forClass(VibrationStats.StatsInfo.class);
+        verify(mVibratorFrameworkStatsLoggerMock, timeout(TEST_TIMEOUT_MILLIS))
+                .writeVibrationReportedAsync(argumentCaptor.capture());
+
+        VibrationStats.StatsInfo metrics = argumentCaptor.getValue();
+        assertEquals(UID, metrics.uid);
+        assertEquals(FrameworkStatsLog.VIBRATION_REPORTED__VIBRATION_TYPE__REPEATED,
+                metrics.vibrationType);
+        assertEquals(VibrationAttributes.USAGE_RINGTONE, metrics.usage);
+        assertEquals(Vibration.Status.CANCELLED_BY_USER.getProtoEnumValue(), metrics.status);
+        assertTrue("Total duration was too low, " + metrics.totalDurationMillis + "ms",
+                metrics.totalDurationMillis >= 100);
+        assertTrue("Vibrator ON duration was too low, " + metrics.vibratorOnMillis + "ms",
+                metrics.vibratorOnMillis >= 100);
+
+        // All unrelated metrics are empty.
+        assertTrue(metrics.repeatCount > 0);
+        assertEquals(0, metrics.halComposeCount);
+        assertEquals(0, metrics.halComposePwleCount);
+        assertEquals(0, metrics.halPerformCount);
+        assertEquals(0, metrics.halSetExternalControlCount);
+        assertEquals(0, metrics.halCompositionSize);
+        assertEquals(0, metrics.halPwleSize);
+        assertNull(metrics.halSupportedCompositionPrimitivesUsed);
+        assertNull(metrics.halSupportedEffectsUsed);
+        assertNull(metrics.halUnsupportedCompositionPrimitivesUsed);
+        assertNull(metrics.halUnsupportedEffectsUsed);
+
+        // Accommodate for ramping off config that might add extra setAmplitudes.
+        assertTrue(metrics.halOnCount > 0);
+        assertTrue(metrics.halOffCount > 0);
+        assertTrue(metrics.halSetAmplitudeCount > 0);
+    }
+
+    @Test
+    public void frameworkStats_prebakedAndComposedVibrations_reportsAllMetrics() throws Exception {
+        mockVibrators(1);
+        mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS);
+        mVibratorProviders.get(1).setSupportedEffects(VibrationEffect.EFFECT_CLICK);
+        mVibratorProviders.get(1).setSupportedPrimitives(
+                VibrationEffect.Composition.PRIMITIVE_TICK);
+
+        VibratorManagerService service = createSystemReadyService();
+        vibrateAndWaitUntilFinished(service,
+                VibrationEffect.startComposition()
+                        .addEffect(VibrationEffect.createPredefined(VibrationEffect.EFFECT_CLICK))
+                        .addPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK)
+                        .addPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK)
+                        .addEffect(VibrationEffect.createPredefined(VibrationEffect.EFFECT_CLICK))
+                        .addEffect(VibrationEffect.createPredefined(VibrationEffect.EFFECT_TICK))
+                        .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK)
+                        .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK)
+                        .compose(),
+                ALARM_ATTRS);
+
+        verify(mVibratorFrameworkStatsLoggerMock, timeout(TEST_TIMEOUT_MILLIS))
+                .writeVibratorStateOnAsync(eq(UID), anyLong());
+        verify(mVibratorFrameworkStatsLoggerMock, timeout(TEST_TIMEOUT_MILLIS))
+                .writeVibratorStateOffAsync(eq(UID));
+
+        ArgumentCaptor<VibrationStats.StatsInfo> argumentCaptor =
+                ArgumentCaptor.forClass(VibrationStats.StatsInfo.class);
+        verify(mVibratorFrameworkStatsLoggerMock, timeout(TEST_TIMEOUT_MILLIS))
+                .writeVibrationReportedAsync(argumentCaptor.capture());
+
+        VibrationStats.StatsInfo metrics = argumentCaptor.getValue();
+        assertEquals(UID, metrics.uid);
+        assertEquals(FrameworkStatsLog.VIBRATION_REPORTED__VIBRATION_TYPE__SINGLE,
+                metrics.vibrationType);
+        assertEquals(VibrationAttributes.USAGE_ALARM, metrics.usage);
+        assertEquals(Vibration.Status.FINISHED.getProtoEnumValue(), metrics.status);
+
+        // At least 4 effect/primitive played, 20ms each, plus configured fallback.
+        assertTrue("Total duration was too low, " + metrics.totalDurationMillis + "ms",
+                metrics.totalDurationMillis >= 80);
+        assertTrue("Vibrator ON duration was too low, " + metrics.vibratorOnMillis + "ms",
+                metrics.vibratorOnMillis >= 80);
+
+        // Related metrics were collected.
+        assertEquals(2, metrics.halComposeCount); // TICK+TICK, then CLICK+CLICK
+        assertEquals(3, metrics.halPerformCount); // CLICK, TICK, then CLICK
+        assertEquals(4, metrics.halCompositionSize); // 2*TICK + 2*CLICK
+        // No repetitions in reported effect/primitive IDs.
+        assertArrayEquals(new int[] {VibrationEffect.Composition.PRIMITIVE_TICK},
+                metrics.halSupportedCompositionPrimitivesUsed);
+        assertArrayEquals(new int[] {VibrationEffect.Composition.PRIMITIVE_CLICK},
+                metrics.halUnsupportedCompositionPrimitivesUsed);
+        assertArrayEquals(new int[] {VibrationEffect.EFFECT_CLICK},
+                metrics.halSupportedEffectsUsed);
+        assertArrayEquals(new int[] {VibrationEffect.EFFECT_TICK},
+                metrics.halUnsupportedEffectsUsed);
+
+        // All unrelated metrics are empty.
+        assertEquals(0, metrics.repeatCount);
+        assertEquals(0, metrics.halComposePwleCount);
+        assertEquals(0, metrics.halSetExternalControlCount);
+        assertEquals(0, metrics.halPwleSize);
+
+        // Accommodate for ramping off config that might add extra setAmplitudes
+        // for the effect that plays the fallback instead of "perform".
+        assertTrue(metrics.halOnCount > 0);
+        assertTrue(metrics.halOffCount > 0);
+        assertTrue(metrics.halSetAmplitudeCount > 0);
+    }
+
+    @Test
+    public void frameworkStats_interruptingVibrations_reportsAllMetrics() throws Exception {
+        mockVibrators(1);
+        VibratorManagerService service = createSystemReadyService();
+
+        vibrate(service, VibrationEffect.createOneShot(1_000, 128), HAPTIC_FEEDBACK_ATTRS);
+
+        // VibrationThread will start this vibration async, so wait until vibration is triggered.
+        assertTrue(waitUntil(s -> !mVibratorProviders.get(1).getAllEffectSegments().isEmpty(),
+                service, TEST_TIMEOUT_MILLIS));
+
+        vibrateAndWaitUntilFinished(service, VibrationEffect.createOneShot(10, 255), ALARM_ATTRS);
+
+        ArgumentCaptor<VibrationStats.StatsInfo> argumentCaptor =
+                ArgumentCaptor.forClass(VibrationStats.StatsInfo.class);
+        verify(mVibratorFrameworkStatsLoggerMock, timeout(TEST_TIMEOUT_MILLIS).times(2))
+                .writeVibrationReportedAsync(argumentCaptor.capture());
+
+        VibrationStats.StatsInfo touchMetrics = argumentCaptor.getAllValues().get(0);
+        assertEquals(UID, touchMetrics.uid);
+        assertEquals(VibrationAttributes.USAGE_TOUCH, touchMetrics.usage);
+        assertEquals(Vibration.Status.CANCELLED_SUPERSEDED.getProtoEnumValue(),
+                touchMetrics.status);
+        assertTrue(touchMetrics.endedBySameUid);
+        assertEquals(VibrationAttributes.USAGE_ALARM, touchMetrics.endedByUsage);
+        assertEquals(-1, touchMetrics.interruptedUsage);
+
+        VibrationStats.StatsInfo alarmMetrics = argumentCaptor.getAllValues().get(1);
+        assertEquals(UID, alarmMetrics.uid);
+        assertEquals(VibrationAttributes.USAGE_ALARM, alarmMetrics.usage);
+        assertEquals(Vibration.Status.FINISHED.getProtoEnumValue(), alarmMetrics.status);
+        assertFalse(alarmMetrics.endedBySameUid);
+        assertEquals(-1, alarmMetrics.endedByUsage);
+        assertEquals(VibrationAttributes.USAGE_TOUCH, alarmMetrics.interruptedUsage);
+    }
+
+    @Test
+    public void frameworkStats_ignoredVibration_reportsStatus() throws Exception {
+        setUserSetting(Settings.System.RING_VIBRATION_INTENSITY,
+                Vibrator.VIBRATION_INTENSITY_OFF);
+
+        mockVibrators(1);
+        VibratorManagerService service = createSystemReadyService();
+        mRegisteredPowerModeListener.onLowPowerModeChanged(LOW_POWER_STATE);
+
+        // Haptic feedback ignored in low power state
+        vibrateAndWaitUntilFinished(service, VibrationEffect.createOneShot(100, 128),
+                HAPTIC_FEEDBACK_ATTRS);
+        // Ringtone vibration user settings are off
+        vibrateAndWaitUntilFinished(service, VibrationEffect.createOneShot(200, 128),
+                RINGTONE_ATTRS);
+
+        ArgumentCaptor<VibrationStats.StatsInfo> argumentCaptor =
+                ArgumentCaptor.forClass(VibrationStats.StatsInfo.class);
+        verify(mVibratorFrameworkStatsLoggerMock, timeout(TEST_TIMEOUT_MILLIS).times(2))
+                .writeVibrationReportedAsync(argumentCaptor.capture());
+
+        VibrationStats.StatsInfo touchMetrics = argumentCaptor.getAllValues().get(0);
+        assertEquals(UID, touchMetrics.uid);
+        assertEquals(VibrationAttributes.USAGE_TOUCH, touchMetrics.usage);
+        assertEquals(Vibration.Status.IGNORED_FOR_POWER.getProtoEnumValue(), touchMetrics.status);
+
+        VibrationStats.StatsInfo ringtoneMetrics = argumentCaptor.getAllValues().get(1);
+        assertEquals(UID, ringtoneMetrics.uid);
+        assertEquals(VibrationAttributes.USAGE_RINGTONE, ringtoneMetrics.usage);
+        assertEquals(Vibration.Status.IGNORED_FOR_SETTINGS.getProtoEnumValue(),
+                ringtoneMetrics.status);
+
+        for (VibrationStats.StatsInfo metrics : argumentCaptor.getAllValues()) {
+            // Latencies are empty since vibrations never started
+            assertEquals(0, metrics.startLatencyMillis);
+            assertEquals(0, metrics.endLatencyMillis);
+            assertEquals(0, metrics.vibratorOnMillis);
+
+            // All unrelated metrics are empty.
+            assertEquals(0, metrics.repeatCount);
+            assertEquals(0, metrics.halComposeCount);
+            assertEquals(0, metrics.halComposePwleCount);
+            assertEquals(0, metrics.halOffCount);
+            assertEquals(0, metrics.halOnCount);
+            assertEquals(0, metrics.halPerformCount);
+            assertEquals(0, metrics.halSetExternalControlCount);
+            assertEquals(0, metrics.halCompositionSize);
+            assertEquals(0, metrics.halPwleSize);
+            assertNull(metrics.halSupportedCompositionPrimitivesUsed);
+            assertNull(metrics.halSupportedEffectsUsed);
+            assertNull(metrics.halUnsupportedCompositionPrimitivesUsed);
+            assertNull(metrics.halUnsupportedEffectsUsed);
+        }
+    }
+
+    @Test
+    public void frameworkStats_multiVibrators_reportsAllMetrics() throws Exception {
+        mockVibrators(1, 2);
+        mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS);
+        mVibratorProviders.get(1).setSupportedPrimitives(
+                VibrationEffect.Composition.PRIMITIVE_TICK);
+        mVibratorProviders.get(2).setSupportedEffects(VibrationEffect.EFFECT_TICK);
+
+        VibratorManagerService service = createSystemReadyService();
+        vibrateAndWaitUntilFinished(service,
+                CombinedVibration.startParallel()
+                        .addVibrator(1,
+                                VibrationEffect.startComposition()
+                                        .addPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK)
+                                        .compose())
+                        .addVibrator(2,
+                                VibrationEffect.createPredefined(VibrationEffect.EFFECT_TICK))
+                        .combine(),
+                NOTIFICATION_ATTRS);
+
+        SparseBooleanArray expectedEffectsUsed = new SparseBooleanArray();
+        expectedEffectsUsed.put(VibrationEffect.EFFECT_TICK, true);
+
+        SparseBooleanArray expectedPrimitivesUsed = new SparseBooleanArray();
+        expectedPrimitivesUsed.put(VibrationEffect.Composition.PRIMITIVE_TICK, true);
+
+        verify(mVibratorFrameworkStatsLoggerMock, timeout(TEST_TIMEOUT_MILLIS))
+                .writeVibratorStateOnAsync(eq(UID), anyLong());
+        verify(mVibratorFrameworkStatsLoggerMock, timeout(TEST_TIMEOUT_MILLIS))
+                .writeVibratorStateOffAsync(eq(UID));
+
+        ArgumentCaptor<VibrationStats.StatsInfo> argumentCaptor =
+                ArgumentCaptor.forClass(VibrationStats.StatsInfo.class);
+        verify(mVibratorFrameworkStatsLoggerMock, timeout(TEST_TIMEOUT_MILLIS))
+                .writeVibrationReportedAsync(argumentCaptor.capture());
+
+        VibrationStats.StatsInfo metrics = argumentCaptor.getValue();
+        assertEquals(UID, metrics.uid);
+        assertEquals(FrameworkStatsLog.VIBRATION_REPORTED__VIBRATION_TYPE__SINGLE,
+                metrics.vibrationType);
+        assertEquals(VibrationAttributes.USAGE_NOTIFICATION, metrics.usage);
+        assertEquals(Vibration.Status.FINISHED.getProtoEnumValue(), metrics.status);
+        assertTrue(metrics.totalDurationMillis >= 20);
+
+        // vibratorOnMillis accumulates both vibrators, it's 20 for each constant.
+        assertEquals(40, metrics.vibratorOnMillis);
+
+        // Related metrics were collected.
+        assertEquals(1, metrics.halComposeCount);
+        assertEquals(1, metrics.halPerformCount);
+        assertEquals(1, metrics.halCompositionSize);
+        assertEquals(2, metrics.halOffCount);
+        assertArrayEquals(new int[] {VibrationEffect.Composition.PRIMITIVE_TICK},
+                metrics.halSupportedCompositionPrimitivesUsed);
+        assertArrayEquals(new int[] {VibrationEffect.EFFECT_TICK},
+                metrics.halSupportedEffectsUsed);
+
+        // All unrelated metrics are empty.
+        assertEquals(0, metrics.repeatCount);
+        assertEquals(0, metrics.halComposePwleCount);
+        assertEquals(0, metrics.halOnCount);
+        assertEquals(0, metrics.halSetAmplitudeCount);
+        assertEquals(0, metrics.halSetExternalControlCount);
+        assertEquals(0, metrics.halPwleSize);
+        assertNull(metrics.halUnsupportedCompositionPrimitivesUsed);
+        assertNull(metrics.halUnsupportedEffectsUsed);
+    }
+
     private VibrationEffectSegment expectedPrebaked(int effectId) {
         return expectedPrebaked(effectId, VibrationEffect.EFFECT_STRENGTH_MEDIUM);
     }
@@ -1429,6 +1812,20 @@
                 mContextSpy.getContentResolver(), settingName, value, UserHandle.USER_CURRENT);
     }
 
+    private void vibrateAndWaitUntilFinished(VibratorManagerService service, VibrationEffect effect,
+            VibrationAttributes attrs) throws InterruptedException {
+        vibrateAndWaitUntilFinished(service, CombinedVibration.createParallel(effect), attrs);
+    }
+
+    private void vibrateAndWaitUntilFinished(VibratorManagerService service,
+            CombinedVibration effect, VibrationAttributes attrs) throws InterruptedException {
+        Vibration vib =
+                service.vibrateInternal(UID, PACKAGE_NAME, effect, attrs, "some reason", service);
+        if (vib != null) {
+            vib.waitForEnd();
+        }
+    }
+
     private void vibrate(VibratorManagerService service, VibrationEffect effect,
             VibrationAttributes attrs) {
         vibrate(service, CombinedVibration.createParallel(effect), attrs);
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeFilteringTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeFilteringTest.java
index 8ac729e..c7905a0 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeFilteringTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeFilteringTest.java
@@ -8,7 +8,7 @@
  *      http://www.apache.org/licenses/LICENSE-2.0
  *
  * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distriZenbuted on an "AS IS" BASIS,
+ * 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.
@@ -397,10 +397,10 @@
         Bundle inputWrong = makeExtrasBundleWithPeople(new String[]{"mailto:nope"});
         assertTrue(ZenModeFiltering.matchesCallFilter(mContext, ZEN_MODE_IMPORTANT_INTERRUPTIONS,
                 policy, UserHandle.SYSTEM,
-                inputMatches, null, 0, 0));
+                inputMatches, null, 0, 0, 0));
         assertFalse(ZenModeFiltering.matchesCallFilter(mContext, ZEN_MODE_IMPORTANT_INTERRUPTIONS,
                 policy, UserHandle.SYSTEM,
-                inputWrong, null, 0, 0));
+                inputWrong, null, 0, 0, 0));
     }
 
     @Test
@@ -428,19 +428,19 @@
         assertTrue("identical numbers should match",
                 ZenModeFiltering.matchesCallFilter(mContext, ZEN_MODE_IMPORTANT_INTERRUPTIONS,
                 policy, UserHandle.SYSTEM,
-                identical, null, 0, 0));
+                identical, null, 0, 0, 0));
         assertTrue("equivalent but non-identical numbers should match",
                 ZenModeFiltering.matchesCallFilter(mContext, ZEN_MODE_IMPORTANT_INTERRUPTIONS,
                 policy, UserHandle.SYSTEM,
-                same, null, 0, 0));
+                same, null, 0, 0, 0));
         assertFalse("non-equivalent numbers should not match",
                 ZenModeFiltering.matchesCallFilter(mContext, ZEN_MODE_IMPORTANT_INTERRUPTIONS,
                 policy, UserHandle.SYSTEM,
-                different, null, 0, 0));
+                different, null, 0, 0, 0));
         assertFalse("non-tel strings should not match",
                 ZenModeFiltering.matchesCallFilter(mContext, ZEN_MODE_IMPORTANT_INTERRUPTIONS,
                 policy, UserHandle.SYSTEM,
-                garbage, null, 0, 0));
+                garbage, null, 0, 0, 0));
     }
 
     @Test
@@ -469,23 +469,23 @@
         assertTrue("same number 1 should match",
                 ZenModeFiltering.matchesCallFilter(mContext, ZEN_MODE_IMPORTANT_INTERRUPTIONS,
                         policy, UserHandle.SYSTEM,
-                        same1, null, 0, 0));
+                        same1, null, 0, 0, 0));
         assertTrue("same number 2 should match",
                 ZenModeFiltering.matchesCallFilter(mContext, ZEN_MODE_IMPORTANT_INTERRUPTIONS,
                         policy, UserHandle.SYSTEM,
-                        same2, null, 0, 0));
+                        same2, null, 0, 0, 0));
         assertTrue("same number 3 should match",
                 ZenModeFiltering.matchesCallFilter(mContext, ZEN_MODE_IMPORTANT_INTERRUPTIONS,
                         policy, UserHandle.SYSTEM,
-                        same3, null, 0, 0));
+                        same3, null, 0, 0, 0));
         assertFalse("different number 1 should not match",
                 ZenModeFiltering.matchesCallFilter(mContext, ZEN_MODE_IMPORTANT_INTERRUPTIONS,
                         policy, UserHandle.SYSTEM,
-                        different1, null, 0, 0));
+                        different1, null, 0, 0, 0));
         assertFalse("different number 2 should not match",
                 ZenModeFiltering.matchesCallFilter(mContext, ZEN_MODE_IMPORTANT_INTERRUPTIONS,
                         policy, UserHandle.SYSTEM,
-                        different2, null, 0, 0));
+                        different2, null, 0, 0, 0));
     }
 
     @Test
@@ -516,14 +516,14 @@
         assertTrue("contact number 1 should match",
                 ZenModeFiltering.matchesCallFilter(mContext, ZEN_MODE_IMPORTANT_INTERRUPTIONS,
                         policy, UserHandle.SYSTEM,
-                        tel1, null, 0, 0));
+                        tel1, null, 0, 0, 0));
         assertTrue("contact number 2 should match",
                 ZenModeFiltering.matchesCallFilter(mContext, ZEN_MODE_IMPORTANT_INTERRUPTIONS,
                         policy, UserHandle.SYSTEM,
-                        tel2, null, 0, 0));
+                        tel2, null, 0, 0, 0));
         assertFalse("different number should not match",
                 ZenModeFiltering.matchesCallFilter(mContext, ZEN_MODE_IMPORTANT_INTERRUPTIONS,
                         policy, UserHandle.SYSTEM,
-                        different, null, 0, 0));
+                        different, null, 0, 0, 0));
     }
 }
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
index 14737e0..b8da8cc 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
@@ -24,6 +24,7 @@
 import static android.view.WindowManager.LayoutParams.INVALID_WINDOW_TYPE;
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG;
+import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
 import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD;
 import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD_DIALOG;
 import static android.view.WindowManager.LayoutParams.TYPE_TOAST;
@@ -43,6 +44,7 @@
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyInt;
@@ -55,16 +57,20 @@
 import android.content.pm.PackageManager;
 import android.graphics.Rect;
 import android.os.Binder;
+import android.os.Bundle;
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.os.UserHandle;
 import android.platform.test.annotations.Presubmit;
+import android.util.MergedConfiguration;
 import android.view.IWindowSessionCallback;
 import android.view.InsetsSourceControl;
 import android.view.InsetsState;
 import android.view.InsetsVisibilities;
+import android.view.SurfaceControl;
 import android.view.View;
 import android.view.WindowManager;
+import android.window.ClientWindowFrames;
 import android.window.WindowContainerToken;
 
 import androidx.test.filters.SmallTest;
@@ -174,6 +180,32 @@
     }
 
     @Test
+    public void testRelayoutExitingWindow() {
+        final WindowState win = createWindow(null, TYPE_BASE_APPLICATION, "appWin");
+        final WindowSurfaceController surfaceController = mock(WindowSurfaceController.class);
+        doReturn(true).when(surfaceController).hasSurface();
+        spyOn(win);
+        doReturn(true).when(win).isExitAnimationRunningSelfOrParent();
+        win.mWinAnimator.mSurfaceController = surfaceController;
+        win.mViewVisibility = View.VISIBLE;
+        win.mHasSurface = true;
+        win.mActivityRecord.mAppStopped = true;
+        win.mActivityRecord.mVisibleRequested = false;
+        win.mActivityRecord.setVisible(false);
+        mWm.mWindowMap.put(win.mClient.asBinder(), win);
+        final int w = 100;
+        final int h = 200;
+        mWm.relayoutWindow(win.mSession, win.mClient, win.mAttrs, w, h, View.GONE, 0, 0, 0,
+                new ClientWindowFrames(), new MergedConfiguration(), new SurfaceControl(),
+                new InsetsState(), new InsetsSourceControl[0], new Bundle());
+        // Because the window is already invisible, it doesn't need to apply exiting animation
+        // and WMS#tryStartExitingAnimation() will destroy the surface directly.
+        assertFalse(win.mAnimatingExit);
+        assertFalse(win.mHasSurface);
+        assertNull(win.mWinAnimator.mSurfaceController);
+    }
+
+    @Test
     public void testMoveWindowTokenToDisplay_NullToken_DoNothing() {
         mWm.moveWindowTokenToDisplay(null, mDisplayContent.getDisplayId());
 
diff --git a/tests/ApkVerityTest/Android.bp b/tests/ApkVerityTest/Android.bp
index 62e16a5..f026bea 100644
--- a/tests/ApkVerityTest/Android.bp
+++ b/tests/ApkVerityTest/Android.bp
@@ -37,8 +37,8 @@
         "general-tests",
         "vts",
     ],
-    target_required: [
-        "block_device_writer_module",
+    data_device_bins_both: [
+        "block_device_writer",
     ],
     data: [
         ":ApkVerityTestCertDer",
diff --git a/tests/ApkVerityTest/AndroidTest.xml b/tests/ApkVerityTest/AndroidTest.xml
index 55704ed..39b75cc 100644
--- a/tests/ApkVerityTest/AndroidTest.xml
+++ b/tests/ApkVerityTest/AndroidTest.xml
@@ -31,10 +31,18 @@
 
     <target_preparer class="com.android.tradefed.targetprep.PushFilePreparer">
         <option name="cleanup" value="true" />
-        <option name="push" value="block_device_writer->/data/local/tmp/block_device_writer" />
         <option name="push" value="ApkVerityTestCert.der->/data/local/tmp/ApkVerityTestCert.der" />
     </target_preparer>
 
+    <target_preparer class="com.android.compatibility.common.tradefed.targetprep.FilePusher">
+        <!-- The build system produces both 32 and 64 bit variants with bitness suffix. Let
+             FilePusher find the filename with bitness and push to a remote name without bitness.
+        -->
+        <option name="append-bitness" value="true" />
+        <option name="cleanup" value="true" />
+        <option name="push" value="block_device_writer->/data/local/tmp/block_device_writer" />
+    </target_preparer>
+
     <test class="com.android.compatibility.common.tradefed.testtype.JarHostTest" >
         <option name="jar" value="ApkVerityTest.jar" />
     </test>
diff --git a/tests/ApkVerityTest/block_device_writer/Android.bp b/tests/ApkVerityTest/block_device_writer/Android.bp
index fdfa41f..0002447d 100644
--- a/tests/ApkVerityTest/block_device_writer/Android.bp
+++ b/tests/ApkVerityTest/block_device_writer/Android.bp
@@ -24,12 +24,7 @@
 }
 
 cc_test {
-    // Depending on how the test runs, the executable may be uploaded to different location.
-    // Before the bug in the file pusher is fixed, workaround by making the name unique.
-    // See b/124718249#comment12.
-    name: "block_device_writer_module",
-    stem: "block_device_writer",
-
+    name: "block_device_writer",
     srcs: ["block_device_writer.cpp"],
     cflags: [
         "-D_FILE_OFFSET_BITS=64",
@@ -42,20 +37,13 @@
         "libbase",
         "libutils",
     ],
-    // For some reasons, cuttlefish (x86) uses x86_64 test suites for testing. Unfortunately, when
-    // the uploader does not pick up the executable from correct output location. The following
-    // workaround allows the test to:
-    //  * upload the 32-bit exectuable for both 32 and 64 bits devices to use
-    //  * refer to the same executable name in Java
-    //  * no need to force the Java test to be archiecture specific.
-    //
-    // See b/145573317 for details.
+    compile_multilib: "both",
     multilib: {
         lib32: {
-            suffix: "",
+            suffix: "32",
         },
         lib64: {
-            suffix: "64", // not really used
+            suffix: "64",
         },
     },
 
diff --git a/tests/ApkVerityTest/block_device_writer/src/com/android/blockdevicewriter/BlockDeviceWriter.java b/tests/ApkVerityTest/block_device_writer/src/com/android/blockdevicewriter/BlockDeviceWriter.java
index 5c2c15b..9be02ec 100644
--- a/tests/ApkVerityTest/block_device_writer/src/com/android/blockdevicewriter/BlockDeviceWriter.java
+++ b/tests/ApkVerityTest/block_device_writer/src/com/android/blockdevicewriter/BlockDeviceWriter.java
@@ -32,11 +32,12 @@
  * <p>To use this class, please push block_device_writer binary to /data/local/tmp.
  * 1. In Android.bp, add:
  * <pre>
- *     target_required: ["block_device_writer_module"],
+ *      data_device_bins_both: ["block_device_writer"],
  * </pre>
  * 2. In AndroidText.xml, add:
  * <pre>
- *     <target_preparer class="com.android.tradefed.targetprep.PushFilePreparer">
+ *     <target_preparer class="com.android.compatibility.common.tradefed.targetprep.FilePusher">
+ *         <option name="append-bitness" value="true" />
  *         <option name="push" value="block_device_writer->/data/local/tmp/block_device_writer" />
  *     </target_preparer>
  * </pre>