Merge "Include descriptor in Binder token for AppPredictor"
diff --git a/apct-tests/perftests/core/src/android/graphics/perftests/TypefaceSerializationPerfTest.java b/apct-tests/perftests/core/src/android/graphics/perftests/TypefaceSerializationPerfTest.java
index d3fcaa1..1e2650d 100644
--- a/apct-tests/perftests/core/src/android/graphics/perftests/TypefaceSerializationPerfTest.java
+++ b/apct-tests/perftests/core/src/android/graphics/perftests/TypefaceSerializationPerfTest.java
@@ -97,6 +97,10 @@
             buffer.position(0);
             Typeface.deserializeFontMap(buffer, out);
             elapsedTime = System.nanoTime() - startTime;
+            for (Typeface typeface : out.values()) {
+                typeface.releaseNativeObjectForTest();
+            }
+            out.clear();
         }
     }
 
diff --git a/core/java/Android.bp b/core/java/Android.bp
index 2fffaab7..af0fa39 100644
--- a/core/java/Android.bp
+++ b/core/java/Android.bp
@@ -187,8 +187,6 @@
         "com/android/internal/util/IndentingPrintWriter.java",
         "com/android/internal/util/MessageUtils.java",
         "com/android/internal/util/WakeupMessage.java",
-        // TODO: delete as soon as NetworkStatsFactory stops using
-        "com/android/internal/util/ProcFileReader.java",
     ],
 }
 
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index d8a48c9..25ef6e8 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -308,7 +308,7 @@
  * <li>The <b>foreground lifetime</b> of an activity happens between a call to
  * {@link android.app.Activity#onResume} until a corresponding call to
  * {@link android.app.Activity#onPause}.  During this time the activity is
- * in visible, active and interacting with the user.  An activity
+ * visible, active and interacting with the user.  An activity
  * can frequently go between the resumed and paused states -- for example when
  * the device goes to sleep, when an activity result is delivered, when a new
  * intent is delivered -- so the code in these methods should be fairly
diff --git a/core/java/android/app/compat/ChangeIdStateCache.java b/core/java/android/app/compat/ChangeIdStateCache.java
index acd404b..dea4e9c8 100644
--- a/core/java/android/app/compat/ChangeIdStateCache.java
+++ b/core/java/android/app/compat/ChangeIdStateCache.java
@@ -32,7 +32,7 @@
 public final class ChangeIdStateCache
         extends PropertyInvalidatedCache<ChangeIdStateQuery, Boolean> {
     private static final String CACHE_KEY = "cache_key.is_compat_change_enabled";
-    private static final int MAX_ENTRIES = 20;
+    private static final int MAX_ENTRIES = 64;
     private static boolean sDisabled = false;
     private volatile IPlatformCompat mPlatformCompat;
 
diff --git a/core/java/android/appwidget/AppWidgetManager.java b/core/java/android/appwidget/AppWidgetManager.java
index fd94969..269ca89 100644
--- a/core/java/android/appwidget/AppWidgetManager.java
+++ b/core/java/android/appwidget/AppWidgetManager.java
@@ -586,6 +586,9 @@
      * The extras can be used to embed additional information about this widget to be accessed
      * by the associated widget's AppWidgetProvider.
      *
+     * <p>
+     * The new options are merged into existing options using {@link Bundle#putAll} semantics.
+     *
      * @see #getAppWidgetOptions(int)
      *
      * @param appWidgetId The AppWidget instances for which to set the RemoteViews.
diff --git a/core/java/android/appwidget/AppWidgetProvider.java b/core/java/android/appwidget/AppWidgetProvider.java
index 3344ebc..e7e8ef9 100644
--- a/core/java/android/appwidget/AppWidgetProvider.java
+++ b/core/java/android/appwidget/AppWidgetProvider.java
@@ -127,7 +127,8 @@
 
     /**
      * Called in response to the {@link AppWidgetManager#ACTION_APPWIDGET_OPTIONS_CHANGED}
-     * broadcast when this widget has been layed out at a new size.
+     * broadcast when this widget has been layed out at a new size or its options changed via
+     * {@link AppWidgetManager#updateAppWidgetOptions}.
      *
      * {@more}
      *
@@ -136,7 +137,7 @@
      * @param appWidgetManager A {@link AppWidgetManager} object you can call {@link
      *                  AppWidgetManager#updateAppWidget} on.
      * @param appWidgetId The appWidgetId of the widget whose size changed.
-     * @param newOptions The appWidgetId of the widget whose size changed.
+     * @param newOptions The new options of the changed widget.
      *
      * @see AppWidgetManager#ACTION_APPWIDGET_OPTIONS_CHANGED
      */
diff --git a/core/java/android/content/ContentProvider.java b/core/java/android/content/ContentProvider.java
index da486ee..0c171ad 100644
--- a/core/java/android/content/ContentProvider.java
+++ b/core/java/android/content/ContentProvider.java
@@ -19,7 +19,7 @@
 import static android.Manifest.permission.INTERACT_ACROSS_USERS;
 import static android.Manifest.permission.INTERACT_ACROSS_USERS_FULL;
 import static android.os.Process.myUserHandle;
-import static android.os.Trace.TRACE_TAG_DATABASE;
+import static android.os.Trace.TRACE_TAG_ACTIVITY_MANAGER;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -278,7 +278,7 @@
                 // Return an empty cursor for all columns.
                 return new MatrixCursor(cursor.getColumnNames(), 0);
             }
-            traceBegin(TRACE_TAG_DATABASE, "query: ", uri.getAuthority());
+            traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "query: ", uri.getAuthority());
             final AttributionSource original = setCallingAttributionSource(
                     attributionSource);
             try {
@@ -289,7 +289,7 @@
                 throw e.rethrowAsRuntimeException();
             } finally {
                 setCallingAttributionSource(original);
-                Trace.traceEnd(TRACE_TAG_DATABASE);
+                Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
             }
         }
 
@@ -298,13 +298,13 @@
             // getCallingPackage() isn't available in getType(), as the javadoc states.
             uri = validateIncomingUri(uri);
             uri = maybeGetUriWithoutUserId(uri);
-            traceBegin(TRACE_TAG_DATABASE, "getType: ", uri.getAuthority());
+            traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "getType: ", uri.getAuthority());
             try {
                 return mInterface.getType(uri);
             } catch (RemoteException e) {
                 throw e.rethrowAsRuntimeException();
             } finally {
-                Trace.traceEnd(TRACE_TAG_DATABASE);
+                Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
             }
         }
 
@@ -336,7 +336,7 @@
                     setCallingAttributionSource(original);
                 }
             }
-            traceBegin(TRACE_TAG_DATABASE, "insert: ", uri.getAuthority());
+            traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "insert: ", uri.getAuthority());
             final AttributionSource original = setCallingAttributionSource(
                     attributionSource);
             try {
@@ -345,7 +345,7 @@
                 throw e.rethrowAsRuntimeException();
             } finally {
                 setCallingAttributionSource(original);
-                Trace.traceEnd(TRACE_TAG_DATABASE);
+                Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
             }
         }
 
@@ -358,7 +358,7 @@
                     != PermissionChecker.PERMISSION_GRANTED) {
                 return 0;
             }
-            traceBegin(TRACE_TAG_DATABASE, "bulkInsert: ", uri.getAuthority());
+            traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "bulkInsert: ", uri.getAuthority());
             final AttributionSource original = setCallingAttributionSource(
                     attributionSource);
             try {
@@ -367,7 +367,7 @@
                 throw e.rethrowAsRuntimeException();
             } finally {
                 setCallingAttributionSource(original);
-                Trace.traceEnd(TRACE_TAG_DATABASE);
+                Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
             }
         }
 
@@ -404,7 +404,7 @@
                     }
                 }
             }
-            traceBegin(TRACE_TAG_DATABASE, "applyBatch: ", authority);
+            traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "applyBatch: ", authority);
             final AttributionSource original = setCallingAttributionSource(
                     attributionSource);
             try {
@@ -423,7 +423,7 @@
                 throw e.rethrowAsRuntimeException();
             } finally {
                 setCallingAttributionSource(original);
-                Trace.traceEnd(TRACE_TAG_DATABASE);
+                Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
             }
         }
 
@@ -436,7 +436,7 @@
                     != PermissionChecker.PERMISSION_GRANTED) {
                 return 0;
             }
-            traceBegin(TRACE_TAG_DATABASE, "delete: ", uri.getAuthority());
+            traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "delete: ", uri.getAuthority());
             final AttributionSource original = setCallingAttributionSource(
                     attributionSource);
             try {
@@ -445,7 +445,7 @@
                 throw e.rethrowAsRuntimeException();
             } finally {
                 setCallingAttributionSource(original);
-                Trace.traceEnd(TRACE_TAG_DATABASE);
+                Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
             }
         }
 
@@ -458,7 +458,7 @@
                     != PermissionChecker.PERMISSION_GRANTED) {
                 return 0;
             }
-            traceBegin(TRACE_TAG_DATABASE, "update: ", uri.getAuthority());
+            traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "update: ", uri.getAuthority());
             final AttributionSource original = setCallingAttributionSource(
                     attributionSource);
             try {
@@ -467,7 +467,7 @@
                 throw e.rethrowAsRuntimeException();
             } finally {
                 setCallingAttributionSource(original);
-                Trace.traceEnd(TRACE_TAG_DATABASE);
+                Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
             }
         }
 
@@ -478,7 +478,7 @@
             uri = validateIncomingUri(uri);
             uri = maybeGetUriWithoutUserId(uri);
             enforceFilePermission(attributionSource, uri, mode);
-            traceBegin(TRACE_TAG_DATABASE, "openFile: ", uri.getAuthority());
+            traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "openFile: ", uri.getAuthority());
             final AttributionSource original = setCallingAttributionSource(
                     attributionSource);
             try {
@@ -488,7 +488,7 @@
                 throw e.rethrowAsRuntimeException();
             } finally {
                 setCallingAttributionSource(original);
-                Trace.traceEnd(TRACE_TAG_DATABASE);
+                Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
             }
         }
 
@@ -499,7 +499,7 @@
             uri = validateIncomingUri(uri);
             uri = maybeGetUriWithoutUserId(uri);
             enforceFilePermission(attributionSource, uri, mode);
-            traceBegin(TRACE_TAG_DATABASE, "openAssetFile: ", uri.getAuthority());
+            traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "openAssetFile: ", uri.getAuthority());
             final AttributionSource original = setCallingAttributionSource(
                     attributionSource);
             try {
@@ -509,7 +509,7 @@
                 throw e.rethrowAsRuntimeException();
             } finally {
                 setCallingAttributionSource(original);
-                Trace.traceEnd(TRACE_TAG_DATABASE);
+                Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
             }
         }
 
@@ -518,7 +518,7 @@
                 String method, @Nullable String arg, @Nullable Bundle extras) {
             validateIncomingAuthority(authority);
             Bundle.setDefusable(extras, true);
-            traceBegin(TRACE_TAG_DATABASE, "call: ", authority);
+            traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "call: ", authority);
             final AttributionSource original = setCallingAttributionSource(
                     attributionSource);
             try {
@@ -527,7 +527,7 @@
                 throw e.rethrowAsRuntimeException();
             } finally {
                 setCallingAttributionSource(original);
-                Trace.traceEnd(TRACE_TAG_DATABASE);
+                Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
             }
         }
 
@@ -536,13 +536,13 @@
             // getCallingPackage() isn't available in getType(), as the javadoc states.
             uri = validateIncomingUri(uri);
             uri = maybeGetUriWithoutUserId(uri);
-            traceBegin(TRACE_TAG_DATABASE, "getStreamTypes: ", uri.getAuthority());
+            traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "getStreamTypes: ", uri.getAuthority());
             try {
                 return mInterface.getStreamTypes(uri, mimeTypeFilter);
             } catch (RemoteException e) {
                 throw e.rethrowAsRuntimeException();
             } finally {
-                Trace.traceEnd(TRACE_TAG_DATABASE);
+                Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
             }
         }
 
@@ -554,7 +554,7 @@
             uri = validateIncomingUri(uri);
             uri = maybeGetUriWithoutUserId(uri);
             enforceFilePermission(attributionSource, uri, "r");
-            traceBegin(TRACE_TAG_DATABASE, "openTypedAssetFile: ", uri.getAuthority());
+            traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "openTypedAssetFile: ", uri.getAuthority());
             final AttributionSource original = setCallingAttributionSource(
                     attributionSource);
             try {
@@ -564,7 +564,7 @@
                 throw e.rethrowAsRuntimeException();
             } finally {
                 setCallingAttributionSource(original);
-                Trace.traceEnd(TRACE_TAG_DATABASE);
+                Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
             }
         }
 
@@ -582,7 +582,7 @@
                     != PermissionChecker.PERMISSION_GRANTED) {
                 return null;
             }
-            traceBegin(TRACE_TAG_DATABASE, "canonicalize: ", uri.getAuthority());
+            traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "canonicalize: ", uri.getAuthority());
             final AttributionSource original = setCallingAttributionSource(
                     attributionSource);
             try {
@@ -591,7 +591,7 @@
                 throw e.rethrowAsRuntimeException();
             } finally {
                 setCallingAttributionSource(original);
-                Trace.traceEnd(TRACE_TAG_DATABASE);
+                Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
             }
         }
 
@@ -618,7 +618,7 @@
                     != PermissionChecker.PERMISSION_GRANTED) {
                 return null;
             }
-            traceBegin(TRACE_TAG_DATABASE, "uncanonicalize: ", uri.getAuthority());
+            traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "uncanonicalize: ", uri.getAuthority());
             final AttributionSource original = setCallingAttributionSource(
                     attributionSource);
             try {
@@ -627,7 +627,7 @@
                 throw e.rethrowAsRuntimeException();
             } finally {
                 setCallingAttributionSource(original);
-                Trace.traceEnd(TRACE_TAG_DATABASE);
+                Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
             }
         }
 
@@ -654,7 +654,7 @@
                     != PermissionChecker.PERMISSION_GRANTED) {
                 return false;
             }
-            traceBegin(TRACE_TAG_DATABASE, "refresh: ", uri.getAuthority());
+            traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "refresh: ", uri.getAuthority());
             final AttributionSource original = setCallingAttributionSource(
                     attributionSource);
             try {
@@ -662,7 +662,7 @@
                         CancellationSignal.fromTransport(cancellationSignal));
             } finally {
                 setCallingAttributionSource(original);
-                Trace.traceEnd(TRACE_TAG_DATABASE);
+                Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
             }
         }
 
@@ -671,7 +671,7 @@
                 int uid, int modeFlags) {
             uri = validateIncomingUri(uri);
             uri = maybeGetUriWithoutUserId(uri);
-            traceBegin(TRACE_TAG_DATABASE, "checkUriPermission: ", uri.getAuthority());
+            traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "checkUriPermission: ", uri.getAuthority());
             final AttributionSource original = setCallingAttributionSource(
                     attributionSource);
             try {
@@ -680,7 +680,7 @@
                 throw e.rethrowAsRuntimeException();
             } finally {
                 setCallingAttributionSource(original);
-                Trace.traceEnd(TRACE_TAG_DATABASE);
+                Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
             }
         }
 
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index ccc2441..dd517c9 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -10422,7 +10422,7 @@
     private static final PropertyInvalidatedCache<ApplicationInfoQuery, ApplicationInfo>
             sApplicationInfoCache =
             new PropertyInvalidatedCache<ApplicationInfoQuery, ApplicationInfo>(
-                    16, PermissionManager.CACHE_KEY_PACKAGE_INFO,
+                    32, PermissionManager.CACHE_KEY_PACKAGE_INFO,
                     "getApplicationInfo") {
                 @Override
                 public ApplicationInfo recompute(ApplicationInfoQuery query) {
@@ -10523,7 +10523,7 @@
     private static final PropertyInvalidatedCache<PackageInfoQuery, PackageInfo>
             sPackageInfoCache =
             new PropertyInvalidatedCache<PackageInfoQuery, PackageInfo>(
-                    32, PermissionManager.CACHE_KEY_PACKAGE_INFO,
+                    64, PermissionManager.CACHE_KEY_PACKAGE_INFO,
                     "getPackageInfo") {
                 @Override
                 public PackageInfo recompute(PackageInfoQuery query) {
diff --git a/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java b/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java
index 336ef7a..41822e7 100644
--- a/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java
+++ b/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java
@@ -1693,8 +1693,10 @@
                         ((i != idx) || notifyCurrentIndex)) {
                     TotalCaptureResult result = previewMap.valueAt(i).second;
                     Long timestamp = result.get(CaptureResult.SENSOR_TIMESTAMP);
-                    mCaptureResultHandler.onCaptureCompleted(timestamp,
-                            initializeFilteredResults(result));
+                    if (mCaptureResultHandler != null) {
+                        mCaptureResultHandler.onCaptureCompleted(timestamp,
+                                initializeFilteredResults(result));
+                    }
 
                     Log.w(TAG, "Preview frame drop with timestamp: " + previewMap.keyAt(i));
                     final long ident = Binder.clearCallingIdentity();
diff --git a/core/java/android/service/voice/VoiceInteractionSession.java b/core/java/android/service/voice/VoiceInteractionSession.java
index 42500b4..b7ec486 100644
--- a/core/java/android/service/voice/VoiceInteractionSession.java
+++ b/core/java/android/service/voice/VoiceInteractionSession.java
@@ -84,12 +84,12 @@
 
 /**
  * An active voice interaction session, providing a facility for the implementation
- * to interact with the user in the voice interaction layer.  The user interface is
- * initially shown by default, and can be created be overriding {@link #onCreateContentView()}
+ * to interact with the user in the voice interaction layer. The user interface is
+ * initially shown by default, and can be created by overriding {@link #onCreateContentView()}
  * in which the UI can be built.
  *
  * <p>A voice interaction session can be self-contained, ultimately calling {@link #finish}
- * when done.  It can also initiate voice interactions with applications by calling
+ * when done. It can also initiate voice interactions with applications by calling
  * {@link #startVoiceActivity}</p>.
  */
 public class VoiceInteractionSession implements KeyEvent.Callback, ComponentCallbacks2 {
diff --git a/core/java/android/view/InsetsState.java b/core/java/android/view/InsetsState.java
index c198098..9f426a1 100644
--- a/core/java/android/view/InsetsState.java
+++ b/core/java/android/view/InsetsState.java
@@ -81,7 +81,9 @@
             ITYPE_BOTTOM_MANDATORY_GESTURES,
             ITYPE_LEFT_MANDATORY_GESTURES,
             ITYPE_RIGHT_MANDATORY_GESTURES,
+            ITYPE_LEFT_TAPPABLE_ELEMENT,
             ITYPE_TOP_TAPPABLE_ELEMENT,
+            ITYPE_RIGHT_TAPPABLE_ELEMENT,
             ITYPE_BOTTOM_TAPPABLE_ELEMENT,
             ITYPE_LEFT_DISPLAY_CUTOUT,
             ITYPE_TOP_DISPLAY_CUTOUT,
diff --git a/core/java/android/view/RemoteAnimationTarget.java b/core/java/android/view/RemoteAnimationTarget.java
index a468951..44f419a 100644
--- a/core/java/android/view/RemoteAnimationTarget.java
+++ b/core/java/android/view/RemoteAnimationTarget.java
@@ -45,6 +45,7 @@
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.util.proto.ProtoOutputStream;
+import android.window.TaskSnapshot;
 
 import java.io.PrintWriter;
 import java.lang.annotation.Retention;
@@ -234,6 +235,12 @@
      */
     public @ColorInt int backgroundColor;
 
+    /**
+     * Whether the activity is going to show IME on the target window after the app transition.
+     * @see TaskSnapshot#hasImeSurface() that used the task snapshot during animating task.
+     */
+    public boolean willShowImeOnTarget;
+
     public RemoteAnimationTarget(int taskId, int mode, SurfaceControl leash, boolean isTranslucent,
             Rect clipRect, Rect contentInsets, int prefixOrderIndex, Point position,
             Rect localBounds, Rect screenSpaceBounds,
@@ -294,12 +301,21 @@
         hasAnimatingParent = in.readBoolean();
         backgroundColor = in.readInt();
         showBackdrop = in.readBoolean();
+        willShowImeOnTarget = in.readBoolean();
     }
 
     public void setShowBackdrop(boolean shouldShowBackdrop) {
         showBackdrop = shouldShowBackdrop;
     }
 
+    public void setWillShowImeOnTarget(boolean showImeOnTarget) {
+        willShowImeOnTarget = showImeOnTarget;
+    }
+
+    public boolean willShowImeOnTarget() {
+        return willShowImeOnTarget;
+    }
+
     @Override
     public int describeContents() {
         return 0;
@@ -328,6 +344,7 @@
         dest.writeBoolean(hasAnimatingParent);
         dest.writeInt(backgroundColor);
         dest.writeBoolean(showBackdrop);
+        dest.writeBoolean(willShowImeOnTarget);
     }
 
     public void dump(PrintWriter pw, String prefix) {
@@ -350,6 +367,7 @@
         pw.print(prefix); pw.print("hasAnimatingParent="); pw.print(hasAnimatingParent);
         pw.print(prefix); pw.print("backgroundColor="); pw.print(backgroundColor);
         pw.print(prefix); pw.print("showBackdrop="); pw.print(showBackdrop);
+        pw.print(prefix); pw.print("willShowImeOnTarget="); pw.print(willShowImeOnTarget);
     }
 
     public void dumpDebug(ProtoOutputStream proto, long fieldId) {
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index de02680..1bedbfc 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -848,6 +848,8 @@
 
     private int mLastTransformHint = Integer.MIN_VALUE;
 
+    private AccessibilityWindowAttributes mAccessibilityWindowAttributes;
+
     /**
      * A temporary object used so relayoutWindow can return the latest SyncSeqId
      * system. The SyncSeqId system was designed to work without synchronous relayout
@@ -1412,8 +1414,12 @@
         if (registered) {
             final AccessibilityWindowAttributes attributes = new AccessibilityWindowAttributes(
                     mWindowAttributes);
-            mAccessibilityManager.setAccessibilityWindowAttributes(getDisplayId(),
-                    mAttachInfo.mAccessibilityWindowId, attributes);
+            if (!attributes.equals(mAccessibilityWindowAttributes)) {
+                mAccessibilityWindowAttributes = attributes;
+                mAccessibilityManager.setAccessibilityWindowAttributes(getDisplayId(),
+                        mAttachInfo.mAccessibilityWindowId, attributes);
+            }
+
         }
     }
 
@@ -10365,6 +10371,7 @@
                     != AccessibilityWindowInfo.UNDEFINED_WINDOW_ID;
             if (registered) {
                 mAttachInfo.mAccessibilityWindowId = AccessibilityWindowInfo.UNDEFINED_WINDOW_ID;
+                mAccessibilityWindowAttributes = null;
                 mAccessibilityManager.removeAccessibilityInteractionConnection(mWindow);
             }
         }
@@ -10927,8 +10934,8 @@
 
     private void registerCompatOnBackInvokedCallback() {
         mCompatOnBackInvokedCallback = () -> {
-                sendBackKeyEvent(KeyEvent.ACTION_DOWN);
-                sendBackKeyEvent(KeyEvent.ACTION_UP);
+            sendBackKeyEvent(KeyEvent.ACTION_DOWN);
+            sendBackKeyEvent(KeyEvent.ACTION_UP);
         };
         mOnBackInvokedDispatcher.registerOnBackInvokedCallback(
                 OnBackInvokedDispatcher.PRIORITY_DEFAULT, mCompatOnBackInvokedCallback);
diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java
index fb298c7..b233e54 100644
--- a/core/java/android/widget/Editor.java
+++ b/core/java/android/widget/Editor.java
@@ -709,7 +709,7 @@
         }
 
         getPositionListener().addSubscriber(mCursorAnchorInfoNotifier, true);
-        resumeBlink();
+        makeBlink();
     }
 
     void onDetachedFromWindow() {
@@ -1685,17 +1685,12 @@
 
     void onWindowFocusChanged(boolean hasWindowFocus) {
         if (hasWindowFocus) {
-            if (mBlink != null) {
-                mBlink.uncancel();
-                makeBlink();
-            }
+            resumeBlink();
             if (mTextView.hasSelection() && !extractedTextModeWillBeStarted()) {
                 refreshTextActionMode();
             }
         } else {
-            if (mBlink != null) {
-                mBlink.cancel();
-            }
+            suspendBlink();
             if (mInputContentType != null) {
                 mInputContentType.enterDown = false;
             }
@@ -2851,7 +2846,8 @@
      * @return True when the TextView isFocused and has a valid zero-length selection (cursor).
      */
     private boolean shouldBlink() {
-        if (!isCursorVisible() || !mTextView.isFocused()) return false;
+        if (!isCursorVisible() || !mTextView.isFocused()
+                || mTextView.getWindowVisibility() != mTextView.VISIBLE) return false;
 
         final int start = mTextView.getSelectionStart();
         if (start < 0) return false;
diff --git a/core/java/android/window/ProxyOnBackInvokedDispatcher.java b/core/java/android/window/ProxyOnBackInvokedDispatcher.java
index 8ad1093..49acde9 100644
--- a/core/java/android/window/ProxyOnBackInvokedDispatcher.java
+++ b/core/java/android/window/ProxyOnBackInvokedDispatcher.java
@@ -122,7 +122,7 @@
         }
         for (Pair<OnBackInvokedCallback, Integer> callbackPair : mCallbacks) {
             int priority = callbackPair.second;
-            if (priority >= 0) {
+            if (priority >= PRIORITY_DEFAULT) {
                 mActualDispatcher.registerOnBackInvokedCallback(priority, callbackPair.first);
             } else {
                 mActualDispatcher.registerSystemOnBackInvokedCallback(callbackPair.first);
diff --git a/core/java/android/window/TransitionInfo.java b/core/java/android/window/TransitionInfo.java
index 1f3841a..b263b08 100644
--- a/core/java/android/window/TransitionInfo.java
+++ b/core/java/android/window/TransitionInfo.java
@@ -116,6 +116,9 @@
     /** The first unused bit. This can be used by remotes to attach custom flags to this change. */
     public static final int FLAG_FIRST_CUSTOM = 1 << 10;
 
+    /** The container is going to show IME on its task after the transition. */
+    public static final int FLAG_WILL_IME_SHOWN = 1 << 11;
+
     /** @hide */
     @IntDef(prefix = { "FLAG_" }, value = {
             FLAG_NONE,
@@ -129,7 +132,8 @@
             FLAG_DISPLAY_HAS_ALERT_WINDOWS,
             FLAG_IS_INPUT_METHOD,
             FLAG_IS_EMBEDDED,
-            FLAG_FIRST_CUSTOM
+            FLAG_FIRST_CUSTOM,
+            FLAG_WILL_IME_SHOWN
     })
     public @interface ChangeFlags {}
 
diff --git a/core/java/android/window/WindowContainerTransaction.java b/core/java/android/window/WindowContainerTransaction.java
index 5a0d11d..2126166 100644
--- a/core/java/android/window/WindowContainerTransaction.java
+++ b/core/java/android/window/WindowContainerTransaction.java
@@ -670,6 +670,39 @@
     }
 
     /**
+     * Sets/removes the always on top flag for this {@code windowContainer}. See
+     * {@link com.android.server.wm.ConfigurationContainer#setAlwaysOnTop(boolean)}.
+     * Please note that this method is only intended to be used for a
+     * {@link com.android.server.wm.DisplayArea}.
+     *
+     * <p>
+     *     Setting always on top to {@code True} will also make the {@code windowContainer} to move
+     *     to the top.
+     * </p>
+     * <p>
+     *     Setting always on top to {@code False} will make this {@code windowContainer} to move
+     *     below the other always on top sibling containers.
+     * </p>
+     *
+     * @param windowContainer the container which the flag need to be updated for.
+     * @param alwaysOnTop denotes whether or not always on top flag should be set.
+     * @hide
+     */
+    @NonNull
+    public WindowContainerTransaction setAlwaysOnTop(
+            @NonNull WindowContainerToken windowContainer,
+            boolean alwaysOnTop) {
+        final HierarchyOp hierarchyOp =
+                new HierarchyOp.Builder(
+                        HierarchyOp.HIERARCHY_OP_TYPE_SET_ALWAYS_ON_TOP)
+                        .setContainer(windowContainer.asBinder())
+                        .setAlwaysOnTop(alwaysOnTop)
+                        .build();
+        mHierarchyOps.add(hierarchyOp);
+        return this;
+    }
+
+    /**
      * When this {@link WindowContainerTransaction} failed to finish on the server side, it will
      * trigger callback with this {@param errorCallbackToken}.
      * @param errorCallbackToken    client provided token that will be passed back as parameter in
@@ -1078,6 +1111,7 @@
         public static final int HIERARCHY_OP_TYPE_ADD_RECT_INSETS_PROVIDER = 16;
         public static final int HIERARCHY_OP_TYPE_REMOVE_INSETS_PROVIDER = 17;
         public static final int HIERARCHY_OP_TYPE_REQUEST_FOCUS_ON_TASK_FRAGMENT = 18;
+        public static final int HIERARCHY_OP_TYPE_SET_ALWAYS_ON_TOP = 19;
 
         // The following key(s) are for use with mLaunchOptions:
         // When launching a task (eg. from recents), this is the taskId to be launched.
@@ -1128,6 +1162,8 @@
         @Nullable
         private ShortcutInfo mShortcutInfo;
 
+        private boolean mAlwaysOnTop;
+
         public static HierarchyOp createForReparent(
                 @NonNull IBinder container, @Nullable IBinder reparent, boolean toTop) {
             return new HierarchyOp.Builder(HIERARCHY_OP_TYPE_REPARENT)
@@ -1225,6 +1261,7 @@
             mTaskFragmentCreationOptions = copy.mTaskFragmentCreationOptions;
             mPendingIntent = copy.mPendingIntent;
             mShortcutInfo = copy.mShortcutInfo;
+            mAlwaysOnTop = copy.mAlwaysOnTop;
         }
 
         protected HierarchyOp(Parcel in) {
@@ -1246,6 +1283,7 @@
             mTaskFragmentCreationOptions = in.readTypedObject(TaskFragmentCreationParams.CREATOR);
             mPendingIntent = in.readTypedObject(PendingIntent.CREATOR);
             mShortcutInfo = in.readTypedObject(ShortcutInfo.CREATOR);
+            mAlwaysOnTop = in.readBoolean();
         }
 
         public int getType() {
@@ -1311,6 +1349,10 @@
             return mActivityIntent;
         }
 
+        public boolean isAlwaysOnTop() {
+            return mAlwaysOnTop;
+        }
+
         @Nullable
         public TaskFragmentCreationParams getTaskFragmentCreationOptions() {
             return mTaskFragmentCreationOptions;
@@ -1379,6 +1421,9 @@
                             + " insetsType=" + Arrays.toString(mInsetsTypes) + "}";
                 case HIERARCHY_OP_TYPE_REQUEST_FOCUS_ON_TASK_FRAGMENT:
                     return "{requestFocusOnTaskFragment: container=" + mContainer + "}";
+                case HIERARCHY_OP_TYPE_SET_ALWAYS_ON_TOP:
+                    return "{setAlwaysOnTop: container=" + mContainer
+                            + " alwaysOnTop=" + mAlwaysOnTop + "}";
                 default:
                     return "{mType=" + mType + " container=" + mContainer + " reparent=" + mReparent
                             + " mToTop=" + mToTop
@@ -1408,6 +1453,7 @@
             dest.writeTypedObject(mTaskFragmentCreationOptions, flags);
             dest.writeTypedObject(mPendingIntent, flags);
             dest.writeTypedObject(mShortcutInfo, flags);
+            dest.writeBoolean(mAlwaysOnTop);
         }
 
         @Override
@@ -1466,6 +1512,8 @@
             @Nullable
             private ShortcutInfo mShortcutInfo;
 
+            private boolean mAlwaysOnTop;
+
             Builder(int type) {
                 mType = type;
             }
@@ -1525,6 +1573,11 @@
                 return this;
             }
 
+            Builder setAlwaysOnTop(boolean alwaysOnTop) {
+                mAlwaysOnTop = alwaysOnTop;
+                return this;
+            }
+
             Builder setTaskFragmentCreationOptions(
                     @Nullable TaskFragmentCreationParams taskFragmentCreationOptions) {
                 mTaskFragmentCreationOptions = taskFragmentCreationOptions;
@@ -1553,6 +1606,7 @@
                 hierarchyOp.mLaunchOptions = mLaunchOptions;
                 hierarchyOp.mActivityIntent = mActivityIntent;
                 hierarchyOp.mPendingIntent = mPendingIntent;
+                hierarchyOp.mAlwaysOnTop = mAlwaysOnTop;
                 hierarchyOp.mTaskFragmentCreationOptions = mTaskFragmentCreationOptions;
                 hierarchyOp.mShortcutInfo = mShortcutInfo;
 
diff --git a/core/java/com/android/internal/util/ArrayUtils.java b/core/java/com/android/internal/util/ArrayUtils.java
index 6eae34b..b64771b 100644
--- a/core/java/com/android/internal/util/ArrayUtils.java
+++ b/core/java/com/android/internal/util/ArrayUtils.java
@@ -39,8 +39,7 @@
 import java.util.function.IntFunction;
 
 /**
- * ArrayUtils contains some methods that you can call to find out
- * the most efficient increments by which to grow arrays.
+ * Static utility methods for arrays that aren't already included in {@link java.util.Arrays}.
  */
 public class ArrayUtils {
     private static final int CACHE_SIZE = 73;
@@ -351,15 +350,16 @@
     }
 
     /**
-     * Combine multiple arrays into a single array.
+     * Returns the concatenation of the given arrays.  Only works for object arrays, not for
+     * primitive arrays.  See {@link #concat(byte[]...)} for a variant that works on byte arrays.
      *
      * @param kind The class of the array elements
-     * @param arrays The arrays to combine
+     * @param arrays The arrays to concatenate.  Null arrays are treated as empty.
      * @param <T> The class of the array elements (inferred from kind).
      * @return A single array containing all the elements of the parameter arrays.
      */
     @SuppressWarnings("unchecked")
-    public static @NonNull <T> T[] concatElements(Class<T> kind, @Nullable T[]... arrays) {
+    public static @NonNull <T> T[] concat(Class<T> kind, @Nullable T[]... arrays) {
         if (arrays == null || arrays.length == 0) {
             return createEmptyArray(kind);
         }
@@ -400,6 +400,29 @@
         return (T[]) Array.newInstance(kind, 0);
     }
 
+    /**
+     * Returns the concatenation of the given byte arrays.  Null arrays are treated as empty.
+     */
+    public static @NonNull byte[] concat(@Nullable byte[]... arrays) {
+        if (arrays == null) {
+            return new byte[0];
+        }
+        int totalLength = 0;
+        for (byte[] a : arrays) {
+            if (a != null) {
+                totalLength += a.length;
+            }
+        }
+        final byte[] result = new byte[totalLength];
+        int pos = 0;
+        for (byte[] a : arrays) {
+            if (a != null) {
+                System.arraycopy(a, 0, result, pos, a.length);
+                pos += a.length;
+            }
+        }
+        return result;
+    }
 
     /**
      * Adds value to given array if not already present, providing set-like
diff --git a/core/java/com/android/internal/util/LatencyTracker.java b/core/java/com/android/internal/util/LatencyTracker.java
index ca40a40..14a6d5e 100644
--- a/core/java/com/android/internal/util/LatencyTracker.java
+++ b/core/java/com/android/internal/util/LatencyTracker.java
@@ -142,6 +142,11 @@
      */
     public static final int ACTION_LOAD_SHARE_SHEET = 16;
 
+    /**
+     * Time it takes to show AOD display after folding the device.
+     */
+    public static final int ACTION_FOLD_TO_AOD = 17;
+
     private static final int[] ACTIONS_ALL = {
         ACTION_EXPAND_PANEL,
         ACTION_TOGGLE_RECENTS,
@@ -160,6 +165,7 @@
         ACTION_UDFPS_ILLUMINATE,
         ACTION_SHOW_BACK_ARROW,
         ACTION_LOAD_SHARE_SHEET,
+        ACTION_FOLD_TO_AOD,
     };
 
     /** @hide */
@@ -181,6 +187,7 @@
         ACTION_UDFPS_ILLUMINATE,
         ACTION_SHOW_BACK_ARROW,
         ACTION_LOAD_SHARE_SHEET,
+        ACTION_FOLD_TO_AOD
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface Action {
@@ -204,6 +211,7 @@
             FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_UDFPS_ILLUMINATE,
             FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_SHOW_BACK_ARROW,
             FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_LOAD_SHARE_SHEET,
+            FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_FOLD_TO_AOD
     };
 
     private static LatencyTracker sLatencyTracker;
@@ -297,6 +305,8 @@
                 return "ACTION_SHOW_BACK_ARROW";
             case 17:
                 return "ACTION_LOAD_SHARE_SHEET";
+            case 19:
+                return "ACTION_FOLD_TO_AOD";
             default:
                 throw new IllegalArgumentException("Invalid action");
         }
diff --git a/core/java/com/android/internal/util/OWNERS b/core/java/com/android/internal/util/OWNERS
index 354dd9a..1808bd5 100644
--- a/core/java/com/android/internal/util/OWNERS
+++ b/core/java/com/android/internal/util/OWNERS
@@ -5,3 +5,4 @@
 per-file Protocol* = etancohen@google.com, lorenzo@google.com
 per-file State* = jchalard@google.com, lorenzo@google.com, satk@google.com
 per-file *Dump* = file:/core/java/com/android/internal/util/dump/OWNERS
+per-file *Screenshot* = file:/packages/SystemUI/src/com/android/systemui/screenshot/OWNERS
diff --git a/core/java/com/android/internal/util/ScreenshotHelper.java b/core/java/com/android/internal/util/ScreenshotHelper.java
index 518fa53..8e54746 100644
--- a/core/java/com/android/internal/util/ScreenshotHelper.java
+++ b/core/java/com/android/internal/util/ScreenshotHelper.java
@@ -1,7 +1,6 @@
 package com.android.internal.util;
 
 import static android.content.Intent.ACTION_USER_SWITCHED;
-import static android.view.WindowManager.ScreenshotSource.SCREENSHOT_OTHER;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -29,6 +28,10 @@
 import android.os.UserHandle;
 import android.util.Log;
 import android.view.WindowManager;
+import android.view.WindowManager.ScreenshotSource;
+import android.view.WindowManager.ScreenshotType;
+
+import com.android.internal.annotations.VisibleForTesting;
 
 import java.util.Objects;
 import java.util.function.Consumer;
@@ -42,24 +45,28 @@
      * Describes a screenshot request (to make it easier to pass data through to the handler).
      */
     public static class ScreenshotRequest implements Parcelable {
-        private int mSource;
-        private boolean mHasStatusBar;
-        private boolean mHasNavBar;
-        private Bundle mBitmapBundle;
-        private Rect mBoundsInScreen;
-        private Insets mInsets;
-        private int mTaskId;
-        private int mUserId;
-        private ComponentName mTopComponent;
+        private final int mSource;
+        private final Bundle mBitmapBundle;
+        private final Rect mBoundsInScreen;
+        private final Insets mInsets;
+        private final int mTaskId;
+        private final int mUserId;
+        private final ComponentName mTopComponent;
 
-        ScreenshotRequest(int source, boolean hasStatus, boolean hasNav) {
+        @VisibleForTesting
+        public ScreenshotRequest(int source) {
             mSource = source;
-            mHasStatusBar = hasStatus;
-            mHasNavBar = hasNav;
+            mBitmapBundle = null;
+            mBoundsInScreen = null;
+            mInsets = null;
+            mTaskId = -1;
+            mUserId = -1;
+            mTopComponent = null;
         }
 
-        ScreenshotRequest(int source, Bundle bitmapBundle, Rect boundsInScreen, Insets insets,
-                int taskId, int userId, ComponentName topComponent) {
+        @VisibleForTesting
+        public ScreenshotRequest(int source, Bundle bitmapBundle, Rect boundsInScreen,
+                Insets insets, int taskId, int userId, ComponentName topComponent) {
             mSource = source;
             mBitmapBundle = bitmapBundle;
             mBoundsInScreen = boundsInScreen;
@@ -71,16 +78,21 @@
 
         ScreenshotRequest(Parcel in) {
             mSource = in.readInt();
-            mHasStatusBar = in.readBoolean();
-            mHasNavBar = in.readBoolean();
-
             if (in.readInt() == 1) {
                 mBitmapBundle = in.readBundle(getClass().getClassLoader());
-                mBoundsInScreen = in.readParcelable(Rect.class.getClassLoader(), android.graphics.Rect.class);
-                mInsets = in.readParcelable(Insets.class.getClassLoader(), android.graphics.Insets.class);
+                mBoundsInScreen = in.readParcelable(Rect.class.getClassLoader(), Rect.class);
+                mInsets = in.readParcelable(Insets.class.getClassLoader(), Insets.class);
                 mTaskId = in.readInt();
                 mUserId = in.readInt();
-                mTopComponent = in.readParcelable(ComponentName.class.getClassLoader(), android.content.ComponentName.class);
+                mTopComponent = in.readParcelable(ComponentName.class.getClassLoader(),
+                        ComponentName.class);
+            } else {
+                mBitmapBundle = null;
+                mBoundsInScreen = null;
+                mInsets = null;
+                mTaskId = -1;
+                mUserId = -1;
+                mTopComponent = null;
             }
         }
 
@@ -88,14 +100,6 @@
             return mSource;
         }
 
-        public boolean getHasStatusBar() {
-            return mHasStatusBar;
-        }
-
-        public boolean getHasNavBar() {
-            return mHasNavBar;
-        }
-
         public Bundle getBitmapBundle() {
             return mBitmapBundle;
         }
@@ -112,7 +116,6 @@
             return mTaskId;
         }
 
-
         public int getUserId() {
             return mUserId;
         }
@@ -129,8 +132,6 @@
         @Override
         public void writeToParcel(Parcel dest, int flags) {
             dest.writeInt(mSource);
-            dest.writeBoolean(mHasStatusBar);
-            dest.writeBoolean(mHasNavBar);
             if (mBitmapBundle == null) {
                 dest.writeInt(0);
             } else {
@@ -144,7 +145,8 @@
             }
         }
 
-        public static final @NonNull Parcelable.Creator<ScreenshotRequest> CREATOR =
+        @NonNull
+        public static final Parcelable.Creator<ScreenshotRequest> CREATOR =
                 new Parcelable.Creator<ScreenshotRequest>() {
 
                     @Override
@@ -254,113 +256,71 @@
 
     /**
      * Request a screenshot be taken.
-     *
+     * <p>
      * Added to support reducing unit test duration; the method variant without a timeout argument
      * is recommended for general use.
      *
-     * @param screenshotType     The type of screenshot, for example either
-     *                           {@link android.view.WindowManager#TAKE_SCREENSHOT_FULLSCREEN}
-     *                           or
-     *                           {@link android.view.WindowManager#TAKE_SCREENSHOT_SELECTED_REGION}
-     * @param hasStatus          {@code true} if the status bar is currently showing. {@code false}
-     *                           if not.
-     * @param hasNav             {@code true} if the navigation bar is currently showing. {@code
-     *                           false} if not.
-     * @param source             The source of the screenshot request. One of
-     *                           {SCREENSHOT_GLOBAL_ACTIONS, SCREENSHOT_KEY_CHORD,
-     *                           SCREENSHOT_OVERVIEW, SCREENSHOT_OTHER}
-     * @param handler            A handler used in case the screenshot times out
-     * @param completionConsumer Consumes `false` if a screenshot was not taken, and `true` if the
-     *                           screenshot was taken.
+     * @param screenshotType The type of screenshot, defined by {@link ScreenshotType}
+     * @param source The source of the screenshot request, defined by {@link ScreenshotSource}
+     * @param handler used to process messages received from the screenshot service
+     * @param completionConsumer receives the URI of the captured screenshot, once saved or
+     *         null if no screenshot was saved
      */
-    public void takeScreenshot(final int screenshotType, final boolean hasStatus,
-            final boolean hasNav, int source, @NonNull Handler handler,
-            @Nullable Consumer<Uri> completionConsumer) {
-        ScreenshotRequest screenshotRequest = new ScreenshotRequest(source, hasStatus, hasNav);
-        takeScreenshot(screenshotType, SCREENSHOT_TIMEOUT_MS, handler, screenshotRequest,
+    public void takeScreenshot(@ScreenshotType int screenshotType, @ScreenshotSource int source,
+            @NonNull Handler handler, @Nullable Consumer<Uri> completionConsumer) {
+        ScreenshotRequest screenshotRequest = new ScreenshotRequest(source);
+        takeScreenshot(screenshotType, handler, screenshotRequest, SCREENSHOT_TIMEOUT_MS,
                 completionConsumer);
     }
 
     /**
-     * Request a screenshot be taken, with provided reason.
-     *
-     * @param screenshotType     The type of screenshot, for example either
-     *                           {@link android.view.WindowManager#TAKE_SCREENSHOT_FULLSCREEN}
-     *                           or
-     *                           {@link android.view.WindowManager#TAKE_SCREENSHOT_SELECTED_REGION}
-     * @param hasStatus          {@code true} if the status bar is currently showing. {@code false}
-     *                           if
-     *                           not.
-     * @param hasNav             {@code true} if the navigation bar is currently showing. {@code
-     *                           false}
-     *                           if not.
-     * @param handler            A handler used in case the screenshot times out
-     * @param completionConsumer Consumes `false` if a screenshot was not taken, and `true` if the
-     *                           screenshot was taken.
-     */
-    public void takeScreenshot(final int screenshotType, final boolean hasStatus,
-            final boolean hasNav, @NonNull Handler handler,
-            @Nullable Consumer<Uri> completionConsumer) {
-        takeScreenshot(screenshotType, hasStatus, hasNav, SCREENSHOT_TIMEOUT_MS, handler,
-                completionConsumer);
-    }
-
-    /**
-     * Request a screenshot be taken with a specific timeout.
-     *
+     * Request a screenshot be taken.
+     * <p>
      * Added to support reducing unit test duration; the method variant without a timeout argument
      * is recommended for general use.
      *
-     * @param screenshotType     The type of screenshot, for example either
-     *                           {@link android.view.WindowManager#TAKE_SCREENSHOT_FULLSCREEN}
-     *                           or
-     *                           {@link android.view.WindowManager#TAKE_SCREENSHOT_SELECTED_REGION}
-     * @param hasStatus          {@code true} if the status bar is currently showing. {@code false}
-     *                           if
-     *                           not.
-     * @param hasNav             {@code true} if the navigation bar is currently showing. {@code
-     *                           false}
-     *                           if not.
-     * @param timeoutMs          If the screenshot hasn't been completed within this time period,
-     *                           the screenshot attempt will be cancelled and `completionConsumer`
-     *                           will be run.
-     * @param handler            A handler used in case the screenshot times out
-     * @param completionConsumer Consumes `false` if a screenshot was not taken, and `true` if the
-     *                           screenshot was taken.
+     * @param screenshotType The type of screenshot, defined by {@link ScreenshotType}
+     * @param source The source of the screenshot request, defined by {@link ScreenshotSource}
+     * @param handler used to process messages received from the screenshot service
+     * @param timeoutMs time limit for processing, intended only for testing
+     * @param completionConsumer receives the URI of the captured screenshot, once saved or
+     *         null if no screenshot was saved
      */
-    public void takeScreenshot(final int screenshotType, final boolean hasStatus,
-            final boolean hasNav, long timeoutMs, @NonNull Handler handler,
-            @Nullable Consumer<Uri> completionConsumer) {
-        ScreenshotRequest screenshotRequest = new ScreenshotRequest(SCREENSHOT_OTHER, hasStatus,
-                hasNav);
-        takeScreenshot(screenshotType, timeoutMs, handler, screenshotRequest, completionConsumer);
+    @VisibleForTesting
+    public void takeScreenshot(@ScreenshotType int screenshotType, @ScreenshotSource int source,
+            @NonNull Handler handler, long timeoutMs, @Nullable Consumer<Uri> completionConsumer) {
+        ScreenshotRequest screenshotRequest = new ScreenshotRequest(source);
+        takeScreenshot(screenshotType, handler, screenshotRequest, timeoutMs, completionConsumer);
     }
 
     /**
      * Request that provided image be handled as if it was a screenshot.
      *
-     * @param screenshotBundle   Bundle containing the buffer and color space of the screenshot.
-     * @param boundsInScreen     The bounds in screen coordinates that the bitmap orginated from.
-     * @param insets             The insets that the image was shown with, inside the screenbounds.
-     * @param taskId             The taskId of the task that the screen shot was taken of.
-     * @param userId             The userId of user running the task provided in taskId.
-     * @param topComponent       The component name of the top component running in the task.
-     * @param handler            A handler used in case the screenshot times out
-     * @param completionConsumer Consumes `false` if a screenshot was not taken, and `true` if the
-     *                           screenshot was taken.
+     * @param screenshotBundle Bundle containing the buffer and color space of the screenshot.
+     * @param boundsInScreen The bounds in screen coordinates that the bitmap originated from.
+     * @param insets The insets that the image was shown with, inside the screen bounds.
+     * @param taskId The taskId of the task that the screen shot was taken of.
+     * @param userId The userId of user running the task provided in taskId.
+     * @param topComponent The component name of the top component running in the task.
+     * @param source The source of the screenshot request, defined by {@link ScreenshotSource}
+     * @param handler A handler used in case the screenshot times out
+     * @param completionConsumer receives the URI of the captured screenshot, once saved or
+     *         null if no screenshot was saved
      */
     public void provideScreenshot(@NonNull Bundle screenshotBundle, @NonNull Rect boundsInScreen,
-            @NonNull Insets insets, int taskId, int userId, ComponentName topComponent, int source,
-            @NonNull Handler handler, @Nullable Consumer<Uri> completionConsumer) {
-        ScreenshotRequest screenshotRequest =
-                new ScreenshotRequest(source, screenshotBundle, boundsInScreen, insets, taskId,
-                        userId, topComponent);
-        takeScreenshot(WindowManager.TAKE_SCREENSHOT_PROVIDED_IMAGE, SCREENSHOT_TIMEOUT_MS,
-                handler, screenshotRequest, completionConsumer);
+            @NonNull Insets insets, int taskId, int userId, ComponentName topComponent,
+            @ScreenshotSource int source, @NonNull Handler handler,
+            @Nullable Consumer<Uri> completionConsumer) {
+        ScreenshotRequest screenshotRequest = new ScreenshotRequest(source, screenshotBundle,
+                boundsInScreen, insets, taskId, userId, topComponent);
+        takeScreenshot(WindowManager.TAKE_SCREENSHOT_PROVIDED_IMAGE, handler, screenshotRequest,
+                SCREENSHOT_TIMEOUT_MS,
+                completionConsumer);
     }
 
-    private void takeScreenshot(final int screenshotType, long timeoutMs, @NonNull Handler handler,
-            ScreenshotRequest screenshotRequest, @Nullable Consumer<Uri> completionConsumer) {
+    private void takeScreenshot(@ScreenshotType int screenshotType, @NonNull Handler handler,
+            ScreenshotRequest screenshotRequest, long timeoutMs,
+            @Nullable Consumer<Uri> completionConsumer) {
         synchronized (mScreenshotLock) {
 
             final Runnable mScreenshotTimeout = () -> {
diff --git a/core/java/com/android/internal/widget/LockPatternUtils.java b/core/java/com/android/internal/widget/LockPatternUtils.java
index cc076ab..3e0a6cb 100644
--- a/core/java/com/android/internal/widget/LockPatternUtils.java
+++ b/core/java/com/android/internal/widget/LockPatternUtils.java
@@ -166,10 +166,6 @@
     private static final String ENABLED_TRUST_AGENTS = "lockscreen.enabledtrustagents";
     private static final String IS_TRUST_USUALLY_MANAGED = "lockscreen.istrustusuallymanaged";
 
-    public static final String PROFILE_KEY_NAME_ENCRYPT = "profile_key_name_encrypt_";
-    public static final String PROFILE_KEY_NAME_DECRYPT = "profile_key_name_decrypt_";
-    public static final String SYNTHETIC_PASSWORD_KEY_PREFIX = "synthetic_password_";
-
     public static final String CURRENT_LSKF_BASED_PROTECTOR_ID_KEY = "sp-handle";
     public static final String PASSWORD_HISTORY_DELIMITER = ",";
 
diff --git a/core/java/com/android/internal/widget/LockscreenCredential.java b/core/java/com/android/internal/widget/LockscreenCredential.java
index 40164a4..9f3f335 100644
--- a/core/java/com/android/internal/widget/LockscreenCredential.java
+++ b/core/java/com/android/internal/widget/LockscreenCredential.java
@@ -29,6 +29,7 @@
 import android.os.storage.StorageManager;
 import android.text.TextUtils;
 
+import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.Preconditions;
 
 import libcore.util.HexEncoding;
@@ -278,58 +279,36 @@
         try {
             MessageDigest sha256 = MessageDigest.getInstance("SHA-256");
             sha256.update(hashFactor);
-            byte[] saltedPassword = Arrays.copyOf(passwordToHash, passwordToHash.length
-                    + salt.length);
-            System.arraycopy(salt, 0, saltedPassword, passwordToHash.length, salt.length);
-            sha256.update(saltedPassword);
-            Arrays.fill(saltedPassword, (byte) 0);
-            return new String(HexEncoding.encode(sha256.digest()));
+            sha256.update(passwordToHash);
+            sha256.update(salt);
+            return HexEncoding.encodeToString(sha256.digest());
         } catch (NoSuchAlgorithmException e) {
             throw new AssertionError("Missing digest algorithm: ", e);
         }
     }
 
     /**
-     * Generate a hash for the given password. To avoid brute force attacks, we use a salted hash.
-     * Not the most secure, but it is at least a second level of protection. First level is that
-     * the file is in a location only readable by the system process.
+     * Hash the given password for the password history, using the legacy algorithm.
      *
-     * @return the hash of the pattern in a byte array.
+     * @deprecated This algorithm is insecure because the password can be easily bruteforced, given
+     *             the hash and salt.  Use {@link #passwordToHistoryHash(byte[], byte[], byte[])}
+     *             instead, which incorporates an SP-derived secret into the hash.
+     *
+     * @return the legacy password hash
      */
-    public String legacyPasswordToHash(byte[] salt) {
-        return legacyPasswordToHash(mCredential, salt);
-    }
-
-    /**
-     * Generate a hash for the given password. To avoid brute force attacks, we use a salted hash.
-     * Not the most secure, but it is at least a second level of protection. First level is that
-     * the file is in a location only readable by the system process.
-     *
-     * @param password the gesture pattern.
-     *
-     * @return the hash of the pattern in a byte array.
-     */
+    @Deprecated
     public static String legacyPasswordToHash(byte[] password, byte[] salt) {
         if (password == null || password.length == 0 || salt == null) {
             return null;
         }
 
         try {
-            // Previously the password was passed as a String with the following code:
-            // byte[] saltedPassword = (password + salt).getBytes();
-            // The code below creates the identical digest preimage using byte arrays:
-            byte[] saltedPassword = Arrays.copyOf(password, password.length + salt.length);
-            System.arraycopy(salt, 0, saltedPassword, password.length, salt.length);
+            byte[] saltedPassword = ArrayUtils.concat(password, salt);
             byte[] sha1 = MessageDigest.getInstance("SHA-1").digest(saltedPassword);
             byte[] md5 = MessageDigest.getInstance("MD5").digest(saltedPassword);
 
-            byte[] combined = new byte[sha1.length + md5.length];
-            System.arraycopy(sha1, 0, combined, 0, sha1.length);
-            System.arraycopy(md5, 0, combined, sha1.length, md5.length);
-
-            final char[] hexEncoded = HexEncoding.encode(combined);
             Arrays.fill(saltedPassword, (byte) 0);
-            return new String(hexEncoded);
+            return HexEncoding.encodeToString(ArrayUtils.concat(sha1, md5));
         } catch (NoSuchAlgorithmException e) {
             throw new AssertionError("Missing digest algorithm: ", e);
         }
diff --git a/core/jni/com_android_internal_os_Zygote.cpp b/core/jni/com_android_internal_os_Zygote.cpp
index 5cb0de3..cbc3462 100644
--- a/core/jni/com_android_internal_os_Zygote.cpp
+++ b/core/jni/com_android_internal_os_Zygote.cpp
@@ -2127,6 +2127,8 @@
 
     // Reset the fd to the unsolicited zygote socket
     gSystemServerSocketFd = -1;
+  } else if (pid == -1) {
+    ALOGE("Failed to fork child process: %s (%d)", strerror(errno), errno);
   } else {
     ALOGD("Forked child process %d", pid);
   }
diff --git a/core/res/res/drawable/ic_sd_card_48dp.xml b/core/res/res/drawable/ic_sd_card_48dp.xml
index 90bab47..10fd120 100644
--- a/core/res/res/drawable/ic_sd_card_48dp.xml
+++ b/core/res/res/drawable/ic_sd_card_48dp.xml
@@ -19,6 +19,6 @@
         android:viewportWidth="48.0"
         android:viewportHeight="48.0">
     <path
-        android:fillColor="#FF000000"
+        android:fillColor="?android:attr/colorAccent"
         android:pathData="M36 4H20L8.04 16 8 40c0 2.2 1.8 4 4 4h24c2.2 0 4,-1.8 4,-4V8c0,-2.2,-1.8,-4,-4,-4zM24 16h-4V8h4v8zm6 0h-4V8h4v8zm6 0h-4V8h4v8z"/>
 </vector>
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index d3dc301..646dedd 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -3971,7 +3971,7 @@
     <string name="ext_media_new_notification_message" product="automotive">You may need to reformat the device. Tap to eject.</string>
 
     <!-- Notification body when external media is ready for use [CHAR LIMIT=NONE] -->
-    <string name="ext_media_ready_notification_message">For transferring photos and media</string>
+    <string name="ext_media_ready_notification_message">For storing  photos, videos, music and more</string>
     <!-- TV specific notification body when external media is ready for use [CHAR LIMIT=75] -->
     <string name="ext_media_ready_notification_message" product="tv">Browse media files</string>
 
@@ -3987,11 +3987,11 @@
     <string name="ext_media_unmountable_notification_message" product="automotive">You may need to reformat the device. Tap to eject.</string>
 
     <!-- Notification title when external media is unsupported [CHAR LIMIT=30] -->
-    <string name="ext_media_unsupported_notification_title">Unsupported <xliff:g id="name" example="SD card">%s</xliff:g></string>
+    <string name="ext_media_unsupported_notification_title"><xliff:g id="name" example="SD card">%s</xliff:g> detected </string>
     <!-- Automotive specific notification title when external media is unsupported [CHAR LIMIT=30] -->
     <string name="ext_media_unsupported_notification_title" product="automotive"><xliff:g id="name" example="SD card">%s</xliff:g> isn\u2019t working</string>
     <!-- Notification body when external media is unsupported [CHAR LIMIT=NONE] -->
-    <string name="ext_media_unsupported_notification_message">This device doesn\u2019t support this <xliff:g id="name" example="SD card">%s</xliff:g>. Tap to set up in a supported format.</string>
+    <string name="ext_media_unsupported_notification_message">Tap to set up .</string>
     <!-- TV-specific notification body when external media is unsupported [CHAR LIMIT=75] -->
     <string name="ext_media_unsupported_notification_message" product="tv">Select to set up <xliff:g id="name" example="SD card">%s</xliff:g> in a supported format.</string>
     <!-- Automotive specific notification body when external media is unsupported [CHAR LIMIT=NONE] -->
@@ -4022,7 +4022,7 @@
     <!-- Notification action to transfer media [CHAR LIMIT=40] -->
     <string name="ext_media_seamless_action">Switch output</string>
 
-    <!-- Notification title when external media is missing [CHAR LIMIT=30] -->
+    <!-- Notification title when adoptable storage media is ejected [CHAR LIMIT=30] -->
     <string name="ext_media_missing_title"><xliff:g id="name" example="SD card">%s</xliff:g> missing</string>
     <!-- Notification body when external media is missing [CHAR LIMIT=30] -->
     <string name="ext_media_missing_message">Insert device again</string>
diff --git a/core/tests/coretests/src/android/database/SQLiteOpenHelperTest.java b/core/tests/coretests/src/android/database/SQLiteOpenHelperTest.java
index 730a3cb..9bad6d2 100644
--- a/core/tests/coretests/src/android/database/SQLiteOpenHelperTest.java
+++ b/core/tests/coretests/src/android/database/SQLiteOpenHelperTest.java
@@ -161,7 +161,7 @@
         boolean dbStatFound = false;
         SQLiteDebug.PagerStats info = SQLiteDebug.getDatabaseInfo();
         for (SQLiteDebug.DbStats dbStat : info.dbStats) {
-            if (dbStat.dbName.endsWith(dbName)) {
+            if (dbStat.dbName.endsWith(dbName) && !dbStat.arePoolStats) {
                 dbStatFound = true;
                 Log.i(TAG, "Lookaside for " + dbStat.dbName + " " + dbStat.lookaside);
                 if (expectDisabled) {
diff --git a/core/tests/coretests/src/com/android/internal/widget/LockscreenCredentialTest.java b/core/tests/coretests/src/com/android/internal/widget/LockscreenCredentialTest.java
index 9d77d16..a47868d 100644
--- a/core/tests/coretests/src/com/android/internal/widget/LockscreenCredentialTest.java
+++ b/core/tests/coretests/src/com/android/internal/widget/LockscreenCredentialTest.java
@@ -223,15 +223,11 @@
 
     public void testLegacyPasswordToHash() {
         String password = "1234";
-        LockscreenCredential credential = LockscreenCredential.createPassword(password);
         String salt = "6d5331dd120077a0";
         String expectedHash =
                 "2DD04348ADBF8F4CABD7F722DC2E2887FAD4B6020A0C3E02C831E09946F0554FDC13B155";
 
         assertThat(
-                credential.legacyPasswordToHash(salt.getBytes()))
-                .isEqualTo(expectedHash);
-        assertThat(
                 LockscreenCredential.legacyPasswordToHash(
                         password.getBytes(), salt.getBytes()))
                 .isEqualTo(expectedHash);
@@ -239,10 +235,8 @@
 
     public void testLegacyPasswordToHashInvalidInput() {
         String password = "1234";
-        LockscreenCredential credential = LockscreenCredential.createPassword(password);
         String salt = "6d5331dd120077a0";
 
-        assertThat(credential.legacyPasswordToHash(/* salt= */ null)).isNull();
         assertThat(LockscreenCredential.legacyPasswordToHash(
                 password.getBytes(), /* salt= */ null)).isNull();
 
diff --git a/core/tests/screenshothelpertests/OWNERS b/core/tests/screenshothelpertests/OWNERS
new file mode 100644
index 0000000..23dc8fb
--- /dev/null
+++ b/core/tests/screenshothelpertests/OWNERS
@@ -0,0 +1,2 @@
+# Bug component: 801321
+file:/packages/SystemUI/src/com/android/systemui/screenshot/OWNERS
diff --git a/core/tests/screenshothelpertests/src/com/android/internal/util/ScreenshotHelperTest.java b/core/tests/screenshothelpertests/src/com/android/internal/util/ScreenshotHelperTest.java
index 4b81737..fd4fb13 100644
--- a/core/tests/screenshothelpertests/src/com/android/internal/util/ScreenshotHelperTest.java
+++ b/core/tests/screenshothelpertests/src/com/android/internal/util/ScreenshotHelperTest.java
@@ -80,13 +80,14 @@
 
     @Test
     public void testFullscreenScreenshot() {
-        mScreenshotHelper.takeScreenshot(TAKE_SCREENSHOT_FULLSCREEN, false, false, mHandler, null);
+        mScreenshotHelper.takeScreenshot(TAKE_SCREENSHOT_FULLSCREEN,
+                WindowManager.ScreenshotSource.SCREENSHOT_OTHER, mHandler, null);
     }
 
     @Test
     public void testSelectedRegionScreenshot() {
-        mScreenshotHelper.takeScreenshot(TAKE_SCREENSHOT_SELECTED_REGION, false, false, mHandler,
-                null);
+        mScreenshotHelper.takeScreenshot(TAKE_SCREENSHOT_SELECTED_REGION,
+                WindowManager.ScreenshotSource.SCREENSHOT_OTHER, mHandler, null);
     }
 
     @Test
@@ -101,8 +102,10 @@
         long timeoutMs = 10;
 
         CountDownLatch lock = new CountDownLatch(1);
-        mScreenshotHelper.takeScreenshot(TAKE_SCREENSHOT_FULLSCREEN, false, false, timeoutMs,
+        mScreenshotHelper.takeScreenshot(TAKE_SCREENSHOT_FULLSCREEN,
+                WindowManager.ScreenshotSource.SCREENSHOT_OTHER,
                 mHandler,
+                timeoutMs,
                 uri -> {
                     assertNull(uri);
                     lock.countDown();
diff --git a/core/tests/utiltests/src/com/android/internal/util/ArrayUtilsTest.java b/core/tests/utiltests/src/com/android/internal/util/ArrayUtilsTest.java
index cb30b3f..c66a743 100644
--- a/core/tests/utiltests/src/com/android/internal/util/ArrayUtilsTest.java
+++ b/core/tests/utiltests/src/com/android/internal/util/ArrayUtilsTest.java
@@ -16,8 +16,6 @@
 
 package com.android.internal.util;
 
-import static com.android.internal.util.ArrayUtils.concatElements;
-
 import static org.junit.Assert.assertArrayEquals;
 
 import junit.framework.TestCase;
@@ -156,61 +154,107 @@
                 ArrayUtils.removeLong(new long[] { 1, 2, 3, 1 }, 1));
     }
 
-    public void testConcatEmpty() throws Exception {
-        assertArrayEquals(new Long[] {},
-                concatElements(Long.class, null, null));
-        assertArrayEquals(new Long[] {},
-                concatElements(Long.class, new Long[] {}, null));
-        assertArrayEquals(new Long[] {},
-                concatElements(Long.class, null, new Long[] {}));
-        assertArrayEquals(new Long[] {},
-                concatElements(Long.class, new Long[] {}, new Long[] {}));
+    public void testConcat_zeroObjectArrays() {
+        // empty varargs array
+        assertArrayEquals(new String[] {}, ArrayUtils.concat(String.class));
+        // null varargs array
+        assertArrayEquals(new String[] {}, ArrayUtils.concat(String.class, (String[][]) null));
     }
 
-    public void testconcatElements() throws Exception {
+    public void testConcat_oneObjectArray() {
+        assertArrayEquals(new String[] { "1", "2" },
+                ArrayUtils.concat(String.class, new String[] { "1", "2" }));
+    }
+
+    public void testConcat_oneEmptyObjectArray() {
+        assertArrayEquals(new String[] {}, ArrayUtils.concat(String.class, (String[]) null));
+        assertArrayEquals(new String[] {}, ArrayUtils.concat(String.class, new String[] {}));
+    }
+
+    public void testConcat_twoObjectArrays() {
         assertArrayEquals(new Long[] { 1L },
-                concatElements(Long.class, new Long[] { 1L }, new Long[] {}));
+                ArrayUtils.concat(Long.class, new Long[] { 1L }, new Long[] {}));
         assertArrayEquals(new Long[] { 1L },
-                concatElements(Long.class, new Long[] {}, new Long[] { 1L }));
+                ArrayUtils.concat(Long.class, new Long[] {}, new Long[] { 1L }));
         assertArrayEquals(new Long[] { 1L, 2L },
-                concatElements(Long.class, new Long[] { 1L }, new Long[] { 2L }));
+                ArrayUtils.concat(Long.class, new Long[] { 1L }, new Long[] { 2L }));
         assertArrayEquals(new Long[] { 1L, 2L, 3L, 4L },
-                concatElements(Long.class, new Long[] { 1L, 2L }, new Long[] { 3L, 4L }));
+                ArrayUtils.concat(Long.class, new Long[] { 1L, 2L }, new Long[] { 3L, 4L }));
     }
 
-    public void testConcatElements_threeWay() {
+    public void testConcat_twoEmptyObjectArrays() {
+        assertArrayEquals(new Long[] {}, ArrayUtils.concat(Long.class, null, null));
+        assertArrayEquals(new Long[] {}, ArrayUtils.concat(Long.class, new Long[] {}, null));
+        assertArrayEquals(new Long[] {}, ArrayUtils.concat(Long.class, null, new Long[] {}));
+        assertArrayEquals(new Long[] {},
+                ArrayUtils.concat(Long.class, new Long[] {}, new Long[] {}));
+    }
+
+    public void testConcat_threeObjectArrays() {
         String[] array1 = { "1", "2" };
         String[] array2 = { "3", "4" };
         String[] array3 = { "5", "6" };
-        String[] expectation = {"1", "2", "3", "4", "5", "6"};
+        String[] expectation = { "1", "2", "3", "4", "5", "6" };
 
-        String[] concatResult = ArrayUtils.concatElements(String.class, array1, array2, array3);
-        assertArrayEquals(expectation, concatResult);
+        assertArrayEquals(expectation, ArrayUtils.concat(String.class, array1, array2, array3));
     }
 
-
-    public void testConcatElements_threeWayWithNull() {
+    public void testConcat_threeObjectArraysWithNull() {
         String[] array1 = { "1", "2" };
         String[] array2 = null;
         String[] array3 = { "5", "6" };
-        String[] expectation = {"1", "2", "5", "6"};
+        String[] expectation = { "1", "2", "5", "6" };
 
-        String[] concatResult = ArrayUtils.concatElements(String.class, array1, array2, array3);
-        assertArrayEquals(expectation, concatResult);
+        assertArrayEquals(expectation, ArrayUtils.concat(String.class, array1, array2, array3));
     }
 
-    public void testConcatElements_zeroElements() {
-        String[] expectation = new String[0];
-
-        String[] concatResult = ArrayUtils.concatElements(String.class);
-        assertArrayEquals(expectation, concatResult);
+    public void testConcat_zeroByteArrays() {
+        // empty varargs array
+        assertArrayEquals(new byte[] {}, ArrayUtils.concat());
+        // null varargs array
+        assertArrayEquals(new byte[] {}, ArrayUtils.concat((byte[][]) null));
     }
 
-    public void testConcatElements_oneNullElement() {
-        String[] expectation = new String[0];
-
-        String[] concatResult = ArrayUtils.concatElements(String.class, null);
-        assertArrayEquals(expectation, concatResult);
+    public void testConcat_oneByteArray() {
+        assertArrayEquals(new byte[] { 1, 2 }, ArrayUtils.concat(new byte[] { 1, 2 }));
     }
 
+    public void testConcat_oneEmptyByteArray() {
+        assertArrayEquals(new byte[] {}, ArrayUtils.concat((byte[]) null));
+        assertArrayEquals(new byte[] {}, ArrayUtils.concat(new byte[] {}));
+    }
+
+    public void testConcat_twoByteArrays() {
+        assertArrayEquals(new byte[] { 1 }, ArrayUtils.concat(new byte[] { 1 }, new byte[] {}));
+        assertArrayEquals(new byte[] { 1 }, ArrayUtils.concat(new byte[] {}, new byte[] { 1 }));
+        assertArrayEquals(new byte[] { 1, 2 },
+                ArrayUtils.concat(new byte[] { 1 }, new byte[] { 2 }));
+        assertArrayEquals(new byte[] { 1, 2, 3, 4 },
+                ArrayUtils.concat(new byte[] { 1, 2 }, new byte[] { 3, 4 }));
+    }
+
+    public void testConcat_twoEmptyByteArrays() {
+        assertArrayEquals(new byte[] {}, ArrayUtils.concat((byte[]) null, null));
+        assertArrayEquals(new byte[] {}, ArrayUtils.concat(new byte[] {}, null));
+        assertArrayEquals(new byte[] {}, ArrayUtils.concat((byte[]) null, new byte[] {}));
+        assertArrayEquals(new byte[] {}, ArrayUtils.concat(new byte[] {}, new byte[] {}));
+    }
+
+    public void testConcat_threeByteArrays() {
+        byte[] array1 = { 1, 2 };
+        byte[] array2 = { 3, 4 };
+        byte[] array3 = { 5, 6 };
+        byte[] expectation = { 1, 2, 3, 4, 5, 6 };
+
+        assertArrayEquals(expectation, ArrayUtils.concat(array1, array2, array3));
+    }
+
+    public void testConcat_threeByteArraysWithNull() {
+        byte[] array1 = { 1, 2 };
+        byte[] array2 = null;
+        byte[] array3 = { 5, 6 };
+        byte[] expectation = { 1, 2, 5, 6 };
+
+        assertArrayEquals(expectation, ArrayUtils.concat(array1, array2, array3));
+    }
 }
diff --git a/data/etc/platform.xml b/data/etc/platform.xml
index 6897c01..9a1b8a9 100644
--- a/data/etc/platform.xml
+++ b/data/etc/platform.xml
@@ -171,10 +171,11 @@
     <assign-permission name="android.permission.UPDATE_DEVICE_STATS" uid="audioserver" />
     <assign-permission name="android.permission.UPDATE_APP_OPS_STATS" uid="audioserver" />
     <assign-permission name="android.permission.PACKAGE_USAGE_STATS" uid="audioserver" />
-    <assign-permission name="android.permission.INTERACT_ACROSS_USERS" uid="audioserver" />
+    <assign-permission name="android.permission.INTERACT_ACROSS_USERS_FULL" uid="audioserver" />
     <assign-permission name="android.permission.OBSERVE_SENSOR_PRIVACY" uid="audioserver" />
 
     <assign-permission name="android.permission.MODIFY_AUDIO_SETTINGS" uid="cameraserver" />
+    <assign-permission name="android.permission.INTERACT_ACROSS_USERS_FULL" uid="cameraserver" />
     <assign-permission name="android.permission.ACCESS_SURFACE_FLINGER" uid="cameraserver" />
     <assign-permission name="android.permission.WAKE_LOCK" uid="cameraserver" />
     <assign-permission name="android.permission.UPDATE_DEVICE_STATS" uid="cameraserver" />
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
index 80cf8c3..d3dc7e8 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
@@ -30,15 +30,20 @@
 import android.graphics.Point;
 import android.graphics.PointF;
 import android.hardware.HardwareBuffer;
+import android.hardware.input.InputManager;
 import android.net.Uri;
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.RemoteException;
+import android.os.SystemClock;
 import android.os.SystemProperties;
 import android.os.UserHandle;
 import android.provider.Settings.Global;
 import android.util.Log;
 import android.view.IWindowFocusObserver;
+import android.view.InputDevice;
+import android.view.KeyCharacterMap;
+import android.view.KeyEvent;
 import android.view.MotionEvent;
 import android.view.RemoteAnimationTarget;
 import android.view.SurfaceControl;
@@ -295,6 +300,9 @@
         } else if (keyAction == MotionEvent.ACTION_UP || keyAction == MotionEvent.ACTION_CANCEL) {
             ProtoLog.d(WM_SHELL_BACK_PREVIEW,
                     "Finishing gesture with event action: %d", keyAction);
+            if (keyAction == MotionEvent.ACTION_CANCEL) {
+                mTriggerBack = false;
+            }
             onGestureFinished(true);
         }
     }
@@ -324,7 +332,6 @@
         ProtoLog.d(WM_SHELL_BACK_PREVIEW, "Received backNavigationInfo:%s", backNavigationInfo);
         if (backNavigationInfo == null) {
             Log.e(TAG, "Received BackNavigationInfo is null.");
-            finishAnimation();
             return;
         }
         int backType = backNavigationInfo.getType();
@@ -400,6 +407,25 @@
         dispatchOnBackProgressed(targetCallback, backEvent);
     }
 
+    private void injectBackKey() {
+        sendBackEvent(KeyEvent.ACTION_DOWN);
+        sendBackEvent(KeyEvent.ACTION_UP);
+    }
+
+    private void sendBackEvent(int action) {
+        final long when = SystemClock.uptimeMillis();
+        final KeyEvent ev = new KeyEvent(when, when, action, KeyEvent.KEYCODE_BACK, 0 /* repeat */,
+                0 /* metaState */, KeyCharacterMap.VIRTUAL_KEYBOARD, 0 /* scancode */,
+                KeyEvent.FLAG_FROM_SYSTEM | KeyEvent.FLAG_VIRTUAL_HARD_KEY,
+                InputDevice.SOURCE_KEYBOARD);
+
+        ev.setDisplayId(mContext.getDisplay().getDisplayId());
+        if (!InputManager.getInstance()
+                .injectInputEvent(ev, InputManager.INJECT_INPUT_EVENT_MODE_ASYNC)) {
+            Log.e(TAG, "Inject input event fail");
+        }
+    }
+
     private void onGestureFinished(boolean fromTouch) {
         ProtoLog.d(WM_SHELL_BACK_PREVIEW, "onGestureFinished() mTriggerBack == %s", mTriggerBack);
         if (fromTouch) {
@@ -408,7 +434,17 @@
             mBackGestureStarted = false;
         }
 
-        if (mTransitionInProgress || mBackNavigationInfo == null) {
+        if (mTransitionInProgress) {
+            return;
+        }
+
+        if (mBackNavigationInfo == null) {
+            // No focus window found or core are running recents animation, inject back key as
+            // legacy behavior.
+            if (mTriggerBack) {
+                injectBackKey();
+            }
+            finishAnimation();
             return;
         }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
index d7f1292..1c2f0d8 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
@@ -70,12 +70,10 @@
 import android.os.UserManager;
 import android.service.notification.NotificationListenerService;
 import android.service.notification.NotificationListenerService.RankingMap;
-import android.util.ArraySet;
 import android.util.Log;
 import android.util.Pair;
 import android.util.Slog;
 import android.util.SparseArray;
-import android.util.SparseSetArray;
 import android.view.View;
 import android.view.ViewGroup;
 import android.view.WindowInsets;
@@ -109,8 +107,10 @@
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
+import java.util.Map;
 import java.util.Objects;
 import java.util.Optional;
+import java.util.Set;
 import java.util.concurrent.Executor;
 import java.util.function.Consumer;
 import java.util.function.IntConsumer;
@@ -177,8 +177,8 @@
     private int mCurrentUserId;
     // Current profiles of the user (e.g. user with a workprofile)
     private SparseArray<UserInfo> mCurrentProfiles;
-    // Saves notification keys of active bubbles when users are switched.
-    private final SparseSetArray<String> mSavedBubbleKeysPerUser;
+    // Saves data about active bubbles when users are switched.
+    private final SparseArray<UserBubbleData> mSavedUserBubbleData;
 
     // Used when ranking updates occur and we check if things should bubble / unbubble
     private NotificationListenerService.Ranking mTmpRanking;
@@ -271,7 +271,7 @@
         mCurrentUserId = ActivityManager.getCurrentUser();
         mBubblePositioner = positioner;
         mBubbleData = data;
-        mSavedBubbleKeysPerUser = new SparseSetArray<>();
+        mSavedUserBubbleData = new SparseArray<>();
         mBubbleIconFactory = new BubbleIconFactory(context);
         mBubbleBadgeIconFactory = new BubbleBadgeIconFactory(context);
         mDisplayController = displayController;
@@ -420,6 +420,13 @@
         List<UserInfo> users = mUserManager.getAliveUsers();
         mDataRepository.sanitizeBubbles(users);
 
+        // Init profiles
+        SparseArray<UserInfo> userProfiles = new SparseArray<>();
+        for (UserInfo user : mUserManager.getProfiles(mCurrentUserId)) {
+            userProfiles.put(user.id, user);
+        }
+        mCurrentProfiles = userProfiles;
+
         mShellController.addConfigurationChangeListener(this);
     }
 
@@ -774,11 +781,13 @@
      */
     private void saveBubbles(@UserIdInt int userId) {
         // First clear any existing keys that might be stored.
-        mSavedBubbleKeysPerUser.remove(userId);
+        mSavedUserBubbleData.remove(userId);
+        UserBubbleData userBubbleData = new UserBubbleData();
         // Add in all active bubbles for the current user.
         for (Bubble bubble : mBubbleData.getBubbles()) {
-            mSavedBubbleKeysPerUser.add(userId, bubble.getKey());
+            userBubbleData.add(bubble.getKey(), bubble.showInShade());
         }
+        mSavedUserBubbleData.put(userId, userBubbleData);
     }
 
     /**
@@ -787,22 +796,23 @@
      * @param userId the id of the user
      */
     private void restoreBubbles(@UserIdInt int userId) {
-        ArraySet<String> savedBubbleKeys = mSavedBubbleKeysPerUser.get(userId);
-        if (savedBubbleKeys == null) {
+        UserBubbleData savedBubbleData = mSavedUserBubbleData.get(userId);
+        if (savedBubbleData == null) {
             // There were no bubbles saved for this used.
             return;
         }
-        mSysuiProxy.getShouldRestoredEntries(savedBubbleKeys, (entries) -> {
+        mSysuiProxy.getShouldRestoredEntries(savedBubbleData.getKeys(), (entries) -> {
             mMainExecutor.execute(() -> {
                 for (BubbleEntry e : entries) {
                     if (canLaunchInTaskView(mContext, e)) {
-                        updateBubble(e, true /* suppressFlyout */, false /* showInShade */);
+                        boolean showInShade = savedBubbleData.isShownInShade(e.getKey());
+                        updateBubble(e, true /* suppressFlyout */, showInShade);
                     }
                 }
             });
         });
         // Finally, remove the entries for this user now that bubbles are restored.
-        mSavedBubbleKeysPerUser.remove(userId);
+        mSavedUserBubbleData.remove(userId);
     }
 
     @Override
@@ -993,7 +1003,19 @@
      */
     @VisibleForTesting
     public void updateBubble(BubbleEntry notif) {
-        updateBubble(notif, false /* suppressFlyout */, true /* showInShade */);
+        int bubbleUserId = notif.getStatusBarNotification().getUserId();
+        if (isCurrentProfile(bubbleUserId)) {
+            updateBubble(notif, false /* suppressFlyout */, true /* showInShade */);
+        } else {
+            // Skip update, but store it in user bubbles so it gets restored after user switch
+            mSavedUserBubbleData.get(bubbleUserId, new UserBubbleData()).add(notif.getKey(),
+                    true /* shownInShade */);
+            if (DEBUG_BUBBLE_CONTROLLER) {
+                Log.d(TAG,
+                        "Ignore update to bubble for not active user. Bubble userId=" + bubbleUserId
+                                + " current userId=" + mCurrentUserId);
+            }
+        }
     }
 
     /**
@@ -1842,4 +1864,33 @@
             }
         }
     }
+
+    /**
+     * Bubble data that is stored per user.
+     * Used to store and restore active bubbles during user switching.
+     */
+    private static class UserBubbleData {
+        private final Map<String, Boolean> mKeyToShownInShadeMap = new HashMap<>();
+
+        /**
+         * Add bubble key and whether it should be shown in notification shade
+         */
+        void add(String key, boolean shownInShade) {
+            mKeyToShownInShadeMap.put(key, shownInShade);
+        }
+
+        /**
+         * Get all bubble keys stored for this user
+         */
+        Set<String> getKeys() {
+            return mKeyToShownInShadeMap.keySet();
+        }
+
+        /**
+         * Check if this bubble with the given key should be shown in the notification shade
+         */
+        boolean isShownInShade(String key) {
+            return mKeyToShownInShadeMap.get(key);
+        }
+    }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java
index f8ccf23..cf792cd 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java
@@ -23,12 +23,10 @@
 
 import android.app.NotificationChannel;
 import android.content.pm.UserInfo;
-import android.content.res.Configuration;
 import android.os.Bundle;
 import android.os.UserHandle;
 import android.service.notification.NotificationListenerService;
 import android.service.notification.NotificationListenerService.RankingMap;
-import android.util.ArraySet;
 import android.util.Pair;
 import android.util.SparseArray;
 
@@ -42,6 +40,7 @@
 import java.lang.annotation.Target;
 import java.util.HashMap;
 import java.util.List;
+import java.util.Set;
 import java.util.concurrent.Executor;
 import java.util.function.Consumer;
 import java.util.function.IntConsumer;
@@ -284,7 +283,7 @@
 
         void getPendingOrActiveEntry(String key, Consumer<BubbleEntry> callback);
 
-        void getShouldRestoredEntries(ArraySet<String> savedBubbleKeys,
+        void getShouldRestoredEntries(Set<String> savedBubbleKeys,
                 Consumer<List<BubbleEntry>> callback);
 
         void setNotificationInterruption(String key);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
index 7fb961f..2008a75 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
@@ -32,6 +32,7 @@
 import static com.android.wm.shell.transition.Transitions.ENABLE_SHELL_TRANSITIONS;
 
 import android.app.ActivityManager;
+import android.app.ActivityOptions;
 import android.app.ActivityTaskManager;
 import android.app.PendingIntent;
 import android.content.ActivityNotFoundException;
@@ -46,6 +47,7 @@
 import android.util.ArrayMap;
 import android.util.Slog;
 import android.view.IRemoteAnimationFinishedCallback;
+import android.view.IRemoteAnimationRunner;
 import android.view.RemoteAnimationAdapter;
 import android.view.RemoteAnimationTarget;
 import android.view.SurfaceControl;
@@ -334,17 +336,37 @@
 
     public void startShortcut(String packageName, String shortcutId, @SplitPosition int position,
             @Nullable Bundle options, UserHandle user) {
+        IRemoteAnimationRunner wrapper = new IRemoteAnimationRunner.Stub() {
+            @Override
+            public void onAnimationStart(@WindowManager.TransitionOldType int transit,
+                    RemoteAnimationTarget[] apps,
+                    RemoteAnimationTarget[] wallpapers,
+                    RemoteAnimationTarget[] nonApps,
+                    final IRemoteAnimationFinishedCallback finishedCallback) {
+                try {
+                    finishedCallback.onAnimationFinished();
+                } catch (RemoteException e) {
+                    Slog.e(TAG, "Failed to invoke onAnimationFinished", e);
+                }
+                final WindowContainerTransaction evictWct = new WindowContainerTransaction();
+                mStageCoordinator.prepareEvictNonOpeningChildTasks(position, apps, evictWct);
+                mSyncQueue.queue(evictWct);
+            }
+            @Override
+            public void onAnimationCancelled(boolean isKeyguardOccluded) {
+            }
+        };
         options = mStageCoordinator.resolveStartStage(STAGE_TYPE_UNDEFINED, position, options,
                 null /* wct */);
-        final WindowContainerTransaction evictWct = new WindowContainerTransaction();
-        mStageCoordinator.prepareEvictChildTasks(position, evictWct);
+        RemoteAnimationAdapter wrappedAdapter = new RemoteAnimationAdapter(wrapper,
+                0 /* duration */, 0 /* statusBarTransitionDelay */);
+        ActivityOptions activityOptions = ActivityOptions.fromBundle(options);
+        activityOptions.update(ActivityOptions.makeRemoteAnimation(wrappedAdapter));
 
         try {
-            LauncherApps launcherApps =
-                    mContext.getSystemService(LauncherApps.class);
+            LauncherApps launcherApps = mContext.getSystemService(LauncherApps.class);
             launcherApps.startShortcut(packageName, shortcutId, null /* sourceBounds */,
-                    options, user);
-            mSyncQueue.queue(evictWct);
+                    activityOptions.toBundle(), user);
         } catch (ActivityNotFoundException e) {
             Slog.e(TAG, "Failed to launch shortcut", e);
         }
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 3c7db33..2b3b61b 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
@@ -68,7 +68,6 @@
 import android.annotation.Nullable;
 import android.app.ActivityManager;
 import android.app.ActivityOptions;
-import android.app.ActivityTaskManager;
 import android.app.PendingIntent;
 import android.app.WindowConfiguration;
 import android.content.Context;
@@ -522,14 +521,8 @@
                                 finishedCallback.onAnimationFinished();
                             }
                         };
+                Transitions.setRunningRemoteTransitionDelegate(adapter.getCallingApplication());
                 try {
-                    try {
-                        ActivityTaskManager.getService().setRunningRemoteTransitionDelegate(
-                                adapter.getCallingApplication());
-                    } catch (SecurityException e) {
-                        Slog.e(TAG, "Unable to boost animation thread. This should only happen"
-                                + " during unit tests");
-                    }
                     adapter.getRunner().onAnimationStart(transit, apps, wallpapers,
                             augmentedNonApps, wrapCallback);
                 } catch (RemoteException e) {
@@ -617,6 +610,15 @@
         }
     }
 
+    void prepareEvictNonOpeningChildTasks(@SplitPosition int position, RemoteAnimationTarget[] apps,
+            WindowContainerTransaction wct) {
+        if (position == mSideStagePosition) {
+            mSideStage.evictNonOpeningChildren(apps, wct);
+        } else {
+            mMainStage.evictNonOpeningChildren(apps, wct);
+        }
+    }
+
     void prepareEvictInvisibleChildTasks(WindowContainerTransaction wct) {
         mMainStage.evictInvisibleChildren(wct);
         mSideStage.evictInvisibleChildren(wct);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java
index f6dc68b..f414d69 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java
@@ -20,6 +20,7 @@
 import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
 import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
 import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
+import static android.view.RemoteAnimationTarget.MODE_OPENING;
 
 import static com.android.wm.shell.common.split.SplitScreenConstants.CONTROLLED_ACTIVITY_TYPES;
 import static com.android.wm.shell.common.split.SplitScreenConstants.CONTROLLED_WINDOWING_MODES_WHEN_ACTIVE;
@@ -33,6 +34,7 @@
 import android.graphics.Rect;
 import android.os.IBinder;
 import android.util.SparseArray;
+import android.view.RemoteAnimationTarget;
 import android.view.SurfaceControl;
 import android.view.SurfaceSession;
 import android.window.WindowContainerToken;
@@ -334,6 +336,19 @@
         }
     }
 
+    void evictNonOpeningChildren(RemoteAnimationTarget[] apps, WindowContainerTransaction wct) {
+        final SparseArray<ActivityManager.RunningTaskInfo> toBeEvict = mChildrenTaskInfo.clone();
+        for (int i = 0; i < apps.length; i++) {
+            if (apps[i].mode == MODE_OPENING) {
+                toBeEvict.remove(apps[i].taskId);
+            }
+        }
+        for (int i = toBeEvict.size() - 1; i >= 0; i--) {
+            final ActivityManager.RunningTaskInfo taskInfo = toBeEvict.valueAt(i);
+            wct.reparent(taskInfo.token, null /* parent */, false /* onTop */);
+        }
+    }
+
     void evictInvisibleChildren(WindowContainerTransaction wct) {
         for (int i = mChildrenTaskInfo.size() - 1; i >= 0; i--) {
             final ActivityManager.RunningTaskInfo taskInfo = mChildrenTaskInfo.valueAt(i);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/OneShotRemoteHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/OneShotRemoteHandler.java
index ebaece2..4e1fa29 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/OneShotRemoteHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/OneShotRemoteHandler.java
@@ -18,11 +18,9 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
-import android.app.ActivityTaskManager;
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.util.Log;
-import android.util.Slog;
 import android.view.SurfaceControl;
 import android.window.IRemoteTransition;
 import android.window.IRemoteTransitionFinishedCallback;
@@ -87,17 +85,11 @@
                 });
             }
         };
+        Transitions.setRunningRemoteTransitionDelegate(mRemote.getAppThread());
         try {
             if (mRemote.asBinder() != null) {
                 mRemote.asBinder().linkToDeath(remoteDied, 0 /* flags */);
             }
-            try {
-                ActivityTaskManager.getService().setRunningRemoteTransitionDelegate(
-                        mRemote.getAppThread());
-            } catch (SecurityException e) {
-                Slog.e(Transitions.TAG, "Unable to boost animation thread. This should only happen"
-                        + " during unit tests");
-            }
             mRemote.getRemoteTransition().startAnimation(transition, info, startTransaction, cb);
             // assume that remote will apply the start transaction.
             startTransaction.clear();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RemoteTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RemoteTransitionHandler.java
index b15c48c..cedb340 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RemoteTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RemoteTransitionHandler.java
@@ -18,7 +18,6 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
-import android.app.ActivityTaskManager;
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.util.ArrayMap;
@@ -129,15 +128,9 @@
                 });
             }
         };
+        Transitions.setRunningRemoteTransitionDelegate(remote.getAppThread());
         try {
             handleDeath(remote.asBinder(), finishCallback);
-            try {
-                ActivityTaskManager.getService().setRunningRemoteTransitionDelegate(
-                        remote.getAppThread());
-            } catch (SecurityException e) {
-                Log.e(Transitions.TAG, "Unable to boost animation thread. This should only happen"
-                        + " during unit tests");
-            }
             remote.getRemoteTransition().startAnimation(transition, info, startTransaction, cb);
             // assume that remote will apply the start transaction.
             startTransaction.clear();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
index 8e36154..881b7a1 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
@@ -31,6 +31,8 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.app.ActivityTaskManager;
+import android.app.IApplicationThread;
 import android.content.ContentResolver;
 import android.content.Context;
 import android.database.ContentObserver;
@@ -234,6 +236,19 @@
         mRemoteTransitionHandler.removeFiltered(remoteTransition);
     }
 
+    /** Boosts the process priority of remote animation player. */
+    public static void setRunningRemoteTransitionDelegate(IApplicationThread appThread) {
+        if (appThread == null) return;
+        try {
+            ActivityTaskManager.getService().setRunningRemoteTransitionDelegate(appThread);
+        } catch (SecurityException e) {
+            Log.e(TAG, "Unable to boost animation process. This should only happen"
+                    + " during unit tests");
+        } catch (RemoteException e) {
+            e.rethrowFromSystemServer();
+        }
+    }
+
     /**
      * Runs the given {@code runnable} when the last active transition has finished, or immediately
      * if there are currently no active transitions.
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonAssertions.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonAssertions.kt
index c1d743b..1e4d23c 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonAssertions.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonAssertions.kt
@@ -47,6 +47,14 @@
     layerBecomesVisible(SPLIT_SCREEN_DIVIDER_COMPONENT)
 }
 
+fun FlickerTestParameter.splitScreenDividerBecomesInvisible() {
+    assertLayers {
+        this.isVisible(SPLIT_SCREEN_DIVIDER_COMPONENT)
+            .then()
+            .isInvisible(SPLIT_SCREEN_DIVIDER_COMPONENT)
+    }
+}
+
 fun FlickerTestParameter.layerBecomesVisible(
     component: IComponentMatcher
 ) {
@@ -57,6 +65,16 @@
     }
 }
 
+fun FlickerTestParameter.layerBecomesInvisible(
+    component: IComponentMatcher
+) {
+    assertLayers {
+        this.isVisible(component)
+            .then()
+            .isInvisible(component)
+    }
+}
+
 fun FlickerTestParameter.layerIsVisibleAtEnd(
     component: IComponentMatcher
 ) {
@@ -66,7 +84,6 @@
 }
 
 fun FlickerTestParameter.splitAppLayerBoundsBecomesVisible(
-    rotation: Int,
     component: IComponentMatcher,
     splitLeftTop: Boolean
 ) {
@@ -75,29 +92,50 @@
         this.isInvisible(component)
             .then()
             .invoke("splitAppLayerBoundsBecomesVisible") {
-                it.visibleRegion(component).overlaps(
+                it.visibleRegion(component).coversAtMost(
                     if (splitLeftTop) {
-                        getSplitLeftTopRegion(dividerRegion, rotation)
+                        getSplitLeftTopRegion(dividerRegion, endRotation)
                     } else {
-                        getSplitRightBottomRegion(dividerRegion, rotation)
+                        getSplitRightBottomRegion(dividerRegion, endRotation)
                     }
                 )
             }
     }
 }
 
+fun FlickerTestParameter.splitAppLayerBoundsBecomesInvisible(
+    component: IComponentMatcher,
+    splitLeftTop: Boolean
+) {
+    assertLayers {
+        val dividerRegion = this.first().layer(SPLIT_SCREEN_DIVIDER_COMPONENT).visibleRegion.region
+        this.invoke("splitAppLayerBoundsBecomesVisible") {
+                it.visibleRegion(component).coversAtMost(
+                    if (splitLeftTop) {
+                        getSplitLeftTopRegion(dividerRegion, endRotation)
+                    } else {
+                        getSplitRightBottomRegion(dividerRegion, endRotation)
+                    }
+                )
+            }
+            .then()
+            .isVisible(component, true)
+            .then()
+            .isInvisible(component)
+    }
+}
+
 fun FlickerTestParameter.splitAppLayerBoundsIsVisibleAtEnd(
-    rotation: Int,
     component: IComponentMatcher,
     splitLeftTop: Boolean
 ) {
     assertLayersEnd {
         val dividerRegion = layer(SPLIT_SCREEN_DIVIDER_COMPONENT).visibleRegion.region
-        visibleRegion(component).overlaps(
+        visibleRegion(component).coversAtMost(
             if (splitLeftTop) {
-                getSplitLeftTopRegion(dividerRegion, rotation)
+                getSplitLeftTopRegion(dividerRegion, endRotation)
             } else {
-                getSplitRightBottomRegion(dividerRegion, rotation)
+                getSplitRightBottomRegion(dividerRegion, endRotation)
             }
         )
     }
@@ -113,6 +151,16 @@
     }
 }
 
+fun FlickerTestParameter.appWindowBecomesInvisible(
+    component: IComponentMatcher
+) {
+    assertWm {
+        this.isAppWindowVisible(component)
+            .then()
+            .isAppWindowInvisible(component)
+    }
+}
+
 fun FlickerTestParameter.appWindowIsVisibleAtEnd(
     component: IComponentMatcher
 ) {
@@ -226,9 +274,17 @@
 fun getSplitLeftTopRegion(dividerRegion: Region, rotation: Int): Region {
     val displayBounds = WindowUtils.getDisplayBounds(rotation)
     return if (displayBounds.width > displayBounds.height) {
-        Region.from(0, 0, dividerRegion.bounds.left, displayBounds.bounds.bottom)
+        Region.from(
+            0,
+            0,
+            (dividerRegion.bounds.left + dividerRegion.bounds.right) / 2,
+            displayBounds.bounds.bottom)
     } else {
-        Region.from(0, 0, displayBounds.bounds.right, dividerRegion.bounds.top)
+        Region.from(
+            0,
+            0,
+            displayBounds.bounds.right,
+            (dividerRegion.bounds.top + dividerRegion.bounds.bottom) / 2)
     }
 }
 
@@ -236,12 +292,16 @@
     val displayBounds = WindowUtils.getDisplayBounds(rotation)
     return if (displayBounds.width > displayBounds.height) {
         Region.from(
-            dividerRegion.bounds.right, 0, displayBounds.bounds.right,
+            (dividerRegion.bounds.left + dividerRegion.bounds.right) / 2,
+            0,
+            displayBounds.bounds.right,
             displayBounds.bounds.bottom
         )
     } else {
         Region.from(
-            0, dividerRegion.bounds.bottom, displayBounds.bounds.right,
+            0,
+            (dividerRegion.bounds.top + dividerRegion.bounds.bottom) / 2,
+            displayBounds.bounds.right,
             displayBounds.bounds.bottom
         )
     }
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/SplitScreenHelper.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/SplitScreenHelper.kt
index ccd5f02..6c9ea7b 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/SplitScreenHelper.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/SplitScreenHelper.kt
@@ -78,6 +78,19 @@
                 Components.SendNotificationActivity.COMPONENT.toFlickerComponent()
             )
 
+        fun waitForSplitComplete(
+                wmHelper: WindowManagerStateHelper,
+                primaryApp: IComponentMatcher,
+                secondaryApp: IComponentMatcher,
+        ) {
+            wmHelper.StateSyncBuilder()
+                    .withAppTransitionIdle()
+                    .withWindowSurfaceAppeared(primaryApp)
+                    .withWindowSurfaceAppeared(secondaryApp)
+                    .withSplitDividerVisible()
+                    .waitForAndVerify()
+        }
+
         fun dragFromNotificationToSplit(
             instrumentation: Instrumentation,
             device: UiDevice,
@@ -190,12 +203,11 @@
         }
 
         fun createShortcutOnHotseatIfNotExist(
-            taplInstrumentation: LauncherInstrumentation,
+            tapl: LauncherInstrumentation,
             appName: String
         ) {
-            taplInstrumentation.workspace
-                .deleteAppIcon(taplInstrumentation.workspace.getHotseatAppIcon(0))
-            val allApps = taplInstrumentation.workspace.switchToAllApps()
+            tapl.workspace.deleteAppIcon(tapl.workspace.getHotseatAppIcon(0))
+            val allApps = tapl.workspace.switchToAllApps()
             allApps.freeze()
             try {
                 allApps.getAppIcon(appName).dragToHotseat(0)
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByGoHome.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByGoHome.kt
new file mode 100644
index 0000000..4f48ff0
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByGoHome.kt
@@ -0,0 +1,191 @@
+/*
+ * 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.wm.shell.flicker.splitscreen
+
+import android.platform.test.annotations.Postsubmit
+import android.platform.test.annotations.Presubmit
+import android.view.WindowManagerPolicyConstants
+import androidx.test.filters.RequiresDevice
+import com.android.server.wm.flicker.FlickerParametersRunnerFactory
+import com.android.server.wm.flicker.FlickerTestParameter
+import com.android.server.wm.flicker.FlickerTestParameterFactory
+import com.android.server.wm.flicker.annotation.Group1
+import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.wm.shell.flicker.appWindowBecomesInvisible
+import com.android.wm.shell.flicker.helpers.SplitScreenHelper
+import com.android.wm.shell.flicker.layerBecomesInvisible
+import com.android.wm.shell.flicker.splitAppLayerBoundsBecomesInvisible
+import com.android.wm.shell.flicker.splitScreenDividerBecomesInvisible
+import org.junit.Assume
+import org.junit.Before
+import org.junit.FixMethodOrder
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+/**
+ * Test dismiss split screen by go home.
+ *
+ * To run this test: `atest WMShellFlickerTests:DismissSplitScreenByGoHome`
+ */
+@RequiresDevice
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+@Group1
+class DismissSplitScreenByGoHome(
+    testSpec: FlickerTestParameter
+) : SplitScreenBase(testSpec) {
+
+    // TODO(b/231399940): Remove this once we can use recent shortcut to enter split.
+    @Before
+    open fun before() {
+        Assume.assumeTrue(tapl.isTablet)
+    }
+
+    override val transition: FlickerBuilder.() -> Unit
+        get() = {
+            super.transition(this)
+            setup {
+                eachRun {
+                    primaryApp.launchViaIntent(wmHelper)
+                    // TODO(b/231399940): Use recent shortcut to enter split.
+                    tapl.launchedAppState.taskbar
+                            .openAllApps()
+                            .getAppIcon(secondaryApp.appName)
+                            .dragToSplitscreen(secondaryApp.`package`, primaryApp.`package`)
+                    SplitScreenHelper.waitForSplitComplete(wmHelper, primaryApp, secondaryApp)
+                }
+            }
+            transitions {
+                tapl.goHome()
+                wmHelper.StateSyncBuilder()
+                        .withAppTransitionIdle()
+                        .withHomeActivityVisible()
+                        .waitForAndVerify()
+            }
+        }
+
+    @Presubmit
+    @Test
+    fun splitScreenDividerBecomesVisible() = testSpec.splitScreenDividerBecomesInvisible()
+
+    @Presubmit
+    @Test
+    fun primaryAppLayerBecomesInvisible() = testSpec.layerBecomesInvisible(primaryApp)
+
+    @Presubmit
+    @Test
+    fun secondaryAppLayerBecomesInvisible() = testSpec.layerBecomesInvisible(primaryApp)
+
+    @Presubmit
+    @Test
+    fun primaryAppBoundsBecomesInvisible() = testSpec.splitAppLayerBoundsBecomesInvisible(
+        primaryApp, splitLeftTop = false)
+
+    @Presubmit
+    @Test
+    fun secondaryAppBoundsBecomesInvisible() = testSpec.splitAppLayerBoundsBecomesInvisible(
+        secondaryApp, splitLeftTop = true)
+
+    @Presubmit
+    @Test
+    fun primaryAppWindowBecomesInvisible() = testSpec.appWindowBecomesInvisible(primaryApp)
+
+    @Presubmit
+    @Test
+    fun secondaryAppWindowBecomesInvisible() = testSpec.appWindowBecomesInvisible(secondaryApp)
+
+    /** {@inheritDoc} */
+    @Postsubmit
+    @Test
+    override fun entireScreenCovered() =
+            super.entireScreenCovered()
+
+    /** {@inheritDoc} */
+    @Postsubmit
+    @Test
+    override fun navBarLayerIsVisibleAtStartAndEnd() =
+            super.navBarLayerIsVisibleAtStartAndEnd()
+
+    /** {@inheritDoc} */
+    @Postsubmit
+    @Test
+    override fun navBarLayerPositionAtStartAndEnd() =
+            super.navBarLayerPositionAtStartAndEnd()
+
+    /** {@inheritDoc} */
+    @Postsubmit
+    @Test
+    override fun navBarWindowIsAlwaysVisible() =
+            super.navBarWindowIsAlwaysVisible()
+
+    /** {@inheritDoc} */
+    @Postsubmit
+    @Test
+    override fun statusBarLayerIsVisibleAtStartAndEnd() =
+            super.statusBarLayerIsVisibleAtStartAndEnd()
+
+    /** {@inheritDoc} */
+    @Postsubmit
+    @Test
+    override fun statusBarLayerPositionAtStartAndEnd() =
+            super.statusBarLayerPositionAtStartAndEnd()
+
+    /** {@inheritDoc} */
+    @Postsubmit
+    @Test
+    override fun statusBarWindowIsAlwaysVisible() =
+            super.statusBarWindowIsAlwaysVisible()
+
+    /** {@inheritDoc} */
+    @Postsubmit
+    @Test
+    override fun taskBarLayerIsVisibleAtStartAndEnd() =
+            super.taskBarLayerIsVisibleAtStartAndEnd()
+
+    /** {@inheritDoc} */
+    @Postsubmit
+    @Test
+    override fun taskBarWindowIsAlwaysVisible() =
+            super.taskBarWindowIsAlwaysVisible()
+
+    /** {@inheritDoc} */
+    @Postsubmit
+    @Test
+    override fun visibleLayersShownMoreThanOneConsecutiveEntry() =
+            super.visibleLayersShownMoreThanOneConsecutiveEntry()
+
+    /** {@inheritDoc} */
+    @Postsubmit
+    @Test
+    override fun visibleWindowsShownMoreThanOneConsecutiveEntry() =
+            super.visibleWindowsShownMoreThanOneConsecutiveEntry()
+
+    companion object {
+        @Parameterized.Parameters(name = "{0}")
+        @JvmStatic
+        fun getParams(): List<FlickerTestParameter> {
+            return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests(
+                repetitions = SplitScreenHelper.TEST_REPETITIONS,
+                // TODO(b/176061063):The 3 buttons of nav bar do not exist in the hierarchy.
+                supportedNavigationModes =
+                    listOf(WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY))
+        }
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromAllApps.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromAllApps.kt
index 779be0a..9564d97 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromAllApps.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromAllApps.kt
@@ -72,15 +72,16 @@
             }
             transitions {
                 tapl.launchedAppState.taskbar
-                    .openAllApps()
-                    .getAppIcon(secondaryApp.appName)
-                    .dragToSplitscreen(secondaryApp.`package`, primaryApp.`package`)
+                        .openAllApps()
+                        .getAppIcon(secondaryApp.appName)
+                        .dragToSplitscreen(secondaryApp.`package`, primaryApp.`package`)
+                SplitScreenHelper.waitForSplitComplete(wmHelper, primaryApp, secondaryApp)
             }
         }
 
     @Presubmit
     @Test
-    fun dividerBecomesVisible() = testSpec.splitScreenDividerBecomesVisible()
+    fun splitScreenDividerBecomesVisible() = testSpec.splitScreenDividerBecomesVisible()
 
     @Presubmit
     @Test
@@ -93,12 +94,12 @@
     @Presubmit
     @Test
     fun primaryAppBoundsIsVisibleAtEnd() = testSpec.splitAppLayerBoundsIsVisibleAtEnd(
-        testSpec.endRotation, primaryApp, false /* splitLeftTop */)
+        primaryApp, splitLeftTop = false)
 
     @Presubmit
     @Test
     fun secondaryAppBoundsBecomesVisible() = testSpec.splitAppLayerBoundsBecomesVisible(
-        testSpec.endRotation, secondaryApp, true /* splitLeftTop */)
+        secondaryApp, splitLeftTop = true)
 
     @Presubmit
     @Test
@@ -106,8 +107,7 @@
 
     @Presubmit
     @Test
-    fun secondaryAppWindowBecomesVisible() =
-        testSpec.appWindowBecomesVisible(secondaryApp)
+    fun secondaryAppWindowBecomesVisible() = testSpec.appWindowBecomesVisible(secondaryApp)
 
     /** {@inheritDoc} */
     @Postsubmit
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromNotification.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromNotification.kt
index d47d81b..3b59716 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromNotification.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromNotification.kt
@@ -84,6 +84,7 @@
             }
             transitions {
                 SplitScreenHelper.dragFromNotificationToSplit(instrumentation, device, wmHelper)
+                SplitScreenHelper.waitForSplitComplete(wmHelper, primaryApp, sendNotificationApp)
             }
             teardown {
                 eachRun {
@@ -94,7 +95,7 @@
 
     @Presubmit
     @Test
-    fun dividerBecomesVisible() = testSpec.splitScreenDividerBecomesVisible()
+    fun splitScreenDividerBecomesVisible() = testSpec.splitScreenDividerBecomesVisible()
 
     @Presubmit
     @Test
@@ -108,14 +109,12 @@
     @Presubmit
     @Test
     fun primaryAppBoundsIsVisibleAtEnd() = testSpec.splitAppLayerBoundsIsVisibleAtEnd(
-        testSpec.endRotation, primaryApp, false /* splitLeftTop */
-    )
+        primaryApp, splitLeftTop = false)
 
     @Presubmit
     @Test
     fun secondaryAppBoundsBecomesVisible() = testSpec.splitAppLayerBoundsBecomesVisible(
-        testSpec.endRotation, sendNotificationApp, true /* splitLeftTop */
-    )
+        sendNotificationApp, splitLeftTop = true)
 
     @Presubmit
     @Test
@@ -123,8 +122,7 @@
 
     @Presubmit
     @Test
-    fun secondaryAppWindowIsVisibleAtEnd() =
-        testSpec.appWindowIsVisibleAtEnd(sendNotificationApp)
+    fun secondaryAppWindowIsVisibleAtEnd() = testSpec.appWindowIsVisibleAtEnd(sendNotificationApp)
 
     /** {@inheritDoc} */
     @Postsubmit
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromTaskbar.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromTaskbar.kt
index 1493d1f..3de9872 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromTaskbar.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromTaskbar.kt
@@ -77,16 +77,14 @@
             transitions {
                 tapl.launchedAppState.taskbar
                     .getAppIcon(secondaryApp.appName)
-                    .dragToSplitscreen(
-                        secondaryApp.`package`,
-                        primaryApp.`package`
-                    )
+                    .dragToSplitscreen(secondaryApp.`package`, primaryApp.`package`)
+                SplitScreenHelper.waitForSplitComplete(wmHelper, primaryApp, secondaryApp)
             }
         }
 
     @Presubmit
     @Test
-    fun dividerBecomesVisible() = testSpec.splitScreenDividerBecomesVisible()
+    fun splitScreenDividerBecomesVisible() = testSpec.splitScreenDividerBecomesVisible()
 
     @Presubmit
     @Test
@@ -99,14 +97,12 @@
     @Presubmit
     @Test
     fun primaryAppBoundsIsVisibleAtEnd() = testSpec.splitAppLayerBoundsIsVisibleAtEnd(
-        testSpec.endRotation, primaryApp, splitLeftTop = false
-    )
+        primaryApp, splitLeftTop = false)
 
     @Presubmit
     @Test
     fun secondaryAppBoundsBecomesVisible() = testSpec.splitAppLayerBoundsBecomesVisible(
-        testSpec.endRotation, secondaryApp, splitLeftTop = true
-    )
+        secondaryApp, splitLeftTop = true)
 
     @Presubmit
     @Test
@@ -114,8 +110,7 @@
 
     @Presubmit
     @Test
-    fun secondaryAppWindowBecomesVisible() =
-        testSpec.appWindowBecomesVisible(secondaryApp)
+    fun secondaryAppWindowBecomesVisible() = testSpec.appWindowBecomesVisible(secondaryApp)
 
     /** {@inheritDoc} */
     @Postsubmit
diff --git a/libs/hwui/jni/android_graphics_Canvas.cpp b/libs/hwui/jni/android_graphics_Canvas.cpp
index 61bb665..fb7d5f7 100644
--- a/libs/hwui/jni/android_graphics_Canvas.cpp
+++ b/libs/hwui/jni/android_graphics_Canvas.cpp
@@ -419,14 +419,28 @@
         indices = (const uint16_t*)(indexA.ptr() + indexIndex);
     }
 
-    SkVertices::VertexMode mode = static_cast<SkVertices::VertexMode>(modeHandle);
+    SkVertices::VertexMode vertexMode = static_cast<SkVertices::VertexMode>(modeHandle);
     const Paint* paint = reinterpret_cast<Paint*>(paintHandle);
-    get_canvas(canvasHandle)->drawVertices(SkVertices::MakeCopy(mode, vertexCount,
-                                           reinterpret_cast<const SkPoint*>(verts),
-                                           reinterpret_cast<const SkPoint*>(texs),
-                                           reinterpret_cast<const SkColor*>(colors),
-                                           indexCount, indices).get(),
-                                           SkBlendMode::kModulate, *paint);
+
+    // Preserve legacy Skia behavior: ignore the shader if there are no texs set.
+    Paint noShaderPaint;
+    if (jtexs == NULL) {
+        noShaderPaint = Paint(*paint);
+        noShaderPaint.setShader(nullptr);
+        paint = &noShaderPaint;
+    }
+    // Since https://skia-review.googlesource.com/c/skia/+/473676, Skia will blend paint and vertex
+    // colors when no shader is provided. This ternary uses kDst to mimic the old behavior of
+    // ignoring the paint and using the vertex colors directly when no shader is provided.
+    SkBlendMode blendMode = paint->getShader() ? SkBlendMode::kModulate : SkBlendMode::kDst;
+
+    get_canvas(canvasHandle)
+            ->drawVertices(SkVertices::MakeCopy(
+                                   vertexMode, vertexCount, reinterpret_cast<const SkPoint*>(verts),
+                                   reinterpret_cast<const SkPoint*>(texs),
+                                   reinterpret_cast<const SkColor*>(colors), indexCount, indices)
+                                   .get(),
+                           blendMode, *paint);
 }
 
 static void drawNinePatch(JNIEnv* env, jobject, jlong canvasHandle, jlong bitmapHandle,
diff --git a/media/native/midi/include/amidi/AMidi.h b/media/native/midi/include/amidi/AMidi.h
index fbb7fb3..2b31703 100644
--- a/media/native/midi/include/amidi/AMidi.h
+++ b/media/native/midi/include/amidi/AMidi.h
@@ -66,7 +66,7 @@
  *
  * Introduced in API 33.
  */
-enum AMidiDevice_Protocol : int32_t {
+typedef enum AMidiDevice_Protocol : int32_t {
     /**
      * Constant representing a default protocol with Universal MIDI Packets (UMP).
      * UMP is defined in "Universal MIDI Packet (UMP) Format and MIDI 2.0 Protocol" spec.
@@ -131,7 +131,7 @@
      * MIDI-CI is defined in "MIDI Capability Inquiry (MIDI-CI)" spec.
      */
     AMIDI_DEVICE_PROTOCOL_UNKNOWN = -1
-};
+} AMidiDevice_Protocol;
 
 /*
  * Device API
diff --git a/packages/SettingsLib/ActivityEmbedding/AndroidManifest.xml b/packages/SettingsLib/ActivityEmbedding/AndroidManifest.xml
index 5ce08b7..2742558 100644
--- a/packages/SettingsLib/ActivityEmbedding/AndroidManifest.xml
+++ b/packages/SettingsLib/ActivityEmbedding/AndroidManifest.xml
@@ -16,7 +16,7 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-          package="com.android.settingslib.activityembedding">
+          package="com.android.settingslib.widget">
 
     <uses-sdk android:minSdkVersion="21" />
 
diff --git a/packages/SettingsLib/CollapsingToolbarBaseActivity/AndroidManifest.xml b/packages/SettingsLib/CollapsingToolbarBaseActivity/AndroidManifest.xml
index 1c47f5f..244b367 100644
--- a/packages/SettingsLib/CollapsingToolbarBaseActivity/AndroidManifest.xml
+++ b/packages/SettingsLib/CollapsingToolbarBaseActivity/AndroidManifest.xml
@@ -16,7 +16,7 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-          package="com.android.settingslib.collapsingtoolbar">
+          package="com.android.settingslib.widget">
 
     <uses-sdk android:minSdkVersion="29" />
 
diff --git a/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/BasePreferencesFragment.java b/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/BasePreferencesFragment.java
index 8ebbac3..3582897 100644
--- a/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/BasePreferencesFragment.java
+++ b/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/BasePreferencesFragment.java
@@ -20,6 +20,7 @@
 import androidx.preference.PreferenceFragmentCompat;
 
 import com.android.settingslib.utils.BuildCompatUtils;
+import com.android.settingslib.widget.R;
 
 import com.google.android.material.appbar.AppBarLayout;
 
diff --git a/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/CollapsingToolbarBaseActivity.java b/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/CollapsingToolbarBaseActivity.java
index 77d6583..8c8b478 100644
--- a/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/CollapsingToolbarBaseActivity.java
+++ b/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/CollapsingToolbarBaseActivity.java
@@ -27,6 +27,7 @@
 import androidx.fragment.app.FragmentActivity;
 
 import com.android.settingslib.utils.BuildCompatUtils;
+import com.android.settingslib.widget.R;
 
 import com.google.android.material.appbar.AppBarLayout;
 import com.google.android.material.appbar.CollapsingToolbarLayout;
diff --git a/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/CollapsingToolbarDelegate.java b/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/CollapsingToolbarDelegate.java
index 31e8cc7..ec091bf 100644
--- a/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/CollapsingToolbarDelegate.java
+++ b/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/CollapsingToolbarDelegate.java
@@ -30,6 +30,8 @@
 import androidx.annotation.Nullable;
 import androidx.coordinatorlayout.widget.CoordinatorLayout;
 
+import com.android.settingslib.widget.R;
+
 import com.google.android.material.appbar.AppBarLayout;
 import com.google.android.material.appbar.CollapsingToolbarLayout;
 
diff --git a/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/widget/CollapsingCoordinatorLayout.java b/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/widget/CollapsingCoordinatorLayout.java
index 1ead2f3..a8c7a3f 100644
--- a/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/widget/CollapsingCoordinatorLayout.java
+++ b/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/widget/CollapsingCoordinatorLayout.java
@@ -36,7 +36,7 @@
 import androidx.appcompat.app.AppCompatActivity;
 import androidx.coordinatorlayout.widget.CoordinatorLayout;
 
-import com.android.settingslib.collapsingtoolbar.R;
+import com.android.settingslib.widget.R;
 
 import com.google.android.material.appbar.AppBarLayout;
 import com.google.android.material.appbar.CollapsingToolbarLayout;
diff --git a/packages/SettingsLib/SettingsTransition/AndroidManifest.xml b/packages/SettingsLib/SettingsTransition/AndroidManifest.xml
index b6aff53..244b367 100644
--- a/packages/SettingsLib/SettingsTransition/AndroidManifest.xml
+++ b/packages/SettingsLib/SettingsTransition/AndroidManifest.xml
@@ -16,7 +16,7 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-          package="com.android.settingslib.transition">
+          package="com.android.settingslib.widget">
 
     <uses-sdk android:minSdkVersion="29" />
 
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java
index d3afccc..6aa08f2 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java
@@ -875,16 +875,16 @@
         String[] whitelist;
         Map<String, Validator> validators = null;
         if (contentUri.equals(Settings.Secure.CONTENT_URI)) {
-            whitelist = ArrayUtils.concatElements(String.class, SecureSettings.SETTINGS_TO_BACKUP,
+            whitelist = ArrayUtils.concat(String.class, SecureSettings.SETTINGS_TO_BACKUP,
                     Settings.Secure.LEGACY_RESTORE_SETTINGS,
                     DeviceSpecificSettings.DEVICE_SPECIFIC_SETTINGS_TO_BACKUP);
             validators = SecureSettingsValidators.VALIDATORS;
         } else if (contentUri.equals(Settings.System.CONTENT_URI)) {
-            whitelist = ArrayUtils.concatElements(String.class, SystemSettings.SETTINGS_TO_BACKUP,
+            whitelist = ArrayUtils.concat(String.class, SystemSettings.SETTINGS_TO_BACKUP,
                     Settings.System.LEGACY_RESTORE_SETTINGS);
             validators = SystemSettingsValidators.VALIDATORS;
         } else if (contentUri.equals(Settings.Global.CONTENT_URI)) {
-            whitelist = ArrayUtils.concatElements(String.class, GlobalSettings.SETTINGS_TO_BACKUP,
+            whitelist = ArrayUtils.concat(String.class, GlobalSettings.SETTINGS_TO_BACKUP,
                     Settings.Global.LEGACY_RESTORE_SETTINGS);
             validators = GlobalSettingsValidators.VALIDATORS;
         } else {
diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp
index 4aa4006..634df39 100644
--- a/packages/SystemUI/Android.bp
+++ b/packages/SystemUI/Android.bp
@@ -146,25 +146,10 @@
 filegroup {
     name: "SystemUI-tests-utils",
     srcs: [
-        "tests/src/com/android/systemui/SysuiBaseFragmentTest.java",
-        "tests/src/com/android/systemui/SysuiTestCase.java",
-        "tests/src/com/android/systemui/TestableDependency.java",
-        "tests/src/com/android/systemui/classifier/FalsingManagerFake.java",
-        "tests/src/com/android/systemui/statusbar/notification/collection/NotificationEntryBuilder.java",
-        "tests/src/com/android/systemui/statusbar/RankingBuilder.java",
-        "tests/src/com/android/systemui/statusbar/SbnBuilder.java",
-        "tests/src/com/android/systemui/SysuiTestableContext.java",
-        "tests/src/com/android/systemui/util/**/*Fake.java",
-        "tests/src/com/android/systemui/utils/leaks/BaseLeakChecker.java",
-        "tests/src/com/android/systemui/utils/leaks/LeakCheckedTest.java",
-        "tests/src/com/android/systemui/**/Fake*.java",
-        "tests/src/com/android/systemui/**/Fake*.kt",
+        "tests/utils/src/**/*.java",
+        "tests/utils/src/**/*.kt",
     ],
-    exclude_srcs: [
-        "tests/src/com/android/systemui/**/*Test.java",
-        "tests/src/com/android/systemui/**/*Test.kt",
-    ],
-    path: "tests/src",
+    path: "tests/utils/src",
 }
 
 java_library {
@@ -172,8 +157,8 @@
     srcs: [
         "src/com/android/systemui/util/concurrency/DelayableExecutor.java",
         "src/com/android/systemui/util/time/SystemClock.java",
-        "tests/src/com/android/systemui/util/concurrency/FakeExecutor.java",
-        "tests/src/com/android/systemui/util/time/FakeSystemClock.java",
+        "tests/utils/src/com/android/systemui/util/concurrency/FakeExecutor.java",
+        "tests/utils/src/com/android/systemui/util/time/FakeSystemClock.java",
     ],
     jarjar_rules: ":jarjar-rules-shared",
 }
@@ -196,6 +181,7 @@
         "src/**/*.java",
         "src/**/I*.aidl",
         ":ReleaseJavaFiles",
+        ":SystemUI-tests-utils",
     ],
     static_libs: [
         "WifiTrackerLib",
@@ -226,6 +212,7 @@
         "androidx.exifinterface_exifinterface",
         "kotlinx-coroutines-android",
         "kotlinx-coroutines-core",
+        "kotlinx_coroutines_test",
         "iconloader_base",
         "SystemUI-tags",
         "SystemUI-proto",
diff --git a/packages/SystemUI/proguard.flags b/packages/SystemUI/proguard.flags
index 84ebe77..a3b4b38 100644
--- a/packages/SystemUI/proguard.flags
+++ b/packages/SystemUI/proguard.flags
@@ -14,6 +14,11 @@
 -keep class * extends com.android.systemui.CoreStartable
 -keep class * implements com.android.systemui.CoreStartable$Injector
 
+# Needed for builds to properly initialize KeyFrames from xml scene
+-keepclassmembers class * extends androidx.constraintlayout.motion.widget.Key {
+  public <init>();
+}
+
 -keepclasseswithmembers class * {
     public <init>(android.content.Context, android.util.AttributeSet);
 }
diff --git a/packages/SystemUI/res/layout/keyguard_bottom_area.xml b/packages/SystemUI/res/layout/keyguard_bottom_area.xml
index 12dfa10..0ca19d9 100644
--- a/packages/SystemUI/res/layout/keyguard_bottom_area.xml
+++ b/packages/SystemUI/res/layout/keyguard_bottom_area.xml
@@ -60,6 +60,30 @@
     </LinearLayout>
 
     <ImageView
+        android:id="@+id/start_button"
+        android:layout_height="@dimen/keyguard_affordance_fixed_height"
+        android:layout_width="@dimen/keyguard_affordance_fixed_width"
+        android:layout_gravity="bottom|start"
+        android:scaleType="center"
+        android:tint="?android:attr/textColorPrimary"
+        android:background="@drawable/keyguard_bottom_affordance_bg"
+        android:layout_marginStart="@dimen/keyguard_affordance_horizontal_offset"
+        android:layout_marginBottom="@dimen/keyguard_affordance_vertical_offset"
+        android:visibility="gone" />
+
+    <ImageView
+        android:id="@+id/end_button"
+        android:layout_height="@dimen/keyguard_affordance_fixed_height"
+        android:layout_width="@dimen/keyguard_affordance_fixed_width"
+        android:layout_gravity="bottom|end"
+        android:scaleType="center"
+        android:tint="?android:attr/textColorPrimary"
+        android:background="@drawable/keyguard_bottom_affordance_bg"
+        android:layout_marginEnd="@dimen/keyguard_affordance_horizontal_offset"
+        android:layout_marginBottom="@dimen/keyguard_affordance_vertical_offset"
+        android:visibility="gone" />
+
+    <ImageView
         android:id="@+id/wallet_button"
         android:layout_height="@dimen/keyguard_affordance_fixed_height"
         android:layout_width="@dimen/keyguard_affordance_fixed_width"
diff --git a/packages/SystemUI/res/values-sw600dp/config.xml b/packages/SystemUI/res/values-sw600dp/config.xml
index 36cc0ad..c0071cb 100644
--- a/packages/SystemUI/res/values-sw600dp/config.xml
+++ b/packages/SystemUI/res/values-sw600dp/config.xml
@@ -40,4 +40,6 @@
 
     <bool name="config_use_large_screen_shade_header">true</bool>
 
+    <!-- Whether to show the side fps hint while on bouncer -->
+    <bool name="config_show_sidefps_hint_on_bouncer">true</bool>
 </resources>
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index 82a3b58..ec22c60 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -548,6 +548,9 @@
     <!-- Package name of the preferred system app to perform eSOS action -->
     <string name="config_preferredEmergencySosPackage" translatable="false"></string>
 
+    <!-- Whether to show the side fps hint while on bouncer -->
+    <bool name="config_show_sidefps_hint_on_bouncer">false</bool>
+
     <!-- Whether to use the split 2-column notification shade -->
     <bool name="config_use_split_notification_shade">false</bool>
 
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationTargetCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationTargetCompat.java
index afa255a..ef9e095 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationTargetCompat.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationTargetCompat.java
@@ -79,6 +79,8 @@
     // Fields used only to unrap into RemoteAnimationTarget
     private final Rect startBounds;
 
+    public final boolean willShowImeOnTarget;
+
     public RemoteAnimationTargetCompat(RemoteAnimationTarget app) {
         taskId = app.taskId;
         mode = app.mode;
@@ -102,6 +104,7 @@
         windowType = app.windowType;
         windowConfiguration = app.windowConfiguration;
         startBounds = app.startBounds;
+        willShowImeOnTarget = app.willShowImeOnTarget;
     }
 
     private static int newModeToLegacyMode(int newMode) {
@@ -118,14 +121,15 @@
     }
 
     public RemoteAnimationTarget unwrap() {
-        return new RemoteAnimationTarget(
+        final RemoteAnimationTarget target = new RemoteAnimationTarget(
                 taskId, mode, leash, isTranslucent, clipRect, contentInsets,
                 prefixOrderIndex, position, localBounds, screenSpaceBounds, windowConfiguration,
                 isNotInRecents, mStartLeash, startBounds, taskInfo, allowEnterPip, windowType
         );
+        target.setWillShowImeOnTarget(willShowImeOnTarget);
+        return target;
     }
 
-
     /**
      * Almost a copy of Transitions#setupStartState.
      * TODO: remove when there is proper cross-process transaction sync.
@@ -234,6 +238,7 @@
             ? change.getTaskInfo().configuration.windowConfiguration
             : new WindowConfiguration();
         startBounds = change.getStartAbsBounds();
+        willShowImeOnTarget = (change.getFlags() & TransitionInfo.FLAG_WILL_IME_SHOWN) != 0;
     }
 
     public static RemoteAnimationTargetCompat[] wrap(RemoteAnimationTarget[] apps) {
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
index 5ee659b..0b3fe46 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
@@ -343,7 +343,8 @@
         if (!mSidefpsController.isPresent()) {
             return;
         }
-        if (mBouncerVisible && mView.isSidedSecurityMode()
+        if (mBouncerVisible
+                && getResources().getBoolean(R.bool.config_show_sidefps_hint_on_bouncer)
                 && mUpdateMonitor.isFingerprintDetectionRunning()
                 && !mUpdateMonitor.userNeedsStrongAuth()) {
             mSidefpsController.get().show();
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index 758ec54..489b4be 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -65,7 +65,6 @@
 import android.hardware.fingerprint.FingerprintManager.AuthenticationResult;
 import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
 import android.nfc.NfcAdapter;
-import android.os.Build;
 import android.os.CancellationSignal;
 import android.os.Handler;
 import android.os.IRemoteCallback;
@@ -95,6 +94,7 @@
 import com.android.internal.jank.InteractionJankMonitor;
 import com.android.internal.util.LatencyTracker;
 import com.android.internal.widget.LockPatternUtils;
+import com.android.keyguard.logging.KeyguardUpdateMonitorLogger;
 import com.android.settingslib.WirelessUtils;
 import com.android.settingslib.fuelgauge.BatteryStatus;
 import com.android.systemui.Dumpable;
@@ -140,12 +140,6 @@
 public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpable {
 
     private static final String TAG = "KeyguardUpdateMonitor";
-    private static final boolean DEBUG = KeyguardConstants.DEBUG;
-    private static final boolean DEBUG_SIM_STATES = KeyguardConstants.DEBUG_SIM_STATES;
-    private static final boolean DEBUG_FACE = Build.IS_DEBUGGABLE;
-    private static final boolean DEBUG_FINGERPRINT = Build.IS_DEBUGGABLE;
-    private static final boolean DEBUG_ACTIVE_UNLOCK = Build.IS_DEBUGGABLE;
-    private static final boolean DEBUG_SPEW = false;
     private static final int BIOMETRIC_LOCKOUT_RESET_DELAY_MS = 600;
 
     // Callback messages
@@ -225,6 +219,7 @@
             "com.android.settings", "com.android.settings.FallbackHome");
 
     private final Context mContext;
+    private final KeyguardUpdateMonitorLogger mLogger;
     private final boolean mIsPrimaryUser;
     private final AuthController mAuthController;
     private final StatusBarStateController mStatusBarStateController;
@@ -321,17 +316,9 @@
     private static final int HAL_ERROR_RETRY_TIMEOUT = 500; // ms
     private static final int HAL_ERROR_RETRY_MAX = 20;
 
-    private final Runnable mFpCancelNotReceived = () -> {
-        Log.e(TAG, "Fp cancellation not received, transitioning to STOPPED");
-        mFingerprintRunningState = BIOMETRIC_STATE_STOPPED;
-        updateFingerprintListeningState(BIOMETRIC_ACTION_STOP);
-    };
+    private final Runnable mFpCancelNotReceived = this::onFingerprintCancelNotReceived;
 
-    private final Runnable mFaceCancelNotReceived = () -> {
-        Log.e(TAG, "Face cancellation not received, transitioning to STOPPED");
-        mFaceRunningState = BIOMETRIC_STATE_STOPPED;
-        updateFaceListeningState(BIOMETRIC_ACTION_STOP);
-    };
+    private final Runnable mFaceCancelNotReceived = this::onFaceCancelNotReceived;
 
     private final Handler mHandler;
 
@@ -454,17 +441,15 @@
 
     private void handleSimSubscriptionInfoChanged() {
         Assert.isMainThread();
-        if (DEBUG_SIM_STATES) {
-            Log.v(TAG, "onSubscriptionInfoChanged()");
-            List<SubscriptionInfo> sil = mSubscriptionManager
-                    .getCompleteActiveSubscriptionInfoList();
-            if (sil != null) {
-                for (SubscriptionInfo subInfo : sil) {
-                    Log.v(TAG, "SubInfo:" + subInfo);
-                }
-            } else {
-                Log.v(TAG, "onSubscriptionInfoChanged: list is null");
+        mLogger.v("onSubscriptionInfoChanged()");
+        List<SubscriptionInfo> sil = mSubscriptionManager
+                .getCompleteActiveSubscriptionInfoList();
+        if (sil != null) {
+            for (SubscriptionInfo subInfo : sil) {
+                mLogger.logSubInfo(subInfo);
             }
+        } else {
+            mLogger.v("onSubscriptionInfoChanged: list is null");
         }
         List<SubscriptionInfo> subscriptionInfos = getSubscriptionInfo(true /* forceReload */);
 
@@ -488,8 +473,7 @@
         while (iter.hasNext()) {
             Map.Entry<Integer, SimData> simData = iter.next();
             if (!activeSubIds.contains(simData.getKey())) {
-                Log.i(TAG, "Previously active sub id " + simData.getKey() + " is now invalid, "
-                        + "will remove");
+                mLogger.logInvalidSubId(simData.getKey());
                 iter.remove();
 
                 SimData data = simData.getValue();
@@ -674,7 +658,7 @@
             try {
                 mDreamManager.awaken();
             } catch (RemoteException e) {
-                Log.e(TAG, "Unable to awaken from dream");
+                mLogger.logException(e, "Unable to awaken from dream");
             }
         }
     }
@@ -758,15 +742,15 @@
             try {
                 userId = ActivityManager.getService().getCurrentUser().id;
             } catch (RemoteException e) {
-                Log.e(TAG, "Failed to get current user id: ", e);
+                mLogger.logException(e, "Failed to get current user id");
                 return;
             }
             if (userId != authUserId) {
-                Log.d(TAG, "Fingerprint authenticated for wrong user: " + authUserId);
+                mLogger.logFingerprintAuthForWrongUser(authUserId);
                 return;
             }
             if (isFingerprintDisabled(userId)) {
-                Log.d(TAG, "Fingerprint disabled by DPM for userId: " + userId);
+                mLogger.logFingerprintDisabledForUser(userId);
                 return;
             }
             onFingerprintAuthenticated(userId, isStrongBiometric);
@@ -789,8 +773,7 @@
     private Runnable mRetryFingerprintAuthentication = new Runnable() {
         @Override
         public void run() {
-            Log.w(TAG,
-                    "Retrying fingerprint attempt: " + mHardwareFingerprintUnavailableRetryCount);
+            mLogger.logRetryAfterFpHwUnavailable(mHardwareFingerprintUnavailableRetryCount);
             if (mFpm.isHardwareDetected()) {
                 updateFingerprintListeningState(BIOMETRIC_ACTION_UPDATE);
             } else if (mHardwareFingerprintUnavailableRetryCount < HAL_ERROR_RETRY_MAX) {
@@ -800,6 +783,12 @@
         }
     };
 
+    private void onFingerprintCancelNotReceived() {
+        mLogger.e("Fp cancellation not received, transitioning to STOPPED");
+        mFingerprintRunningState = BIOMETRIC_STATE_STOPPED;
+        KeyguardUpdateMonitor.this.updateFingerprintListeningState(BIOMETRIC_ACTION_STOP);
+    }
+
     private void handleFingerprintError(int msgId, String errString) {
         Assert.isMainThread();
         if (mHandler.hasCallbacks(mFpCancelNotReceived)) {
@@ -827,7 +816,7 @@
         if (msgId == FingerprintManager.FINGERPRINT_ERROR_LOCKOUT_PERMANENT) {
             lockedOutStateChanged |= !mFingerprintLockedOutPermanent;
             mFingerprintLockedOutPermanent = true;
-            Log.d(TAG, "Fingerprint locked out - requiring strong auth");
+            mLogger.d("Fingerprint locked out - requiring strong auth");
             mLockPatternUtils.requireStrongAuth(
                     STRONG_AUTH_REQUIRED_AFTER_LOCKOUT, getCurrentUser());
         }
@@ -855,7 +844,7 @@
     }
 
     private void handleFingerprintLockoutReset(@LockoutMode int mode) {
-        Log.d(TAG, "handleFingerprintLockoutReset: " + mode);
+        mLogger.logFingerprintLockoutReset(mode);
         final boolean wasLockout = mFingerprintLockedOut;
         final boolean wasLockoutPermanent = mFingerprintLockedOutPermanent;
         mFingerprintLockedOut = (mode == BIOMETRIC_LOCKOUT_TIMED)
@@ -886,7 +875,7 @@
         boolean wasRunning = mFingerprintRunningState == BIOMETRIC_STATE_RUNNING;
         boolean isRunning = fingerprintRunningState == BIOMETRIC_STATE_RUNNING;
         mFingerprintRunningState = fingerprintRunningState;
-        Log.d(TAG, "fingerprintRunningState: " + mFingerprintRunningState);
+        mLogger.logFingerprintRunningState(mFingerprintRunningState);
         // Clients of KeyguardUpdateMonitor don't care about the internal state about the
         // asynchronousness of the cancel cycle. So only notify them if the actually running state
         // has changed.
@@ -953,7 +942,7 @@
 
     private void handleFaceAcquired(int acquireInfo) {
         Assert.isMainThread();
-        if (DEBUG_FACE) Log.d(TAG, "Face acquired acquireInfo=" + acquireInfo);
+        mLogger.logFaceAcquired(acquireInfo);
         for (int i = 0; i < mCallbacks.size(); i++) {
             KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
             if (cb != null) {
@@ -966,25 +955,25 @@
         Trace.beginSection("KeyGuardUpdateMonitor#handlerFaceAuthenticated");
         try {
             if (mGoingToSleep) {
-                Log.d(TAG, "Aborted successful auth because device is going to sleep.");
+                mLogger.d("Aborted successful auth because device is going to sleep.");
                 return;
             }
             final int userId;
             try {
                 userId = ActivityManager.getService().getCurrentUser().id;
             } catch (RemoteException e) {
-                Log.e(TAG, "Failed to get current user id: ", e);
+                mLogger.logException(e, "Failed to get current user id");
                 return;
             }
             if (userId != authUserId) {
-                Log.d(TAG, "Face authenticated for wrong user: " + authUserId);
+                mLogger.logFaceAuthForWrongUser(authUserId);
                 return;
             }
             if (isFaceDisabled(userId)) {
-                Log.d(TAG, "Face authentication disabled by DPM for userId: " + userId);
+                mLogger.logFaceAuthDisabledForUser(userId);
                 return;
             }
-            if (DEBUG_FACE) Log.d(TAG, "Face auth succeeded for user " + userId);
+            mLogger.logFaceAuthSuccess(userId);
             onFaceAuthenticated(userId, isStrongBiometric);
         } finally {
             setFaceRunningState(BIOMETRIC_STATE_STOPPED);
@@ -994,7 +983,7 @@
 
     private void handleFaceHelp(int msgId, String helpString) {
         Assert.isMainThread();
-        if (DEBUG_FACE) Log.d(TAG, "Face help received: " + helpString);
+        mLogger.logFaceAuthHelpMsg(msgId, helpString);
         for (int i = 0; i < mCallbacks.size(); i++) {
             KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
             if (cb != null) {
@@ -1006,15 +995,21 @@
     private Runnable mRetryFaceAuthentication = new Runnable() {
         @Override
         public void run() {
-            Log.w(TAG, "Retrying face after HW unavailable, attempt " +
-                    mHardwareFaceUnavailableRetryCount);
+            mLogger.logRetryingAfterFaceHwUnavailable(mHardwareFaceUnavailableRetryCount);
             updateFaceListeningState(BIOMETRIC_ACTION_UPDATE);
         }
     };
 
-    private void handleFaceError(int msgId, String errString) {
+    private void onFaceCancelNotReceived() {
+        mLogger.e("Face cancellation not received, transitioning to STOPPED");
+        mFaceRunningState = BIOMETRIC_STATE_STOPPED;
+        KeyguardUpdateMonitor.this.updateFaceListeningState(BIOMETRIC_ACTION_STOP);
+    }
+
+    private void handleFaceError(int msgId, final String originalErrMsg) {
         Assert.isMainThread();
-        if (DEBUG_FACE) Log.d(TAG, "Face error received: " + errString + " msgId=" + msgId);
+        String errString = originalErrMsg;
+        mLogger.logFaceAuthError(msgId, originalErrMsg);
         if (mHandler.hasCallbacks(mFaceCancelNotReceived)) {
             mHandler.removeCallbacks(mFaceCancelNotReceived);
         }
@@ -1071,7 +1066,7 @@
     }
 
     private void handleFaceLockoutReset(@LockoutMode int mode) {
-        Log.d(TAG, "handleFaceLockoutReset: " + mode);
+        mLogger.logFaceLockoutReset(mode);
         final boolean wasLockoutPermanent = mFaceLockedOutPermanent;
         mFaceLockedOutPermanent = (mode == BIOMETRIC_LOCKOUT_PERMANENT);
         final boolean changed = (mFaceLockedOutPermanent != wasLockoutPermanent);
@@ -1089,7 +1084,7 @@
         boolean wasRunning = mFaceRunningState == BIOMETRIC_STATE_RUNNING;
         boolean isRunning = faceRunningState == BIOMETRIC_STATE_RUNNING;
         mFaceRunningState = faceRunningState;
-        Log.d(TAG, "faceRunningState: " + mFaceRunningState);
+        mLogger.logFaceRunningState(mFaceRunningState);
         // Clients of KeyguardUpdateMonitor don't care about the internal state or about the
         // asynchronousness of the cancel cycle. So only notify them if the actually running state
         // has changed.
@@ -1205,8 +1200,7 @@
                     mDevicePolicyManager.getProfileOwnerOrDeviceOwnerSupervisionComponent(
                             UserHandle.of(userId));
             if (supervisorComponent == null) {
-                Log.e(TAG, "No Profile Owner or Device Owner supervision app found for User "
-                        + userId);
+                mLogger.logMissingSupervisorAppError(userId);
             } else {
                 Intent intent =
                         new Intent(DevicePolicyManager.ACTION_BIND_SECONDARY_LOCKSCREEN_SERVICE)
@@ -1338,7 +1332,7 @@
         @Override
         public void onReceive(Context context, Intent intent) {
             final String action = intent.getAction();
-            if (DEBUG) Log.d(TAG, "received broadcast " + action);
+            mLogger.logBroadcastReceived(action);
 
             if (Intent.ACTION_TIME_TICK.equals(action)
                     || Intent.ACTION_TIME_CHANGED.equals(action)) {
@@ -1365,12 +1359,10 @@
                     }
                     return;
                 }
-                if (DEBUG_SIM_STATES) {
-                    Log.v(TAG, "action " + action
-                            + " state: " + intent.getStringExtra(
-                            Intent.EXTRA_SIM_STATE)
-                            + " slotId: " + args.slotId + " subid: " + args.subId);
-                }
+                mLogger.logSimStateFromIntent(action,
+                        intent.getStringExtra(Intent.EXTRA_SIM_STATE),
+                        args.slotId,
+                        args.subId);
                 mHandler.obtainMessage(MSG_SIM_STATE_CHANGE, args.subId, args.slotId, args.simState)
                         .sendToTarget();
             } else if (TelephonyManager.ACTION_PHONE_STATE_CHANGED.equals(action)) {
@@ -1382,10 +1374,7 @@
                 ServiceState serviceState = ServiceState.newFromBundle(intent.getExtras());
                 int subId = intent.getIntExtra(SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX,
                         SubscriptionManager.INVALID_SUBSCRIPTION_ID);
-                if (DEBUG) {
-                    Log.v(TAG, "action " + action + " serviceState=" + serviceState + " subId="
-                            + subId);
-                }
+                mLogger.logServiceStateIntent(action, serviceState, subId);
                 mHandler.sendMessage(
                         mHandler.obtainMessage(MSG_SERVICE_STATE_CHANGE, subId, 0, serviceState));
             } else if (TelephonyManager.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED.equals(action)) {
@@ -1505,7 +1494,7 @@
                  */
                 @Override
                 public void onUdfpsPointerDown(int sensorId) {
-                    Log.d(TAG, "onUdfpsPointerDown, sensorId: " + sensorId);
+                    mLogger.logUdfpsPointerDown(sensorId);
                     requestFaceAuth(true);
                 }
 
@@ -1514,7 +1503,7 @@
                  */
                 @Override
                 public void onUdfpsPointerUp(int sensorId) {
-                    Log.d(TAG, "onUdfpsPointerUp, sensorId: " + sensorId);
+                    mLogger.logUdfpsPointerUp(sensorId);
                 }
             };
 
@@ -1810,7 +1799,9 @@
             TelephonyListenerManager telephonyListenerManager,
             InteractionJankMonitor interactionJankMonitor,
             LatencyTracker latencyTracker,
-            ActiveUnlockConfig activeUnlockConfiguration) {
+            ActiveUnlockConfig activeUnlockConfiguration,
+            KeyguardUpdateMonitorLogger logger) {
+        mLogger = logger;
         mContext = context;
         mSubscriptionManager = SubscriptionManager.from(context);
         mTelephonyListenerManager = telephonyListenerManager;
@@ -2154,13 +2145,13 @@
                 || mFingerprintRunningState == BIOMETRIC_STATE_CANCELLING_RESTARTING;
         if (runningOrRestarting && !shouldListenForFingerprint) {
             if (action == BIOMETRIC_ACTION_START) {
-                Log.v(TAG, "Ignoring stopListeningForFingerprint()");
+                mLogger.v("Ignoring stopListeningForFingerprint()");
                 return;
             }
             stopListeningForFingerprint();
         } else if (!runningOrRestarting && shouldListenForFingerprint) {
             if (action == BIOMETRIC_ACTION_STOP) {
-                Log.v(TAG, "Ignoring startListeningForFingerprint()");
+                mLogger.v("Ignoring startListeningForFingerprint()");
                 return;
             }
             startListeningForFingerprint();
@@ -2186,7 +2177,7 @@
      * @param active If the interrupt started or ended.
      */
     public void onAuthInterruptDetected(boolean active) {
-        if (DEBUG) Log.d(TAG, "onAuthInterruptDetected(" + active + ")");
+        mLogger.logAuthInterruptDetected(active);
         if (mAuthInterruptActive == active) {
             return;
         }
@@ -2201,7 +2192,7 @@
      * @param userInitiatedRequest true if the user explicitly requested face auth
      */
     public void requestFaceAuth(boolean userInitiatedRequest) {
-        if (DEBUG) Log.d(TAG, "requestFaceAuth() userInitiated=" + userInitiatedRequest);
+        mLogger.logFaceAuthRequested(userInitiatedRequest);
         mIsFaceAuthUserRequested |= userInitiatedRequest;
         updateFaceListeningState(BIOMETRIC_ACTION_START);
     }
@@ -2227,14 +2218,14 @@
         boolean shouldListenForFace = shouldListenForFace();
         if (mFaceRunningState == BIOMETRIC_STATE_RUNNING && !shouldListenForFace) {
             if (action == BIOMETRIC_ACTION_START) {
-                Log.v(TAG, "Ignoring stopListeningForFace()");
+                mLogger.v("Ignoring stopListeningForFace()");
                 return;
             }
             mIsFaceAuthUserRequested = false;
             stopListeningForFace();
         } else if (mFaceRunningState != BIOMETRIC_STATE_RUNNING && shouldListenForFace) {
             if (action == BIOMETRIC_ACTION_STOP) {
-                Log.v(TAG, "Ignoring startListeningForFace()");
+                mLogger.v("Ignoring startListeningForFace()");
                 return;
             }
             startListeningForFace();
@@ -2251,9 +2242,7 @@
         }
 
         if (shouldTriggerActiveUnlock()) {
-            if (DEBUG_ACTIVE_UNLOCK) {
-                Log.d("ActiveUnlock", "initiate active unlock triggerReason=" + reason);
-            }
+            mLogger.logActiveUnlockTriggered(reason);
             mTrustManager.reportUserMayRequestUnlock(KeyguardUpdateMonitor.getCurrentUser());
         }
     }
@@ -2281,12 +2270,7 @@
         }
 
         if (allowRequest && shouldTriggerActiveUnlock()) {
-            if (DEBUG_ACTIVE_UNLOCK) {
-                Log.d("ActiveUnlock", "reportUserRequestedUnlock"
-                        + " origin=" + requestOrigin.name()
-                        + " reason=" + reason
-                        + " dismissKeyguard=" + dismissKeyguard);
-            }
+            mLogger.logUserRequestedUnlock(requestOrigin, reason, dismissKeyguard);
             mTrustManager.reportUserRequestedUnlock(KeyguardUpdateMonitor.getCurrentUser(),
                     dismissKeyguard);
         }
@@ -2436,32 +2420,30 @@
         final boolean shouldListen = shouldListenKeyguardState && shouldListenUserState
                 && shouldListenBouncerState && shouldListenUdfpsState && !isFingerprintLockedOut();
 
-        if (DEBUG_FINGERPRINT || DEBUG_SPEW) {
-            maybeLogListenerModelData(
-                    new KeyguardFingerprintListenModel(
-                        System.currentTimeMillis(),
-                        user,
-                        shouldListen,
-                        biometricEnabledForUser,
-                        mBouncerIsOrWillBeShowing,
-                        userCanSkipBouncer,
-                        mCredentialAttempted,
-                        mDeviceInteractive,
-                        mIsDreaming,
-                        isEncryptedOrLockdownForUser,
-                        fingerprintDisabledForUser,
-                        mFingerprintLockedOut,
-                        mGoingToSleep,
-                        mKeyguardGoingAway,
-                        mKeyguardIsVisible,
-                        mKeyguardOccluded,
-                        mOccludingAppRequestingFp,
-                        mIsPrimaryUser,
-                        shouldListenForFingerprintAssistant,
-                        mSwitchingUser,
-                        isUdfps,
-                        userDoesNotHaveTrust));
-        }
+        maybeLogListenerModelData(
+                new KeyguardFingerprintListenModel(
+                    System.currentTimeMillis(),
+                    user,
+                    shouldListen,
+                    biometricEnabledForUser,
+                    mBouncerIsOrWillBeShowing,
+                    userCanSkipBouncer,
+                    mCredentialAttempted,
+                    mDeviceInteractive,
+                    mIsDreaming,
+                    isEncryptedOrLockdownForUser,
+                    fingerprintDisabledForUser,
+                    mFingerprintLockedOut,
+                    mGoingToSleep,
+                    mKeyguardGoingAway,
+                    mKeyguardIsVisible,
+                    mKeyguardOccluded,
+                    mOccludingAppRequestingFp,
+                    mIsPrimaryUser,
+                    shouldListenForFingerprintAssistant,
+                    mSwitchingUser,
+                    isUdfps,
+                    userDoesNotHaveTrust));
 
         return shouldListen;
     }
@@ -2536,59 +2518,49 @@
                 && !fpLockedout;
 
         // Aggregate relevant fields for debug logging.
-        if (DEBUG_FACE || DEBUG_SPEW) {
-            maybeLogListenerModelData(
-                    new KeyguardFaceListenModel(
-                        System.currentTimeMillis(),
-                        user,
-                        shouldListen,
-                        mAuthInterruptActive,
-                        becauseCannotSkipBouncer,
-                        biometricEnabledForUser,
-                        mBouncerFullyShown,
-                        faceAuthenticated,
-                        faceDisabledForUser,
-                        mGoingToSleep,
-                        awakeKeyguard,
-                        mKeyguardGoingAway,
-                        shouldListenForFaceAssistant,
-                        mOccludingAppRequestingFace,
-                        mIsPrimaryUser,
-                        strongAuthAllowsScanning,
-                        mSecureCameraLaunched,
-                        mSwitchingUser,
-                        mUdfpsBouncerShowing));
-        }
+        maybeLogListenerModelData(
+                new KeyguardFaceListenModel(
+                    System.currentTimeMillis(),
+                    user,
+                    shouldListen,
+                    mAuthInterruptActive,
+                    becauseCannotSkipBouncer,
+                    biometricEnabledForUser,
+                    mBouncerFullyShown,
+                    faceAuthenticated,
+                    faceDisabledForUser,
+                    mGoingToSleep,
+                    awakeKeyguard,
+                    mKeyguardGoingAway,
+                    shouldListenForFaceAssistant,
+                    mOccludingAppRequestingFace,
+                    mIsPrimaryUser,
+                    strongAuthAllowsScanning,
+                    mSecureCameraLaunched,
+                    mSwitchingUser,
+                    mUdfpsBouncerShowing));
 
         return shouldListen;
     }
 
     private void maybeLogListenerModelData(KeyguardListenModel model) {
-        // Too chatty, but very useful when debugging issues.
-        if (DEBUG_SPEW) {
-            Log.v(TAG, model.toString());
-        }
+        mLogger.logKeyguardListenerModel(model);
 
-        if (DEBUG_ACTIVE_UNLOCK
-                && model instanceof KeyguardActiveUnlockModel) {
+        if (model instanceof KeyguardActiveUnlockModel) {
             mListenModels.add(model);
             return;
         }
 
         // Add model data to the historical buffer.
         final boolean notYetRunning =
-                (DEBUG_FACE
-                    && model instanceof KeyguardFaceListenModel
-                    && mFaceRunningState != BIOMETRIC_STATE_RUNNING)
-                || (DEBUG_FINGERPRINT
-                    && model instanceof KeyguardFingerprintListenModel
-                    && mFingerprintRunningState != BIOMETRIC_STATE_RUNNING);
+                (model instanceof KeyguardFaceListenModel
+                        && mFaceRunningState != BIOMETRIC_STATE_RUNNING)
+                || (model instanceof KeyguardFingerprintListenModel
+                        && mFingerprintRunningState != BIOMETRIC_STATE_RUNNING);
         final boolean running =
-                (DEBUG_FACE
-                        && model instanceof KeyguardFaceListenModel
+                (model instanceof KeyguardFaceListenModel
                         && mFaceRunningState == BIOMETRIC_STATE_RUNNING)
-                        || (DEBUG_FINGERPRINT
-                        && model instanceof KeyguardFingerprintListenModel
+                        || (model instanceof KeyguardFingerprintListenModel
                         && mFingerprintRunningState == BIOMETRIC_STATE_RUNNING);
         if (notYetRunning && model.getListening()
                 || running && !model.getListening()) {
@@ -2600,9 +2572,9 @@
         final int userId = getCurrentUser();
         final boolean unlockPossible = isUnlockWithFingerprintPossible(userId);
         if (mFingerprintCancelSignal != null) {
-            Log.e(TAG, "Cancellation signal is not null, high chance of bug in fp auth lifecycle"
-                    + " management. FP state: " + mFingerprintRunningState
-                    + ", unlockPossible: " + unlockPossible);
+            mLogger.logUnexpectedFpCancellationSignalState(
+                    mFingerprintRunningState,
+                    unlockPossible);
         }
 
         if (mFingerprintRunningState == BIOMETRIC_STATE_CANCELLING) {
@@ -2613,7 +2585,7 @@
             // Waiting for restart via handleFingerprintError().
             return;
         }
-        if (DEBUG) Log.v(TAG, "startListeningForFingerprint()");
+        mLogger.v("startListeningForFingerprint()");
 
         if (unlockPossible) {
             mFingerprintCancelSignal = new CancellationSignal();
@@ -2634,9 +2606,7 @@
         final int userId = getCurrentUser();
         final boolean unlockPossible = isUnlockWithFacePossible(userId);
         if (mFaceCancelSignal != null) {
-            Log.e(TAG, "Cancellation signal is not null, high chance of bug in face auth lifecycle"
-                    + " management. Face state: " + mFaceRunningState
-                    + ", unlockPossible: " + unlockPossible);
+            mLogger.logUnexpectedFaceCancellationSignalState(mFaceRunningState, unlockPossible);
         }
 
         if (mFaceRunningState == BIOMETRIC_STATE_CANCELLING) {
@@ -2646,7 +2616,7 @@
             // Waiting for ERROR_CANCELED before requesting auth again
             return;
         }
-        if (DEBUG) Log.v(TAG, "startListeningForFace(): " + mFaceRunningState);
+        mLogger.logStartedListeningForFace(mFaceRunningState);
 
         if (unlockPossible) {
             mFaceCancelSignal = new CancellationSignal();
@@ -2712,7 +2682,7 @@
     }
 
     private void stopListeningForFingerprint() {
-        if (DEBUG) Log.v(TAG, "stopListeningForFingerprint()");
+        mLogger.v("stopListeningForFingerprint()");
         if (mFingerprintRunningState == BIOMETRIC_STATE_RUNNING) {
             if (mFingerprintCancelSignal != null) {
                 mFingerprintCancelSignal.cancel();
@@ -2728,7 +2698,7 @@
     }
 
     private void stopListeningForFace() {
-        if (DEBUG) Log.v(TAG, "stopListeningForFace()");
+        mLogger.v("stopListeningForFace()");
         if (mFaceRunningState == BIOMETRIC_STATE_RUNNING) {
             if (mFaceCancelSignal != null) {
                 mFaceCancelSignal.cancel();
@@ -2757,7 +2727,7 @@
                 if (mDeviceProvisioned) {
                     mHandler.sendEmptyMessage(MSG_DEVICE_PROVISIONED);
                 }
-                if (DEBUG) Log.d(TAG, "DEVICE_PROVISIONED state = " + mDeviceProvisioned);
+                mLogger.logDeviceProvisionedState(mDeviceProvisioned);
             }
         };
 
@@ -2862,7 +2832,7 @@
      */
     private void handlePhoneStateChanged(String newState) {
         Assert.isMainThread();
-        if (DEBUG) Log.d(TAG, "handlePhoneStateChanged(" + newState + ")");
+        mLogger.logPhoneStateChanged(newState);
         if (TelephonyManager.EXTRA_STATE_IDLE.equals(newState)) {
             mPhoneState = TelephonyManager.CALL_STATE_IDLE;
         } else if (TelephonyManager.EXTRA_STATE_OFFHOOK.equals(newState)) {
@@ -2883,7 +2853,7 @@
      */
     private void handleTimeUpdate() {
         Assert.isMainThread();
-        if (DEBUG) Log.d(TAG, "handleTimeUpdate");
+        mLogger.d("handleTimeUpdate");
         for (int i = 0; i < mCallbacks.size(); i++) {
             KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
             if (cb != null) {
@@ -2897,7 +2867,7 @@
      */
     private void handleTimeZoneUpdate(String timeZone) {
         Assert.isMainThread();
-        if (DEBUG) Log.d(TAG, "handleTimeZoneUpdate");
+        mLogger.d("handleTimeZoneUpdate");
         for (int i = 0; i < mCallbacks.size(); i++) {
             KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
             if (cb != null) {
@@ -2915,7 +2885,7 @@
      */
     private void handleTimeFormatUpdate(String timeFormat) {
         Assert.isMainThread();
-        if (DEBUG) Log.d(TAG, "handleTimeFormatUpdate timeFormat=" + timeFormat);
+        mLogger.logTimeFormatChanged(timeFormat);
         for (int i = 0; i < mCallbacks.size(); i++) {
             KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
             if (cb != null) {
@@ -2929,7 +2899,7 @@
      */
     private void handleBatteryUpdate(BatteryStatus status) {
         Assert.isMainThread();
-        if (DEBUG) Log.d(TAG, "handleBatteryUpdate");
+        mLogger.d("handleBatteryUpdate");
         final boolean batteryUpdateInteresting = isBatteryUpdateInteresting(mBatteryStatus, status);
         mBatteryStatus = status;
         if (batteryUpdateInteresting) {
@@ -2966,14 +2936,11 @@
     @VisibleForTesting
     void handleSimStateChange(int subId, int slotId, int state) {
         Assert.isMainThread();
-        if (DEBUG_SIM_STATES) {
-            Log.d(TAG, "handleSimStateChange(subId=" + subId + ", slotId="
-                    + slotId + ", state=" + state + ")");
-        }
+        mLogger.logSimState(subId, slotId, state);
 
         boolean becameAbsent = false;
         if (!SubscriptionManager.isValidSubscriptionId(subId)) {
-            Log.w(TAG, "invalid subId in handleSimStateChange()");
+            mLogger.w("invalid subId in handleSimStateChange()");
             /* Only handle No SIM(ABSENT) and Card Error(CARD_IO_ERROR) due to
              * handleServiceStateChange() handle other case */
             if (state == TelephonyManager.SIM_STATE_ABSENT) {
@@ -3022,13 +2989,10 @@
      */
     @VisibleForTesting
     void handleServiceStateChange(int subId, ServiceState serviceState) {
-        if (DEBUG) {
-            Log.d(TAG,
-                    "handleServiceStateChange(subId=" + subId + ", serviceState=" + serviceState);
-        }
+        mLogger.logServiceStateChange(subId, serviceState);
 
         if (!SubscriptionManager.isValidSubscriptionId(subId)) {
-            Log.w(TAG, "invalid subId in handleServiceStateChange()");
+            mLogger.w("invalid subId in handleServiceStateChange()");
             return;
         } else {
             updateTelephonyCapable(true);
@@ -3050,7 +3014,7 @@
      */
     public void onKeyguardVisibilityChanged(boolean showing) {
         Assert.isMainThread();
-        Log.d(TAG, "onKeyguardVisibilityChanged(" + showing + ")");
+        mLogger.logKeyguardVisibilityChanged(showing);
         mKeyguardIsVisible = showing;
 
         if (showing) {
@@ -3070,7 +3034,7 @@
      * Handle {@link #MSG_KEYGUARD_RESET}
      */
     private void handleKeyguardReset() {
-        if (DEBUG) Log.d(TAG, "handleKeyguardReset");
+        mLogger.d("handleKeyguardReset");
         updateBiometricListeningState(BIOMETRIC_ACTION_UPDATE);
         mNeedsSlowUnlockTransition = resolveNeedsSlowUnlockTransition();
     }
@@ -3084,8 +3048,8 @@
                 0 /* flags */, getCurrentUser());
 
         if (resolveInfo == null) {
-            Log.w(TAG, "resolveNeedsSlowUnlockTransition: returning false since activity "
-                    + "could not be resolved.");
+            mLogger.w("resolveNeedsSlowUnlockTransition: returning false since activity could "
+                            + "not be resolved.");
             return false;
         }
 
@@ -3103,11 +3067,7 @@
         final boolean wasBouncerFullyShown = mBouncerFullyShown;
         mBouncerIsOrWillBeShowing = bouncerIsOrWillBeShowing == 1;
         mBouncerFullyShown = bouncerFullyShown == 1;
-        if (DEBUG) {
-            Log.d(TAG, "handleKeyguardBouncerChanged"
-                    + " bouncerIsOrWillBeShowing=" + mBouncerIsOrWillBeShowing
-                    + " bouncerFullyShowing=" + mBouncerFullyShown);
-        }
+        mLogger.logKeyguardBouncerChanged(mBouncerIsOrWillBeShowing, mBouncerFullyShown);
 
         if (mBouncerFullyShown) {
             // If the bouncer is shown, always clear this flag. This can happen in the following
@@ -3227,9 +3187,7 @@
      */
     public void removeCallback(KeyguardUpdateMonitorCallback callback) {
         Assert.isMainThread();
-        if (DEBUG) {
-            Log.v(TAG, "*** unregister callback for " + callback);
-        }
+        mLogger.logUnregisterCallback(callback);
 
         mCallbacks.removeIf(el -> el.get() == callback);
     }
@@ -3242,15 +3200,14 @@
      */
     public void registerCallback(KeyguardUpdateMonitorCallback callback) {
         Assert.isMainThread();
-        if (DEBUG) Log.v(TAG, "*** register callback for " + callback);
+        mLogger.logRegisterCallback(callback);
         // Prevent adding duplicate callbacks
 
         for (int i = 0; i < mCallbacks.size(); i++) {
             if (mCallbacks.get(i).get() == callback) {
-                if (DEBUG) {
-                    Log.e(TAG, "Object tried to add another callback",
-                            new Exception("Called by"));
-                }
+                mLogger.logException(
+                        new Exception("Called by"),
+                        "Object tried to add another callback");
                 return;
             }
         }
@@ -3300,11 +3257,7 @@
      */
     public void sendKeyguardBouncerChanged(boolean bouncerIsOrWillBeShowing,
             boolean bouncerFullyShown) {
-        if (DEBUG) {
-            Log.d(TAG, "sendKeyguardBouncerChanged"
-                    + " bouncerIsOrWillBeShowing=" + bouncerIsOrWillBeShowing
-                    + " bouncerFullyShown=" + bouncerFullyShown);
-        }
+        mLogger.logSendKeyguardBouncerChanged(bouncerIsOrWillBeShowing, bouncerFullyShown);
         Message message = mHandler.obtainMessage(MSG_KEYGUARD_BOUNCER_CHANGED);
         message.arg1 = bouncerIsOrWillBeShowing ? 1 : 0;
         message.arg2 = bouncerFullyShown ? 1 : 0;
@@ -3321,7 +3274,7 @@
      */
     @MainThread
     public void reportSimUnlocked(int subId) {
-        if (DEBUG_SIM_STATES) Log.v(TAG, "reportSimUnlocked(subId=" + subId + ")");
+        mLogger.logSimUnlocked(subId);
         handleSimStateChange(subId, getSlotId(subId), TelephonyManager.SIM_STATE_READY);
     }
 
@@ -3598,7 +3551,9 @@
         try {
             ActivityManager.getService().unregisterUserSwitchObserver(mUserSwitchObserver);
         } catch (RemoteException e) {
-            Log.d(TAG, "RemoteException onDestroy. cannot unregister userSwitchObserver");
+            mLogger.logException(
+                    e,
+                    "RemoteException onDestroy. cannot unregister userSwitchObserver");
         }
 
         TaskStackChangeListeners.getInstance().unregisterTaskStackListener(mTaskStackListener);
diff --git a/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt b/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt
new file mode 100644
index 0000000..035b7f0
--- /dev/null
+++ b/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt
@@ -0,0 +1,326 @@
+/*
+ * 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.keyguard.logging
+
+import android.hardware.biometrics.BiometricConstants.LockoutMode
+import android.telephony.ServiceState
+import android.telephony.SubscriptionInfo
+import com.android.keyguard.ActiveUnlockConfig
+import com.android.keyguard.KeyguardListenModel
+import com.android.keyguard.KeyguardUpdateMonitorCallback
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.LogLevel
+import com.android.systemui.log.LogLevel.DEBUG
+import com.android.systemui.log.LogLevel.ERROR
+import com.android.systemui.log.LogLevel.INFO
+import com.android.systemui.log.LogLevel.VERBOSE
+import com.android.systemui.log.LogLevel.WARNING
+import com.android.systemui.log.dagger.KeyguardUpdateMonitorLog
+import com.google.errorprone.annotations.CompileTimeConstant
+import javax.inject.Inject
+
+private const val TAG = "KeyguardUpdateMonitorLog"
+
+/**
+ * Helper class for logging for [com.android.keyguard.KeyguardUpdateMonitor]
+ */
+class KeyguardUpdateMonitorLogger @Inject constructor(
+        @KeyguardUpdateMonitorLog private val logBuffer: LogBuffer
+) {
+    fun d(@CompileTimeConstant msg: String) = log(msg, DEBUG)
+
+    fun e(@CompileTimeConstant msg: String) = log(msg, ERROR)
+
+    fun v(@CompileTimeConstant msg: String) = log(msg, ERROR)
+
+    fun w(@CompileTimeConstant msg: String) = log(msg, WARNING)
+
+    fun log(@CompileTimeConstant msg: String, level: LogLevel) = logBuffer.log(TAG, level, msg)
+
+    fun logActiveUnlockTriggered(reason: String) {
+        logBuffer.log("ActiveUnlock", DEBUG,
+                { str1 = reason },
+                { "initiate active unlock triggerReason=$str1" })
+    }
+
+    fun logAuthInterruptDetected(active: Boolean) {
+        logBuffer.log(TAG, DEBUG,
+                { bool1 = active },
+                { "onAuthInterruptDetected($bool1)" })
+    }
+
+    fun logBroadcastReceived(action: String?) {
+        logBuffer.log(TAG, DEBUG, { str1 = action }, { "received broadcast $str1" })
+    }
+
+    fun logDeviceProvisionedState(deviceProvisioned: Boolean) {
+        logBuffer.log(TAG, DEBUG,
+                { bool1 = deviceProvisioned },
+                { "DEVICE_PROVISIONED state = $bool1" })
+    }
+
+    fun logException(ex: Exception, @CompileTimeConstant logMsg: String) {
+        logBuffer.log(TAG, ERROR, {}, { logMsg }, exception = ex)
+    }
+
+    fun logFaceAcquired(acquireInfo: Int) {
+        logBuffer.log(TAG, DEBUG,
+                { int1 = acquireInfo },
+                { "Face acquired acquireInfo=$int1" })
+    }
+
+    fun logFaceAuthDisabledForUser(userId: Int) {
+        logBuffer.log(TAG, DEBUG,
+                { int1 = userId },
+                { "Face authentication disabled by DPM for userId: $int1" })
+    }
+    fun logFaceAuthError(msgId: Int, originalErrMsg: String) {
+        logBuffer.log(TAG, DEBUG, {
+                    str1 = originalErrMsg
+                    int1 = msgId
+                }, { "Face error received: $str1 msgId= $int1" })
+    }
+
+    fun logFaceAuthForWrongUser(authUserId: Int) {
+        logBuffer.log(TAG, DEBUG,
+                { int1 = authUserId },
+                { "Face authenticated for wrong user: $int1" })
+    }
+
+    fun logFaceAuthHelpMsg(msgId: Int, helpMsg: String) {
+        logBuffer.log(TAG, DEBUG, {
+                    int1 = msgId
+                    str1 = helpMsg
+                }, { "Face help received, msgId: $int1 msg: $str1" })
+    }
+
+    fun logFaceAuthRequested(userInitiatedRequest: Boolean) {
+        logBuffer.log(TAG, DEBUG,
+                { bool1 = userInitiatedRequest },
+                { "requestFaceAuth() userInitiated=$bool1" })
+    }
+
+    fun logFaceAuthSuccess(userId: Int) {
+        logBuffer.log(TAG, DEBUG,
+                { int1 = userId },
+                { "Face auth succeeded for user $int1" })
+    }
+
+    fun logFaceLockoutReset(@LockoutMode mode: Int) {
+        logBuffer.log(TAG, DEBUG, { int1 = mode }, { "handleFaceLockoutReset: $int1" })
+    }
+
+    fun logFaceRunningState(faceRunningState: Int) {
+        logBuffer.log(TAG, DEBUG, { int1 = faceRunningState }, { "faceRunningState: $int1" })
+    }
+
+    fun logFingerprintAuthForWrongUser(authUserId: Int) {
+        logBuffer.log(TAG, DEBUG,
+                { int1 = authUserId },
+                { "Fingerprint authenticated for wrong user: $int1" })
+    }
+
+    fun logFingerprintDisabledForUser(userId: Int) {
+        logBuffer.log(TAG, DEBUG,
+                { int1 = userId },
+                { "Fingerprint disabled by DPM for userId: $int1" })
+    }
+
+    fun logFingerprintLockoutReset(@LockoutMode mode: Int) {
+        logBuffer.log(TAG, DEBUG, { int1 = mode }, { "handleFingerprintLockoutReset: $int1" })
+    }
+
+    fun logFingerprintRunningState(fingerprintRunningState: Int) {
+        logBuffer.log(TAG, DEBUG,
+                { int1 = fingerprintRunningState },
+                { "fingerprintRunningState: $int1" })
+    }
+
+    fun logInvalidSubId(subId: Int) {
+        logBuffer.log(TAG, INFO,
+                { int1 = subId },
+                { "Previously active sub id $int1 is now invalid, will remove" })
+    }
+
+    fun logKeyguardBouncerChanged(bouncerIsOrWillBeShowing: Boolean, bouncerFullyShown: Boolean) {
+        logBuffer.log(TAG, DEBUG, {
+            bool1 = bouncerIsOrWillBeShowing
+            bool2 = bouncerFullyShown
+        }, {
+            "handleKeyguardBouncerChanged " +
+                    "bouncerIsOrWillBeShowing=$bool1 bouncerFullyShowing=$bool2"
+        })
+    }
+
+    fun logKeyguardListenerModel(model: KeyguardListenModel) {
+        logBuffer.log(TAG, VERBOSE, { str1 = "$model" }, { str1!! })
+    }
+
+    fun logKeyguardVisibilityChanged(showing: Boolean) {
+        logBuffer.log(TAG, DEBUG, { bool1 = showing }, { "onKeyguardVisibilityChanged($bool1)" })
+    }
+
+    fun logMissingSupervisorAppError(userId: Int) {
+        logBuffer.log(TAG, ERROR,
+                { int1 = userId },
+                { "No Profile Owner or Device Owner supervision app found for User $int1" })
+    }
+
+    fun logPhoneStateChanged(newState: String) {
+        logBuffer.log(TAG, DEBUG,
+                { str1 = newState },
+                { "handlePhoneStateChanged($str1)" })
+    }
+
+    fun logRegisterCallback(callback: KeyguardUpdateMonitorCallback?) {
+        logBuffer.log(TAG, VERBOSE,
+                { str1 = "$callback" },
+                { "*** register callback for $str1" })
+    }
+
+    fun logRetryingAfterFaceHwUnavailable(retryCount: Int) {
+        logBuffer.log(TAG, WARNING,
+                { int1 = retryCount },
+                { "Retrying face after HW unavailable, attempt $int1" })
+    }
+
+    fun logRetryAfterFpHwUnavailable(retryCount: Int) {
+        logBuffer.log(TAG, WARNING,
+                { int1 = retryCount },
+                { "Retrying fingerprint attempt: $int1" })
+    }
+
+    fun logSendKeyguardBouncerChanged(
+        bouncerIsOrWillBeShowing: Boolean,
+        bouncerFullyShown: Boolean,
+    ) {
+        logBuffer.log(TAG, DEBUG, {
+            bool1 = bouncerIsOrWillBeShowing
+            bool2 = bouncerFullyShown
+        }, {
+            "sendKeyguardBouncerChanged bouncerIsOrWillBeShowing=$bool1 " +
+                    "bouncerFullyShown=$bool2"
+        })
+    }
+
+    fun logServiceStateChange(subId: Int, serviceState: ServiceState?) {
+        logBuffer.log(TAG, DEBUG, {
+            int1 = subId
+            str1 = "$serviceState"
+        }, { "handleServiceStateChange(subId=$int1, serviceState=$str1)" })
+    }
+
+    fun logServiceStateIntent(action: String, serviceState: ServiceState?, subId: Int) {
+        logBuffer.log(TAG, VERBOSE, {
+            str1 = action
+            str2 = "$serviceState"
+            int1 = subId
+        }, { "action $str1 serviceState=$str2 subId=$int1" })
+    }
+
+    fun logSimState(subId: Int, slotId: Int, state: Int) {
+        logBuffer.log(TAG, DEBUG, {
+            int1 = subId
+            int2 = slotId
+            long1 = state.toLong()
+        }, { "handleSimStateChange(subId=$int1, slotId=$int2, state=$long1)" })
+    }
+
+    fun logSimStateFromIntent(action: String, extraSimState: String, slotId: Int, subId: Int) {
+        logBuffer.log(TAG, VERBOSE, {
+            str1 = action
+            str2 = extraSimState
+            int1 = slotId
+            int2 = subId
+        }, { "action $str1 state: $str2 slotId: $int1 subid: $int2" })
+    }
+
+    fun logSimUnlocked(subId: Int) {
+        logBuffer.log(TAG, VERBOSE, { int1 = subId }, { "reportSimUnlocked(subId=$int1)" })
+    }
+
+    fun logStartedListeningForFace(faceRunningState: Int) {
+        logBuffer.log(TAG, VERBOSE,
+                { int1 = faceRunningState },
+                { "startListeningForFace(): $int1" })
+    }
+
+    fun logSubInfo(subInfo: SubscriptionInfo?) {
+        logBuffer.log(TAG, VERBOSE,
+                { str1 = "$subInfo" },
+                { "SubInfo:$str1" })
+    }
+
+    fun logTimeFormatChanged(newTimeFormat: String) {
+        logBuffer.log(TAG, DEBUG,
+                { str1 = newTimeFormat },
+                { "handleTimeFormatUpdate timeFormat=$str1" })
+    }
+
+    fun logUdfpsPointerDown(sensorId: Int) {
+        logBuffer.log(TAG, DEBUG,
+                { int1 = sensorId },
+                { "onUdfpsPointerDown, sensorId: $int1" })
+    }
+    fun logUdfpsPointerUp(sensorId: Int) {
+        logBuffer.log(TAG, DEBUG,
+                { int1 = sensorId },
+                { "onUdfpsPointerUp, sensorId: $int1" })
+    }
+
+    fun logUnexpectedFaceCancellationSignalState(faceRunningState: Int, unlockPossible: Boolean) {
+        logBuffer.log(TAG, ERROR, {
+                    int1 = faceRunningState
+                    bool1 = unlockPossible
+                }, {
+                    "Cancellation signal is not null, high chance of bug in " +
+                            "face auth lifecycle management. " +
+                            "Face state: $int1, unlockPossible: $bool1"
+                })
+    }
+
+    fun logUnexpectedFpCancellationSignalState(
+        fingerprintRunningState: Int,
+        unlockPossible: Boolean
+    ) {
+        logBuffer.log(TAG, ERROR, {
+                    int1 = fingerprintRunningState
+                    bool1 = unlockPossible
+                }, {
+                    "Cancellation signal is not null, high chance of bug in " +
+                            "fp auth lifecycle management. FP state: $int1, unlockPossible: $bool1"
+                })
+    }
+
+    fun logUnregisterCallback(callback: KeyguardUpdateMonitorCallback?) {
+        logBuffer.log(TAG, VERBOSE,
+                { str1 = "$callback" },
+                { "*** unregister callback for $str1" })
+    }
+
+    fun logUserRequestedUnlock(
+        requestOrigin: ActiveUnlockConfig.ACTIVE_UNLOCK_REQUEST_ORIGIN,
+        reason: String,
+        dismissKeyguard: Boolean
+    ) {
+        logBuffer.log("ActiveUnlock", DEBUG, {
+                    str1 = requestOrigin.name
+                    str2 = reason
+                    bool1 = dismissKeyguard
+                }, { "reportUserRequestedUnlock origin=$str1 reason=$str2 dismissKeyguard=$bool1" })
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/Dependency.java b/packages/SystemUI/src/com/android/systemui/Dependency.java
index 720d708..5c84ff3 100644
--- a/packages/SystemUI/src/com/android/systemui/Dependency.java
+++ b/packages/SystemUI/src/com/android/systemui/Dependency.java
@@ -81,7 +81,6 @@
 import com.android.systemui.statusbar.NotificationMediaManager;
 import com.android.systemui.statusbar.NotificationRemoteInputManager;
 import com.android.systemui.statusbar.NotificationShadeWindowController;
-import com.android.systemui.statusbar.NotificationViewHierarchyManager;
 import com.android.systemui.statusbar.SmartReplyController;
 import com.android.systemui.statusbar.VibratorHelper;
 import com.android.systemui.statusbar.events.PrivacyDotViewController;
@@ -323,7 +322,6 @@
     @Inject Lazy<SmartReplyConstants> mSmartReplyConstants;
     @Inject Lazy<NotificationListener> mNotificationListener;
     @Inject Lazy<NotificationLogger> mNotificationLogger;
-    @Inject Lazy<NotificationViewHierarchyManager> mNotificationViewHierarchyManager;
     @Inject Lazy<NotificationFilter> mNotificationFilter;
     @Inject Lazy<KeyguardDismissUtil> mKeyguardDismissUtil;
     @Inject Lazy<SmartReplyController> mSmartReplyController;
@@ -540,8 +538,6 @@
         mProviders.put(SmartReplyConstants.class, mSmartReplyConstants::get);
         mProviders.put(NotificationListener.class, mNotificationListener::get);
         mProviders.put(NotificationLogger.class, mNotificationLogger::get);
-        mProviders.put(NotificationViewHierarchyManager.class,
-                mNotificationViewHierarchyManager::get);
         mProviders.put(NotificationFilter.class, mNotificationFilter::get);
         mProviders.put(KeyguardDismissUtil.class, mKeyguardDismissUtil::get);
         mProviders.put(SmartReplyController.class, mSmartReplyController::get);
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java b/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java
index 448b99b..a1288b5 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java
@@ -500,7 +500,7 @@
 
     private void handleTakeScreenshot() {
         ScreenshotHelper screenshotHelper = new ScreenshotHelper(mContext);
-        screenshotHelper.takeScreenshot(WindowManager.TAKE_SCREENSHOT_FULLSCREEN, true, true,
+        screenshotHelper.takeScreenshot(WindowManager.TAKE_SCREENSHOT_FULLSCREEN,
                 SCREENSHOT_ACCESSIBILITY_ACTIONS, new Handler(Looper.getMainLooper()), null);
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/assist/AssistLogger.kt b/packages/SystemUI/src/com/android/systemui/assist/AssistLogger.kt
index e30f2e4..7326ab3 100644
--- a/packages/SystemUI/src/com/android/systemui/assist/AssistLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/assist/AssistLogger.kt
@@ -25,10 +25,10 @@
 import com.android.internal.logging.InstanceIdSequence
 import com.android.internal.logging.UiEventLogger
 import com.android.internal.util.FrameworkStatsLog
-import com.android.keyguard.KeyguardUpdateMonitor
 import com.android.systemui.assist.AssistantInvocationEvent.Companion.deviceStateFromLegacyDeviceState
 import com.android.systemui.assist.AssistantInvocationEvent.Companion.eventFromLegacyInvocationType
 import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.settings.UserTracker
 import javax.inject.Inject
 
 /** Class for reporting events related to Assistant sessions. */
@@ -37,7 +37,8 @@
     protected val context: Context,
     protected val uiEventLogger: UiEventLogger,
     private val assistUtils: AssistUtils,
-    private val phoneStateMonitor: PhoneStateMonitor
+    private val phoneStateMonitor: PhoneStateMonitor,
+    private val userTracker: UserTracker,
 ) {
 
     private val instanceIdSequence = InstanceIdSequence(INSTANCE_ID_MAX)
@@ -78,7 +79,7 @@
                 FrameworkStatsLog.ASSISTANT_INVOCATION_REPORTED,
                 invocationEvent.id,
                 assistantUid,
-                assistComponentFinal.flattenToString(),
+                assistComponentFinal?.flattenToString() ?: "",
                 getOrCreateInstanceId().id,
                 deviceStateFinal,
                 false)
@@ -91,7 +92,7 @@
         uiEventLogger.logWithInstanceId(
                 sessionEvent,
                 assistantUid,
-                assistantComponent.flattenToString(),
+                assistantComponent?.flattenToString(),
                 getOrCreateInstanceId())
 
         if (SESSION_END_EVENTS.contains(sessionEvent)) {
@@ -112,11 +113,15 @@
         currentInstanceId = null
     }
 
-    protected fun getAssistantComponentForCurrentUser(): ComponentName {
-        return assistUtils.getAssistComponentForUser(KeyguardUpdateMonitor.getCurrentUser())
+    protected fun getAssistantComponentForCurrentUser(): ComponentName? {
+        return assistUtils.getAssistComponentForUser(userTracker.userId)
     }
 
-    protected fun getAssistantUid(assistantComponent: ComponentName): Int {
+    protected fun getAssistantUid(assistantComponent: ComponentName?): Int {
+        if (assistantComponent == null) {
+            return 0
+        }
+
         var assistantUid = 0
         try {
             assistantUid = context.packageManager.getApplicationInfo(
@@ -138,4 +143,4 @@
                         AssistantSessionEvent.ASSISTANT_SESSION_INVOCATION_CANCELLED,
                         AssistantSessionEvent.ASSISTANT_SESSION_CLOSE)
     }
-}
\ No newline at end of file
+}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/SidefpsController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/SidefpsController.kt
index bbffb73..d03106b 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/SidefpsController.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/SidefpsController.kt
@@ -43,6 +43,8 @@
 import android.view.ViewPropertyAnimator
 import android.view.WindowInsets
 import android.view.WindowManager
+import android.view.WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION
+import android.view.WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY
 import android.view.accessibility.AccessibilityEvent
 import androidx.annotation.RawRes
 import com.airbnb.lottie.LottieAnimationView
@@ -130,7 +132,7 @@
         fitInsetsTypes = 0 // overrides default, avoiding status bars during layout
         gravity = Gravity.TOP or Gravity.LEFT
         layoutInDisplayCutoutMode = WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS
-        privateFlags = WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY
+        privateFlags = PRIVATE_FLAG_TRUSTED_OVERLAY or PRIVATE_FLAG_NO_MOVE_ANIMATION
     }
 
     init {
diff --git a/packages/SystemUI/src/com/android/systemui/common/coroutine/ChannelExt.kt b/packages/SystemUI/src/com/android/systemui/common/coroutine/ChannelExt.kt
new file mode 100644
index 0000000..6f3beac
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/common/coroutine/ChannelExt.kt
@@ -0,0 +1,54 @@
+/*
+ *  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.common.coroutine
+
+import android.util.Log
+import kotlinx.coroutines.channels.SendChannel
+import kotlinx.coroutines.channels.onFailure
+
+object ChannelExt {
+
+    /**
+     * Convenience wrapper around [SendChannel.trySend] that also logs on failure. This is the
+     * equivalent of calling:
+     *
+     * ```
+     * sendChannel.trySend(element).onFailure {
+     *     Log.e(
+     *         loggingTag,
+     *         "Failed to send $elementDescription" +
+     *             " - downstream canceled or failed.",
+     *          it,
+     *    )
+     *}
+     * ```
+     */
+    fun <T> SendChannel<T>.trySendWithFailureLogging(
+        element: T,
+        loggingTag: String,
+        elementDescription: String = "updated state",
+    ) {
+        trySend(element).onFailure {
+            Log.e(
+                loggingTag,
+                "Failed to send $elementDescription - downstream canceled or failed.",
+                it,
+            )
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/common/coroutine/ConflatedCallbackFlow.kt b/packages/SystemUI/src/com/android/systemui/common/coroutine/ConflatedCallbackFlow.kt
new file mode 100644
index 0000000..d4a1f74
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/common/coroutine/ConflatedCallbackFlow.kt
@@ -0,0 +1,40 @@
+/*
+ * 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.common.coroutine
+
+import kotlin.experimental.ExperimentalTypeInference
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.channels.Channel
+import kotlinx.coroutines.channels.ProducerScope
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.buffer
+import kotlinx.coroutines.flow.callbackFlow
+
+object ConflatedCallbackFlow {
+
+    /**
+     * A [callbackFlow] that uses a buffer [Channel] that is "conflated" meaning that, if
+     * backpressure occurs (if the producer that emits new values into the flow is faster than the
+     * consumer(s) of the values in the flow), the values are buffered and, if the buffer fills up,
+     * we drop the oldest values automatically instead of suspending the producer.
+     */
+    @Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
+    @OptIn(ExperimentalTypeInference::class, ExperimentalCoroutinesApi::class)
+    fun <T> conflatedCallbackFlow(
+        @BuilderInference block: suspend ProducerScope<T>.() -> Unit,
+    ): Flow<T> = callbackFlow(block).buffer(capacity = Channel.CONFLATED)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/common/data/model/Position.kt b/packages/SystemUI/src/com/android/systemui/common/data/model/Position.kt
new file mode 100644
index 0000000..7c9df10
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/common/data/model/Position.kt
@@ -0,0 +1,23 @@
+/*
+ * 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.common.data.model
+
+/** Models a two-dimensional position */
+data class Position(
+    val x: Int,
+    val y: Int,
+)
diff --git a/packages/SystemUI/src/com/android/systemui/common/domain/model/Position.kt b/packages/SystemUI/src/com/android/systemui/common/domain/model/Position.kt
new file mode 100644
index 0000000..f697c0a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/common/domain/model/Position.kt
@@ -0,0 +1,34 @@
+/*
+ * 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.common.domain.model
+
+import com.android.systemui.common.data.model.Position as DataLayerPosition
+
+/** Models a two-dimensional position */
+data class Position(
+    val x: Int,
+    val y: Int,
+) {
+    companion object {
+        fun DataLayerPosition.toDomainLayer(): Position {
+            return Position(
+                x = x,
+                y = y,
+            )
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/containeddrawable/ContainedDrawable.kt b/packages/SystemUI/src/com/android/systemui/containeddrawable/ContainedDrawable.kt
new file mode 100644
index 0000000..d6a059d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/containeddrawable/ContainedDrawable.kt
@@ -0,0 +1,27 @@
+/*
+ *  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.containeddrawable
+
+import android.graphics.drawable.Drawable
+import androidx.annotation.DrawableRes
+
+/** Convenience container for [Drawable] or a way to load it later. */
+sealed class ContainedDrawable {
+    data class WithDrawable(val drawable: Drawable) : ContainedDrawable()
+    data class WithResource(@DrawableRes val resourceId: Int) : ContainedDrawable()
+}
diff --git a/packages/SystemUI/src/com/android/systemui/controls/dagger/ControlsComponent.kt b/packages/SystemUI/src/com/android/systemui/controls/dagger/ControlsComponent.kt
index 2fd3731..9e4a364 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/dagger/ControlsComponent.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/dagger/ControlsComponent.kt
@@ -21,20 +21,22 @@
 import android.database.ContentObserver
 import android.os.UserHandle
 import android.provider.Settings
+import com.android.internal.widget.LockPatternUtils
+import com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_BOOT
 import com.android.systemui.controls.controller.ControlsController
+import com.android.systemui.controls.controller.ControlsTileResourceConfiguration
+import com.android.systemui.controls.controller.ControlsTileResourceConfigurationImpl
 import com.android.systemui.controls.management.ControlsListingController
 import com.android.systemui.controls.ui.ControlsUiController
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.settings.UserTracker
 import com.android.systemui.statusbar.policy.KeyguardStateController
 import com.android.systemui.util.settings.SecureSettings
-import com.android.internal.widget.LockPatternUtils
-import com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_BOOT
-import com.android.systemui.controls.controller.ControlsTileResourceConfiguration
-import com.android.systemui.controls.controller.ControlsTileResourceConfigurationImpl
 import dagger.Lazy
 import java.util.Optional
 import javax.inject.Inject
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.asStateFlow
 
 /**
  * Pseudo-component to inject into classes outside `com.android.systemui.controls`.
@@ -59,7 +61,8 @@
     private val contentResolver: ContentResolver
         get() = context.contentResolver
 
-    private var canShowWhileLockedSetting = false
+    private val _canShowWhileLockedSetting = MutableStateFlow(false)
+    val canShowWhileLockedSetting = _canShowWhileLockedSetting.asStateFlow()
 
     private val controlsTileResourceConfiguration: ControlsTileResourceConfiguration =
         optionalControlsTileResourceConfiguration.orElse(
@@ -117,7 +120,7 @@
                 == STRONG_AUTH_REQUIRED_AFTER_BOOT) {
             return Visibility.AVAILABLE_AFTER_UNLOCK
         }
-        if (!canShowWhileLockedSetting && !keyguardStateController.isUnlocked()) {
+        if (!canShowWhileLockedSetting.value && !keyguardStateController.isUnlocked()) {
             return Visibility.AVAILABLE_AFTER_UNLOCK
         }
 
@@ -125,7 +128,7 @@
     }
 
     private fun updateShowWhileLocked() {
-        canShowWhileLockedSetting = secureSettings.getIntForUser(
+        _canShowWhileLockedSetting.value = secureSettings.getIntForUser(
             Settings.Secure.LOCKSCREEN_SHOW_CONTROLS, 0, UserHandle.USER_CURRENT) != 0
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java b/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java
index 718befa..029cabb 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java
@@ -31,7 +31,6 @@
 import com.android.systemui.media.taptotransfer.receiver.MediaTttChipControllerReceiver;
 import com.android.systemui.media.taptotransfer.sender.MediaTttChipControllerSender;
 import com.android.systemui.people.PeopleProvider;
-import com.android.systemui.statusbar.pipeline.ConnectivityInfoProcessor;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.unfold.FoldStateLogger;
 import com.android.systemui.unfold.FoldStateLoggingProvider;
@@ -131,7 +130,6 @@
         getMediaTttCommandLineHelper();
         getMediaMuteAwaitConnectionCli();
         getNearbyMediaDevicesManager();
-        getConnectivityInfoProcessor();
         getUnfoldLatencyTracker().init();
         getFoldStateLoggingProvider().ifPresent(FoldStateLoggingProvider::init);
         getFoldStateLogger().ifPresent(FoldStateLogger::init);
@@ -214,9 +212,6 @@
     /** */
     Optional<NearbyMediaDevicesManager> getNearbyMediaDevicesManager();
 
-    /** */
-    Optional<ConnectivityInfoProcessor> getConnectivityInfoProcessor();
-
     /**
      * Returns {@link CoreStartable}s that should be started with the application.
      */
diff --git a/packages/SystemUI/src/com/android/systemui/doze/util/BurnInHelperWrapper.kt b/packages/SystemUI/src/com/android/systemui/doze/util/BurnInHelperWrapper.kt
new file mode 100644
index 0000000..d853e04
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/doze/util/BurnInHelperWrapper.kt
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2016 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.doze.util
+
+import javax.inject.Inject
+
+/** Injectable wrapper around `BurnInHelper` functions */
+class BurnInHelperWrapper @Inject constructor() {
+
+    fun burnInOffset(amplitude: Int, xAxis: Boolean): Int {
+        return getBurnInOffset(amplitude, xAxis)
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/DreamClockDateComplication.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/DreamClockDateComplication.java
deleted file mode 100644
index 1ca06b2..0000000
--- a/packages/SystemUI/src/com/android/systemui/dreams/complication/DreamClockDateComplication.java
+++ /dev/null
@@ -1,111 +0,0 @@
-/*
- * 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.dreams.complication;
-
-import static com.android.systemui.dreams.complication.dagger.DreamClockDateComplicationModule.DREAM_CLOCK_DATE_COMPLICATION_LAYOUT_PARAMS;
-import static com.android.systemui.dreams.complication.dagger.DreamClockDateComplicationModule.DREAM_CLOCK_DATE_COMPLICATION_VIEW;
-
-import android.content.Context;
-import android.view.View;
-
-import com.android.systemui.CoreStartable;
-import com.android.systemui.dreams.DreamOverlayStateController;
-
-import javax.inject.Inject;
-import javax.inject.Named;
-import javax.inject.Provider;
-
-/**
- * Clock Date Complication that produce Clock Date view holder.
- */
-public class DreamClockDateComplication implements Complication {
-    private final Provider<DreamClockDateViewHolder> mDreamClockDateViewHolderProvider;
-
-    /**
-     * Default constructor for {@link DreamClockDateComplication}.
-     */
-    @Inject
-    public DreamClockDateComplication(
-            Provider<DreamClockDateViewHolder> dreamClockDateViewHolderProvider) {
-        mDreamClockDateViewHolderProvider = dreamClockDateViewHolderProvider;
-    }
-
-    @Override
-    public int getRequiredTypeAvailability() {
-        return COMPLICATION_TYPE_DATE;
-    }
-
-    /**
-     * Create {@link DreamClockDateViewHolder}.
-     */
-    @Override
-    public ViewHolder createView(ComplicationViewModel model) {
-        return mDreamClockDateViewHolderProvider.get();
-    }
-
-    /**
-     * {@link CoreStartable} responsible for registering {@link DreamClockDateComplication} with
-     * SystemUI.
-     */
-    public static class Registrant extends CoreStartable {
-        private final DreamOverlayStateController mDreamOverlayStateController;
-        private final DreamClockDateComplication mComplication;
-
-        /**
-         * Default constructor to register {@link DreamClockDateComplication}.
-         */
-        @Inject
-        public Registrant(Context context,
-                DreamOverlayStateController dreamOverlayStateController,
-                DreamClockDateComplication dreamClockDateComplication) {
-            super(context);
-            mDreamOverlayStateController = dreamOverlayStateController;
-            mComplication = dreamClockDateComplication;
-        }
-
-        @Override
-        public void start() {
-            mDreamOverlayStateController.addComplication(mComplication);
-        }
-    }
-
-    /**
-     * {@link ViewHolder} to contain value/logic associated with {@link DreamClockDateComplication}.
-     */
-    public static class DreamClockDateViewHolder implements ViewHolder {
-        private final View mView;
-        private final ComplicationLayoutParams mLayoutParams;
-
-        @Inject
-        DreamClockDateViewHolder(@Named(DREAM_CLOCK_DATE_COMPLICATION_VIEW) View view,
-                @Named(DREAM_CLOCK_DATE_COMPLICATION_LAYOUT_PARAMS)
-                        ComplicationLayoutParams layoutParams) {
-            mView = view;
-            mLayoutParams = layoutParams;
-        }
-
-        @Override
-        public View getView() {
-            return mView;
-        }
-
-        @Override
-        public ComplicationLayoutParams getLayoutParams() {
-            return mLayoutParams;
-        }
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/DreamClockTimeComplication.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/DreamClockTimeComplication.java
index 7f67ecd..675a2f4 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/complication/DreamClockTimeComplication.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/DreamClockTimeComplication.java
@@ -16,8 +16,8 @@
 
 package com.android.systemui.dreams.complication;
 
-import static com.android.systemui.dreams.complication.dagger.DreamClockTimeComplicationModule.DREAM_CLOCK_TIME_COMPLICATION_LAYOUT_PARAMS;
 import static com.android.systemui.dreams.complication.dagger.DreamClockTimeComplicationModule.DREAM_CLOCK_TIME_COMPLICATION_VIEW;
+import static com.android.systemui.dreams.complication.dagger.RegisteredComplicationsModule.DREAM_CLOCK_TIME_COMPLICATION_LAYOUT_PARAMS;
 
 import android.content.Context;
 import android.view.View;
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/DreamHomeControlsComplication.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/DreamHomeControlsComplication.java
index 1a9d9b5..02c5de3 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/complication/DreamHomeControlsComplication.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/DreamHomeControlsComplication.java
@@ -19,8 +19,8 @@
 import static com.android.systemui.controls.dagger.ControlsComponent.Visibility.AVAILABLE;
 import static com.android.systemui.controls.dagger.ControlsComponent.Visibility.AVAILABLE_AFTER_UNLOCK;
 import static com.android.systemui.controls.dagger.ControlsComponent.Visibility.UNAVAILABLE;
-import static com.android.systemui.dreams.complication.dagger.DreamHomeControlsComplicationComponent.DreamHomeControlsModule.DREAM_HOME_CONTROLS_CHIP_LAYOUT_PARAMS;
 import static com.android.systemui.dreams.complication.dagger.DreamHomeControlsComplicationComponent.DreamHomeControlsModule.DREAM_HOME_CONTROLS_CHIP_VIEW;
+import static com.android.systemui.dreams.complication.dagger.RegisteredComplicationsModule.DREAM_HOME_CONTROLS_CHIP_LAYOUT_PARAMS;
 
 import android.content.Context;
 import android.content.Intent;
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/SmartSpaceComplication.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/SmartSpaceComplication.java
similarity index 79%
rename from packages/SystemUI/src/com/android/systemui/dreams/SmartSpaceComplication.java
rename to packages/SystemUI/src/com/android/systemui/dreams/complication/SmartSpaceComplication.java
index be94e50..ac6edba 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/SmartSpaceComplication.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/SmartSpaceComplication.java
@@ -14,7 +14,9 @@
  * limitations under the License.
  */
 
-package com.android.systemui.dreams;
+package com.android.systemui.dreams.complication;
+
+import static com.android.systemui.dreams.complication.dagger.RegisteredComplicationsModule.DREAM_SMARTSPACE_LAYOUT_PARAMS;
 
 import android.content.Context;
 import android.os.Parcelable;
@@ -23,21 +25,33 @@
 import android.widget.FrameLayout;
 
 import com.android.systemui.CoreStartable;
-import com.android.systemui.dreams.complication.Complication;
-import com.android.systemui.dreams.complication.ComplicationLayoutParams;
-import com.android.systemui.dreams.complication.ComplicationViewModel;
+import com.android.systemui.dreams.DreamOverlayStateController;
 import com.android.systemui.dreams.smartspace.DreamSmartspaceController;
 import com.android.systemui.plugins.BcSmartspaceDataPlugin;
 
 import java.util.List;
 
 import javax.inject.Inject;
+import javax.inject.Named;
+import javax.inject.Provider;
 
 /**
  * {@link SmartSpaceComplication} embodies the SmartSpace view found on the lockscreen as a
  * {@link Complication}
  */
 public class SmartSpaceComplication implements Complication {
+    private final Provider<SmartSpaceComplicationViewHolder> mViewHolderProvider;
+
+    @Inject
+    public SmartSpaceComplication(Provider<SmartSpaceComplicationViewHolder> viewHolderProvider) {
+        mViewHolderProvider = viewHolderProvider;
+    }
+
+    @Override
+    public ViewHolder createView(ComplicationViewModel model) {
+        return mViewHolderProvider.get();
+    }
+
     /**
      * {@link CoreStartable} responsbile for registering {@link SmartSpaceComplication} with
      * SystemUI.
@@ -89,17 +103,20 @@
         }
     }
 
-    private static class SmartSpaceComplicationViewHolder implements ViewHolder {
+    static class SmartSpaceComplicationViewHolder implements ViewHolder {
         private View mView = null;
-        private static final int SMARTSPACE_COMPLICATION_WEIGHT = 10;
         private final DreamSmartspaceController mSmartSpaceController;
         private final Context mContext;
+        private final ComplicationLayoutParams mLayoutParams;
 
+        @Inject
         protected SmartSpaceComplicationViewHolder(
                 Context context,
-                DreamSmartspaceController smartSpaceController) {
+                DreamSmartspaceController smartSpaceController,
+                @Named(DREAM_SMARTSPACE_LAYOUT_PARAMS) ComplicationLayoutParams layoutParams) {
             mSmartSpaceController = smartSpaceController;
             mContext = context;
+            mLayoutParams = layoutParams;
         }
 
         @Override
@@ -119,25 +136,7 @@
 
         @Override
         public ComplicationLayoutParams getLayoutParams() {
-            return new ComplicationLayoutParams(0, ViewGroup.LayoutParams.WRAP_CONTENT,
-                    ComplicationLayoutParams.POSITION_TOP | ComplicationLayoutParams.POSITION_START,
-                    ComplicationLayoutParams.DIRECTION_DOWN,
-                    SMARTSPACE_COMPLICATION_WEIGHT, true);
+            return mLayoutParams;
         }
     }
-
-    private final DreamSmartspaceController mSmartSpaceController;
-    private final Context mContext;
-
-    @Inject
-    public SmartSpaceComplication(Context context,
-            DreamSmartspaceController smartSpaceController) {
-        mContext = context;
-        mSmartSpaceController = smartSpaceController;
-    }
-
-    @Override
-    public ViewHolder createView(ComplicationViewModel model) {
-        return new SmartSpaceComplicationViewHolder(mContext, mSmartSpaceController);
-    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/DreamClockDateComplicationModule.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/DreamClockDateComplicationModule.java
deleted file mode 100644
index 3ab26ce..0000000
--- a/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/DreamClockDateComplicationModule.java
+++ /dev/null
@@ -1,71 +0,0 @@
-/*
- * 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.dreams.complication.dagger;
-
-
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-
-import com.android.internal.util.Preconditions;
-import com.android.systemui.R;
-import com.android.systemui.dreams.complication.ComplicationLayoutParams;
-import com.android.systemui.dreams.complication.DreamClockDateComplication;
-
-import javax.inject.Named;
-
-import dagger.Module;
-import dagger.Provides;
-
-/**
- * Module for providing {@link DreamClockDateComplication}.
- */
-@Module
-public interface DreamClockDateComplicationModule {
-    String DREAM_CLOCK_DATE_COMPLICATION_VIEW = "clock_date_complication_view";
-    String DREAM_CLOCK_DATE_COMPLICATION_LAYOUT_PARAMS =
-            "clock_date_complication_layout_params";
-    // Order weight of insert into parent container
-    //TODO(b/217199227): move to a single location.
-    int INSERT_ORDER_WEIGHT = 3;
-
-    /**
-     * Provides the complication view.
-     */
-    @Provides
-    @Named(DREAM_CLOCK_DATE_COMPLICATION_VIEW)
-    static View provideComplicationView(LayoutInflater layoutInflater) {
-        return Preconditions.checkNotNull(
-                layoutInflater.inflate(R.layout.dream_overlay_complication_clock_date,
-                        null, false),
-                "R.layout.dream_overlay_complication_clock_date did not properly inflated");
-    }
-
-    /**
-     * Provides the layout parameters for the complication view.
-     */
-    @Provides
-    @Named(DREAM_CLOCK_DATE_COMPLICATION_LAYOUT_PARAMS)
-    static ComplicationLayoutParams provideLayoutParams() {
-        return new ComplicationLayoutParams(0,
-                ViewGroup.LayoutParams.WRAP_CONTENT,
-                ComplicationLayoutParams.POSITION_BOTTOM
-                        | ComplicationLayoutParams.POSITION_START,
-                ComplicationLayoutParams.DIRECTION_END,
-                INSERT_ORDER_WEIGHT, /* snapToGuide= */ true);
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/DreamClockTimeComplicationModule.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/DreamClockTimeComplicationModule.java
index 3ad7d3d..5250d44 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/DreamClockTimeComplicationModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/DreamClockTimeComplicationModule.java
@@ -19,12 +19,10 @@
 
 import android.view.LayoutInflater;
 import android.view.View;
-import android.view.ViewGroup;
 import android.widget.TextClock;
 
 import com.android.internal.util.Preconditions;
 import com.android.systemui.R;
-import com.android.systemui.dreams.complication.ComplicationLayoutParams;
 import com.android.systemui.dreams.complication.DreamClockTimeComplication;
 
 import javax.inject.Named;
@@ -38,11 +36,6 @@
 @Module
 public interface DreamClockTimeComplicationModule {
     String DREAM_CLOCK_TIME_COMPLICATION_VIEW = "clock_time_complication_view";
-    String DREAM_CLOCK_TIME_COMPLICATION_LAYOUT_PARAMS =
-            "clock_time_complication_layout_params";
-    // Order weight of insert into parent container
-    //TODO(b/217199227): move to a single location.
-    int INSERT_ORDER_WEIGHT = 0;
     String TAG_WEIGHT = "'wght' ";
     int WEIGHT = 200;
 
@@ -59,18 +52,4 @@
         view.setFontVariationSettings(TAG_WEIGHT + WEIGHT);
         return view;
     }
-
-    /**
-     * Provides the layout parameters for the complication view.
-     */
-    @Provides
-    @Named(DREAM_CLOCK_TIME_COMPLICATION_LAYOUT_PARAMS)
-    static ComplicationLayoutParams provideLayoutParams() {
-        return new ComplicationLayoutParams(0,
-                ViewGroup.LayoutParams.WRAP_CONTENT,
-                ComplicationLayoutParams.POSITION_BOTTOM
-                        | ComplicationLayoutParams.POSITION_START,
-                ComplicationLayoutParams.DIRECTION_UP,
-                INSERT_ORDER_WEIGHT);
-    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/DreamHomeControlsComplicationComponent.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/DreamHomeControlsComplicationComponent.java
index 033ce39..cf05d2d 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/DreamHomeControlsComplicationComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/DreamHomeControlsComplicationComponent.java
@@ -18,13 +18,10 @@
 
 import static java.lang.annotation.RetentionPolicy.RUNTIME;
 
-import android.content.res.Resources;
 import android.view.LayoutInflater;
 import android.widget.ImageView;
 
 import com.android.systemui.R;
-import com.android.systemui.dagger.qualifiers.Main;
-import com.android.systemui.dreams.complication.ComplicationLayoutParams;
 import com.android.systemui.dreams.complication.DreamHomeControlsComplication;
 
 import java.lang.annotation.Documented;
@@ -70,12 +67,6 @@
     @Module
     interface DreamHomeControlsModule {
         String DREAM_HOME_CONTROLS_CHIP_VIEW = "dream_home_controls_chip_view";
-        String DREAM_HOME_CONTROLS_CHIP_LAYOUT_PARAMS = "home_controls_chip_layout_params";
-
-        // TODO(b/217199227): move to a single location.
-        // Weight of order in the parent container. The home controls complication should have low
-        // weight and be placed at the end.
-        int INSERT_ORDER_WEIGHT = 0;
 
         /**
          * Provides the dream home controls chip view.
@@ -87,22 +78,5 @@
             return (ImageView) layoutInflater.inflate(R.layout.dream_overlay_home_controls_chip,
                     null, false);
         }
-
-        /**
-         * Provides the layout parameters for the dream home controls complication.
-         */
-        @Provides
-        @DreamHomeControlsComplicationScope
-        @Named(DREAM_HOME_CONTROLS_CHIP_LAYOUT_PARAMS)
-        static ComplicationLayoutParams provideLayoutParams(@Main Resources res) {
-            return new ComplicationLayoutParams(
-                    res.getDimensionPixelSize(R.dimen.keyguard_affordance_fixed_width),
-                    res.getDimensionPixelSize(R.dimen.keyguard_affordance_fixed_height),
-                    ComplicationLayoutParams.POSITION_BOTTOM
-                            | ComplicationLayoutParams.POSITION_START,
-                    ComplicationLayoutParams.DIRECTION_END,
-                    INSERT_ORDER_WEIGHT);
-        }
     }
-
 }
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/RegisteredComplicationsModule.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/RegisteredComplicationsModule.java
index 4a515f0..eb07238 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/RegisteredComplicationsModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/RegisteredComplicationsModule.java
@@ -16,20 +16,79 @@
 
 package com.android.systemui.dreams.complication.dagger;
 
+import android.content.res.Resources;
+import android.view.ViewGroup;
+
+import com.android.systemui.R;
 import com.android.systemui.dagger.SystemUIBinder;
+import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.dreams.complication.ComplicationLayoutParams;
+
+import javax.inject.Named;
 
 import dagger.Module;
+import dagger.Provides;
 
 /**
  * Module for all components with corresponding dream layer complications registered in
  * {@link SystemUIBinder}.
  */
 @Module(includes = {
-                DreamClockDateComplicationModule.class,
                 DreamClockTimeComplicationModule.class,
         },
         subcomponents = {
                 DreamHomeControlsComplicationComponent.class,
         })
 public interface RegisteredComplicationsModule {
+    String DREAM_CLOCK_TIME_COMPLICATION_LAYOUT_PARAMS = "time_complication_layout_params";
+    String DREAM_SMARTSPACE_LAYOUT_PARAMS = "smartspace_layout_params";
+    String DREAM_HOME_CONTROLS_CHIP_LAYOUT_PARAMS = "home_controls_chip_layout_params";
+
+    int DREAM_CLOCK_TIME_COMPLICATION_WEIGHT = 1;
+    int DREAM_SMARTSPACE_COMPLICATION_WEIGHT = 0;
+    int DREAM_HOME_CONTROLS_CHIP_COMPLICATION_WEIGHT = 1;
+
+    /**
+     * Provides layout parameters for the clock time complication.
+     */
+    @Provides
+    @Named(DREAM_CLOCK_TIME_COMPLICATION_LAYOUT_PARAMS)
+    static ComplicationLayoutParams provideClockTimeLayoutParams() {
+        return new ComplicationLayoutParams(0,
+            ViewGroup.LayoutParams.WRAP_CONTENT,
+            ComplicationLayoutParams.POSITION_TOP
+                    | ComplicationLayoutParams.POSITION_START,
+            ComplicationLayoutParams.DIRECTION_DOWN,
+            DREAM_CLOCK_TIME_COMPLICATION_WEIGHT);
+    }
+
+    /**
+     * Provides layout parameters for the home controls complication.
+     */
+    @Provides
+    @Named(DREAM_HOME_CONTROLS_CHIP_LAYOUT_PARAMS)
+    static ComplicationLayoutParams provideHomeControlsChipLayoutParams(@Main Resources res) {
+        return new ComplicationLayoutParams(
+            res.getDimensionPixelSize(R.dimen.keyguard_affordance_fixed_width),
+            res.getDimensionPixelSize(R.dimen.keyguard_affordance_fixed_height),
+            ComplicationLayoutParams.POSITION_BOTTOM
+                    | ComplicationLayoutParams.POSITION_START,
+            ComplicationLayoutParams.DIRECTION_END,
+            DREAM_HOME_CONTROLS_CHIP_COMPLICATION_WEIGHT);
+    }
+
+    /**
+     * Provides layout parameters for the smartspace complication.
+     */
+    @Provides
+    @Named(DREAM_SMARTSPACE_LAYOUT_PARAMS)
+    static ComplicationLayoutParams provideSmartspaceLayoutParams() {
+        return new ComplicationLayoutParams(0,
+            ViewGroup.LayoutParams.WRAP_CONTENT,
+            ComplicationLayoutParams.POSITION_TOP
+                    | ComplicationLayoutParams.POSITION_START,
+            ComplicationLayoutParams.DIRECTION_DOWN,
+            DREAM_SMARTSPACE_COMPLICATION_WEIGHT,
+            true /*snapToGuide*/);
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.java b/packages/SystemUI/src/com/android/systemui/flags/Flags.java
index b0f025d..20c8971 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.java
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.java
@@ -204,6 +204,10 @@
             new DeviceConfigBooleanFlag(1102, "record_task_content",
                     NAMESPACE_WINDOW_MANAGER, false, true);
 
+    @Keep
+    public static final SysPropBooleanFlag HIDE_NAVBAR_WINDOW =
+            new SysPropBooleanFlag(1103, "persist.wm.debug.hide_navbar_window", false);
+
     // 1200 - predictive back
     @Keep
     public static final SysPropBooleanFlag WM_ENABLE_PREDICTIVE_BACK = new SysPropBooleanFlag(
diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java
index ab30db2..ca65d12 100644
--- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java
+++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java
@@ -947,7 +947,7 @@
             mHandler.postDelayed(new Runnable() {
                 @Override
                 public void run() {
-                    mScreenshotHelper.takeScreenshot(TAKE_SCREENSHOT_FULLSCREEN, true, true,
+                    mScreenshotHelper.takeScreenshot(TAKE_SCREENSHOT_FULLSCREEN,
                             SCREENSHOT_GLOBAL_ACTIONS, mHandler, null);
                     mMetricsLogger.action(MetricsEvent.ACTION_SCREENSHOT_POWER_MENU);
                     mUiEventLogger.log(GlobalActionsEvent.GA_SCREENSHOT_PRESS);
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index 7ddce62..b4f40e2 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -627,13 +627,13 @@
                 case TelephonyManager.SIM_STATE_PUK_REQUIRED:
                     synchronized (KeyguardViewMediator.this) {
                         mSimWasLocked.append(slotId, true);
+                        mPendingPinLock = true;
                         if (!mShowing) {
                             if (DEBUG_SIM_STATES) Log.d(TAG,
                                     "INTENT_VALUE_ICC_LOCKED and keygaurd isn't "
                                     + "showing; need to show keyguard so user can enter sim pin");
                             doKeyguardLocked(null);
                         } else {
-                            mPendingPinLock = true;
                             resetStateLocked();
                         }
                     }
@@ -2985,6 +2985,7 @@
         pw.print("  mPendingReset: "); pw.println(mPendingReset);
         pw.print("  mPendingLock: "); pw.println(mPendingLock);
         pw.print("  wakeAndUnlocking: "); pw.println(mWakeAndUnlocking);
+        pw.print("  mPendingPinLock: "); pw.println(mPendingPinLock);
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
index 165af13..4ff008f 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
@@ -42,6 +42,8 @@
 import com.android.systemui.keyguard.DismissCallbackRegistry;
 import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
 import com.android.systemui.keyguard.KeyguardViewMediator;
+import com.android.systemui.keyguard.data.repository.KeyguardRepositoryModule;
+import com.android.systemui.keyguard.domain.usecase.KeyguardUseCaseModule;
 import com.android.systemui.navigationbar.NavigationModeController;
 import com.android.systemui.statusbar.NotificationShadeDepthController;
 import com.android.systemui.statusbar.NotificationShadeWindowController;
@@ -66,7 +68,11 @@
         KeyguardStatusBarViewComponent.class,
         KeyguardStatusViewComponent.class,
         KeyguardUserSwitcherComponent.class},
-        includes = {FalsingModule.class})
+        includes = {
+            FalsingModule.class,
+            KeyguardRepositoryModule.class,
+            KeyguardUseCaseModule.class,
+        })
 public class KeyguardModule {
     /**
      * Provides our instance of KeyguardViewMediator which is considered optional.
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfig.kt
new file mode 100644
index 0000000..3202ecb
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfig.kt
@@ -0,0 +1,121 @@
+/*
+ *  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.keyguard.data.quickaffordance
+
+import android.content.Context
+import android.content.Intent
+import androidx.annotation.DrawableRes
+import com.android.systemui.animation.ActivityLaunchAnimator
+import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
+import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
+import com.android.systemui.containeddrawable.ContainedDrawable
+import com.android.systemui.controls.ControlsServiceInfo
+import com.android.systemui.controls.controller.StructureInfo
+import com.android.systemui.controls.dagger.ControlsComponent
+import com.android.systemui.controls.management.ControlsListingController
+import com.android.systemui.controls.ui.ControlsActivity
+import com.android.systemui.controls.ui.ControlsUiController
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.util.kotlin.getOrNull
+import javax.inject.Inject
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.flowOf
+
+/** Home controls quick affordance data source. */
+@SysUISingleton
+class HomeControlsKeyguardQuickAffordanceConfig
+@Inject
+constructor(
+    @Application context: Context,
+    private val component: ControlsComponent,
+) : KeyguardQuickAffordanceConfig {
+
+    private val appContext = context.applicationContext
+
+    override val state: Flow<KeyguardQuickAffordanceConfig.State> =
+        stateInternal(component.getControlsListingController().getOrNull())
+
+    override fun onQuickAffordanceClicked(
+        animationController: ActivityLaunchAnimator.Controller?,
+    ): KeyguardQuickAffordanceConfig.OnClickedResult {
+        return KeyguardQuickAffordanceConfig.OnClickedResult.StartActivity(
+            intent =
+                Intent(appContext, ControlsActivity::class.java)
+                    .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_NEW_TASK)
+                    .putExtra(
+                        ControlsUiController.EXTRA_ANIMATE,
+                        true,
+                    ),
+            canShowWhileLocked = component.canShowWhileLockedSetting.value,
+        )
+    }
+
+    private fun stateInternal(
+        listingController: ControlsListingController?,
+    ): Flow<KeyguardQuickAffordanceConfig.State> {
+        if (listingController == null) {
+            return flowOf(KeyguardQuickAffordanceConfig.State.Hidden)
+        }
+
+        return conflatedCallbackFlow {
+            val callback =
+                object : ControlsListingController.ControlsListingCallback {
+                    override fun onServicesUpdated(serviceInfos: List<ControlsServiceInfo>) {
+                        val favorites: List<StructureInfo>? =
+                            component.getControlsController().getOrNull()?.getFavorites()
+
+                        trySendWithFailureLogging(
+                            state(
+                                isFeatureEnabled = component.isEnabled(),
+                                hasFavorites = favorites?.isNotEmpty() == true,
+                                hasServiceInfos = serviceInfos.isNotEmpty(),
+                                iconResourceId = component.getTileImageId(),
+                            ),
+                            TAG,
+                        )
+                    }
+                }
+
+            listingController.addCallback(callback)
+
+            awaitClose { listingController.removeCallback(callback) }
+        }
+    }
+
+    private fun state(
+        isFeatureEnabled: Boolean,
+        hasFavorites: Boolean,
+        hasServiceInfos: Boolean,
+        @DrawableRes iconResourceId: Int?,
+    ): KeyguardQuickAffordanceConfig.State {
+        return if (isFeatureEnabled && hasFavorites && hasServiceInfos && iconResourceId != null) {
+            KeyguardQuickAffordanceConfig.State.Visible(
+                icon = ContainedDrawable.WithResource(iconResourceId),
+                contentDescriptionResourceId = component.getTileTitleId(),
+            )
+        } else {
+            KeyguardQuickAffordanceConfig.State.Hidden
+        }
+    }
+
+    companion object {
+        private const val TAG = "HomeControlsKeyguardQuickAffordanceConfig"
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceConfig.kt
new file mode 100644
index 0000000..67a776e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceConfig.kt
@@ -0,0 +1,73 @@
+/*
+ *  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.keyguard.data.quickaffordance
+
+import android.content.Intent
+import androidx.annotation.StringRes
+import com.android.systemui.animation.ActivityLaunchAnimator
+import com.android.systemui.containeddrawable.ContainedDrawable
+import kotlinx.coroutines.flow.Flow
+
+/** Defines interface that can act as data source for a single quick affordance model. */
+interface KeyguardQuickAffordanceConfig {
+
+    val state: Flow<State>
+
+    fun onQuickAffordanceClicked(
+        animationController: ActivityLaunchAnimator.Controller?
+    ): OnClickedResult
+
+    /**
+     * Encapsulates the state of a "quick affordance" in the keyguard bottom area (for example, a
+     * button on the lock-screen).
+     */
+    sealed class State {
+
+        /** No affordance should show up. */
+        object Hidden : State()
+
+        /** An affordance is visible. */
+        data class Visible(
+            /** An icon for the affordance. */
+            val icon: ContainedDrawable,
+            /**
+             * Resource ID for a string to use for the accessibility content description text of the
+             * affordance.
+             */
+            @StringRes val contentDescriptionResourceId: Int,
+        ) : State()
+    }
+
+    sealed class OnClickedResult {
+        /**
+         * Returning this as a result from the [onQuickAffordanceClicked] method means that the
+         * implementation has taken care of the click, the system will do nothing.
+         */
+        object Handled : OnClickedResult()
+
+        /**
+         * Returning this as a result from the [onQuickAffordanceClicked] method means that the
+         * implementation has _not_ taken care of the click and the system should start an activity
+         * using the given [Intent].
+         */
+        data class StartActivity(
+            val intent: Intent,
+            val canShowWhileLocked: Boolean,
+        ) : OnClickedResult()
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfig.kt
new file mode 100644
index 0000000..758e411
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfig.kt
@@ -0,0 +1,95 @@
+/*
+ *  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.keyguard.data.quickaffordance
+
+import android.content.Context
+import com.android.systemui.R
+import com.android.systemui.animation.ActivityLaunchAnimator
+import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
+import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
+import com.android.systemui.containeddrawable.ContainedDrawable
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.qrcodescanner.controller.QRCodeScannerController
+import javax.inject.Inject
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+
+/** QR code scanner quick affordance data source. */
+@SysUISingleton
+class QrCodeScannerKeyguardQuickAffordanceConfig
+@Inject
+constructor(
+    @Application context: Context,
+    private val controller: QRCodeScannerController,
+) : KeyguardQuickAffordanceConfig {
+
+    private val appContext = context.applicationContext
+
+    override val state: Flow<KeyguardQuickAffordanceConfig.State> = conflatedCallbackFlow {
+        val callback =
+            object : QRCodeScannerController.Callback {
+                override fun onQRCodeScannerActivityChanged() {
+                    trySendWithFailureLogging(state(), TAG)
+                }
+                override fun onQRCodeScannerPreferenceChanged() {
+                    trySendWithFailureLogging(state(), TAG)
+                }
+            }
+
+        controller.addCallback(callback)
+        controller.registerQRCodeScannerChangeObservers(
+            QRCodeScannerController.DEFAULT_QR_CODE_SCANNER_CHANGE,
+            QRCodeScannerController.QR_CODE_SCANNER_PREFERENCE_CHANGE
+        )
+        // Registering does not push an initial update.
+        trySendWithFailureLogging(state(), "initial state", TAG)
+
+        awaitClose {
+            controller.unregisterQRCodeScannerChangeObservers(
+                QRCodeScannerController.DEFAULT_QR_CODE_SCANNER_CHANGE,
+                QRCodeScannerController.QR_CODE_SCANNER_PREFERENCE_CHANGE
+            )
+            controller.removeCallback(callback)
+        }
+    }
+
+    override fun onQuickAffordanceClicked(
+        animationController: ActivityLaunchAnimator.Controller?,
+    ): KeyguardQuickAffordanceConfig.OnClickedResult {
+        return KeyguardQuickAffordanceConfig.OnClickedResult.StartActivity(
+            intent = controller.intent,
+            canShowWhileLocked = true,
+        )
+    }
+
+    private fun state(): KeyguardQuickAffordanceConfig.State {
+        return if (controller.isEnabledForLockScreenButton) {
+            KeyguardQuickAffordanceConfig.State.Visible(
+                icon = ContainedDrawable.WithResource(R.drawable.ic_qr_code_scanner),
+                contentDescriptionResourceId = R.string.accessibility_qr_code_scanner_button,
+            )
+        } else {
+            KeyguardQuickAffordanceConfig.State.Hidden
+        }
+    }
+
+    companion object {
+        private const val TAG = "QrCodeScannerKeyguardQuickAffordanceConfig"
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfig.kt
new file mode 100644
index 0000000..c686e27
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfig.kt
@@ -0,0 +1,134 @@
+/*
+ *  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.keyguard.data.quickaffordance
+
+import android.graphics.drawable.Drawable
+import android.service.quickaccesswallet.GetWalletCardsError
+import android.service.quickaccesswallet.GetWalletCardsResponse
+import android.service.quickaccesswallet.QuickAccessWalletClient
+import android.util.Log
+import com.android.systemui.R
+import com.android.systemui.animation.ActivityLaunchAnimator
+import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
+import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
+import com.android.systemui.containeddrawable.ContainedDrawable
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.systemui.statusbar.policy.KeyguardStateControllerExt.isKeyguardShowing
+import com.android.systemui.wallet.controller.QuickAccessWalletController
+import javax.inject.Inject
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.flowOf
+
+/** Quick access wallet quick affordance data source. */
+@SysUISingleton
+class QuickAccessWalletKeyguardQuickAffordanceConfig
+@Inject
+constructor(
+    private val keyguardStateController: KeyguardStateController,
+    private val walletController: QuickAccessWalletController,
+    private val activityStarter: ActivityStarter,
+) : KeyguardQuickAffordanceConfig {
+
+    override val state: Flow<KeyguardQuickAffordanceConfig.State> =
+        keyguardStateController
+            .isKeyguardShowing(TAG)
+            .flatMapLatest { isKeyguardShowing ->
+                stateInternal(isKeyguardShowing)
+            }
+
+    override fun onQuickAffordanceClicked(
+        animationController: ActivityLaunchAnimator.Controller?,
+    ): KeyguardQuickAffordanceConfig.OnClickedResult {
+        walletController.startQuickAccessUiIntent(
+            activityStarter,
+            animationController,
+            /* hasCard= */ true,
+        )
+        return KeyguardQuickAffordanceConfig.OnClickedResult.Handled
+    }
+
+    private fun stateInternal(
+        isKeyguardShowing: Boolean
+    ): Flow<KeyguardQuickAffordanceConfig.State> {
+        if (!isKeyguardShowing) {
+            return flowOf(KeyguardQuickAffordanceConfig.State.Hidden)
+        }
+
+        return conflatedCallbackFlow {
+            val callback =
+                object : QuickAccessWalletClient.OnWalletCardsRetrievedCallback {
+                    override fun onWalletCardsRetrieved(response: GetWalletCardsResponse?) {
+                        trySendWithFailureLogging(
+                            state(
+                                isFeatureEnabled = walletController.isWalletEnabled,
+                                hasCard = response?.walletCards?.isNotEmpty() == true,
+                                tileIcon = walletController.walletClient.tileIcon,
+                            ),
+                            TAG,
+                        )
+                    }
+
+                    override fun onWalletCardRetrievalError(error: GetWalletCardsError?) {
+                        Log.e(TAG, "Wallet card retrieval error, message: \"${error?.message}\"")
+                        trySendWithFailureLogging(
+                            KeyguardQuickAffordanceConfig.State.Hidden,
+                            TAG,
+                        )
+                    }
+                }
+
+            walletController.setupWalletChangeObservers(
+                callback,
+                QuickAccessWalletController.WalletChangeEvent.WALLET_PREFERENCE_CHANGE,
+                QuickAccessWalletController.WalletChangeEvent.DEFAULT_PAYMENT_APP_CHANGE
+            )
+            walletController.updateWalletPreference()
+            walletController.queryWalletCards(callback)
+
+            awaitClose {
+                walletController.unregisterWalletChangeObservers(
+                    QuickAccessWalletController.WalletChangeEvent.WALLET_PREFERENCE_CHANGE,
+                    QuickAccessWalletController.WalletChangeEvent.DEFAULT_PAYMENT_APP_CHANGE
+                )
+            }
+        }
+    }
+
+    private fun state(
+        isFeatureEnabled: Boolean,
+        hasCard: Boolean,
+        tileIcon: Drawable?,
+    ): KeyguardQuickAffordanceConfig.State {
+        return if (isFeatureEnabled && hasCard && tileIcon != null) {
+            KeyguardQuickAffordanceConfig.State.Visible(
+                icon = ContainedDrawable.WithDrawable(tileIcon),
+                contentDescriptionResourceId = R.string.accessibility_wallet_button,
+            )
+        } else {
+            KeyguardQuickAffordanceConfig.State.Hidden
+        }
+    }
+
+    companion object {
+        private const val TAG = "QuickAccessWalletKeyguardQuickAffordanceConfig"
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceConfigs.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceConfigs.kt
new file mode 100644
index 0000000..7164215
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceConfigs.kt
@@ -0,0 +1,67 @@
+/*
+ *  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.keyguard.data.config
+
+import com.android.systemui.keyguard.data.quickaffordance.HomeControlsKeyguardQuickAffordanceConfig
+import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig
+import com.android.systemui.keyguard.data.quickaffordance.QrCodeScannerKeyguardQuickAffordanceConfig
+import com.android.systemui.keyguard.data.quickaffordance.QuickAccessWalletKeyguardQuickAffordanceConfig
+import com.android.systemui.keyguard.shared.model.KeyguardQuickAffordancePosition
+import javax.inject.Inject
+import kotlin.reflect.KClass
+
+/** Injectable provider of the positioning of the known quick affordance configs. */
+interface KeyguardQuickAffordanceConfigs {
+    fun getAll(position: KeyguardQuickAffordancePosition): List<KeyguardQuickAffordanceConfig>
+    fun get(configClass: KClass<out KeyguardQuickAffordanceConfig>): KeyguardQuickAffordanceConfig
+}
+
+class KeyguardQuickAffordanceConfigsImpl
+@Inject
+constructor(
+    homeControls: HomeControlsKeyguardQuickAffordanceConfig,
+    quickAccessWallet: QuickAccessWalletKeyguardQuickAffordanceConfig,
+    qrCodeScanner: QrCodeScannerKeyguardQuickAffordanceConfig,
+) : KeyguardQuickAffordanceConfigs {
+    private val configsByPosition =
+        mapOf(
+            KeyguardQuickAffordancePosition.BOTTOM_START to
+                listOf(
+                    homeControls,
+                ),
+            KeyguardQuickAffordancePosition.BOTTOM_END to
+                listOf(
+                    quickAccessWallet,
+                    qrCodeScanner,
+                ),
+        )
+    private val configByClass =
+        configsByPosition.values.flatten().associateBy { config -> config::class }
+
+    override fun getAll(
+        position: KeyguardQuickAffordancePosition,
+    ): List<KeyguardQuickAffordanceConfig> {
+        return configsByPosition.getValue(position)
+    }
+
+    override fun get(
+        configClass: KClass<out KeyguardQuickAffordanceConfig>
+    ): KeyguardQuickAffordanceConfig {
+        return configByClass.getValue(configClass)
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepository.kt
new file mode 100644
index 0000000..43c4fa0
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepository.kt
@@ -0,0 +1,65 @@
+/*
+ * 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.keyguard.data.repository
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.keyguard.data.config.KeyguardQuickAffordanceConfigs
+import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig.State
+import com.android.systemui.keyguard.shared.model.KeyguardQuickAffordanceModel
+import com.android.systemui.keyguard.shared.model.KeyguardQuickAffordancePosition
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.combine
+
+/** Defines interface for classes that encapsulate quick affordance state for the keyguard. */
+interface KeyguardQuickAffordanceRepository {
+    fun affordance(position: KeyguardQuickAffordancePosition): Flow<KeyguardQuickAffordanceModel>
+}
+
+/** Real implementation of [KeyguardQuickAffordanceRepository] */
+@SysUISingleton
+class KeyguardQuickAffordanceRepositoryImpl
+@Inject
+constructor(
+    private val configs: KeyguardQuickAffordanceConfigs,
+) : KeyguardQuickAffordanceRepository {
+
+    /** Returns an observable for the quick affordance model in the given position. */
+    override fun affordance(
+        position: KeyguardQuickAffordancePosition
+    ): Flow<KeyguardQuickAffordanceModel> {
+        val configs = configs.getAll(position)
+        return combine(configs.map { config -> config.state }) { states ->
+            val index = states.indexOfFirst { state -> state is State.Visible }
+            val visibleState =
+                if (index != -1) {
+                    states[index] as State.Visible
+                } else {
+                    null
+                }
+            if (visibleState != null) {
+                KeyguardQuickAffordanceModel.Visible(
+                    configKey = configs[index]::class,
+                    icon = visibleState.icon,
+                    contentDescriptionResourceId = visibleState.contentDescriptionResourceId,
+                )
+            } else {
+                KeyguardQuickAffordanceModel.Hidden
+            }
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
new file mode 100644
index 0000000..be91e51
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
@@ -0,0 +1,147 @@
+/*
+ * 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.keyguard.data.repository
+
+import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
+import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
+import com.android.systemui.common.data.model.Position
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.plugins.statusbar.StatusBarStateController
+import javax.inject.Inject
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
+
+/** Defines interface for classes that encapsulate application state for the keyguard. */
+interface KeyguardRepository {
+    /**
+     * Observable for whether the bottom area UI should animate the transition out of doze state.
+     *
+     * To learn more about doze state, please see [isDozing].
+     */
+    val animateBottomAreaDozingTransitions: StateFlow<Boolean>
+
+    /**
+     * Observable for the current amount of alpha that should be used for rendering the bottom area.
+     * UI.
+     */
+    val bottomAreaAlpha: StateFlow<Float>
+
+    /**
+     * Observable of the relative offset of the lock-screen clock from its natural position on the
+     * screen.
+     */
+    val clockPosition: StateFlow<Position>
+
+    /**
+     * Observable for whether we are in doze state.
+     *
+     * Doze state is the same as "Always on Display" or "AOD". It is the state that the device can
+     * enter to conserve battery when the device is locked and inactive.
+     *
+     * Note that it is possible for the system to be transitioning into doze while this flow still
+     * returns `false`. In order to account for that, observers should also use the [dozeAmount]
+     * flow to check if it's greater than `0`
+     */
+    val isDozing: Flow<Boolean>
+
+    /**
+     * Observable for the amount of doze we are currently in.
+     *
+     * While in doze state, this amount can change - driving a cycle of animations designed to avoid
+     * pixel burn-in, etc.
+     *
+     * Also note that the value here may be greater than `0` while [isDozing] is still `false`, this
+     * happens during an animation/transition into doze mode. An observer would be wise to account
+     * for both flows if needed.
+     */
+    val dozeAmount: Flow<Float>
+
+    /** Sets whether the bottom area UI should animate the transition out of doze state. */
+    fun setAnimateDozingTransitions(animate: Boolean)
+
+    /** Sets the current amount of alpha that should be used for rendering the bottom area. */
+    fun setBottomAreaAlpha(alpha: Float)
+
+    /**
+     * Sets the relative offset of the lock-screen clock from its natural position on the screen.
+     */
+    fun setClockPosition(x: Int, y: Int)
+}
+
+/** Encapsulates application state for the keyguard. */
+@SysUISingleton
+class KeyguardRepositoryImpl
+@Inject
+constructor(
+    statusBarStateController: StatusBarStateController,
+) : KeyguardRepository {
+    private val _animateBottomAreaDozingTransitions = MutableStateFlow(false)
+    override val animateBottomAreaDozingTransitions =
+        _animateBottomAreaDozingTransitions.asStateFlow()
+
+    private val _bottomAreaAlpha = MutableStateFlow(1f)
+    override val bottomAreaAlpha = _bottomAreaAlpha.asStateFlow()
+
+    private val _clockPosition = MutableStateFlow(Position(0, 0))
+    override val clockPosition = _clockPosition.asStateFlow()
+
+    override val isDozing: Flow<Boolean> = conflatedCallbackFlow {
+        val callback =
+            object : StatusBarStateController.StateListener {
+                override fun onDozingChanged(isDozing: Boolean) {
+                    trySendWithFailureLogging(isDozing, TAG, "updated isDozing")
+                }
+            }
+
+        statusBarStateController.addCallback(callback)
+        trySendWithFailureLogging(statusBarStateController.isDozing, TAG, "initial isDozing")
+
+        awaitClose { statusBarStateController.removeCallback(callback) }
+    }
+    override val dozeAmount: Flow<Float> = conflatedCallbackFlow {
+        val callback =
+            object : StatusBarStateController.StateListener {
+                override fun onDozeAmountChanged(linear: Float, eased: Float) {
+                    trySendWithFailureLogging(eased, TAG, "updated dozeAmount")
+                }
+            }
+
+        statusBarStateController.addCallback(callback)
+        trySendWithFailureLogging(statusBarStateController.dozeAmount, TAG, "initial dozeAmount")
+
+        awaitClose { statusBarStateController.removeCallback(callback) }
+    }
+
+    override fun setAnimateDozingTransitions(animate: Boolean) {
+        _animateBottomAreaDozingTransitions.value = animate
+    }
+
+    override fun setBottomAreaAlpha(alpha: Float) {
+        _bottomAreaAlpha.value = alpha
+    }
+
+    override fun setClockPosition(x: Int, y: Int) {
+        _clockPosition.value = Position(x, y)
+    }
+
+    companion object {
+        private const val TAG = "KeyguardRepositoryImpl"
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryModule.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryModule.kt
new file mode 100644
index 0000000..d2ab3e9
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryModule.kt
@@ -0,0 +1,36 @@
+/*
+ * 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.keyguard.data.repository
+
+import com.android.systemui.keyguard.data.config.KeyguardQuickAffordanceConfigs
+import com.android.systemui.keyguard.data.config.KeyguardQuickAffordanceConfigsImpl
+import dagger.Binds
+import dagger.Module
+
+@Module
+interface KeyguardRepositoryModule {
+    @Binds fun keyguardRepository(impl: KeyguardRepositoryImpl): KeyguardRepository
+
+    @Binds
+    fun keyguardQuickAffordanceRepository(
+        impl: KeyguardQuickAffordanceRepositoryImpl
+    ): KeyguardQuickAffordanceRepository
+
+    @Binds fun keyguardQuickAffordanceConfigs(
+        impl: KeyguardQuickAffordanceConfigsImpl
+    ): KeyguardQuickAffordanceConfigs
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/KeyguardUseCaseModule.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/KeyguardUseCaseModule.kt
new file mode 100644
index 0000000..c44c2c9
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/KeyguardUseCaseModule.kt
@@ -0,0 +1,29 @@
+/*
+ * 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.keyguard.domain.usecase
+
+import dagger.Binds
+import dagger.Module
+
+@Module
+interface KeyguardUseCaseModule {
+
+    @Binds
+    fun launchQuickAffordance(
+        impl: LaunchKeyguardQuickAffordanceUseCaseImpl
+    ): LaunchKeyguardQuickAffordanceUseCase
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/LaunchKeyguardQuickAffordanceUseCase.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/LaunchKeyguardQuickAffordanceUseCase.kt
new file mode 100644
index 0000000..3d60399
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/LaunchKeyguardQuickAffordanceUseCase.kt
@@ -0,0 +1,76 @@
+/*
+ * 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.keyguard.domain.usecase
+
+import android.content.Intent
+import com.android.internal.widget.LockPatternUtils
+import com.android.internal.widget.LockPatternUtils.StrongAuthTracker.StrongAuthFlags
+import com.android.systemui.animation.ActivityLaunchAnimator
+import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.settings.UserTracker
+import com.android.systemui.statusbar.policy.KeyguardStateController
+import javax.inject.Inject
+
+/** Defines interface for classes that can launch a quick affordance. */
+interface LaunchKeyguardQuickAffordanceUseCase {
+    operator fun invoke(
+        intent: Intent,
+        canShowWhileLocked: Boolean,
+        animationController: ActivityLaunchAnimator.Controller?,
+    )
+}
+
+/** Real implementation of [LaunchKeyguardQuickAffordanceUseCase] */
+class LaunchKeyguardQuickAffordanceUseCaseImpl
+@Inject
+constructor(
+    private val lockPatternUtils: LockPatternUtils,
+    private val keyguardStateController: KeyguardStateController,
+    private val userTracker: UserTracker,
+    private val activityStarter: ActivityStarter,
+) : LaunchKeyguardQuickAffordanceUseCase {
+    override operator fun invoke(
+        intent: Intent,
+        canShowWhileLocked: Boolean,
+        animationController: ActivityLaunchAnimator.Controller?,
+    ) {
+        @StrongAuthFlags
+        val strongAuthFlags =
+            lockPatternUtils.getStrongAuthForUser(userTracker.userHandle.identifier)
+        val needsToUnlockFirst =
+            when {
+                strongAuthFlags ==
+                    LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_BOOT -> true
+                !canShowWhileLocked && !keyguardStateController.isUnlocked -> true
+                else -> false
+            }
+        if (needsToUnlockFirst) {
+            activityStarter.postStartActivityDismissingKeyguard(
+                intent,
+                0 /* delay */,
+                animationController
+            )
+        } else {
+            activityStarter.startActivity(
+                intent,
+                true /* dismissShade */,
+                animationController,
+                true /* showOverLockscreenWhenLocked */,
+            )
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/ObserveAnimateBottomAreaTransitionsUseCase.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/ObserveAnimateBottomAreaTransitionsUseCase.kt
new file mode 100644
index 0000000..ca37727
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/ObserveAnimateBottomAreaTransitionsUseCase.kt
@@ -0,0 +1,32 @@
+/*
+ * 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.keyguard.domain.usecase
+
+import com.android.systemui.keyguard.data.repository.KeyguardRepository
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+
+/** Use-case for observing whether doze state transitions should animate the bottom area */
+class ObserveAnimateBottomAreaTransitionsUseCase
+@Inject
+constructor(
+    private val repository: KeyguardRepository,
+) {
+    operator fun invoke(): Flow<Boolean> {
+        return repository.animateBottomAreaDozingTransitions
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/ObserveBottomAreaAlphaUseCase.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/ObserveBottomAreaAlphaUseCase.kt
new file mode 100644
index 0000000..151b704
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/ObserveBottomAreaAlphaUseCase.kt
@@ -0,0 +1,32 @@
+/*
+ * 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.keyguard.domain.usecase
+
+import com.android.systemui.keyguard.data.repository.KeyguardRepository
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+
+/** Use-case for observing the alpha of the bottom area */
+class ObserveBottomAreaAlphaUseCase
+@Inject
+constructor(
+    private val repository: KeyguardRepository,
+) {
+    operator fun invoke(): Flow<Float> {
+        return repository.bottomAreaAlpha
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/ObserveClockPositionUseCase.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/ObserveClockPositionUseCase.kt
new file mode 100644
index 0000000..02c5737
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/ObserveClockPositionUseCase.kt
@@ -0,0 +1,35 @@
+/*
+ * 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.keyguard.domain.usecase
+
+import com.android.systemui.common.domain.model.Position
+import com.android.systemui.common.domain.model.Position.Companion.toDomainLayer
+import com.android.systemui.keyguard.data.repository.KeyguardRepository
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.map
+
+/** Use-case for observing the position of the clock. */
+class ObserveClockPositionUseCase
+@Inject
+constructor(
+    private val repository: KeyguardRepository,
+) {
+    operator fun invoke(): Flow<Position> {
+        return repository.clockPosition.map { it.toDomainLayer() }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/ObserveDozeAmountUseCase.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/ObserveDozeAmountUseCase.kt
new file mode 100644
index 0000000..56d6182
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/ObserveDozeAmountUseCase.kt
@@ -0,0 +1,32 @@
+/*
+ * 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.keyguard.domain.usecase
+
+import com.android.systemui.keyguard.data.repository.KeyguardRepository
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+
+/** Use-case for observing the amount of doze the system is in. */
+class ObserveDozeAmountUseCase
+@Inject
+constructor(
+    private val repository: KeyguardRepository,
+) {
+    operator fun invoke(): Flow<Float> {
+        return repository.dozeAmount
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/ObserveIsDozingUseCase.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/ObserveIsDozingUseCase.kt
new file mode 100644
index 0000000..1d241d9
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/ObserveIsDozingUseCase.kt
@@ -0,0 +1,32 @@
+/*
+ * 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.keyguard.domain.usecase
+
+import com.android.systemui.keyguard.data.repository.KeyguardRepository
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+
+/** Use-case for observing whether we are dozing. */
+class ObserveIsDozingUseCase
+@Inject
+constructor(
+    private val repository: KeyguardRepository,
+) {
+    operator fun invoke(): Flow<Boolean> {
+        return repository.isDozing
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/ObserveKeyguardQuickAffordanceUseCase.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/ObserveKeyguardQuickAffordanceUseCase.kt
new file mode 100644
index 0000000..aac3d75
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/ObserveKeyguardQuickAffordanceUseCase.kt
@@ -0,0 +1,49 @@
+/*
+ * 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.keyguard.domain.usecase
+
+import com.android.systemui.keyguard.data.repository.KeyguardQuickAffordanceRepository
+import com.android.systemui.keyguard.shared.model.KeyguardQuickAffordanceModel
+import com.android.systemui.keyguard.shared.model.KeyguardQuickAffordancePosition
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.combine
+
+/** Use-case for observing the model of a quick affordance in the keyguard. */
+class ObserveKeyguardQuickAffordanceUseCase
+@Inject
+constructor(
+    private val repository: KeyguardQuickAffordanceRepository,
+    private val isDozingUseCase: ObserveIsDozingUseCase,
+    private val dozeAmountUseCase: ObserveDozeAmountUseCase,
+) {
+    operator fun invoke(
+        position: KeyguardQuickAffordancePosition
+    ): Flow<KeyguardQuickAffordanceModel> {
+        return combine(
+            repository.affordance(position),
+            isDozingUseCase(),
+            dozeAmountUseCase(),
+        ) { affordance, isDozing, dozeAmount ->
+            if (!isDozing && dozeAmount == 0f) {
+                affordance
+            } else {
+                KeyguardQuickAffordanceModel.Hidden
+            }
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/OnKeyguardQuickAffordanceClickedUseCase.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/OnKeyguardQuickAffordanceClickedUseCase.kt
new file mode 100644
index 0000000..f8db90f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/OnKeyguardQuickAffordanceClickedUseCase.kt
@@ -0,0 +1,49 @@
+/*
+ * 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.keyguard.domain.usecase
+
+import com.android.systemui.animation.ActivityLaunchAnimator
+import com.android.systemui.keyguard.data.config.KeyguardQuickAffordanceConfigs
+import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig
+import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig.OnClickedResult
+import javax.inject.Inject
+import kotlin.reflect.KClass
+
+/** Use-case for handling a click on a keyguard quick affordance (e.g. bottom button). */
+class OnKeyguardQuickAffordanceClickedUseCase
+@Inject
+constructor(
+    private val configs: KeyguardQuickAffordanceConfigs,
+    private val launchAffordanceUseCase: LaunchKeyguardQuickAffordanceUseCase,
+) {
+    operator fun invoke(
+        configKey: KClass<*>,
+        animationController: ActivityLaunchAnimator.Controller?,
+    ) {
+        @Suppress("UNCHECKED_CAST")
+        val config = configs.get(configKey as KClass<out KeyguardQuickAffordanceConfig>)
+        when (val result = config.onQuickAffordanceClicked(animationController)) {
+            is OnClickedResult.StartActivity ->
+                launchAffordanceUseCase(
+                    intent = result.intent,
+                    canShowWhileLocked = result.canShowWhileLocked,
+                    animationController = animationController
+                )
+            is OnClickedResult.Handled -> Unit
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/SetClockPositionUseCase.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/SetClockPositionUseCase.kt
new file mode 100644
index 0000000..8f746e5
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/SetClockPositionUseCase.kt
@@ -0,0 +1,31 @@
+/*
+ * 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.keyguard.domain.usecase
+
+import com.android.systemui.keyguard.data.repository.KeyguardRepository
+import javax.inject.Inject
+
+/** Use-case for setting the updated clock position. */
+class SetClockPositionUseCase
+@Inject
+constructor(
+    private val keyguardRepository: KeyguardRepository,
+) {
+    operator fun invoke(x: Int, y: Int) {
+        keyguardRepository.setClockPosition(x, y)
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/SetKeyguardBottomAreaAlphaUseCase.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/SetKeyguardBottomAreaAlphaUseCase.kt
new file mode 100644
index 0000000..90be1ec
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/SetKeyguardBottomAreaAlphaUseCase.kt
@@ -0,0 +1,31 @@
+/*
+ * 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.keyguard.domain.usecase
+
+import com.android.systemui.keyguard.data.repository.KeyguardRepository
+import javax.inject.Inject
+
+/** Use-case for setting the alpha that the keyguard bottom area should use */
+class SetKeyguardBottomAreaAlphaUseCase
+@Inject
+constructor(
+    private val repository: KeyguardRepository,
+) {
+    operator fun invoke(alpha: Float) {
+        repository.setBottomAreaAlpha(alpha)
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/SetKeyguardBottomAreaAnimateDozingTransitionsUseCase.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/SetKeyguardBottomAreaAnimateDozingTransitionsUseCase.kt
new file mode 100644
index 0000000..007780a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/SetKeyguardBottomAreaAnimateDozingTransitionsUseCase.kt
@@ -0,0 +1,33 @@
+/*
+ * 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.keyguard.domain.usecase
+
+import com.android.systemui.keyguard.data.repository.KeyguardRepository
+import javax.inject.Inject
+
+/**
+ * Use-case for setting whether the keyguard bottom area should animate the next doze transitions
+ */
+class SetKeyguardBottomAreaAnimateDozingTransitionsUseCase
+@Inject
+constructor(
+    private val repository: KeyguardRepository,
+) {
+    operator fun invoke(animate: Boolean) {
+        repository.setAnimateDozingTransitions(animate)
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardQuickAffordanceModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardQuickAffordanceModel.kt
new file mode 100644
index 0000000..09785df
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardQuickAffordanceModel.kt
@@ -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.systemui.keyguard.shared.model
+
+import androidx.annotation.StringRes
+import com.android.systemui.containeddrawable.ContainedDrawable
+import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig
+import kotlin.reflect.KClass
+
+/**
+ * Models a "quick affordance" in the keyguard bottom area (for example, a button on the
+ * lock-screen).
+ */
+sealed class KeyguardQuickAffordanceModel {
+
+    /** No affordance should show up. */
+    object Hidden : KeyguardQuickAffordanceModel()
+
+    /** A affordance is visible. */
+    data class Visible(
+        /** Identifier for the affordance this is modeling. */
+        val configKey: KClass<out KeyguardQuickAffordanceConfig>,
+        /** An icon for the affordance. */
+        val icon: ContainedDrawable,
+        /**
+         * Resource ID for a string to use for the accessibility content description text of the
+         * affordance.
+         */
+        @StringRes val contentDescriptionResourceId: Int,
+    ) : KeyguardQuickAffordanceModel()
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardQuickAffordancePosition.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardQuickAffordancePosition.kt
new file mode 100644
index 0000000..b71e15d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardQuickAffordancePosition.kt
@@ -0,0 +1,23 @@
+/*
+ * 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.keyguard.shared.model
+
+/** Enumerates all possible positions for quick affordances that can appear on the lock-screen. */
+enum class KeyguardQuickAffordancePosition {
+    BOTTOM_START,
+    BOTTOM_END,
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt
new file mode 100644
index 0000000..04d30bf
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt
@@ -0,0 +1,307 @@
+/*
+ * 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.keyguard.ui.binder
+
+import android.util.Size
+import android.util.TypedValue
+import android.view.View
+import android.view.ViewGroup
+import android.view.ViewPropertyAnimator
+import android.widget.ImageView
+import android.widget.TextView
+import androidx.core.view.isVisible
+import androidx.core.view.updateLayoutParams
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.repeatOnLifecycle
+import com.android.settingslib.Utils
+import com.android.systemui.R
+import com.android.systemui.animation.ActivityLaunchAnimator
+import com.android.systemui.animation.Interpolators
+import com.android.systemui.containeddrawable.ContainedDrawable
+import com.android.systemui.keyguard.ui.viewmodel.KeyguardBottomAreaViewModel
+import com.android.systemui.keyguard.ui.viewmodel.KeyguardQuickAffordanceViewModel
+import com.android.systemui.lifecycle.repeatWhenAttached
+import com.android.systemui.plugins.FalsingManager
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.collect
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.launch
+
+/**
+ * Binds a keyguard bottom area view to its view-model.
+ *
+ * To use this properly, users should maintain a one-to-one relationship between the [View] and the
+ * view-binding, binding each view only once. It is okay and expected for the same instance of the
+ * view-model to be reused for multiple view/view-binder bindings.
+ */
+object KeyguardBottomAreaViewBinder {
+
+    private const val EXIT_DOZE_BUTTON_REVEAL_ANIMATION_DURATION_MS = 250L
+
+    /**
+     * Defines interface for an object that acts as the binding between the view and its view-model.
+     *
+     * Users of the [KeyguardBottomAreaViewBinder] class should use this to control the binder after
+     * it is bound.
+     */
+    interface Binding {
+        /**
+         * Returns a collection of [ViewPropertyAnimator] instances that can be used to animate the
+         * indication areas.
+         */
+        fun getIndicationAreaAnimators(): List<ViewPropertyAnimator>
+
+        /** Notifies that device configuration has changed. */
+        fun onConfigurationChanged()
+    }
+
+    /** Binds the view to the view-model, continuing to update the former based on the latter. */
+    @JvmStatic
+    fun bind(
+        view: ViewGroup,
+        viewModel: KeyguardBottomAreaViewModel,
+        falsingManager: FalsingManager,
+    ): Binding {
+        val indicationArea: View = view.requireViewById(R.id.keyguard_indication_area)
+        val ambientIndicationArea: View? = view.findViewById(R.id.ambient_indication_container)
+        val startButton: ImageView = view.requireViewById(R.id.start_button)
+        val endButton: ImageView = view.requireViewById(R.id.end_button)
+        val overlayContainer: View = view.requireViewById(R.id.overlay_container)
+        val indicationText: TextView = view.requireViewById(R.id.keyguard_indication_text)
+        val indicationTextBottom: TextView =
+            view.requireViewById(R.id.keyguard_indication_text_bottom)
+
+        view.clipChildren = false
+        view.clipToPadding = false
+
+        val configurationBasedDimensions = MutableStateFlow(loadFromResources(view))
+
+        view.repeatWhenAttached {
+            repeatOnLifecycle(Lifecycle.State.STARTED) {
+                launch {
+                    combine(viewModel.startButton, viewModel.animateButtonReveal) {
+                            buttonModel,
+                            animateReveal ->
+                            Pair(buttonModel, animateReveal)
+                        }
+                        .collect { (buttonModel, animateReveal) ->
+                            updateButton(
+                                view = startButton,
+                                viewModel = buttonModel,
+                                animateReveal = animateReveal,
+                                falsingManager = falsingManager,
+                            )
+                        }
+                }
+
+                launch {
+                    combine(viewModel.endButton, viewModel.animateButtonReveal) {
+                            buttonModel,
+                            animateReveal ->
+                            Pair(buttonModel, animateReveal)
+                        }
+                        .collect { (buttonModel, animateReveal) ->
+                            updateButton(
+                                view = endButton,
+                                viewModel = buttonModel,
+                                animateReveal = animateReveal,
+                                falsingManager = falsingManager,
+                            )
+                        }
+                }
+
+                launch {
+                    viewModel.isOverlayContainerVisible.collect { isVisible ->
+                        overlayContainer.visibility =
+                            if (isVisible) {
+                                View.VISIBLE
+                            } else {
+                                View.INVISIBLE
+                            }
+                    }
+                }
+
+                launch {
+                    viewModel.alpha.collect { alpha ->
+                        view.importantForAccessibility =
+                            if (alpha == 0f) {
+                                View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS
+                            } else {
+                                View.IMPORTANT_FOR_ACCESSIBILITY_AUTO
+                            }
+
+                        ambientIndicationArea?.alpha = alpha
+                        indicationArea.alpha = alpha
+                        startButton.alpha = alpha
+                        endButton.alpha = alpha
+                    }
+                }
+
+                launch {
+                    viewModel.indicationAreaTranslationX.collect { translationX ->
+                        indicationArea.translationX = translationX
+                        ambientIndicationArea?.translationX = translationX
+                    }
+                }
+
+                launch {
+                    combine(
+                            viewModel.isIndicationAreaPadded,
+                            configurationBasedDimensions.map { it.indicationAreaPaddingPx },
+                        ) { isPadded, paddingIfPaddedPx ->
+                            if (isPadded) {
+                                paddingIfPaddedPx
+                            } else {
+                                0
+                            }
+                        }
+                        .collect { paddingPx ->
+                            indicationArea.setPadding(paddingPx, 0, paddingPx, 0)
+                        }
+                }
+
+                launch {
+                    configurationBasedDimensions
+                        .map { it.defaultBurnInPreventionYOffsetPx }
+                        .flatMapLatest { defaultBurnInOffsetY ->
+                            viewModel.indicationAreaTranslationY(defaultBurnInOffsetY)
+                        }
+                        .collect { translationY ->
+                            indicationArea.translationY = translationY
+                            ambientIndicationArea?.translationY = translationY
+                        }
+                }
+
+                launch {
+                    configurationBasedDimensions.collect { dimensions ->
+                        indicationText.setTextSize(
+                            TypedValue.COMPLEX_UNIT_PX,
+                            dimensions.indicationTextSizePx.toFloat(),
+                        )
+                        indicationTextBottom.setTextSize(
+                            TypedValue.COMPLEX_UNIT_PX,
+                            dimensions.indicationTextSizePx.toFloat(),
+                        )
+
+                        startButton.updateLayoutParams<ViewGroup.LayoutParams> {
+                            width = dimensions.buttonSizePx.width
+                            height = dimensions.buttonSizePx.height
+                        }
+                        endButton.updateLayoutParams<ViewGroup.LayoutParams> {
+                            width = dimensions.buttonSizePx.width
+                            height = dimensions.buttonSizePx.height
+                        }
+                    }
+                }
+            }
+        }
+
+        return object : Binding {
+            override fun getIndicationAreaAnimators(): List<ViewPropertyAnimator> {
+                return listOf(indicationArea, ambientIndicationArea).mapNotNull { it?.animate() }
+            }
+
+            override fun onConfigurationChanged() {
+                configurationBasedDimensions.value = loadFromResources(view)
+            }
+        }
+    }
+
+    private fun updateButton(
+        view: ImageView,
+        viewModel: KeyguardQuickAffordanceViewModel,
+        animateReveal: Boolean,
+        falsingManager: FalsingManager,
+    ) {
+        if (!viewModel.isVisible) {
+            view.isVisible = false
+            return
+        }
+
+        if (!view.isVisible) {
+            view.isVisible = true
+            if (animateReveal) {
+                view.alpha = 0f
+                view.translationY = view.height / 2f
+                view
+                    .animate()
+                    .alpha(1f)
+                    .translationY(0f)
+                    .setInterpolator(Interpolators.LINEAR_OUT_SLOW_IN)
+                    .setDuration(EXIT_DOZE_BUTTON_REVEAL_ANIMATION_DURATION_MS)
+                    .start()
+            }
+        }
+
+        when (viewModel.icon) {
+            is ContainedDrawable.WithDrawable -> view.setImageDrawable(viewModel.icon.drawable)
+            is ContainedDrawable.WithResource -> view.setImageResource(viewModel.icon.resourceId)
+        }
+
+        view.drawable.setTint(
+            Utils.getColorAttrDefaultColor(
+                view.context,
+                com.android.internal.R.attr.textColorPrimary
+            )
+        )
+        view.backgroundTintList =
+            Utils.getColorAttr(view.context, com.android.internal.R.attr.colorSurface)
+
+        view.contentDescription = view.context.getString(viewModel.contentDescriptionResourceId)
+        view.setOnClickListener {
+            if (falsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) {
+                return@setOnClickListener
+            }
+
+            if (viewModel.configKey != null) {
+                viewModel.onClicked(
+                    KeyguardQuickAffordanceViewModel.OnClickedParameters(
+                        configKey = viewModel.configKey,
+                        animationController = ActivityLaunchAnimator.Controller.fromView(view),
+                    )
+                )
+            }
+        }
+    }
+
+    private fun loadFromResources(view: View): ConfigurationBasedDimensions {
+        return ConfigurationBasedDimensions(
+            defaultBurnInPreventionYOffsetPx =
+                view.resources.getDimensionPixelOffset(R.dimen.default_burn_in_prevention_offset),
+            indicationAreaPaddingPx =
+                view.resources.getDimensionPixelOffset(R.dimen.keyguard_indication_area_padding),
+            indicationTextSizePx =
+                view.resources.getDimensionPixelSize(
+                    com.android.internal.R.dimen.text_size_small_material,
+                ),
+            buttonSizePx =
+                Size(
+                    view.resources.getDimensionPixelSize(R.dimen.keyguard_affordance_fixed_width),
+                    view.resources.getDimensionPixelSize(R.dimen.keyguard_affordance_fixed_height),
+                ),
+        )
+    }
+
+    private data class ConfigurationBasedDimensions(
+        val defaultBurnInPreventionYOffsetPx: Int,
+        val indicationAreaPaddingPx: Int,
+        val indicationTextSizePx: Int,
+        val buttonSizePx: Size,
+    )
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModel.kt
new file mode 100644
index 0000000..4b69a81
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModel.kt
@@ -0,0 +1,114 @@
+/*
+ * 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.keyguard.ui.viewmodel
+
+import com.android.systemui.doze.util.BurnInHelperWrapper
+import com.android.systemui.keyguard.domain.usecase.ObserveAnimateBottomAreaTransitionsUseCase
+import com.android.systemui.keyguard.domain.usecase.ObserveBottomAreaAlphaUseCase
+import com.android.systemui.keyguard.domain.usecase.ObserveClockPositionUseCase
+import com.android.systemui.keyguard.domain.usecase.ObserveDozeAmountUseCase
+import com.android.systemui.keyguard.domain.usecase.ObserveIsDozingUseCase
+import com.android.systemui.keyguard.domain.usecase.ObserveKeyguardQuickAffordanceUseCase
+import com.android.systemui.keyguard.domain.usecase.OnKeyguardQuickAffordanceClickedUseCase
+import com.android.systemui.keyguard.shared.model.KeyguardQuickAffordanceModel
+import com.android.systemui.keyguard.shared.model.KeyguardQuickAffordancePosition
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.map
+
+/** View-model for the keyguard bottom area view */
+class KeyguardBottomAreaViewModel
+@Inject
+constructor(
+    private val observeQuickAffordanceUseCase: ObserveKeyguardQuickAffordanceUseCase,
+    private val onQuickAffordanceClickedUseCase: OnKeyguardQuickAffordanceClickedUseCase,
+    observeBottomAreaAlphaUseCase: ObserveBottomAreaAlphaUseCase,
+    observeIsDozingUseCase: ObserveIsDozingUseCase,
+    observeAnimateBottomAreaTransitionsUseCase: ObserveAnimateBottomAreaTransitionsUseCase,
+    private val observeDozeAmountUseCase: ObserveDozeAmountUseCase,
+    observeClockPositionUseCase: ObserveClockPositionUseCase,
+    private val burnInHelperWrapper: BurnInHelperWrapper,
+) {
+    /** An observable for the view-model of the "start button" quick affordance. */
+    val startButton: Flow<KeyguardQuickAffordanceViewModel> =
+        button(KeyguardQuickAffordancePosition.BOTTOM_START)
+    /** An observable for the view-model of the "end button" quick affordance. */
+    val endButton: Flow<KeyguardQuickAffordanceViewModel> =
+        button(KeyguardQuickAffordancePosition.BOTTOM_END)
+    /**
+     * An observable for whether the next time a quick action button becomes visible, it should
+     * animate.
+     */
+    val animateButtonReveal: Flow<Boolean> =
+        observeAnimateBottomAreaTransitionsUseCase().distinctUntilChanged()
+    /** An observable for whether the overlay container should be visible. */
+    val isOverlayContainerVisible: Flow<Boolean> =
+        observeIsDozingUseCase().map { !it }.distinctUntilChanged()
+    /** An observable for the alpha level for the entire bottom area. */
+    val alpha: Flow<Float> = observeBottomAreaAlphaUseCase().distinctUntilChanged()
+    /** An observable for whether the indication area should be padded. */
+    val isIndicationAreaPadded: Flow<Boolean> =
+        combine(startButton, endButton) { startButtonModel, endButtonModel ->
+                startButtonModel.isVisible || endButtonModel.isVisible
+            }
+            .distinctUntilChanged()
+    /** An observable for the x-offset by which the indication area should be translated. */
+    val indicationAreaTranslationX: Flow<Float> =
+        observeClockPositionUseCase().map { it.x.toFloat() }.distinctUntilChanged()
+
+    /** Returns an observable for the y-offset by which the indication area should be translated. */
+    fun indicationAreaTranslationY(defaultBurnInOffset: Int): Flow<Float> {
+        return observeDozeAmountUseCase()
+            .map { dozeAmount ->
+                dozeAmount *
+                    (burnInHelperWrapper.burnInOffset(
+                        /* amplitude = */ defaultBurnInOffset * 2,
+                        /* xAxis= */ false,
+                    ) - defaultBurnInOffset)
+            }
+            .distinctUntilChanged()
+    }
+
+    private fun button(
+        position: KeyguardQuickAffordancePosition
+    ): Flow<KeyguardQuickAffordanceViewModel> {
+        return observeQuickAffordanceUseCase(position)
+            .map { model -> model.toViewModel() }
+            .distinctUntilChanged()
+    }
+
+    private fun KeyguardQuickAffordanceModel.toViewModel(): KeyguardQuickAffordanceViewModel {
+        return when (this) {
+            is KeyguardQuickAffordanceModel.Visible ->
+                KeyguardQuickAffordanceViewModel(
+                    configKey = configKey,
+                    isVisible = true,
+                    icon = icon,
+                    contentDescriptionResourceId = contentDescriptionResourceId,
+                    onClicked = { parameters ->
+                        onQuickAffordanceClickedUseCase(
+                            configKey = parameters.configKey,
+                            animationController = parameters.animationController,
+                        )
+                    },
+                )
+            is KeyguardQuickAffordanceModel.Hidden -> KeyguardQuickAffordanceViewModel()
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordanceViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordanceViewModel.kt
new file mode 100644
index 0000000..2417998
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordanceViewModel.kt
@@ -0,0 +1,36 @@
+/*
+ * 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.keyguard.ui.viewmodel
+
+import androidx.annotation.StringRes
+import com.android.systemui.animation.ActivityLaunchAnimator
+import com.android.systemui.containeddrawable.ContainedDrawable
+import kotlin.reflect.KClass
+
+/** Models the UI state of a keyguard quick affordance button. */
+data class KeyguardQuickAffordanceViewModel(
+    val configKey: KClass<*>? = null,
+    val isVisible: Boolean = false,
+    val icon: ContainedDrawable = ContainedDrawable.WithResource(0),
+    @StringRes val contentDescriptionResourceId: Int = 0,
+    val onClicked: (OnClickedParameters) -> Unit = {},
+) {
+    data class OnClickedParameters(
+        val configKey: KClass<*>,
+        val animationController: ActivityLaunchAnimator.Controller?,
+    )
+}
diff --git a/packages/SystemUI/src/com/android/systemui/lifecycle/RepeatWhenAttached.kt b/packages/SystemUI/src/com/android/systemui/lifecycle/RepeatWhenAttached.kt
new file mode 100644
index 0000000..e364918
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/lifecycle/RepeatWhenAttached.kt
@@ -0,0 +1,183 @@
+/*
+ *  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.lifecycle
+
+import android.view.View
+import android.view.ViewTreeObserver
+import androidx.annotation.MainThread
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.LifecycleOwner
+import androidx.lifecycle.LifecycleRegistry
+import androidx.lifecycle.lifecycleScope
+import com.android.systemui.util.Assert
+import kotlin.coroutines.CoroutineContext
+import kotlin.coroutines.EmptyCoroutineContext
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.DisposableHandle
+import kotlinx.coroutines.launch
+
+/**
+ * Runs the given [block] every time the [View] becomes attached (or immediately after calling this
+ * function, if the view was already attached), automatically canceling the work when the `View`
+ * becomes detached.
+ *
+ * Only use from the main thread.
+ *
+ * When [block] is run, it is run in the context of a [ViewLifecycleOwner] which the caller can use
+ * to launch jobs, with confidence that the jobs will be properly canceled when the view is
+ * detached.
+ *
+ * The [block] may be run multiple times, running once per every time the view is attached. Each
+ * time the block is run for a new attachment event, the [ViewLifecycleOwner] provided will be a
+ * fresh one.
+ *
+ * @param coroutineContext An optional [CoroutineContext] to replace the dispatcher [block] is
+ * invoked on.
+ * @param block The block of code that should be run when the view becomes attached. It can end up
+ * being invoked multiple times if the view is reattached after being detached.
+ * @return A [DisposableHandle] to invoke when the caller of the function destroys its [View] and is
+ * no longer interested in the [block] being run the next time its attached. Calling this is an
+ * optional optimization as the logic will be properly cleaned up and destroyed each time the view
+ * is detached. Using this is not *thread-safe* and should only be used on the main thread.
+ */
+@MainThread
+fun View.repeatWhenAttached(
+    coroutineContext: CoroutineContext = EmptyCoroutineContext,
+    block: suspend LifecycleOwner.(View) -> Unit,
+): DisposableHandle {
+    Assert.isMainThread()
+    val view = this
+    // The suspend block will run on the app's main thread unless the caller supplies a different
+    // dispatcher to use. We don't want it to run on the Dispatchers.Default thread pool as
+    // default behavior. Instead, we want it to run on the view's UI thread since the user will
+    // presumably want to call view methods that require being called from said UI thread.
+    val lifecycleCoroutineContext = Dispatchers.Main + coroutineContext
+    var lifecycleOwner: ViewLifecycleOwner? = null
+    val onAttachListener =
+        object : View.OnAttachStateChangeListener {
+            override fun onViewAttachedToWindow(v: View?) {
+                Assert.isMainThread()
+                lifecycleOwner?.onDestroy()
+                lifecycleOwner =
+                    createLifecycleOwnerAndRun(
+                        view,
+                        lifecycleCoroutineContext,
+                        block,
+                    )
+            }
+
+            override fun onViewDetachedFromWindow(v: View?) {
+                lifecycleOwner?.onDestroy()
+                lifecycleOwner = null
+            }
+        }
+
+    addOnAttachStateChangeListener(onAttachListener)
+    if (view.isAttachedToWindow) {
+        lifecycleOwner =
+            createLifecycleOwnerAndRun(
+                view,
+                lifecycleCoroutineContext,
+                block,
+            )
+    }
+
+    return object : DisposableHandle {
+        override fun dispose() {
+            Assert.isMainThread()
+
+            lifecycleOwner?.onDestroy()
+            lifecycleOwner = null
+            view.removeOnAttachStateChangeListener(onAttachListener)
+        }
+    }
+}
+
+private fun createLifecycleOwnerAndRun(
+    view: View,
+    coroutineContext: CoroutineContext,
+    block: suspend LifecycleOwner.(View) -> Unit,
+): ViewLifecycleOwner {
+    return ViewLifecycleOwner(view).apply {
+        onCreate()
+        lifecycleScope.launch(coroutineContext) { block(view) }
+    }
+}
+
+/**
+ * A [LifecycleOwner] for a [View] for exclusive use by the [repeatWhenAttached] extension function.
+ *
+ * The implementation requires the caller to call [onCreate] and [onDestroy] when the view is
+ * attached to or detached from a view hierarchy. After [onCreate] and before [onDestroy] is called,
+ * the implementation monitors window state in the following way
+ *
+ * * If the window is not visible, we are in the [Lifecycle.State.CREATED] state
+ * * If the window is visible but not focused, we are in the [Lifecycle.State.STARTED] state
+ * * If the window is visible and focused, we are in the [Lifecycle.State.RESUMED] state
+ *
+ * Or in table format:
+ * ```
+ * ┌───────────────┬───────────────────┬──────────────┬─────────────────┐
+ * │ View attached │ Window Visibility │ Window Focus │ Lifecycle State │
+ * ├───────────────┼───────────────────┴──────────────┼─────────────────┤
+ * │ Not attached  │                 Any              │       N/A       │
+ * ├───────────────┼───────────────────┬──────────────┼─────────────────┤
+ * │               │    Not visible    │     Any      │     CREATED     │
+ * │               ├───────────────────┼──────────────┼─────────────────┤
+ * │   Attached    │                   │   No focus   │     STARTED     │
+ * │               │      Visible      ├──────────────┼─────────────────┤
+ * │               │                   │  Has focus   │     RESUMED     │
+ * └───────────────┴───────────────────┴──────────────┴─────────────────┘
+ * ```
+ */
+private class ViewLifecycleOwner(
+    private val view: View,
+) : LifecycleOwner {
+
+    private val windowVisibleListener =
+        ViewTreeObserver.OnWindowVisibilityChangeListener { updateState() }
+    private val windowFocusListener = ViewTreeObserver.OnWindowFocusChangeListener { updateState() }
+
+    private val registry = LifecycleRegistry(this)
+
+    fun onCreate() {
+        registry.currentState = Lifecycle.State.CREATED
+        view.viewTreeObserver.addOnWindowVisibilityChangeListener(windowVisibleListener)
+        view.viewTreeObserver.addOnWindowFocusChangeListener(windowFocusListener)
+        updateState()
+    }
+
+    fun onDestroy() {
+        view.viewTreeObserver.removeOnWindowVisibilityChangeListener(windowVisibleListener)
+        view.viewTreeObserver.removeOnWindowFocusChangeListener(windowFocusListener)
+        registry.currentState = Lifecycle.State.DESTROYED
+    }
+
+    override fun getLifecycle(): Lifecycle {
+        return registry
+    }
+
+    private fun updateState() {
+        registry.currentState =
+            when {
+                view.windowVisibility != View.VISIBLE -> Lifecycle.State.CREATED
+                !view.hasWindowFocus() -> Lifecycle.State.STARTED
+                else -> Lifecycle.State.RESUMED
+            }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/lifecycle/WindowAddedViewLifecycleOwner.kt b/packages/SystemUI/src/com/android/systemui/lifecycle/WindowAddedViewLifecycleOwner.kt
deleted file mode 100644
index 55c7ac9..0000000
--- a/packages/SystemUI/src/com/android/systemui/lifecycle/WindowAddedViewLifecycleOwner.kt
+++ /dev/null
@@ -1,114 +0,0 @@
-package com.android.systemui.lifecycle
-
-import android.view.View
-import android.view.ViewTreeObserver
-import androidx.lifecycle.Lifecycle
-import androidx.lifecycle.LifecycleOwner
-import androidx.lifecycle.LifecycleRegistry
-
-/**
- * [LifecycleOwner] for Window-added Views.
- *
- * These are [View] instances that are added to a `Window` using the `WindowManager` API.
- *
- * This implementation goes to:
- * * The <b>CREATED</b> `Lifecycle.State` when the view gets attached to the window but the window
- * is not yet visible
- * * The <b>STARTED</b> `Lifecycle.State` when the view is attached to the window and the window is
- * visible
- * * The <b>RESUMED</b> `Lifecycle.State` when the view is attached to the window and the window is
- * visible and the window receives focus
- *
- * In table format:
- * ```
- * | ----------------------------------------------------------------------------- |
- * | View attached to window | Window visible | Window has focus | Lifecycle state |
- * | ----------------------------------------------------------------------------- |
- * |       not attached      |               Any                 |   INITIALIZED   |
- * | ----------------------------------------------------------------------------- |
- * |                         |  not visible   |       Any        |     CREATED     |
- * |                         ----------------------------------------------------- |
- * |        attached         |                |    not focused   |     STARTED     |
- * |                         |   is visible   |----------------------------------- |
- * |                         |                |    has focus     |     RESUMED     |
- * | ----------------------------------------------------------------------------- |
- * ```
- * ### Notes
- * * [dispose] must be invoked when the [LifecycleOwner] is done and won't be reused
- * * It is always better for [LifecycleOwner] implementations to be more explicit than just
- * listening to the state of the `Window`. E.g. if the code that added the `View` to the `Window`
- * already has access to the correct state to know when that `View` should become visible and when
- * it is ready to receive interaction from the user then it already knows when to move to `STARTED`
- * and `RESUMED`, respectively. In that case, it's better to implement your own `LifecycleOwner`
- * instead of relying on the `Window` callbacks.
- */
-class WindowAddedViewLifecycleOwner
-@JvmOverloads
-constructor(
-    private val view: View,
-    registryFactory: (LifecycleOwner) -> LifecycleRegistry = { LifecycleRegistry(it) },
-) : LifecycleOwner {
-
-    private val windowAttachListener =
-        object : ViewTreeObserver.OnWindowAttachListener {
-            override fun onWindowAttached() {
-                updateCurrentState()
-            }
-
-            override fun onWindowDetached() {
-                updateCurrentState()
-            }
-        }
-    private val windowFocusListener =
-        ViewTreeObserver.OnWindowFocusChangeListener { updateCurrentState() }
-    private val windowVisibilityListener =
-        ViewTreeObserver.OnWindowVisibilityChangeListener { updateCurrentState() }
-
-    private val registry = registryFactory(this)
-
-    init {
-        setCurrentState(Lifecycle.State.INITIALIZED)
-
-        with(view.viewTreeObserver) {
-            addOnWindowAttachListener(windowAttachListener)
-            addOnWindowVisibilityChangeListener(windowVisibilityListener)
-            addOnWindowFocusChangeListener(windowFocusListener)
-        }
-
-        updateCurrentState()
-    }
-
-    override fun getLifecycle(): Lifecycle {
-        return registry
-    }
-
-    /**
-     * Disposes of this [LifecycleOwner], performing proper clean-up.
-     *
-     * <p>Invoke this when the instance is finished and won't be reused.
-     */
-    fun dispose() {
-        with(view.viewTreeObserver) {
-            removeOnWindowAttachListener(windowAttachListener)
-            removeOnWindowVisibilityChangeListener(windowVisibilityListener)
-            removeOnWindowFocusChangeListener(windowFocusListener)
-        }
-    }
-
-    private fun updateCurrentState() {
-        val state =
-            when {
-                !view.isAttachedToWindow -> Lifecycle.State.INITIALIZED
-                view.windowVisibility != View.VISIBLE -> Lifecycle.State.CREATED
-                !view.hasWindowFocus() -> Lifecycle.State.STARTED
-                else -> Lifecycle.State.RESUMED
-            }
-        setCurrentState(state)
-    }
-
-    private fun setCurrentState(state: Lifecycle.State) {
-        if (registry.currentState != state) {
-            registry.currentState = state
-        }
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/log/LogBuffer.kt b/packages/SystemUI/src/com/android/systemui/log/LogBuffer.kt
index db446c3..dc23684d 100644
--- a/packages/SystemUI/src/com/android/systemui/log/LogBuffer.kt
+++ b/packages/SystemUI/src/com/android/systemui/log/LogBuffer.kt
@@ -20,14 +20,19 @@
 import android.util.Log
 import com.android.systemui.log.dagger.LogModule
 import com.android.systemui.util.collection.RingBuffer
+import com.google.errorprone.annotations.CompileTimeConstant
 import java.io.PrintWriter
 import java.text.SimpleDateFormat
+import java.util.Arrays.stream
 import java.util.Locale
 import java.util.concurrent.ArrayBlockingQueue
 import java.util.concurrent.BlockingQueue
 import kotlin.concurrent.thread
 import kotlin.math.max
 
+const val UNBOUNDED_STACK_TRACE = -1
+const val NESTED_TRACE_DEPTH = 10
+
 /**
  * A simple ring buffer of recyclable log messages
  *
@@ -69,12 +74,18 @@
  * @param maxSize The maximum number of messages to keep in memory at any one time. Buffers start
  * out empty and grow up to [maxSize] as new messages are logged. Once the buffer's size reaches
  * the maximum, it behaves like a ring buffer.
+ * @param rootStackTraceDepth The number of stack trace elements to be logged for an exception when
+ * the logBuffer is dumped. Defaulted to -1 [UNBOUNDED_STACK_TRACE] to print the entire stack trace.
+ * @param nestedStackTraceDepth The number of stack trace elements to be logged for any nested
+ * exceptions present in [Throwable.cause] or [Throwable.suppressedExceptions].
  */
 class LogBuffer @JvmOverloads constructor(
     private val name: String,
     private val maxSize: Int,
     private val logcatEchoTracker: LogcatEchoTracker,
-    private val systrace: Boolean = true
+    private val systrace: Boolean = true,
+    private val rootStackTraceDepth: Int = UNBOUNDED_STACK_TRACE,
+    private val nestedStackTraceDepth: Int = NESTED_TRACE_DEPTH,
 ) {
     private val buffer = RingBuffer(maxSize) { LogMessageImpl.create() }
 
@@ -107,11 +118,11 @@
      * May also log the message to logcat if echoing is enabled for this buffer or tag.
      *
      * The actual string of the log message is not constructed until it is needed. To accomplish
-     * this, logging a message is a two-step process. First, a fresh instance  of [LogMessage] is
-     * obtained and is passed to the [initializer]. The initializer stores any relevant data on the
-     * message's fields. The message is then inserted into the buffer where it waits until it is
-     * either pushed out by newer messages or it needs to printed. If and when this latter moment
-     * occurs, the [printer] function is called on the message. It reads whatever data the
+     * this, logging a message is a two-step process. First, a fresh instance of [LogMessage] is
+     * obtained and is passed to the [messageInitializer]. The initializer stores any relevant data
+     * on the message's fields. The message is then inserted into the buffer where it waits until it
+     * is either pushed out by newer messages or it needs to printed. If and when this latter moment
+     * occurs, the [messagePrinter] function is called on the message. It reads whatever data the
      * initializer stored and converts it to a human-readable log message.
      *
      * @param tag A string of at most 23 characters, used for grouping logs into categories or
@@ -120,27 +131,49 @@
      * echoed. In general, a module should split most of its logs into either INFO or DEBUG level.
      * INFO level should be reserved for information that other parts of the system might care
      * about, leaving the specifics of code's day-to-day operations to DEBUG.
-     * @param initializer A function that will be called immediately to store relevant data on the
-     * log message. The value of `this` will be the LogMessage to be initialized.
-     * @param printer A function that will be called if and when the message needs to be dumped to
-     * logcat or a bug report. It should read the data stored by the initializer and convert it to
-     * a human-readable string. The value of `this` will be the LogMessage to be printed.
-     * **IMPORTANT:** The printer should ONLY ever reference fields on the LogMessage and NEVER any
-     * variables in its enclosing scope. Otherwise, the runtime will need to allocate a new instance
-     * of the printer for each call, thwarting our attempts at avoiding any sort of allocation.
+     * @param messageInitializer A function that will be called immediately to store relevant data
+     * on the log message. The value of `this` will be the LogMessage to be initialized.
+     * @param messagePrinter A function that will be called if and when the message needs to be
+     * dumped to logcat or a bug report. It should read the data stored by the initializer and
+     * convert it to a human-readable string. The value of `this` will be the LogMessage to be
+     * printed. **IMPORTANT:** The printer should ONLY ever reference fields on the LogMessage and
+     * NEVER any variables in its enclosing scope. Otherwise, the runtime will need to allocate a
+     * new instance of the printer for each call, thwarting our attempts at avoiding any sort of
+     * allocation.
+     * @param exception Provide any exception that need to be logged. This is saved as
+     * [LogMessage.exception]
      */
+    @JvmOverloads
     inline fun log(
-        tag: String,
-        level: LogLevel,
-        initializer: LogMessage.() -> Unit,
-        noinline printer: LogMessage.() -> String
+            tag: String,
+            level: LogLevel,
+            messageInitializer: MessageInitializer,
+            noinline messagePrinter: MessagePrinter,
+            exception: Throwable? = null,
     ) {
-        val message = obtain(tag, level, printer)
-        initializer(message)
+        val message = obtain(tag, level, messagePrinter, exception)
+        messageInitializer(message)
         commit(message)
     }
 
     /**
+     * Logs a compile-time string constant [message] to the log buffer. Use sparingly.
+     *
+     * May also log the message to logcat if echoing is enabled for this buffer or tag. This is for
+     * simpler use-cases where [message] is a compile time string constant. For use-cases where the
+     * log message is built during runtime, use the [LogBuffer.log] overloaded method that takes in
+     * an initializer and a message printer.
+     *
+     * Log buffers are limited by the number of entries, so logging more frequently
+     * will limit the time window that the LogBuffer covers in a bug report.  Richer logs, on the
+     * other hand, make a bug report more actionable, so using the [log] with a messagePrinter to
+     * add more detail to every log may do more to improve overall logging than adding more logs
+     * with this method.
+     */
+    fun log(tag: String, level: LogLevel, @CompileTimeConstant message: String) =
+            log(tag, level, {str1 = message}, { str1!! })
+
+    /**
      * You should call [log] instead of this method.
      *
      * Obtains the next [LogMessage] from the ring buffer. If the buffer is not yet at max size,
@@ -151,15 +184,16 @@
      */
     @Synchronized
     fun obtain(
-        tag: String,
-        level: LogLevel,
-        printer: (LogMessage) -> String
-    ): LogMessageImpl {
+            tag: String,
+            level: LogLevel,
+            messagePrinter: MessagePrinter,
+            exception: Throwable? = null,
+    ): LogMessage {
         if (!mutable) {
             return FROZEN_MESSAGE
         }
         val message = buffer.advance()
-        message.reset(tag, level, System.currentTimeMillis(), printer)
+        message.reset(tag, level, System.currentTimeMillis(), messagePrinter, exception)
         return message
     }
 
@@ -230,19 +264,79 @@
         }
     }
 
-    private fun dumpMessage(message: LogMessage, pw: PrintWriter) {
-        pw.print(DATE_FORMAT.format(message.timestamp))
+    private fun dumpMessage(
+        message: LogMessage,
+        pw: PrintWriter
+    ) {
+        val formattedTimestamp = DATE_FORMAT.format(message.timestamp)
+        val shortLevel = message.level.shortString
+        val messageToPrint = message.messagePrinter(message)
+        val tag = message.tag
+        printLikeLogcat(pw, formattedTimestamp, shortLevel, tag, messageToPrint)
+        message.exception?.let { ex ->
+            printException(
+                pw,
+                formattedTimestamp,
+                shortLevel,
+                ex,
+                tag,
+                stackTraceDepth = rootStackTraceDepth)
+        }
+    }
+
+    private fun printException(
+            pw: PrintWriter,
+            timestamp: String,
+            level: String,
+            exception: Throwable,
+            tag: String,
+            exceptionMessagePrefix: String = "",
+            stackTraceDepth: Int = UNBOUNDED_STACK_TRACE
+    ) {
+        val message = "$exceptionMessagePrefix$exception"
+        printLikeLogcat(pw, timestamp, level, tag, message)
+        var stacktraceStream = stream(exception.stackTrace)
+        if (stackTraceDepth != UNBOUNDED_STACK_TRACE) {
+            stacktraceStream = stacktraceStream.limit(stackTraceDepth.toLong())
+        }
+        stacktraceStream.forEach { line ->
+            printLikeLogcat(pw, timestamp, level, tag, "\tat $line")
+        }
+        exception.cause?.let { cause ->
+            printException(pw, timestamp, level, cause, tag, "Caused by: ", nestedStackTraceDepth)
+        }
+        exception.suppressedExceptions.forEach { suppressed ->
+            printException(
+                pw,
+                timestamp,
+                level,
+                suppressed,
+                tag,
+                "Suppressed: ",
+                nestedStackTraceDepth
+            )
+        }
+    }
+
+    private fun printLikeLogcat(
+        pw: PrintWriter,
+        formattedTimestamp: String,
+        shortLogLevel: String,
+        tag: String,
+        message: String
+    ) {
+        pw.print(formattedTimestamp)
         pw.print(" ")
-        pw.print(message.level.shortString)
+        pw.print(shortLogLevel)
         pw.print(" ")
-        pw.print(message.tag)
+        pw.print(tag)
         pw.print(": ")
-        pw.println(message.printer(message))
+        pw.println(message)
     }
 
     private fun echo(message: LogMessage, toLogcat: Boolean, toSystrace: Boolean) {
         if (toLogcat || toSystrace) {
-            val strMessage = message.printer(message)
+            val strMessage = message.messagePrinter(message)
             if (toSystrace) {
                 echoToSystrace(message, strMessage)
             }
@@ -259,16 +353,22 @@
 
     private fun echoToLogcat(message: LogMessage, strMessage: String) {
         when (message.level) {
-            LogLevel.VERBOSE -> Log.v(message.tag, strMessage)
-            LogLevel.DEBUG -> Log.d(message.tag, strMessage)
-            LogLevel.INFO -> Log.i(message.tag, strMessage)
-            LogLevel.WARNING -> Log.w(message.tag, strMessage)
-            LogLevel.ERROR -> Log.e(message.tag, strMessage)
-            LogLevel.WTF -> Log.wtf(message.tag, strMessage)
+            LogLevel.VERBOSE -> Log.v(message.tag, strMessage, message.exception)
+            LogLevel.DEBUG -> Log.d(message.tag, strMessage, message.exception)
+            LogLevel.INFO -> Log.i(message.tag, strMessage, message.exception)
+            LogLevel.WARNING -> Log.w(message.tag, strMessage, message.exception)
+            LogLevel.ERROR -> Log.e(message.tag, strMessage, message.exception)
+            LogLevel.WTF -> Log.wtf(message.tag, strMessage, message.exception)
         }
     }
 }
 
+/**
+ * A function that will be called immediately to store relevant data on the log message. The value
+ * of `this` will be the LogMessage to be initialized.
+ */
+typealias MessageInitializer = LogMessage.() -> Unit
+
 private const val TAG = "LogBuffer"
 private val DATE_FORMAT = SimpleDateFormat("MM-dd HH:mm:ss.SSS", Locale.US)
-private val FROZEN_MESSAGE = LogMessageImpl.create()
\ No newline at end of file
+private val FROZEN_MESSAGE = LogMessageImpl.create()
diff --git a/packages/SystemUI/src/com/android/systemui/log/LogMessage.kt b/packages/SystemUI/src/com/android/systemui/log/LogMessage.kt
index 2a0a2aa6..987aea8 100644
--- a/packages/SystemUI/src/com/android/systemui/log/LogMessage.kt
+++ b/packages/SystemUI/src/com/android/systemui/log/LogMessage.kt
@@ -25,7 +25,7 @@
  *
  * When a message is logged, the code doing the logging stores data in one or more of the generic
  * fields ([str1], [int1], etc). When it comes time to dump the message to logcat/bugreport/etc, the
- * [printer] function reads the data stored in the generic fields and converts that to a human-
+ * [messagePrinter] function reads the data stored in the generic fields and converts that to a human-
  * readable string. Thus, for every log type there must be a specialized initializer function that
  * stores data specific to that log type and a specialized printer function that prints that data.
  *
@@ -35,7 +35,8 @@
     val level: LogLevel
     val tag: String
     val timestamp: Long
-    val printer: LogMessage.() -> String
+    val messagePrinter: MessagePrinter
+    val exception: Throwable?
 
     var str1: String?
     var str2: String?
@@ -50,3 +51,13 @@
     var bool3: Boolean
     var bool4: Boolean
 }
+
+/**
+ * A function that will be called if and when the message needs to be dumped to
+ * logcat or a bug report. It should read the data stored by the initializer and convert it to
+ * a human-readable string. The value of `this` will be the LogMessage to be printed.
+ * **IMPORTANT:** The printer should ONLY ever reference fields on the LogMessage and NEVER any
+ * variables in its enclosing scope. Otherwise, the runtime will need to allocate a new instance
+ * of the printer for each call, thwarting our attempts at avoiding any sort of allocation.
+ */
+typealias MessagePrinter = LogMessage.() -> String
diff --git a/packages/SystemUI/src/com/android/systemui/log/LogMessageImpl.kt b/packages/SystemUI/src/com/android/systemui/log/LogMessageImpl.kt
index d33ac4b..4dd6f65 100644
--- a/packages/SystemUI/src/com/android/systemui/log/LogMessageImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/log/LogMessageImpl.kt
@@ -23,7 +23,8 @@
     override var level: LogLevel,
     override var tag: String,
     override var timestamp: Long,
-    override var printer: LogMessage.() -> String,
+    override var messagePrinter: MessagePrinter,
+    override var exception: Throwable?,
     override var str1: String?,
     override var str2: String?,
     override var str3: String?,
@@ -35,19 +36,21 @@
     override var bool1: Boolean,
     override var bool2: Boolean,
     override var bool3: Boolean,
-    override var bool4: Boolean
+    override var bool4: Boolean,
 ) : LogMessage {
 
     fun reset(
         tag: String,
         level: LogLevel,
         timestamp: Long,
-        renderer: LogMessage.() -> String
+        renderer: MessagePrinter,
+        exception: Throwable? = null,
     ) {
         this.level = level
         this.tag = tag
         this.timestamp = timestamp
-        this.printer = renderer
+        this.messagePrinter = renderer
+        this.exception = exception
         str1 = null
         str2 = null
         str3 = null
@@ -68,7 +71,8 @@
                     LogLevel.DEBUG,
                     DEFAULT_TAG,
                     0,
-                    DEFAULT_RENDERER,
+                    DEFAULT_PRINTER,
+                    null,
                     null,
                     null,
                     null,
@@ -86,4 +90,4 @@
 }
 
 private const val DEFAULT_TAG = "UnknownTag"
-private val DEFAULT_RENDERER: LogMessage.() -> String = { "Unknown message: $this" }
+private val DEFAULT_PRINTER: MessagePrinter = { "Unknown message: $this" }
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/KeyguardUpdateMonitorLog.kt b/packages/SystemUI/src/com/android/systemui/log/dagger/KeyguardUpdateMonitorLog.kt
new file mode 100644
index 0000000..323ee21
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/KeyguardUpdateMonitorLog.kt
@@ -0,0 +1,4 @@
+package com.android.systemui.log.dagger
+
+/** A [com.android.systemui.log.LogBuffer] for KeyguardUpdateMonitor. */
+annotation class KeyguardUpdateMonitorLog
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
index d0da18a..c858bc3 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
@@ -255,6 +255,16 @@
         return factory.create("MediaCarouselCtlrLog", 20);
     }
 
+    /**
+     * Provides a {@link LogBuffer} for use in the status bar connectivity pipeline
+     */
+    @Provides
+    @SysUISingleton
+    @StatusBarConnectivityLog
+    public static LogBuffer provideStatusBarConnectivityBuffer(LogBufferFactory factory) {
+        return factory.create("StatusBarConnectivityLog", 64);
+    }
+
     /** Allows logging buffers to be tweaked via adb on debug builds but not on prod builds. */
     @Provides
     @SysUISingleton
@@ -277,4 +287,14 @@
     public static LogBuffer provideStatusBarNetworkControllerBuffer(LogBufferFactory factory) {
         return factory.create("StatusBarNetworkControllerLog", 20);
     }
+
+    /**
+     * Provides a {@link LogBuffer} for use by {@link com.android.keyguard.KeyguardUpdateMonitor}.
+     */
+    @Provides
+    @SysUISingleton
+    @KeyguardUpdateMonitorLog
+    public static LogBuffer provideKeyguardUpdateMonitorLogBuffer(LogBufferFactory factory) {
+        return factory.create("KeyguardUpdateMonitorLog", 200);
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/StatusBarConnectivityLog.java b/packages/SystemUI/src/com/android/systemui/log/dagger/StatusBarConnectivityLog.java
new file mode 100644
index 0000000..f03fbcb
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/StatusBarConnectivityLog.java
@@ -0,0 +1,36 @@
+/*
+ * 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.log.dagger;
+
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import com.android.systemui.log.LogBuffer;
+import com.android.systemui.statusbar.pipeline.ConnectivityInfoProcessor;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+
+import javax.inject.Qualifier;
+
+/**
+ * A {@link LogBuffer} for events processed by {@link ConnectivityInfoProcessor}
+ */
+@Qualifier
+@Documented
+@Retention(RUNTIME)
+public @interface StatusBarConnectivityLog {
+}
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttChipControllerCommon.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttChipControllerCommon.kt
index c9fce79..79e1fb9 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttChipControllerCommon.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttChipControllerCommon.kt
@@ -33,11 +33,13 @@
 import android.view.accessibility.AccessibilityManager.FLAG_CONTENT_CONTROLS
 import android.view.accessibility.AccessibilityManager.FLAG_CONTENT_ICONS
 import android.view.accessibility.AccessibilityManager.FLAG_CONTENT_TEXT
+import androidx.annotation.CallSuper
 import com.android.internal.widget.CachingIconView
 import com.android.settingslib.Utils
 import com.android.systemui.R
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.statusbar.gesture.TapGestureDetector
+import com.android.systemui.statusbar.policy.ConfigurationController
 import com.android.systemui.util.concurrency.DelayableExecutor
 import com.android.systemui.util.view.ViewUtil
 
@@ -52,17 +54,17 @@
  * display the chip in a certain state, since they receive <T> in [updateChipView].
  */
 abstract class MediaTttChipControllerCommon<T : ChipInfoCommon>(
-    internal val context: Context,
-    internal val logger: MediaTttLogger,
-    internal val windowManager: WindowManager,
-    private val viewUtil: ViewUtil,
-    @Main private val mainExecutor: DelayableExecutor,
-    private val accessibilityManager: AccessibilityManager,
-    private val tapGestureDetector: TapGestureDetector,
-    private val powerManager: PowerManager,
-    @LayoutRes private val chipLayoutRes: Int
+        internal val context: Context,
+        internal val logger: MediaTttLogger,
+        internal val windowManager: WindowManager,
+        private val viewUtil: ViewUtil,
+        @Main private val mainExecutor: DelayableExecutor,
+        private val accessibilityManager: AccessibilityManager,
+        private val configurationController: ConfigurationController,
+        private val tapGestureDetector: TapGestureDetector,
+        private val powerManager: PowerManager,
+        @LayoutRes private val chipLayoutRes: Int,
 ) {
-
     /**
      * Window layout params that will be used as a starting point for the [windowLayoutParams] of
      * all subclasses.
@@ -89,42 +91,40 @@
     /** The chip view currently being displayed. Null if the chip is not being displayed. */
     private var chipView: ViewGroup? = null
 
+    /** The chip info currently being displayed. Null if the chip is not being displayed. */
+    internal var chipInfo: T? = null
+
     /** A [Runnable] that, when run, will cancel the pending timeout of the chip. */
     private var cancelChipViewTimeout: Runnable? = null
 
     /**
-     * Displays the chip with the current state.
+     * Displays the chip with the provided [newChipInfo].
      *
      * This method handles inflating and attaching the view, then delegates to [updateChipView] to
      * display the correct information in the chip.
      */
-    fun displayChip(chipInfo: T) {
-        val oldChipView = chipView
-        if (chipView == null) {
-            chipView = LayoutInflater
-                .from(context)
-                .inflate(chipLayoutRes, null) as ViewGroup
-        }
-        val currentChipView = chipView!!
+    fun displayChip(newChipInfo: T) {
+        val currentChipView = chipView
 
-        updateChipView(chipInfo, currentChipView)
-
-        // Add view if necessary
-        if (oldChipView == null) {
+        if (currentChipView != null) {
+            updateChipView(newChipInfo, currentChipView)
+        } else {
+            // The chip is new, so set up all our callbacks and inflate the view
+            configurationController.addCallback(displayScaleListener)
             tapGestureDetector.addOnGestureDetectedCallback(TAG, this::onScreenTapped)
-            windowManager.addView(chipView, windowLayoutParams)
             // Wake the screen so the user will see the chip
             powerManager.wakeUp(
                 SystemClock.uptimeMillis(),
                 PowerManager.WAKE_REASON_APPLICATION,
                 "com.android.systemui:media_tap_to_transfer_activated"
             )
-            animateChipIn(currentChipView)
+
+            inflateAndUpdateChip(newChipInfo)
         }
 
         // Cancel and re-set the chip timeout each time we get a new state.
         val timeout = accessibilityManager.getRecommendedTimeoutMillis(
-            chipInfo.getTimeoutMs().toInt(),
+            newChipInfo.getTimeoutMs().toInt(),
             // Not all chips have controls so FLAG_CONTENT_CONTROLS might be superfluous, but
             // include it just to be safe.
             FLAG_CONTENT_ICONS or FLAG_CONTENT_TEXT or FLAG_CONTENT_CONTROLS
@@ -136,6 +136,32 @@
         )
     }
 
+    /** Inflates a new chip view, updates it with [newChipInfo], and adds the view to the window. */
+    private fun inflateAndUpdateChip(newChipInfo: T) {
+        val newChipView = LayoutInflater
+                .from(context)
+                .inflate(chipLayoutRes, null) as ViewGroup
+        chipView = newChipView
+        updateChipView(newChipInfo, newChipView)
+        windowManager.addView(newChipView, windowLayoutParams)
+        animateChipIn(newChipView)
+    }
+
+    /** Removes then re-inflates the chip. */
+    private fun reinflateChip() {
+        val currentChipInfo = chipInfo
+        if (chipView == null || currentChipInfo == null) { return }
+
+        windowManager.removeView(chipView)
+        inflateAndUpdateChip(currentChipInfo)
+    }
+
+    private val displayScaleListener = object : ConfigurationController.ConfigurationListener {
+        override fun onDensityOrFontScaleChanged() {
+            reinflateChip()
+        }
+    }
+
     /**
      * Hides the chip.
      *
@@ -145,17 +171,22 @@
     open fun removeChip(removalReason: String) {
         if (chipView == null) { return }
         logger.logChipRemoval(removalReason)
+        configurationController.removeCallback(displayScaleListener)
         tapGestureDetector.removeOnGestureDetectedCallback(TAG)
         windowManager.removeView(chipView)
         chipView = null
+        chipInfo = null
         // No need to time the chip out since it's already gone
         cancelChipViewTimeout?.run()
     }
 
     /**
-     * A method implemented by subclasses to update [currentChipView] based on [chipInfo].
+     * A method implemented by subclasses to update [currentChipView] based on [newChipInfo].
      */
-    abstract fun updateChipView(chipInfo: T, currentChipView: ViewGroup)
+    @CallSuper
+    open fun updateChipView(newChipInfo: T, currentChipView: ViewGroup) {
+        chipInfo = newChipInfo
+    }
 
     /**
      * A method that can be implemented by subclcasses to do custom animations for when the chip
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 99a5b8b..f0e5a3a 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
@@ -42,6 +42,7 @@
 import com.android.systemui.media.taptotransfer.common.MediaTttLogger
 import com.android.systemui.statusbar.CommandQueue
 import com.android.systemui.statusbar.gesture.TapGestureDetector
+import com.android.systemui.statusbar.policy.ConfigurationController
 import com.android.systemui.util.animation.AnimationUtil.Companion.frames
 import com.android.systemui.util.concurrency.DelayableExecutor
 import com.android.systemui.util.view.ViewUtil
@@ -54,27 +55,29 @@
  */
 @SysUISingleton
 class MediaTttChipControllerReceiver @Inject constructor(
-    commandQueue: CommandQueue,
-    context: Context,
-    @MediaTttReceiverLogger logger: MediaTttLogger,
-    windowManager: WindowManager,
-    viewUtil: ViewUtil,
-    mainExecutor: DelayableExecutor,
-    accessibilityManager: AccessibilityManager,
-    tapGestureDetector: TapGestureDetector,
-    powerManager: PowerManager,
-    @Main private val mainHandler: Handler,
-    private val uiEventLogger: MediaTttReceiverUiEventLogger,
+        commandQueue: CommandQueue,
+        context: Context,
+        @MediaTttReceiverLogger logger: MediaTttLogger,
+        windowManager: WindowManager,
+        viewUtil: ViewUtil,
+        mainExecutor: DelayableExecutor,
+        accessibilityManager: AccessibilityManager,
+        configurationController: ConfigurationController,
+        tapGestureDetector: TapGestureDetector,
+        powerManager: PowerManager,
+        @Main private val mainHandler: Handler,
+        private val uiEventLogger: MediaTttReceiverUiEventLogger,
 ) : MediaTttChipControllerCommon<ChipReceiverInfo>(
-    context,
-    logger,
-    windowManager,
-    viewUtil,
-    mainExecutor,
-    accessibilityManager,
-    tapGestureDetector,
-    powerManager,
-    R.layout.media_ttt_chip_receiver
+        context,
+        logger,
+        windowManager,
+        viewUtil,
+        mainExecutor,
+        accessibilityManager,
+        configurationController,
+        tapGestureDetector,
+        powerManager,
+        R.layout.media_ttt_chip_receiver,
 ) {
     @SuppressLint("WrongConstant") // We're allowed to use LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS
     override val windowLayoutParams = commonWindowLayoutParams.apply {
@@ -140,12 +143,13 @@
         )
     }
 
-    override fun updateChipView(chipInfo: ChipReceiverInfo, currentChipView: ViewGroup) {
+    override fun updateChipView(newChipInfo: ChipReceiverInfo, currentChipView: ViewGroup) {
+        super.updateChipView(newChipInfo, currentChipView)
         setIcon(
                 currentChipView,
-                chipInfo.routeInfo.packageName,
-                chipInfo.appIconDrawableOverride,
-                chipInfo.appNameOverride
+                newChipInfo.routeInfo.packageName,
+                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 797a770..540798a 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
@@ -39,6 +39,7 @@
 import com.android.systemui.media.taptotransfer.common.MediaTttRemovalReason
 import com.android.systemui.statusbar.CommandQueue
 import com.android.systemui.statusbar.gesture.TapGestureDetector
+import com.android.systemui.statusbar.policy.ConfigurationController
 import com.android.systemui.util.concurrency.DelayableExecutor
 import com.android.systemui.util.view.ViewUtil
 import javax.inject.Inject
@@ -49,33 +50,33 @@
  */
 @SysUISingleton
 class MediaTttChipControllerSender @Inject constructor(
-    commandQueue: CommandQueue,
-    context: Context,
-    @MediaTttSenderLogger logger: MediaTttLogger,
-    windowManager: WindowManager,
-    viewUtil: ViewUtil,
-    @Main mainExecutor: DelayableExecutor,
-    accessibilityManager: AccessibilityManager,
-    tapGestureDetector: TapGestureDetector,
-    powerManager: PowerManager,
-    private val uiEventLogger: MediaTttSenderUiEventLogger
+        commandQueue: CommandQueue,
+        context: Context,
+        @MediaTttSenderLogger logger: MediaTttLogger,
+        windowManager: WindowManager,
+        viewUtil: ViewUtil,
+        @Main mainExecutor: DelayableExecutor,
+        accessibilityManager: AccessibilityManager,
+        configurationController: ConfigurationController,
+        tapGestureDetector: TapGestureDetector,
+        powerManager: PowerManager,
+        private val uiEventLogger: MediaTttSenderUiEventLogger
 ) : MediaTttChipControllerCommon<ChipSenderInfo>(
-    context,
-    logger,
-    windowManager,
-    viewUtil,
-    mainExecutor,
-    accessibilityManager,
-    tapGestureDetector,
-    powerManager,
-    R.layout.media_ttt_chip
+        context,
+        logger,
+        windowManager,
+        viewUtil,
+        mainExecutor,
+        accessibilityManager,
+        configurationController,
+        tapGestureDetector,
+        powerManager,
+        R.layout.media_ttt_chip,
 ) {
     override val windowLayoutParams = commonWindowLayoutParams.apply {
         gravity = Gravity.TOP.or(Gravity.CENTER_HORIZONTAL)
     }
 
-    private var currentlyDisplayedChipState: ChipStateSender? = null
-
     private val commandQueueCallbacks = object : CommandQueue.Callbacks {
         override fun updateMediaTapToTransferSenderDisplay(
                 @StatusBarManager.MediaTransferSenderState displayState: Int,
@@ -116,16 +117,18 @@
 
     /** Displays the chip view for the given state. */
     override fun updateChipView(
-            chipInfo: ChipSenderInfo,
-            currentChipView: ViewGroup) {
-        val chipState = chipInfo.state
-        currentlyDisplayedChipState = chipState
+            newChipInfo: ChipSenderInfo,
+            currentChipView: ViewGroup
+    ) {
+        super.updateChipView(newChipInfo, currentChipView)
+
+        val chipState = newChipInfo.state
 
         // App icon
-        setIcon(currentChipView, chipInfo.routeInfo.packageName)
+        setIcon(currentChipView, newChipInfo.routeInfo.packageName)
 
         // Text
-        val otherDeviceName = chipInfo.routeInfo.name.toString()
+        val otherDeviceName = newChipInfo.routeInfo.name.toString()
         currentChipView.requireViewById<TextView>(R.id.text).apply {
             text = chipState.getChipTextString(context, otherDeviceName)
         }
@@ -137,7 +140,7 @@
         // Undo
         val undoView = currentChipView.requireViewById<View>(R.id.undo)
         val undoClickListener = chipState.undoClickListener(
-                this, chipInfo.routeInfo, chipInfo.undoCallback, uiEventLogger
+                this, newChipInfo.routeInfo, newChipInfo.undoCallback, uiEventLogger
         )
         undoView.setOnClickListener(undoClickListener)
         undoView.visibility = (undoClickListener != null).visibleIfTrue()
@@ -161,12 +164,11 @@
     override fun removeChip(removalReason: String) {
         // Don't remove the chip if we're mid-transfer since the user should still be able to
         // see the status of the transfer. (But do remove it if it's finally timed out.)
-        if (currentlyDisplayedChipState?.isMidTransfer == true
-                && removalReason != MediaTttRemovalReason.REASON_TIMEOUT) {
+        if (chipInfo?.state?.isMidTransfer == true &&
+                removalReason != MediaTttRemovalReason.REASON_TIMEOUT) {
             return
         }
         super.removeChip(removalReason)
-        currentlyDisplayedChipState = null
     }
 
     private fun Boolean.visibleIfTrue(): Int {
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java
index 2a7c871..2d7a809 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java
@@ -51,6 +51,8 @@
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.dump.DumpManager;
+import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.flags.Flags;
 import com.android.systemui.model.SysUiState;
 import com.android.systemui.recents.OverviewProxyService;
 import com.android.systemui.shared.system.QuickStepContract;
@@ -82,6 +84,7 @@
     private final Context mContext;
     private final Handler mHandler;
     private final NavigationBarComponent.Factory mNavigationBarComponentFactory;
+    private FeatureFlags mFeatureFlags;
     private final DisplayManager mDisplayManager;
     private final TaskbarDelegate mTaskbarDelegate;
     private final StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
@@ -113,10 +116,12 @@
             AutoHideController autoHideController,
             LightBarController lightBarController,
             Optional<Pip> pipOptional,
-            Optional<BackAnimation> backAnimation) {
+            Optional<BackAnimation> backAnimation,
+            FeatureFlags featureFlags) {
         mContext = context;
         mHandler = mainHandler;
         mNavigationBarComponentFactory = navigationBarComponentFactory;
+        mFeatureFlags = featureFlags;
         mDisplayManager = mContext.getSystemService(DisplayManager.class);
         commandQueue.addCallback(this);
         configurationController.addCallback(this);
@@ -217,7 +222,10 @@
 
     /** @return {@code true} if taskbar is enabled, false otherwise */
     private boolean initializeTaskbarIfNecessary() {
-        if (mIsTablet) {
+        // Enable for tablet or (phone AND flag is set); assuming phone = !mIsTablet
+        boolean taskbarEnabled = mIsTablet || mFeatureFlags.isEnabled(Flags.HIDE_NAVBAR_WINDOW);
+
+        if (taskbarEnabled) {
             Trace.beginSection("NavigationBarController#initializeTaskbarIfNecessary");
             // Remove navigation bar when taskbar is showing
             removeNavigationBar(mContext.getDisplayId());
@@ -226,7 +234,7 @@
         } else {
             mTaskbarDelegate.destroy();
         }
-        return mIsTablet;
+        return taskbarEnabled;
     }
 
     @Override
@@ -294,6 +302,10 @@
      */
     @VisibleForTesting
     void createNavigationBar(Display display, Bundle savedState, RegisterStatusBarResult result) {
+        if (initializeTaskbarIfNecessary()) {
+            return;
+        }
+
         if (display == null) {
             return;
         }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/carrier/QSCarrierGroupController.java b/packages/SystemUI/src/com/android/systemui/qs/carrier/QSCarrierGroupController.java
index 6908e5a..209d09d 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/carrier/QSCarrierGroupController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/carrier/QSCarrierGroupController.java
@@ -243,7 +243,7 @@
             mActivityStarter.postStartActivityDismissingKeyguard(
                     new Intent(Settings.ACTION_WIRELESS_SETTINGS), 0);
         };
-        view.setOnClickListener(onClickListener);
+
         mNoSimTextView = view.getNoSimTextView();
         mNoSimTextView.setOnClickListener(onClickListener);
         mMainHandler = new H(mainLooper, this::handleUpdateCarrierInfo, this::handleUpdateState);
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index 24448bb..58e9bb8 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -128,6 +128,10 @@
 import com.android.systemui.fragments.FragmentHostManager.FragmentListener;
 import com.android.systemui.fragments.FragmentService;
 import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
+import com.android.systemui.keyguard.domain.usecase.SetClockPositionUseCase;
+import com.android.systemui.keyguard.domain.usecase.SetKeyguardBottomAreaAlphaUseCase;
+import com.android.systemui.keyguard.domain.usecase.SetKeyguardBottomAreaAnimateDozingTransitionsUseCase;
+import com.android.systemui.keyguard.ui.viewmodel.KeyguardBottomAreaViewModel;
 import com.android.systemui.media.KeyguardMediaController;
 import com.android.systemui.media.MediaDataManager;
 import com.android.systemui.media.MediaHierarchyManager;
@@ -697,6 +701,12 @@
     };
 
     private final CameraGestureHelper mCameraGestureHelper;
+    private final Provider<KeyguardBottomAreaViewModel> mKeyguardBottomAreaViewModelProvider;
+    private final Provider<SetClockPositionUseCase> mSetClockPositionUseCaseProvider;
+    private final Provider<SetKeyguardBottomAreaAlphaUseCase>
+            mSetKeyguardBottomAreaAlphaUseCaseProvider;
+    private final Provider<SetKeyguardBottomAreaAnimateDozingTransitionsUseCase>
+            mSetKeyguardBottomAreaAnimateDozingTransitionsUseCaseProvider;
 
     @Inject
     public NotificationPanelViewController(NotificationPanelView view,
@@ -767,7 +777,12 @@
             UnlockedScreenOffAnimationController unlockedScreenOffAnimationController,
             ShadeTransitionController shadeTransitionController,
             SystemClock systemClock,
-            CameraGestureHelper cameraGestureHelper) {
+            CameraGestureHelper cameraGestureHelper,
+            Provider<KeyguardBottomAreaViewModel> keyguardBottomAreaViewModelProvider,
+            Provider<SetClockPositionUseCase> setClockPositionUseCaseProvider,
+            Provider<SetKeyguardBottomAreaAlphaUseCase> setKeyguardBottomAreaAlphaUseCaseProvider,
+            Provider<SetKeyguardBottomAreaAnimateDozingTransitionsUseCase>
+                    setKeyguardBottomAreaAnimateDozingTransitionsUseCaseProvider) {
         super(view,
                 falsingManager,
                 dozeLog,
@@ -897,6 +912,7 @@
 
         mQsFrameTranslateController = qsFrameTranslateController;
         updateUserSwitcherFlags();
+        mKeyguardBottomAreaViewModelProvider = keyguardBottomAreaViewModelProvider;
         onFinishInflate();
         keyguardUnlockAnimationController.addKeyguardUnlockAnimationListener(
                 new KeyguardUnlockAnimationController.KeyguardUnlockAnimationListener() {
@@ -950,6 +966,10 @@
                     }
                 });
         mCameraGestureHelper = cameraGestureHelper;
+        mSetClockPositionUseCaseProvider = setClockPositionUseCaseProvider;
+        mSetKeyguardBottomAreaAlphaUseCaseProvider = setKeyguardBottomAreaAlphaUseCaseProvider;
+        mSetKeyguardBottomAreaAnimateDozingTransitionsUseCaseProvider =
+                setKeyguardBottomAreaAnimateDozingTransitionsUseCaseProvider;
     }
 
     @VisibleForTesting
@@ -1274,11 +1294,17 @@
     }
 
     private void initBottomArea() {
-        mKeyguardBottomArea.init(
-                mFalsingManager,
-                mQuickAccessWalletController,
-                mControlsComponent,
-                mQRCodeScannerController);
+        if (mFeatureFlags.isEnabled(Flags.MODERN_BOTTOM_AREA)) {
+            mKeyguardBottomArea.init(mKeyguardBottomAreaViewModelProvider.get(), mFalsingManager);
+        } else {
+            // TODO(b/235403546): remove this method call when the new implementation is complete
+            //  and these are not needed.
+            mKeyguardBottomArea.init(
+                    mFalsingManager,
+                    mQuickAccessWalletController,
+                    mControlsComponent,
+                    mQRCodeScannerController);
+        }
     }
 
     @VisibleForTesting
@@ -1454,6 +1480,8 @@
                 mKeyguardStatusViewController.getClockBottom(mStatusBarHeaderHeightKeyguard),
                 mKeyguardStatusViewController.isClockTopAligned());
         mClockPositionAlgorithm.run(mClockPositionResult);
+        mSetClockPositionUseCaseProvider.get().invoke(
+                mClockPositionResult.clockX, mClockPositionResult.clockY);
         boolean animate = mNotificationStackScrollLayoutController.isAddOrRemoveAnimationPending();
         boolean animateClock = (animate || mAnimateNextPositionUpdate) && shouldAnimateClockChange;
         mKeyguardStatusViewController.updatePosition(
@@ -3220,6 +3248,7 @@
         float alpha = Math.min(expansionAlpha, 1 - computeQsExpansionFraction());
         alpha *= mBottomAreaShadeAlpha;
         mKeyguardBottomArea.setComponentAlphas(alpha);
+        mSetKeyguardBottomAreaAlphaUseCaseProvider.get().invoke(alpha);
         mLockIconViewController.setAlpha(alpha);
     }
 
@@ -3419,6 +3448,7 @@
 
     private void updateDozingVisibilities(boolean animate) {
         mKeyguardBottomArea.setDozing(mDozing, animate);
+        mSetKeyguardBottomAreaAnimateDozingTransitionsUseCaseProvider.get().invoke(animate);
         if (!mDozing && animate) {
             mKeyguardStatusBarViewController.animateKeyguardStatusBarIn();
         }
@@ -3721,6 +3751,7 @@
         mDozing = dozing;
         mNotificationStackScrollLayoutController.setDozing(mDozing, animate, wakeUpTouchLocation);
         mKeyguardBottomArea.setDozing(mDozing, animate);
+        mSetKeyguardBottomAreaAnimateDozingTransitionsUseCaseProvider.get().invoke(animate);
         mKeyguardStatusBarViewController.setDozing(mDozing);
 
         if (dozing) {
@@ -3850,29 +3881,37 @@
     }
 
     /**
-     * Starts fold to AOD animation
+     * Starts fold to AOD animation.
+     *
+     * @param startAction invoked when the animation starts.
+     * @param endAction invoked when the animation finishes, also if it was cancelled.
+     * @param cancelAction invoked when the animation is cancelled, before endAction.
      */
-    public void startFoldToAodAnimation(Runnable endAction) {
+    public void startFoldToAodAnimation(Runnable startAction, Runnable endAction,
+            Runnable cancelAction) {
         mView.animate()
-                .translationX(0)
-                .alpha(1f)
-                .setDuration(ANIMATION_DURATION_FOLD_TO_AOD)
-                .setInterpolator(EMPHASIZED_DECELERATE)
-                .setListener(new AnimatorListenerAdapter() {
-                    @Override
-                    public void onAnimationCancel(Animator animation) {
-                        endAction.run();
-                    }
+            .translationX(0)
+            .alpha(1f)
+            .setDuration(ANIMATION_DURATION_FOLD_TO_AOD)
+            .setInterpolator(EMPHASIZED_DECELERATE)
+            .setListener(new AnimatorListenerAdapter() {
+                @Override
+                public void onAnimationStart(Animator animation) {
+                    startAction.run();
+                }
 
-                    @Override
-                    public void onAnimationEnd(Animator animation) {
-                        endAction.run();
-                    }
-                })
-                .setUpdateListener(anim -> {
-                    mKeyguardStatusViewController.animateFoldToAod(anim.getAnimatedFraction());
-                })
-                .start();
+                @Override
+                public void onAnimationCancel(Animator animation) {
+                    cancelAction.run();
+                }
+
+                @Override
+                public void onAnimationEnd(Animator animation) {
+                    endAction.run();
+                }
+            }).setUpdateListener(anim -> {
+                mKeyguardStatusViewController.animateFoldToAod(anim.getAnimatedFraction());
+            }).start();
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java
index 4b71b2c..15e1129 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java
@@ -44,8 +44,6 @@
 import android.view.WindowManager.LayoutParams;
 import android.view.WindowManagerGlobal;
 
-import androidx.lifecycle.ViewTreeLifecycleOwner;
-
 import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.systemui.Dumpable;
 import com.android.systemui.R;
@@ -54,7 +52,6 @@
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.keyguard.KeyguardViewMediator;
-import com.android.systemui.lifecycle.WindowAddedViewLifecycleOwner;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.plugins.statusbar.StatusBarStateController.StateListener;
 import com.android.systemui.statusbar.NotificationShadeWindowController;
@@ -251,15 +248,6 @@
 
         mWindowManager.addView(mNotificationShadeView, mLp);
 
-        // Set up and "inject" a LifecycleOwner bound to the Window-View relationship such that all
-        // views in the sub-tree rooted under this view can access the LifecycleOwner using
-        // ViewTreeLifecycleOwner.get(...).
-        if (ViewTreeLifecycleOwner.get(mNotificationShadeView) == null) {
-            ViewTreeLifecycleOwner.set(
-                    mNotificationShadeView,
-                    new WindowAddedViewLifecycleOwner(mNotificationShadeView));
-        }
-
         mLpChanged.copyFrom(mLp);
         onThemeChanged();
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationPresenter.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationPresenter.java
index 3b3b5a2..cb414ba 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationPresenter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationPresenter.java
@@ -50,16 +50,6 @@
     boolean isDeviceInVrMode();
 
     /**
-     * Updates the visual representation of the notifications.
-     */
-    void updateNotificationViews(String reason);
-
-    /**
-     * Called when the row states are updated by {@link NotificationViewHierarchyManager}.
-     */
-    void onUpdateRowStates();
-
-    /**
      * @return true if the shade is collapsing.
      */
     boolean isCollapsing();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java
deleted file mode 100644
index 054543c..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java
+++ /dev/null
@@ -1,631 +0,0 @@
-/*
- * Copyright (C) 2017 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;
-
-import static com.android.systemui.statusbar.notification.stack.NotificationPriorityBucketKt.BUCKET_SILENT;
-
-import android.content.Context;
-import android.content.res.Resources;
-import android.os.Handler;
-import android.os.Trace;
-import android.os.UserHandle;
-import android.util.Log;
-import android.view.View;
-import android.view.ViewGroup;
-
-import com.android.keyguard.KeyguardUpdateMonitor;
-import com.android.systemui.R;
-import com.android.systemui.dagger.qualifiers.Main;
-import com.android.systemui.flags.FeatureFlags;
-import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.statusbar.dagger.CentralSurfacesModule;
-import com.android.systemui.statusbar.notification.AssistantFeedbackController;
-import com.android.systemui.statusbar.notification.DynamicChildBindController;
-import com.android.systemui.statusbar.notification.DynamicPrivacyController;
-import com.android.systemui.statusbar.notification.NotifPipelineFlags;
-import com.android.systemui.statusbar.notification.NotificationEntryManager;
-import com.android.systemui.statusbar.notification.collection.NotificationEntry;
-import com.android.systemui.statusbar.notification.collection.legacy.LowPriorityInflationHelper;
-import com.android.systemui.statusbar.notification.collection.legacy.NotificationGroupManagerLegacy;
-import com.android.systemui.statusbar.notification.collection.legacy.VisualStabilityManager;
-import com.android.systemui.statusbar.notification.collection.render.NotifStackController;
-import com.android.systemui.statusbar.notification.collection.render.NotifStats;
-import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
-import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
-import com.android.systemui.statusbar.phone.KeyguardBypassController;
-import com.android.systemui.statusbar.policy.KeyguardStateController;
-import com.android.systemui.util.Assert;
-import com.android.wm.shell.bubbles.Bubbles;
-
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Optional;
-import java.util.Stack;
-
-/**
- * NotificationViewHierarchyManager manages updating the view hierarchy of notification views based
- * on their group structure. For example, if a notification becomes bundled with another,
- * NotificationViewHierarchyManager will update the view hierarchy to reflect that. It also will
- * tell NotificationListContainer which notifications to display, and inform it of changes to those
- * notifications that might affect their display.
- */
-public class NotificationViewHierarchyManager implements DynamicPrivacyController.Listener {
-    private static final String TAG = "NotificationViewHierarchyManager";
-
-    private final Handler mHandler;
-
-    /**
-     * Re-usable map of top-level notifications to their sorted children if any.
-     * If the top-level notification doesn't have children, its key will still exist in this map
-     * with its value explicitly set to null.
-     */
-    private final HashMap<NotificationEntry, List<NotificationEntry>> mTmpChildOrderMap =
-            new HashMap<>();
-
-    // Dependencies:
-    private final DynamicChildBindController mDynamicChildBindController;
-    private final FeatureFlags mFeatureFlags;
-    protected final NotificationLockscreenUserManager mLockscreenUserManager;
-    protected final NotificationGroupManagerLegacy mGroupManager;
-    protected final VisualStabilityManager mVisualStabilityManager;
-    private final SysuiStatusBarStateController mStatusBarStateController;
-    private final NotificationEntryManager mEntryManager;
-    private final LowPriorityInflationHelper mLowPriorityInflationHelper;
-
-    /**
-     * {@code true} if notifications not part of a group should by default be rendered in their
-     * expanded state. If {@code false}, then only the first notification will be expanded if
-     * possible.
-     */
-    private final boolean mAlwaysExpandNonGroupedNotification;
-    private final Optional<Bubbles> mBubblesOptional;
-    private final DynamicPrivacyController mDynamicPrivacyController;
-    private final KeyguardBypassController mBypassController;
-    private final NotifPipelineFlags mNotifPipelineFlags;
-    private AssistantFeedbackController mAssistantFeedbackController;
-    private final KeyguardStateController mKeyguardStateController;
-    private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
-    private final Context mContext;
-
-    private NotificationPresenter mPresenter;
-    private NotifStackController mStackController;
-    private NotificationListContainer mListContainer;
-
-    // Used to help track down re-entrant calls to our update methods, which will cause bugs.
-    private boolean mPerformingUpdate;
-    // Hack to get around re-entrant call in onDynamicPrivacyChanged() until we can track down
-    // the problem.
-    private boolean mIsHandleDynamicPrivacyChangeScheduled;
-
-    /**
-     * Injected constructor. See {@link CentralSurfacesModule}.
-     */
-    public NotificationViewHierarchyManager(
-            Context context,
-            @Main Handler mainHandler,
-            FeatureFlags featureFlags,
-            NotificationLockscreenUserManager notificationLockscreenUserManager,
-            NotificationGroupManagerLegacy groupManager,
-            VisualStabilityManager visualStabilityManager,
-            StatusBarStateController statusBarStateController,
-            NotificationEntryManager notificationEntryManager,
-            KeyguardBypassController bypassController,
-            Optional<Bubbles> bubblesOptional,
-            DynamicPrivacyController privacyController,
-            DynamicChildBindController dynamicChildBindController,
-            LowPriorityInflationHelper lowPriorityInflationHelper,
-            AssistantFeedbackController assistantFeedbackController,
-            NotifPipelineFlags notifPipelineFlags,
-            KeyguardUpdateMonitor keyguardUpdateMonitor,
-            KeyguardStateController keyguardStateController) {
-        mContext = context;
-        mHandler = mainHandler;
-        mFeatureFlags = featureFlags;
-        mLockscreenUserManager = notificationLockscreenUserManager;
-        mBypassController = bypassController;
-        mGroupManager = groupManager;
-        mVisualStabilityManager = visualStabilityManager;
-        mStatusBarStateController = (SysuiStatusBarStateController) statusBarStateController;
-        mEntryManager = notificationEntryManager;
-        mNotifPipelineFlags = notifPipelineFlags;
-        Resources res = context.getResources();
-        mAlwaysExpandNonGroupedNotification =
-                res.getBoolean(R.bool.config_alwaysExpandNonGroupedNotifications);
-        mBubblesOptional = bubblesOptional;
-        mDynamicPrivacyController = privacyController;
-        mDynamicChildBindController = dynamicChildBindController;
-        mLowPriorityInflationHelper = lowPriorityInflationHelper;
-        mAssistantFeedbackController = assistantFeedbackController;
-        mKeyguardUpdateMonitor = keyguardUpdateMonitor;
-        mKeyguardStateController = keyguardStateController;
-    }
-
-    public void setUpWithPresenter(NotificationPresenter presenter,
-            NotifStackController stackController,
-            NotificationListContainer listContainer) {
-        mPresenter = presenter;
-        mStackController = stackController;
-        mListContainer = listContainer;
-        if (!mNotifPipelineFlags.isNewPipelineEnabled()) {
-            mDynamicPrivacyController.addListener(this);
-        }
-    }
-
-    /**
-     * Updates the visual representation of the notifications.
-     */
-    //TODO: Rewrite this to focus on Entries, or some other data object instead of views
-    public void updateNotificationViews() {
-        Assert.isMainThread();
-        if (!mNotifPipelineFlags.checkLegacyPipelineEnabled()) {
-            return;
-        }
-        Trace.beginSection("NotificationViewHierarchyManager.updateNotificationViews");
-
-        beginUpdate();
-
-        boolean dynamicallyUnlocked = mDynamicPrivacyController.isDynamicallyUnlocked()
-                && !(mStatusBarStateController.getState() == StatusBarState.KEYGUARD
-                && mKeyguardUpdateMonitor.getUserUnlockedWithBiometricAndIsBypassing(
-                KeyguardUpdateMonitor.getCurrentUser()))
-                && !mKeyguardStateController.isKeyguardGoingAway();
-        List<NotificationEntry> activeNotifications = mEntryManager.getVisibleNotifications();
-        ArrayList<ExpandableNotificationRow> toShow = new ArrayList<>(activeNotifications.size());
-        final int N = activeNotifications.size();
-        for (int i = 0; i < N; i++) {
-            NotificationEntry ent = activeNotifications.get(i);
-            if (shouldSuppressActiveNotification(ent)) {
-                continue;
-            }
-
-            int userId = ent.getSbn().getUserId();
-
-            // Display public version of the notification if we need to redact.
-            // TODO: This area uses a lot of calls into NotificationLockscreenUserManager.
-            // We can probably move some of this code there.
-            int currentUserId = mLockscreenUserManager.getCurrentUserId();
-            boolean devicePublic = mLockscreenUserManager.isLockscreenPublicMode(currentUserId);
-            boolean userPublic = devicePublic
-                    || mLockscreenUserManager.isLockscreenPublicMode(userId);
-            if (userPublic && dynamicallyUnlocked
-                    && (userId == currentUserId || userId == UserHandle.USER_ALL
-                    || !mLockscreenUserManager.needsSeparateWorkChallenge(userId))) {
-                userPublic = false;
-            }
-            boolean needsRedaction = mLockscreenUserManager.needsRedaction(ent);
-            boolean sensitive = userPublic && needsRedaction;
-            boolean deviceSensitive = devicePublic
-                    && !mLockscreenUserManager.userAllowsPrivateNotificationsInPublic(
-                    currentUserId);
-            ent.setSensitive(sensitive, deviceSensitive);
-            ent.getRow().setNeedsRedaction(needsRedaction);
-            mLowPriorityInflationHelper.recheckLowPriorityViewAndInflate(ent, ent.getRow());
-            boolean isChildInGroup = mGroupManager.isChildInGroup(ent);
-
-            boolean groupChangesAllowed =
-                    mVisualStabilityManager.areGroupChangesAllowed() // user isn't looking at notifs
-                    || !ent.hasFinishedInitialization(); // notif recently added
-
-            NotificationEntry parent = mGroupManager.getGroupSummary(ent);
-            if (!groupChangesAllowed) {
-                // We don't to change groups while the user is looking at them
-                boolean wasChildInGroup = ent.isChildInGroup();
-                if (isChildInGroup && !wasChildInGroup) {
-                    isChildInGroup = wasChildInGroup;
-                    mVisualStabilityManager.addGroupChangesAllowedCallback(mEntryManager,
-                            false /* persistent */);
-                } else if (!isChildInGroup && wasChildInGroup) {
-                    // We allow grouping changes if the group was collapsed
-                    if (mGroupManager.isLogicalGroupExpanded(ent.getSbn())) {
-                        isChildInGroup = wasChildInGroup;
-                        parent = ent.getRow().getNotificationParent().getEntry();
-                        mVisualStabilityManager.addGroupChangesAllowedCallback(mEntryManager,
-                                false /* persistent */);
-                    }
-                }
-            }
-
-            if (isChildInGroup) {
-                List<NotificationEntry> orderedChildren = mTmpChildOrderMap.get(parent);
-                if (orderedChildren == null) {
-                    orderedChildren = new ArrayList<>();
-                    mTmpChildOrderMap.put(parent, orderedChildren);
-                }
-                orderedChildren.add(ent);
-            } else {
-                // Top-level notif (either a summary or single notification)
-
-                // A child may have already added its summary to mTmpChildOrderMap with a
-                // list of children. This can happen since there's no guarantee summaries are
-                // sorted before its children.
-                if (!mTmpChildOrderMap.containsKey(ent)) {
-                    // mTmpChildOrderMap's keyset is used to iterate through all entries, so it's
-                    // necessary to add each top-level notif as a key
-                    mTmpChildOrderMap.put(ent, null);
-                }
-                toShow.add(ent.getRow());
-            }
-
-        }
-
-        ArrayList<ExpandableNotificationRow> viewsToRemove = new ArrayList<>();
-        for (int i=0; i< mListContainer.getContainerChildCount(); i++) {
-            View child = mListContainer.getContainerChildAt(i);
-            if (!toShow.contains(child) && child instanceof ExpandableNotificationRow) {
-                ExpandableNotificationRow row = (ExpandableNotificationRow) child;
-
-                // Blocking helper is effectively a detached view. Don't bother removing it from the
-                // layout.
-                if (!row.isBlockingHelperShowing()) {
-                    viewsToRemove.add((ExpandableNotificationRow) child);
-                }
-            }
-        }
-
-        for (ExpandableNotificationRow viewToRemove : viewsToRemove) {
-            NotificationEntry entry = viewToRemove.getEntry();
-            if (mEntryManager.getPendingOrActiveNotif(entry.getKey()) != null
-                && !shouldSuppressActiveNotification(entry)) {
-                // we are only transferring this notification to its parent, don't generate an
-                // animation. If the notification is suppressed, this isn't a transfer.
-                mListContainer.setChildTransferInProgress(true);
-            }
-            if (viewToRemove.isSummaryWithChildren()) {
-                viewToRemove.removeAllChildren();
-            }
-            mListContainer.removeContainerView(viewToRemove);
-            mListContainer.setChildTransferInProgress(false);
-        }
-
-        removeNotificationChildren();
-
-        for (int i = 0; i < toShow.size(); i++) {
-            View v = toShow.get(i);
-            if (v.getParent() == null) {
-                mVisualStabilityManager.notifyViewAddition(v);
-                mListContainer.addContainerView(v);
-            } else if (!mListContainer.containsView(v)) {
-                // the view is added somewhere else. Let's make sure
-                // the ordering works properly below, by excluding these
-                toShow.remove(v);
-                i--;
-            }
-        }
-
-        addNotificationChildrenAndSort();
-
-        // So after all this work notifications still aren't sorted correctly.
-        // Let's do that now by advancing through toShow and mListContainer in
-        // lock-step, making sure mListContainer matches what we see in toShow.
-        int j = 0;
-        for (int i = 0; i < mListContainer.getContainerChildCount(); i++) {
-            View child = mListContainer.getContainerChildAt(i);
-            if (!(child instanceof ExpandableNotificationRow)) {
-                // We don't care about non-notification views.
-                continue;
-            }
-            if (((ExpandableNotificationRow) child).isBlockingHelperShowing()) {
-                // Don't count/reorder notifications that are showing the blocking helper!
-                continue;
-            }
-
-            ExpandableNotificationRow targetChild = toShow.get(j);
-            if (child != targetChild) {
-                // Oops, wrong notification at this position. Put the right one
-                // here and advance both lists.
-                if (mVisualStabilityManager.canReorderNotification(targetChild)) {
-                    mListContainer.changeViewPosition(targetChild, i);
-                } else {
-                    mVisualStabilityManager.addReorderingAllowedCallback(mEntryManager,
-                            false  /* persistent */);
-                }
-            }
-            j++;
-
-        }
-
-        mDynamicChildBindController.updateContentViews(mTmpChildOrderMap);
-        mVisualStabilityManager.onReorderingFinished();
-        // clear the map again for the next usage
-        mTmpChildOrderMap.clear();
-
-        updateRowStatesInternal();
-        updateNotifStats();
-
-        mListContainer.onNotificationViewUpdateFinished();
-
-        endUpdate();
-        Trace.endSection();
-    }
-
-    /**
-     * In the spirit of unidirectional data flow, calculate this information when the notification
-     * views are updated, and set it once, speeding up lookups later.
-     * This is analogous to logic in the
-     * {@link com.android.systemui.statusbar.notification.collection.coordinator.StackCoordinator}
-     */
-    private void updateNotifStats() {
-        Trace.beginSection("NotificationViewHierarchyManager.updateNotifStats");
-        boolean hasNonClearableAlertingNotifs = false;
-        boolean hasClearableAlertingNotifs = false;
-        boolean hasNonClearableSilentNotifs = false;
-        boolean hasClearableSilentNotifs = false;
-        final int childCount = mListContainer.getContainerChildCount();
-        int visibleTopLevelEntries = 0;
-        for (int i = 0; i < childCount; i++) {
-            View child = mListContainer.getContainerChildAt(i);
-            if (child == null || child.getVisibility() == View.GONE) {
-                continue;
-            }
-            if (!(child instanceof ExpandableNotificationRow)) {
-                continue;
-            }
-            final ExpandableNotificationRow row = (ExpandableNotificationRow) child;
-            boolean isSilent = row.getEntry().getBucket() == BUCKET_SILENT;
-            // NOTE: NotificationEntry.isClearable() will internally check group children to ensure
-            //  the group itself definitively clearable.
-            boolean isClearable = row.getEntry().isClearable();
-            visibleTopLevelEntries++;
-            if (isSilent) {
-                if (isClearable) {
-                    hasClearableSilentNotifs = true;
-                } else {  // !isClearable
-                    hasNonClearableSilentNotifs = true;
-                }
-            } else {  // !isSilent
-                if (isClearable) {
-                    hasClearableAlertingNotifs = true;
-                } else {  // !isClearable
-                    hasNonClearableAlertingNotifs = true;
-                }
-            }
-        }
-        mStackController.setNotifStats(new NotifStats(
-                visibleTopLevelEntries /* numActiveNotifs */,
-                hasNonClearableAlertingNotifs /* hasNonClearableAlertingNotifs */,
-                hasClearableAlertingNotifs /* hasClearableAlertingNotifs */,
-                hasNonClearableSilentNotifs /* hasNonClearableSilentNotifs */,
-                hasClearableSilentNotifs /* hasClearableSilentNotifs */
-        ));
-        Trace.endSection();
-    }
-
-    /**
-     * Should a notification entry from the active list be suppressed and not show?
-     */
-    private boolean shouldSuppressActiveNotification(NotificationEntry ent) {
-        final boolean isBubbleNotificationSuppressedFromShade = mBubblesOptional.isPresent()
-                && mBubblesOptional.get().isBubbleNotificationSuppressedFromShade(
-                        ent.getKey(), ent.getSbn().getGroupKey());
-        if (ent.isRowDismissed() || ent.isRowRemoved()
-                || isBubbleNotificationSuppressedFromShade) {
-            // we want to suppress removed notifications because they could
-            // temporarily become children if they were isolated before.
-            return true;
-        }
-        return false;
-    }
-
-    private void addNotificationChildrenAndSort() {
-        // Let's now add all notification children which are missing
-        boolean orderChanged = false;
-        ArrayList<ExpandableNotificationRow> orderedRows = new ArrayList<>();
-        for (int i = 0; i < mListContainer.getContainerChildCount(); i++) {
-            View view = mListContainer.getContainerChildAt(i);
-            if (!(view instanceof ExpandableNotificationRow)) {
-                // We don't care about non-notification views.
-                continue;
-            }
-
-            ExpandableNotificationRow parent = (ExpandableNotificationRow) view;
-            List<ExpandableNotificationRow> children = parent.getAttachedChildren();
-            List<NotificationEntry> orderedChildren = mTmpChildOrderMap.get(parent.getEntry());
-            if (orderedChildren == null) {
-                // Not a group
-                continue;
-            }
-            parent.setUntruncatedChildCount(orderedChildren.size());
-            for (int childIndex = 0; childIndex < orderedChildren.size(); childIndex++) {
-                ExpandableNotificationRow childView = orderedChildren.get(childIndex).getRow();
-                if (children == null || !children.contains(childView)) {
-                    if (childView.getParent() != null) {
-                        Log.wtf(TAG, "trying to add a notification child that already has "
-                                + "a parent. class:" + childView.getParent().getClass()
-                                + "\n child: " + childView);
-                        // This shouldn't happen. We can recover by removing it though.
-                        ((ViewGroup) childView.getParent()).removeView(childView);
-                    }
-                    mVisualStabilityManager.notifyViewAddition(childView);
-                    parent.addChildNotification(childView, childIndex);
-                    mListContainer.notifyGroupChildAdded(childView);
-                }
-                orderedRows.add(childView);
-            }
-
-            // Finally after removing and adding has been performed we can apply the order.
-            orderChanged |= parent.applyChildOrder(orderedRows, mVisualStabilityManager,
-                    mEntryManager);
-            orderedRows.clear();
-        }
-        if (orderChanged) {
-            mListContainer.generateChildOrderChangedEvent();
-        }
-    }
-
-    private void removeNotificationChildren() {
-        // First let's remove all children which don't belong in the parents
-        ArrayList<ExpandableNotificationRow> toRemove = new ArrayList<>();
-        for (int i = 0; i < mListContainer.getContainerChildCount(); i++) {
-            View view = mListContainer.getContainerChildAt(i);
-            if (!(view instanceof ExpandableNotificationRow)) {
-                // We don't care about non-notification views.
-                continue;
-            }
-
-            ExpandableNotificationRow parent = (ExpandableNotificationRow) view;
-            List<ExpandableNotificationRow> children = parent.getAttachedChildren();
-            List<NotificationEntry> orderedChildren = mTmpChildOrderMap.get(parent.getEntry());
-
-            if (children != null) {
-                toRemove.clear();
-                for (ExpandableNotificationRow childRow : children) {
-                    if ((orderedChildren == null
-                            || !orderedChildren.contains(childRow.getEntry()))
-                            && !childRow.keepInParent()) {
-                        toRemove.add(childRow);
-                    }
-                }
-                for (ExpandableNotificationRow remove : toRemove) {
-                    parent.removeChildNotification(remove);
-                    if (mEntryManager.getActiveNotificationUnfiltered(
-                            remove.getEntry().getSbn().getKey()) == null) {
-                        // We only want to add an animation if the view is completely removed
-                        // otherwise it's just a transfer
-                        mListContainer.notifyGroupChildRemoved(remove,
-                                parent.getChildrenContainer());
-                    }
-                }
-            }
-        }
-    }
-
-    /**
-     * Updates expanded, dimmed and locked states of notification rows.
-     */
-    public void updateRowStates() {
-        Assert.isMainThread();
-        if (!mNotifPipelineFlags.checkLegacyPipelineEnabled()) {
-            return;
-        }
-
-        beginUpdate();
-        updateRowStatesInternal();
-        endUpdate();
-    }
-
-    private void updateRowStatesInternal() {
-        Trace.beginSection("NotificationViewHierarchyManager.updateRowStates");
-        final int N = mListContainer.getContainerChildCount();
-
-        int visibleNotifications = 0;
-        boolean onKeyguard =
-                mStatusBarStateController.getCurrentOrUpcomingState() == StatusBarState.KEYGUARD;
-        Stack<ExpandableNotificationRow> stack = new Stack<>();
-        for (int i = N - 1; i >= 0; i--) {
-            View child = mListContainer.getContainerChildAt(i);
-            if (!(child instanceof ExpandableNotificationRow)) {
-                continue;
-            }
-            stack.push((ExpandableNotificationRow) child);
-        }
-        while(!stack.isEmpty()) {
-            ExpandableNotificationRow row = stack.pop();
-            NotificationEntry entry = row.getEntry();
-            boolean isChildNotification = mGroupManager.isChildInGroup(entry);
-
-            if (!onKeyguard) {
-                // If mAlwaysExpandNonGroupedNotification is false, then only expand the
-                // very first notification and if it's not a child of grouped notifications.
-                row.setSystemExpanded(mAlwaysExpandNonGroupedNotification
-                        || (visibleNotifications == 0 && !isChildNotification
-                        && !row.isLowPriority()));
-            }
-
-            int userId = entry.getSbn().getUserId();
-            boolean suppressedSummary = mGroupManager.isSummaryOfSuppressedGroup(
-                    entry.getSbn()) && !entry.isRowRemoved();
-            boolean showOnKeyguard = mLockscreenUserManager.shouldShowOnKeyguard(entry);
-            if (!showOnKeyguard) {
-                // min priority notifications should show if their summary is showing
-                if (mGroupManager.isChildInGroup(entry)) {
-                    NotificationEntry summary = mGroupManager.getLogicalGroupSummary(entry);
-                    if (summary != null && mLockscreenUserManager.shouldShowOnKeyguard(summary)) {
-                        showOnKeyguard = true;
-                    }
-                }
-            }
-            if (suppressedSummary
-                    || mLockscreenUserManager.shouldHideNotifications(userId)
-                    || (onKeyguard && !showOnKeyguard)) {
-                entry.getRow().setVisibility(View.GONE);
-            } else {
-                boolean wasGone = entry.getRow().getVisibility() == View.GONE;
-                if (wasGone) {
-                    entry.getRow().setVisibility(View.VISIBLE);
-                }
-                if (!isChildNotification && !entry.getRow().isRemoved()) {
-                    if (wasGone) {
-                        // notify the scroller of a child addition
-                        mListContainer.generateAddAnimation(entry.getRow(),
-                                !showOnKeyguard /* fromMoreCard */);
-                    }
-                    visibleNotifications++;
-                }
-            }
-            if (row.isSummaryWithChildren()) {
-                List<ExpandableNotificationRow> notificationChildren =
-                        row.getAttachedChildren();
-                int size = notificationChildren.size();
-                for (int i = size - 1; i >= 0; i--) {
-                    stack.push(notificationChildren.get(i));
-                }
-            }
-            row.setFeedbackIcon(mAssistantFeedbackController.getFeedbackIcon(entry));
-            row.setLastAudiblyAlertedMs(entry.getLastAudiblyAlertedMs());
-        }
-
-        Trace.beginSection("NotificationPresenter#onUpdateRowStates");
-        mPresenter.onUpdateRowStates();
-        Trace.endSection();
-        Trace.endSection();
-    }
-
-    @Override
-    public void onDynamicPrivacyChanged() {
-        mNotifPipelineFlags.assertLegacyPipelineEnabled();
-        if (mPerformingUpdate) {
-            Log.w(TAG, "onDynamicPrivacyChanged made a re-entrant call");
-        }
-        // This listener can be called from updateNotificationViews() via a convoluted listener
-        // chain, so we post here to prevent a re-entrant call. See b/136186188
-        // TODO: Refactor away the need for this
-        if (!mIsHandleDynamicPrivacyChangeScheduled) {
-            mIsHandleDynamicPrivacyChangeScheduled = true;
-            mHandler.post(this::onHandleDynamicPrivacyChanged);
-        }
-    }
-
-    private void onHandleDynamicPrivacyChanged() {
-        mIsHandleDynamicPrivacyChangeScheduled = false;
-        updateNotificationViews();
-    }
-
-    private void beginUpdate() {
-        if (mPerformingUpdate) {
-            Log.wtf(TAG, "Re-entrant code during update", new Exception());
-        }
-        mPerformingUpdate = true;
-    }
-
-    private void endUpdate() {
-        if (!mPerformingUpdate) {
-            Log.wtf(TAG, "Manager state has become desynced", new Exception());
-        }
-        mPerformingUpdate = false;
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java
index 9e77dbc..48e3450 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java
@@ -23,13 +23,11 @@
 
 import com.android.internal.jank.InteractionJankMonitor;
 import com.android.internal.statusbar.IStatusBarService;
-import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.systemui.animation.ActivityLaunchAnimator;
 import com.android.systemui.animation.DialogLaunchAnimator;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.dump.DumpManager;
-import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.media.MediaDataManager;
 import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
@@ -42,23 +40,16 @@
 import com.android.systemui.statusbar.NotificationMediaManager;
 import com.android.systemui.statusbar.NotificationRemoteInputManager;
 import com.android.systemui.statusbar.NotificationShadeWindowController;
-import com.android.systemui.statusbar.NotificationViewHierarchyManager;
 import com.android.systemui.statusbar.RemoteInputNotificationRebuilder;
 import com.android.systemui.statusbar.SmartReplyController;
 import com.android.systemui.statusbar.StatusBarStateControllerImpl;
 import com.android.systemui.statusbar.SysuiStatusBarStateController;
 import com.android.systemui.statusbar.commandline.CommandRegistry;
 import com.android.systemui.statusbar.gesture.SwipeStatusBarAwayGestureHandler;
-import com.android.systemui.statusbar.notification.AssistantFeedbackController;
-import com.android.systemui.statusbar.notification.DynamicChildBindController;
-import com.android.systemui.statusbar.notification.DynamicPrivacyController;
 import com.android.systemui.statusbar.notification.NotifPipelineFlags;
 import com.android.systemui.statusbar.notification.NotificationEntryManager;
 import com.android.systemui.statusbar.notification.collection.NotifCollection;
 import com.android.systemui.statusbar.notification.collection.NotifPipeline;
-import com.android.systemui.statusbar.notification.collection.legacy.LowPriorityInflationHelper;
-import com.android.systemui.statusbar.notification.collection.legacy.NotificationGroupManagerLegacy;
-import com.android.systemui.statusbar.notification.collection.legacy.VisualStabilityManager;
 import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection;
 import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider;
 import com.android.systemui.statusbar.phone.CentralSurfaces;
@@ -73,13 +64,11 @@
 import com.android.systemui.statusbar.phone.ongoingcall.OngoingCallController;
 import com.android.systemui.statusbar.phone.ongoingcall.OngoingCallFlags;
 import com.android.systemui.statusbar.phone.ongoingcall.OngoingCallLogger;
-import com.android.systemui.statusbar.policy.KeyguardStateController;
 import com.android.systemui.statusbar.policy.RemoteInputUriController;
 import com.android.systemui.statusbar.window.StatusBarWindowController;
 import com.android.systemui.tracing.ProtoTracer;
 import com.android.systemui.util.concurrency.DelayableExecutor;
 import com.android.systemui.util.time.SystemClock;
-import com.android.wm.shell.bubbles.Bubbles;
 
 import java.util.Optional;
 import java.util.concurrent.Executor;
@@ -182,47 +171,6 @@
     NotificationRemoteInputManager.Callback provideNotificationRemoteInputManagerCallback(
             StatusBarRemoteInputCallback callbackImpl);
 
-    /** */
-    @SysUISingleton
-    @Provides
-    static NotificationViewHierarchyManager provideNotificationViewHierarchyManager(
-            Context context,
-            @Main Handler mainHandler,
-            FeatureFlags featureFlags,
-            NotificationLockscreenUserManager notificationLockscreenUserManager,
-            NotificationGroupManagerLegacy groupManager,
-            VisualStabilityManager visualStabilityManager,
-            StatusBarStateController statusBarStateController,
-            NotificationEntryManager notificationEntryManager,
-            KeyguardBypassController bypassController,
-            Optional<Bubbles> bubblesOptional,
-            DynamicPrivacyController privacyController,
-            DynamicChildBindController dynamicChildBindController,
-            LowPriorityInflationHelper lowPriorityInflationHelper,
-            AssistantFeedbackController assistantFeedbackController,
-            NotifPipelineFlags notifPipelineFlags,
-            KeyguardUpdateMonitor keyguardUpdateMonitor,
-            KeyguardStateController keyguardStateController) {
-        return new NotificationViewHierarchyManager(
-                context,
-                mainHandler,
-                featureFlags,
-                notificationLockscreenUserManager,
-                groupManager,
-                visualStabilityManager,
-                statusBarStateController,
-                notificationEntryManager,
-                bypassController,
-                bubblesOptional,
-                privacyController,
-                dynamicChildBindController,
-                lowPriorityInflationHelper,
-                assistantFeedbackController,
-                notifPipelineFlags,
-                keyguardUpdateMonitor,
-                keyguardStateController);
-    }
-
     /**
      * Provides our instance of CommandQueue which is considered optional.
      */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotifPipelineFlags.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotifPipelineFlags.kt
index 0c6a91f..7fbdd35 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotifPipelineFlags.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotifPipelineFlags.kt
@@ -29,10 +29,6 @@
     val featureFlags: FeatureFlags
 ) {
     fun checkLegacyPipelineEnabled(): Boolean {
-        if (!isNewPipelineEnabled()) {
-            return true
-        }
-
         if (Compile.IS_DEBUG) {
             Toast.makeText(context, "Old pipeline code running!", Toast.LENGTH_SHORT).show()
         }
@@ -45,11 +41,6 @@
         return false
     }
 
-    fun assertLegacyPipelineEnabled(): Unit =
-        check(!isNewPipelineEnabled()) { "Old pipeline code running w/ new pipeline enabled" }
-
-    fun isNewPipelineEnabled(): Boolean = true
-
     fun isDevLoggingEnabled(): Boolean =
         featureFlags.isEnabled(Flags.NOTIFICATION_PIPELINE_DEVELOPER_LOGGING)
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java
index 852d5f7..7583a98 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java
@@ -46,11 +46,9 @@
 import com.android.systemui.statusbar.NotificationLifetimeExtender;
 import com.android.systemui.statusbar.NotificationListener;
 import com.android.systemui.statusbar.NotificationListener.NotificationHandler;
-import com.android.systemui.statusbar.NotificationPresenter;
 import com.android.systemui.statusbar.NotificationRemoteInputManager;
 import com.android.systemui.statusbar.NotificationRemoveInterceptor;
 import com.android.systemui.statusbar.NotificationUiAdjustment;
-import com.android.systemui.statusbar.notification.collection.NotifLiveDataStoreImpl;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinder;
 import com.android.systemui.statusbar.notification.collection.legacy.LegacyNotificationRanker;
@@ -113,7 +111,6 @@
     private final Lazy<NotificationRemoteInputManager> mRemoteInputManagerLazy;
     private final LeakDetector mLeakDetector;
     private final IStatusBarService mStatusBarService;
-    private final NotifLiveDataStoreImpl mNotifLiveDataStore;
     private final DumpManager mDumpManager;
     private final Executor mBgExecutor;
 
@@ -141,7 +138,6 @@
     private final List<NotifCollectionListener> mNotifCollectionListeners = new ArrayList<>();
 
     private LegacyNotificationRanker mRanker = new LegacyNotificationRankerStub();
-    private NotificationPresenter mPresenter;
     private RankingMap mLatestRankingMap;
 
     @VisibleForTesting
@@ -161,7 +157,6 @@
             Lazy<NotificationRemoteInputManager> notificationRemoteInputManagerLazy,
             LeakDetector leakDetector,
             IStatusBarService statusBarService,
-            NotifLiveDataStoreImpl notifLiveDataStore,
             DumpManager dumpManager,
             @Background Executor bgExecutor
     ) {
@@ -172,7 +167,6 @@
         mRemoteInputManagerLazy = notificationRemoteInputManagerLazy;
         mLeakDetector = leakDetector;
         mStatusBarService = statusBarService;
-        mNotifLiveDataStore = notifLiveDataStore;
         mDumpManager = dumpManager;
         mBgExecutor = bgExecutor;
     }
@@ -250,10 +244,6 @@
         mRemoveInterceptors.remove(interceptor);
     }
 
-    public void setUpWithPresenter(NotificationPresenter presenter) {
-        mPresenter = presenter;
-    }
-
     /** Adds multiple {@link NotificationLifetimeExtender}s. */
     public void addNotificationLifetimeExtenders(List<NotificationLifetimeExtender> extenders) {
         for (NotificationLifetimeExtender extender : extenders) {
@@ -663,11 +653,6 @@
             listener.onEntryBind(entry, notification);
         }
 
-        // Construct the expanded view.
-        if (!mNotifPipelineFlags.isNewPipelineEnabled()) {
-            mNotificationRowBinderLazy.get().inflateViews(entry, null, mInflationCallback);
-        }
-
         mPendingNotifications.put(key, entry);
         mLogger.logNotifAdded(entry.getKey());
         for (NotificationEntryListener listener : mNotificationEntryListeners) {
@@ -724,10 +709,6 @@
             listener.onEntryUpdated(entry, fromSystem);
         }
 
-        if (!mNotifPipelineFlags.isNewPipelineEnabled()) {
-            mNotificationRowBinderLazy.get().inflateViews(entry, null, mInflationCallback);
-        }
-
         updateNotifications("updateNotificationInternal");
 
         for (NotificationEntryListener listener : mNotificationEntryListeners) {
@@ -752,17 +733,7 @@
      * @param reason why the notifications are updating
      */
     public void updateNotifications(String reason) {
-        if (mNotifPipelineFlags.isNewPipelineEnabled()) {
-            mLogger.logUseWhileNewPipelineActive("updateNotifications", reason);
-            return;
-        }
-        Trace.beginSection("NotificationEntryManager.updateNotifications");
-        reapplyFilterAndSort(reason);
-        if (mPresenter != null) {
-            mPresenter.updateNotificationViews(reason);
-        }
-        mNotifLiveDataStore.setActiveNotifList(getVisibleNotifications());
-        Trace.endSection();
+        mLogger.logUseWhileNewPipelineActive("updateNotifications", reason);
     }
 
     public void updateNotificationRanking(RankingMap rankingMap) {
@@ -939,26 +910,12 @@
 
     /** Resorts / filters the current notification set with the current RankingMap */
     public void reapplyFilterAndSort(String reason) {
-        if (mNotifPipelineFlags.isNewPipelineEnabled()) {
-            mLogger.logUseWhileNewPipelineActive("reapplyFilterAndSort", reason);
-            return;
-        }
-        Trace.beginSection("NotificationEntryManager.reapplyFilterAndSort");
-        updateRankingAndSort(mRanker.getRankingMap(), reason);
-        Trace.endSection();
+        mLogger.logUseWhileNewPipelineActive("reapplyFilterAndSort", reason);
     }
 
     /** Calls to NotificationRankingManager and updates mSortedAndFiltered */
     private void updateRankingAndSort(RankingMap rankingMap, String reason) {
-        if (mNotifPipelineFlags.isNewPipelineEnabled()) {
-            mLogger.logUseWhileNewPipelineActive("updateRankingAndSort", reason);
-            return;
-        }
-        Trace.beginSection("NotificationEntryManager.updateRankingAndSort");
-        mSortedAndFiltered.clear();
-        mSortedAndFiltered.addAll(mRanker.updateRanking(
-                rankingMap, mActiveNotifications.values(), reason));
-        Trace.endSection();
+        mLogger.logUseWhileNewPipelineActive("updateRankingAndSort", reason);
     }
 
     /** dump the current active notification list. Called from CentralSurfaces */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListAttachState.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListAttachState.kt
index 945bc72..9e5dab1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListAttachState.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListAttachState.kt
@@ -68,9 +68,6 @@
      */
     var stableIndex: Int = -1
 
-    /** Access the index of the [section] or -1 if the entry does not have one */
-    val sectionIndex: Int get() = section?.index ?: -1
-
     /** Copies the state of another instance. */
     fun clone(other: ListAttachState) {
         parent = other.parent
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java
index 697054a..420f21d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java
@@ -1039,25 +1039,22 @@
         NotifSection currentSection = requireNonNull(notifList.get(0).getSection());
         int sectionMemberIndex = 0;
         for (int i = 0; i < notifList.size(); i++) {
-            final ListEntry entry = notifList.get(i);
+            ListEntry entry = notifList.get(i);
             NotifSection section = requireNonNull(entry.getSection());
             if (section.getIndex() != currentSection.getIndex()) {
                 sectionMemberIndex = 0;
                 currentSection = section;
             }
-            entry.getAttachState().setStableIndex(sectionMemberIndex++);
+            entry.getAttachState().setStableIndex(sectionMemberIndex);
             if (entry instanceof GroupEntry) {
-                final GroupEntry parent = (GroupEntry) entry;
-                final NotificationEntry summary = parent.getSummary();
-                if (summary != null) {
-                    summary.getAttachState().setStableIndex(sectionMemberIndex++);
-                }
-                final List<NotificationEntry> children = parent.getChildren();
-                for (int j = 0; j < children.size(); j++) {
-                    final NotificationEntry child = children.get(j);
-                    child.getAttachState().setStableIndex(sectionMemberIndex++);
+                GroupEntry parent = (GroupEntry) entry;
+                for (int j = 0; j < parent.getChildren().size(); j++) {
+                    entry = parent.getChildren().get(j);
+                    entry.getAttachState().setStableIndex(sectionMemberIndex);
+                    sectionMemberIndex++;
                 }
             }
+            sectionMemberIndex++;
         }
     }
 
@@ -1197,9 +1194,9 @@
                 o2.getSectionIndex());
         if (cmp != 0) return cmp;
 
-        cmp = Integer.compare(
-                getStableOrderIndex(o1),
-                getStableOrderIndex(o2));
+        int index1 = canReorder(o1) ? -1 : o1.getPreviousAttachState().getStableIndex();
+        int index2 = canReorder(o2) ? -1 : o2.getPreviousAttachState().getStableIndex();
+        cmp = Integer.compare(index1, index2);
         if (cmp != 0) return cmp;
 
         NotifComparator sectionComparator = getSectionComparator(o1, o2);
@@ -1213,32 +1210,31 @@
             if (cmp != 0) return cmp;
         }
 
-        cmp = Integer.compare(
-                o1.getRepresentativeEntry().getRanking().getRank(),
-                o2.getRepresentativeEntry().getRanking().getRank());
+        final NotificationEntry rep1 = o1.getRepresentativeEntry();
+        final NotificationEntry rep2 = o2.getRepresentativeEntry();
+            cmp = rep1.getRanking().getRank() - rep2.getRanking().getRank();
         if (cmp != 0) return cmp;
 
-        cmp = -1 * Long.compare(
-                o1.getRepresentativeEntry().getSbn().getNotification().when,
-                o2.getRepresentativeEntry().getSbn().getNotification().when);
+        cmp = Long.compare(
+                rep2.getSbn().getNotification().when,
+                rep1.getSbn().getNotification().when);
         return cmp;
     };
 
 
     private final Comparator<NotificationEntry> mGroupChildrenComparator = (o1, o2) -> {
-        int cmp = Integer.compare(
-                getStableOrderIndex(o1),
-                getStableOrderIndex(o2));
+        int index1 = canReorder(o1) ? -1 : o1.getPreviousAttachState().getStableIndex();
+        int index2 = canReorder(o2) ? -1 : o2.getPreviousAttachState().getStableIndex();
+        int cmp = Integer.compare(index1, index2);
         if (cmp != 0) return cmp;
 
-        cmp = Integer.compare(
-                o1.getRepresentativeEntry().getRanking().getRank(),
-                o2.getRepresentativeEntry().getRanking().getRank());
+        cmp = o1.getRepresentativeEntry().getRanking().getRank()
+                - o2.getRepresentativeEntry().getRanking().getRank();
         if (cmp != 0) return cmp;
 
-        cmp = -1 * Long.compare(
-                o1.getRepresentativeEntry().getSbn().getNotification().when,
-                o2.getRepresentativeEntry().getSbn().getNotification().when);
+        cmp = Long.compare(
+                o2.getRepresentativeEntry().getSbn().getNotification().when,
+                o1.getRepresentativeEntry().getSbn().getNotification().when);
         return cmp;
     };
 
@@ -1248,21 +1244,8 @@
      */
     private boolean mForceReorderable = false;
 
-    private int getStableOrderIndex(ListEntry entry) {
-        if (mForceReorderable) {
-            // this is used to determine if the list is correctly sorted
-            return -1;
-        }
-        if (getStabilityManager().isEntryReorderingAllowed(entry)) {
-            // let the stability manager constrain or allow reordering
-            return -1;
-        }
-        if (entry.getAttachState().getSectionIndex()
-                != entry.getPreviousAttachState().getSectionIndex()) {
-            // stable index is only valid within the same section; otherwise we allow reordering
-            return -1;
-        }
-        return entry.getPreviousAttachState().getStableIndex();
+    private boolean canReorder(ListEntry entry) {
+        return mForceReorderable || getStabilityManager().isEntryReorderingAllowed(entry);
     }
 
     private boolean applyFilters(NotificationEntry entry, long now, List<NotifFilter> filters) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/ShadeListBuilderLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/ShadeListBuilderLogger.kt
index 4c406e3..d8dae5d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/ShadeListBuilderLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/ShadeListBuilderLogger.kt
@@ -336,7 +336,7 @@
     }
 
     fun logPipelineRunSuppressed() =
-        buffer.log(TAG, INFO, {}) { "Suppressing pipeline run during animation." }
+        buffer.log(TAG, INFO, {}, { "Suppressing pipeline run during animation." })
 }
 
 private const val TAG = "ShadeListBuilder"
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java
index fcf35bf..fac234c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java
@@ -127,7 +127,6 @@
             Lazy<NotificationRemoteInputManager> notificationRemoteInputManagerLazy,
             LeakDetector leakDetector,
             IStatusBarService statusBarService,
-            NotifLiveDataStoreImpl notifLiveDataStore,
             DumpManager dumpManager,
             @Background Executor bgExecutor) {
         return new NotificationEntryManager(
@@ -138,7 +137,6 @@
                 notificationRemoteInputManagerLazy,
                 leakDetector,
                 statusBarService,
-                notifLiveDataStore,
                 dumpManager,
                 bgExecutor);
     }
@@ -174,7 +172,6 @@
                 accessibilityManager,
                 highPriorityProvider,
                 notificationManager,
-                notificationEntryManager,
                 peopleSpaceWidgetManager,
                 launcherApps,
                 shortcutManager,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java
index a72b381..558fd62 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java
@@ -37,7 +37,6 @@
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.statusbar.StatusBarState;
 import com.android.systemui.statusbar.notification.NotifPipelineFlags;
-import com.android.systemui.statusbar.notification.NotificationFilter;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.policy.BatteryController;
 import com.android.systemui.statusbar.policy.HeadsUpManager;
@@ -60,13 +59,11 @@
     private final List<NotificationInterruptSuppressor> mSuppressors = new ArrayList<>();
     private final StatusBarStateController mStatusBarStateController;
     private final KeyguardStateController mKeyguardStateController;
-    private final NotificationFilter mNotificationFilter;
     private final ContentResolver mContentResolver;
     private final PowerManager mPowerManager;
     private final IDreamManager mDreamManager;
     private final AmbientDisplayConfiguration mAmbientDisplayConfiguration;
     private final BatteryController mBatteryController;
-    private final ContentObserver mHeadsUpObserver;
     private final HeadsUpManager mHeadsUpManager;
     private final NotificationInterruptLogger mLogger;
     private final NotifPipelineFlags mFlags;
@@ -81,7 +78,6 @@
             PowerManager powerManager,
             IDreamManager dreamManager,
             AmbientDisplayConfiguration ambientDisplayConfiguration,
-            NotificationFilter notificationFilter,
             BatteryController batteryController,
             StatusBarStateController statusBarStateController,
             KeyguardStateController keyguardStateController,
@@ -95,14 +91,13 @@
         mDreamManager = dreamManager;
         mBatteryController = batteryController;
         mAmbientDisplayConfiguration = ambientDisplayConfiguration;
-        mNotificationFilter = notificationFilter;
         mStatusBarStateController = statusBarStateController;
         mKeyguardStateController = keyguardStateController;
         mHeadsUpManager = headsUpManager;
         mLogger = logger;
         mFlags = flags;
         mKeyguardNotificationVisibilityProvider = keyguardNotificationVisibilityProvider;
-        mHeadsUpObserver = new ContentObserver(mainHandler) {
+        ContentObserver headsUpObserver = new ContentObserver(mainHandler) {
             @Override
             public void onChange(boolean selfChange) {
                 boolean wasUsing = mUseHeadsUp;
@@ -125,12 +120,12 @@
             mContentResolver.registerContentObserver(
                     Settings.Global.getUriFor(Settings.Global.HEADS_UP_NOTIFICATIONS_ENABLED),
                     true,
-                    mHeadsUpObserver);
+                    headsUpObserver);
             mContentResolver.registerContentObserver(
                     Settings.Global.getUriFor(SETTING_HEADS_UP_TICKER), true,
-                    mHeadsUpObserver);
+                    headsUpObserver);
         }
-        mHeadsUpObserver.onChange(true); // set up
+        headsUpObserver.onChange(true); // set up
     }
 
     @Override
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 c228af4..c4ff259 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
@@ -21,7 +21,6 @@
 
 import android.app.INotificationManager;
 import android.app.NotificationChannel;
-import android.appwidget.AppWidgetManager;
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.LauncherApps;
@@ -63,13 +62,11 @@
 import com.android.systemui.statusbar.StatusBarStateControllerImpl;
 import com.android.systemui.statusbar.notification.AssistantFeedbackController;
 import com.android.systemui.statusbar.notification.NotificationActivityStarter;
-import com.android.systemui.statusbar.notification.NotificationEntryManager;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider;
 import com.android.systemui.statusbar.notification.collection.render.NotifGutsViewListener;
 import com.android.systemui.statusbar.notification.collection.render.NotifGutsViewManager;
 import com.android.systemui.statusbar.notification.dagger.NotificationsModule;
-import com.android.systemui.statusbar.notification.row.NotificationInfo.CheckSaveListener;
 import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
 import com.android.systemui.statusbar.phone.CentralSurfaces;
 import com.android.systemui.statusbar.policy.DeviceProvisionedController;
@@ -114,7 +111,6 @@
     private NotificationPresenter mPresenter;
     private NotificationActivityStarter mNotificationActivityStarter;
     private NotificationListContainer mListContainer;
-    private CheckSaveListener mCheckSaveListener;
     private OnSettingsClickListener mOnSettingsClickListener;
     @VisibleForTesting
     protected String mKeyToRemoveOnGutsClosed;
@@ -131,7 +127,6 @@
     private final UserContextProvider mContextTracker;
     private final UiEventLogger mUiEventLogger;
     private final ShadeController mShadeController;
-    private final AppWidgetManager mAppWidgetManager;
     private NotifGutsViewListener mGutsListener;
 
     /**
@@ -144,7 +139,6 @@
             AccessibilityManager accessibilityManager,
             HighPriorityProvider highPriorityProvider,
             INotificationManager notificationManager,
-            NotificationEntryManager notificationEntryManager,
             PeopleSpaceWidgetManager peopleSpaceWidgetManager,
             LauncherApps launcherApps,
             ShortcutManager shortcutManager,
@@ -173,17 +167,15 @@
         mUiEventLogger = uiEventLogger;
         mOnUserInteractionCallback = onUserInteractionCallback;
         mShadeController = shadeController;
-        mAppWidgetManager = AppWidgetManager.getInstance(context);
 
         dumpManager.registerDumpable(this);
     }
 
     public void setUpWithPresenter(NotificationPresenter presenter,
             NotificationListContainer listContainer,
-            CheckSaveListener checkSave, OnSettingsClickListener onSettingsClick) {
+            OnSettingsClickListener onSettingsClick) {
         mPresenter = presenter;
         mListContainer = listContainer;
-        mCheckSaveListener = checkSave;
         mOnSettingsClickListener = onSettingsClick;
     }
 
@@ -218,14 +210,11 @@
     }
 
     private void startAppDetailsSettingsActivity(String packageName, final int appUid,
-            final NotificationChannel channel, ExpandableNotificationRow row) {
+            ExpandableNotificationRow row) {
         final Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
         intent.setData(Uri.fromParts("package", packageName, null));
         intent.putExtra(Settings.EXTRA_APP_PACKAGE, packageName);
         intent.putExtra(Settings.EXTRA_APP_UID, appUid);
-        if (channel != null) {
-            intent.putExtra(EXTRA_FRAGMENT_ARG_KEY, channel.getId());
-        }
         mNotificationActivityStarter.startNotificationGutsIntent(intent, appUid, row);
     }
 
@@ -233,7 +222,7 @@
             ExpandableNotificationRow row) {
         if (ops.contains(OP_SYSTEM_ALERT_WINDOW)) {
             if (ops.contains(OP_CAMERA) || ops.contains(OP_RECORD_AUDIO)) {
-                startAppDetailsSettingsActivity(pkg, uid, null, row);
+                startAppDetailsSettingsActivity(pkg, uid, row);
             } else {
                 Intent intent = new Intent(Settings.ACTION_MANAGE_APP_OVERLAY_PERMISSION);
                 intent.setData(Uri.fromParts("package", pkg, null));
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
index d7ebce5..a2ea0ba 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
@@ -195,14 +195,12 @@
 import com.android.systemui.statusbar.NotificationShadeDepthController;
 import com.android.systemui.statusbar.NotificationShadeWindowController;
 import com.android.systemui.statusbar.NotificationShelfController;
-import com.android.systemui.statusbar.NotificationViewHierarchyManager;
 import com.android.systemui.statusbar.PowerButtonReveal;
 import com.android.systemui.statusbar.PulseExpansionHandler;
 import com.android.systemui.statusbar.StatusBarState;
 import com.android.systemui.statusbar.SysuiStatusBarStateController;
 import com.android.systemui.statusbar.core.StatusBarInitializer;
 import com.android.systemui.statusbar.notification.DynamicPrivacyController;
-import com.android.systemui.statusbar.notification.NotifPipelineFlags;
 import com.android.systemui.statusbar.notification.NotificationActivityStarter;
 import com.android.systemui.statusbar.notification.NotificationEntryManager;
 import com.android.systemui.statusbar.notification.NotificationLaunchAnimatorControllerProvider;
@@ -499,7 +497,6 @@
     protected final NotificationEntryManager mEntryManager;
     private final NotificationGutsManager mGutsManager;
     private final NotificationLogger mNotificationLogger;
-    private final NotificationViewHierarchyManager mViewHierarchyManager;
     private final PanelExpansionStateManager mPanelExpansionStateManager;
     private final KeyguardViewMediator mKeyguardViewMediator;
     protected final NotificationInterruptStateProvider mNotificationInterruptStateProvider;
@@ -635,7 +632,6 @@
     private final Optional<Bubbles> mBubblesOptional;
     private final Bubbles.BubbleExpandListener mBubbleExpandListener;
     private final Optional<StartingSurface> mStartingSurfaceOptional;
-    private final NotifPipelineFlags mNotifPipelineFlags;
 
     private final ActivityIntentHelper mActivityIntentHelper;
     private NotificationStackScrollLayoutController mStackScrollerController;
@@ -677,7 +673,6 @@
             NotificationGutsManager notificationGutsManager,
             NotificationLogger notificationLogger,
             NotificationInterruptStateProvider notificationInterruptStateProvider,
-            NotificationViewHierarchyManager notificationViewHierarchyManager,
             PanelExpansionStateManager panelExpansionStateManager,
             KeyguardViewMediator keyguardViewMediator,
             DisplayMetrics displayMetrics,
@@ -740,7 +735,6 @@
             WallpaperManager wallpaperManager,
             Optional<StartingSurface> startingSurfaceOptional,
             ActivityLaunchAnimator activityLaunchAnimator,
-            NotifPipelineFlags notifPipelineFlags,
             InteractionJankMonitor jankMonitor,
             DeviceStateManager deviceStateManager,
             WiredChargingRippleController wiredChargingRippleController,
@@ -767,7 +761,6 @@
         mGutsManager = notificationGutsManager;
         mNotificationLogger = notificationLogger;
         mNotificationInterruptStateProvider = notificationInterruptStateProvider;
-        mViewHierarchyManager = notificationViewHierarchyManager;
         mPanelExpansionStateManager = panelExpansionStateManager;
         mKeyguardViewMediator = keyguardViewMediator;
         mDisplayMetrics = displayMetrics;
@@ -828,7 +821,6 @@
 
         mLockscreenShadeTransitionController = lockscreenShadeTransitionController;
         mStartingSurfaceOptional = startingSurfaceOptional;
-        mNotifPipelineFlags = notifPipelineFlags;
         mDreamManager = dreamManager;
         lockscreenShadeTransitionController.setCentralSurfaces(this);
         statusBarWindowStateController.addListener(this::onStatusBarWindowStateChanged);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java
index ff3d122..cc451d9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java
@@ -16,6 +16,7 @@
 
 package com.android.systemui.statusbar.phone;
 
+import static com.android.internal.util.Preconditions.checkNotNull;
 import static com.android.systemui.controls.dagger.ControlsComponent.Visibility.AVAILABLE;
 import static com.android.systemui.doze.util.BurnInHelperKt.getBurnInOffset;
 import static com.android.systemui.wallet.controller.QuickAccessWalletController.WalletChangeEvent.DEFAULT_PAYMENT_APP_CHANGE;
@@ -57,6 +58,8 @@
 import com.android.systemui.controls.management.ControlsListingController;
 import com.android.systemui.controls.ui.ControlsActivity;
 import com.android.systemui.controls.ui.ControlsUiController;
+import com.android.systemui.keyguard.ui.binder.KeyguardBottomAreaViewBinder;
+import com.android.systemui.keyguard.ui.viewmodel.KeyguardBottomAreaViewModel;
 import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.plugins.FalsingManager;
 import com.android.systemui.qrcodescanner.controller.QRCodeScannerController;
@@ -125,6 +128,9 @@
         }
     };
 
+    @Nullable private KeyguardBottomAreaViewBinder.Binding mBinding;
+    private boolean mUsesBinder;
+
     public KeyguardBottomAreaView(Context context) {
         this(context, null);
     }
@@ -142,13 +148,36 @@
         super(context, attrs, defStyleAttr, defStyleRes);
     }
 
-    /** Initializes the {@link KeyguardBottomAreaView} with the given dependencies */
+    /**
+     * Initializes the view.
+     */
+    public void init(
+            final KeyguardBottomAreaViewModel viewModel,
+            final FalsingManager falsingManager) {
+        Log.i(TAG, System.identityHashCode(this) + " initialized with a binder");
+        mUsesBinder = true;
+        mBinding = KeyguardBottomAreaViewBinder.bind(this, viewModel, falsingManager);
+    }
+
+    /**
+     * Initializes the {@link KeyguardBottomAreaView} with the given dependencies
+     *
+     * @deprecated Use
+     * {@link #init(KeyguardBottomAreaViewModel, FalsingManager)} instead
+     */
+    @Deprecated
     public void init(
             FalsingManager falsingManager,
             QuickAccessWalletController controller,
             ControlsComponent controlsComponent,
             QRCodeScannerController qrCodeScannerController) {
+        if (mUsesBinder) {
+            return;
+        }
+
+        Log.i(TAG, "initialized without a binder");
         mFalsingManager = falsingManager;
+
         mQuickAccessWalletController = controller;
         mQuickAccessWalletController.setupWalletChangeObservers(
                 mCardRetriever, WALLET_PREFERENCE_CHANGE, DEFAULT_PAYMENT_APP_CHANGE);
@@ -174,6 +203,10 @@
      * another {@link KeyguardBottomAreaView}
      */
     public void initFrom(KeyguardBottomAreaView oldBottomArea) {
+        if (mUsesBinder) {
+            return;
+        }
+
         // if it exists, continue to use the original ambient indication container
         // instead of the newly inflated one
         if (mAmbientIndicationArea != null) {
@@ -201,6 +234,10 @@
     @Override
     protected void onFinishInflate() {
         super.onFinishInflate();
+        if (mUsesBinder) {
+            return;
+        }
+
         mOverlayContainer = findViewById(R.id.overlay_container);
         mWalletButton = findViewById(R.id.wallet_button);
         mQRCodeScannerButton = findViewById(R.id.qr_code_scanner_button);
@@ -229,6 +266,10 @@
     @Override
     protected void onAttachedToWindow() {
         super.onAttachedToWindow();
+        if (mUsesBinder) {
+            return;
+        }
+
         final IntentFilter filter = new IntentFilter();
         filter.addAction(DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED);
         mKeyguardStateController.addCallback(mKeyguardStateCallback);
@@ -237,6 +278,10 @@
     @Override
     protected void onDetachedFromWindow() {
         super.onDetachedFromWindow();
+        if (mUsesBinder) {
+            return;
+        }
+
         mKeyguardStateController.removeCallback(mKeyguardStateCallback);
 
         if (mQuickAccessWalletController != null) {
@@ -259,6 +304,13 @@
     @Override
     protected void onConfigurationChanged(Configuration newConfig) {
         super.onConfigurationChanged(newConfig);
+        if (mUsesBinder) {
+            if (mBinding != null) {
+                mBinding.onConfigurationChanged();
+            }
+            return;
+        }
+
         mIndicationBottomMargin = getResources().getDimensionPixelSize(
                 R.dimen.keyguard_indication_margin_bottom);
         mBurnInYOffset = getResources().getDimensionPixelSize(
@@ -301,6 +353,10 @@
     }
 
     private void updateWalletVisibility() {
+        if (mUsesBinder) {
+            return;
+        }
+
         if (mDozing
                 || mQuickAccessWalletController == null
                 || !mQuickAccessWalletController.isWalletEnabled()
@@ -318,6 +374,10 @@
     }
 
     private void updateControlsVisibility() {
+        if (mUsesBinder) {
+            return;
+        }
+
         if (mControlsComponent == null) return;
 
         mControlsButton.setImageResource(mControlsComponent.getTileImageId());
@@ -344,6 +404,10 @@
     }
 
     public void setDarkAmount(float darkAmount) {
+        if (mUsesBinder) {
+            return;
+        }
+
         if (darkAmount == mDarkAmount) {
             return;
         }
@@ -355,6 +419,10 @@
      * Returns a list of animators to use to animate the indication areas.
      */
     public List<ViewPropertyAnimator> getIndicationAreaAnimators() {
+        if (mUsesBinder) {
+            return checkNotNull(mBinding).getIndicationAreaAnimators();
+        }
+
         List<ViewPropertyAnimator> animators =
                 new ArrayList<>(mAmbientIndicationArea != null ? 2 : 1);
         animators.add(mIndicationArea.animate());
@@ -394,6 +462,10 @@
     }
 
     public void setDozing(boolean dozing, boolean animate) {
+        if (mUsesBinder) {
+            return;
+        }
+
         mDozing = dozing;
 
         updateWalletVisibility();
@@ -411,6 +483,10 @@
     }
 
     public void dozeTimeTick() {
+        if (mUsesBinder) {
+            return;
+        }
+
         int burnInYOffset = getBurnInOffset(mBurnInYOffset * 2, false /* xAxis */)
                 - mBurnInYOffset;
         mIndicationArea.setTranslationY(burnInYOffset * mDarkAmount);
@@ -420,6 +496,10 @@
     }
 
     public void setAntiBurnInOffsetX(int burnInXOffset) {
+        if (mUsesBinder) {
+            return;
+        }
+
         if (mBurnInXOffset == burnInXOffset) {
             return;
         }
@@ -435,6 +515,10 @@
      * action buttons. Does not set the alpha of the lock icon.
      */
     public void setComponentAlphas(float alpha) {
+        if (mUsesBinder) {
+            return;
+        }
+
         setImportantForAccessibility(
                 alpha == 0f
                         ? View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS
@@ -461,6 +545,10 @@
     }
 
     private void updateQRCodeButtonVisibility() {
+        if (mUsesBinder) {
+            return;
+        }
+
         if (mQuickAccessWalletController != null
                 && mQuickAccessWalletController.isWalletEnabled()) {
             // Don't enable if quick access wallet is enabled
@@ -481,6 +569,10 @@
     }
 
     private void onQRCodeScannerClicked(View view) {
+        if (mUsesBinder) {
+            return;
+        }
+
         Intent intent = mQRCodeScannerController.getIntent();
         if (intent != null) {
             try {
@@ -500,6 +592,10 @@
     }
 
     private void updateAffordanceColors() {
+        if (mUsesBinder) {
+            return;
+        }
+
         int iconColor = Utils.getColorAttrDefaultColor(
                 mContext,
                 com.android.internal.R.attr.textColorPrimary);
@@ -516,6 +612,10 @@
     }
 
     private void onWalletClick(View v) {
+        if (mUsesBinder) {
+            return;
+        }
+
         // More coming here; need to inform the user about how to proceed
         if (mFalsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) {
             return;
@@ -531,6 +631,10 @@
     }
 
     private void onControlsClick(View v) {
+        if (mUsesBinder) {
+            return;
+        }
+
         if (mFalsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) {
             return;
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LetterboxAppearanceCalculator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LetterboxAppearanceCalculator.kt
index 70ec13b14..4496607 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LetterboxAppearanceCalculator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LetterboxAppearanceCalculator.kt
@@ -17,7 +17,6 @@
 package com.android.systemui.statusbar.phone
 
 import android.annotation.ColorInt
-import android.graphics.Color
 import android.graphics.Rect
 import android.view.InsetsFlags
 import android.view.ViewDebug
@@ -39,7 +38,13 @@
 class LetterboxAppearance(
     @Appearance val appearance: Int,
     val appearanceRegions: Array<AppearanceRegion>
-)
+) {
+    override fun toString(): String {
+        val appearanceString =
+                ViewDebug.flagsToString(InsetsFlags::class.java, "appearance", appearance)
+        return "LetterboxAppearance{$appearanceString, ${appearanceRegions.contentToString()}}"
+    }
+}
 
 /**
  * Responsible for calculating the [Appearance] and [AppearanceRegion] for the status bar when apps
@@ -51,6 +56,7 @@
 constructor(
     private val lightBarController: LightBarController,
     private val dumpManager: DumpManager,
+    private val letterboxBackgroundProvider: LetterboxBackgroundProvider,
 ) : OnStatusBarViewInitializedListener, CentralSurfacesComponent.Startable {
 
     private var statusBarBoundsProvider: StatusBarBoundsProvider? = null
@@ -184,13 +190,11 @@
 
     @ColorInt
     private fun outerLetterboxBackgroundColor(): Int {
-        // TODO(b/238607453): retrieve this information from WindowManager.
-        return Color.BLACK
+        return letterboxBackgroundProvider.letterboxBackgroundColor
     }
 
     private fun isOuterLetterboxMultiColored(): Boolean {
-        // TODO(b/238607453): retrieve this information from WindowManager.
-        return false
+        return letterboxBackgroundProvider.isLetterboxBackgroundMultiColored
     }
 
     private fun getEndSideIconsBounds(): Rect {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LetterboxBackgroundProvider.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LetterboxBackgroundProvider.kt
new file mode 100644
index 0000000..96b9aca
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LetterboxBackgroundProvider.kt
@@ -0,0 +1,74 @@
+/*
+ * 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.phone
+
+import android.annotation.ColorInt
+import android.graphics.Color
+import android.os.RemoteException
+import android.view.IWindowManager
+import com.android.systemui.Dumpable
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.dump.DumpManager
+import com.android.systemui.statusbar.phone.dagger.CentralSurfacesComponent
+import com.android.systemui.statusbar.phone.dagger.CentralSurfacesComponent.CentralSurfacesScope
+import java.io.PrintWriter
+import java.util.concurrent.Executor
+import javax.inject.Inject
+
+/** Responsible for providing information about the background of letterboxed apps. */
+@CentralSurfacesScope
+class LetterboxBackgroundProvider
+@Inject
+constructor(
+    private val windowManager: IWindowManager,
+    @Background private val backgroundExecutor: Executor,
+    private val dumpManager: DumpManager,
+) : CentralSurfacesComponent.Startable, Dumpable {
+
+    @ColorInt
+    var letterboxBackgroundColor: Int = Color.BLACK
+        private set
+
+    var isLetterboxBackgroundMultiColored: Boolean = false
+        private set
+
+    override fun start() {
+        dumpManager.registerDumpable(javaClass.simpleName, this)
+
+        // Using a background executor, as binder calls to IWindowManager are blocking
+        backgroundExecutor.execute {
+            try {
+                isLetterboxBackgroundMultiColored = windowManager.isLetterboxBackgroundMultiColored
+                letterboxBackgroundColor = windowManager.letterboxBackgroundColorInArgb
+            } catch (e: RemoteException) {
+                e.rethrowFromSystemServer()
+            }
+        }
+    }
+
+    override fun stop() {
+        dumpManager.unregisterDumpable(javaClass.simpleName)
+    }
+
+    override fun dump(pw: PrintWriter, args: Array<out String>) {
+        pw.println(
+            """
+           letterboxBackgroundColor: ${Color.valueOf(letterboxBackgroundColor)}
+           isLetterboxBackgroundMultiColored: $isLetterboxBackgroundMultiColored
+       """.trimIndent())
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
index ab640d2..b1104ae 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
@@ -41,7 +41,6 @@
 import com.android.systemui.plugins.ActivityStarter.OnDismissAction;
 import com.android.systemui.shade.NotificationPanelViewController;
 import com.android.systemui.shade.NotificationShadeWindowView;
-import com.android.systemui.shade.ShadeController;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.KeyguardIndicationController;
 import com.android.systemui.statusbar.LockscreenShadeTransitionController;
@@ -92,10 +91,8 @@
     private final HeadsUpManagerPhone mHeadsUpManager;
     private final AboveShelfObserver mAboveShelfObserver;
     private final DozeScrimController mDozeScrimController;
-    private final ScrimController mScrimController;
     private final KeyguardIndicationController mKeyguardIndicationController;
     private final CentralSurfaces mCentralSurfaces;
-    private final com.android.systemui.shade.ShadeController mShadeController;
     private final LockscreenShadeTransitionController mShadeTransitionController;
     private final CommandQueue mCommandQueue;
 
@@ -110,20 +107,19 @@
     protected boolean mVrMode;
 
     @Inject
-    StatusBarNotificationPresenter(Context context,
+    StatusBarNotificationPresenter(
+            Context context,
             NotificationPanelViewController panel,
             HeadsUpManagerPhone headsUp,
             NotificationShadeWindowView statusBarWindow,
             ActivityStarter activityStarter,
             NotificationStackScrollLayoutController stackScrollerController,
             DozeScrimController dozeScrimController,
-            ScrimController scrimController,
             NotificationShadeWindowController notificationShadeWindowController,
             DynamicPrivacyController dynamicPrivacyController,
             KeyguardStateController keyguardStateController,
             KeyguardIndicationController keyguardIndicationController,
             CentralSurfaces centralSurfaces,
-            ShadeController shadeController,
             LockscreenShadeTransitionController shadeTransitionController,
             CommandQueue commandQueue,
             NotificationLockscreenUserManager lockscreenUserManager,
@@ -146,7 +142,6 @@
         mKeyguardIndicationController = keyguardIndicationController;
         // TODO: use KeyguardStateController#isOccluded to remove this dependency
         mCentralSurfaces = centralSurfaces;
-        mShadeController = shadeController;
         mShadeTransitionController = shadeTransitionController;
         mCommandQueue = commandQueue;
         mLockscreenUserManager = lockscreenUserManager;
@@ -162,7 +157,6 @@
                 R.id.notification_container_parent));
         mAccessibilityManager = context.getSystemService(AccessibilityManager.class);
         mDozeScrimController = dozeScrimController;
-        mScrimController = scrimController;
         mKeyguardManager = context.getSystemService(KeyguardManager.class);
         mBarService = IStatusBarService.Stub.asInterface(
                 ServiceManager.getService(Context.STATUS_BAR_SERVICE));
@@ -188,7 +182,7 @@
             notificationInterruptStateProvider.addSuppressor(mInterruptSuppressor);
             mLockscreenUserManager.setUpWithPresenter(this);
             mGutsManager.setUpWithPresenter(
-                    this, mNotifListContainer, mCheckSaveListener, mOnSettingsClickListener);
+                    this, mNotifListContainer, mOnSettingsClickListener);
             // ForegroundServiceNotificationListener adds its listener in its constructor
             // but we need to request it here in order for it to be instantiated.
             // TODO: figure out how to do this correctly once Dependency.get() is gone.
@@ -225,23 +219,6 @@
     }
 
     @Override
-    public void updateNotificationViews(final String reason) {
-        if (!mNotifPipelineFlags.checkLegacyPipelineEnabled()) {
-            return;
-        }
-        // The function updateRowStates depends on both of these being non-null, so check them here.
-        // We may be called before they are set from DeviceProvisionedController's callback.
-        if (mScrimController == null) return;
-
-        // Do not modify the notifications during collapse.
-        if (isCollapsing()) {
-            mShadeController.addPostCollapseAction(() -> updateNotificationViews(reason));
-            return;
-        }
-        mNotificationPanel.updateNotificationViews(reason);
-    }
-
-    @Override
     public void onUserSwitched(int newUserId) {
         // Begin old BaseStatusBar.userSwitched
         mHeadsUpManager.setUser(newUserId);
@@ -295,11 +272,6 @@
     }
 
     @Override
-    public void onUpdateRowStates() {
-        mNotificationPanel.onUpdateRowStates();
-    }
-
-    @Override
     public void onExpandClicked(NotificationEntry clickedEntry, View clickedView,
             boolean nowExpanded) {
         mHeadsUpManager.setExpanded(clickedEntry, nowExpanded);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/CentralSurfacesStartableModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/CentralSurfacesStartableModule.java
index d57e6a7..b0532d7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/CentralSurfacesStartableModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/CentralSurfacesStartableModule.java
@@ -17,6 +17,7 @@
 package com.android.systemui.statusbar.phone.dagger;
 
 import com.android.systemui.statusbar.phone.LetterboxAppearanceCalculator;
+import com.android.systemui.statusbar.phone.LetterboxBackgroundProvider;
 import com.android.systemui.statusbar.phone.SystemBarAttributesListener;
 
 import java.util.Set;
@@ -40,4 +41,9 @@
     @IntoSet
     CentralSurfacesComponent.Startable sysBarAttrsListener(
             SystemBarAttributesListener systemBarAttributesListener);
+
+    @Binds
+    @IntoSet
+    CentralSurfacesComponent.Startable letterboxBgProvider(
+            LetterboxBackgroundProvider letterboxBackgroundProvider);
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/ConnectivityInfoProcessor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/ConnectivityInfoProcessor.kt
index bd6cf9a..a841914 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/ConnectivityInfoProcessor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/ConnectivityInfoProcessor.kt
@@ -16,6 +16,8 @@
 
 package com.android.systemui.statusbar.pipeline
 
+import android.content.Context
+import com.android.systemui.CoreStartable
 import com.android.systemui.dagger.SysUISingleton
 import javax.inject.Inject
 
@@ -27,4 +29,18 @@
  * displayed in the RHS of the status bar.
  */
 @SysUISingleton
-class ConnectivityInfoProcessor @Inject constructor()
+class ConnectivityInfoProcessor @Inject constructor(
+        context: Context,
+        private val statusBarPipelineFlags: StatusBarPipelineFlags,
+) : CoreStartable(context) {
+    override fun start() {
+        if (statusBarPipelineFlags.isNewPipelineEnabled()) {
+            init()
+        }
+    }
+
+    /** Initializes this processor and everything it depends on. */
+    private fun init() {
+        // TODO(b/238425913): Register all the connectivity callbacks here.
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/ConnectivityPipelineLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/ConnectivityPipelineLogger.kt
new file mode 100644
index 0000000..f88e9d6
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/ConnectivityPipelineLogger.kt
@@ -0,0 +1,59 @@
+/*
+ * 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.pipeline
+
+import android.net.Network
+import android.net.NetworkCapabilities
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.LogLevel
+import com.android.systemui.log.dagger.StatusBarConnectivityLog
+import javax.inject.Inject
+
+@SysUISingleton
+class ConnectivityPipelineLogger @Inject constructor(
+    @StatusBarConnectivityLog private val buffer: LogBuffer,
+) {
+    fun logOnCapabilitiesChanged(network: Network, networkCapabilities: NetworkCapabilities) {
+        buffer.log(
+            TAG,
+            LogLevel.INFO,
+            {
+                int1 = network.getNetId()
+                str1 = networkCapabilities.toString()
+            },
+            {
+                "onCapabilitiesChanged: net=$int1 capabilities=$str1"
+            }
+        )
+    }
+
+    fun logOnLost(network: Network) {
+        buffer.log(
+            TAG,
+            LogLevel.INFO,
+            {
+                int1 = network.getNetId()
+            },
+            {
+                "onLost: net=$int1"
+            }
+        )
+    }
+}
+
+private const val TAG = "SbConnectivityPipeline"
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/StatusBarPipelineFlags.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/StatusBarPipelineFlags.kt
new file mode 100644
index 0000000..589cdb8
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/StatusBarPipelineFlags.kt
@@ -0,0 +1,37 @@
+/*
+ * 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.pipeline
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
+import javax.inject.Inject
+
+/** All flagging methods related to the new status bar pipeline (see b/238425913). */
+@SysUISingleton
+class StatusBarPipelineFlags @Inject constructor(private val featureFlags: FeatureFlags) {
+    /**
+     * Returns true if we should run the new pipeline.
+     *
+     * TODO(b/238425913): We may want to split this out into:
+     *   (1) isNewPipelineLoggingEnabled(), where the new pipeline runs and logs its decisions but
+     *       doesn't change the UI at all.
+     *   (2) isNewPipelineEnabled(), where the new pipeline runs and does change the UI (and the old
+     *       pipeline doesn't change the UI).
+     */
+    fun isNewPipelineEnabled(): Boolean = featureFlags.isEnabled(Flags.NEW_STATUS_BAR_PIPELINE)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt
index 734bd2d..771bb0c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt
@@ -16,27 +16,18 @@
 
 package com.android.systemui.statusbar.pipeline.dagger
 
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.flags.Flags
+import com.android.systemui.CoreStartable
 import com.android.systemui.statusbar.pipeline.ConnectivityInfoProcessor
-import dagger.Lazy
+import dagger.Binds
 import dagger.Module
-import dagger.Provides
-import java.util.Optional
+import dagger.multibindings.ClassKey
+import dagger.multibindings.IntoMap
 
 @Module
-class StatusBarPipelineModule {
-    @Provides
-    @SysUISingleton
-    fun provideConnectivityInfoProcessor(
-            featureFlags: FeatureFlags,
-            processorLazy: Lazy<ConnectivityInfoProcessor>
-    ): Optional<ConnectivityInfoProcessor> {
-        return if (featureFlags.isEnabled(Flags.NEW_STATUS_BAR_PIPELINE)) {
-            Optional.of(processorLazy.get())
-        } else {
-            Optional.empty()
-        }
-    }
+abstract class StatusBarPipelineModule {
+    /** Inject into ConnectivityInfoProcessor. */
+    @Binds
+    @IntoMap
+    @ClassKey(ConnectivityInfoProcessor::class)
+    abstract fun bindConnectivityInfoProcessor(cip: ConnectivityInfoProcessor): CoreStartable
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/repository/NetworkCapabilitiesRepo.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/repository/NetworkCapabilitiesRepo.kt
new file mode 100644
index 0000000..e5980c3
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/repository/NetworkCapabilitiesRepo.kt
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+@file:OptIn(ExperimentalCoroutinesApi::class)
+
+package com.android.systemui.statusbar.pipeline.repository
+
+import android.annotation.SuppressLint
+import android.net.ConnectivityManager
+import android.net.Network
+import android.net.NetworkCapabilities
+import android.net.NetworkRequest
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.statusbar.pipeline.ConnectivityPipelineLogger
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.SharingStarted.Companion.Lazily
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.callbackFlow
+import kotlinx.coroutines.flow.stateIn
+
+/** Repository that contains all relevant [NetworkCapabilites] for the current networks */
+@SysUISingleton
+class NetworkCapabilitiesRepo @Inject constructor(
+    connectivityManager: ConnectivityManager,
+    @Application scope: CoroutineScope,
+    logger: ConnectivityPipelineLogger,
+) {
+    @SuppressLint("MissingPermission")
+    val dataStream: StateFlow<Map<Int, NetworkCapabilityInfo>> = run {
+        var state = emptyMap<Int, NetworkCapabilityInfo>()
+        callbackFlow {
+                val networkRequest: NetworkRequest =
+                    NetworkRequest.Builder()
+                        .clearCapabilities()
+                        .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN)
+                        .addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
+                        .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
+                        .build()
+                val callback =
+                    // TODO (b/240569788): log these using [LogBuffer]
+                    object : ConnectivityManager.NetworkCallback(FLAG_INCLUDE_LOCATION_INFO) {
+                        override fun onCapabilitiesChanged(
+                            network: Network,
+                            networkCapabilities: NetworkCapabilities
+                        ) {
+                            logger.logOnCapabilitiesChanged(network, networkCapabilities)
+                            state =
+                                state.toMutableMap().also {
+                                    it[network.getNetId()] =
+                                        NetworkCapabilityInfo(network, networkCapabilities)
+                                }
+                            trySend(state)
+                        }
+
+                        override fun onLost(network: Network) {
+                            logger.logOnLost(network)
+                            state = state.toMutableMap().also { it.remove(network.getNetId()) }
+                            trySend(state)
+                        }
+                    }
+                connectivityManager.registerNetworkCallback(networkRequest, callback)
+
+                awaitClose { connectivityManager.unregisterNetworkCallback(callback) }
+            }
+            .stateIn(scope, started = Lazily, initialValue = state)
+    }
+}
+
+/** contains info about network capabilities. */
+data class NetworkCapabilityInfo(
+    val network: Network,
+    val capabilities: NetworkCapabilities,
+)
+
+private const val TAG = "ConnectivityRepository"
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerExt.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerExt.kt
new file mode 100644
index 0000000..b3f1eeb
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerExt.kt
@@ -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.systemui.statusbar.policy
+
+import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
+import com.android.systemui.common.coroutine.ConflatedCallbackFlow
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+
+object KeyguardStateControllerExt {
+    /**
+     * Returns an observable for whether the keyguard is currently shown or not.
+     */
+    fun KeyguardStateController.isKeyguardShowing(loggingTag: String): Flow<Boolean> {
+        return ConflatedCallbackFlow.conflatedCallbackFlow {
+            val callback =
+                object : KeyguardStateController.Callback {
+                    override fun onKeyguardShowingChanged() {
+                        trySendWithFailureLogging(
+                            isShowing, loggingTag, "updated isKeyguardShowing")
+                    }
+                }
+
+            addCallback(callback)
+            // Adding the callback does not send an initial update.
+            trySendWithFailureLogging(isShowing, loggingTag, "initial isKeyguardShowing")
+
+            awaitClose { removeCallback(callback) }
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/unfold/FoldAodAnimationController.kt b/packages/SystemUI/src/com/android/systemui/unfold/FoldAodAnimationController.kt
index d6dfcea..8f127fd 100644
--- a/packages/SystemUI/src/com/android/systemui/unfold/FoldAodAnimationController.kt
+++ b/packages/SystemUI/src/com/android/systemui/unfold/FoldAodAnimationController.kt
@@ -22,11 +22,12 @@
 import android.os.PowerManager
 import android.provider.Settings
 import androidx.core.view.OneShotPreDrawListener
+import com.android.internal.util.LatencyTracker
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.keyguard.WakefulnessLifecycle
 import com.android.systemui.statusbar.LightRevealScrim
-import com.android.systemui.statusbar.phone.ScreenOffAnimation
 import com.android.systemui.statusbar.phone.CentralSurfaces
+import com.android.systemui.statusbar.phone.ScreenOffAnimation
 import com.android.systemui.statusbar.policy.CallbackController
 import com.android.systemui.unfold.FoldAodAnimationController.FoldAodAnimationStatus
 import com.android.systemui.util.settings.GlobalSettings
@@ -47,7 +48,8 @@
     private val context: Context,
     private val deviceStateManager: DeviceStateManager,
     private val wakefulnessLifecycle: WakefulnessLifecycle,
-    private val globalSettings: GlobalSettings
+    private val globalSettings: GlobalSettings,
+    private val latencyTracker: LatencyTracker,
 ) : CallbackController<FoldAodAnimationStatus>, ScreenOffAnimation, WakefulnessLifecycle.Observer {
 
     private lateinit var mCentralSurfaces: CentralSurfaces
@@ -64,12 +66,14 @@
     private var isAnimationPlaying = false
 
     private val statusListeners = arrayListOf<FoldAodAnimationStatus>()
+    private val foldToAodLatencyTracker = FoldToAodLatencyTracker()
 
     private val startAnimationRunnable = Runnable {
-        mCentralSurfaces.notificationPanelViewController.startFoldToAodAnimation {
-            // End action
-            setAnimationState(playing = false)
-        }
+        mCentralSurfaces.notificationPanelViewController.startFoldToAodAnimation(
+            /* startAction= */ { foldToAodLatencyTracker.onAnimationStarted() },
+            /* endAction= */ { setAnimationState(playing = false) },
+            /* cancelAction= */ { setAnimationState(playing = false) },
+        )
     }
 
     override fun initialize(centralSurfaces: CentralSurfaces, lightRevealScrim: LightRevealScrim) {
@@ -82,11 +86,13 @@
     /** Returns true if we should run fold to AOD animation */
     override fun shouldPlayAnimation(): Boolean = shouldPlayAnimation
 
-    override fun startAnimation(): Boolean =
-        if (alwaysOnEnabled &&
+    private fun shouldStartAnimation(): Boolean =
+        alwaysOnEnabled &&
             wakefulnessLifecycle.lastSleepReason == PowerManager.GO_TO_SLEEP_REASON_DEVICE_FOLD &&
             globalSettings.getString(Settings.Global.ANIMATOR_DURATION_SCALE) != "0"
-        ) {
+
+    override fun startAnimation(): Boolean =
+        if (shouldStartAnimation()) {
             setAnimationState(playing = true)
             mCentralSurfaces.notificationPanelViewController.prepareFoldToAodAnimation()
             true
@@ -97,6 +103,7 @@
 
     override fun onStartedWakingUp() {
         if (isAnimationPlaying) {
+            foldToAodLatencyTracker.cancel()
             handler.removeCallbacks(startAnimationRunnable)
             mCentralSurfaces.notificationPanelViewController.cancelFoldToAodAnimation()
         }
@@ -137,7 +144,8 @@
             // but we should wait for the initial animation preparations to be drawn
             // (setting initial alpha/translation)
             OneShotPreDrawListener.add(
-                mCentralSurfaces.notificationPanelViewController.view, onReady
+                mCentralSurfaces.notificationPanelViewController.view,
+                onReady
             )
         } else {
             // No animation, call ready callback immediately
@@ -209,5 +217,41 @@
                     isFoldHandled = false
                 }
                 this.isFolded = isFolded
-            })
+                if (isFolded) {
+                    foldToAodLatencyTracker.onFolded()
+                }
+            }
+        )
+
+    /**
+     * Tracks the latency of fold to AOD using [LatencyTracker].
+     *
+     * Events that trigger start and end are:
+     *
+     * - Start: Once [DeviceStateManager] sends the folded signal [FoldToAodLatencyTracker.onFolded]
+     * is called and latency tracking starts.
+     * - End: Once the fold -> AOD animation starts, [FoldToAodLatencyTracker.onAnimationStarted] is
+     * called, and latency tracking stops.
+     */
+    private inner class FoldToAodLatencyTracker {
+
+        /** Triggers the latency logging, if needed. */
+        fun onFolded() {
+            if (shouldStartAnimation()) {
+                latencyTracker.onActionStart(LatencyTracker.ACTION_FOLD_TO_AOD)
+            }
+        }
+        /**
+         * Called once the Fold -> AOD animation is started.
+         *
+         * For latency tracking, this determines the end of the fold to aod action.
+         */
+        fun onAnimationStarted() {
+            latencyTracker.onActionEnd(LatencyTracker.ACTION_FOLD_TO_AOD)
+        }
+
+        fun cancel() {
+            latencyTracker.onActionCancel(LatencyTracker.ACTION_FOLD_TO_AOD)
+        }
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/usb/StorageNotification.java b/packages/SystemUI/src/com/android/systemui/usb/StorageNotification.java
index 345fc99..4dc78f9 100644
--- a/packages/SystemUI/src/com/android/systemui/usb/StorageNotification.java
+++ b/packages/SystemUI/src/com/android/systemui/usb/StorageNotification.java
@@ -21,6 +21,7 @@
 import android.app.Notification.Action;
 import android.app.NotificationManager;
 import android.app.PendingIntent;
+import android.app.ActivityManager;
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
@@ -215,9 +216,11 @@
 
             } else {
                 // Boo, annoy the user to reinsert the private volume
-                final CharSequence title = mContext.getString(R.string.ext_media_missing_title,
+                final CharSequence title =
+                  mContext.getString(R.string.ext_media_missing_title,
                         rec.getNickname());
-                final CharSequence text = mContext.getString(R.string.ext_media_missing_message);
+                final CharSequence text =
+                  mContext.getString(R.string.ext_media_missing_message);
 
                 Notification.Builder builder =
                         new Notification.Builder(mContext, NotificationChannels.STORAGE)
@@ -381,8 +384,8 @@
         if (rec.isSnoozed() && disk.isAdoptable()) {
             return null;
         }
-
-        if (disk.isAdoptable() && !rec.isInited()) {
+        if (disk.isAdoptable() && !rec.isInited() && rec.getType() != VolumeInfo.TYPE_PUBLIC
+            && rec.getType() != VolumeInfo.TYPE_PRIVATE) {
             final CharSequence title = disk.getDescription();
             final CharSequence text = mContext.getString(
                     R.string.ext_media_new_notification_message, disk.getDescription());
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
index c94a915..13c3df3 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
@@ -378,11 +378,13 @@
 
         // The ringer and rows container has extra height at the top to fit the expanded ringer
         // drawer. This area should not be touchable unless the ringer drawer is open.
+        // In landscape the ringer expands to the left and it has to be ensured that if there
+        // are multiple rows they are touchable.
         if (view == mTopContainer && !mIsRingerDrawerOpen) {
             if (!isLandscape()) {
                 y += getRingerDrawerOpenExtraSize();
-            } else {
-                x += getRingerDrawerOpenExtraSize();
+            } else if (getRingerDrawerOpenExtraSize() > getVisibleRowsExtraSize()) {
+                x += (getRingerDrawerOpenExtraSize() - getVisibleRowsExtraSize());
             }
         }
 
@@ -1968,6 +1970,21 @@
         return (mRingerCount - 1) * mRingerDrawerItemSize;
     }
 
+    /**
+     * Return the size of the additionally visible rows next to the default stream.
+     * An additional row is visible for example while receiving a voice call.
+     */
+    private int getVisibleRowsExtraSize() {
+        VolumeRow activeRow = getActiveRow();
+        int visibleRows = 0;
+        for (final VolumeRow row : mRows) {
+            if (shouldBeVisibleH(row, activeRow)) {
+                visibleRows++;
+            }
+        }
+        return (visibleRows - 1) * (mDialogWidth + mRingerRowsPadding);
+    }
+
     private void updateBackgroundForDrawerClosedAmount() {
         if (mRingerAndDrawerContainerBackground == null) {
             return;
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java b/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java
index 3e07144..e22a896 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java
@@ -40,7 +40,6 @@
 import android.provider.Settings;
 import android.service.notification.NotificationListenerService.RankingMap;
 import android.service.notification.ZenModeConfig;
-import android.util.ArraySet;
 import android.util.Log;
 import android.util.Pair;
 import android.util.SparseArray;
@@ -83,6 +82,7 @@
 import java.util.HashMap;
 import java.util.List;
 import java.util.Optional;
+import java.util.Set;
 import java.util.concurrent.Executor;
 import java.util.function.Consumer;
 import java.util.function.IntConsumer;
@@ -262,7 +262,7 @@
             }
 
             @Override
-            public void getShouldRestoredEntries(ArraySet<String> savedBubbleKeys,
+            public void getShouldRestoredEntries(Set<String> savedBubbleKeys,
                     Consumer<List<BubbleEntry>> callback) {
                 sysuiMainExecutor.execute(() -> {
                     List<BubbleEntry> result = new ArrayList<>();
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java
index 68e49c0..dc87a6a 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java
@@ -318,7 +318,7 @@
     @Test
     public void onBouncerVisibilityChanged_withoutSidedSecurity_sideFpsHintHidden() {
         setupConditionsToEnableSideFpsHint();
-        setSidedSecurityMode(false);
+        setSideFpsHintEnabledFromResources(false);
         reset(mSidefpsController);
 
         mKeyguardSecurityContainerController.onBouncerVisibilityChanged(View.VISIBLE);
@@ -383,7 +383,7 @@
 
     private void setupConditionsToEnableSideFpsHint() {
         attachView();
-        setSidedSecurityMode(true);
+        setSideFpsHintEnabledFromResources(true);
         setFingerprintDetectionRunning(true);
         setNeedsStrongAuth(false);
     }
@@ -399,8 +399,9 @@
                 BiometricSourceType.FINGERPRINT);
     }
 
-    private void setSidedSecurityMode(boolean sided) {
-        when(mView.isSidedSecurityMode()).thenReturn(sided);
+    private void setSideFpsHintEnabledFromResources(boolean enabled) {
+        when(mResources.getBoolean(R.bool.config_show_sidefps_hint_on_bouncer)).thenReturn(
+                enabled);
     }
 
     private void setNeedsStrongAuth(boolean needed) {
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
index 84903d1..85ecfd3 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
@@ -86,6 +86,7 @@
 import com.android.internal.widget.ILockSettings;
 import com.android.internal.widget.LockPatternUtils;
 import com.android.keyguard.KeyguardUpdateMonitor.BiometricAuthenticated;
+import com.android.keyguard.logging.KeyguardUpdateMonitorLogger;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.biometrics.AuthController;
 import com.android.systemui.broadcast.BroadcastDispatcher;
@@ -179,6 +180,8 @@
     private KeyguardUpdateMonitorCallback mTestCallback;
     @Mock
     private ActiveUnlockConfig mActiveUnlockConfig;
+    @Mock
+    private KeyguardUpdateMonitorLogger mKeyguardUpdateMonitorLogger;
     // Direct executor
     private Executor mBackgroundExecutor = Runnable::run;
     private Executor mMainExecutor = Runnable::run;
@@ -1189,7 +1192,8 @@
                     mBackgroundExecutor, mMainExecutor,
                     mStatusBarStateController, mLockPatternUtils,
                     mAuthController, mTelephonyListenerManager,
-                    mInteractionJankMonitor, mLatencyTracker, mActiveUnlockConfig);
+                    mInteractionJankMonitor, mLatencyTracker, mActiveUnlockConfig,
+                    mKeyguardUpdateMonitorLogger);
             setStrongAuthTracker(KeyguardUpdateMonitorTest.this.mStrongAuthTracker);
         }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/SidefpsControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/SidefpsControllerTest.kt
index 6157ccb..8d969d0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/SidefpsControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/SidefpsControllerTest.kt
@@ -45,6 +45,8 @@
 import android.view.ViewPropertyAnimator
 import android.view.WindowInsets
 import android.view.WindowManager
+import android.view.WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION
+import android.view.WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY
 import android.view.WindowMetrics
 import androidx.test.filters.SmallTest
 import com.airbnb.lottie.LottieAnimationView
@@ -438,6 +440,44 @@
 
         assertThat(fingerprintManager.hasSideFpsSensor()).isFalse()
     }
+
+    @Test
+    fun testLayoutParams_hasNoMoveAnimationWindowFlag() = testWithDisplay(
+        deviceConfig = DeviceConfig.Y_ALIGNED_UNFOLDED
+    ) {
+        sideFpsController.overlayOffsets = sensorLocation
+        sideFpsController.updateOverlayParams(
+            windowManager.defaultDisplay,
+            indicatorBounds
+        )
+        overlayController.show(SENSOR_ID, REASON_UNKNOWN)
+        executor.runAllReady()
+
+        verify(windowManager).updateViewLayout(any(), overlayViewParamsCaptor.capture())
+
+        val lpFlags = overlayViewParamsCaptor.value.privateFlags
+
+        assertThat((lpFlags and PRIVATE_FLAG_NO_MOVE_ANIMATION) != 0).isTrue()
+    }
+
+    @Test
+    fun testLayoutParams_hasTrustedOverlayWindowFlag() = testWithDisplay(
+        deviceConfig = DeviceConfig.Y_ALIGNED_UNFOLDED
+    ) {
+        sideFpsController.overlayOffsets = sensorLocation
+        sideFpsController.updateOverlayParams(
+            windowManager.defaultDisplay,
+            indicatorBounds
+        )
+        overlayController.show(SENSOR_ID, REASON_UNKNOWN)
+        executor.runAllReady()
+
+        verify(windowManager).updateViewLayout(any(), overlayViewParamsCaptor.capture())
+
+        val lpFlags = overlayViewParamsCaptor.value.privateFlags
+
+        assertThat((lpFlags and PRIVATE_FLAG_TRUSTED_OVERLAY) != 0).isTrue()
+    }
 }
 
 private fun insetsForSmallNavbar() = insetsWithBottom(60)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/DreamClockDateComplicationTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/DreamClockDateComplicationTest.java
deleted file mode 100644
index 86aa14d..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/DreamClockDateComplicationTest.java
+++ /dev/null
@@ -1,128 +0,0 @@
-/*
- * 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.dreams.complication;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.junit.Assert.assertEquals;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import android.content.Context;
-import android.testing.AndroidTestingRunner;
-import android.view.View;
-
-import androidx.test.filters.SmallTest;
-
-import com.android.systemui.SysuiTestCase;
-import com.android.systemui.dreams.DreamOverlayStateController;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-import javax.inject.Provider;
-
-@SmallTest
-@RunWith(AndroidTestingRunner.class)
-public class DreamClockDateComplicationTest extends SysuiTestCase {
-    @SuppressWarnings("HidingField")
-    @Mock
-    private Context mContext;
-
-    @Mock
-    private DreamOverlayStateController mDreamOverlayStateController;
-
-    @Mock
-    private DreamClockDateComplication mComplication;
-
-    @Mock
-    private Provider<DreamClockDateComplication.DreamClockDateViewHolder>
-            mDreamClockDateViewHolderProvider;
-
-    @Mock
-    private DreamClockDateComplication.DreamClockDateViewHolder
-            mDreamClockDateViewHolder;
-
-    @Mock
-    private ComplicationViewModel mComplicationViewModel;
-
-    @Mock
-    private View mView;
-
-    @Mock
-    private ComplicationLayoutParams mLayoutParams;
-
-    @Before
-    public void setup() {
-        MockitoAnnotations.initMocks(this);
-        when(mDreamClockDateViewHolderProvider.get()).thenReturn(mDreamClockDateViewHolder);
-
-    }
-
-    /**
-     * Ensures {@link DreamClockDateComplication} is registered.
-     */
-    @Test
-    public void testComplicationAdded() {
-        final DreamClockDateComplication.Registrant registrant =
-                new DreamClockDateComplication.Registrant(
-                        mContext,
-                        mDreamOverlayStateController,
-                        mComplication);
-        registrant.start();
-        verify(mDreamOverlayStateController).addComplication(eq(mComplication));
-    }
-
-    /**
-     * Verifies {@link DreamClockDateComplication} has the required type.
-     */
-    @Test
-    public void testComplicationRequiredTypeAvailability() {
-        final DreamClockDateComplication complication =
-                new DreamClockDateComplication(mDreamClockDateViewHolderProvider);
-        assertEquals(Complication.COMPLICATION_TYPE_DATE,
-                complication.getRequiredTypeAvailability());
-    }
-
-    /**
-     * Verifies {@link DreamClockDateComplication.DreamClockDateViewHolder} is obtainable from its
-     * provider when the complication creates view.
-     */
-    @Test
-    public void testComplicationViewHolderProviderOnCreateView() {
-        final DreamClockDateComplication complication =
-                new DreamClockDateComplication(mDreamClockDateViewHolderProvider);
-        final Complication.ViewHolder viewHolder = complication.createView(mComplicationViewModel);
-        verify(mDreamClockDateViewHolderProvider).get();
-        assertThat(viewHolder).isEqualTo(mDreamClockDateViewHolder);
-    }
-
-    /**
-     * Verifies {@link DreamClockDateComplication.DreamClockDateViewHolder} has the intended view
-     * and layout parameters from constructor.
-     */
-    @Test
-    public void testComplicationViewHolderContentAccessors() {
-        final DreamClockDateComplication.DreamClockDateViewHolder viewHolder =
-                new DreamClockDateComplication.DreamClockDateViewHolder(mView, mLayoutParams);
-        assertThat(viewHolder.getView()).isEqualTo(mView);
-        assertThat(viewHolder.getLayoutParams()).isEqualTo(mLayoutParams);
-    }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/SmartSpaceComplicationTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/SmartSpaceComplicationTest.java
similarity index 94%
rename from packages/SystemUI/tests/src/com/android/systemui/dreams/SmartSpaceComplicationTest.java
rename to packages/SystemUI/tests/src/com/android/systemui/dreams/complication/SmartSpaceComplicationTest.java
index 964e6d7..7d54758 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/SmartSpaceComplicationTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/SmartSpaceComplicationTest.java
@@ -13,11 +13,12 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package com.android.systemui.dreams;
+package com.android.systemui.dreams.complication;
 
 import static org.junit.Assert.assertEquals;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
@@ -30,8 +31,7 @@
 import androidx.test.filters.SmallTest;
 
 import com.android.systemui.SysuiTestCase;
-import com.android.systemui.dreams.complication.Complication;
-import com.android.systemui.dreams.complication.ComplicationViewModel;
+import com.android.systemui.dreams.DreamOverlayStateController;
 import com.android.systemui.dreams.smartspace.DreamSmartspaceController;
 import com.android.systemui.plugins.BcSmartspaceDataPlugin;
 
@@ -183,9 +183,9 @@
 
     @Test
     public void testGetView_reusesSameView() {
-        final SmartSpaceComplication complication = new SmartSpaceComplication(getContext(),
-                mSmartspaceController);
-        final Complication.ViewHolder viewHolder = complication.createView(mComplicationViewModel);
+        final Complication.ViewHolder viewHolder =
+                new SmartSpaceComplication.SmartSpaceComplicationViewHolder(getContext(),
+                        mSmartspaceController, mock(ComplicationLayoutParams.class));
         when(mSmartspaceController.buildAndConnectView(any())).thenReturn(mBcSmartspaceView);
         assertEquals(viewHolder.getView(), viewHolder.getView());
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/FakeFeatureFlags.kt b/packages/SystemUI/tests/src/com/android/systemui/flags/FakeFeatureFlags.kt
new file mode 100644
index 0000000..b2a4e33
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/flags/FakeFeatureFlags.kt
@@ -0,0 +1,93 @@
+/*
+ * 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.flags
+
+import android.util.SparseArray
+import android.util.SparseBooleanArray
+import androidx.core.util.containsKey
+import java.lang.IllegalStateException
+
+class FakeFeatureFlags : FeatureFlags {
+    private val booleanFlags = SparseBooleanArray()
+    private val stringFlags = SparseArray<String>()
+    private val knownFlagNames = mutableMapOf<Int, String>()
+
+    init {
+        Flags.getFlagFields().forEach { field ->
+            val flag: Flag<*> = field.get(null) as Flag<*>
+            knownFlagNames[flag.id] = field.name
+        }
+    }
+
+    fun set(flag: BooleanFlag, value: Boolean) {
+        booleanFlags.put(flag.id, value)
+    }
+
+    fun set(flag: DeviceConfigBooleanFlag, value: Boolean) {
+        booleanFlags.put(flag.id, value)
+    }
+
+    fun set(flag: ResourceBooleanFlag, value: Boolean) {
+        booleanFlags.put(flag.id, value)
+    }
+
+    fun set(flag: SysPropBooleanFlag, value: Boolean) {
+        booleanFlags.put(flag.id, value)
+    }
+
+    fun set(flag: StringFlag, value: String) {
+        stringFlags.put(flag.id, value)
+    }
+
+    fun set(flag: ResourceStringFlag, value: String) {
+        stringFlags.put(flag.id, value)
+    }
+
+    override fun isEnabled(flag: BooleanFlag): Boolean = requireBooleanValue(flag.id)
+
+    override fun isEnabled(flag: ResourceBooleanFlag): Boolean = requireBooleanValue(flag.id)
+
+    override fun isEnabled(flag: DeviceConfigBooleanFlag): Boolean = requireBooleanValue(flag.id)
+
+    override fun isEnabled(flag: SysPropBooleanFlag): Boolean = requireBooleanValue(flag.id)
+
+    override fun getString(flag: StringFlag): String = requireStringValue(flag.id)
+
+    override fun getString(flag: ResourceStringFlag): String = requireStringValue(flag.id)
+
+    override fun addListener(flag: Flag<*>, listener: FlagListenable.Listener) {}
+
+    override fun removeListener(listener: FlagListenable.Listener) {}
+
+    private fun flagName(flagId: Int): String {
+        return knownFlagNames[flagId] ?: "UNKNOWN(id=$flagId)"
+    }
+
+    private fun requireBooleanValue(flagId: Int): Boolean {
+        if (!booleanFlags.containsKey(flagId)) {
+            throw IllegalStateException("Flag ${flagName(flagId)} was accessed but not specified.")
+        }
+        return booleanFlags[flagId]
+    }
+
+    private fun requireStringValue(flagId: Int): String {
+        if (!stringFlags.containsKey(flagId)) {
+            throw IllegalStateException("Flag ${flagName(flagId)} was accessed but not specified.")
+        }
+        return stringFlags[flagId]
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/FakeFeatureFlagsTest.kt b/packages/SystemUI/tests/src/com/android/systemui/flags/FakeFeatureFlagsTest.kt
new file mode 100644
index 0000000..7d4b4f5
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/flags/FakeFeatureFlagsTest.kt
@@ -0,0 +1,106 @@
+/*
+ * 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.flags
+
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.google.common.truth.Truth.assertThat
+import java.lang.IllegalStateException
+import org.junit.Assert.fail
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class FakeFeatureFlagsTest : SysuiTestCase() {
+
+    private val booleanFlag = BooleanFlag(-1000)
+    private val stringFlag = StringFlag(-1001)
+    private val resourceBooleanFlag = ResourceBooleanFlag(-1002, resourceId = -1)
+    private val resourceStringFlag = ResourceStringFlag(-1003, resourceId = -1)
+    private val sysPropBooleanFlag = SysPropBooleanFlag(-1004, name = "test")
+
+    /**
+     * FakeFeatureFlags does not honor any default values. All flags which are accessed must be
+     * specified. If not, an exception is thrown.
+     */
+    @Test
+    fun throwsIfUnspecifiedFlagIsAccessed() {
+        val flags: FeatureFlags = FakeFeatureFlags()
+        try {
+            assertThat(flags.isEnabled(Flags.TEAMFOOD)).isFalse()
+            fail("Expected an exception when accessing an unspecified flag.")
+        } catch (ex: IllegalStateException) {
+            assertThat(ex.message).contains("TEAMFOOD")
+        }
+        try {
+            assertThat(flags.isEnabled(booleanFlag)).isFalse()
+            fail("Expected an exception when accessing an unspecified flag.")
+        } catch (ex: IllegalStateException) {
+            assertThat(ex.message).contains("UNKNOWN(id=-1000)")
+        }
+        try {
+            assertThat(flags.isEnabled(resourceBooleanFlag)).isFalse()
+            fail("Expected an exception when accessing an unspecified flag.")
+        } catch (ex: IllegalStateException) {
+            assertThat(ex.message).contains("UNKNOWN(id=-1002)")
+        }
+        try {
+            assertThat(flags.isEnabled(sysPropBooleanFlag)).isFalse()
+            fail("Expected an exception when accessing an unspecified flag.")
+        } catch (ex: IllegalStateException) {
+            assertThat(ex.message).contains("UNKNOWN(id=-1004)")
+        }
+        try {
+            assertThat(flags.getString(stringFlag)).isEmpty()
+            fail("Expected an exception when accessing an unspecified flag.")
+        } catch (ex: IllegalStateException) {
+            assertThat(ex.message).contains("UNKNOWN(id=-1001)")
+        }
+        try {
+            assertThat(flags.getString(resourceStringFlag)).isEmpty()
+            fail("Expected an exception when accessing an unspecified flag.")
+        } catch (ex: IllegalStateException) {
+            assertThat(ex.message).contains("UNKNOWN(id=-1003)")
+        }
+    }
+
+    @Test
+    fun specifiedFlagsReturnCorrectValues() {
+        val flags = FakeFeatureFlags()
+        flags.set(booleanFlag, false)
+        flags.set(resourceBooleanFlag, false)
+        flags.set(sysPropBooleanFlag, false)
+        flags.set(resourceStringFlag, "")
+
+        assertThat(flags.isEnabled(booleanFlag)).isFalse()
+        assertThat(flags.isEnabled(resourceBooleanFlag)).isFalse()
+        assertThat(flags.isEnabled(sysPropBooleanFlag)).isFalse()
+        assertThat(flags.getString(resourceStringFlag)).isEmpty()
+
+        flags.set(booleanFlag, true)
+        flags.set(resourceBooleanFlag, true)
+        flags.set(sysPropBooleanFlag, true)
+        flags.set(resourceStringFlag, "Android")
+
+        assertThat(flags.isEnabled(booleanFlag)).isTrue()
+        assertThat(flags.isEnabled(resourceBooleanFlag)).isTrue()
+        assertThat(flags.isEnabled(sysPropBooleanFlag)).isTrue()
+        assertThat(flags.getString(resourceStringFlag)).isEqualTo("Android")
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
index 9b66555..21c018a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
@@ -186,6 +186,31 @@
     }
 
     @Test
+    @TestableLooper.RunWithLooper(setAsMainLooper = true)
+    public void restoreBouncerWhenSimLockedAndKeyguardIsGoingAway_initiallyNotShowing() {
+        // When showing and provisioned
+        mViewMediator.onSystemReady();
+        when(mUpdateMonitor.isDeviceProvisioned()).thenReturn(true);
+        mViewMediator.setShowingLocked(false);
+
+        // and a SIM becomes locked and requires a PIN
+        mViewMediator.mUpdateCallback.onSimStateChanged(
+                1 /* subId */,
+                0 /* slotId */,
+                TelephonyManager.SIM_STATE_PIN_REQUIRED);
+
+        // and the keyguard goes away
+        mViewMediator.setShowingLocked(false);
+        when(mStatusBarKeyguardViewManager.isShowing()).thenReturn(false);
+        mViewMediator.mUpdateCallback.onKeyguardVisibilityChanged(false);
+
+        TestableLooper.get(this).processAllMessages();
+
+        // then make sure it comes back
+        verify(mStatusBarKeyguardViewManager, atLeast(1)).show(null);
+    }
+
+    @Test
     public void testBouncerPrompt_deviceLockedByAdmin() {
         // GIVEN no trust agents enabled and biometrics aren't enrolled
         when(mUpdateMonitor.isTrustUsuallyManaged(anyInt())).thenReturn(false);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/FakeKeyguardQuickAffordanceConfig.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/FakeKeyguardQuickAffordanceConfig.kt
new file mode 100644
index 0000000..6fff440
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/FakeKeyguardQuickAffordanceConfig.kt
@@ -0,0 +1,60 @@
+/*
+ * 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.keyguard.data.repository
+
+import com.android.systemui.animation.ActivityLaunchAnimator
+import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig
+import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig.OnClickedResult
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.yield
+
+/**
+ * Fake implementation of a quick affordance data source.
+ *
+ * This class is abstract to force tests to provide extensions of it as the system that references
+ * these configs uses each implementation's class type to refer to them.
+ */
+abstract class FakeKeyguardQuickAffordanceConfig : KeyguardQuickAffordanceConfig {
+
+    private val _onClickedInvocations = mutableListOf<ActivityLaunchAnimator.Controller?>()
+    val onClickedInvocations: List<ActivityLaunchAnimator.Controller?> = _onClickedInvocations
+
+    var onClickedResult: OnClickedResult = OnClickedResult.Handled
+
+    private val _state =
+        MutableStateFlow<KeyguardQuickAffordanceConfig.State>(
+            KeyguardQuickAffordanceConfig.State.Hidden
+        )
+    override val state: Flow<KeyguardQuickAffordanceConfig.State> = _state
+
+    override fun onQuickAffordanceClicked(
+        animationController: ActivityLaunchAnimator.Controller?,
+    ): OnClickedResult {
+        _onClickedInvocations.add(animationController)
+        return onClickedResult
+    }
+
+    suspend fun setState(state: KeyguardQuickAffordanceConfig.State) {
+        _state.value = state
+        // Yield to allow the test's collection coroutine to "catch up" and collect this value
+        // before the test continues to the next line.
+        // TODO(b/239834928): once coroutines.test is updated, switch to the approach described in
+        // https://developer.android.com/kotlin/flow/test#continuous-collection and remove this.
+        yield()
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/FakeKeyguardQuickAffordanceConfigs.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/FakeKeyguardQuickAffordanceConfigs.kt
new file mode 100644
index 0000000..a24fc93
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/FakeKeyguardQuickAffordanceConfigs.kt
@@ -0,0 +1,44 @@
+/*
+ * 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.keyguard.data.repository
+
+import com.android.systemui.keyguard.data.config.KeyguardQuickAffordanceConfigs
+import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig
+import com.android.systemui.keyguard.shared.model.KeyguardQuickAffordancePosition
+import kotlin.reflect.KClass
+
+/** Fake implementation of [KeyguardQuickAffordanceConfigs], for tests. */
+class FakeKeyguardQuickAffordanceConfigs(
+    private val configsByPosition:
+        Map<KeyguardQuickAffordancePosition, List<KeyguardQuickAffordanceConfig>>,
+) : KeyguardQuickAffordanceConfigs {
+
+    override fun getAll(
+        position: KeyguardQuickAffordancePosition
+    ): List<KeyguardQuickAffordanceConfig> {
+        return configsByPosition.getValue(position)
+    }
+
+    override fun get(
+        configClass: KClass<out KeyguardQuickAffordanceConfig>
+    ): KeyguardQuickAffordanceConfig {
+        return configsByPosition.values
+            .flatten()
+            .associateBy { config -> config::class }
+            .getValue(configClass)
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/FakeKeyguardQuickAffordanceRepository.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/FakeKeyguardQuickAffordanceRepository.kt
new file mode 100644
index 0000000..7d1cccb
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/FakeKeyguardQuickAffordanceRepository.kt
@@ -0,0 +1,55 @@
+/*
+ * 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.keyguard.data.repository
+
+import com.android.systemui.keyguard.shared.model.KeyguardQuickAffordanceModel
+import com.android.systemui.keyguard.shared.model.KeyguardQuickAffordancePosition
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.yield
+
+/** Fake implementation of [KeyguardQuickAffordanceRepository], for tests. */
+class FakeKeyguardQuickAffordanceRepository : KeyguardQuickAffordanceRepository {
+
+    private val modelByPosition =
+        mutableMapOf<
+                KeyguardQuickAffordancePosition, MutableStateFlow<KeyguardQuickAffordanceModel>>()
+
+    init {
+        KeyguardQuickAffordancePosition.values().forEach { value ->
+            modelByPosition[value] = MutableStateFlow(KeyguardQuickAffordanceModel.Hidden)
+        }
+    }
+
+    override fun affordance(
+        position: KeyguardQuickAffordancePosition
+    ): Flow<KeyguardQuickAffordanceModel> {
+        return modelByPosition.getValue(position)
+    }
+
+    suspend fun setModel(
+        position: KeyguardQuickAffordancePosition,
+        model: KeyguardQuickAffordanceModel
+    ) {
+        modelByPosition.getValue(position).value = model
+        // Yield to allow the test's collection coroutine to "catch up" and collect this value
+        // before the test continues to the next line.
+        // TODO(b/239834928): once coroutines.test is updated, switch to the approach described in
+        // https://developer.android.com/kotlin/flow/test#continuous-collection and remove this.
+        yield()
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt
new file mode 100644
index 0000000..c82803a
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt
@@ -0,0 +1,77 @@
+/*
+ * 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.keyguard.data.repository
+
+import com.android.systemui.common.data.model.Position
+import kotlinx.coroutines.channels.BufferOverflow
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableSharedFlow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+
+/** Fake implementation of [KeyguardRepository] */
+class FakeKeyguardRepository : KeyguardRepository {
+
+    private val _animateBottomAreaDozingTransitions = MutableStateFlow(false)
+    override val animateBottomAreaDozingTransitions: StateFlow<Boolean> =
+        _animateBottomAreaDozingTransitions
+
+    private val _bottomAreaAlpha = MutableStateFlow(1f)
+    override val bottomAreaAlpha: StateFlow<Float> = _bottomAreaAlpha
+
+    private val _clockPosition = MutableStateFlow(Position(0, 0))
+    override val clockPosition: StateFlow<Position> = _clockPosition
+
+    private val _isDozing =
+        MutableSharedFlow<Boolean>(
+            replay = 1,
+            onBufferOverflow = BufferOverflow.DROP_OLDEST,
+        )
+    override val isDozing: Flow<Boolean> = _isDozing
+
+    private val _dozeAmount =
+        MutableSharedFlow<Float>(
+            replay = 1,
+            onBufferOverflow = BufferOverflow.DROP_OLDEST,
+        )
+    override val dozeAmount: Flow<Float> = _dozeAmount
+
+    init {
+        setDozeAmount(0f)
+        setDozing(false)
+    }
+
+    override fun setAnimateDozingTransitions(animate: Boolean) {
+        _animateBottomAreaDozingTransitions.tryEmit(animate)
+    }
+
+    override fun setBottomAreaAlpha(alpha: Float) {
+        _bottomAreaAlpha.value = alpha
+    }
+
+    override fun setClockPosition(x: Int, y: Int) {
+        _clockPosition.value = Position(x, y)
+    }
+
+    fun setDozing(isDozing: Boolean) {
+        _isDozing.tryEmit(isDozing)
+    }
+
+    fun setDozeAmount(dozeAmount: Float) {
+        _dozeAmount.tryEmit(dozeAmount)
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/HomeControlsKeyguardQuickAffordanceConfigParameterizedStateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/HomeControlsKeyguardQuickAffordanceConfigParameterizedStateTest.kt
new file mode 100644
index 0000000..bcc76ab
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/HomeControlsKeyguardQuickAffordanceConfigParameterizedStateTest.kt
@@ -0,0 +1,133 @@
+/*
+ * 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.keyguard.data.repository
+
+import androidx.test.filters.SmallTest
+import com.android.systemui.R
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.controls.controller.ControlsController
+import com.android.systemui.controls.dagger.ControlsComponent
+import com.android.systemui.controls.management.ControlsListingController
+import com.android.systemui.keyguard.data.quickaffordance.HomeControlsKeyguardQuickAffordanceConfig
+import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig
+import com.android.systemui.util.mockito.mock
+import com.google.common.truth.Truth.assertThat
+import java.util.Optional
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.test.runBlockingTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+import org.junit.runners.Parameterized.Parameter
+import org.junit.runners.Parameterized.Parameters
+import org.mockito.ArgumentCaptor
+import org.mockito.Captor
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when` as whenever
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(Parameterized::class)
+class HomeControlsKeyguardQuickAffordanceConfigParameterizedStateTest : SysuiTestCase() {
+
+    companion object {
+        @Parameters(
+            name =
+                "feature enabled = {0}, has favorites = {1}, has service infos = {2} - expected" +
+                    " visible = {3}"
+        )
+        @JvmStatic
+        fun data() =
+            (0 until 8)
+                .map { combination ->
+                    arrayOf(
+                        /* isFeatureEnabled= */ combination and 0b100 != 0,
+                        /* hasFavorites= */ combination and 0b010 != 0,
+                        /* hasServiceInfos= */ combination and 0b001 != 0,
+                        /* isVisible= */ combination == 0b111,
+                    )
+                }
+                .toList()
+    }
+
+    @Mock private lateinit var component: ControlsComponent
+    @Mock private lateinit var controlsController: ControlsController
+    @Mock private lateinit var controlsListingController: ControlsListingController
+    @Captor
+    private lateinit var callbackCaptor:
+        ArgumentCaptor<ControlsListingController.ControlsListingCallback>
+
+    private lateinit var underTest: HomeControlsKeyguardQuickAffordanceConfig
+
+    @JvmField @Parameter(0) var isFeatureEnabled: Boolean = false
+    @JvmField @Parameter(1) var hasFavorites: Boolean = false
+    @JvmField @Parameter(2) var hasServiceInfos: Boolean = false
+    @JvmField @Parameter(3) var isVisible: Boolean = false
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+        whenever(component.getTileImageId()).thenReturn(R.drawable.controls_icon)
+        whenever(component.getTileTitleId()).thenReturn(R.string.quick_controls_title)
+        whenever(component.getControlsController()).thenReturn(Optional.of(controlsController))
+        whenever(component.getControlsListingController())
+            .thenReturn(Optional.of(controlsListingController))
+
+        underTest =
+            HomeControlsKeyguardQuickAffordanceConfig(
+                context = context,
+                component = component,
+            )
+    }
+
+    @Test
+    fun state() = runBlockingTest {
+        whenever(component.isEnabled()).thenReturn(isFeatureEnabled)
+        whenever(controlsController.getFavorites())
+            .thenReturn(
+                if (hasFavorites) {
+                    listOf(mock())
+                } else {
+                    emptyList()
+                }
+            )
+        val values = mutableListOf<KeyguardQuickAffordanceConfig.State>()
+        val job = underTest.state.onEach(values::add).launchIn(this)
+
+        verify(controlsListingController).addCallback(callbackCaptor.capture())
+        callbackCaptor.value.onServicesUpdated(
+            if (hasServiceInfos) {
+                listOf(mock())
+            } else {
+                emptyList()
+            }
+        )
+
+        assertThat(values.last())
+            .isInstanceOf(
+                if (isVisible) {
+                    KeyguardQuickAffordanceConfig.State.Visible::class.java
+                } else {
+                    KeyguardQuickAffordanceConfig.State.Hidden::class.java
+                }
+            )
+        job.cancel()
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/HomeControlsKeyguardQuickAffordanceConfigTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/HomeControlsKeyguardQuickAffordanceConfigTest.kt
new file mode 100644
index 0000000..9ce5724
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/HomeControlsKeyguardQuickAffordanceConfigTest.kt
@@ -0,0 +1,100 @@
+/*
+ * 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.keyguard.data.repository
+
+import androidx.test.filters.SmallTest
+import com.android.systemui.R
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.animation.ActivityLaunchAnimator
+import com.android.systemui.controls.controller.ControlsController
+import com.android.systemui.controls.dagger.ControlsComponent
+import com.android.systemui.keyguard.data.quickaffordance.HomeControlsKeyguardQuickAffordanceConfig
+import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig
+import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig.OnClickedResult
+import com.android.systemui.util.mockito.mock
+import com.google.common.truth.Truth.assertThat
+import java.util.Optional
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.test.runBlockingTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.mockito.Mock
+import org.mockito.Mockito.`when` as whenever
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(JUnit4::class)
+class HomeControlsKeyguardQuickAffordanceConfigTest : SysuiTestCase() {
+
+    @Mock private lateinit var component: ControlsComponent
+    @Mock private lateinit var animationController: ActivityLaunchAnimator.Controller
+
+    private lateinit var underTest: HomeControlsKeyguardQuickAffordanceConfig
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+
+        underTest =
+            HomeControlsKeyguardQuickAffordanceConfig(
+                context = context,
+                component = component,
+            )
+    }
+
+    @Test
+    fun `state - when listing controller is missing - returns None`() = runBlockingTest {
+        whenever(component.isEnabled()).thenReturn(true)
+        whenever(component.getTileImageId()).thenReturn(R.drawable.controls_icon)
+        whenever(component.getTileTitleId()).thenReturn(R.string.quick_controls_title)
+        val controlsController = mock<ControlsController>()
+        whenever(component.getControlsController()).thenReturn(Optional.of(controlsController))
+        whenever(component.getControlsListingController()).thenReturn(Optional.empty())
+        whenever(controlsController.getFavorites()).thenReturn(listOf(mock()))
+
+        val values = mutableListOf<KeyguardQuickAffordanceConfig.State>()
+        val job = underTest.state.onEach(values::add).launchIn(this)
+
+        assertThat(values.last())
+            .isInstanceOf(KeyguardQuickAffordanceConfig.State.Hidden::class.java)
+        job.cancel()
+    }
+
+    @Test
+    fun `onQuickAffordanceClicked - canShowWhileLockedSetting is true`() = runBlockingTest {
+        whenever(component.canShowWhileLockedSetting).thenReturn(MutableStateFlow(true))
+
+        val onClickedResult = underTest.onQuickAffordanceClicked(animationController)
+
+        assertThat(onClickedResult).isInstanceOf(OnClickedResult.StartActivity::class.java)
+        assertThat((onClickedResult as OnClickedResult.StartActivity).canShowWhileLocked).isTrue()
+    }
+
+    @Test
+    fun `onQuickAffordanceClicked - canShowWhileLockedSetting is false`() = runBlockingTest {
+        whenever(component.canShowWhileLockedSetting).thenReturn(MutableStateFlow(false))
+
+        val onClickedResult = underTest.onQuickAffordanceClicked(animationController)
+
+        assertThat(onClickedResult).isInstanceOf(OnClickedResult.StartActivity::class.java)
+        assertThat((onClickedResult as OnClickedResult.StartActivity).canShowWhileLocked).isFalse()
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepositoryImplTest.kt
new file mode 100644
index 0000000..dc0e6f7
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepositoryImplTest.kt
@@ -0,0 +1,193 @@
+/*
+ * 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.keyguard.data.repository
+
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig
+import com.android.systemui.keyguard.shared.model.KeyguardQuickAffordanceModel
+import com.android.systemui.keyguard.shared.model.KeyguardQuickAffordancePosition
+import com.android.systemui.util.mockito.mock
+import com.google.common.truth.Truth.assertThat
+import kotlin.reflect.KClass
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.test.runBlockingTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(JUnit4::class)
+class KeyguardQuickAffordanceRepositoryImplTest : SysuiTestCase() {
+
+    private lateinit var underTest: KeyguardQuickAffordanceRepository
+
+    private lateinit var homeControls: FakeKeyguardQuickAffordanceConfig
+    private lateinit var quickAccessWallet: FakeKeyguardQuickAffordanceConfig
+    private lateinit var qrCodeScanner: FakeKeyguardQuickAffordanceConfig
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+
+        homeControls = object : FakeKeyguardQuickAffordanceConfig() {}
+        quickAccessWallet = object : FakeKeyguardQuickAffordanceConfig() {}
+        qrCodeScanner = object : FakeKeyguardQuickAffordanceConfig() {}
+
+        underTest =
+            KeyguardQuickAffordanceRepositoryImpl(
+                configs =
+                    FakeKeyguardQuickAffordanceConfigs(
+                        mapOf(
+                            KeyguardQuickAffordancePosition.BOTTOM_START to
+                                listOf(
+                                    homeControls,
+                                ),
+                            KeyguardQuickAffordancePosition.BOTTOM_END to
+                                listOf(
+                                    quickAccessWallet,
+                                    qrCodeScanner,
+                                ),
+                        ),
+                    ),
+            )
+    }
+
+    @Test
+    fun `bottom start affordance - none`() = runBlockingTest {
+        // TODO(b/239834928): once coroutines.test is updated, switch to the approach described in
+        // https://developer.android.com/kotlin/flow/test#continuous-collection
+        var latest: KeyguardQuickAffordanceModel? = null
+        val job =
+            underTest
+                .affordance(KeyguardQuickAffordancePosition.BOTTOM_START)
+                .onEach { latest = it }
+                .launchIn(this)
+
+        assertThat(latest).isEqualTo(KeyguardQuickAffordanceModel.Hidden)
+        job.cancel()
+    }
+
+    @Test
+    fun `bottom start affordance - home controls`() = runBlockingTest {
+        // TODO(b/239834928): once coroutines.test is updated, switch to the approach described in
+        // https://developer.android.com/kotlin/flow/test#continuous-collection
+        var latest: KeyguardQuickAffordanceModel? = null
+        val job =
+            underTest
+                .affordance(KeyguardQuickAffordancePosition.BOTTOM_START)
+                .onEach { latest = it }
+                .launchIn(this)
+
+        val state =
+            KeyguardQuickAffordanceConfig.State.Visible(
+                icon = mock(),
+                contentDescriptionResourceId = CONTENT_DESCRIPTION_RESOURCE_ID,
+            )
+        homeControls.setState(state)
+
+        assertThat(latest).isEqualTo(state.toModel(homeControls::class))
+        job.cancel()
+    }
+
+    @Test
+    fun `bottom end affordance - none`() = runBlockingTest {
+        // TODO(b/239834928): once coroutines.test is updated, switch to the approach described in
+        // https://developer.android.com/kotlin/flow/test#continuous-collection
+        var latest: KeyguardQuickAffordanceModel? = null
+        val job =
+            underTest
+                .affordance(KeyguardQuickAffordancePosition.BOTTOM_END)
+                .onEach { latest = it }
+                .launchIn(this)
+
+        assertThat(latest).isEqualTo(KeyguardQuickAffordanceModel.Hidden)
+        job.cancel()
+    }
+
+    @Test
+    fun `bottom end affordance - quick access wallet`() = runBlockingTest {
+        // TODO(b/239834928): once coroutines.test is updated, switch to the approach described in
+        // https://developer.android.com/kotlin/flow/test#continuous-collection
+        var latest: KeyguardQuickAffordanceModel? = null
+        val job =
+            underTest
+                .affordance(KeyguardQuickAffordancePosition.BOTTOM_END)
+                .onEach { latest = it }
+                .launchIn(this)
+
+        val quickAccessWalletState =
+            KeyguardQuickAffordanceConfig.State.Visible(
+                icon = mock(),
+                contentDescriptionResourceId = CONTENT_DESCRIPTION_RESOURCE_ID,
+            )
+        quickAccessWallet.setState(quickAccessWalletState)
+        val qrCodeScannerState =
+            KeyguardQuickAffordanceConfig.State.Visible(
+                icon = mock(),
+                contentDescriptionResourceId = CONTENT_DESCRIPTION_RESOURCE_ID,
+            )
+        qrCodeScanner.setState(qrCodeScannerState)
+
+        assertThat(latest).isEqualTo(quickAccessWalletState.toModel(quickAccessWallet::class))
+        job.cancel()
+    }
+
+    @Test
+    fun `bottom end affordance - qr code scanner`() = runBlockingTest {
+        // TODO(b/239834928): once coroutines.test is updated, switch to the approach described in
+        // https://developer.android.com/kotlin/flow/test#continuous-collection
+        var latest: KeyguardQuickAffordanceModel? = null
+        val job =
+            underTest
+                .affordance(KeyguardQuickAffordancePosition.BOTTOM_END)
+                .onEach { latest = it }
+                .launchIn(this)
+
+        val state =
+            KeyguardQuickAffordanceConfig.State.Visible(
+                icon = mock(),
+                contentDescriptionResourceId = CONTENT_DESCRIPTION_RESOURCE_ID,
+            )
+        qrCodeScanner.setState(state)
+
+        assertThat(latest).isEqualTo(state.toModel(qrCodeScanner::class))
+        job.cancel()
+    }
+
+    private fun KeyguardQuickAffordanceConfig.State?.toModel(
+        configKey: KClass<out KeyguardQuickAffordanceConfig>,
+    ): KeyguardQuickAffordanceModel? {
+        return when (this) {
+            is KeyguardQuickAffordanceConfig.State.Visible ->
+                KeyguardQuickAffordanceModel.Visible(
+                    configKey = configKey,
+                    icon = icon,
+                    contentDescriptionResourceId = CONTENT_DESCRIPTION_RESOURCE_ID,
+                )
+            is KeyguardQuickAffordanceConfig.State.Hidden -> KeyguardQuickAffordanceModel.Hidden
+            null -> null
+        }
+    }
+
+    companion object {
+        private const val CONTENT_DESCRIPTION_RESOURCE_ID = 1337
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt
new file mode 100644
index 0000000..2ee8034
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt
@@ -0,0 +1,137 @@
+/*
+ * 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.keyguard.data.repository
+
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.common.data.model.Position
+import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.util.mockito.argumentCaptor
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.test.runBlockingTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(JUnit4::class)
+class KeyguardRepositoryImplTest : SysuiTestCase() {
+
+    @Mock private lateinit var statusBarStateController: StatusBarStateController
+
+    private lateinit var underTest: KeyguardRepositoryImpl
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+
+        underTest = KeyguardRepositoryImpl(statusBarStateController)
+    }
+
+    @Test
+    fun animateBottomAreaDozingTransitions() = runBlockingTest {
+        assertThat(underTest.animateBottomAreaDozingTransitions.value).isEqualTo(false)
+
+        underTest.setAnimateDozingTransitions(true)
+        assertThat(underTest.animateBottomAreaDozingTransitions.value).isTrue()
+
+        underTest.setAnimateDozingTransitions(false)
+        assertThat(underTest.animateBottomAreaDozingTransitions.value).isFalse()
+
+        underTest.setAnimateDozingTransitions(true)
+        assertThat(underTest.animateBottomAreaDozingTransitions.value).isTrue()
+    }
+
+    @Test
+    fun bottomAreaAlpha() = runBlockingTest {
+        assertThat(underTest.bottomAreaAlpha.value).isEqualTo(1f)
+
+        underTest.setBottomAreaAlpha(0.1f)
+        assertThat(underTest.bottomAreaAlpha.value).isEqualTo(0.1f)
+
+        underTest.setBottomAreaAlpha(0.2f)
+        assertThat(underTest.bottomAreaAlpha.value).isEqualTo(0.2f)
+
+        underTest.setBottomAreaAlpha(0.3f)
+        assertThat(underTest.bottomAreaAlpha.value).isEqualTo(0.3f)
+
+        underTest.setBottomAreaAlpha(0.5f)
+        assertThat(underTest.bottomAreaAlpha.value).isEqualTo(0.5f)
+
+        underTest.setBottomAreaAlpha(1.0f)
+        assertThat(underTest.bottomAreaAlpha.value).isEqualTo(1f)
+    }
+
+    @Test
+    fun clockPosition() = runBlockingTest {
+        assertThat(underTest.clockPosition.value).isEqualTo(Position(0, 0))
+
+        underTest.setClockPosition(0, 1)
+        assertThat(underTest.clockPosition.value).isEqualTo(Position(0, 1))
+
+        underTest.setClockPosition(1, 9)
+        assertThat(underTest.clockPosition.value).isEqualTo(Position(1, 9))
+
+        underTest.setClockPosition(1, 0)
+        assertThat(underTest.clockPosition.value).isEqualTo(Position(1, 0))
+
+        underTest.setClockPosition(3, 1)
+        assertThat(underTest.clockPosition.value).isEqualTo(Position(3, 1))
+    }
+
+    @Test
+    fun isDozing() = runBlockingTest {
+        var latest: Boolean? = null
+        val job = underTest.isDozing.onEach { latest = it }.launchIn(this)
+
+        val captor = argumentCaptor<StatusBarStateController.StateListener>()
+        verify(statusBarStateController).addCallback(captor.capture())
+
+        captor.value.onDozingChanged(true)
+        assertThat(latest).isTrue()
+
+        captor.value.onDozingChanged(false)
+        assertThat(latest).isFalse()
+
+        job.cancel()
+        verify(statusBarStateController).removeCallback(captor.value)
+    }
+
+    @Test
+    fun dozeAmount() = runBlockingTest {
+        val values = mutableListOf<Float>()
+        val job = underTest.dozeAmount.onEach(values::add).launchIn(this)
+
+        val captor = argumentCaptor<StatusBarStateController.StateListener>()
+        verify(statusBarStateController).addCallback(captor.capture())
+
+        captor.value.onDozeAmountChanged(0.433f, 0.4f)
+        captor.value.onDozeAmountChanged(0.498f, 0.5f)
+        captor.value.onDozeAmountChanged(0.661f, 0.65f)
+
+        assertThat(values).isEqualTo(listOf(0f, 0.4f, 0.5f, 0.65f))
+
+        job.cancel()
+        verify(statusBarStateController).removeCallback(captor.value)
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/QrCodeScannerKeyguardQuickAffordanceConfigTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/QrCodeScannerKeyguardQuickAffordanceConfigTest.kt
new file mode 100644
index 0000000..4bef00a
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/QrCodeScannerKeyguardQuickAffordanceConfigTest.kt
@@ -0,0 +1,149 @@
+/*
+ * 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.keyguard.data.repository
+
+import android.content.Intent
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig
+import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig.OnClickedResult
+import com.android.systemui.keyguard.data.quickaffordance.QrCodeScannerKeyguardQuickAffordanceConfig
+import com.android.systemui.qrcodescanner.controller.QRCodeScannerController
+import com.android.systemui.util.mockito.argumentCaptor
+import com.android.systemui.util.mockito.mock
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.test.runBlockingTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when` as whenever
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(JUnit4::class)
+class QrCodeScannerKeyguardQuickAffordanceConfigTest : SysuiTestCase() {
+
+    @Mock private lateinit var controller: QRCodeScannerController
+
+    private lateinit var underTest: QrCodeScannerKeyguardQuickAffordanceConfig
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+        whenever(controller.intent).thenReturn(INTENT_1)
+
+        underTest = QrCodeScannerKeyguardQuickAffordanceConfig(context, controller)
+    }
+
+    @Test
+    fun `affordance - sets up registration and delivers initial model`() = runBlockingTest {
+        whenever(controller.isEnabledForLockScreenButton).thenReturn(true)
+        var latest: KeyguardQuickAffordanceConfig.State? = null
+
+        val job = underTest.state.onEach { latest = it }.launchIn(this)
+
+        val callbackCaptor = argumentCaptor<QRCodeScannerController.Callback>()
+        verify(controller).addCallback(callbackCaptor.capture())
+        verify(controller)
+            .registerQRCodeScannerChangeObservers(
+                QRCodeScannerController.DEFAULT_QR_CODE_SCANNER_CHANGE,
+                QRCodeScannerController.QR_CODE_SCANNER_PREFERENCE_CHANGE
+            )
+        assertVisibleState(latest)
+
+        job.cancel()
+        verify(controller).removeCallback(callbackCaptor.value)
+    }
+
+    @Test
+    fun `affordance - scanner activity changed - delivers model with updated intent`() =
+        runBlockingTest {
+            whenever(controller.isEnabledForLockScreenButton).thenReturn(true)
+            var latest: KeyguardQuickAffordanceConfig.State? = null
+            val job = underTest.state.onEach { latest = it }.launchIn(this)
+            val callbackCaptor = argumentCaptor<QRCodeScannerController.Callback>()
+            verify(controller).addCallback(callbackCaptor.capture())
+
+            whenever(controller.intent).thenReturn(INTENT_2)
+            callbackCaptor.value.onQRCodeScannerActivityChanged()
+
+            assertVisibleState(latest)
+
+            job.cancel()
+            verify(controller).removeCallback(callbackCaptor.value)
+        }
+
+    @Test
+    fun `affordance - scanner preference changed - delivers visible model`() = runBlockingTest {
+        var latest: KeyguardQuickAffordanceConfig.State? = null
+        val job = underTest.state.onEach { latest = it }.launchIn(this)
+        val callbackCaptor = argumentCaptor<QRCodeScannerController.Callback>()
+        verify(controller).addCallback(callbackCaptor.capture())
+
+        whenever(controller.isEnabledForLockScreenButton).thenReturn(true)
+        callbackCaptor.value.onQRCodeScannerPreferenceChanged()
+
+        assertVisibleState(latest)
+
+        job.cancel()
+        verify(controller).removeCallback(callbackCaptor.value)
+    }
+
+    @Test
+    fun `affordance - scanner preference changed - delivers none`() = runBlockingTest {
+        var latest: KeyguardQuickAffordanceConfig.State? = null
+        val job = underTest.state.onEach { latest = it }.launchIn(this)
+        val callbackCaptor = argumentCaptor<QRCodeScannerController.Callback>()
+        verify(controller).addCallback(callbackCaptor.capture())
+
+        whenever(controller.isEnabledForLockScreenButton).thenReturn(false)
+        callbackCaptor.value.onQRCodeScannerPreferenceChanged()
+
+        assertThat(latest).isEqualTo(KeyguardQuickAffordanceConfig.State.Hidden)
+
+        job.cancel()
+        verify(controller).removeCallback(callbackCaptor.value)
+    }
+
+    @Test
+    fun onQuickAffordanceClicked() {
+        assertThat(underTest.onQuickAffordanceClicked(mock()))
+            .isEqualTo(
+                OnClickedResult.StartActivity(
+                    intent = INTENT_1,
+                    canShowWhileLocked = true,
+                )
+            )
+    }
+
+    private fun assertVisibleState(latest: KeyguardQuickAffordanceConfig.State?) {
+        assertThat(latest).isInstanceOf(KeyguardQuickAffordanceConfig.State.Visible::class.java)
+        val visibleState = latest as KeyguardQuickAffordanceConfig.State.Visible
+        assertThat(visibleState.icon).isNotNull()
+        assertThat(visibleState.contentDescriptionResourceId).isNotNull()
+    }
+
+    companion object {
+        private val INTENT_1 = Intent("intent1")
+        private val INTENT_2 = Intent("intent2")
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/QuickAccessWalletKeyguardQuickAffordanceConfigTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/QuickAccessWalletKeyguardQuickAffordanceConfigTest.kt
new file mode 100644
index 0000000..ee1d4d8
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/QuickAccessWalletKeyguardQuickAffordanceConfigTest.kt
@@ -0,0 +1,213 @@
+/*
+ * 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.keyguard.data.repository
+
+import android.graphics.drawable.Drawable
+import android.service.quickaccesswallet.GetWalletCardsResponse
+import android.service.quickaccesswallet.QuickAccessWalletClient
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.animation.ActivityLaunchAnimator
+import com.android.systemui.containeddrawable.ContainedDrawable
+import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig
+import com.android.systemui.keyguard.data.quickaffordance.QuickAccessWalletKeyguardQuickAffordanceConfig
+import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.wallet.controller.QuickAccessWalletController
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.test.runBlockingTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when` as whenever
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(JUnit4::class)
+class QuickAccessWalletKeyguardQuickAffordanceConfigTest : SysuiTestCase() {
+
+    @Mock private lateinit var walletController: QuickAccessWalletController
+    @Mock private lateinit var keyguardStateController: KeyguardStateController
+    @Mock private lateinit var activityStarter: ActivityStarter
+
+    private lateinit var underTest: QuickAccessWalletKeyguardQuickAffordanceConfig
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+
+        underTest =
+            QuickAccessWalletKeyguardQuickAffordanceConfig(
+                keyguardStateController,
+                walletController,
+                activityStarter,
+            )
+    }
+
+    @Test
+    fun `affordance - keyguard showing - has wallet card - visible model`() = runBlockingTest {
+        val callback = setUpState()
+        var latest: KeyguardQuickAffordanceConfig.State? = null
+
+        val job = underTest.state.onEach { latest = it }.launchIn(this)
+
+        val visibleModel = latest as KeyguardQuickAffordanceConfig.State.Visible
+        assertThat(visibleModel.icon).isEqualTo(ContainedDrawable.WithDrawable(ICON))
+        assertThat(visibleModel.contentDescriptionResourceId).isNotNull()
+        job.cancel()
+        callback?.let { verify(keyguardStateController).removeCallback(it) }
+    }
+
+    @Test
+    fun `affordance - keyguard not showing - model is none`() = runBlockingTest {
+        val callback = setUpState(isKeyguardShowing = false)
+        var latest: KeyguardQuickAffordanceConfig.State? = null
+
+        val job = underTest.state.onEach { latest = it }.launchIn(this)
+
+        assertThat(latest).isEqualTo(KeyguardQuickAffordanceConfig.State.Hidden)
+
+        job.cancel()
+        callback?.let { verify(keyguardStateController).removeCallback(it) }
+    }
+
+    @Test
+    fun `affordance - wallet not enabled - model is none`() = runBlockingTest {
+        val callback = setUpState(isWalletEnabled = false)
+        var latest: KeyguardQuickAffordanceConfig.State? = null
+
+        val job = underTest.state.onEach { latest = it }.launchIn(this)
+
+        assertThat(latest).isEqualTo(KeyguardQuickAffordanceConfig.State.Hidden)
+
+        job.cancel()
+        callback?.let { verify(keyguardStateController).removeCallback(it) }
+    }
+
+    @Test
+    fun `affordance - query not successful - model is none`() = runBlockingTest {
+        val callback = setUpState(isWalletQuerySuccessful = false)
+        var latest: KeyguardQuickAffordanceConfig.State? = null
+
+        val job = underTest.state.onEach { latest = it }.launchIn(this)
+
+        assertThat(latest).isEqualTo(KeyguardQuickAffordanceConfig.State.Hidden)
+
+        job.cancel()
+        callback?.let { verify(keyguardStateController).removeCallback(it) }
+    }
+
+    @Test
+    fun `affordance - missing icon - model is none`() = runBlockingTest {
+        val callback = setUpState(hasWalletIcon = false)
+        var latest: KeyguardQuickAffordanceConfig.State? = null
+
+        val job = underTest.state.onEach { latest = it }.launchIn(this)
+
+        assertThat(latest).isEqualTo(KeyguardQuickAffordanceConfig.State.Hidden)
+
+        job.cancel()
+        callback?.let { verify(keyguardStateController).removeCallback(it) }
+    }
+
+    @Test
+    fun `affordance - no selected card - model is none`() = runBlockingTest {
+        val callback = setUpState(hasWalletIcon = false)
+        var latest: KeyguardQuickAffordanceConfig.State? = null
+
+        val job = underTest.state.onEach { latest = it }.launchIn(this)
+
+        assertThat(latest).isEqualTo(KeyguardQuickAffordanceConfig.State.Hidden)
+
+        job.cancel()
+        callback?.let { verify(keyguardStateController).removeCallback(it) }
+    }
+
+    @Test
+    fun onQuickAffordanceClicked() {
+        val animationController: ActivityLaunchAnimator.Controller = mock()
+
+        assertThat(underTest.onQuickAffordanceClicked(animationController))
+            .isEqualTo(KeyguardQuickAffordanceConfig.OnClickedResult.Handled)
+        verify(walletController)
+            .startQuickAccessUiIntent(
+                activityStarter,
+                animationController,
+                /* hasCard= */ true,
+            )
+    }
+
+    private fun setUpState(
+        isKeyguardShowing: Boolean = true,
+        isWalletEnabled: Boolean = true,
+        isWalletQuerySuccessful: Boolean = true,
+        hasWalletIcon: Boolean = true,
+        hasSelectedCard: Boolean = true,
+    ): KeyguardStateController.Callback? {
+        var returnedCallback: KeyguardStateController.Callback? = null
+        whenever(keyguardStateController.isShowing).thenReturn(isKeyguardShowing)
+        whenever(keyguardStateController.addCallback(any())).thenAnswer { invocation ->
+            with(invocation.arguments[0] as KeyguardStateController.Callback) {
+                returnedCallback = this
+                onKeyguardShowingChanged()
+            }
+        }
+
+        whenever(walletController.isWalletEnabled).thenReturn(isWalletEnabled)
+
+        val walletClient: QuickAccessWalletClient = mock()
+        val icon: Drawable? =
+            if (hasWalletIcon) {
+                ICON
+            } else {
+                null
+            }
+        whenever(walletClient.tileIcon).thenReturn(icon)
+        whenever(walletController.walletClient).thenReturn(walletClient)
+
+        whenever(walletController.queryWalletCards(any())).thenAnswer { invocation ->
+            with(
+                invocation.arguments[0] as QuickAccessWalletClient.OnWalletCardsRetrievedCallback
+            ) {
+                if (isWalletQuerySuccessful) {
+                    onWalletCardsRetrieved(
+                        if (hasSelectedCard) {
+                            GetWalletCardsResponse(listOf(mock()), 0)
+                        } else {
+                            GetWalletCardsResponse(emptyList(), 0)
+                        }
+                    )
+                } else {
+                    onWalletCardRetrievalError(mock())
+                }
+            }
+        }
+
+        return returnedCallback
+    }
+
+    companion object {
+        private val ICON: Drawable = mock()
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/usecase/FakeLaunchKeyguardQuickAffordanceUseCase.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/usecase/FakeLaunchKeyguardQuickAffordanceUseCase.kt
new file mode 100644
index 0000000..ba0c31f
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/usecase/FakeLaunchKeyguardQuickAffordanceUseCase.kt
@@ -0,0 +1,47 @@
+/*
+ * 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.keyguard.domain.usecase
+
+import android.content.Intent
+import com.android.systemui.animation.ActivityLaunchAnimator
+
+/** Fake implementation of [LaunchKeyguardQuickAffordanceUseCase], for tests. */
+class FakeLaunchKeyguardQuickAffordanceUseCase : LaunchKeyguardQuickAffordanceUseCase {
+
+    data class Invocation(
+        val intent: Intent,
+        val canShowWhileLocked: Boolean,
+        val animationController: ActivityLaunchAnimator.Controller?
+    )
+
+    private val _invocations = mutableListOf<Invocation>()
+    val invocations: List<Invocation> = _invocations
+
+    override fun invoke(
+        intent: Intent,
+        canShowWhileLocked: Boolean,
+        animationController: ActivityLaunchAnimator.Controller?
+    ) {
+        _invocations.add(
+            Invocation(
+                intent = intent,
+                canShowWhileLocked = canShowWhileLocked,
+                animationController = animationController,
+            )
+        )
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/usecase/LaunchKeyguardQuickAffordanceUseCaseImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/usecase/LaunchKeyguardQuickAffordanceUseCaseImplTest.kt
new file mode 100644
index 0000000..b3c1ae0
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/usecase/LaunchKeyguardQuickAffordanceUseCaseImplTest.kt
@@ -0,0 +1,178 @@
+/*
+ * 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.keyguard.domain.usecase
+
+import android.content.Intent
+import androidx.test.filters.SmallTest
+import com.android.internal.widget.LockPatternUtils
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.animation.ActivityLaunchAnimator
+import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.settings.UserTracker
+import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.mock
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+import org.junit.runners.Parameterized.Parameter
+import org.junit.runners.Parameterized.Parameters
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when` as whenever
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(Parameterized::class)
+class LaunchKeyguardQuickAffordanceUseCaseImplTest : SysuiTestCase() {
+
+    companion object {
+        private val INTENT = Intent("some.intent.action")
+
+        @Parameters(
+            name =
+                "needStrongAuthAfterBoot={0}, canShowWhileLocked={1}," +
+                    " keyguardIsUnlocked={2}, needsToUnlockFirst={3}"
+        )
+        @JvmStatic
+        fun data() =
+            listOf(
+                arrayOf(
+                    /* needStrongAuthAfterBoot= */ false,
+                    /* canShowWhileLocked= */ false,
+                    /* keyguardIsUnlocked= */ false,
+                    /* needsToUnlockFirst= */ true,
+                ),
+                arrayOf(
+                    /* needStrongAuthAfterBoot= */ false,
+                    /* canShowWhileLocked= */ false,
+                    /* keyguardIsUnlocked= */ true,
+                    /* needsToUnlockFirst= */ false,
+                ),
+                arrayOf(
+                    /* needStrongAuthAfterBoot= */ false,
+                    /* canShowWhileLocked= */ true,
+                    /* keyguardIsUnlocked= */ false,
+                    /* needsToUnlockFirst= */ false,
+                ),
+                arrayOf(
+                    /* needStrongAuthAfterBoot= */ false,
+                    /* canShowWhileLocked= */ true,
+                    /* keyguardIsUnlocked= */ true,
+                    /* needsToUnlockFirst= */ false,
+                ),
+                arrayOf(
+                    /* needStrongAuthAfterBoot= */ true,
+                    /* canShowWhileLocked= */ false,
+                    /* keyguardIsUnlocked= */ false,
+                    /* needsToUnlockFirst= */ true,
+                ),
+                arrayOf(
+                    /* needStrongAuthAfterBoot= */ true,
+                    /* canShowWhileLocked= */ false,
+                    /* keyguardIsUnlocked= */ true,
+                    /* needsToUnlockFirst= */ true,
+                ),
+                arrayOf(
+                    /* needStrongAuthAfterBoot= */ true,
+                    /* canShowWhileLocked= */ true,
+                    /* keyguardIsUnlocked= */ false,
+                    /* needsToUnlockFirst= */ true,
+                ),
+                arrayOf(
+                    /* needStrongAuthAfterBoot= */ true,
+                    /* canShowWhileLocked= */ true,
+                    /* keyguardIsUnlocked= */ true,
+                    /* needsToUnlockFirst= */ true,
+                ),
+            )
+    }
+
+    @Mock private lateinit var lockPatternUtils: LockPatternUtils
+    @Mock private lateinit var keyguardStateController: KeyguardStateController
+    @Mock private lateinit var userTracker: UserTracker
+    @Mock private lateinit var activityStarter: ActivityStarter
+    @Mock private lateinit var animationController: ActivityLaunchAnimator.Controller
+
+    private lateinit var underTest: LaunchKeyguardQuickAffordanceUseCase
+
+    @JvmField @Parameter(0) var needStrongAuthAfterBoot: Boolean = false
+    @JvmField @Parameter(1) var canShowWhileLocked: Boolean = false
+    @JvmField @Parameter(2) var keyguardIsUnlocked: Boolean = false
+    @JvmField @Parameter(3) var needsToUnlockFirst: Boolean = false
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+
+        underTest =
+            LaunchKeyguardQuickAffordanceUseCaseImpl(
+                lockPatternUtils = lockPatternUtils,
+                keyguardStateController = keyguardStateController,
+                userTracker = userTracker,
+                activityStarter = activityStarter,
+            )
+    }
+
+    @Test
+    fun invoke() {
+        setUpMocks(
+            needStrongAuthAfterBoot = needStrongAuthAfterBoot,
+            keyguardIsUnlocked = keyguardIsUnlocked,
+        )
+
+        underTest(
+            intent = INTENT,
+            canShowWhileLocked = canShowWhileLocked,
+            animationController = animationController,
+        )
+
+        if (needsToUnlockFirst) {
+            verify(activityStarter)
+                .postStartActivityDismissingKeyguard(
+                    INTENT,
+                    /* delay= */ 0,
+                    animationController,
+                )
+        } else {
+            verify(activityStarter)
+                .startActivity(
+                    INTENT,
+                    /* dismissShade= */ true,
+                    animationController,
+                    /* showOverLockscreenWhenLocked= */ true,
+                )
+        }
+    }
+
+    private fun setUpMocks(
+        needStrongAuthAfterBoot: Boolean = true,
+        keyguardIsUnlocked: Boolean = false,
+    ) {
+        whenever(userTracker.userHandle).thenReturn(mock())
+        whenever(lockPatternUtils.getStrongAuthForUser(any()))
+            .thenReturn(
+                if (needStrongAuthAfterBoot) {
+                    LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_BOOT
+                } else {
+                    LockPatternUtils.StrongAuthTracker.STRONG_AUTH_NOT_REQUIRED
+                }
+            )
+        whenever(keyguardStateController.isUnlocked).thenReturn(keyguardIsUnlocked)
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/usecase/ObserveKeyguardQuickAffordanceUseCaseTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/usecase/ObserveKeyguardQuickAffordanceUseCaseTest.kt
new file mode 100644
index 0000000..a60657c5
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/usecase/ObserveKeyguardQuickAffordanceUseCaseTest.kt
@@ -0,0 +1,156 @@
+/*
+ * 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.keyguard.domain.usecase
+
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.containeddrawable.ContainedDrawable
+import com.android.systemui.keyguard.data.quickaffordance.HomeControlsKeyguardQuickAffordanceConfig
+import com.android.systemui.keyguard.data.repository.FakeKeyguardQuickAffordanceRepository
+import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
+import com.android.systemui.keyguard.shared.model.KeyguardQuickAffordanceModel
+import com.android.systemui.keyguard.shared.model.KeyguardQuickAffordancePosition
+import com.android.systemui.util.mockito.mock
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.test.runBlockingTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@SmallTest
+@RunWith(JUnit4::class)
+class ObserveKeyguardQuickAffordanceUseCaseTest : SysuiTestCase() {
+
+    private lateinit var underTest: ObserveKeyguardQuickAffordanceUseCase
+
+    private lateinit var repository: FakeKeyguardRepository
+    private lateinit var quickAffordanceRepository: FakeKeyguardQuickAffordanceRepository
+    private lateinit var isDozingUseCase: ObserveIsDozingUseCase
+    private lateinit var dozeAmountUseCase: ObserveDozeAmountUseCase
+
+    @Before
+    fun setUp() {
+        repository = FakeKeyguardRepository()
+        isDozingUseCase = ObserveIsDozingUseCase(repository)
+        dozeAmountUseCase = ObserveDozeAmountUseCase(repository)
+        quickAffordanceRepository = FakeKeyguardQuickAffordanceRepository()
+
+        underTest =
+            ObserveKeyguardQuickAffordanceUseCase(
+                repository = quickAffordanceRepository,
+                isDozingUseCase = isDozingUseCase,
+                dozeAmountUseCase = dozeAmountUseCase,
+            )
+    }
+
+    @Test
+    fun `invoke - affordance is visible`() = runBlockingTest {
+        val configKey = HomeControlsKeyguardQuickAffordanceConfig::class
+        val model =
+            KeyguardQuickAffordanceModel.Visible(
+                configKey = configKey,
+                icon = ICON,
+                contentDescriptionResourceId = CONTENT_DESCRIPTION_RESOURCE_ID,
+            )
+        quickAffordanceRepository.setModel(
+            KeyguardQuickAffordancePosition.BOTTOM_END,
+            model,
+        )
+
+        var latest: KeyguardQuickAffordanceModel? = null
+        val job = underTest(KeyguardQuickAffordancePosition.BOTTOM_END)
+            .onEach { latest = it }
+            .launchIn(this)
+
+        assertThat(latest).isInstanceOf(KeyguardQuickAffordanceModel.Visible::class.java)
+        val visibleModel = latest as KeyguardQuickAffordanceModel.Visible
+        assertThat(visibleModel.configKey).isEqualTo(configKey)
+        assertThat(visibleModel.icon).isEqualTo(ICON)
+        assertThat(visibleModel.contentDescriptionResourceId)
+            .isEqualTo(CONTENT_DESCRIPTION_RESOURCE_ID)
+        job.cancel()
+    }
+
+    @Test
+    fun `invoke - affordance not visible while dozing`() = runBlockingTest {
+        repository.setDozing(true)
+        val configKey = HomeControlsKeyguardQuickAffordanceConfig::class
+        val model =
+            KeyguardQuickAffordanceModel.Visible(
+                configKey = configKey,
+                icon = ICON,
+                contentDescriptionResourceId = CONTENT_DESCRIPTION_RESOURCE_ID,
+            )
+        quickAffordanceRepository.setModel(
+            KeyguardQuickAffordancePosition.BOTTOM_END,
+            model,
+        )
+
+        var latest: KeyguardQuickAffordanceModel? = null
+        val job = underTest(KeyguardQuickAffordancePosition.BOTTOM_END)
+            .onEach { latest = it }
+            .launchIn(this)
+        assertThat(latest).isEqualTo(KeyguardQuickAffordanceModel.Hidden)
+        job.cancel()
+    }
+
+    @Test
+    fun `invoke - affordance not visible doze amount is not 0`() = runBlockingTest {
+        repository.setDozeAmount(0.3f)
+        val configKey = HomeControlsKeyguardQuickAffordanceConfig::class
+        val model =
+            KeyguardQuickAffordanceModel.Visible(
+                configKey = configKey,
+                icon = ICON,
+                contentDescriptionResourceId = CONTENT_DESCRIPTION_RESOURCE_ID,
+            )
+        quickAffordanceRepository.setModel(
+            KeyguardQuickAffordancePosition.BOTTOM_END,
+            model,
+        )
+
+        var latest: KeyguardQuickAffordanceModel? = null
+        val job = underTest(KeyguardQuickAffordancePosition.BOTTOM_END)
+            .onEach { latest = it }
+            .launchIn(this)
+        assertThat(latest).isEqualTo(KeyguardQuickAffordanceModel.Hidden)
+        job.cancel()
+    }
+
+    @Test
+    fun `invoke - affordance is none`() = runBlockingTest {
+        quickAffordanceRepository.setModel(
+            KeyguardQuickAffordancePosition.BOTTOM_START,
+            KeyguardQuickAffordanceModel.Hidden,
+        )
+
+        var latest: KeyguardQuickAffordanceModel? = null
+        val job = underTest(KeyguardQuickAffordancePosition.BOTTOM_START)
+            .onEach { latest = it }
+            .launchIn(this)
+        assertThat(latest).isEqualTo(KeyguardQuickAffordanceModel.Hidden)
+        job.cancel()
+    }
+
+    companion object {
+        private val ICON: ContainedDrawable = mock()
+        private const val CONTENT_DESCRIPTION_RESOURCE_ID = 1337
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt
new file mode 100644
index 0000000..cb9cbba
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt
@@ -0,0 +1,467 @@
+/*
+ * 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.keyguard.ui.viewmodel
+
+import android.content.Intent
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.animation.ActivityLaunchAnimator
+import com.android.systemui.containeddrawable.ContainedDrawable
+import com.android.systemui.doze.util.BurnInHelperWrapper
+import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig
+import com.android.systemui.keyguard.data.repository.FakeKeyguardQuickAffordanceConfig
+import com.android.systemui.keyguard.data.repository.FakeKeyguardQuickAffordanceConfigs
+import com.android.systemui.keyguard.data.repository.FakeKeyguardQuickAffordanceRepository
+import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
+import com.android.systemui.keyguard.domain.usecase.FakeLaunchKeyguardQuickAffordanceUseCase
+import com.android.systemui.keyguard.domain.usecase.ObserveAnimateBottomAreaTransitionsUseCase
+import com.android.systemui.keyguard.domain.usecase.ObserveBottomAreaAlphaUseCase
+import com.android.systemui.keyguard.domain.usecase.ObserveClockPositionUseCase
+import com.android.systemui.keyguard.domain.usecase.ObserveDozeAmountUseCase
+import com.android.systemui.keyguard.domain.usecase.ObserveIsDozingUseCase
+import com.android.systemui.keyguard.domain.usecase.ObserveKeyguardQuickAffordanceUseCase
+import com.android.systemui.keyguard.domain.usecase.OnKeyguardQuickAffordanceClickedUseCase
+import com.android.systemui.keyguard.shared.model.KeyguardQuickAffordanceModel
+import com.android.systemui.keyguard.shared.model.KeyguardQuickAffordancePosition
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.mock
+import com.google.common.truth.Truth.assertThat
+import kotlin.reflect.KClass
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.test.runBlockingTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.Mock
+import org.mockito.Mockito.`when` as whenever
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(JUnit4::class)
+class KeyguardBottomAreaViewModelTest : SysuiTestCase() {
+
+    @Mock private lateinit var animationController: ActivityLaunchAnimator.Controller
+    @Mock private lateinit var burnInHelperWrapper: BurnInHelperWrapper
+
+    private lateinit var underTest: KeyguardBottomAreaViewModel
+
+    private lateinit var affordanceRepository: FakeKeyguardQuickAffordanceRepository
+    private lateinit var repository: FakeKeyguardRepository
+    private lateinit var isDozingUseCase: ObserveIsDozingUseCase
+    private lateinit var dozeAmountUseCase: ObserveDozeAmountUseCase
+    private lateinit var launchQuickAffordanceUseCase: FakeLaunchKeyguardQuickAffordanceUseCase
+    private lateinit var homeControlsQuickAffordanceConfig: FakeKeyguardQuickAffordanceConfig
+    private lateinit var quickAccessWalletAffordanceConfig: FakeKeyguardQuickAffordanceConfig
+    private lateinit var qrCodeScannerAffordanceConfig: FakeKeyguardQuickAffordanceConfig
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+        whenever(burnInHelperWrapper.burnInOffset(anyInt(), any()))
+            .thenReturn(RETURNED_BURN_IN_OFFSET)
+
+        affordanceRepository = FakeKeyguardQuickAffordanceRepository()
+        repository = FakeKeyguardRepository()
+        isDozingUseCase =
+            ObserveIsDozingUseCase(
+                repository = repository,
+            )
+        dozeAmountUseCase =
+            ObserveDozeAmountUseCase(
+                repository = repository,
+            )
+        launchQuickAffordanceUseCase = FakeLaunchKeyguardQuickAffordanceUseCase()
+        homeControlsQuickAffordanceConfig = object : FakeKeyguardQuickAffordanceConfig() {}
+        quickAccessWalletAffordanceConfig = object : FakeKeyguardQuickAffordanceConfig() {}
+        qrCodeScannerAffordanceConfig = object : FakeKeyguardQuickAffordanceConfig() {}
+
+        underTest =
+            KeyguardBottomAreaViewModel(
+                observeQuickAffordanceUseCase =
+                    ObserveKeyguardQuickAffordanceUseCase(
+                        repository = affordanceRepository,
+                        isDozingUseCase = isDozingUseCase,
+                        dozeAmountUseCase = dozeAmountUseCase,
+                    ),
+                onQuickAffordanceClickedUseCase =
+                    OnKeyguardQuickAffordanceClickedUseCase(
+                        configs =
+                            FakeKeyguardQuickAffordanceConfigs(
+                                mapOf(
+                                    KeyguardQuickAffordancePosition.BOTTOM_START to
+                                        listOf(
+                                            homeControlsQuickAffordanceConfig,
+                                        ),
+                                    KeyguardQuickAffordancePosition.BOTTOM_END to
+                                        listOf(
+                                            quickAccessWalletAffordanceConfig,
+                                            qrCodeScannerAffordanceConfig,
+                                        ),
+                                ),
+                            ),
+                        launchAffordanceUseCase = launchQuickAffordanceUseCase,
+                    ),
+                observeBottomAreaAlphaUseCase =
+                    ObserveBottomAreaAlphaUseCase(
+                        repository = repository,
+                    ),
+                observeIsDozingUseCase = isDozingUseCase,
+                observeAnimateBottomAreaTransitionsUseCase =
+                    ObserveAnimateBottomAreaTransitionsUseCase(
+                        repository = repository,
+                    ),
+                observeDozeAmountUseCase =
+                    ObserveDozeAmountUseCase(
+                        repository = repository,
+                    ),
+                observeClockPositionUseCase =
+                    ObserveClockPositionUseCase(
+                        repository = repository,
+                    ),
+                burnInHelperWrapper = burnInHelperWrapper,
+            )
+    }
+
+    @Test
+    fun `startButton - present and not dozing - visible model - starts activity on click`() =
+        runBlockingTest {
+            var latest: KeyguardQuickAffordanceViewModel? = null
+            val job = underTest.startButton.onEach { latest = it }.launchIn(this)
+
+            repository.setDozing(false)
+            val testConfig =
+                TestConfig(
+                    isVisible = true,
+                    icon = mock(),
+                    canShowWhileLocked = false,
+                    intent = Intent("action"),
+                )
+            val configKey =
+                setUpQuickAffordanceModel(
+                    position = KeyguardQuickAffordancePosition.BOTTOM_START,
+                    testConfig = testConfig,
+                )
+
+            assertQuickAffordanceViewModel(
+                viewModel = latest,
+                testConfig = testConfig,
+                configKey = configKey,
+            )
+            job.cancel()
+        }
+
+    @Test
+    fun `endButton - present and not dozing - visible model - do nothing on click`() =
+        runBlockingTest {
+            var latest: KeyguardQuickAffordanceViewModel? = null
+            val job = underTest.endButton.onEach { latest = it }.launchIn(this)
+
+            repository.setDozing(false)
+            val config =
+                TestConfig(
+                    isVisible = true,
+                    icon = mock(),
+                    canShowWhileLocked = false,
+                    intent =
+                        null, // This will cause it to tell the system that the click was handled.
+                )
+            val configKey =
+                setUpQuickAffordanceModel(
+                    position = KeyguardQuickAffordancePosition.BOTTOM_END,
+                    testConfig = config,
+                )
+
+            assertQuickAffordanceViewModel(
+                viewModel = latest,
+                testConfig = config,
+                configKey = configKey,
+            )
+            job.cancel()
+        }
+
+    @Test
+    fun `startButton - not present and not dozing - model is none`() = runBlockingTest {
+        var latest: KeyguardQuickAffordanceViewModel? = null
+        val job = underTest.startButton.onEach { latest = it }.launchIn(this)
+
+        repository.setDozing(false)
+        val config =
+            TestConfig(
+                isVisible = false,
+            )
+        val configKey =
+            setUpQuickAffordanceModel(
+                position = KeyguardQuickAffordancePosition.BOTTOM_START,
+                testConfig = config,
+            )
+
+        assertQuickAffordanceViewModel(
+            viewModel = latest,
+            testConfig = config,
+            configKey = configKey,
+        )
+        job.cancel()
+    }
+
+    @Test
+    fun `startButton - present but dozing - model is none`() = runBlockingTest {
+        var latest: KeyguardQuickAffordanceViewModel? = null
+        val job = underTest.startButton.onEach { latest = it }.launchIn(this)
+
+        repository.setDozing(true)
+        val config =
+            TestConfig(
+                isVisible = true,
+                icon = mock(),
+                canShowWhileLocked = false,
+                intent = Intent("action"),
+            )
+        val configKey =
+            setUpQuickAffordanceModel(
+                position = KeyguardQuickAffordancePosition.BOTTOM_START,
+                testConfig = config,
+            )
+
+        assertQuickAffordanceViewModel(
+            viewModel = latest,
+            testConfig = TestConfig(isVisible = false),
+            configKey = configKey,
+        )
+        job.cancel()
+    }
+
+    @Test
+    fun animateButtonReveal() = runBlockingTest {
+        val values = mutableListOf<Boolean>()
+        val job = underTest.animateButtonReveal.onEach(values::add).launchIn(this)
+
+        repository.setAnimateDozingTransitions(true)
+        repository.setAnimateDozingTransitions(false)
+
+        assertThat(values).isEqualTo(listOf(false, true, false))
+        job.cancel()
+    }
+
+    @Test
+    fun isOverlayContainerVisible() = runBlockingTest {
+        val values = mutableListOf<Boolean>()
+        val job = underTest.isOverlayContainerVisible.onEach(values::add).launchIn(this)
+
+        repository.setDozing(true)
+        repository.setDozing(false)
+
+        assertThat(values).isEqualTo(listOf(true, false, true))
+        job.cancel()
+    }
+
+    @Test
+    fun alpha() = runBlockingTest {
+        val values = mutableListOf<Float>()
+        val job = underTest.alpha.onEach(values::add).launchIn(this)
+
+        repository.setBottomAreaAlpha(0.1f)
+        repository.setBottomAreaAlpha(0.5f)
+        repository.setBottomAreaAlpha(0.2f)
+        repository.setBottomAreaAlpha(0f)
+
+        assertThat(values).isEqualTo(listOf(1f, 0.1f, 0.5f, 0.2f, 0f))
+        job.cancel()
+    }
+
+    @Test
+    fun isIndicationAreaPadded() = runBlockingTest {
+        val values = mutableListOf<Boolean>()
+        val job = underTest.isIndicationAreaPadded.onEach(values::add).launchIn(this)
+
+        setUpQuickAffordanceModel(
+            position = KeyguardQuickAffordancePosition.BOTTOM_START,
+            testConfig =
+                TestConfig(
+                    isVisible = true,
+                    icon = mock(),
+                    canShowWhileLocked = true,
+                )
+        )
+        setUpQuickAffordanceModel(
+            position = KeyguardQuickAffordancePosition.BOTTOM_END,
+            testConfig =
+                TestConfig(
+                    isVisible = true,
+                    icon = mock(),
+                    canShowWhileLocked = false,
+                )
+        )
+        setUpQuickAffordanceModel(
+            position = KeyguardQuickAffordancePosition.BOTTOM_START,
+            testConfig =
+                TestConfig(
+                    isVisible = false,
+                )
+        )
+        setUpQuickAffordanceModel(
+            position = KeyguardQuickAffordancePosition.BOTTOM_END,
+            testConfig =
+                TestConfig(
+                    isVisible = false,
+                )
+        )
+
+        assertThat(values)
+            .isEqualTo(
+                listOf(
+                    // Initially, no button is visible so the indication area is not padded.
+                    false,
+                    // Once we add the first visible button, the indication area becomes padded.
+                    // This
+                    // continues to be true after we add the second visible button and even after we
+                    // make the first button not visible anymore.
+                    true,
+                    // Once both buttons are not visible, the indication area is, again, not padded.
+                    false,
+                )
+            )
+        job.cancel()
+    }
+
+    @Test
+    fun indicationAreaTranslationX() = runBlockingTest {
+        val values = mutableListOf<Float>()
+        val job = underTest.indicationAreaTranslationX.onEach(values::add).launchIn(this)
+
+        repository.setClockPosition(100, 100)
+        repository.setClockPosition(200, 100)
+        repository.setClockPosition(200, 200)
+        repository.setClockPosition(300, 100)
+
+        assertThat(values).isEqualTo(listOf(0f, 100f, 200f, 300f))
+        job.cancel()
+    }
+
+    @Test
+    fun indicationAreaTranslationY() = runBlockingTest {
+        val values = mutableListOf<Float>()
+        val job =
+            underTest
+                .indicationAreaTranslationY(DEFAULT_BURN_IN_OFFSET)
+                .onEach(values::add)
+                .launchIn(this)
+
+        val expectedTranslationValues =
+            listOf(
+                -0f, // Negative 0 - apparently there's a difference in floating point arithmetic -
+                // FML
+                setDozeAmountAndCalculateExpectedTranslationY(0.1f),
+                setDozeAmountAndCalculateExpectedTranslationY(0.2f),
+                setDozeAmountAndCalculateExpectedTranslationY(0.5f),
+                setDozeAmountAndCalculateExpectedTranslationY(1f),
+            )
+
+        assertThat(values).isEqualTo(expectedTranslationValues)
+        job.cancel()
+    }
+
+    private fun setDozeAmountAndCalculateExpectedTranslationY(dozeAmount: Float): Float {
+        repository.setDozeAmount(dozeAmount)
+        return dozeAmount * (RETURNED_BURN_IN_OFFSET - DEFAULT_BURN_IN_OFFSET)
+    }
+
+    private suspend fun setUpQuickAffordanceModel(
+        position: KeyguardQuickAffordancePosition,
+        testConfig: TestConfig,
+    ): KClass<*> {
+        val config =
+            when (position) {
+                KeyguardQuickAffordancePosition.BOTTOM_START -> homeControlsQuickAffordanceConfig
+                KeyguardQuickAffordancePosition.BOTTOM_END -> quickAccessWalletAffordanceConfig
+            }
+
+        affordanceRepository.setModel(
+            position = position,
+            model =
+                if (testConfig.isVisible) {
+                    if (testConfig.intent != null) {
+                        config.onClickedResult =
+                            KeyguardQuickAffordanceConfig.OnClickedResult.StartActivity(
+                                intent = testConfig.intent,
+                                canShowWhileLocked = testConfig.canShowWhileLocked,
+                            )
+                    }
+                    KeyguardQuickAffordanceModel.Visible(
+                        configKey = config::class,
+                        icon = testConfig.icon ?: error("Icon is unexpectedly null!"),
+                        contentDescriptionResourceId = CONTENT_DESCRIPTION_RESOURCE_ID,
+                    )
+                } else {
+                    KeyguardQuickAffordanceModel.Hidden
+                }
+        )
+        return config::class
+    }
+
+    private fun assertQuickAffordanceViewModel(
+        viewModel: KeyguardQuickAffordanceViewModel?,
+        testConfig: TestConfig,
+        configKey: KClass<*>,
+    ) {
+        checkNotNull(viewModel)
+        assertThat(viewModel.isVisible).isEqualTo(testConfig.isVisible)
+        if (testConfig.isVisible) {
+            assertThat(viewModel.icon).isEqualTo(testConfig.icon)
+            viewModel.onClicked.invoke(
+                KeyguardQuickAffordanceViewModel.OnClickedParameters(
+                    configKey = configKey,
+                    animationController = animationController,
+                )
+            )
+            testConfig.intent?.let { intent ->
+                assertThat(launchQuickAffordanceUseCase.invocations)
+                    .isEqualTo(
+                        listOf(
+                            FakeLaunchKeyguardQuickAffordanceUseCase.Invocation(
+                                intent = intent,
+                                canShowWhileLocked = testConfig.canShowWhileLocked,
+                                animationController = animationController,
+                            )
+                        )
+                    )
+            }
+                ?: run { assertThat(launchQuickAffordanceUseCase.invocations).isEmpty() }
+        } else {
+            assertThat(viewModel.isVisible).isFalse()
+        }
+    }
+
+    private data class TestConfig(
+        val isVisible: Boolean,
+        val icon: ContainedDrawable? = null,
+        val canShowWhileLocked: Boolean = false,
+        val intent: Intent? = null,
+    ) {
+        init {
+            check(!isVisible || icon != null) { "Must supply non-null icon if visible!" }
+        }
+    }
+
+    companion object {
+        private const val DEFAULT_BURN_IN_OFFSET = 5
+        private const val RETURNED_BURN_IN_OFFSET = 3
+        private const val CONTENT_DESCRIPTION_RESOURCE_ID = 1337
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/lifecycle/RepeatWhenAttachedTest.kt b/packages/SystemUI/tests/src/com/android/systemui/lifecycle/RepeatWhenAttachedTest.kt
new file mode 100644
index 0000000..80f3e46
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/lifecycle/RepeatWhenAttachedTest.kt
@@ -0,0 +1,319 @@
+/*
+ *  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.lifecycle
+
+import android.testing.TestableLooper.RunWithLooper
+import android.view.View
+import android.view.ViewTreeObserver
+import androidx.arch.core.executor.ArchTaskExecutor
+import androidx.arch.core.executor.TaskExecutor
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.LifecycleOwner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.util.Assert
+import com.android.systemui.util.mockito.argumentCaptor
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.DisposableHandle
+import kotlinx.coroutines.test.runBlockingTest
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.rules.TestWatcher
+import org.junit.runner.Description
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.mockito.Mock
+import org.mockito.Mockito.any
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when` as whenever
+import org.mockito.junit.MockitoJUnit
+
+@SmallTest
+@RunWith(JUnit4::class)
+@RunWithLooper
+class RepeatWhenAttachedTest : SysuiTestCase() {
+
+    @JvmField @Rule val mockito = MockitoJUnit.rule()
+    @JvmField @Rule val instantTaskExecutor = InstantTaskExecutorRule()
+
+    @Mock private lateinit var view: View
+    @Mock private lateinit var viewTreeObserver: ViewTreeObserver
+
+    private lateinit var block: Block
+    private lateinit var attachListeners: MutableList<View.OnAttachStateChangeListener>
+
+    @Before
+    fun setUp() {
+        Assert.setTestThread(Thread.currentThread())
+        whenever(view.viewTreeObserver).thenReturn(viewTreeObserver)
+        whenever(view.windowVisibility).thenReturn(View.GONE)
+        whenever(view.hasWindowFocus()).thenReturn(false)
+        attachListeners = mutableListOf()
+        whenever(view.addOnAttachStateChangeListener(any())).then {
+            attachListeners.add(it.arguments[0] as View.OnAttachStateChangeListener)
+        }
+        whenever(view.removeOnAttachStateChangeListener(any())).then {
+            attachListeners.remove(it.arguments[0] as View.OnAttachStateChangeListener)
+        }
+        block = Block()
+    }
+
+    @Test(expected = IllegalStateException::class)
+    fun `repeatWhenAttached - enforces main thread`() = runBlockingTest {
+        Assert.setTestThread(null)
+
+        repeatWhenAttached()
+    }
+
+    @Test(expected = IllegalStateException::class)
+    fun `repeatWhenAttached - dispose enforces main thread`() = runBlockingTest {
+        val disposableHandle = repeatWhenAttached()
+        Assert.setTestThread(null)
+
+        disposableHandle.dispose()
+    }
+
+    @Test
+    fun `repeatWhenAttached - view starts detached - runs block when attached`() = runBlockingTest {
+        whenever(view.isAttachedToWindow).thenReturn(false)
+        repeatWhenAttached()
+        assertThat(block.invocationCount).isEqualTo(0)
+
+        whenever(view.isAttachedToWindow).thenReturn(true)
+        attachListeners.last().onViewAttachedToWindow(view)
+
+        assertThat(block.invocationCount).isEqualTo(1)
+        assertThat(block.latestLifecycleState).isEqualTo(Lifecycle.State.CREATED)
+    }
+
+    @Test
+    fun `repeatWhenAttached - view already attached - immediately runs block`() = runBlockingTest {
+        whenever(view.isAttachedToWindow).thenReturn(true)
+
+        repeatWhenAttached()
+
+        assertThat(block.invocationCount).isEqualTo(1)
+        assertThat(block.latestLifecycleState).isEqualTo(Lifecycle.State.CREATED)
+    }
+
+    @Test
+    fun `repeatWhenAttached - starts visible without focus - STARTED`() = runBlockingTest {
+        whenever(view.isAttachedToWindow).thenReturn(true)
+        whenever(view.windowVisibility).thenReturn(View.VISIBLE)
+
+        repeatWhenAttached()
+
+        assertThat(block.invocationCount).isEqualTo(1)
+        assertThat(block.latestLifecycleState).isEqualTo(Lifecycle.State.STARTED)
+    }
+
+    @Test
+    fun `repeatWhenAttached - starts with focus but invisible - CREATED`() = runBlockingTest {
+        whenever(view.isAttachedToWindow).thenReturn(true)
+        whenever(view.hasWindowFocus()).thenReturn(true)
+
+        repeatWhenAttached()
+
+        assertThat(block.invocationCount).isEqualTo(1)
+        assertThat(block.latestLifecycleState).isEqualTo(Lifecycle.State.CREATED)
+    }
+
+    @Test
+    fun `repeatWhenAttached - starts visible and with focus - RESUMED`() = runBlockingTest {
+        whenever(view.isAttachedToWindow).thenReturn(true)
+        whenever(view.windowVisibility).thenReturn(View.VISIBLE)
+        whenever(view.hasWindowFocus()).thenReturn(true)
+
+        repeatWhenAttached()
+
+        assertThat(block.invocationCount).isEqualTo(1)
+        assertThat(block.latestLifecycleState).isEqualTo(Lifecycle.State.RESUMED)
+    }
+
+    @Test
+    fun `repeatWhenAttached - becomes visible without focus - STARTED`() = runBlockingTest {
+        whenever(view.isAttachedToWindow).thenReturn(true)
+        repeatWhenAttached()
+        val listenerCaptor = argumentCaptor<ViewTreeObserver.OnWindowVisibilityChangeListener>()
+        verify(viewTreeObserver).addOnWindowVisibilityChangeListener(listenerCaptor.capture())
+
+        whenever(view.windowVisibility).thenReturn(View.VISIBLE)
+        listenerCaptor.value.onWindowVisibilityChanged(View.VISIBLE)
+
+        assertThat(block.invocationCount).isEqualTo(1)
+        assertThat(block.latestLifecycleState).isEqualTo(Lifecycle.State.STARTED)
+    }
+
+    @Test
+    fun `repeatWhenAttached - gains focus but invisible - CREATED`() = runBlockingTest {
+        whenever(view.isAttachedToWindow).thenReturn(true)
+        repeatWhenAttached()
+        val listenerCaptor = argumentCaptor<ViewTreeObserver.OnWindowFocusChangeListener>()
+        verify(viewTreeObserver).addOnWindowFocusChangeListener(listenerCaptor.capture())
+
+        whenever(view.hasWindowFocus()).thenReturn(true)
+        listenerCaptor.value.onWindowFocusChanged(true)
+
+        assertThat(block.invocationCount).isEqualTo(1)
+        assertThat(block.latestLifecycleState).isEqualTo(Lifecycle.State.CREATED)
+    }
+
+    @Test
+    fun `repeatWhenAttached - becomes visible and gains focus - RESUMED`() = runBlockingTest {
+        whenever(view.isAttachedToWindow).thenReturn(true)
+        repeatWhenAttached()
+        val visibleCaptor = argumentCaptor<ViewTreeObserver.OnWindowVisibilityChangeListener>()
+        verify(viewTreeObserver).addOnWindowVisibilityChangeListener(visibleCaptor.capture())
+        val focusCaptor = argumentCaptor<ViewTreeObserver.OnWindowFocusChangeListener>()
+        verify(viewTreeObserver).addOnWindowFocusChangeListener(focusCaptor.capture())
+
+        whenever(view.windowVisibility).thenReturn(View.VISIBLE)
+        visibleCaptor.value.onWindowVisibilityChanged(View.VISIBLE)
+        whenever(view.hasWindowFocus()).thenReturn(true)
+        focusCaptor.value.onWindowFocusChanged(true)
+
+        assertThat(block.invocationCount).isEqualTo(1)
+        assertThat(block.latestLifecycleState).isEqualTo(Lifecycle.State.RESUMED)
+    }
+
+    @Test
+    fun `repeatWhenAttached - view gets detached - destroys the lifecycle`() = runBlockingTest {
+        whenever(view.isAttachedToWindow).thenReturn(true)
+        repeatWhenAttached()
+
+        whenever(view.isAttachedToWindow).thenReturn(false)
+        attachListeners.last().onViewDetachedFromWindow(view)
+
+        assertThat(block.invocationCount).isEqualTo(1)
+        assertThat(block.latestLifecycleState).isEqualTo(Lifecycle.State.DESTROYED)
+    }
+
+    @Test
+    fun `repeatWhenAttached - view gets reattached - recreates a lifecycle`() = runBlockingTest {
+        whenever(view.isAttachedToWindow).thenReturn(true)
+        repeatWhenAttached()
+        whenever(view.isAttachedToWindow).thenReturn(false)
+        attachListeners.last().onViewDetachedFromWindow(view)
+
+        whenever(view.isAttachedToWindow).thenReturn(true)
+        attachListeners.last().onViewAttachedToWindow(view)
+
+        assertThat(block.invocationCount).isEqualTo(2)
+        assertThat(block.invocations[0].lifecycleState).isEqualTo(Lifecycle.State.DESTROYED)
+        assertThat(block.invocations[1].lifecycleState).isEqualTo(Lifecycle.State.CREATED)
+    }
+
+    @Test
+    fun `repeatWhenAttached - dispose attached`() = runBlockingTest {
+        whenever(view.isAttachedToWindow).thenReturn(true)
+        val handle = repeatWhenAttached()
+
+        handle.dispose()
+
+        assertThat(attachListeners).isEmpty()
+        assertThat(block.invocationCount).isEqualTo(1)
+        assertThat(block.latestLifecycleState).isEqualTo(Lifecycle.State.DESTROYED)
+    }
+
+    @Test
+    fun `repeatWhenAttached - dispose never attached`() = runBlockingTest {
+        whenever(view.isAttachedToWindow).thenReturn(false)
+        val handle = repeatWhenAttached()
+
+        handle.dispose()
+
+        assertThat(attachListeners).isEmpty()
+        assertThat(block.invocationCount).isEqualTo(0)
+    }
+
+    @Test
+    fun `repeatWhenAttached - dispose previously attached now detached`() = runBlockingTest {
+        whenever(view.isAttachedToWindow).thenReturn(true)
+        val handle = repeatWhenAttached()
+        attachListeners.last().onViewDetachedFromWindow(view)
+
+        handle.dispose()
+
+        assertThat(attachListeners).isEmpty()
+        assertThat(block.invocationCount).isEqualTo(1)
+        assertThat(block.latestLifecycleState).isEqualTo(Lifecycle.State.DESTROYED)
+    }
+
+    private fun CoroutineScope.repeatWhenAttached(): DisposableHandle {
+        return view.repeatWhenAttached(
+            coroutineContext = coroutineContext,
+            block = block,
+        )
+    }
+
+    private class Block : suspend LifecycleOwner.(View) -> Unit {
+        data class Invocation(
+            val lifecycleOwner: LifecycleOwner,
+        ) {
+            val lifecycleState: Lifecycle.State
+                get() = lifecycleOwner.lifecycle.currentState
+        }
+
+        private val _invocations = mutableListOf<Invocation>()
+        val invocations: List<Invocation> = _invocations
+        val invocationCount: Int
+            get() = _invocations.size
+        val latestLifecycleState: Lifecycle.State
+            get() = _invocations.last().lifecycleState
+
+        override suspend fun invoke(lifecycleOwner: LifecycleOwner, view: View) {
+            _invocations.add(Invocation(lifecycleOwner))
+        }
+    }
+
+    /**
+     * Test rule that makes ArchTaskExecutor main thread assertions pass. There is one such assert
+     * in LifecycleRegistry.
+     */
+    class InstantTaskExecutorRule : TestWatcher() {
+        // TODO(b/240620122): This is a copy of
+        //  androidx/arch/core/executor/testing/InstantTaskExecutorRule which should be replaced
+        //  with a dependency on the real library once b/ is cleared.
+        override fun starting(description: Description) {
+            super.starting(description)
+            ArchTaskExecutor.getInstance()
+                .setDelegate(
+                    object : TaskExecutor() {
+                        override fun executeOnDiskIO(runnable: Runnable) {
+                            runnable.run()
+                        }
+
+                        override fun postToMainThread(runnable: Runnable) {
+                            runnable.run()
+                        }
+
+                        override fun isMainThread(): Boolean {
+                            return true
+                        }
+                    }
+                )
+        }
+
+        override fun finished(description: Description) {
+            super.finished(description)
+            ArchTaskExecutor.getInstance().setDelegate(null)
+        }
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/lifecycle/WindowAddedViewLifecycleOwnerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/lifecycle/WindowAddedViewLifecycleOwnerTest.kt
deleted file mode 100644
index 4f5c570..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/lifecycle/WindowAddedViewLifecycleOwnerTest.kt
+++ /dev/null
@@ -1,150 +0,0 @@
-package com.android.systemui.lifecycle
-
-import android.view.View
-import android.view.ViewTreeObserver
-import androidx.lifecycle.Lifecycle
-import androidx.lifecycle.LifecycleRegistry
-import androidx.test.filters.SmallTest
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.util.mockito.any
-import com.android.systemui.util.mockito.argumentCaptor
-import com.android.systemui.util.mockito.capture
-import com.google.common.truth.Truth.assertThat
-import org.junit.Before
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
-import org.mockito.Mock
-import org.mockito.Mockito.verify
-import org.mockito.Mockito.`when` as whenever
-import org.mockito.MockitoAnnotations
-
-@SmallTest
-@RunWith(JUnit4::class)
-class WindowAddedViewLifecycleOwnerTest : SysuiTestCase() {
-
-    @Mock lateinit var view: View
-    @Mock lateinit var viewTreeObserver: ViewTreeObserver
-
-    private lateinit var underTest: WindowAddedViewLifecycleOwner
-
-    @Before
-    fun setUp() {
-        MockitoAnnotations.initMocks(this)
-        whenever(view.viewTreeObserver).thenReturn(viewTreeObserver)
-        whenever(view.isAttachedToWindow).thenReturn(false)
-        whenever(view.windowVisibility).thenReturn(View.INVISIBLE)
-        whenever(view.hasWindowFocus()).thenReturn(false)
-
-        underTest = WindowAddedViewLifecycleOwner(view) { LifecycleRegistry.createUnsafe(it) }
-    }
-
-    @Test
-    fun `detached - invisible - does not have focus -- INITIALIZED`() {
-        assertThat(underTest.lifecycle.currentState).isEqualTo(Lifecycle.State.INITIALIZED)
-    }
-
-    @Test
-    fun `detached - invisible - has focus -- INITIALIZED`() {
-        whenever(view.hasWindowFocus()).thenReturn(true)
-        val captor = argumentCaptor<ViewTreeObserver.OnWindowFocusChangeListener>()
-        verify(viewTreeObserver).addOnWindowFocusChangeListener(capture(captor))
-        captor.value.onWindowFocusChanged(true)
-
-        assertThat(underTest.lifecycle.currentState).isEqualTo(Lifecycle.State.INITIALIZED)
-    }
-
-    @Test
-    fun `detached - visible - does not have focus -- INITIALIZED`() {
-        whenever(view.windowVisibility).thenReturn(View.VISIBLE)
-        val captor = argumentCaptor<ViewTreeObserver.OnWindowVisibilityChangeListener>()
-        verify(viewTreeObserver).addOnWindowVisibilityChangeListener(capture(captor))
-        captor.value.onWindowVisibilityChanged(View.VISIBLE)
-
-        assertThat(underTest.lifecycle.currentState).isEqualTo(Lifecycle.State.INITIALIZED)
-    }
-
-    @Test
-    fun `detached - visible - has focus -- INITIALIZED`() {
-        whenever(view.hasWindowFocus()).thenReturn(true)
-        val focusCaptor = argumentCaptor<ViewTreeObserver.OnWindowFocusChangeListener>()
-        verify(viewTreeObserver).addOnWindowFocusChangeListener(capture(focusCaptor))
-        focusCaptor.value.onWindowFocusChanged(true)
-
-        whenever(view.windowVisibility).thenReturn(View.VISIBLE)
-        val visibilityCaptor = argumentCaptor<ViewTreeObserver.OnWindowVisibilityChangeListener>()
-        verify(viewTreeObserver).addOnWindowVisibilityChangeListener(capture(visibilityCaptor))
-        visibilityCaptor.value.onWindowVisibilityChanged(View.VISIBLE)
-
-        assertThat(underTest.lifecycle.currentState).isEqualTo(Lifecycle.State.INITIALIZED)
-    }
-
-    @Test
-    fun `attached - invisible - does not have focus -- CREATED`() {
-        whenever(view.isAttachedToWindow).thenReturn(true)
-        val captor = argumentCaptor<ViewTreeObserver.OnWindowAttachListener>()
-        verify(viewTreeObserver).addOnWindowAttachListener(capture(captor))
-        captor.value.onWindowAttached()
-
-        assertThat(underTest.lifecycle.currentState).isEqualTo(Lifecycle.State.CREATED)
-    }
-
-    @Test
-    fun `attached - invisible - has focus -- CREATED`() {
-        whenever(view.isAttachedToWindow).thenReturn(true)
-        val attachCaptor = argumentCaptor<ViewTreeObserver.OnWindowAttachListener>()
-        verify(viewTreeObserver).addOnWindowAttachListener(capture(attachCaptor))
-        attachCaptor.value.onWindowAttached()
-
-        whenever(view.hasWindowFocus()).thenReturn(true)
-        val focusCaptor = argumentCaptor<ViewTreeObserver.OnWindowFocusChangeListener>()
-        verify(viewTreeObserver).addOnWindowFocusChangeListener(capture(focusCaptor))
-        focusCaptor.value.onWindowFocusChanged(true)
-
-        assertThat(underTest.lifecycle.currentState).isEqualTo(Lifecycle.State.CREATED)
-    }
-
-    @Test
-    fun `attached - visible - does not have focus -- STARTED`() {
-        whenever(view.isAttachedToWindow).thenReturn(true)
-        val attachCaptor = argumentCaptor<ViewTreeObserver.OnWindowAttachListener>()
-        verify(viewTreeObserver).addOnWindowAttachListener(capture(attachCaptor))
-        attachCaptor.value.onWindowAttached()
-
-        whenever(view.windowVisibility).thenReturn(View.VISIBLE)
-        val visibilityCaptor = argumentCaptor<ViewTreeObserver.OnWindowVisibilityChangeListener>()
-        verify(viewTreeObserver).addOnWindowVisibilityChangeListener(capture(visibilityCaptor))
-        visibilityCaptor.value.onWindowVisibilityChanged(View.VISIBLE)
-
-        assertThat(underTest.lifecycle.currentState).isEqualTo(Lifecycle.State.STARTED)
-    }
-
-    @Test
-    fun `attached - visible - has focus -- RESUMED`() {
-        whenever(view.isAttachedToWindow).thenReturn(true)
-        val attachCaptor = argumentCaptor<ViewTreeObserver.OnWindowAttachListener>()
-        verify(viewTreeObserver).addOnWindowAttachListener(capture(attachCaptor))
-        attachCaptor.value.onWindowAttached()
-
-        whenever(view.hasWindowFocus()).thenReturn(true)
-        val focusCaptor = argumentCaptor<ViewTreeObserver.OnWindowFocusChangeListener>()
-        verify(viewTreeObserver).addOnWindowFocusChangeListener(capture(focusCaptor))
-        focusCaptor.value.onWindowFocusChanged(true)
-
-        whenever(view.windowVisibility).thenReturn(View.VISIBLE)
-        val visibilityCaptor = argumentCaptor<ViewTreeObserver.OnWindowVisibilityChangeListener>()
-        verify(viewTreeObserver).addOnWindowVisibilityChangeListener(capture(visibilityCaptor))
-        visibilityCaptor.value.onWindowVisibilityChanged(View.VISIBLE)
-
-        assertThat(underTest.lifecycle.currentState).isEqualTo(Lifecycle.State.RESUMED)
-    }
-
-    @Test
-    fun dispose() {
-        underTest.dispose()
-
-        verify(viewTreeObserver).removeOnWindowAttachListener(any())
-        verify(viewTreeObserver).removeOnWindowVisibilityChangeListener(any())
-        verify(viewTreeObserver).removeOnWindowFocusChangeListener(any())
-    }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/log/LogBufferTest.kt b/packages/SystemUI/tests/src/com/android/systemui/log/LogBufferTest.kt
new file mode 100644
index 0000000..4abb973
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/log/LogBufferTest.kt
@@ -0,0 +1,167 @@
+package com.android.systemui.log
+
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.google.common.truth.Truth.assertThat
+import java.io.PrintWriter
+import java.io.StringWriter
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.junit.MockitoJUnitRunner
+
+@SmallTest
+@RunWith(MockitoJUnitRunner::class)
+class LogBufferTest : SysuiTestCase() {
+    private lateinit var buffer: LogBuffer
+
+    private lateinit var outputWriter: StringWriter
+
+    @Mock
+    private lateinit var logcatEchoTracker: LogcatEchoTracker
+
+    @Before
+    fun setup() {
+        outputWriter = StringWriter()
+        buffer = createBuffer(UNBOUNDED_STACK_TRACE, NESTED_TRACE_DEPTH)
+    }
+
+    private fun createBuffer(rootTraceDepth: Int, nestedTraceDepth: Int): LogBuffer {
+        return LogBuffer("TestBuffer",
+                1,
+                logcatEchoTracker,
+                false,
+                rootStackTraceDepth = rootTraceDepth,
+                nestedStackTraceDepth = nestedTraceDepth)
+    }
+
+    @Test
+    fun log_shouldSaveLogToBuffer() {
+        buffer.log("Test", LogLevel.INFO, "Some test message")
+
+        val dumpedString = dumpBuffer()
+
+        assertThat(dumpedString).contains("Some test message")
+    }
+
+    @Test
+    fun log_shouldRotateIfLogBufferIsFull() {
+        buffer.log("Test", LogLevel.INFO, "This should be rotated")
+        buffer.log("Test", LogLevel.INFO, "New test message")
+
+        val dumpedString = dumpBuffer()
+
+        assertThat(dumpedString).contains("New test message")
+    }
+
+    @Test
+    fun dump_writesExceptionAndStacktraceLimitedToGivenDepth() {
+        buffer = createBuffer(rootTraceDepth = 2, nestedTraceDepth = -1)
+        // stack trace depth of 5
+        val exception = createTestException("Exception message", "TestClass", 5)
+        buffer.log("Tag", LogLevel.ERROR, { str1 = "Extra message" }, { str1!! }, exception)
+
+        val dumpedString = dumpBuffer()
+
+        // logs are limited to depth 2
+        assertThat(dumpedString).contains("E Tag: Extra message")
+        assertThat(dumpedString).contains("E Tag: java.lang.RuntimeException: Exception message")
+        assertThat(dumpedString).contains("E Tag: \tat TestClass.TestMethod(TestClass.java:1)")
+        assertThat(dumpedString).contains("E Tag: \tat TestClass.TestMethod(TestClass.java:2)")
+        assertThat(dumpedString)
+                .doesNotContain("E Tag: \tat TestClass.TestMethod(TestClass.java:3)")
+    }
+
+    @Test
+    fun dump_writesCauseAndStacktraceLimitedToGivenDepth() {
+        buffer = createBuffer(rootTraceDepth = 0, nestedTraceDepth = 2)
+        val exception = createTestException("Exception message",
+                "TestClass",
+                1,
+                cause = createTestException("The real cause!", "TestClass", 5))
+        buffer.log("Tag", LogLevel.ERROR, { str1 = "Extra message" }, { str1!! }, exception)
+
+        val dumpedString = dumpBuffer()
+
+        // logs are limited to depth 2
+        assertThat(dumpedString)
+                .contains("E Tag: Caused by: java.lang.RuntimeException: The real cause!")
+        assertThat(dumpedString).contains("E Tag: \tat TestClass.TestMethod(TestClass.java:1)")
+        assertThat(dumpedString).contains("E Tag: \tat TestClass.TestMethod(TestClass.java:2)")
+        assertThat(dumpedString)
+                .doesNotContain("E Tag: \tat TestClass.TestMethod(TestClass.java:3)")
+    }
+
+    @Test
+    fun dump_writesSuppressedExceptionAndStacktraceLimitedToGivenDepth() {
+        buffer = createBuffer(rootTraceDepth = 0, nestedTraceDepth = 2)
+        val exception = RuntimeException("Root exception message")
+        exception.addSuppressed(
+                createTestException(
+                        "First suppressed exception",
+                        "FirstClass",
+                        5,
+                        createTestException("Cause of suppressed exp", "ThirdClass", 5)
+                ))
+        exception.addSuppressed(
+                createTestException("Second suppressed exception", "SecondClass", 5))
+        buffer.log("Tag", LogLevel.ERROR, { str1 = "Extra message" }, { str1!! }, exception)
+
+        val dumpedStr = dumpBuffer()
+
+        // logs are limited to depth 2
+        // first suppressed exception
+        assertThat(dumpedStr)
+                .contains("E Tag: Suppressed: " +
+                        "java.lang.RuntimeException: First suppressed exception")
+        assertThat(dumpedStr).contains("E Tag: \tat FirstClass.TestMethod(FirstClass.java:1)")
+        assertThat(dumpedStr).contains("E Tag: \tat FirstClass.TestMethod(FirstClass.java:2)")
+        assertThat(dumpedStr)
+                .doesNotContain("E Tag: \tat FirstClass.TestMethod(FirstClass.java:3)")
+
+        assertThat(dumpedStr)
+                .contains("E Tag: Caused by: java.lang.RuntimeException: Cause of suppressed exp")
+        assertThat(dumpedStr).contains("E Tag: \tat ThirdClass.TestMethod(ThirdClass.java:1)")
+        assertThat(dumpedStr).contains("E Tag: \tat ThirdClass.TestMethod(ThirdClass.java:2)")
+        assertThat(dumpedStr)
+                .doesNotContain("E Tag: \tat ThirdClass.TestMethod(ThirdClass.java:3)")
+
+        // second suppressed exception
+        assertThat(dumpedStr)
+                .contains("E Tag: Suppressed: " +
+                        "java.lang.RuntimeException: Second suppressed exception")
+        assertThat(dumpedStr).contains("E Tag: \tat SecondClass.TestMethod(SecondClass.java:1)")
+        assertThat(dumpedStr).contains("E Tag: \tat SecondClass.TestMethod(SecondClass.java:2)")
+        assertThat(dumpedStr)
+                .doesNotContain("E Tag: \tat SecondClass.TestMethod(SecondClass.java:3)")
+    }
+
+    private fun createTestException(
+        message: String,
+        errorClass: String,
+        stackTraceLength: Int,
+        cause: Throwable? = null
+    ): Exception {
+        val exception = RuntimeException(message, cause)
+        exception.stackTrace = createStackTraceElements(errorClass, stackTraceLength)
+        return exception
+    }
+
+    private fun dumpBuffer(): String {
+        buffer.dump(PrintWriter(outputWriter), tailLength = 100)
+        return outputWriter.toString()
+    }
+
+    private fun createStackTraceElements(
+        errorClass: String,
+        stackTraceLength: Int
+    ): Array<StackTraceElement> {
+        return (1..stackTraceLength).map { lineNumber ->
+            StackTraceElement(errorClass,
+                    "TestMethod",
+                    "$errorClass.java",
+                    lineNumber)
+        }.toTypedArray()
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttChipControllerCommonTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttChipControllerCommonTest.kt
index 2eb4783..5539786 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttChipControllerCommonTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttChipControllerCommonTest.kt
@@ -32,6 +32,8 @@
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.statusbar.gesture.TapGestureDetector
+import com.android.systemui.statusbar.policy.ConfigurationController
+import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener
 import com.android.systemui.util.concurrency.DelayableExecutor
 import com.android.systemui.util.concurrency.FakeExecutor
 import com.android.systemui.util.mockito.any
@@ -53,7 +55,7 @@
 
 @SmallTest
 class MediaTttChipControllerCommonTest : SysuiTestCase() {
-    private lateinit var controllerCommon: MediaTttChipControllerCommon<ChipInfo>
+    private lateinit var controllerCommon: TestControllerCommon
 
     private lateinit var fakeClock: FakeSystemClock
     private lateinit var fakeExecutor: FakeExecutor
@@ -68,6 +70,8 @@
     @Mock
     private lateinit var accessibilityManager: AccessibilityManager
     @Mock
+    private lateinit var configurationController: ConfigurationController
+    @Mock
     private lateinit var windowManager: WindowManager
     @Mock
     private lateinit var viewUtil: ViewUtil
@@ -98,14 +102,15 @@
         fakeExecutor = FakeExecutor(fakeClock)
 
         controllerCommon = TestControllerCommon(
-            context,
-            logger,
-            windowManager,
-            viewUtil,
-            fakeExecutor,
-            accessibilityManager,
-            tapGestureDetector,
-            powerManager
+                context,
+                logger,
+                windowManager,
+                viewUtil,
+                fakeExecutor,
+                accessibilityManager,
+                configurationController,
+                tapGestureDetector,
+                powerManager,
         )
     }
 
@@ -186,6 +191,19 @@
     }
 
     @Test
+    fun displayScaleChange_chipReinflatedWithMostRecentState() {
+        controllerCommon.displayChip(getState(name = "First name"))
+        controllerCommon.displayChip(getState(name = "Second name"))
+        reset(windowManager)
+
+        getConfigurationListener().onDensityOrFontScaleChanged()
+
+        verify(windowManager).removeView(any())
+        verify(windowManager).addView(any(), any())
+        assertThat(controllerCommon.mostRecentChipInfo?.name).isEqualTo("Second name")
+    }
+
+    @Test
     fun removeChip_chipRemovedAndGestureDetectionStoppedAndRemovalLogged() {
         // First, add the chip
         controllerCommon.displayChip(getState())
@@ -341,7 +359,7 @@
         verify(windowManager, never()).removeView(any())
     }
 
-    private fun getState() = ChipInfo()
+    private fun getState(name: String = "name") = ChipInfo(name)
 
     private fun getChipView(): ViewGroup {
         val viewCaptor = ArgumentCaptor.forClass(View::class.java)
@@ -351,6 +369,12 @@
 
     private fun ViewGroup.getAppIconView() = this.requireViewById<ImageView>(R.id.app_icon)
 
+    private fun getConfigurationListener(): ConfigurationListener {
+        val callbackCaptor = argumentCaptor<ConfigurationListener>()
+        verify(configurationController).addCallback(capture(callbackCaptor))
+        return callbackCaptor.value
+    }
+
     inner class TestControllerCommon(
         context: Context,
         logger: MediaTttLogger,
@@ -358,8 +382,9 @@
         viewUtil: ViewUtil,
         @Main mainExecutor: DelayableExecutor,
         accessibilityManager: AccessibilityManager,
+        configurationController: ConfigurationController,
         tapGestureDetector: TapGestureDetector,
-        powerManager: PowerManager
+        powerManager: PowerManager,
     ) : MediaTttChipControllerCommon<ChipInfo>(
         context,
         logger,
@@ -367,16 +392,22 @@
         viewUtil,
         mainExecutor,
         accessibilityManager,
+        configurationController,
         tapGestureDetector,
         powerManager,
-        R.layout.media_ttt_chip
+        R.layout.media_ttt_chip,
     ) {
+        var mostRecentChipInfo: ChipInfo? = null
+
         override val windowLayoutParams = commonWindowLayoutParams
-        override fun updateChipView(chipInfo: ChipInfo, currentChipView: ViewGroup) {}
+        override fun updateChipView(newChipInfo: ChipInfo, currentChipView: ViewGroup) {
+            super.updateChipView(newChipInfo, currentChipView)
+            mostRecentChipInfo = newChipInfo
+        }
         override fun getIconSize(isAppIcon: Boolean): Int = ICON_SIZE
     }
 
-    inner class ChipInfo : ChipInfoCommon {
+    inner class ChipInfo(val name: String) : ChipInfoCommon {
         override fun getTimeoutMs() = 1L
     }
 }
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 bbc5641..7c5d077 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
@@ -37,6 +37,7 @@
 import com.android.systemui.media.taptotransfer.common.MediaTttLogger
 import com.android.systemui.statusbar.CommandQueue
 import com.android.systemui.statusbar.gesture.TapGestureDetector
+import com.android.systemui.statusbar.policy.ConfigurationController
 import com.android.systemui.util.concurrency.FakeExecutor
 import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.eq
@@ -68,6 +69,8 @@
     @Mock
     private lateinit var accessibilityManager: AccessibilityManager
     @Mock
+    private lateinit var configurationController: ConfigurationController
+    @Mock
     private lateinit var powerManager: PowerManager
     @Mock
     private lateinit var windowManager: WindowManager
@@ -103,6 +106,7 @@
             viewUtil,
             FakeExecutor(FakeSystemClock()),
             accessibilityManager,
+            configurationController,
             TapGestureDetector(context),
             powerManager,
             Handler.getMain(),
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 7ca0cd3..e06a27d 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
@@ -38,12 +38,12 @@
 import com.android.systemui.media.taptotransfer.common.MediaTttLogger
 import com.android.systemui.statusbar.CommandQueue
 import com.android.systemui.statusbar.gesture.TapGestureDetector
+import com.android.systemui.statusbar.policy.ConfigurationController
 import com.android.systemui.util.concurrency.FakeExecutor
 import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.eq
 import com.android.systemui.util.time.FakeSystemClock
 import com.android.systemui.util.view.ViewUtil
-
 import com.google.common.truth.Truth.assertThat
 import org.junit.Before
 import org.junit.Test
@@ -52,8 +52,8 @@
 import org.mockito.Mock
 import org.mockito.Mockito.never
 import org.mockito.Mockito.verify
-import org.mockito.Mockito.`when` as whenever
 import org.mockito.MockitoAnnotations
+import org.mockito.Mockito.`when` as whenever
 
 @SmallTest
 @RunWith(AndroidTestingRunner::class)
@@ -70,6 +70,8 @@
     @Mock
     private lateinit var accessibilityManager: AccessibilityManager
     @Mock
+    private lateinit var configurationController: ConfigurationController
+    @Mock
     private lateinit var powerManager: PowerManager
     @Mock
     private lateinit var windowManager: WindowManager
@@ -112,6 +114,7 @@
             viewUtil,
             fakeExecutor,
             accessibilityManager,
+            configurationController,
             TapGestureDetector(context),
             powerManager,
             senderUiEventLogger
diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarControllerTest.java
index 4e3bdea..b0cf061 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarControllerTest.java
@@ -41,6 +41,7 @@
 import com.android.systemui.Dependency;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.dump.DumpManager;
+import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.model.SysUiState;
 import com.android.systemui.recents.OverviewProxyService;
 import com.android.systemui.statusbar.CommandQueue;
@@ -96,7 +97,8 @@
                         mock(AutoHideController.class),
                         mock(LightBarController.class),
                         Optional.of(mock(Pip.class)),
-                        Optional.of(mock(BackAnimation.class))));
+                        Optional.of(mock(BackAnimation.class)),
+                        mock(FeatureFlags.class)));
         initializeNavigationBars();
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/carrier/QSCarrierGroupControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/carrier/QSCarrierGroupControllerTest.java
index bd794d6..09ce37b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/carrier/QSCarrierGroupControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/carrier/QSCarrierGroupControllerTest.java
@@ -16,19 +16,26 @@
 
 package com.android.systemui.qs.carrier;
 
+import static com.google.common.truth.Truth.assertThat;
+
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.reset;
 import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyZeroInteractions;
 import static org.mockito.Mockito.when;
 
+import android.content.Context;
+import android.content.Intent;
 import android.os.Handler;
+import android.provider.Settings;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 import android.view.View;
@@ -50,6 +57,7 @@
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
@@ -85,6 +93,7 @@
     private QSCarrierGroupController.OnSingleCarrierChangedListener mOnSingleCarrierChangedListener;
 
     private FakeSlotIndexResolver mSlotIndexResolver;
+    private ClickListenerTextView mNoCarrierTextView;
 
     @Before
     public void setup() throws Exception {
@@ -108,7 +117,8 @@
                 .when(mCarrierTextManager)
                 .setListening(any(CarrierTextManager.CarrierTextCallback.class));
 
-        when(mQSCarrierGroup.getNoSimTextView()).thenReturn(new TextView(mContext));
+        mNoCarrierTextView = new ClickListenerTextView(mContext);
+        when(mQSCarrierGroup.getNoSimTextView()).thenReturn(mNoCarrierTextView);
         when(mQSCarrierGroup.getCarrier1View()).thenReturn(mQSCarrier1);
         when(mQSCarrierGroup.getCarrier2View()).thenReturn(mQSCarrier2);
         when(mQSCarrierGroup.getCarrier3View()).thenReturn(mQSCarrier3);
@@ -376,6 +386,47 @@
         verify(mOnSingleCarrierChangedListener, never()).onSingleCarrierChanged(anyBoolean());
     }
 
+    @Test
+    public void testOnlyInternalViewsHaveClickableListener() {
+        ArgumentCaptor<View.OnClickListener> captor =
+                ArgumentCaptor.forClass(View.OnClickListener.class);
+
+        verify(mQSCarrier1).setOnClickListener(captor.capture());
+        verify(mQSCarrier2).setOnClickListener(captor.getValue());
+        verify(mQSCarrier3).setOnClickListener(captor.getValue());
+
+        assertThat(mNoCarrierTextView.getOnClickListener()).isSameInstanceAs(captor.getValue());
+        verify(mQSCarrierGroup, never()).setOnClickListener(any());
+    }
+
+    @Test
+    public void testOnClickListenerDoesntStartActivityIfViewNotVisible() {
+        ArgumentCaptor<View.OnClickListener> captor =
+                ArgumentCaptor.forClass(View.OnClickListener.class);
+
+        verify(mQSCarrier1).setOnClickListener(captor.capture());
+        when(mQSCarrier1.isVisibleToUser()).thenReturn(false);
+
+        captor.getValue().onClick(mQSCarrier1);
+        verifyZeroInteractions(mActivityStarter);
+    }
+
+    @Test
+    public void testOnClickListenerLaunchesActivityIfViewVisible() {
+        ArgumentCaptor<View.OnClickListener> listenerCaptor =
+                ArgumentCaptor.forClass(View.OnClickListener.class);
+        ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
+
+        verify(mQSCarrier1).setOnClickListener(listenerCaptor.capture());
+        when(mQSCarrier1.isVisibleToUser()).thenReturn(true);
+
+        listenerCaptor.getValue().onClick(mQSCarrier1);
+        verify(mActivityStarter)
+                .postStartActivityDismissingKeyguard(intentCaptor.capture(), anyInt());
+        assertThat(intentCaptor.getValue().getAction())
+                .isEqualTo(Settings.ACTION_WIRELESS_SETTINGS);
+    }
+
     private class FakeSlotIndexResolver implements QSCarrierGroupController.SlotIndexResolver {
         public boolean overrideInvalid;
 
@@ -384,4 +435,22 @@
             return overrideInvalid ? -1 : subscriptionId;
         }
     }
+
+    private class ClickListenerTextView extends TextView {
+        View.OnClickListener mListener = null;
+
+        ClickListenerTextView(Context context) {
+            super(context);
+        }
+
+        @Override
+        public void setOnClickListener(OnClickListener l) {
+            super.setOnClickListener(l);
+            mListener = l;
+        }
+
+        View.OnClickListener getOnClickListener() {
+            return mListener;
+        }
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
index e2673bb..c9405c8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
@@ -97,6 +97,10 @@
 import com.android.systemui.fragments.FragmentHostManager;
 import com.android.systemui.fragments.FragmentService;
 import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
+import com.android.systemui.keyguard.domain.usecase.SetClockPositionUseCase;
+import com.android.systemui.keyguard.domain.usecase.SetKeyguardBottomAreaAlphaUseCase;
+import com.android.systemui.keyguard.domain.usecase.SetKeyguardBottomAreaAnimateDozingTransitionsUseCase;
+import com.android.systemui.keyguard.ui.viewmodel.KeyguardBottomAreaViewModel;
 import com.android.systemui.media.KeyguardMediaController;
 import com.android.systemui.media.MediaDataManager;
 import com.android.systemui.media.MediaHierarchyManager;
@@ -373,6 +377,11 @@
     private ViewParent mViewParent;
     @Mock
     private ViewTreeObserver mViewTreeObserver;
+    @Mock private KeyguardBottomAreaViewModel mKeyguardBottomAreaViewModel;
+    @Mock private SetClockPositionUseCase mSetClockPositionUseCase;
+    @Mock private SetKeyguardBottomAreaAlphaUseCase mSetKeyguardBottomAreaAlphaUseCase;
+    @Mock private SetKeyguardBottomAreaAnimateDozingTransitionsUseCase
+            mSetKeyguardBottomAreaAnimateDozingTransitionsUseCase;
     private NotificationPanelViewController.PanelEventsEmitter mPanelEventsEmitter;
     private Optional<SysUIUnfoldComponent> mSysUIUnfoldComponent = Optional.empty();
     private SysuiStatusBarStateController mStatusBarStateController;
@@ -564,7 +573,11 @@
                 mUnlockedScreenOffAnimationController,
                 mShadeTransitionController,
                 mSystemClock,
-                mock(CameraGestureHelper.class));
+                mock(CameraGestureHelper.class),
+                () -> mKeyguardBottomAreaViewModel,
+                () -> mSetClockPositionUseCase,
+                () -> mSetKeyguardBottomAreaAlphaUseCase,
+                () -> mSetKeyguardBottomAreaAnimateDozingTransitionsUseCase);
         mNotificationPanelViewController.initDependencies(
                 mCentralSurfaces,
                 () -> {},
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java
index ec1fa48..ad3d3d2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java
@@ -28,7 +28,6 @@
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.atLeastOnce;
 import static org.mockito.Mockito.clearInvocations;
-import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.reset;
 import static org.mockito.Mockito.spy;
@@ -44,8 +43,6 @@
 import android.view.View;
 import android.view.WindowManager;
 
-import androidx.lifecycle.LifecycleOwner;
-import androidx.lifecycle.ViewTreeLifecycleOwner;
 import androidx.test.filters.SmallTest;
 
 import com.android.internal.colorextraction.ColorExtractor;
@@ -188,24 +185,6 @@
     }
 
     @Test
-    public void attach_setsUpLifecycleOwner() {
-        mNotificationShadeWindowController.attach();
-
-        assertThat(ViewTreeLifecycleOwner.get(mNotificationShadeWindowView)).isNotNull();
-    }
-
-    @Test
-    public void attach_doesNotSetUpLifecycleOwnerIfAlreadySet() {
-        final LifecycleOwner previouslySet = mock(LifecycleOwner.class);
-        ViewTreeLifecycleOwner.set(mNotificationShadeWindowView, previouslySet);
-
-        mNotificationShadeWindowController.attach();
-
-        assertThat(ViewTreeLifecycleOwner.get(mNotificationShadeWindowView))
-                .isEqualTo(previouslySet);
-    }
-
-    @Test
     public void setScrimsVisibility_earlyReturn() {
         clearInvocations(mWindowManager);
         mNotificationShadeWindowController.setScrimsVisibility(ScrimController.TRANSPARENT);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NonPhoneDependencyTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NonPhoneDependencyTest.java
index b2ef7b3..79b1bb0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NonPhoneDependencyTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NonPhoneDependencyTest.java
@@ -27,16 +27,13 @@
 import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.systemui.Dependency;
 import com.android.systemui.SysuiTestCase;
-import com.android.systemui.shade.ShadeController;
 import com.android.systemui.statusbar.notification.NotificationEntryListener;
 import com.android.systemui.statusbar.notification.NotificationEntryManager;
-import com.android.systemui.statusbar.notification.collection.render.NotifStackController;
 import com.android.systemui.statusbar.notification.logging.NotificationLogger;
 import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
 import com.android.systemui.statusbar.notification.row.NotificationGutsManager.OnSettingsClickListener;
 import com.android.systemui.statusbar.notification.row.NotificationInfo.CheckSaveListener;
 import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
-import com.android.systemui.statusbar.policy.HeadsUpManager;
 
 import org.junit.Before;
 import org.junit.Ignore;
@@ -55,11 +52,8 @@
 @TestableLooper.RunWithLooper(setAsMainLooper = true)
 public class NonPhoneDependencyTest extends SysuiTestCase {
     @Mock private NotificationPresenter mPresenter;
-    @Mock private NotifStackController mStackController;
     @Mock private NotificationListContainer mListContainer;
-    @Mock
-    private NotificationEntryListener mEntryListener;
-    @Mock private HeadsUpManager mHeadsUpManager;
+    @Mock private NotificationEntryListener mEntryListener;
     @Mock private RemoteInputController.Delegate mDelegate;
     @Mock private NotificationRemoteInputManager.Callback mRemoteInputManagerCallback;
     @Mock private CheckSaveListener mCheckSaveListener;
@@ -76,28 +70,22 @@
     @Ignore("Causes binder calls which fail")
     @Test
     public void testNotificationManagementCodeHasNoDependencyOnStatusBarWindowManager() {
-        mDependency.injectMockDependency(ShadeController.class);
         NotificationEntryManager entryManager = Dependency.get(NotificationEntryManager.class);
         NotificationGutsManager gutsManager = Dependency.get(NotificationGutsManager.class);
-        NotificationListener notificationListener = Dependency.get(NotificationListener.class);
         NotificationLogger notificationLogger = Dependency.get(NotificationLogger.class);
         NotificationMediaManager mediaManager = Dependency.get(NotificationMediaManager.class);
         NotificationRemoteInputManager remoteInputManager =
                 Dependency.get(NotificationRemoteInputManager.class);
         NotificationLockscreenUserManager lockscreenUserManager =
                 Dependency.get(NotificationLockscreenUserManager.class);
-        NotificationViewHierarchyManager viewHierarchyManager =
-                Dependency.get(NotificationViewHierarchyManager.class);
-        entryManager.setUpWithPresenter(mPresenter);
         entryManager.addNotificationEntryListener(mEntryListener);
         gutsManager.setUpWithPresenter(mPresenter, mListContainer,
-                mCheckSaveListener, mOnSettingsClickListener);
+                mOnSettingsClickListener);
         notificationLogger.setUpWithContainer(mListContainer);
         mediaManager.setUpWithPresenter(mPresenter);
         remoteInputManager.setUpWithCallback(mRemoteInputManagerCallback,
                 mDelegate);
         lockscreenUserManager.setUpWithPresenter(mPresenter);
-        viewHierarchyManager.setUpWithPresenter(mPresenter, mStackController, mListContainer);
 
         TestableLooper.get(this).processAllMessages();
         assertFalse(mDependency.hasInstantiatedDependency(NotificationShadeWindowController.class));
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationViewHierarchyManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationViewHierarchyManagerTest.java
deleted file mode 100644
index 407044b..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationViewHierarchyManagerTest.java
+++ /dev/null
@@ -1,375 +0,0 @@
-/*
- * Copyright (C) 2017 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;
-
-import static junit.framework.Assert.assertTrue;
-
-import static org.junit.Assert.assertEquals;
-import static org.mockito.Mockito.clearInvocations;
-import static org.mockito.Mockito.doAnswer;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import android.os.Handler;
-import android.testing.AndroidTestingRunner;
-import android.testing.TestableLooper;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.LinearLayout;
-
-import androidx.test.filters.SmallTest;
-
-import com.android.keyguard.KeyguardUpdateMonitor;
-import com.android.systemui.SysuiTestCase;
-import com.android.systemui.flags.FeatureFlags;
-import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper;
-import com.android.systemui.statusbar.notification.AssistantFeedbackController;
-import com.android.systemui.statusbar.notification.DynamicChildBindController;
-import com.android.systemui.statusbar.notification.DynamicPrivacyController;
-import com.android.systemui.statusbar.notification.NotifPipelineFlags;
-import com.android.systemui.statusbar.notification.NotificationActivityStarter;
-import com.android.systemui.statusbar.notification.NotificationEntryManager;
-import com.android.systemui.statusbar.notification.collection.NotificationEntry;
-import com.android.systemui.statusbar.notification.collection.legacy.LowPriorityInflationHelper;
-import com.android.systemui.statusbar.notification.collection.legacy.NotificationGroupManagerLegacy;
-import com.android.systemui.statusbar.notification.collection.legacy.VisualStabilityManager;
-import com.android.systemui.statusbar.notification.collection.render.NotifStackController;
-import com.android.systemui.statusbar.notification.logging.NotificationLogger;
-import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
-import com.android.systemui.statusbar.notification.row.ExpandableView;
-import com.android.systemui.statusbar.notification.row.NotificationTestHelper;
-import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
-import com.android.systemui.statusbar.phone.KeyguardBypassController;
-import com.android.systemui.statusbar.policy.KeyguardStateController;
-import com.android.wm.shell.bubbles.Bubbles;
-
-import com.google.android.collect.Lists;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-import org.mockito.Spy;
-
-import java.util.List;
-import java.util.Optional;
-
-@SmallTest
-@RunWith(AndroidTestingRunner.class)
-@TestableLooper.RunWithLooper
-public class NotificationViewHierarchyManagerTest extends SysuiTestCase {
-    @Mock private NotificationPresenter mPresenter;
-    @Mock private NotifStackController mStackController;
-    @Spy private FakeListContainer mListContainer = new FakeListContainer();
-
-    // Dependency mocks:
-    @Mock private FeatureFlags mFeatureFlags;
-    @Mock private NotifPipelineFlags mNotifPipelineFlags;
-    @Mock private NotificationEntryManager mEntryManager;
-    @Mock private NotificationLockscreenUserManager mLockscreenUserManager;
-    @Mock private NotificationGroupManagerLegacy mGroupManager;
-    @Mock private VisualStabilityManager mVisualStabilityManager;
-
-    private TestableLooper mTestableLooper;
-    private Handler mHandler;
-    private NotificationViewHierarchyManager mViewHierarchyManager;
-    private NotificationTestHelper mHelper;
-    private boolean mMadeReentrantCall = false;
-
-    @Before
-    public void setUp() {
-        MockitoAnnotations.initMocks(this);
-        mTestableLooper = TestableLooper.get(this);
-        allowTestableLooperAsMainThread();
-        mHandler = Handler.createAsync(mTestableLooper.getLooper());
-
-        mDependency.injectTestDependency(NotificationEntryManager.class, mEntryManager);
-        mDependency.injectTestDependency(NotificationLockscreenUserManager.class,
-                mLockscreenUserManager);
-        mDependency.injectTestDependency(NotificationGroupManagerLegacy.class, mGroupManager);
-        mDependency.injectTestDependency(VisualStabilityManager.class, mVisualStabilityManager);
-        when(mVisualStabilityManager.areGroupChangesAllowed()).thenReturn(true);
-        when(mVisualStabilityManager.isReorderingAllowed()).thenReturn(true);
-
-        when(mNotifPipelineFlags.checkLegacyPipelineEnabled()).thenReturn(true);
-        when(mNotifPipelineFlags.isNewPipelineEnabled()).thenReturn(false);
-
-        mHelper = new NotificationTestHelper(mContext, mDependency, TestableLooper.get(this));
-
-        mViewHierarchyManager = new NotificationViewHierarchyManager(mContext,
-                mHandler, mFeatureFlags, mLockscreenUserManager, mGroupManager,
-                mVisualStabilityManager,
-                mock(StatusBarStateControllerImpl.class), mEntryManager,
-                mock(KeyguardBypassController.class),
-                Optional.of(mock(Bubbles.class)),
-                mock(DynamicPrivacyController.class),
-                mock(DynamicChildBindController.class),
-                mock(LowPriorityInflationHelper.class),
-                mock(AssistantFeedbackController.class),
-                mNotifPipelineFlags,
-                mock(KeyguardUpdateMonitor.class),
-                mock(KeyguardStateController.class));
-        mViewHierarchyManager.setUpWithPresenter(mPresenter, mStackController, mListContainer);
-    }
-
-    private NotificationEntry createEntry() throws Exception {
-        ExpandableNotificationRow row = mHelper.createRow();
-        return row.getEntry();
-    }
-
-    @Test
-    public void testNotificationsBecomingBundled() throws Exception {
-        // Tests 3 top level notifications becoming a single bundled notification with |entry0| as
-        // the summary.
-        NotificationEntry entry0 = createEntry();
-        NotificationEntry entry1 = createEntry();
-        NotificationEntry entry2 = createEntry();
-
-        // Set up the prior state to look like three top level notifications.
-        mListContainer.addContainerView(entry0.getRow());
-        mListContainer.addContainerView(entry1.getRow());
-        mListContainer.addContainerView(entry2.getRow());
-        when(mEntryManager.getVisibleNotifications()).thenReturn(
-                Lists.newArrayList(entry0, entry1, entry2));
-
-        // Set up group manager to report that they should be bundled now.
-        when(mGroupManager.isChildInGroup(entry0)).thenReturn(false);
-        when(mGroupManager.isChildInGroup(entry1)).thenReturn(true);
-        when(mGroupManager.isChildInGroup(entry2)).thenReturn(true);
-        when(mGroupManager.getGroupSummary(entry1)).thenReturn(entry0);
-        when(mGroupManager.getGroupSummary(entry2)).thenReturn(entry0);
-
-        // Run updateNotifications - the view hierarchy should be reorganized.
-        mViewHierarchyManager.updateNotificationViews();
-
-        verify(mListContainer).notifyGroupChildAdded(entry1.getRow());
-        verify(mListContainer).notifyGroupChildAdded(entry2.getRow());
-        assertTrue(Lists.newArrayList(entry0.getRow()).equals(mListContainer.mRows));
-    }
-
-    @Test
-    public void testNotificationsBecomingUnbundled() throws Exception {
-        // Tests a bundled notification becoming three top level notifications.
-        NotificationEntry entry0 = createEntry();
-        NotificationEntry entry1 = createEntry();
-        NotificationEntry entry2 = createEntry();
-        entry0.getRow().addChildNotification(entry1.getRow());
-        entry0.getRow().addChildNotification(entry2.getRow());
-
-        // Set up the prior state to look like one top level notification.
-        mListContainer.addContainerView(entry0.getRow());
-        when(mEntryManager.getVisibleNotifications()).thenReturn(
-                Lists.newArrayList(entry0, entry1, entry2));
-
-        // Set up group manager to report that they should not be bundled now.
-        when(mGroupManager.isChildInGroup(entry0)).thenReturn(false);
-        when(mGroupManager.isChildInGroup(entry1)).thenReturn(false);
-        when(mGroupManager.isChildInGroup(entry2)).thenReturn(false);
-
-        // Run updateNotifications - the view hierarchy should be reorganized.
-        mViewHierarchyManager.updateNotificationViews();
-
-        verify(mListContainer).notifyGroupChildRemoved(
-                entry1.getRow(), entry0.getRow().getChildrenContainer());
-        verify(mListContainer).notifyGroupChildRemoved(
-                entry2.getRow(), entry0.getRow().getChildrenContainer());
-        assertTrue(
-                Lists.newArrayList(entry0.getRow(), entry1.getRow(), entry2.getRow())
-                        .equals(mListContainer.mRows));
-    }
-
-    @Test
-    public void testNotificationsBecomingSuppressed() throws Exception {
-        // Tests two top level notifications becoming a suppressed summary and a child.
-        NotificationEntry entry0 = createEntry();
-        NotificationEntry entry1 = createEntry();
-        entry0.getRow().addChildNotification(entry1.getRow());
-
-        // Set up the prior state to look like a top level notification.
-        mListContainer.addContainerView(entry0.getRow());
-        when(mEntryManager.getVisibleNotifications()).thenReturn(
-                Lists.newArrayList(entry0, entry1));
-
-        // Set up group manager to report a suppressed summary now.
-        when(mGroupManager.isChildInGroup(entry0)).thenReturn(false);
-        when(mGroupManager.isChildInGroup(entry1)).thenReturn(false);
-        when(mGroupManager.isSummaryOfSuppressedGroup(entry0.getSbn())).thenReturn(true);
-
-        // Run updateNotifications - the view hierarchy should be reorganized.
-        mViewHierarchyManager.updateNotificationViews();
-
-        verify(mListContainer).notifyGroupChildRemoved(
-                entry1.getRow(), entry0.getRow().getChildrenContainer());
-        assertTrue(Lists.newArrayList(entry0.getRow(), entry1.getRow()).equals(mListContainer.mRows));
-        assertEquals(View.GONE, entry0.getRow().getVisibility());
-        assertEquals(View.VISIBLE, entry1.getRow().getVisibility());
-    }
-
-    @Test
-    public void testReentrantCallsToOnDynamicPrivacyChangedPostForLater() {
-        // GIVEN a ListContainer that will make a re-entrant call to updateNotificationViews()
-        mMadeReentrantCall = false;
-        doAnswer((invocation) -> {
-            if (!mMadeReentrantCall) {
-                mMadeReentrantCall = true;
-                mViewHierarchyManager.onDynamicPrivacyChanged();
-            }
-            return null;
-        }).when(mListContainer).onNotificationViewUpdateFinished();
-
-        // WHEN we call updateNotificationViews()
-        mViewHierarchyManager.updateNotificationViews();
-
-        // THEN onNotificationViewUpdateFinished() is only called once
-        verify(mListContainer).onNotificationViewUpdateFinished();
-
-        // WHEN we drain the looper
-        mTestableLooper.processAllMessages();
-
-        // THEN updateNotificationViews() is called a second time (for the reentrant call)
-        verify(mListContainer, times(2)).onNotificationViewUpdateFinished();
-    }
-
-    @Test
-    public void testMultipleReentrantCallsToOnDynamicPrivacyChangedOnlyPostOnce() {
-        // GIVEN a ListContainer that will make many re-entrant calls to updateNotificationViews()
-        mMadeReentrantCall = false;
-        doAnswer((invocation) -> {
-            if (!mMadeReentrantCall) {
-                mMadeReentrantCall = true;
-                mViewHierarchyManager.onDynamicPrivacyChanged();
-                mViewHierarchyManager.onDynamicPrivacyChanged();
-                mViewHierarchyManager.onDynamicPrivacyChanged();
-                mViewHierarchyManager.onDynamicPrivacyChanged();
-            }
-            return null;
-        }).when(mListContainer).onNotificationViewUpdateFinished();
-
-        // WHEN we call updateNotificationViews() and drain the looper
-        mViewHierarchyManager.updateNotificationViews();
-        verify(mListContainer).onNotificationViewUpdateFinished();
-        clearInvocations(mListContainer);
-        mTestableLooper.processAllMessages();
-
-        // THEN updateNotificationViews() is called only one more time
-        verify(mListContainer).onNotificationViewUpdateFinished();
-    }
-
-    private class FakeListContainer implements NotificationListContainer {
-        final LinearLayout mLayout = new LinearLayout(mContext);
-        final List<View> mRows = Lists.newArrayList();
-
-        @Override
-        public void setChildTransferInProgress(boolean childTransferInProgress) {}
-
-        @Override
-        public void changeViewPosition(ExpandableView child, int newIndex) {
-            mRows.remove(child);
-            mRows.add(newIndex, child);
-        }
-
-        @Override
-        public void notifyGroupChildAdded(ExpandableView row) {}
-
-        @Override
-        public void notifyGroupChildRemoved(ExpandableView row, ViewGroup childrenContainer) {}
-
-        @Override
-        public void generateAddAnimation(ExpandableView child, boolean fromMoreCard) {}
-
-        @Override
-        public void generateChildOrderChangedEvent() {}
-
-        @Override
-        public void onReset(ExpandableView view) {}
-
-        @Override
-        public int getContainerChildCount() {
-            return mRows.size();
-        }
-
-        @Override
-        public View getContainerChildAt(int i) {
-            return mRows.get(i);
-        }
-
-        @Override
-        public void removeContainerView(View v) {
-            mLayout.removeView(v);
-            mRows.remove(v);
-        }
-
-        @Override
-        public void setNotificationActivityStarter(
-                NotificationActivityStarter notificationActivityStarter) {}
-
-        @Override
-        public void addContainerView(View v) {
-            mLayout.addView(v);
-            mRows.add(v);
-        }
-
-        @Override
-        public void addContainerViewAt(View v, int index) {
-            mLayout.addView(v, index);
-            mRows.add(index, v);
-        }
-
-        @Override
-        public void setMaxDisplayedNotifications(int maxNotifications) {
-        }
-
-        @Override
-        public ViewGroup getViewParentForNotification(NotificationEntry entry) {
-            return null;
-        }
-
-        @Override
-        public void onHeightChanged(ExpandableView view, boolean animate) {}
-
-        @Override
-        public void resetExposedMenuView(boolean animate, boolean force) {}
-
-        @Override
-        public NotificationSwipeActionHelper getSwipeActionHelper() {
-            return null;
-        }
-
-        @Override
-        public void cleanUpViewStateForEntry(NotificationEntry entry) { }
-
-        @Override
-        public boolean isInVisibleLocation(NotificationEntry entry) {
-            return true;
-        }
-
-        @Override
-        public void setChildLocationsChangedListener(
-                NotificationLogger.OnChildLocationsChangedListener listener) {}
-
-        @Override
-        public boolean hasPulsingNotifications() {
-            return false;
-        }
-
-        @Override
-        public void onNotificationViewUpdateFinished() { }
-    }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java
index aeef6b0..842f057 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java
@@ -77,10 +77,8 @@
 import com.android.systemui.statusbar.NotificationPresenter;
 import com.android.systemui.statusbar.NotificationRemoteInputManager;
 import com.android.systemui.statusbar.NotificationRemoveInterceptor;
-import com.android.systemui.statusbar.RankingBuilder;
 import com.android.systemui.statusbar.SmartReplyController;
 import com.android.systemui.statusbar.notification.NotificationEntryManager.KeyguardEnvironment;
-import com.android.systemui.statusbar.notification.collection.NotifLiveDataStoreMocksKt;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
 import com.android.systemui.statusbar.notification.collection.NotificationRankingManager;
@@ -91,7 +89,6 @@
 import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider;
 import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
-import com.android.systemui.statusbar.notification.row.NotificationEntryManagerInflationTest;
 import com.android.systemui.statusbar.notification.row.RowInflaterTask;
 import com.android.systemui.statusbar.policy.DeviceProvisionedController;
 import com.android.systemui.statusbar.policy.HeadsUpManager;
@@ -115,9 +112,7 @@
 import java.util.Set;
 
 /**
- * Unit tests for {@link NotificationEntryManager}. This test will not test any interactions with
- * inflation. Instead, for functional inflation tests, see
- * {@link NotificationEntryManagerInflationTest}.
+ * Unit tests for {@link NotificationEntryManager}.
  */
 @SmallTest
 @RunWith(AndroidTestingRunner.class)
@@ -205,7 +200,6 @@
         mStats = defaultStats(mEntry);
         mSbn = mEntry.getSbn();
 
-        when(mNotifPipelineFlags.isNewPipelineEnabled()).thenReturn(false);
         mEntryManager = new NotificationEntryManager(
                 mLogger,
                 mGroupManager,
@@ -214,7 +208,6 @@
                 () -> mRemoteInputManager,
                 mLeakDetector,
                 mStatusBarService,
-                NotifLiveDataStoreMocksKt.createNotifLiveDataStoreImplMock(),
                 mock(DumpManager.class),
                 mBgExecutor
         );
@@ -230,7 +223,6 @@
                         mock(PeopleNotificationIdentifier.class),
                         mock(HighPriorityProvider.class),
                         mEnvironment));
-        mEntryManager.setUpWithPresenter(mPresenter);
         mEntryManager.addNotificationEntryListener(mEntryListener);
         mEntryManager.addCollectionListener(mNotifCollectionListener);
         mEntryManager.addNotificationRemoveInterceptor(mRemoveInterceptor);
@@ -273,17 +265,6 @@
     }
 
     @Test
-    public void testUpdateNotification_updatesUserSentiment() {
-        mEntryManager.addActiveNotificationForTest(mEntry);
-        setUserSentiment(
-                mEntry.getKey(), Ranking.USER_SENTIMENT_NEGATIVE);
-
-        mEntryManager.updateNotification(mSbn, mRankingMap);
-
-        assertEquals(Ranking.USER_SENTIMENT_NEGATIVE, mEntry.getUserSentiment());
-    }
-
-    @Test
     public void testUpdateNotification_prePostEntryOrder() throws Exception {
         TestableLooper.get(this).processAllMessages();
 
@@ -294,7 +275,6 @@
         // Ensure that update callbacks happen in correct order
         InOrder order = inOrder(mEntryListener, mPresenter, mEntryListener);
         order.verify(mEntryListener).onPreEntryUpdated(mEntry);
-        order.verify(mPresenter).updateNotificationViews(any());
         order.verify(mEntryListener).onPostEntryUpdated(mEntry);
     }
 
@@ -305,7 +285,6 @@
 
         mEntryManager.removeNotification(mSbn.getKey(), mRankingMap, UNDEFINED_DISMISS_REASON);
 
-        verify(mPresenter).updateNotificationViews(any());
         verify(mEntryListener).onEntryRemoved(
                 argThat(matchEntryOnKey()), any(),
                 eq(false) /* removedByUser */, eq(UNDEFINED_DISMISS_REASON));
@@ -379,23 +358,6 @@
     }
 
     @Test
-    public void testUpdateNotificationRanking() {
-        when(mDeviceProvisionedController.isDeviceProvisioned()).thenReturn(true);
-        when(mEnvironment.isDeviceProvisioned()).thenReturn(true);
-        when(mEnvironment.isNotificationForCurrentProfiles(any())).thenReturn(true);
-
-        mEntry.setRow(mRow);
-        mEntry.setInflationTask(mAsyncInflationTask);
-        mEntryManager.addActiveNotificationForTest(mEntry);
-        setSmartActions(mEntry.getKey(), new ArrayList<>(Arrays.asList(createAction())));
-
-        mEntryManager.updateNotificationRanking(mRankingMap);
-        assertEquals(1, mEntry.getSmartActions().size());
-        assertEquals("action", mEntry.getSmartActions().get(0).title);
-        verify(mEntryListener).onNotificationRankingUpdated(mRankingMap);
-    }
-
-    @Test
     public void testUpdateNotificationRanking_noChange() {
         when(mDeviceProvisionedController.isDeviceProvisioned()).thenReturn(true);
         when(mEnvironment.isNotificationForCurrentProfiles(any())).thenReturn(true);
@@ -409,20 +371,6 @@
     }
 
     @Test
-    public void testUpdateNotificationRanking_rowNotInflatedYet() {
-        when(mDeviceProvisionedController.isDeviceProvisioned()).thenReturn(true);
-        when(mEnvironment.isNotificationForCurrentProfiles(any())).thenReturn(true);
-
-        mEntry.setRow(null);
-        mEntryManager.addActiveNotificationForTest(mEntry);
-        setSmartActions(mEntry.getKey(), new ArrayList<>(Arrays.asList(createAction())));
-
-        mEntryManager.updateNotificationRanking(mRankingMap);
-        assertEquals(1, mEntry.getSmartActions().size());
-        assertEquals("action", mEntry.getSmartActions().get(0).title);
-    }
-
-    @Test
     public void testUpdateNotificationRanking_pendingNotification() {
         when(mDeviceProvisionedController.isDeviceProvisioned()).thenReturn(true);
         when(mEnvironment.isNotificationForCurrentProfiles(any())).thenReturn(true);
@@ -614,31 +562,6 @@
     /* Tests annexed from NotificationDataTest go here */
 
     @Test
-    public void testChannelIsSetWhenAdded() {
-        NotificationChannel nc = new NotificationChannel(
-                "testId",
-                "testName",
-                IMPORTANCE_DEFAULT);
-
-        Ranking r = new RankingBuilder()
-                .setKey(mEntry.getKey())
-                .setChannel(nc)
-                .build();
-
-        RankingMap rm = new RankingMap(new Ranking[] { r });
-
-        // GIVEN: a notification is added, and the ranking updated
-        mEntryManager.addActiveNotificationForTest(mEntry);
-        mEntryManager.updateRanking(rm, "testReason");
-
-        // THEN the notification entry better have a channel on it
-        assertEquals(
-                "Channel must be set when adding a notification",
-                nc.getName(),
-                mEntry.getChannel().getName());
-    }
-
-    @Test
     public void testGetNotificationsForCurrentUser_shouldFilterNonCurrentUserNotifications() {
         Notification.Builder n = new Notification.Builder(mContext, "di")
                 .setSmallIcon(R.drawable.ic_person)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilderTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilderTest.java
index c283cec..dfa38ab 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilderTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilderTest.java
@@ -33,7 +33,6 @@
 import static org.mockito.Mockito.atLeastOnce;
 import static org.mockito.Mockito.clearInvocations;
 import static org.mockito.Mockito.inOrder;
-import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.times;
@@ -1846,103 +1845,6 @@
     }
 
     @Test
-    public void stableOrderingDisregardedWithSectionChange() {
-        // GIVEN the first sectioner's packages can be changed from run-to-run
-        List<String> mutableSectionerPackages = new ArrayList<>();
-        mutableSectionerPackages.add(PACKAGE_1);
-        mListBuilder.setSectioners(asList(
-                new PackageSectioner(mutableSectionerPackages, null),
-                new PackageSectioner(List.of(PACKAGE_1, PACKAGE_2, PACKAGE_3), null)));
-        mStabilityManager.setAllowEntryReordering(false);
-
-        // WHEN the list is originally built with reordering disabled (and section changes allowed)
-        addNotif(0, PACKAGE_1).setRank(1);
-        addNotif(1, PACKAGE_1).setRank(5);
-        addNotif(2, PACKAGE_2).setRank(2);
-        addNotif(3, PACKAGE_2).setRank(3);
-        addNotif(4, PACKAGE_3).setRank(4);
-        dispatchBuild();
-
-        // VERIFY the order and that entry reordering has not been suppressed
-        verifyBuiltList(
-                notif(0),
-                notif(1),
-                notif(2),
-                notif(3),
-                notif(4)
-        );
-        verify(mStabilityManager, never()).onEntryReorderSuppressed();
-
-        // WHEN the first section now claims PACKAGE_3 notifications
-        mutableSectionerPackages.add(PACKAGE_3);
-        dispatchBuild();
-
-        // VERIFY the re-sectioned notification is inserted at the top of the first section, because
-        // it's effectively "new" and "new" things are inserted at the top of their section.
-        verifyBuiltList(
-                notif(4),
-                notif(0),
-                notif(1),
-                notif(2),
-                notif(3)
-        );
-        verify(mStabilityManager).onEntryReorderSuppressed();
-        clearInvocations(mStabilityManager);
-
-        // WHEN reordering is now allowed again
-        mStabilityManager.setAllowEntryReordering(true);
-        dispatchBuild();
-
-        // VERIFY that list order changes to put the re-sectioned notification in the middle where
-        // it is ranked.
-        verifyBuiltList(
-                notif(0),
-                notif(4),
-                notif(1),
-                notif(2),
-                notif(3)
-        );
-        verify(mStabilityManager, never()).onEntryReorderSuppressed();
-    }
-
-    @Test
-    public void groupRevertingToSummaryRetainsStablePosition() {
-        // GIVEN a notification group is on screen
-        mStabilityManager.setAllowEntryReordering(false);
-
-        // WHEN the list is originally built with reordering disabled (and section changes allowed)
-        addNotif(0, PACKAGE_1).setRank(2);
-        addNotif(1, PACKAGE_1).setRank(3);
-        addGroupSummary(2, PACKAGE_1, "group").setRank(4);
-        addGroupChild(3, PACKAGE_1, "group").setRank(5);
-        addGroupChild(4, PACKAGE_1, "group").setRank(6);
-        dispatchBuild();
-
-        verifyBuiltList(
-                notif(0),
-                notif(1),
-                group(
-                        summary(2),
-                        child(3),
-                        child(4)
-                )
-        );
-
-        // WHEN the notification summary rank increases and children removed
-        setNewRank(notif(2).entry, 1);
-        mEntrySet.remove(4);
-        mEntrySet.remove(3);
-        dispatchBuild();
-
-        // VERIFY the summary stays in the same location on rebuild
-        verifyBuiltList(
-                notif(0),
-                notif(1),
-                notif(2)
-        );
-    }
-
-    @Test
     public void testStableChildOrdering() {
         // WHEN the list is originally built with reordering disabled
         mStabilityManager.setAllowEntryReordering(false);
@@ -2138,7 +2040,6 @@
 
     private void assertOrder(String visible, String active, String expected) {
         StringBuilder differenceSb = new StringBuilder();
-        NotifSection section = new NotifSection(mock(NotifSectioner.class), 0);
         for (char c : active.toCharArray()) {
             if (visible.indexOf(c) < 0) differenceSb.append(c);
         }
@@ -2147,7 +2048,6 @@
         for (int i = 0; i < visible.length(); i++) {
             addNotif(i, String.valueOf(visible.charAt(i)))
                     .setRank(active.indexOf(visible.charAt(i)))
-                    .setSection(section)
                     .setStableIndex(i);
 
         }
@@ -2155,7 +2055,6 @@
         for (int i = 0; i < difference.length(); i++) {
             addNotif(i + visible.length(), String.valueOf(difference.charAt(i)))
                     .setRank(active.indexOf(difference.charAt(i)))
-                    .setSection(section)
                     .setStableIndex(-1);
         }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImplTest.java
index 5386171..e00e20f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImplTest.java
@@ -118,7 +118,6 @@
                         mPowerManager,
                         mDreamManager,
                         mAmbientDisplayConfiguration,
-                        mNotificationFilter,
                         mBatteryController,
                         mStatusBarStateController,
                         mKeyguardStateController,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationEntryManagerInflationTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationEntryManagerInflationTest.java
deleted file mode 100644
index bf7549a..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationEntryManagerInflationTest.java
+++ /dev/null
@@ -1,467 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.statusbar.notification.row;
-
-import static android.app.NotificationManager.IMPORTANCE_DEFAULT;
-
-import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_HEADS_UP;
-
-import static junit.framework.Assert.assertNotNull;
-
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import android.app.Notification;
-import android.content.Context;
-import android.content.pm.LauncherApps;
-import android.os.Handler;
-import android.service.notification.NotificationListenerService;
-import android.service.notification.NotificationListenerService.Ranking;
-import android.service.notification.StatusBarNotification;
-import android.testing.AndroidTestingRunner;
-import android.testing.TestableLooper;
-
-import androidx.asynclayoutinflater.view.AsyncLayoutInflater;
-import androidx.test.filters.SmallTest;
-
-import com.android.internal.logging.MetricsLogger;
-import com.android.internal.statusbar.IStatusBarService;
-import com.android.internal.util.NotificationMessagingUtil;
-import com.android.systemui.R;
-import com.android.systemui.SysuiTestCase;
-import com.android.systemui.classifier.FalsingCollectorFake;
-import com.android.systemui.classifier.FalsingManagerFake;
-import com.android.systemui.dump.DumpManager;
-import com.android.systemui.flags.FeatureFlags;
-import com.android.systemui.media.MediaFeatureFlag;
-import com.android.systemui.media.dialog.MediaOutputDialogFactory;
-import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.shared.plugins.PluginManager;
-import com.android.systemui.statusbar.NotificationListener;
-import com.android.systemui.statusbar.NotificationLockscreenUserManager;
-import com.android.systemui.statusbar.NotificationMediaManager;
-import com.android.systemui.statusbar.NotificationPresenter;
-import com.android.systemui.statusbar.NotificationRemoteInputManager;
-import com.android.systemui.statusbar.SbnBuilder;
-import com.android.systemui.statusbar.SmartReplyController;
-import com.android.systemui.statusbar.notification.ConversationNotificationProcessor;
-import com.android.systemui.statusbar.notification.NotifPipelineFlags;
-import com.android.systemui.statusbar.notification.NotificationClicker;
-import com.android.systemui.statusbar.notification.NotificationEntryListener;
-import com.android.systemui.statusbar.notification.NotificationEntryManager;
-import com.android.systemui.statusbar.notification.NotificationEntryManagerLogger;
-import com.android.systemui.statusbar.notification.NotificationFilter;
-import com.android.systemui.statusbar.notification.NotificationSectionsFeatureManager;
-import com.android.systemui.statusbar.notification.collection.NotifLiveDataStoreMocksKt;
-import com.android.systemui.statusbar.notification.collection.NotificationEntry;
-import com.android.systemui.statusbar.notification.collection.NotificationRankingManager;
-import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinderImpl;
-import com.android.systemui.statusbar.notification.collection.legacy.LowPriorityInflationHelper;
-import com.android.systemui.statusbar.notification.collection.legacy.NotificationGroupManagerLegacy;
-import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider;
-import com.android.systemui.statusbar.notification.icon.IconBuilder;
-import com.android.systemui.statusbar.notification.icon.IconManager;
-import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider;
-import com.android.systemui.statusbar.notification.logging.NotificationLogger;
-import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier;
-import com.android.systemui.statusbar.notification.row.dagger.ExpandableNotificationRowComponent;
-import com.android.systemui.statusbar.notification.row.dagger.NotificationRowComponent;
-import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
-import com.android.systemui.statusbar.phone.KeyguardBypassController;
-import com.android.systemui.statusbar.policy.HeadsUpManager;
-import com.android.systemui.statusbar.policy.InflatedSmartReplyState;
-import com.android.systemui.statusbar.policy.InflatedSmartReplyViewHolder;
-import com.android.systemui.statusbar.policy.SmartReplyConstants;
-import com.android.systemui.statusbar.policy.SmartReplyStateInflater;
-import com.android.systemui.statusbar.policy.dagger.RemoteInputViewSubcomponent;
-import com.android.systemui.util.concurrency.FakeExecutor;
-import com.android.systemui.util.leak.LeakDetector;
-import com.android.systemui.util.time.FakeSystemClock;
-import com.android.systemui.wmshell.BubblesManager;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Answers;
-import org.mockito.ArgumentCaptor;
-import org.mockito.Mock;
-import org.mockito.Mockito;
-import org.mockito.MockitoAnnotations;
-import org.mockito.stubbing.Answer;
-
-import java.util.Optional;
-import java.util.concurrent.CountDownLatch;
-
-/**
- * Functional tests for notification inflation from {@link NotificationEntryManager}.
- */
-@SmallTest
-@RunWith(AndroidTestingRunner.class)
-@TestableLooper.RunWithLooper(setAsMainLooper = true)
-public class NotificationEntryManagerInflationTest extends SysuiTestCase {
-
-    private static final String TEST_TITLE = "Title";
-    private static final String TEST_TEXT = "Text";
-    private static final long TIMEOUT_TIME = 10000;
-    private static final Runnable TIMEOUT_RUNNABLE = () -> {
-        throw new RuntimeException("Timed out waiting to inflate");
-    };
-
-    @Mock private NotificationListener mNotificationListener;
-    @Mock private NotificationPresenter mPresenter;
-    @Mock private NotificationEntryManager.KeyguardEnvironment mEnvironment;
-    @Mock private NotificationListContainer mListContainer;
-    @Mock private NotificationEntryListener mEntryListener;
-    @Mock private NotificationRowBinderImpl.BindRowCallback mBindCallback;
-    @Mock private HeadsUpManager mHeadsUpManager;
-    @Mock private NotificationInterruptStateProvider mNotificationInterruptionStateProvider;
-    @Mock private NotificationLockscreenUserManager mLockscreenUserManager;
-    @Mock private NotificationGutsManager mGutsManager;
-    @Mock private NotificationRemoteInputManager mRemoteInputManager;
-    @Mock private NotificationMediaManager mNotificationMediaManager;
-    @Mock(answer = Answers.RETURNS_SELF)
-    private ExpandableNotificationRowComponent.Builder mExpandableNotificationRowComponentBuilder;
-    @Mock private ExpandableNotificationRowComponent mExpandableNotificationRowComponent;
-    @Mock private KeyguardBypassController mKeyguardBypassController;
-    @Mock private StatusBarStateController mStatusBarStateController;
-
-    @Mock private NotificationGroupManagerLegacy mGroupMembershipManager;
-    @Mock private NotificationGroupManagerLegacy mGroupExpansionManager;
-    @Mock private NotifPipelineFlags mNotifPipelineFlags;
-    @Mock private LeakDetector mLeakDetector;
-
-    @Mock private ActivatableNotificationViewController mActivatableNotificationViewController;
-    @Mock private NotificationRowComponent.Builder mNotificationRowComponentBuilder;
-    @Mock private PeopleNotificationIdentifier mPeopleNotificationIdentifier;
-    @Mock private InflatedSmartReplyState mInflatedSmartReplyState;
-    @Mock private InflatedSmartReplyViewHolder mInflatedSmartReplies;
-
-    private StatusBarNotification mSbn;
-    private NotificationListenerService.RankingMap mRankingMap;
-    private NotificationEntryManager mEntryManager;
-    private NotificationRowBinderImpl mRowBinder;
-    private Handler mHandler;
-    private FakeExecutor mBgExecutor;
-    private RowContentBindStage mRowContentBindStage;
-
-    @Before
-    public void setUp() {
-        MockitoAnnotations.initMocks(this);
-        mDependency.injectMockDependency(SmartReplyController.class);
-        mDependency.injectMockDependency(MediaOutputDialogFactory.class);
-
-        mHandler = Handler.createAsync(TestableLooper.get(this).getLooper());
-
-        // Add an action so heads up content views are made
-        Notification.Action action = new Notification.Action.Builder(null, null, null).build();
-        Notification notification = new Notification.Builder(mContext)
-                .setSmallIcon(R.drawable.ic_person)
-                .setContentTitle(TEST_TITLE)
-                .setContentText(TEST_TEXT)
-                .setActions(action)
-                .build();
-        mSbn = new SbnBuilder()
-                .setNotification(notification)
-                .build();
-
-        when(mNotifPipelineFlags.checkLegacyPipelineEnabled()).thenReturn(true);
-        when(mNotifPipelineFlags.isNewPipelineEnabled()).thenReturn(false);
-
-        mEntryManager = new NotificationEntryManager(
-                mock(NotificationEntryManagerLogger.class),
-                mGroupMembershipManager,
-                mNotifPipelineFlags,
-                () -> mRowBinder,
-                () -> mRemoteInputManager,
-                mLeakDetector,
-                mock(IStatusBarService.class),
-                NotifLiveDataStoreMocksKt.createNotifLiveDataStoreImplMock(),
-                mock(DumpManager.class),
-                mBgExecutor
-        );
-        mEntryManager.initialize(
-                mNotificationListener,
-                new NotificationRankingManager(
-                        () -> mock(NotificationMediaManager.class),
-                        mGroupMembershipManager,
-                        mHeadsUpManager,
-                        mock(NotificationFilter.class),
-                        mock(NotificationEntryManagerLogger.class),
-                        mock(NotificationSectionsFeatureManager.class),
-                        mock(PeopleNotificationIdentifier.class),
-                        mock(HighPriorityProvider.class),
-                        mEnvironment));
-
-        NotifRemoteViewCache cache = new NotifRemoteViewCacheImpl(mEntryManager);
-        NotifBindPipeline pipeline = new NotifBindPipeline(
-                mEntryManager,
-                mock(NotifBindPipelineLogger.class),
-                TestableLooper.get(this).getLooper());
-        mBgExecutor = new FakeExecutor(new FakeSystemClock());
-        NotificationContentInflater binder = new NotificationContentInflater(
-                cache,
-                mRemoteInputManager,
-                mock(ConversationNotificationProcessor.class),
-                mock(MediaFeatureFlag.class),
-                mBgExecutor,
-                new SmartReplyStateInflater() {
-                    @Override
-                    public InflatedSmartReplyState inflateSmartReplyState(NotificationEntry entry) {
-                        return mInflatedSmartReplyState;
-                    }
-
-                    @Override
-                    public InflatedSmartReplyViewHolder inflateSmartReplyViewHolder(
-                            Context sysuiContext, Context notifPackageContext,
-                            NotificationEntry entry,
-                            InflatedSmartReplyState existingSmartReplyState,
-                            InflatedSmartReplyState newSmartReplyState) {
-                        return mInflatedSmartReplies;
-                    }
-                });
-        mRowContentBindStage = new RowContentBindStage(
-                binder,
-                mock(NotifInflationErrorManager.class),
-                mock(RowContentBindStageLogger.class));
-        pipeline.setStage(mRowContentBindStage);
-
-        ArgumentCaptor<ExpandableNotificationRow> viewCaptor =
-                ArgumentCaptor.forClass(ExpandableNotificationRow.class);
-        when(mExpandableNotificationRowComponentBuilder
-                .expandableNotificationRow(viewCaptor.capture()))
-                .thenReturn(mExpandableNotificationRowComponentBuilder);
-        when(mExpandableNotificationRowComponentBuilder.build())
-                .thenReturn(mExpandableNotificationRowComponent);
-
-        when(mExpandableNotificationRowComponent.getExpandableNotificationRowController())
-                .thenAnswer((Answer<ExpandableNotificationRowController>) invocation ->
-                        new ExpandableNotificationRowController(
-                                viewCaptor.getValue(),
-                                mock(ActivatableNotificationViewController.class),
-                                mock(RemoteInputViewSubcomponent.Factory.class),
-                                mock(MetricsLogger.class),
-                                mListContainer,
-                                mNotificationMediaManager,
-                                mock(SmartReplyConstants.class),
-                                mock(SmartReplyController.class),
-                                mock(PluginManager.class),
-                                new FakeSystemClock(),
-                                "FOOBAR",
-                                "FOOBAR",
-                                mKeyguardBypassController,
-                                mGroupMembershipManager,
-                                mGroupExpansionManager,
-                                mRowContentBindStage,
-                                mock(NotificationLogger.class),
-                                mHeadsUpManager,
-                                mPresenter,
-                                mStatusBarStateController,
-                                mGutsManager,
-                                true,
-                                null,
-                                new FalsingManagerFake(),
-                                new FalsingCollectorFake(),
-                                mock(FeatureFlags.class),
-                                mPeopleNotificationIdentifier,
-                                Optional.of(mock(BubblesManager.class)),
-                                mock(ExpandableNotificationRowDragController.class)));
-
-        when(mNotificationRowComponentBuilder.activatableNotificationView(any()))
-                .thenReturn(mNotificationRowComponentBuilder);
-        when(mNotificationRowComponentBuilder.build()).thenReturn(
-                () -> mActivatableNotificationViewController);
-
-        mRowBinder = new NotificationRowBinderImpl(
-                mContext,
-                new NotificationMessagingUtil(mContext),
-                mRemoteInputManager,
-                mLockscreenUserManager,
-                pipeline,
-                mRowContentBindStage,
-                RowInflaterTask::new,
-                mExpandableNotificationRowComponentBuilder,
-                new IconManager(
-                        mEntryManager,
-                        mock(LauncherApps.class),
-                        new IconBuilder(mContext)),
-                mock(LowPriorityInflationHelper.class),
-                mNotifPipelineFlags);
-
-        mEntryManager.setUpWithPresenter(mPresenter);
-        mEntryManager.addNotificationEntryListener(mEntryListener);
-
-        mRowBinder.setUpWithPresenter(mPresenter, mListContainer, mBindCallback);
-        mRowBinder.setNotificationClicker(mock(NotificationClicker.class));
-
-        Ranking ranking = new Ranking();
-        ranking.populate(
-                mSbn.getKey(),
-                0,
-                false,
-                0,
-                0,
-                IMPORTANCE_DEFAULT,
-                null,
-                null,
-                null,
-                null,
-                null,
-                true,
-                Ranking.USER_SENTIMENT_NEUTRAL,
-                false,
-                -1,
-                false,
-                null,
-                null,
-                false,
-                false,
-                false,
-                null,
-                0,
-                false
-            );
-        mRankingMap = new NotificationListenerService.RankingMap(new Ranking[] {ranking});
-
-        TestableLooper.get(this).processAllMessages();
-    }
-
-    @After
-    public void cleanUp() {
-        // Don't leave anything on main thread
-        TestableLooper.get(this).processAllMessages();
-    }
-
-    @Test
-    public void testAddNotification() {
-        // WHEN a notification is added
-        mEntryManager.addNotification(mSbn, mRankingMap);
-        ArgumentCaptor<NotificationEntry> entryCaptor = ArgumentCaptor.forClass(
-                NotificationEntry.class);
-        verify(mEntryListener).onPendingEntryAdded(entryCaptor.capture());
-        NotificationEntry entry = entryCaptor.getValue();
-
-        waitForInflation();
-
-        // THEN the notification has its row inflated
-        assertNotNull(entry.getRow());
-        assertNotNull(entry.getRow().getPrivateLayout().getContractedChild());
-
-        // THEN inflation callbacks are called
-        verify(mBindCallback).onBindRow(entry.getRow());
-        verify(mEntryListener, never()).onInflationError(any(), any());
-        verify(mEntryListener).onEntryInflated(entry);
-        verify(mEntryListener).onNotificationAdded(entry);
-
-        // THEN the notification is active
-        assertNotNull(mEntryManager.getActiveNotificationUnfiltered(mSbn.getKey()));
-
-        // THEN we update the presenter
-        verify(mPresenter).updateNotificationViews(any());
-    }
-
-    @Test
-    public void testUpdateNotification() {
-        // GIVEN a notification already added
-        mEntryManager.addNotification(mSbn, mRankingMap);
-        ArgumentCaptor<NotificationEntry> entryCaptor = ArgumentCaptor.forClass(
-                NotificationEntry.class);
-        verify(mEntryListener).onPendingEntryAdded(entryCaptor.capture());
-        NotificationEntry entry = entryCaptor.getValue();
-        waitForInflation();
-
-        Mockito.reset(mEntryListener);
-        Mockito.reset(mPresenter);
-
-        // WHEN the notification is updated
-        mEntryManager.updateNotification(mSbn, mRankingMap);
-
-        waitForInflation();
-
-        // THEN the notification has its row and inflated
-        assertNotNull(entry.getRow());
-
-        // THEN inflation callbacks are called
-        verify(mEntryListener, never()).onInflationError(any(), any());
-        verify(mEntryListener).onEntryReinflated(entry);
-
-        // THEN we update the presenter
-        verify(mPresenter).updateNotificationViews(any());
-    }
-
-    @Test
-    public void testContentViewInflationDuringRowInflationInflatesCorrectViews() {
-        // GIVEN a notification is added and the row is inflating
-        mEntryManager.addNotification(mSbn, mRankingMap);
-        ArgumentCaptor<NotificationEntry> entryCaptor = ArgumentCaptor.forClass(
-                NotificationEntry.class);
-        verify(mEntryListener).onPendingEntryAdded(entryCaptor.capture());
-        NotificationEntry entry = entryCaptor.getValue();
-
-        // WHEN we try to bind a content view
-        mRowContentBindStage.getStageParams(entry).requireContentViews(FLAG_CONTENT_VIEW_HEADS_UP);
-        mRowContentBindStage.requestRebind(entry, null);
-
-        waitForInflation();
-
-        // THEN the notification has its row and all relevant content views inflated
-        assertNotNull(entry.getRow());
-        assertNotNull(entry.getRow().getPrivateLayout().getContractedChild());
-        assertNotNull(entry.getRow().getPrivateLayout().getHeadsUpChild());
-    }
-
-    /**
-     * Wait for inflation to finish.
-     *
-     * A few things to note
-     * 1) Row inflation is done via {@link AsyncLayoutInflater} on its own background thread that
-     * calls back to main thread which is why we wait on main thread.
-     * 2) Row *content* inflation is done on the {@link FakeExecutor} we pass in in this test class
-     * so we control when that work is done. The callback is still always on the main thread.
-     */
-    private void waitForInflation() {
-        mHandler.postDelayed(TIMEOUT_RUNNABLE, TIMEOUT_TIME);
-        final CountDownLatch latch = new CountDownLatch(1);
-        NotificationEntryListener inflationListener = new NotificationEntryListener() {
-            @Override
-            public void onEntryInflated(NotificationEntry entry) {
-                latch.countDown();
-            }
-
-            @Override
-            public void onEntryReinflated(NotificationEntry entry) {
-                latch.countDown();
-            }
-
-            @Override
-            public void onInflationError(StatusBarNotification notification, Exception exception) {
-                latch.countDown();
-            }
-        };
-        mEntryManager.addNotificationEntryListener(inflationListener);
-        while (latch.getCount() != 0) {
-            mBgExecutor.runAllReady();
-            TestableLooper.get(this).processMessages(1);
-        }
-        mHandler.removeCallbacks(TIMEOUT_RUNNABLE);
-        mEntryManager.removeNotificationEntryListener(inflationListener);
-    }
-
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java
index e26c583..f57c409 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java
@@ -156,13 +156,13 @@
 
         mGutsManager = new NotificationGutsManager(mContext,
                 () -> Optional.of(mCentralSurfaces), mHandler, mHandler, mAccessibilityManager,
-                mHighPriorityProvider, mINotificationManager, mNotificationEntryManager,
+                mHighPriorityProvider, mINotificationManager,
                 mPeopleSpaceWidgetManager, mLauncherApps, mShortcutManager,
                 mChannelEditorDialogController, mContextTracker, mAssistantFeedbackController,
                 Optional.of(mBubblesManager), new UiEventLoggerFake(), mOnUserInteractionCallback,
                 mShadeController, mock(DumpManager.class));
         mGutsManager.setUpWithPresenter(mPresenter, mNotificationListContainer,
-                mCheckSaveListener, mOnSettingsClickListener);
+                mOnSettingsClickListener);
         mGutsManager.setNotificationActivityStarter(mNotificationActivityStarter);
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
index 30c40b9..2a3509c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
@@ -121,7 +121,6 @@
 import com.android.systemui.statusbar.NotificationRemoteInputManager;
 import com.android.systemui.statusbar.NotificationShadeDepthController;
 import com.android.systemui.statusbar.NotificationShadeWindowController;
-import com.android.systemui.statusbar.NotificationViewHierarchyManager;
 import com.android.systemui.statusbar.OperatorNameViewController;
 import com.android.systemui.statusbar.PulseExpansionHandler;
 import com.android.systemui.statusbar.StatusBarState;
@@ -129,7 +128,6 @@
 import com.android.systemui.statusbar.notification.DynamicPrivacyController;
 import com.android.systemui.statusbar.notification.NotifPipelineFlags;
 import com.android.systemui.statusbar.notification.NotificationEntryManager;
-import com.android.systemui.statusbar.notification.NotificationFilter;
 import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator;
 import com.android.systemui.statusbar.notification.collection.NotifLiveDataStore;
 import com.android.systemui.statusbar.notification.collection.NotifPipeline;
@@ -220,7 +218,6 @@
     @Mock private BatteryController mBatteryController;
     @Mock private DeviceProvisionedController mDeviceProvisionedController;
     @Mock private StatusBarNotificationPresenter mNotificationPresenter;
-    @Mock private NotificationFilter mNotificationFilter;
     @Mock private AmbientDisplayConfiguration mAmbientDisplayConfiguration;
     @Mock private NotificationLogger.ExpansionStateLogger mExpansionStateLogger;
     @Mock private KeyguardUpdateMonitor mKeyguardUpdateMonitor;
@@ -242,7 +239,6 @@
     @Mock private AutoHideController mAutoHideController;
     @Mock private StatusBarWindowController mStatusBarWindowController;
     @Mock private StatusBarWindowStateController mStatusBarWindowStateController;
-    @Mock private NotificationViewHierarchyManager mNotificationViewHierarchyManager;
     @Mock private UserSwitcherController mUserSwitcherController;
     @Mock private Bubbles mBubbles;
     @Mock private NotificationShadeWindowController mNotificationShadeWindowController;
@@ -283,7 +279,6 @@
     @Mock private OperatorNameViewController mOperatorNameViewController;
     @Mock private OperatorNameViewController.Factory mOperatorNameViewControllerFactory;
     @Mock private ActivityLaunchAnimator mActivityLaunchAnimator;
-    @Mock private NotifPipelineFlags mNotifPipelineFlags;
     @Mock private NotifLiveDataStore mNotifLiveDataStore;
     @Mock private InteractionJankMonitor mJankMonitor;
     @Mock private DeviceStateManager mDeviceStateManager;
@@ -298,7 +293,6 @@
     @Before
     public void setup() throws Exception {
         MockitoAnnotations.initMocks(this);
-        mDependency.injectTestDependency(NotificationFilter.class, mNotificationFilter);
 
         IPowerManager powerManagerService = mock(IPowerManager.class);
         IThermalService thermalService = mock(IThermalService.class);
@@ -310,7 +304,6 @@
                         mPowerManager,
                         mDreamManager,
                         mAmbientDisplayConfiguration,
-                        mNotificationFilter,
                         mStatusBarStateController,
                         mKeyguardStateController,
                         mBatteryController,
@@ -410,7 +403,6 @@
                 mNotificationGutsManager,
                 notificationLogger,
                 mNotificationInterruptStateProvider,
-                mNotificationViewHierarchyManager,
                 new PanelExpansionStateManager(),
                 mKeyguardViewMediator,
                 new DisplayMetrics(),
@@ -472,7 +464,6 @@
                 mWallpaperManager,
                 Optional.of(mStartingSurface),
                 mActivityLaunchAnimator,
-                mNotifPipelineFlags,
                 mJankMonitor,
                 mDeviceStateManager,
                 mWiredChargingRippleController, mDreamManager);
@@ -659,7 +650,6 @@
     public void testShouldHeadsUp_nonSuppressedGroupSummary() throws Exception {
         when(mPowerManager.isScreenOn()).thenReturn(true);
         when(mHeadsUpManager.isSnoozed(anyString())).thenReturn(false);
-        when(mNotificationFilter.shouldFilterOut(any())).thenReturn(false);
         when(mDreamManager.isDreaming()).thenReturn(false);
 
         Notification n = new Notification.Builder(getContext(), "a")
@@ -683,7 +673,6 @@
     public void testShouldHeadsUp_suppressedGroupSummary() throws Exception {
         when(mPowerManager.isScreenOn()).thenReturn(true);
         when(mHeadsUpManager.isSnoozed(anyString())).thenReturn(false);
-        when(mNotificationFilter.shouldFilterOut(any())).thenReturn(false);
         when(mDreamManager.isDreaming()).thenReturn(false);
 
         Notification n = new Notification.Builder(getContext(), "a")
@@ -707,7 +696,6 @@
     public void testShouldHeadsUp_suppressedHeadsUp() throws Exception {
         when(mPowerManager.isScreenOn()).thenReturn(true);
         when(mHeadsUpManager.isSnoozed(anyString())).thenReturn(false);
-        when(mNotificationFilter.shouldFilterOut(any())).thenReturn(false);
         when(mDreamManager.isDreaming()).thenReturn(false);
 
         Notification n = new Notification.Builder(getContext(), "a").build();
@@ -729,7 +717,6 @@
     public void testShouldHeadsUp_noSuppressedHeadsUp() throws Exception {
         when(mPowerManager.isScreenOn()).thenReturn(true);
         when(mHeadsUpManager.isSnoozed(anyString())).thenReturn(false);
-        when(mNotificationFilter.shouldFilterOut(any())).thenReturn(false);
         when(mDreamManager.isDreaming()).thenReturn(false);
 
         Notification n = new Notification.Builder(getContext(), "a").build();
@@ -1041,7 +1028,6 @@
                 PowerManager powerManager,
                 IDreamManager dreamManager,
                 AmbientDisplayConfiguration ambientDisplayConfiguration,
-                NotificationFilter filter,
                 StatusBarStateController controller,
                 KeyguardStateController keyguardStateController,
                 BatteryController batteryController,
@@ -1055,7 +1041,6 @@
                     powerManager,
                     dreamManager,
                     ambientDisplayConfiguration,
-                    filter,
                     batteryController,
                     controller,
                     keyguardStateController,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LetterboxAppearanceCalculatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LetterboxAppearanceCalculatorTest.kt
index 0531bc7..c0243dc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LetterboxAppearanceCalculatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LetterboxAppearanceCalculatorTest.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.statusbar.phone
 
+import android.graphics.Color
 import android.graphics.Rect
 import android.testing.AndroidTestingRunner
 import android.view.WindowInsetsController
@@ -56,6 +57,7 @@
     @Mock private lateinit var statusBarBoundsProvider: StatusBarBoundsProvider
     @Mock private lateinit var statusBarFragmentComponent: StatusBarFragmentComponent
     @Mock private lateinit var dumpManager: DumpManager
+    @Mock private lateinit var letterboxBackgroundProvider: LetterboxBackgroundProvider
 
     private lateinit var calculator: LetterboxAppearanceCalculator
 
@@ -63,8 +65,12 @@
     fun setUp() {
         MockitoAnnotations.initMocks(this)
         whenever(statusBarFragmentComponent.boundsProvider).thenReturn(statusBarBoundsProvider)
-        calculator = LetterboxAppearanceCalculator(lightBarController, dumpManager)
+        calculator =
+            LetterboxAppearanceCalculator(
+                lightBarController, dumpManager, letterboxBackgroundProvider)
         calculator.onStatusBarViewInitialized(statusBarFragmentComponent)
+        whenever(letterboxBackgroundProvider.letterboxBackgroundColor).thenReturn(Color.BLACK)
+        whenever(letterboxBackgroundProvider.isLetterboxBackgroundMultiColored).thenReturn(false)
     }
 
     @Test
@@ -100,6 +106,23 @@
     }
 
     @Test
+    fun getLetterboxAppearance_noOverlap_BackgroundMultiColor_returnsAppearanceWithScrim() {
+        whenever(letterboxBackgroundProvider.isLetterboxBackgroundMultiColored).thenReturn(true)
+        whenever(statusBarBoundsProvider.visibleStartSideBounds).thenReturn(Rect(0, 0, 100, 100))
+        whenever(statusBarBoundsProvider.visibleEndSideBounds).thenReturn(Rect(200, 0, 300, 100))
+        val letterbox = letterboxWithInnerBounds(Rect(101, 0, 199, 100))
+
+        val letterboxAppearance =
+            calculator.getLetterboxAppearance(
+                TEST_APPEARANCE, TEST_APPEARANCE_REGIONS, arrayOf(letterbox))
+
+        expect
+                .that(letterboxAppearance.appearance)
+                .isEqualTo(TEST_APPEARANCE or APPEARANCE_SEMI_TRANSPARENT_STATUS_BARS)
+        expect.that(letterboxAppearance.appearanceRegions).isEqualTo(TEST_APPEARANCE_REGIONS)
+    }
+
+    @Test
     fun getLetterboxAppearance_noOverlap_returnsAppearanceWithoutScrim() {
         whenever(statusBarBoundsProvider.visibleStartSideBounds).thenReturn(Rect(0, 0, 100, 100))
         whenever(statusBarBoundsProvider.visibleEndSideBounds).thenReturn(Rect(200, 0, 300, 100))
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LetterboxBackgroundProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LetterboxBackgroundProviderTest.kt
new file mode 100644
index 0000000..44325dd
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LetterboxBackgroundProviderTest.kt
@@ -0,0 +1,100 @@
+/*
+ * 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.phone
+
+import android.graphics.Color
+import android.testing.AndroidTestingRunner
+import android.view.IWindowManager
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.dump.DumpManager
+import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.time.FakeSystemClock
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.`when` as whenever
+import org.mockito.MockitoAnnotations
+
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+class LetterboxBackgroundProviderTest : SysuiTestCase() {
+
+    private val fakeSystemClock = FakeSystemClock()
+    private val fakeExecutor = FakeExecutor(fakeSystemClock)
+
+    @Mock private lateinit var windowManager: IWindowManager
+    @Mock private lateinit var dumpManager: DumpManager
+
+    private lateinit var provider: LetterboxBackgroundProvider
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+
+        provider = LetterboxBackgroundProvider(windowManager, fakeExecutor, dumpManager)
+    }
+
+    @Test
+    fun letterboxBackgroundColor_defaultValue_returnsBlack() {
+        assertThat(provider.letterboxBackgroundColor).isEqualTo(Color.BLACK)
+    }
+
+    @Test
+    fun letterboxBackgroundColor_afterOnStart_executorNotDone_returnsDefaultValue() {
+        whenever(windowManager.letterboxBackgroundColorInArgb).thenReturn(Color.RED)
+
+        provider.start()
+
+        assertThat(provider.letterboxBackgroundColor).isEqualTo(Color.BLACK)
+    }
+
+    @Test
+    fun letterboxBackgroundColor_afterOnStart_executorDone_returnsValueFromWindowManager() {
+        whenever(windowManager.letterboxBackgroundColorInArgb).thenReturn(Color.RED)
+
+        provider.start()
+        fakeExecutor.runAllReady()
+
+        assertThat(provider.letterboxBackgroundColor).isEqualTo(Color.RED)
+    }
+
+    @Test
+    fun isLetterboxBackgroundMultiColored_defaultValue_returnsFalse() {
+        assertThat(provider.isLetterboxBackgroundMultiColored).isEqualTo(false)
+    }
+    @Test
+    fun isLetterboxBackgroundMultiColored_afterOnStart_executorNotDone_returnsDefaultValue() {
+        whenever(windowManager.isLetterboxBackgroundMultiColored).thenReturn(true)
+
+        provider.start()
+
+        assertThat(provider.isLetterboxBackgroundMultiColored).isFalse()
+    }
+
+    @Test
+    fun isBackgroundMultiColored_afterOnStart_executorDone_returnsValueFromWindowManager() {
+        whenever(windowManager.isLetterboxBackgroundMultiColored).thenReturn(true)
+
+        provider.start()
+        fakeExecutor.runAllReady()
+
+        assertThat(provider.isLetterboxBackgroundMultiColored).isTrue()
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java
index cf1eb35..eef43bd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java
@@ -43,7 +43,6 @@
 import com.android.systemui.shade.NotificationPanelViewController;
 import com.android.systemui.shade.NotificationShadeWindowView;
 import com.android.systemui.shade.ShadeController;
-import com.android.systemui.shade.ShadeControllerImpl;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.KeyguardIndicationController;
 import com.android.systemui.statusbar.LockscreenShadeTransitionController;
@@ -76,16 +75,17 @@
 @RunWithLooper()
 public class StatusBarNotificationPresenterTest extends SysuiTestCase {
     private StatusBarNotificationPresenter mStatusBarNotificationPresenter;
-    private NotificationInterruptStateProvider mNotificationInterruptStateProvider =
+    private final NotificationInterruptStateProvider mNotificationInterruptStateProvider =
             mock(NotificationInterruptStateProvider.class);
     private NotificationInterruptSuppressor mInterruptSuppressor;
     private CommandQueue mCommandQueue;
     private FakeMetricsLogger mMetricsLogger;
-    private ShadeController mShadeController = mock(ShadeController.class);
-    private CentralSurfaces mCentralSurfaces = mock(CentralSurfaces.class);
-    private KeyguardStateController mKeyguardStateController = mock(KeyguardStateController.class);
-    private NotifPipelineFlags mNotifPipelineFlags = mock(NotifPipelineFlags.class);
-    private InitController mInitController = new InitController();
+    private final ShadeController mShadeController = mock(ShadeController.class);
+    private final CentralSurfaces mCentralSurfaces = mock(CentralSurfaces.class);
+    private final KeyguardStateController mKeyguardStateController =
+            mock(KeyguardStateController.class);
+    private final NotifPipelineFlags mNotifPipelineFlags = mock(NotifPipelineFlags.class);
+    private final InitController mInitController = new InitController();
 
     @Before
     public void setup() {
@@ -116,13 +116,11 @@
                 mock(ActivityStarter.class),
                 stackScrollLayoutController,
                 mock(DozeScrimController.class),
-                mock(ScrimController.class),
                 mock(NotificationShadeWindowController.class),
                 mock(DynamicPrivacyController.class),
                 mKeyguardStateController,
                 mock(KeyguardIndicationController.class),
                 mCentralSurfaces,
-                mock(ShadeControllerImpl.class),
                 mock(LockscreenShadeTransitionController.class),
                 mCommandQueue,
                 mock(NotificationLockscreenUserManager.class),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/ConnectivityPipelineLoggerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/ConnectivityPipelineLoggerTest.kt
new file mode 100644
index 0000000..2915ae8
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/ConnectivityPipelineLoggerTest.kt
@@ -0,0 +1,75 @@
+/*
+ * 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.pipeline
+
+import android.net.Network
+import android.net.NetworkCapabilities
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.dump.DumpManager
+import com.android.systemui.log.LogBufferFactory
+import com.android.systemui.log.LogcatEchoTracker
+import com.google.common.truth.Truth.assertThat
+import java.io.PrintWriter
+import java.io.StringWriter
+import org.junit.Test
+import org.mockito.Mockito
+import org.mockito.Mockito.mock
+
+@SmallTest
+class ConnectivityPipelineLoggerTest : SysuiTestCase() {
+    private val buffer = LogBufferFactory(DumpManager(), mock(LogcatEchoTracker::class.java))
+        .create("buffer", 10)
+    private val logger = ConnectivityPipelineLogger(buffer)
+
+    @Test
+    fun testLogNetworkCapsChange_bufferHasInfo() {
+        logger.logOnCapabilitiesChanged(NET_1, NET_1_CAPS)
+
+        val stringWriter = StringWriter()
+        buffer.dump(PrintWriter(stringWriter), tailLength = 0)
+        val actualString = stringWriter.toString()
+
+        val expectedNetId = NET_1_ID.toString()
+        val expectedCaps = NET_1_CAPS.toString()
+
+        assertThat(actualString).contains(expectedNetId)
+        assertThat(actualString).contains(expectedCaps)
+    }
+
+    @Test
+    fun testLogOnLost_bufferHasNetIdOfLostNetwork() {
+        logger.logOnLost(NET_1)
+
+        val stringWriter = StringWriter()
+        buffer.dump(PrintWriter(stringWriter), tailLength = 0)
+        val actualString = stringWriter.toString()
+
+        val expectedNetId = NET_1_ID.toString()
+
+        assertThat(actualString).contains(expectedNetId)
+    }
+
+    private val NET_1_ID = 100
+    private val NET_1 = com.android.systemui.util.mockito.mock<Network>().also {
+        Mockito.`when`(it.getNetId()).thenReturn(NET_1_ID)
+    }
+    private val NET_1_CAPS = NetworkCapabilities.Builder()
+        .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
+        .addCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED)
+        .build()
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/repository/NetworkCapabilitiesRepoTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/repository/NetworkCapabilitiesRepoTest.kt
new file mode 100644
index 0000000..40f8fbf
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/repository/NetworkCapabilitiesRepoTest.kt
@@ -0,0 +1,252 @@
+/*
+ * 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.pipeline.repository
+
+import android.net.ConnectivityManager
+import android.net.ConnectivityManager.NetworkCallback
+import android.net.Network
+import android.net.NetworkCapabilities
+import android.net.NetworkCapabilities.NET_CAPABILITY_NOT_METERED
+import android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED
+import android.net.NetworkCapabilities.TRANSPORT_CELLULAR
+import android.net.NetworkCapabilities.TRANSPORT_WIFI
+import android.net.NetworkRequest
+import android.test.suitebuilder.annotation.SmallTest
+import android.testing.AndroidTestingRunner
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.statusbar.pipeline.ConnectivityPipelineLogger
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.withArgCaptor
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.CoroutineStart
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.cancel
+import kotlinx.coroutines.flow.collect
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.runBlocking
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.any
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when` as whenever
+import org.mockito.MockitoAnnotations
+
+// TODO(b/240619365): Update this test to use `runTest` when we update the testing library
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class NetworkCapabilitiesRepoTest : SysuiTestCase() {
+    @Mock private lateinit var connectivityManager: ConnectivityManager
+    @Mock private lateinit var logger: ConnectivityPipelineLogger
+
+    @Before
+    fun setup() {
+        MockitoAnnotations.initMocks(this)
+    }
+
+    @Test
+    fun testOnCapabilitiesChanged_oneNewNetwork_networkStored() = runBlocking {
+        // GIVEN a repo hooked up to [ConnectivityManager]
+        val scope = CoroutineScope(Dispatchers.Unconfined)
+        val repo = NetworkCapabilitiesRepo(
+            connectivityManager = connectivityManager,
+            scope = scope,
+            logger = logger,
+        )
+
+        val job = launch(start = CoroutineStart.UNDISPATCHED) {
+            repo.dataStream.collect {
+            }
+        }
+
+        val callback: NetworkCallback = withArgCaptor {
+            verify(connectivityManager)
+                .registerNetworkCallback(any(NetworkRequest::class.java), capture())
+        }
+
+        // WHEN a new network is added
+        callback.onCapabilitiesChanged(NET_1, NET_1_CAPS)
+
+        val currentMap = repo.dataStream.value
+
+        // THEN it is emitted from the flow
+        assertThat(currentMap[NET_1_ID]?.network).isEqualTo(NET_1)
+        assertThat(currentMap[NET_1_ID]?.capabilities).isEqualTo(NET_1_CAPS)
+
+        job.cancel()
+        scope.cancel()
+    }
+
+    @Test
+    fun testOnCapabilitiesChanged_twoNewNetworks_bothStored() = runBlocking {
+        // GIVEN a repo hooked up to [ConnectivityManager]
+        val scope = CoroutineScope(Dispatchers.Unconfined)
+        val repo = NetworkCapabilitiesRepo(
+            connectivityManager = connectivityManager,
+            scope = scope,
+            logger = logger,
+        )
+
+        val job = launch(start = CoroutineStart.UNDISPATCHED) {
+            repo.dataStream.collect {
+            }
+        }
+
+        val callback: NetworkCallback = withArgCaptor {
+            verify(connectivityManager)
+                .registerNetworkCallback(any(NetworkRequest::class.java), capture())
+        }
+
+        // WHEN two new networks are added
+        callback.onCapabilitiesChanged(NET_1, NET_1_CAPS)
+        callback.onCapabilitiesChanged(NET_2, NET_2_CAPS)
+
+        val currentMap = repo.dataStream.value
+
+        // THEN the current state of the flow reflects 2 networks
+        assertThat(currentMap[NET_1_ID]?.network).isEqualTo(NET_1)
+        assertThat(currentMap[NET_1_ID]?.capabilities).isEqualTo(NET_1_CAPS)
+        assertThat(currentMap[NET_2_ID]?.network).isEqualTo(NET_2)
+        assertThat(currentMap[NET_2_ID]?.capabilities).isEqualTo(NET_2_CAPS)
+
+        job.cancel()
+        scope.cancel()
+    }
+
+    @Test
+    fun testOnCapabilitesChanged_newCapabilitiesForExistingNetwork_areCaptured() = runBlocking {
+        // GIVEN a repo hooked up to [ConnectivityManager]
+        val scope = CoroutineScope(Dispatchers.Unconfined)
+        val repo = NetworkCapabilitiesRepo(
+            connectivityManager = connectivityManager,
+            scope = scope,
+            logger = logger,
+        )
+
+        val job = launch(start = CoroutineStart.UNDISPATCHED) {
+            repo.dataStream.collect {
+            }
+        }
+
+        val callback: NetworkCallback = withArgCaptor {
+            verify(connectivityManager)
+                .registerNetworkCallback(any(NetworkRequest::class.java), capture())
+        }
+
+        // WHEN a network is added, and then its capabilities are changed
+        callback.onCapabilitiesChanged(NET_1, NET_1_CAPS)
+        callback.onCapabilitiesChanged(NET_1, NET_2_CAPS)
+
+        val currentMap = repo.dataStream.value
+
+        // THEN the current state of the flow reflects the new capabilities
+        assertThat(currentMap[NET_1_ID]?.capabilities).isEqualTo(NET_2_CAPS)
+
+        job.cancel()
+        scope.cancel()
+    }
+
+    @Test
+    fun testOnLost_networkIsRemoved() = runBlocking {
+        // GIVEN a repo hooked up to [ConnectivityManager]
+        val scope = CoroutineScope(Dispatchers.Unconfined)
+        val repo = NetworkCapabilitiesRepo(
+            connectivityManager = connectivityManager,
+            scope = scope,
+            logger = logger,
+        )
+
+        val job = launch(start = CoroutineStart.UNDISPATCHED) {
+            repo.dataStream.collect {
+            }
+        }
+
+        val callback: NetworkCallback = withArgCaptor {
+            verify(connectivityManager)
+                .registerNetworkCallback(any(NetworkRequest::class.java), capture())
+        }
+
+        // WHEN two new networks are added, and one is removed
+        callback.onCapabilitiesChanged(NET_1, NET_1_CAPS)
+        callback.onCapabilitiesChanged(NET_2, NET_2_CAPS)
+        callback.onLost(NET_1)
+
+        val currentMap = repo.dataStream.value
+
+        // THEN the current state of the flow reflects only the remaining network
+        assertThat(currentMap[NET_1_ID]).isNull()
+        assertThat(currentMap[NET_2_ID]?.network).isEqualTo(NET_2)
+        assertThat(currentMap[NET_2_ID]?.capabilities).isEqualTo(NET_2_CAPS)
+
+        job.cancel()
+        scope.cancel()
+    }
+
+    @Test
+    fun testOnLost_noNetworks_doesNotCrash() = runBlocking {
+        // GIVEN a repo hooked up to [ConnectivityManager]
+        val scope = CoroutineScope(Dispatchers.Unconfined)
+        val repo = NetworkCapabilitiesRepo(
+            connectivityManager = connectivityManager,
+            scope = scope,
+            logger = logger,
+        )
+
+        val job = launch(start = CoroutineStart.UNDISPATCHED) {
+            repo.dataStream.collect {
+            }
+        }
+
+        val callback: NetworkCallback = withArgCaptor {
+            verify(connectivityManager)
+                .registerNetworkCallback(any(NetworkRequest::class.java), capture())
+        }
+
+        // WHEN no networks are added, and one is removed
+        callback.onLost(NET_1)
+
+        val currentMap = repo.dataStream.value
+
+        // THEN the current state of the flow shows no networks
+        assertThat(currentMap).isEmpty()
+
+        job.cancel()
+        scope.cancel()
+    }
+
+    private val NET_1_ID = 100
+    private val NET_1 = mock<Network>().also {
+        whenever(it.getNetId()).thenReturn(NET_1_ID)
+    }
+    private val NET_2_ID = 200
+    private val NET_2 = mock<Network>().also {
+        whenever(it.getNetId()).thenReturn(NET_2_ID)
+    }
+
+    private val NET_1_CAPS = NetworkCapabilities.Builder()
+        .addTransportType(TRANSPORT_CELLULAR)
+        .addCapability(NET_CAPABILITY_VALIDATED)
+        .build()
+
+    private val NET_2_CAPS = NetworkCapabilities.Builder()
+        .addTransportType(TRANSPORT_WIFI)
+        .addCapability(NET_CAPABILITY_NOT_METERED)
+        .addCapability(NET_CAPABILITY_VALIDATED)
+        .build()
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/FoldAodAnimationControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/unfold/FoldAodAnimationControllerTest.kt
new file mode 100644
index 0000000..f51f783
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/unfold/FoldAodAnimationControllerTest.kt
@@ -0,0 +1,166 @@
+/*
+ * 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.unfold
+
+import android.hardware.devicestate.DeviceStateManager
+import android.hardware.devicestate.DeviceStateManager.FoldStateListener
+import android.os.Handler
+import android.os.PowerManager
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import android.testing.TestableLooper.RunWithLooper
+import android.view.ViewGroup
+import android.view.ViewTreeObserver
+import androidx.test.filters.SmallTest
+import com.android.internal.util.LatencyTracker
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.keyguard.WakefulnessLifecycle
+import com.android.systemui.shade.NotificationPanelViewController
+import com.android.systemui.statusbar.LightRevealScrim
+import com.android.systemui.statusbar.phone.CentralSurfaces
+import com.android.systemui.unfold.util.FoldableDeviceStates
+import com.android.systemui.unfold.util.FoldableTestUtils
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.settings.GlobalSettings
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.Captor
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.verifyNoMoreInteractions
+import org.mockito.Mockito.`when` as whenever
+import org.mockito.MockitoAnnotations
+
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+@RunWithLooper
+class FoldAodAnimationControllerTest : SysuiTestCase() {
+
+    @Mock lateinit var deviceStateManager: DeviceStateManager
+
+    @Mock lateinit var wakefulnessLifecycle: WakefulnessLifecycle
+
+    @Mock lateinit var globalSettings: GlobalSettings
+
+    @Mock lateinit var latencyTracker: LatencyTracker
+
+    @Mock lateinit var centralSurfaces: CentralSurfaces
+
+    @Mock lateinit var lightRevealScrim: LightRevealScrim
+
+    @Mock lateinit var notificationPanelViewController: NotificationPanelViewController
+
+    @Mock lateinit var viewGroup: ViewGroup
+
+    @Mock lateinit var viewTreeObserver: ViewTreeObserver
+
+    @Captor private lateinit var foldStateListenerCaptor: ArgumentCaptor<FoldStateListener>
+
+    private lateinit var deviceStates: FoldableDeviceStates
+
+    private lateinit var testableLooper: TestableLooper
+
+    lateinit var foldAodAnimationController: FoldAodAnimationController
+
+    @Before
+    fun setup() {
+        MockitoAnnotations.initMocks(this)
+        testableLooper = TestableLooper.get(this)
+
+        foldAodAnimationController =
+            FoldAodAnimationController(
+                    Handler(testableLooper.looper),
+                    context.mainExecutor,
+                    context,
+                    deviceStateManager,
+                    wakefulnessLifecycle,
+                    globalSettings,
+                    latencyTracker,
+                )
+                .apply { initialize(centralSurfaces, lightRevealScrim) }
+        deviceStates = FoldableTestUtils.findDeviceStates(context)
+
+        whenever(notificationPanelViewController.view).thenReturn(viewGroup)
+        whenever(viewGroup.viewTreeObserver).thenReturn(viewTreeObserver)
+        whenever(wakefulnessLifecycle.lastSleepReason)
+            .thenReturn(PowerManager.GO_TO_SLEEP_REASON_DEVICE_FOLD)
+        whenever(centralSurfaces.notificationPanelViewController)
+            .thenReturn(notificationPanelViewController)
+        whenever(notificationPanelViewController.startFoldToAodAnimation(any(), any(), any()))
+            .then {
+                val onActionStarted = it.arguments[0] as Runnable
+                onActionStarted.run()
+            }
+        verify(deviceStateManager).registerCallback(any(), foldStateListenerCaptor.capture())
+
+        foldAodAnimationController.setIsDozing(dozing = true)
+        setAodEnabled(enabled = true)
+        sendFoldEvent(folded = false)
+    }
+
+    @Test
+    fun onFolded_aodDisabled_doesNotLogLatency() {
+        setAodEnabled(enabled = false)
+
+        fold()
+        simulateScreenTurningOn()
+
+        verifyNoMoreInteractions(latencyTracker)
+    }
+
+    @Test
+    fun onFolded_aodEnabled_logsLatency() {
+        setAodEnabled(enabled = true)
+
+        fold()
+        simulateScreenTurningOn()
+
+        verify(latencyTracker).onActionStart(any())
+        verify(latencyTracker).onActionEnd(any())
+    }
+
+    @Test
+    fun onFolded_animationCancelled_doesNotLogLatency() {
+        setAodEnabled(enabled = true)
+
+        fold()
+        foldAodAnimationController.onScreenTurningOn({})
+        foldAodAnimationController.onStartedWakingUp()
+        testableLooper.processAllMessages()
+
+        verify(latencyTracker).onActionStart(any())
+        verify(latencyTracker).onActionCancel(any())
+    }
+
+    private fun simulateScreenTurningOn() {
+        foldAodAnimationController.onScreenTurningOn({})
+        foldAodAnimationController.onScreenTurnedOn()
+        testableLooper.processAllMessages()
+    }
+
+    private fun fold() = sendFoldEvent(folded = true)
+
+    private fun setAodEnabled(enabled: Boolean) =
+        foldAodAnimationController.onAlwaysOnChanged(alwaysOn = enabled)
+
+    private fun sendFoldEvent(folded: Boolean) {
+        val state = if (folded) deviceStates.folded else deviceStates.unfolded
+        foldStateListenerCaptor.value.onStateChanged(state)
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
index 59a9a3c..d009280 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
@@ -43,6 +43,7 @@
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
+import android.app.ActivityManager;
 import android.app.IActivityManager;
 import android.app.INotificationManager;
 import android.app.Notification;
@@ -93,7 +94,6 @@
 import com.android.systemui.statusbar.RankingBuilder;
 import com.android.systemui.statusbar.SysuiStatusBarStateController;
 import com.android.systemui.statusbar.notification.NotifPipelineFlags;
-import com.android.systemui.statusbar.notification.NotificationFilter;
 import com.android.systemui.statusbar.notification.collection.NotifPipeline;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
@@ -143,7 +143,10 @@
 import org.mockito.Captor;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
+import org.mockito.stubbing.Answer;
 
+import java.util.Arrays;
+import java.util.Collections;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Optional;
@@ -252,6 +255,8 @@
     private TaskViewTransitions mTaskViewTransitions;
     @Mock
     private Optional<OneHandedController> mOneHandedOptional;
+    @Mock
+    private UserManager mUserManager;
 
     private TestableBubblePositioner mPositioner;
 
@@ -314,12 +319,14 @@
         mPositioner.setMaxBubbles(5);
         mBubbleData = new BubbleData(mContext, mBubbleLogger, mPositioner, syncExecutor);
 
+        when(mUserManager.getProfiles(ActivityManager.getCurrentUser())).thenReturn(
+                Collections.singletonList(mock(UserInfo.class)));
+
         TestableNotificationInterruptStateProviderImpl interruptionStateProvider =
                 new TestableNotificationInterruptStateProviderImpl(mContext.getContentResolver(),
                         mock(PowerManager.class),
                         mock(IDreamManager.class),
                         mock(AmbientDisplayConfiguration.class),
-                        mock(NotificationFilter.class),
                         mock(StatusBarStateController.class),
                         mock(KeyguardStateController.class),
                         mock(BatteryController.class),
@@ -339,7 +346,7 @@
                 mStatusBarService,
                 mWindowManager,
                 mWindowManagerShellWrapper,
-                mock(UserManager.class),
+                mUserManager,
                 mLauncherApps,
                 mBubbleLogger,
                 mTaskStackListener,
@@ -1025,7 +1032,7 @@
         assertThat(mBubbleData.getOverflowBubbleWithKey(mBubbleEntry2.getKey())).isNotNull();
 
         // Switch users
-        mBubbleController.onUserChanged(secondUserId);
+        switchUser(secondUserId);
         assertThat(mBubbleData.getOverflowBubbles()).isEmpty();
 
         // Give this user some bubbles
@@ -1042,6 +1049,41 @@
         verify(mDataRepository, times(2)).loadBubbles(anyInt(), any());
     }
 
+    @Test
+    public void testOnUserChanged_bubblesRestored() {
+        int firstUserId = mBubbleEntry.getStatusBarNotification().getUser().getIdentifier();
+        int secondUserId = mBubbleEntryUser11.getStatusBarNotification().getUser().getIdentifier();
+        // Mock current profile
+        when(mLockscreenUserManager.isCurrentProfile(firstUserId)).thenReturn(true);
+        when(mLockscreenUserManager.isCurrentProfile(secondUserId)).thenReturn(false);
+
+        mBubbleController.updateBubble(mBubbleEntry);
+        assertThat(mBubbleController.hasBubbles()).isTrue();
+        // We start with 1 bubble
+        assertThat(mBubbleData.getBubbles()).hasSize(1);
+
+        // Switch to second user
+        switchUser(secondUserId);
+
+        // Second user has no bubbles
+        assertThat(mBubbleController.hasBubbles()).isFalse();
+
+        // Send bubble update for first user, ensure it does not show up
+        mBubbleController.updateBubble(mBubbleEntry2);
+        assertThat(mBubbleController.hasBubbles()).isFalse();
+
+        // Start returning notif for first user again
+        when(mCommonNotifCollection.getAllNotifs()).thenReturn(Arrays.asList(mRow, mRow2));
+
+        // Switch back to first user
+        switchUser(firstUserId);
+
+        // Check we now have two bubbles, one previous and one new that came in
+        assertThat(mBubbleController.hasBubbles()).isTrue();
+        // Now there are 2 bubbles
+        assertThat(mBubbleData.getBubbles()).hasSize(2);
+    }
+
     /**
      * Verifies we only load the overflow data once.
      */
@@ -1443,6 +1485,14 @@
                 .build();
     }
 
+    private void switchUser(int userId) {
+        when(mLockscreenUserManager.isCurrentProfile(anyInt())).thenAnswer(
+                (Answer<Boolean>) invocation -> invocation.<Integer>getArgument(0) == userId);
+        SparseArray<UserInfo> userInfos = new SparseArray<>(1);
+        userInfos.put(userId, mock(UserInfo.class));
+        mBubbleController.onCurrentProfilesChanged(userInfos);
+        mBubbleController.onUserChanged(userId);
+    }
 
     /**
      * Asserts that the bubble stack is expanded and also validates the cached state is updated.
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/TestableNotificationInterruptStateProviderImpl.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/TestableNotificationInterruptStateProviderImpl.java
index d80ea15..9635faf 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/TestableNotificationInterruptStateProviderImpl.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/TestableNotificationInterruptStateProviderImpl.java
@@ -24,7 +24,6 @@
 
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.statusbar.notification.NotifPipelineFlags;
-import com.android.systemui.statusbar.notification.NotificationFilter;
 import com.android.systemui.statusbar.notification.interruption.KeyguardNotificationVisibilityProvider;
 import com.android.systemui.statusbar.notification.interruption.NotificationInterruptLogger;
 import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProviderImpl;
@@ -40,7 +39,6 @@
             PowerManager powerManager,
             IDreamManager dreamManager,
             AmbientDisplayConfiguration ambientDisplayConfiguration,
-            NotificationFilter filter,
             StatusBarStateController statusBarStateController,
             KeyguardStateController keyguardStateController,
             BatteryController batteryController,
@@ -53,7 +51,6 @@
                 powerManager,
                 dreamManager,
                 ambientDisplayConfiguration,
-                filter,
                 batteryController,
                 statusBarStateController,
                 keyguardStateController,
diff --git a/packages/SystemUI/tests/utils/AndroidManifest.xml b/packages/SystemUI/tests/utils/AndroidManifest.xml
new file mode 100644
index 0000000..cbef5f6
--- /dev/null
+++ b/packages/SystemUI/tests/utils/AndroidManifest.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.android.systemui.tests.utils">
+
+
+</manifest>
+
diff --git a/packages/SystemUI/tests/src/com/android/systemui/SysuiBaseFragmentTest.java b/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiBaseFragmentTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/SysuiBaseFragmentTest.java
rename to packages/SystemUI/tests/utils/src/com/android/systemui/SysuiBaseFragmentTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/SysuiTestCase.java b/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCase.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/SysuiTestCase.java
rename to packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCase.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/SysuiTestableContext.java b/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestableContext.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/SysuiTestableContext.java
rename to packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestableContext.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/TestableDependency.java b/packages/SystemUI/tests/utils/src/com/android/systemui/TestableDependency.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/TestableDependency.java
rename to packages/SystemUI/tests/utils/src/com/android/systemui/TestableDependency.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/broadcast/FakeBroadcastDispatcher.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/broadcast/FakeBroadcastDispatcher.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/broadcast/FakeBroadcastDispatcher.kt
rename to packages/SystemUI/tests/utils/src/com/android/systemui/broadcast/FakeBroadcastDispatcher.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingManagerFake.java b/packages/SystemUI/tests/utils/src/com/android/systemui/classifier/FalsingManagerFake.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingManagerFake.java
rename to packages/SystemUI/tests/utils/src/com/android/systemui/classifier/FalsingManagerFake.java
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/people/FakePeopleTileRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/people/FakePeopleTileRepository.kt
new file mode 100644
index 0000000..0bde5d2
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/people/FakePeopleTileRepository.kt
@@ -0,0 +1,30 @@
+/*
+ * 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.people
+
+import com.android.systemui.people.data.model.PeopleTileModel
+import com.android.systemui.people.data.repository.PeopleTileRepository
+
+/** A fake [PeopleTileRepository] to be used in tests. */
+class FakePeopleTileRepository(
+    private val priorityTiles: List<PeopleTileModel>,
+    private val recentTiles: List<PeopleTileModel>,
+) : PeopleTileRepository {
+    override fun priorityTiles(): List<PeopleTileModel> = priorityTiles
+
+    override fun recentTiles(): List<PeopleTileModel> = recentTiles
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/people/FakePeopleWidgetRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/people/FakePeopleWidgetRepository.kt
new file mode 100644
index 0000000..2f81409
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/people/FakePeopleWidgetRepository.kt
@@ -0,0 +1,29 @@
+/*
+ * 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.people
+
+import com.android.systemui.people.data.repository.PeopleWidgetRepository
+import com.android.systemui.people.widget.PeopleTileKey
+
+/** A fake [PeopleWidgetRepository] to be used in tests. */
+class FakePeopleWidgetRepository(
+    private val onSetWidgetTile: (widgetId: Int, tileKey: PeopleTileKey) -> Unit = { _, _ -> },
+) : PeopleWidgetRepository {
+    override fun setWidgetTile(widgetId: Int, tileKey: PeopleTileKey) {
+        onSetWidgetTile(widgetId, tileKey)
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/FakeScrollCaptureConnection.java b/packages/SystemUI/tests/utils/src/com/android/systemui/screenshot/FakeScrollCaptureConnection.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/screenshot/FakeScrollCaptureConnection.java
rename to packages/SystemUI/tests/utils/src/com/android/systemui/screenshot/FakeScrollCaptureConnection.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/FakeSession.java b/packages/SystemUI/tests/utils/src/com/android/systemui/screenshot/FakeSession.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/screenshot/FakeSession.java
rename to packages/SystemUI/tests/utils/src/com/android/systemui/screenshot/FakeSession.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/RankingBuilder.java b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/RankingBuilder.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/RankingBuilder.java
rename to packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/RankingBuilder.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/SbnBuilder.java b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/SbnBuilder.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/SbnBuilder.java
rename to packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/SbnBuilder.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationEntryBuilder.java b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/collection/NotificationEntryBuilder.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationEntryBuilder.java
rename to packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/collection/NotificationEntryBuilder.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/FakeConfigurationController.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/FakeConfigurationController.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/FakeConfigurationController.kt
rename to packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/FakeConfigurationController.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/DeviceConfigProxyFake.java b/packages/SystemUI/tests/utils/src/com/android/systemui/util/DeviceConfigProxyFake.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/util/DeviceConfigProxyFake.java
rename to packages/SystemUI/tests/utils/src/com/android/systemui/util/DeviceConfigProxyFake.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/concurrency/FakeExecutor.java b/packages/SystemUI/tests/utils/src/com/android/systemui/util/concurrency/FakeExecutor.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/util/concurrency/FakeExecutor.java
rename to packages/SystemUI/tests/utils/src/com/android/systemui/util/concurrency/FakeExecutor.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/concurrency/FakeRepeatableExecutor.java b/packages/SystemUI/tests/utils/src/com/android/systemui/util/concurrency/FakeRepeatableExecutor.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/util/concurrency/FakeRepeatableExecutor.java
rename to packages/SystemUI/tests/utils/src/com/android/systemui/util/concurrency/FakeRepeatableExecutor.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/concurrency/FakeThreadFactory.java b/packages/SystemUI/tests/utils/src/com/android/systemui/util/concurrency/FakeThreadFactory.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/util/concurrency/FakeThreadFactory.java
rename to packages/SystemUI/tests/utils/src/com/android/systemui/util/concurrency/FakeThreadFactory.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/condition/FakeCondition.java b/packages/SystemUI/tests/utils/src/com/android/systemui/util/condition/FakeCondition.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/util/condition/FakeCondition.java
rename to packages/SystemUI/tests/utils/src/com/android/systemui/util/condition/FakeCondition.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/io/FakeBasicFileAttributes.java b/packages/SystemUI/tests/utils/src/com/android/systemui/util/io/FakeBasicFileAttributes.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/util/io/FakeBasicFileAttributes.java
rename to packages/SystemUI/tests/utils/src/com/android/systemui/util/io/FakeBasicFileAttributes.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/sensors/FakeProximitySensor.java b/packages/SystemUI/tests/utils/src/com/android/systemui/util/sensors/FakeProximitySensor.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/util/sensors/FakeProximitySensor.java
rename to packages/SystemUI/tests/utils/src/com/android/systemui/util/sensors/FakeProximitySensor.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/sensors/FakeSensorManager.java b/packages/SystemUI/tests/utils/src/com/android/systemui/util/sensors/FakeSensorManager.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/util/sensors/FakeSensorManager.java
rename to packages/SystemUI/tests/utils/src/com/android/systemui/util/sensors/FakeSensorManager.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/sensors/FakeThresholdSensor.java b/packages/SystemUI/tests/utils/src/com/android/systemui/util/sensors/FakeThresholdSensor.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/util/sensors/FakeThresholdSensor.java
rename to packages/SystemUI/tests/utils/src/com/android/systemui/util/sensors/FakeThresholdSensor.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/settings/FakeSettings.java b/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/FakeSettings.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/util/settings/FakeSettings.java
rename to packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/FakeSettings.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/time/FakeSystemClock.java b/packages/SystemUI/tests/utils/src/com/android/systemui/util/time/FakeSystemClock.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/util/time/FakeSystemClock.java
rename to packages/SystemUI/tests/utils/src/com/android/systemui/util/time/FakeSystemClock.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/wakelock/WakeLockFake.java b/packages/SystemUI/tests/utils/src/com/android/systemui/util/wakelock/WakeLockFake.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/util/wakelock/WakeLockFake.java
rename to packages/SystemUI/tests/utils/src/com/android/systemui/util/wakelock/WakeLockFake.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/BaseLeakChecker.java b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/BaseLeakChecker.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/utils/leaks/BaseLeakChecker.java
rename to packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/BaseLeakChecker.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeBatteryController.java b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeBatteryController.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeBatteryController.java
rename to packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeBatteryController.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeBluetoothController.java b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeBluetoothController.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeBluetoothController.java
rename to packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeBluetoothController.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeCastController.java b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeCastController.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeCastController.java
rename to packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeCastController.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeConfigurationController.java b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeConfigurationController.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeConfigurationController.java
rename to packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeConfigurationController.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeDataSaverController.java b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeDataSaverController.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeDataSaverController.java
rename to packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeDataSaverController.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeExtensionController.java b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeExtensionController.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeExtensionController.java
rename to packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeExtensionController.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeFlashlightController.java b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeFlashlightController.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeFlashlightController.java
rename to packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeFlashlightController.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeHotspotController.java b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeHotspotController.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeHotspotController.java
rename to packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeHotspotController.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeKeyguardStateController.java b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeKeyguardStateController.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeKeyguardStateController.java
rename to packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeKeyguardStateController.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeLocationController.java b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeLocationController.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeLocationController.java
rename to packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeLocationController.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeManagedProfileController.java b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeManagedProfileController.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeManagedProfileController.java
rename to packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeManagedProfileController.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeNetworkController.java b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeNetworkController.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeNetworkController.java
rename to packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeNetworkController.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeNextAlarmController.java b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeNextAlarmController.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeNextAlarmController.java
rename to packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeNextAlarmController.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakePluginManager.java b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakePluginManager.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakePluginManager.java
rename to packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakePluginManager.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeRotationLockController.java b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeRotationLockController.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeRotationLockController.java
rename to packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeRotationLockController.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeSecurityController.java b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeSecurityController.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeSecurityController.java
rename to packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeSecurityController.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeStatusBarIconController.java b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeStatusBarIconController.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeStatusBarIconController.java
rename to packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeStatusBarIconController.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeTunerService.java b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeTunerService.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeTunerService.java
rename to packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeTunerService.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeUserInfoController.java b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeUserInfoController.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeUserInfoController.java
rename to packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeUserInfoController.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeZenModeController.java b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeZenModeController.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeZenModeController.java
rename to packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeZenModeController.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/LeakCheckedTest.java b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/LeakCheckedTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/utils/leaks/LeakCheckedTest.java
rename to packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/LeakCheckedTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/os/FakeHandler.java b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/os/FakeHandler.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/utils/os/FakeHandler.java
rename to packages/SystemUI/tests/utils/src/com/android/systemui/utils/os/FakeHandler.java
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java
index 676bde7..d33e7b2 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java
@@ -146,11 +146,8 @@
             if (getWindowTokenForUserAndWindowIdLocked(resolvedUserId, windowId) == null) {
                 return;
             }
-            final AccessibilityWindowAttributes currentAttrs = mWindowAttributes.get(windowId);
-            if (currentAttrs == null || !currentAttrs.equals(attributes)) {
-                mWindowAttributes.put(windowId, attributes);
-                shouldComputeWindows = findWindowInfoByIdLocked(windowId) != null;
-            }
+            mWindowAttributes.put(windowId, attributes);
+            shouldComputeWindows = findWindowInfoByIdLocked(windowId) != null;
         }
         if (shouldComputeWindows) {
             mWindowManagerInternal.computeWindowsForAccessibility(displayId);
diff --git a/services/accessibility/java/com/android/server/accessibility/SystemActionPerformer.java b/services/accessibility/java/com/android/server/accessibility/SystemActionPerformer.java
index 803177b..3fa0ab6 100644
--- a/services/accessibility/java/com/android/server/accessibility/SystemActionPerformer.java
+++ b/services/accessibility/java/com/android/server/accessibility/SystemActionPerformer.java
@@ -16,8 +16,6 @@
 
 package com.android.server.accessibility;
 
-import static android.view.WindowManager.ScreenshotSource.SCREENSHOT_ACCESSIBILITY_ACTIONS;
-
 import android.accessibilityservice.AccessibilityService;
 import android.app.PendingIntent;
 import android.app.RemoteAction;
@@ -34,6 +32,7 @@
 import android.view.InputDevice;
 import android.view.KeyCharacterMap;
 import android.view.KeyEvent;
+import android.view.WindowManager;
 import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
 
 import com.android.internal.R;
@@ -392,8 +391,8 @@
     private boolean takeScreenshot() {
         ScreenshotHelper screenshotHelper = (mScreenshotHelperSupplier != null)
                 ? mScreenshotHelperSupplier.get() : new ScreenshotHelper(mContext);
-        screenshotHelper.takeScreenshot(android.view.WindowManager.TAKE_SCREENSHOT_FULLSCREEN,
-                true, true, SCREENSHOT_ACCESSIBILITY_ACTIONS,
+        screenshotHelper.takeScreenshot(WindowManager.TAKE_SCREENSHOT_FULLSCREEN,
+                WindowManager.ScreenshotSource.SCREENSHOT_ACCESSIBILITY_ACTIONS,
                 new Handler(Looper.getMainLooper()), null);
         return true;
     }
diff --git a/services/autofill/java/com/android/server/autofill/ui/AutoFillUI.java b/services/autofill/java/com/android/server/autofill/ui/AutoFillUI.java
index 3ab873d..e07f412 100644
--- a/services/autofill/java/com/android/server/autofill/ui/AutoFillUI.java
+++ b/services/autofill/java/com/android/server/autofill/ui/AutoFillUI.java
@@ -439,6 +439,7 @@
                             log(MetricsEvent.TYPE_DISMISS);
                             hideFillDialogUiThread(callback);
                             callback.requestShowSoftInput(focusedId);
+                            callback.requestFallbackFromFillDialog();
                         }
 
                         @Override
diff --git a/services/core/java/com/android/server/am/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java
index a238705..768fdfd 100644
--- a/services/core/java/com/android/server/am/BatteryStatsService.java
+++ b/services/core/java/com/android/server/am/BatteryStatsService.java
@@ -755,6 +755,7 @@
                 case FrameworkStatsLog.BATTERY_USAGE_STATS_SINCE_RESET:
                     final BatteryUsageStatsQuery querySinceReset =
                             new BatteryUsageStatsQuery.Builder()
+                                    .setMaxStatsAgeMs(0)
                                     .includeProcessStateData()
                                     .includeVirtualUids()
                                     .build();
@@ -763,6 +764,7 @@
                 case FrameworkStatsLog.BATTERY_USAGE_STATS_SINCE_RESET_USING_POWER_PROFILE_MODEL:
                     final BatteryUsageStatsQuery queryPowerProfile =
                             new BatteryUsageStatsQuery.Builder()
+                                    .setMaxStatsAgeMs(0)
                                     .includeProcessStateData()
                                     .includeVirtualUids()
                                     .powerProfileModeledOnly()
@@ -779,6 +781,7 @@
                     final long sessionEnd = mStats.getStartClockTime();
                     final BatteryUsageStatsQuery queryBeforeReset =
                             new BatteryUsageStatsQuery.Builder()
+                                    .setMaxStatsAgeMs(0)
                                     .includeProcessStateData()
                                     .includeVirtualUids()
                                     .aggregateSnapshots(sessionStart, sessionEnd)
diff --git a/services/core/java/com/android/server/am/UidObserverController.java b/services/core/java/com/android/server/am/UidObserverController.java
index e8c1b54..51cb987 100644
--- a/services/core/java/com/android/server/am/UidObserverController.java
+++ b/services/core/java/com/android/server/am/UidObserverController.java
@@ -15,6 +15,7 @@
  */
 package com.android.server.am;
 
+import static android.Manifest.permission.INTERACT_ACROSS_USERS_FULL;
 import static android.app.ActivityManager.PROCESS_STATE_NONEXISTENT;
 
 import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_UID_OBSERVERS;
@@ -25,6 +26,7 @@
 import android.app.ActivityManager;
 import android.app.ActivityManagerProto;
 import android.app.IUidObserver;
+import android.content.pm.PackageManager;
 import android.os.Handler;
 import android.os.RemoteCallbackList;
 import android.os.RemoteException;
@@ -81,7 +83,9 @@
             @NonNull String callingPackage, int callingUid) {
         synchronized (mLock) {
             mUidObservers.register(observer, new UidObserverRegistration(callingUid,
-                    callingPackage, which, cutpoint));
+                    callingPackage, which, cutpoint,
+                    ActivityManager.checkUidPermission(INTERACT_ACROSS_USERS_FULL, callingUid)
+                    == PackageManager.PERMISSION_GRANTED));
         }
     }
 
@@ -252,6 +256,11 @@
                 final ChangeRecord item = mActiveUidChanges[j];
                 final long start = SystemClock.uptimeMillis();
                 final int change = item.change;
+                // Does the user have permission? Don't send a non user UID change otherwise
+                if (UserHandle.getUserId(item.uid) != UserHandle.getUserId(reg.mUid)
+                        && !reg.mCanInteractAcrossUsers) {
+                    continue;
+                }
                 if (change == UidRecord.CHANGE_PROCSTATE
                         && (reg.mWhich & ActivityManager.UID_OBSERVER_PROCSTATE) == 0) {
                     // No-op common case: no significant change, the observer is not
@@ -437,6 +446,7 @@
         private final String mPkg;
         private final int mWhich;
         private final int mCutpoint;
+        private final boolean mCanInteractAcrossUsers;
 
         /**
          * Total # of callback calls that took more than {@link #SLOW_UID_OBSERVER_THRESHOLD_MS}.
@@ -467,11 +477,13 @@
                 ActivityManagerProto.UID_OBSERVER_FLAG_PROC_OOM_ADJ,
         };
 
-        UidObserverRegistration(int uid, @NonNull String pkg, int which, int cutpoint) {
+        UidObserverRegistration(int uid, @NonNull String pkg, int which, int cutpoint,
+                boolean canInteractAcrossUsers) {
             this.mUid = uid;
             this.mPkg = pkg;
             this.mWhich = which;
             this.mCutpoint = cutpoint;
+            this.mCanInteractAcrossUsers = canInteractAcrossUsers;
             mLastProcStates = cutpoint >= ActivityManager.MIN_PROCESS_STATE
                     ? new SparseIntArray() : null;
         }
diff --git a/services/core/java/com/android/server/connectivity/Vpn.java b/services/core/java/com/android/server/connectivity/Vpn.java
index 774fe5b..16a060a 100644
--- a/services/core/java/com/android/server/connectivity/Vpn.java
+++ b/services/core/java/com/android/server/connectivity/Vpn.java
@@ -222,6 +222,11 @@
      */
     private static final int VPN_DEFAULT_SCORE = 101;
 
+    /**
+     * The initial token value of IKE session.
+     */
+    private static final int STARTING_TOKEN = -1;
+
     // TODO: create separate trackers for each unique VPN to support
     // automated reconnection
 
@@ -514,12 +519,8 @@
                 @NonNull NetworkScore score,
                 @NonNull NetworkAgentConfig config,
                 @Nullable NetworkProvider provider) {
-            return new NetworkAgent(context, looper, logTag, nc, lp, score, config, provider) {
-                @Override
-                public void onNetworkUnwanted() {
-                    // We are user controlled, not driven by NetworkRequest.
-                }
-            };
+            return new VpnNetworkAgentWrapper(
+                    context, looper, logTag, nc, lp, score, config, provider);
         }
     }
 
@@ -785,7 +786,7 @@
         }
     }
 
-    private boolean isVpnApp(String packageName) {
+    private static boolean isVpnApp(String packageName) {
         return packageName != null && !VpnConfig.LEGACY_VPN.equals(packageName);
     }
 
@@ -1813,7 +1814,7 @@
                         Log.wtf(TAG, "Failed to add restricted user to owner", e);
                     }
                     if (mNetworkAgent != null) {
-                        mNetworkAgent.sendNetworkCapabilities(mNetworkCapabilities);
+                        doSendNetworkCapabilities(mNetworkAgent, mNetworkCapabilities);
                     }
                 }
                 setVpnForcedLocked(mLockdown);
@@ -1843,7 +1844,7 @@
                         Log.wtf(TAG, "Failed to remove restricted user to owner", e);
                     }
                     if (mNetworkAgent != null) {
-                        mNetworkAgent.sendNetworkCapabilities(mNetworkCapabilities);
+                        doSendNetworkCapabilities(mNetworkAgent, mNetworkCapabilities);
                     }
                 }
                 setVpnForcedLocked(mLockdown);
@@ -2077,7 +2078,7 @@
             return false;
         }
         boolean success = jniAddAddress(mInterface, address, prefixLength);
-        mNetworkAgent.sendLinkProperties(makeLinkProperties());
+        doSendLinkProperties(mNetworkAgent, makeLinkProperties());
         return success;
     }
 
@@ -2086,7 +2087,7 @@
             return false;
         }
         boolean success = jniDelAddress(mInterface, address, prefixLength);
-        mNetworkAgent.sendLinkProperties(makeLinkProperties());
+        doSendLinkProperties(mNetworkAgent, makeLinkProperties());
         return success;
     }
 
@@ -2100,8 +2101,11 @@
         // Make defensive copy since the content of array might be altered by the caller.
         mConfig.underlyingNetworks =
                 (networks != null) ? Arrays.copyOf(networks, networks.length) : null;
-        mNetworkAgent.setUnderlyingNetworks((mConfig.underlyingNetworks != null)
-                ? Arrays.asList(mConfig.underlyingNetworks) : null);
+        doSetUnderlyingNetworks(
+                mNetworkAgent,
+                (mConfig.underlyingNetworks != null)
+                        ? Arrays.asList(mConfig.underlyingNetworks)
+                        : null);
         return true;
     }
 
@@ -2589,7 +2593,7 @@
     }
 
     @Nullable
-    protected synchronized NetworkCapabilities getRedactedNetworkCapabilitiesOfUnderlyingNetwork(
+    private synchronized NetworkCapabilities getRedactedNetworkCapabilities(
             NetworkCapabilities nc) {
         if (nc == null) return null;
         return mConnectivityManager.getRedactedNetworkCapabilitiesForPackage(
@@ -2597,8 +2601,7 @@
     }
 
     @Nullable
-    protected synchronized LinkProperties getRedactedLinkPropertiesOfUnderlyingNetwork(
-            LinkProperties lp) {
+    private synchronized LinkProperties getRedactedLinkProperties(LinkProperties lp) {
         if (lp == null) return null;
         return mConnectivityManager.getRedactedLinkPropertiesForPackage(lp, mOwnerUID, mPackage);
     }
@@ -2712,11 +2715,13 @@
         private boolean mIsRunning = true;
 
         /**
-         * The token used by the primary/current/active IKE session.
+         * The token that identifies the most recently created IKE session.
          *
-         * <p>This token MUST be updated when the VPN switches to use a new IKE session.
+         * <p>This token is monotonically increasing and will never be reset in the lifetime of this
+         * Ikev2VpnRunner, but it does get reset across runs. It also MUST be accessed on the
+         * executor thread and updated when a new IKE session is created.
          */
-        private int mCurrentToken = -1;
+        private int mCurrentToken = STARTING_TOKEN;
 
         @Nullable private IpSecTunnelInterface mTunnelIface;
         @Nullable private Network mActiveNetwork;
@@ -2910,7 +2915,7 @@
                         return; // Link properties are already sent.
                     } else {
                         // Underlying networks also set in agentConnect()
-                        networkAgent.setUnderlyingNetworks(Collections.singletonList(network));
+                        doSetUnderlyingNetworks(networkAgent, Collections.singletonList(network));
                         mNetworkCapabilities =
                                 new NetworkCapabilities.Builder(mNetworkCapabilities)
                                         .setUnderlyingNetworks(Collections.singletonList(network))
@@ -2920,7 +2925,7 @@
                     lp = makeLinkProperties(); // Accesses VPN instance fields; must be locked
                 }
 
-                networkAgent.sendLinkProperties(lp);
+                doSendLinkProperties(networkAgent, lp);
             } catch (Exception e) {
                 Log.d(TAG, "Error in ChildOpened for token " + token, e);
                 onSessionLost(token, e);
@@ -2987,7 +2992,7 @@
                             new NetworkCapabilities.Builder(mNetworkCapabilities)
                                     .setUnderlyingNetworks(Collections.singletonList(network))
                                     .build();
-                    mNetworkAgent.setUnderlyingNetworks(Collections.singletonList(network));
+                    doSetUnderlyingNetworks(mNetworkAgent, Collections.singletonList(network));
                 }
 
                 mTunnelIface.setUnderlyingNetwork(network);
@@ -3208,7 +3213,7 @@
                         mExecutor.schedule(
                                 () -> {
                                     if (isActiveToken(token)) {
-                                        handleSessionLost(null, network);
+                                        handleSessionLost(null /* exception */, network);
                                     } else {
                                         Log.d(
                                                 TAG,
@@ -3225,7 +3230,7 @@
                                 TimeUnit.MILLISECONDS);
             } else {
                 Log.d(TAG, "Call handleSessionLost for losing network " + network);
-                handleSessionLost(null, network);
+                handleSessionLost(null /* exception */, network);
             }
         }
 
@@ -3293,70 +3298,69 @@
             // already terminated due to other failures.
             cancelHandleNetworkLostTimeout();
 
-            synchronized (Vpn.this) {
-                String category = null;
-                int errorClass = -1;
-                int errorCode = -1;
-                if (exception instanceof IkeProtocolException) {
-                    final IkeProtocolException ikeException = (IkeProtocolException) exception;
-                    category = VpnManager.CATEGORY_EVENT_IKE_ERROR;
-                    errorCode = ikeException.getErrorType();
+            String category = null;
+            int errorClass = -1;
+            int errorCode = -1;
+            if (exception instanceof IllegalArgumentException) {
+                // Failed to build IKE/ChildSessionParams; fatal profile configuration error
+                markFailedAndDisconnect(exception);
+                return;
+            }
 
-                    switch (ikeException.getErrorType()) {
-                        case IkeProtocolException.ERROR_TYPE_NO_PROPOSAL_CHOSEN: // Fallthrough
-                        case IkeProtocolException.ERROR_TYPE_INVALID_KE_PAYLOAD: // Fallthrough
-                        case IkeProtocolException.ERROR_TYPE_AUTHENTICATION_FAILED: // Fallthrough
-                        case IkeProtocolException.ERROR_TYPE_SINGLE_PAIR_REQUIRED: // Fallthrough
-                        case IkeProtocolException.ERROR_TYPE_FAILED_CP_REQUIRED: // Fallthrough
-                        case IkeProtocolException.ERROR_TYPE_TS_UNACCEPTABLE:
-                            // All the above failures are configuration errors, and are terminal
-                            errorClass = VpnManager.ERROR_CLASS_NOT_RECOVERABLE;
-                            break;
-                        // All other cases possibly recoverable.
-                        default:
-                            // All the above failures are configuration errors, and are terminal
-                            errorClass = VpnManager.ERROR_CLASS_RECOVERABLE;
-                    }
-                } else if (exception instanceof IllegalArgumentException) {
-                    // Failed to build IKE/ChildSessionParams; fatal profile configuration error
-                    markFailedAndDisconnect(exception);
-                    return;
-                } else if (exception instanceof IkeNetworkLostException) {
-                    category = VpnManager.CATEGORY_EVENT_NETWORK_ERROR;
-                    errorClass = VpnManager.ERROR_CLASS_RECOVERABLE;
-                    errorCode = VpnManager.ERROR_CODE_NETWORK_LOST;
-                } else if (exception instanceof IkeNonProtocolException) {
-                    category = VpnManager.CATEGORY_EVENT_NETWORK_ERROR;
-                    errorClass = VpnManager.ERROR_CLASS_RECOVERABLE;
-                    if (exception.getCause() instanceof UnknownHostException) {
-                        errorCode = VpnManager.ERROR_CODE_NETWORK_UNKNOWN_HOST;
-                    } else if (exception.getCause() instanceof IkeTimeoutException) {
-                        errorCode = VpnManager.ERROR_CODE_NETWORK_PROTOCOL_TIMEOUT;
-                    } else if (exception.getCause() instanceof IOException) {
-                        errorCode = VpnManager.ERROR_CODE_NETWORK_IO;
-                    }
-                } else if (exception != null) {
-                    Log.wtf(TAG, "onSessionLost: exception = " + exception);
+            if (exception instanceof IkeProtocolException) {
+                final IkeProtocolException ikeException = (IkeProtocolException) exception;
+                category = VpnManager.CATEGORY_EVENT_IKE_ERROR;
+                errorCode = ikeException.getErrorType();
+
+                switch (ikeException.getErrorType()) {
+                    case IkeProtocolException.ERROR_TYPE_NO_PROPOSAL_CHOSEN: // Fallthrough
+                    case IkeProtocolException.ERROR_TYPE_INVALID_KE_PAYLOAD: // Fallthrough
+                    case IkeProtocolException.ERROR_TYPE_AUTHENTICATION_FAILED: // Fallthrough
+                    case IkeProtocolException.ERROR_TYPE_SINGLE_PAIR_REQUIRED: // Fallthrough
+                    case IkeProtocolException.ERROR_TYPE_FAILED_CP_REQUIRED: // Fallthrough
+                    case IkeProtocolException.ERROR_TYPE_TS_UNACCEPTABLE:
+                        // All the above failures are configuration errors, and are terminal
+                        errorClass = VpnManager.ERROR_CLASS_NOT_RECOVERABLE;
+                        break;
+                    // All other cases possibly recoverable.
+                    default:
+                        errorClass = VpnManager.ERROR_CLASS_RECOVERABLE;
                 }
+            } else if (exception instanceof IkeNetworkLostException) {
+                category = VpnManager.CATEGORY_EVENT_NETWORK_ERROR;
+                errorClass = VpnManager.ERROR_CLASS_RECOVERABLE;
+                errorCode = VpnManager.ERROR_CODE_NETWORK_LOST;
+            } else if (exception instanceof IkeNonProtocolException) {
+                category = VpnManager.CATEGORY_EVENT_NETWORK_ERROR;
+                errorClass = VpnManager.ERROR_CLASS_RECOVERABLE;
+                if (exception.getCause() instanceof UnknownHostException) {
+                    errorCode = VpnManager.ERROR_CODE_NETWORK_UNKNOWN_HOST;
+                } else if (exception.getCause() instanceof IkeTimeoutException) {
+                    errorCode = VpnManager.ERROR_CODE_NETWORK_PROTOCOL_TIMEOUT;
+                } else if (exception.getCause() instanceof IOException) {
+                    errorCode = VpnManager.ERROR_CODE_NETWORK_IO;
+                }
+            } else if (exception != null) {
+                Log.wtf(TAG, "onSessionLost: exception = " + exception);
+            }
 
+            synchronized (Vpn.this) {
                 // TODO(b/230548427): Remove SDK check once VPN related stuff are
                 //  decoupled from ConnectivityServiceTest.
                 if (SdkLevel.isAtLeastT() && category != null && isVpnApp(mPackage)) {
                     sendEventToVpnManagerApp(category, errorClass, errorCode,
                             getPackage(), mSessionKey, makeVpnProfileStateLocked(),
                             mActiveNetwork,
-                            getRedactedNetworkCapabilitiesOfUnderlyingNetwork(
-                                    mUnderlyingNetworkCapabilities),
-                            getRedactedLinkPropertiesOfUnderlyingNetwork(
-                                    mUnderlyingLinkProperties));
+                            getRedactedNetworkCapabilities(mUnderlyingNetworkCapabilities),
+                            getRedactedLinkProperties(mUnderlyingLinkProperties));
                 }
+            }
 
-                if (errorClass == VpnManager.ERROR_CLASS_NOT_RECOVERABLE) {
-                    markFailedAndDisconnect(exception);
-                    return;
-                } else {
-                    scheduleRetryNewIkeSession();
-                }
+            if (errorClass == VpnManager.ERROR_CLASS_NOT_RECOVERABLE) {
+                markFailedAndDisconnect(exception);
+                return;
+            } else {
+                scheduleRetryNewIkeSession();
             }
 
             mUnderlyingNetworkCapabilities = null;
@@ -3384,7 +3388,7 @@
                                     null /*gateway*/, null /*iface*/, RTN_UNREACHABLE));
                         }
                         if (mNetworkAgent != null) {
-                            mNetworkAgent.sendLinkProperties(makeLinkProperties());
+                            doSendLinkProperties(mNetworkAgent, makeLinkProperties());
                         }
                     }
                 }
@@ -4121,7 +4125,7 @@
                         .setUids(createUserAndRestrictedProfilesRanges(
                                 mUserId, null /* allowedApplications */, excludedApps))
                         .build();
-                mNetworkAgent.sendNetworkCapabilities(mNetworkCapabilities);
+                doSendNetworkCapabilities(mNetworkAgent, mNetworkCapabilities);
             }
         }
     }
@@ -4198,6 +4202,85 @@
         return isCurrentIkev2VpnLocked(packageName) ? makeVpnProfileStateLocked() : null;
     }
 
+    /** Proxy to allow different testing setups */
+    // TODO: b/240492694 Remove VpnNetworkAgentWrapper and this method when
+    // NetworkAgent#sendLinkProperties can be un-finalized.
+    private static void doSendLinkProperties(
+            @NonNull NetworkAgent agent, @NonNull LinkProperties lp) {
+        if (agent instanceof VpnNetworkAgentWrapper) {
+            ((VpnNetworkAgentWrapper) agent).doSendLinkProperties(lp);
+        } else {
+            agent.sendLinkProperties(lp);
+        }
+    }
+
+    /** Proxy to allow different testing setups */
+    // TODO: b/240492694 Remove VpnNetworkAgentWrapper and this method when
+    // NetworkAgent#sendNetworkCapabilities can be un-finalized.
+    private static void doSendNetworkCapabilities(
+            @NonNull NetworkAgent agent, @NonNull NetworkCapabilities nc) {
+        if (agent instanceof VpnNetworkAgentWrapper) {
+            ((VpnNetworkAgentWrapper) agent).doSendNetworkCapabilities(nc);
+        } else {
+            agent.sendNetworkCapabilities(nc);
+        }
+    }
+
+    /** Proxy to allow different testing setups */
+    // TODO: b/240492694 Remove VpnNetworkAgentWrapper and this method when
+    // NetworkAgent#setUnderlyingNetworks can be un-finalized.
+    private static void doSetUnderlyingNetworks(
+            @NonNull NetworkAgent agent, @NonNull List<Network> networks) {
+        if (agent instanceof VpnNetworkAgentWrapper) {
+            ((VpnNetworkAgentWrapper) agent).doSetUnderlyingNetworks(networks);
+        } else {
+            agent.setUnderlyingNetworks(networks);
+        }
+    }
+
+    /**
+     * Proxy to allow testing
+     *
+     * @hide
+     */
+    // TODO: b/240492694 Remove VpnNetworkAgentWrapper when NetworkAgent's methods can be
+    // un-finalized.
+    @VisibleForTesting
+    public static class VpnNetworkAgentWrapper extends NetworkAgent {
+        /** Create an VpnNetworkAgentWrapper */
+        public VpnNetworkAgentWrapper(
+                @NonNull Context context,
+                @NonNull Looper looper,
+                @NonNull String logTag,
+                @NonNull NetworkCapabilities nc,
+                @NonNull LinkProperties lp,
+                @NonNull NetworkScore score,
+                @NonNull NetworkAgentConfig config,
+                @Nullable NetworkProvider provider) {
+            super(context, looper, logTag, nc, lp, score, config, provider);
+        }
+
+        /** Update the LinkProperties */
+        public void doSendLinkProperties(@NonNull LinkProperties lp) {
+            sendLinkProperties(lp);
+        }
+
+        /** Update the NetworkCapabilities */
+        public void doSendNetworkCapabilities(@NonNull NetworkCapabilities nc) {
+            sendNetworkCapabilities(nc);
+        }
+
+        /** Set the underlying networks */
+        public void doSetUnderlyingNetworks(@NonNull List<Network> networks) {
+            setUnderlyingNetworks(networks);
+        }
+
+        @Override
+        public void onNetworkUnwanted() {
+            // We are user controlled, not driven by NetworkRequest.
+        }
+    }
+
     /**
      * Proxy to allow testing
      *
diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java
index 4e33fd0..c01a228 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController.java
@@ -1501,6 +1501,8 @@
         // Animate the screen brightness when the screen is on or dozing.
         // Skip the animation when the screen is off or suspended or transition to/from VR.
         boolean brightnessAdjusted = false;
+        final boolean brightnessIsTemporary =
+                mAppliedTemporaryBrightness || mAppliedTemporaryAutoBrightnessAdjustment;
         if (!mPendingScreenOff) {
             if (mSkipScreenOnBrightnessRamp) {
                 if (state == Display.STATE_ON) {
@@ -1533,8 +1535,6 @@
             // level without it being a noticeable jump since any actual content isn't yet visible.
             final boolean isDisplayContentVisible =
                     mColorFadeEnabled && mPowerState.getColorFadeLevel() == 1.0f;
-            final boolean brightnessIsTemporary =
-                    mAppliedTemporaryBrightness || mAppliedTemporaryAutoBrightnessAdjustment;
             // We only want to animate the brightness if it is between 0.0f and 1.0f.
             // brightnessState can contain the values -1.0f and NaN, which we do not want to
             // animate to. To avoid this, we check the value first.
@@ -1607,7 +1607,8 @@
             brightnessAdjusted = saveBrightnessInfo(getScreenBrightnessSetting());
         }
 
-        if (brightnessAdjusted) {
+        // Only notify if the brightness adjustment is not temporary (i.e. slider has been released)
+        if (brightnessAdjusted && !brightnessIsTemporary) {
             postBrightnessChangeRunnable();
         }
 
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsService.java b/services/core/java/com/android/server/locksettings/LockSettingsService.java
index 804315c..b86fa7a 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsService.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsService.java
@@ -35,8 +35,6 @@
 import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PIN;
 import static com.android.internal.widget.LockPatternUtils.CURRENT_LSKF_BASED_PROTECTOR_ID_KEY;
 import static com.android.internal.widget.LockPatternUtils.EscrowTokenStateChangeCallback;
-import static com.android.internal.widget.LockPatternUtils.PROFILE_KEY_NAME_DECRYPT;
-import static com.android.internal.widget.LockPatternUtils.PROFILE_KEY_NAME_ENCRYPT;
 import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_LOCKOUT;
 import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_FOR_UNATTENDED_UPDATE;
 import static com.android.internal.widget.LockPatternUtils.USER_FRP;
@@ -122,6 +120,7 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
 import com.android.internal.notification.SystemNotificationChannels;
+import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.DumpUtils;
 import com.android.internal.util.IndentingPrintWriter;
 import com.android.internal.util.Preconditions;
@@ -147,7 +146,6 @@
 
 import libcore.util.HexEncoding;
 
-import java.io.ByteArrayOutputStream;
 import java.io.FileDescriptor;
 import java.io.FileNotFoundException;
 import java.io.IOException;
@@ -211,6 +209,9 @@
     // user's credential must be presented again, e.g. via ConfirmLockPattern/ConfirmLockPassword.
     private static final int GK_PW_HANDLE_STORE_DURATION_MS = 10 * 60 * 1000; // 10 minutes
 
+    private static final String PROFILE_KEY_NAME_ENCRYPT = "profile_key_name_encrypt_";
+    private static final String PROFILE_KEY_NAME_DECRYPT = "profile_key_name_decrypt_";
+
     // Order of holding lock: mSeparateChallengeLock -> mSpManager -> this
     // Do not call into ActivityManager while holding mSpManager lock.
     private final Object mSeparateChallengeLock = new Object();
@@ -234,7 +235,8 @@
     protected final UserManager mUserManager;
     private final IStorageManager mStorageManager;
     private final IActivityManager mActivityManager;
-    private final SyntheticPasswordManager mSpManager;
+    @VisibleForTesting
+    protected final SyntheticPasswordManager mSpManager;
 
     private final KeyStore mKeyStore;
     private final java.security.KeyStore mJavaKeyStore;
@@ -1625,7 +1627,7 @@
             }
 
             onSyntheticPasswordKnown(userId, sp);
-            setLockCredentialWithSpLocked(credential, sp, userId);
+            setLockCredentialWithSpLocked(credential, sp, userId, isLockTiedToParent);
             sendCredentialsOnChangeIfRequired(credential, userId, isLockTiedToParent);
             return true;
         }
@@ -1639,15 +1641,15 @@
         if (newCredential.isPattern()) {
             setBoolean(LockPatternUtils.PATTERN_EVER_CHOSEN_KEY, true, userHandle);
         }
-        updatePasswordHistory(newCredential, userHandle);
         mContext.getSystemService(TrustManager.class).reportEnabledTrustAgentsChanged(userHandle);
     }
 
     /**
-     * Store the hash of the *current* password in the password history list, if device policy
-     * enforces password history requirement.
+     * Store the hash of the new password in the password history list, if device policy enforces
+     * a password history requirement.
      */
-    private void updatePasswordHistory(LockscreenCredential password, int userHandle) {
+    private void updatePasswordHistory(SyntheticPassword sp, LockscreenCredential password,
+            int userHandle, boolean isLockTiedToParent) {
         if (password.isNone()) {
             return;
         }
@@ -1655,8 +1657,11 @@
             // Do not keep track of historical patterns
             return;
         }
-        // Add the password to the password history. We assume all
-        // password hashes have the same length for simplicity of implementation.
+        if (isLockTiedToParent) {
+            // Do not keep track of historical auto-generated profile passwords
+            return;
+        }
+        // Add the password to the password history.
         String passwordHistory = getString(
                 LockPatternUtils.PASSWORD_HISTORY_KEY, /* defaultValue= */ null, userHandle);
         if (passwordHistory == null) {
@@ -1666,13 +1671,9 @@
         if (passwordHistoryLength == 0) {
             passwordHistory = "";
         } else {
-            final byte[] hashFactor = getHashFactor(password, userHandle);
+            final byte[] hashFactor = sp.derivePasswordHashFactor();
             final byte[] salt = getSalt(userHandle).getBytes();
             String hash = password.passwordToHistoryHash(salt, hashFactor);
-            if (hash == null) {
-                Slog.e(TAG, "Compute new style password hash failed, fallback to legacy style");
-                hash = password.legacyPasswordToHash(salt);
-            }
             if (TextUtils.isEmpty(passwordHistory)) {
                 passwordHistory = hash;
             } else {
@@ -1847,8 +1848,8 @@
     @VisibleForTesting /** Note: this method is overridden in unit tests */
     protected void tieProfileLockToParent(int userId, LockscreenCredential password) {
         if (DEBUG) Slog.v(TAG, "tieProfileLockToParent for user: " + userId);
-        byte[] encryptionResult;
-        byte[] iv;
+        final byte[] iv;
+        final byte[] ciphertext;
         try {
             KeyGenerator keyGenerator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES);
             keyGenerator.init(new SecureRandom());
@@ -1877,7 +1878,7 @@
                         KeyProperties.KEY_ALGORITHM_AES + "/" + KeyProperties.BLOCK_MODE_GCM + "/"
                                 + KeyProperties.ENCRYPTION_PADDING_NONE);
                 cipher.init(Cipher.ENCRYPT_MODE, keyStoreEncryptionKey);
-                encryptionResult = cipher.doFinal(password.getCredential());
+                ciphertext = cipher.doFinal(password.getCredential());
                 iv = cipher.getIV();
             } finally {
                 // The original key can now be discarded.
@@ -1888,17 +1889,10 @@
                 | NoSuchPaddingException | NoSuchAlgorithmException | InvalidKeyException e) {
             throw new IllegalStateException("Failed to encrypt key", e);
         }
-        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
-        try {
-            if (iv.length != PROFILE_KEY_IV_SIZE) {
-                throw new IllegalArgumentException("Invalid iv length: " + iv.length);
-            }
-            outputStream.write(iv);
-            outputStream.write(encryptionResult);
-        } catch (IOException e) {
-            throw new IllegalStateException("Failed to concatenate byte arrays", e);
+        if (iv.length != PROFILE_KEY_IV_SIZE) {
+            throw new IllegalArgumentException("Invalid iv length: " + iv.length);
         }
-        mStorage.writeChildProfileLock(userId, outputStream.toByteArray());
+        mStorage.writeChildProfileLock(userId, ArrayUtils.concat(iv, ciphertext));
     }
 
     private void setUserKeyProtection(int userId, byte[] key) {
@@ -2650,7 +2644,7 @@
      */
     @GuardedBy("mSpManager")
     private long setLockCredentialWithSpLocked(LockscreenCredential credential,
-            SyntheticPassword sp, int userId) {
+            SyntheticPassword sp, int userId, boolean isLockTiedToParent) {
         if (DEBUG) Slog.d(TAG, "setLockCredentialWithSpLocked: user=" + userId);
         final int savedCredentialType = getCredentialTypeInternal(userId);
         final long oldProtectorId = getCurrentLskfBasedProtectorId(userId);
@@ -2688,6 +2682,7 @@
         LockPatternUtils.invalidateCredentialTypeCache();
         synchronizeUnifiedWorkChallengeForProfiles(userId, profilePasswords);
 
+        updatePasswordHistory(sp, credential, userId, isLockTiedToParent);
         setUserPasswordMetrics(credential, userId);
         mManagedProfilePasswordCache.removePassword(userId);
         if (savedCredentialType != CREDENTIAL_TYPE_NONE) {
@@ -2933,7 +2928,8 @@
             return false;
         }
         onSyntheticPasswordKnown(userId, result.syntheticPassword);
-        setLockCredentialWithSpLocked(credential, result.syntheticPassword, userId);
+        setLockCredentialWithSpLocked(credential, result.syntheticPassword, userId,
+                /* isLockTiedToParent= */ false);
         return true;
     }
 
diff --git a/services/core/java/com/android/server/locksettings/ManagedProfilePasswordCache.java b/services/core/java/com/android/server/locksettings/ManagedProfilePasswordCache.java
index 672c3f7..e43d4e8 100644
--- a/services/core/java/com/android/server/locksettings/ManagedProfilePasswordCache.java
+++ b/services/core/java/com/android/server/locksettings/ManagedProfilePasswordCache.java
@@ -26,6 +26,7 @@
 import android.util.Slog;
 import android.util.SparseArray;
 
+import com.android.internal.util.ArrayUtils;
 import com.android.internal.widget.LockscreenCredential;
 
 import java.security.GeneralSecurityException;
@@ -117,8 +118,7 @@
                 cipher.init(Cipher.ENCRYPT_MODE, key);
                 byte[] ciphertext = cipher.doFinal(password.getCredential());
                 byte[] iv = cipher.getIV();
-                byte[] block = Arrays.copyOf(iv, ciphertext.length + iv.length);
-                System.arraycopy(ciphertext, 0, block, iv.length, ciphertext.length);
+                byte[] block = ArrayUtils.concat(iv, ciphertext);
                 mEncryptedPasswords.put(userId, block);
             } catch (GeneralSecurityException e) {
                 Slog.d(TAG, "Cannot encrypt", e);
diff --git a/services/core/java/com/android/server/locksettings/SyntheticPasswordCrypto.java b/services/core/java/com/android/server/locksettings/SyntheticPasswordCrypto.java
index b06af8e..a931844 100644
--- a/services/core/java/com/android/server/locksettings/SyntheticPasswordCrypto.java
+++ b/services/core/java/com/android/server/locksettings/SyntheticPasswordCrypto.java
@@ -25,7 +25,8 @@
 import android.system.keystore2.KeyDescriptor;
 import android.util.Slog;
 
-import java.io.ByteArrayOutputStream;
+import com.android.internal.util.ArrayUtils;
+
 import java.io.IOException;
 import java.security.InvalidAlgorithmParameterException;
 import java.security.InvalidKeyException;
@@ -50,9 +51,9 @@
 
 public class SyntheticPasswordCrypto {
     private static final String TAG = "SyntheticPasswordCrypto";
-    private static final int PROFILE_KEY_IV_SIZE = 12;
-    private static final int DEFAULT_TAG_LENGTH_BITS = 128;
-    private static final int AES_KEY_LENGTH = 32; // 256-bit AES key
+    private static final int AES_GCM_KEY_SIZE = 32; // AES-256-GCM
+    private static final int AES_GCM_IV_SIZE = 12;
+    private static final int AES_GCM_TAG_SIZE = 16;
     private static final byte[] PROTECTOR_SECRET_PERSONALIZATION = "application-id".getBytes();
     // Time between the user credential is verified with GK and the decryption of synthetic password
     // under the auth-bound key. This should always happen one after the other, but give it 15
@@ -65,11 +66,11 @@
         if (blob == null) {
             return null;
         }
-        byte[] iv = Arrays.copyOfRange(blob, 0, PROFILE_KEY_IV_SIZE);
-        byte[] ciphertext = Arrays.copyOfRange(blob, PROFILE_KEY_IV_SIZE, blob.length);
+        byte[] iv = Arrays.copyOfRange(blob, 0, AES_GCM_IV_SIZE);
+        byte[] ciphertext = Arrays.copyOfRange(blob, AES_GCM_IV_SIZE, blob.length);
         Cipher cipher = Cipher.getInstance(KeyProperties.KEY_ALGORITHM_AES + "/"
                 + KeyProperties.BLOCK_MODE_GCM + "/" + KeyProperties.ENCRYPTION_PADDING_NONE);
-        cipher.init(Cipher.DECRYPT_MODE, key, new GCMParameterSpec(DEFAULT_TAG_LENGTH_BITS, iv));
+        cipher.init(Cipher.DECRYPT_MODE, key, new GCMParameterSpec(AES_GCM_TAG_SIZE * 8, iv));
         return cipher.doFinal(ciphertext);
     }
 
@@ -86,23 +87,20 @@
         cipher.init(Cipher.ENCRYPT_MODE, key);
         byte[] ciphertext = cipher.doFinal(blob);
         byte[] iv = cipher.getIV();
-        if (iv.length != PROFILE_KEY_IV_SIZE) {
-            throw new IllegalArgumentException("Invalid iv length: " + iv.length);
+        if (iv.length != AES_GCM_IV_SIZE) {
+            throw new IllegalArgumentException("Invalid iv length: " + iv.length + " bytes");
         }
         final GCMParameterSpec spec = cipher.getParameters().getParameterSpec(
                 GCMParameterSpec.class);
-        if (spec.getTLen() != DEFAULT_TAG_LENGTH_BITS) {
-            throw new IllegalArgumentException("Invalid tag length: " + spec.getTLen());
+        if (spec.getTLen() != AES_GCM_TAG_SIZE * 8) {
+            throw new IllegalArgumentException("Invalid tag length: " + spec.getTLen() + " bits");
         }
-        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
-        outputStream.write(iv);
-        outputStream.write(ciphertext);
-        return outputStream.toByteArray();
+        return ArrayUtils.concat(iv, ciphertext);
     }
 
     public static byte[] encrypt(byte[] keyBytes, byte[] personalization, byte[] message) {
         byte[] keyHash = personalizedHash(personalization, keyBytes);
-        SecretKeySpec key = new SecretKeySpec(Arrays.copyOf(keyHash, AES_KEY_LENGTH),
+        SecretKeySpec key = new SecretKeySpec(Arrays.copyOf(keyHash, AES_GCM_KEY_SIZE),
                 KeyProperties.KEY_ALGORITHM_AES);
         try {
             return encrypt(key, message);
@@ -116,7 +114,7 @@
 
     public static byte[] decrypt(byte[] keyBytes, byte[] personalization, byte[] ciphertext) {
         byte[] keyHash = personalizedHash(personalization, keyBytes);
-        SecretKeySpec key = new SecretKeySpec(Arrays.copyOf(keyHash, AES_KEY_LENGTH),
+        SecretKeySpec key = new SecretKeySpec(Arrays.copyOf(keyHash, AES_GCM_KEY_SIZE),
                 KeyProperties.KEY_ALGORITHM_AES);
         try {
             return decrypt(key, ciphertext);
@@ -129,18 +127,20 @@
     }
 
     /**
-     * Decrypt a legacy SP blob which did the Keystore and software encryption layers in the wrong
+     * Decrypts a legacy SP blob which did the Keystore and software encryption layers in the wrong
      * order.
      */
-    public static byte[] decryptBlobV1(String keyAlias, byte[] blob, byte[] protectorSecret) {
+    public static byte[] decryptBlobV1(String protectorKeyAlias, byte[] blob,
+            byte[] protectorSecret) {
         try {
             KeyStore keyStore = getKeyStore();
-            SecretKey keyStoreKey = (SecretKey) keyStore.getKey(keyAlias, null);
-            if (keyStoreKey == null) {
-                throw new IllegalStateException("SP key is missing: " + keyAlias);
+            SecretKey protectorKey = (SecretKey) keyStore.getKey(protectorKeyAlias, null);
+            if (protectorKey == null) {
+                throw new IllegalStateException("SP protector key is missing: "
+                        + protectorKeyAlias);
             }
             byte[] intermediate = decrypt(protectorSecret, PROTECTOR_SECRET_PERSONALIZATION, blob);
-            return decrypt(keyStoreKey, intermediate);
+            return decrypt(protectorKey, intermediate);
         } catch (Exception e) {
             Slog.e(TAG, "Failed to decrypt V1 blob", e);
             throw new IllegalStateException("Failed to decrypt blob", e);
@@ -165,15 +165,17 @@
     /**
      * Decrypts an SP blob that was created by {@link #createBlob}.
      */
-    public static byte[] decryptBlob(String keyAlias, byte[] blob, byte[] protectorSecret) {
+    public static byte[] decryptBlob(String protectorKeyAlias, byte[] blob,
+            byte[] protectorSecret) {
         try {
             final KeyStore keyStore = getKeyStore();
 
-            SecretKey keyStoreKey = (SecretKey) keyStore.getKey(keyAlias, null);
-            if (keyStoreKey == null) {
-                throw new IllegalStateException("SP key is missing: " + keyAlias);
+            SecretKey protectorKey = (SecretKey) keyStore.getKey(protectorKeyAlias, null);
+            if (protectorKey == null) {
+                throw new IllegalStateException("SP protector key is missing: "
+                        + protectorKeyAlias);
             }
-            byte[] intermediate = decrypt(keyStoreKey, blob);
+            byte[] intermediate = decrypt(protectorKey, blob);
             return decrypt(protectorSecret, PROTECTOR_SECRET_PERSONALIZATION, intermediate);
         } catch (CertificateException | IOException | BadPaddingException
                 | IllegalBlockSizeException
@@ -187,20 +189,21 @@
 
     /**
      * Creates a new SP blob by encrypting the given data.  Two encryption layers are applied: an
-     * inner layer using a hash of protectorSecret as the key, and an outer layer using a new
-     * Keystore key with the given alias and optionally bound to a SID.
+     * inner layer using a hash of protectorSecret as the key, and an outer layer using the
+     * protector key, which is a Keystore key that is optionally bound to a SID.  This method
+     * creates the protector key and stores it under protectorKeyAlias.
      *
      * The reason we use a layer of software encryption, instead of using protectorSecret as the
      * applicationId of the Keystore key, is to work around buggy KeyMint implementations that don't
      * cryptographically bind the applicationId to the key.  The Keystore layer has to be the outer
      * layer, so that LSKF verification is ratelimited by Gatekeeper when Weaver is unavailable.
      */
-    public static byte[] createBlob(String keyAlias, byte[] data, byte[] protectorSecret,
+    public static byte[] createBlob(String protectorKeyAlias, byte[] data, byte[] protectorSecret,
             long sid) {
         try {
             KeyGenerator keyGenerator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES);
-            keyGenerator.init(AES_KEY_LENGTH * 8, new SecureRandom());
-            SecretKey keyStoreKey = keyGenerator.generateKey();
+            keyGenerator.init(AES_GCM_KEY_SIZE * 8, new SecureRandom());
+            SecretKey protectorKey = keyGenerator.generateKey();
             final KeyStore keyStore = getKeyStore();
             KeyProtection.Builder builder = new KeyProtection.Builder(KeyProperties.PURPOSE_DECRYPT)
                     .setBlockModes(KeyProperties.BLOCK_MODE_GCM)
@@ -214,9 +217,9 @@
             final KeyProtection protNonRollbackResistant = builder.build();
             builder.setRollbackResistant(true);
             final KeyProtection protRollbackResistant = builder.build();
-            final KeyStore.SecretKeyEntry entry = new KeyStore.SecretKeyEntry(keyStoreKey);
+            final KeyStore.SecretKeyEntry entry = new KeyStore.SecretKeyEntry(protectorKey);
             try {
-                keyStore.setEntry(keyAlias, entry, protRollbackResistant);
+                keyStore.setEntry(protectorKeyAlias, entry, protRollbackResistant);
                 Slog.i(TAG, "Using rollback-resistant key");
             } catch (KeyStoreException e) {
                 if (!(e.getCause() instanceof android.security.KeyStoreException)) {
@@ -228,11 +231,11 @@
                 }
                 Slog.w(TAG, "Rollback-resistant keys unavailable.  Falling back to "
                         + "non-rollback-resistant key");
-                keyStore.setEntry(keyAlias, entry, protNonRollbackResistant);
+                keyStore.setEntry(protectorKeyAlias, entry, protNonRollbackResistant);
             }
 
             byte[] intermediate = encrypt(protectorSecret, PROTECTOR_SECRET_PERSONALIZATION, data);
-            return encrypt(keyStoreKey, intermediate);
+            return encrypt(protectorKey, intermediate);
         } catch (CertificateException | IOException | BadPaddingException
                 | IllegalBlockSizeException
                 | KeyStoreException | NoSuchPaddingException | NoSuchAlgorithmException
@@ -243,15 +246,15 @@
         }
     }
 
-    public static void destroyBlobKey(String keyAlias) {
+    public static void destroyProtectorKey(String keyAlias) {
         KeyStore keyStore;
         try {
             keyStore = getKeyStore();
             keyStore.deleteEntry(keyAlias);
-            Slog.i(TAG, "SP key deleted: " + keyAlias);
+            Slog.i(TAG, "Deleted SP protector key " + keyAlias);
         } catch (KeyStoreException | NoSuchAlgorithmException | CertificateException
                 | IOException e) {
-            Slog.e(TAG, "Failed to destroy blob", e);
+            Slog.e(TAG, "Failed to delete SP protector key " + keyAlias, e);
         }
     }
 
diff --git a/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java b/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java
index 7ace03d..d9cf353 100644
--- a/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java
+++ b/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java
@@ -135,8 +135,10 @@
     private static final byte PROTECTOR_TYPE_STRONG_TOKEN_BASED = 1;
     private static final byte PROTECTOR_TYPE_WEAK_TOKEN_BASED = 2;
 
-    // 256-bit synthetic password
-    private static final byte SYNTHETIC_PASSWORD_LENGTH = 256 / 8;
+    private static final String PROTECTOR_KEY_ALIAS_PREFIX = "synthetic_password_";
+
+    // The security strength of the synthetic password, in bytes
+    private static final int SYNTHETIC_PASSWORD_SECURITY_STRENGTH = 256 / 8;
 
     private static final int PASSWORD_SCRYPT_LOG_N = 11;
     private static final int PASSWORD_SCRYPT_LOG_R = 3;
@@ -275,8 +277,8 @@
          */
         static SyntheticPassword create() {
             SyntheticPassword result = new SyntheticPassword(SYNTHETIC_PASSWORD_VERSION_V3);
-            byte[] escrowSplit0 = secureRandom(SYNTHETIC_PASSWORD_LENGTH);
-            byte[] escrowSplit1 = secureRandom(SYNTHETIC_PASSWORD_LENGTH);
+            byte[] escrowSplit0 = secureRandom(SYNTHETIC_PASSWORD_SECURITY_STRENGTH);
+            byte[] escrowSplit1 = secureRandom(SYNTHETIC_PASSWORD_SECURITY_STRENGTH);
             result.recreate(escrowSplit0, escrowSplit1);
             byte[] encrypteEscrowSplit0 = SyntheticPasswordCrypto.encrypt(result.mSyntheticPassword,
                     PERSONALIZATION_E0, escrowSplit0);
@@ -583,7 +585,7 @@
         for (long protectorId : mStorage.listSyntheticPasswordProtectorsForUser(SP_BLOB_NAME,
                     userId)) {
             destroyWeaverSlot(protectorId, userId);
-            destroySPBlobKey(getKeyName(protectorId));
+            destroyProtectorKey(getProtectorKeyAlias(protectorId));
         }
         // Remove potential persistent state (in RPMB), to prevent them from accumulating and
         // causing problems.
@@ -999,7 +1001,8 @@
         } else {
             spSecret = sp.getSyntheticPassword();
         }
-        byte[] content = createSPBlob(getKeyName(protectorId), spSecret, protectorSecret, sid);
+        byte[] content = createSpBlob(getProtectorKeyAlias(protectorId), spSecret, protectorSecret,
+                sid);
         /*
          * We can upgrade from v1 to v2 because that's just a change in the way that
          * the SP is stored. However, we can't upgrade to v3 because that is a change
@@ -1210,10 +1213,11 @@
         }
         final byte[] spSecret;
         if (blob.mVersion == SYNTHETIC_PASSWORD_VERSION_V1) {
-            spSecret = SyntheticPasswordCrypto.decryptBlobV1(getKeyName(protectorId), blob.mContent,
-                    protectorSecret);
+            spSecret = SyntheticPasswordCrypto.decryptBlobV1(getProtectorKeyAlias(protectorId),
+                    blob.mContent, protectorSecret);
         } else {
-            spSecret = decryptSPBlob(getKeyName(protectorId), blob.mContent, protectorSecret);
+            spSecret = decryptSpBlob(getProtectorKeyAlias(protectorId), blob.mContent,
+                    protectorSecret);
         }
         if (spSecret == null) {
             Slog.e(TAG, "Fail to decrypt SP for user " + userId);
@@ -1337,7 +1341,7 @@
 
     private void destroyProtectorCommon(long protectorId, int userId) {
         destroyState(SP_BLOB_NAME, protectorId, userId);
-        destroySPBlobKey(getKeyName(protectorId));
+        destroyProtectorKey(getProtectorKeyAlias(protectorId));
         destroyState(SECDISCARDABLE_NAME, protectorId, userId);
         if (hasState(WEAVER_SLOT_NAME, protectorId, userId)) {
             destroyWeaverSlot(protectorId, userId);
@@ -1347,19 +1351,13 @@
     private byte[] transformUnderWeaverSecret(byte[] data, byte[] secret) {
         byte[] weaverSecret = SyntheticPasswordCrypto.personalizedHash(
                 PERSONALIZATION_WEAVER_PASSWORD, secret);
-        byte[] result = new byte[data.length + weaverSecret.length];
-        System.arraycopy(data, 0, result, 0, data.length);
-        System.arraycopy(weaverSecret, 0, result, data.length, weaverSecret.length);
-        return result;
+        return ArrayUtils.concat(data, weaverSecret);
     }
 
     private byte[] transformUnderSecdiscardable(byte[] data, byte[] rawSecdiscardable) {
         byte[] secdiscardable = SyntheticPasswordCrypto.personalizedHash(
                 PERSONALIZATION_SECDISCARDABLE, rawSecdiscardable);
-        byte[] result = new byte[data.length + secdiscardable.length];
-        System.arraycopy(data, 0, result, 0, data.length);
-        System.arraycopy(secdiscardable, 0, result, data.length, secdiscardable.length);
-        return result;
+        return ArrayUtils.concat(data, secdiscardable);
     }
 
     private byte[] createSecdiscardable(long protectorId, int userId) {
@@ -1428,17 +1426,20 @@
         mStorage.deleteSyntheticPasswordState(userId, protectorId, stateName);
     }
 
-    protected byte[] decryptSPBlob(String blobKeyName, byte[] blob, byte[] protectorSecret) {
-        return SyntheticPasswordCrypto.decryptBlob(blobKeyName, blob, protectorSecret);
+    @VisibleForTesting
+    protected byte[] decryptSpBlob(String protectorKeyAlias, byte[] blob, byte[] protectorSecret) {
+        return SyntheticPasswordCrypto.decryptBlob(protectorKeyAlias, blob, protectorSecret);
     }
 
-    protected byte[] createSPBlob(String blobKeyName, byte[] data, byte[] protectorSecret,
+    @VisibleForTesting
+    protected byte[] createSpBlob(String protectorKeyAlias, byte[] data, byte[] protectorSecret,
             long sid) {
-        return SyntheticPasswordCrypto.createBlob(blobKeyName, data, protectorSecret, sid);
+        return SyntheticPasswordCrypto.createBlob(protectorKeyAlias, data, protectorSecret, sid);
     }
 
-    protected void destroySPBlobKey(String keyAlias) {
-        SyntheticPasswordCrypto.destroyBlobKey(keyAlias);
+    @VisibleForTesting
+    protected void destroyProtectorKey(String keyAlias) {
+        SyntheticPasswordCrypto.destroyProtectorKey(keyAlias);
     }
 
     public static long generateProtectorId() {
@@ -1463,8 +1464,8 @@
         }
     }
 
-    private String getKeyName(long protectorId) {
-        return String.format("%s%x", LockPatternUtils.SYNTHETIC_PASSWORD_KEY_PREFIX, protectorId);
+    private String getProtectorKeyAlias(long protectorId) {
+        return String.format("%s%x", PROTECTOR_KEY_ALIAS_PREFIX, protectorId);
     }
 
     private byte[] computePasswordToken(LockscreenCredential credential, PasswordData data) {
@@ -1517,7 +1518,7 @@
     }
 
     /**
-     * Migrate all existing SP keystore keys from uid 1000 app domain to LSS selinux domain
+     * Migrates all existing SP protector keys from uid 1000 app domain to LSS selinux domain.
      */
     public boolean migrateKeyNamespace() {
         boolean success = true;
@@ -1525,7 +1526,8 @@
             mStorage.listSyntheticPasswordProtectorsForAllUsers(SP_BLOB_NAME);
         for (List<Long> userProtectors : allProtectors.values()) {
             for (long protectorId : userProtectors) {
-                success &= SyntheticPasswordCrypto.migrateLockSettingsKey(getKeyName(protectorId));
+                success &= SyntheticPasswordCrypto.migrateLockSettingsKey(
+                        getProtectorKeyAlias(protectorId));
             }
         }
         return success;
diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/KeySyncUtils.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/KeySyncUtils.java
index 24d575e..7921619 100644
--- a/services/core/java/com/android/server/locksettings/recoverablekeystore/KeySyncUtils.java
+++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/KeySyncUtils.java
@@ -20,6 +20,7 @@
 import android.util.Pair;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.ArrayUtils;
 
 import java.nio.ByteBuffer;
 import java.nio.ByteOrder;
@@ -88,7 +89,7 @@
     ) throws NoSuchAlgorithmException, InvalidKeyException {
         byte[] encryptedRecoveryKey = locallyEncryptRecoveryKey(lockScreenHash, recoveryKey);
         byte[] thmKfHash = calculateThmKfHash(lockScreenHash);
-        byte[] header = concat(THM_ENCRYPTED_RECOVERY_KEY_HEADER, vaultParams);
+        byte[] header = ArrayUtils.concat(THM_ENCRYPTED_RECOVERY_KEY_HEADER, vaultParams);
         return SecureBox.encrypt(
                 /*theirPublicKey=*/ publicKey,
                 /*sharedSecret=*/ thmKfHash,
@@ -171,7 +172,7 @@
                 // Note that Android P devices do not have the API to provide the optional metadata,
                 // so all the keys with non-empty metadata stored on Android Q+ devices cannot be
                 // recovered on Android P devices.
-                header = concat(ENCRYPTED_APPLICATION_KEY_HEADER, metadata);
+                header = ArrayUtils.concat(ENCRYPTED_APPLICATION_KEY_HEADER, metadata);
             }
             byte[] encryptedKey = SecureBox.encrypt(
                     /*theirPublicKey=*/ null,
@@ -218,8 +219,8 @@
         return SecureBox.encrypt(
                 publicKey,
                 /*sharedSecret=*/ null,
-                /*header=*/ concat(RECOVERY_CLAIM_HEADER, vaultParams, challenge),
-                /*payload=*/ concat(thmKfHash, keyClaimant));
+                /*header=*/ ArrayUtils.concat(RECOVERY_CLAIM_HEADER, vaultParams, challenge),
+                /*payload=*/ ArrayUtils.concat(thmKfHash, keyClaimant));
     }
 
     /**
@@ -240,7 +241,7 @@
         return SecureBox.decrypt(
                 /*ourPrivateKey=*/ null,
                 /*sharedSecret=*/ keyClaimant,
-                /*header=*/ concat(RECOVERY_RESPONSE_HEADER, vaultParams),
+                /*header=*/ ArrayUtils.concat(RECOVERY_RESPONSE_HEADER, vaultParams),
                 /*encryptedPayload=*/ encryptedResponse);
     }
 
@@ -280,7 +281,7 @@
         if (applicationKeyMetadata == null) {
             header = ENCRYPTED_APPLICATION_KEY_HEADER;
         } else {
-            header = concat(ENCRYPTED_APPLICATION_KEY_HEADER, applicationKeyMetadata);
+            header = ArrayUtils.concat(ENCRYPTED_APPLICATION_KEY_HEADER, applicationKeyMetadata);
         }
         return SecureBox.decrypt(
                 /*ourPrivateKey=*/ null,
@@ -333,26 +334,6 @@
                 .array();
     }
 
-    /**
-     * Returns the concatenation of all the given {@code arrays}.
-     */
-    @VisibleForTesting
-    static byte[] concat(byte[]... arrays) {
-        int length = 0;
-        for (byte[] array : arrays) {
-            length += array.length;
-        }
-
-        byte[] concatenated = new byte[length];
-        int pos = 0;
-        for (byte[] array : arrays) {
-            System.arraycopy(array, /*srcPos=*/ 0, concatenated, pos, array.length);
-            pos += array.length;
-        }
-
-        return concatenated;
-    }
-
     // Statics only
     private KeySyncUtils() {}
 }
diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/SecureBox.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/SecureBox.java
index 807ee03..51a37b3 100644
--- a/services/core/java/com/android/server/locksettings/recoverablekeystore/SecureBox.java
+++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/SecureBox.java
@@ -18,6 +18,7 @@
 
 import android.annotation.Nullable;
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.ArrayUtils;
 import java.math.BigInteger;
 import java.nio.BufferUnderflowException;
 import java.nio.ByteBuffer;
@@ -69,7 +70,7 @@
 
     private static final byte[] VERSION = new byte[] {(byte) 0x02, 0}; // LITTLE_ENDIAN_TWO_BYTES(2)
     private static final byte[] HKDF_SALT =
-            concat("SECUREBOX".getBytes(StandardCharsets.UTF_8), VERSION);
+            ArrayUtils.concat("SECUREBOX".getBytes(StandardCharsets.UTF_8), VERSION);
     private static final byte[] HKDF_INFO_WITH_PUBLIC_KEY =
             "P256 HKDF-SHA-256 AES-128-GCM".getBytes(StandardCharsets.UTF_8);
     private static final byte[] HKDF_INFO_WITHOUT_PUBLIC_KEY =
@@ -199,13 +200,13 @@
         }
 
         byte[] randNonce = genRandomNonce();
-        byte[] keyingMaterial = concat(dhSecret, sharedSecret);
+        byte[] keyingMaterial = ArrayUtils.concat(dhSecret, sharedSecret);
         SecretKey encryptionKey = hkdfDeriveKey(keyingMaterial, HKDF_SALT, hkdfInfo);
         byte[] ciphertext = aesGcmEncrypt(encryptionKey, randNonce, payload, header);
         if (senderKeyPair == null) {
-            return concat(VERSION, randNonce, ciphertext);
+            return ArrayUtils.concat(VERSION, randNonce, ciphertext);
         } else {
-            return concat(
+            return ArrayUtils.concat(
                     VERSION, encodePublicKey(senderKeyPair.getPublic()), randNonce, ciphertext);
         }
     }
@@ -268,7 +269,7 @@
 
         byte[] randNonce = readEncryptedPayload(ciphertextBuffer, GCM_NONCE_LEN_BYTES);
         byte[] ciphertext = readEncryptedPayload(ciphertextBuffer, ciphertextBuffer.remaining());
-        byte[] keyingMaterial = concat(dhSecret, sharedSecret);
+        byte[] keyingMaterial = ArrayUtils.concat(dhSecret, sharedSecret);
         SecretKey decryptionKey = hkdfDeriveKey(keyingMaterial, HKDF_SALT, hkdfInfo);
         return aesGcmDecrypt(decryptionKey, randNonce, ciphertext, header);
     }
@@ -446,25 +447,6 @@
         return nonce;
     }
 
-    @VisibleForTesting
-    static byte[] concat(byte[]... inputs) {
-        int length = 0;
-        for (int i = 0; i < inputs.length; i++) {
-            if (inputs[i] == null) {
-                inputs[i] = EMPTY_BYTE_ARRAY;
-            }
-            length += inputs[i].length;
-        }
-
-        byte[] output = new byte[length];
-        int outputPos = 0;
-        for (byte[] input : inputs) {
-            System.arraycopy(input, /*srcPos=*/ 0, output, outputPos, input.length);
-            outputPos += input.length;
-        }
-        return output;
-    }
-
     private static byte[] emptyByteArrayIfNull(@Nullable byte[] input) {
         return input == null ? EMPTY_BYTE_ARRAY : input;
     }
diff --git a/services/core/java/com/android/server/media/MediaButtonReceiverHolder.java b/services/core/java/com/android/server/media/MediaButtonReceiverHolder.java
index 9a19031..a5c762a 100644
--- a/services/core/java/com/android/server/media/MediaButtonReceiverHolder.java
+++ b/services/core/java/com/android/server/media/MediaButtonReceiverHolder.java
@@ -32,6 +32,7 @@
 import android.os.PowerWhitelistManager;
 import android.os.UserHandle;
 import android.text.TextUtils;
+import android.util.EventLog;
 import android.util.Log;
 import android.view.KeyEvent;
 
@@ -117,6 +118,12 @@
         int componentType = getComponentType(pendingIntent);
         ComponentName componentName = getComponentName(pendingIntent, componentType);
         if (componentName != null) {
+            if (!TextUtils.equals(componentName.getPackageName(), sessionPackageName)) {
+                EventLog.writeEvent(0x534e4554, "238177121", -1, "");
+                throw new IllegalArgumentException("ComponentName does not belong to "
+                        + "sessionPackageName. sessionPackageName = " + sessionPackageName
+                        + ", ComponentName pkg = " + componentName.getPackageName());
+            }
             return new MediaButtonReceiverHolder(userId, pendingIntent, componentName,
                     componentType);
         }
diff --git a/services/core/java/com/android/server/media/MediaSessionRecord.java b/services/core/java/com/android/server/media/MediaSessionRecord.java
index 34cd6a0..1ee9a87 100644
--- a/services/core/java/com/android/server/media/MediaSessionRecord.java
+++ b/services/core/java/com/android/server/media/MediaSessionRecord.java
@@ -50,6 +50,8 @@
 import android.os.RemoteException;
 import android.os.ResultReceiver;
 import android.os.SystemClock;
+import android.text.TextUtils;
+import android.util.EventLog;
 import android.util.Log;
 import android.view.KeyEvent;
 
@@ -914,6 +916,14 @@
         @Override
         public void setMediaButtonReceiver(PendingIntent pi, String sessionPackageName)
                 throws RemoteException {
+            //mPackageName has been verified in MediaSessionService.enforcePackageName().
+            if (!TextUtils.equals(sessionPackageName, mPackageName)) {
+                EventLog.writeEvent(0x534e4554, "238177121", -1, "");
+                throw new IllegalArgumentException("sessionPackageName name does not match "
+                        + "package name provided to MediaSessionRecord. sessionPackageName = "
+                        + sessionPackageName + ", pkg = "
+                        + mPackageName);
+            }
             final long token = Binder.clearCallingIdentity();
             try {
                 if ((mPolicies & MediaSessionPolicyProvider.SESSION_POLICY_IGNORE_BUTTON_RECEIVER)
@@ -932,6 +942,15 @@
         public void setMediaButtonBroadcastReceiver(ComponentName receiver) throws RemoteException {
             final long token = Binder.clearCallingIdentity();
             try {
+                //mPackageName has been verified in MediaSessionService.enforcePackageName().
+                if (receiver != null && !TextUtils.equals(
+                        mPackageName, receiver.getPackageName())) {
+                    EventLog.writeEvent(0x534e4554, "238177121", -1, "");
+                    throw new IllegalArgumentException("receiver does not belong to "
+                            + "package name provided to MediaSessionRecord. Pkg = " + mPackageName
+                            + ", Receiver Pkg = " + receiver.getPackageName());
+                }
+
                 if ((mPolicies & MediaSessionPolicyProvider.SESSION_POLICY_IGNORE_BUTTON_RECEIVER)
                         != 0) {
                     return;
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java
index 7d4cfdf..164445c 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java
@@ -1698,7 +1698,8 @@
                 mDefaultPermissionCallback.onInstallPermissionGranted();
             }
 
-            public void onPermissionRevoked(int uid, int userId, String reason) {
+            public void onPermissionRevoked(int uid, int userId, String reason,
+                    boolean overrideKill, @Nullable String permissionName) {
                 revokedPermissions.add(IntPair.of(uid, userId));
 
                 syncUpdatedUsers.add(userId);
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 2b3a685..111d8a0 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -2611,7 +2611,7 @@
         // either way, abort and reset the sequence.
         if (parcelable == null
                 || mTransferringSplashScreenState != TRANSFER_SPLASH_SCREEN_COPYING
-                || mStartingWindow == null
+                || mStartingWindow == null || mStartingWindow.mRemoved
                 || finishing) {
             if (parcelable != null) {
                 parcelable.clearIfNeeded();
@@ -9744,6 +9744,7 @@
                 record.mThumbnailAdapter != null ? record.mThumbnailAdapter.mCapturedLeash : null,
                 record.mStartBounds, task.getTaskInfo(), checkEnterPictureInPictureAppOpsState());
         target.setShowBackdrop(record.mShowBackdrop);
+        target.setWillShowImeOnTarget(mStartingData != null && mStartingData.hasImeSurface());
         target.hasAnimatingParent = record.hasAnimatingParent();
         return target;
     }
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index 8e4de31..e062c38 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -1659,9 +1659,6 @@
                 && transitionController.getTransitionPlayer() != null)
                 ? transitionController.createTransition(TRANSIT_OPEN) : null;
         RemoteTransition remoteTransition = r.takeRemoteTransition();
-        if (newTransition != null && remoteTransition != null) {
-            newTransition.setRemoteTransition(remoteTransition);
-        }
         transitionController.collect(r);
         try {
             mService.deferWindowLayout();
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index 6fd28ca..471424b 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -5324,7 +5324,13 @@
     }
 
     @Override
-    public void setRunningRemoteTransitionDelegate(IApplicationThread caller) {
+    public void setRunningRemoteTransitionDelegate(IApplicationThread delegate) {
+        final TransitionController controller = getTransitionController();
+        // A quick path without entering WM lock.
+        if (delegate != null && controller.mRemotePlayer.reportRunning(delegate)) {
+            // The delegate was known as running remote transition.
+            return;
+        }
         mAmInternal.enforceCallingPermission(CONTROL_REMOTE_APP_TRANSITION_ANIMATIONS,
                 "setRunningRemoteTransition");
         final int callingPid = Binder.getCallingPid();
@@ -5341,13 +5347,12 @@
                 Slog.e(TAG, msg);
                 throw new SecurityException(msg);
             }
-            final WindowProcessController wpc = getProcessController(caller);
+            final WindowProcessController wpc = getProcessController(delegate);
             if (wpc == null) {
-                Slog.w(TAG, "Unable to find process for application " + caller);
+                Slog.w(TAG, "setRunningRemoteTransition: no process for " + delegate);
                 return;
             }
-            wpc.setRunningRemoteAnimation(true /* running */);
-            callingProc.addRemoteAnimationDelegate(wpc);
+            controller.mRemotePlayer.update(wpc, true /* running */, false /* predict */);
         }
     }
 
diff --git a/services/core/java/com/android/server/wm/BackNavigationController.java b/services/core/java/com/android/server/wm/BackNavigationController.java
index f967cf9..d9ab971 100644
--- a/services/core/java/com/android/server/wm/BackNavigationController.java
+++ b/services/core/java/com/android/server/wm/BackNavigationController.java
@@ -131,7 +131,6 @@
                         "Focused window found using getFocusedWindowToken");
             }
 
-            OnBackInvokedCallbackInfo overrideCallbackInfo = null;
             if (window != null) {
                 // This is needed to bridge the old and new back behavior with recents.  While in
                 // Overview with live tile enabled, the previous app is technically focused but we
@@ -140,15 +139,18 @@
                 // the right window to consume back while in overview, so we need to route it to
                 // launcher and use the legacy behavior of injecting KEYCODE_BACK since the existing
                 // compat callback in VRI only works when the window is focused.
+                // This symptom also happen while shell transition enabled, we can check that by
+                // isTransientLaunch to know whether the focus window is point to live tile.
                 final RecentsAnimationController recentsAnimationController =
                         wmService.getRecentsAnimationController();
-                if (recentsAnimationController != null
-                        && recentsAnimationController.shouldApplyInputConsumer(
-                        window.getActivityRecord())) {
-                    window = recentsAnimationController.getTargetAppMainWindow();
-                    overrideCallbackInfo = recentsAnimationController.getBackInvokedInfo();
+                final ActivityRecord ar = window.mActivityRecord;
+                if ((ar != null && ar.isActivityTypeHomeOrRecents()
+                        && ar.mTransitionController.isTransientLaunch(ar))
+                        || (recentsAnimationController != null
+                        && recentsAnimationController.shouldApplyInputConsumer(ar))) {
                     ProtoLog.d(WM_DEBUG_BACK_PREVIEW, "Current focused window being animated by "
                             + "recents. Overriding back callback to recents controller callback.");
+                    return null;
                 }
             }
 
@@ -166,9 +168,7 @@
             if (window != null) {
                 currentActivity = window.mActivityRecord;
                 currentTask = window.getTask();
-                callbackInfo = overrideCallbackInfo != null
-                        ? overrideCallbackInfo
-                        : window.getOnBackInvokedCallbackInfo();
+                callbackInfo = window.getOnBackInvokedCallbackInfo();
                 if (callbackInfo == null) {
                     Slog.e(TAG, "No callback registered, returning null.");
                     return null;
@@ -213,8 +213,10 @@
             Task finalTask = currentTask;
             prevActivity = currentTask.getActivity(
                     (r) -> !r.finishing && r.getTask() == finalTask && !r.isTopRunningActivity());
-            if (window.getParent().getChildCount() > 1 && window.getParent().getChildAt(0)
-                    != window) {
+            // TODO Dialog window does not need to attach on activity, check
+            // window.mAttrs.type != TYPE_BASE_APPLICATION
+            if ((window.getParent().getChildCount() > 1
+                    && window.getParent().getChildAt(0) != window)) {
                 // Are we the top window of our parent? If not, we are a window on top of the
                 // activity, we won't close the activity.
                 backType = BackNavigationInfo.TYPE_DIALOG_CLOSE;
@@ -379,7 +381,8 @@
 
     private void onBackNavigationDone(
             Bundle result, WindowState focusedWindow, WindowContainer<?> windowContainer,
-            int backType, Task task, ActivityRecord prevActivity, boolean prepareAnimation) {
+            int backType, @Nullable Task task, @Nullable ActivityRecord prevActivity,
+            boolean prepareAnimation) {
         SurfaceControl surfaceControl = windowContainer.getSurfaceControl();
         boolean triggerBack = result != null && result.getBoolean(
                 BackNavigationInfo.KEY_TRIGGER_BACK);
@@ -404,7 +407,7 @@
                         "Setting Activity.mLauncherTaskBehind to false. Activity=%s",
                         prevActivity);
             }
-        } else {
+        } else if (task != null) {
             task.mBackGestureStarted = false;
         }
         resetSurfaces(windowContainer);
diff --git a/services/core/java/com/android/server/wm/DisplayArea.java b/services/core/java/com/android/server/wm/DisplayArea.java
index 863782a..0422906 100644
--- a/services/core/java/com/android/server/wm/DisplayArea.java
+++ b/services/core/java/com/android/server/wm/DisplayArea.java
@@ -207,6 +207,23 @@
         return false;
     }
 
+    @Override
+    public void setAlwaysOnTop(boolean alwaysOnTop) {
+        if (isAlwaysOnTop() == alwaysOnTop) {
+            return;
+        }
+        super.setAlwaysOnTop(alwaysOnTop);
+        // positionChildAtTop() must be called even when always on top gets turned off because
+        // we need to make sure that the display area is moved from among always on top containers
+        // to below other always on top containers. Since the position the display area should be
+        // inserted into is calculated properly in {@link DisplayContent#getTopInsertPosition()}
+        // in both cases, we can just request that the root task is put at top here.
+        if (getParent().asDisplayArea() != null) {
+            getParent().asDisplayArea().positionChildAt(POSITION_TOP, this,
+                    false /* includingParents */);
+        }
+    }
+
     boolean getIgnoreOrientationRequest() {
         // Adding an exception for when ignoreOrientationRequest is overridden at runtime for all
         // DisplayArea-s. For example, this is needed for the Kids Mode since many Kids apps aren't
@@ -234,6 +251,18 @@
         // The min possible position we can insert the child at.
         int minPosition = findMinPositionForChildDisplayArea(child);
 
+        // Place all non-always-on-top containers below always-on-top ones.
+        int alwaysOnTopCount = 0;
+        for (int i = minPosition; i <= maxPosition; i++) {
+            if (mChildren.get(i).isAlwaysOnTop()) {
+                alwaysOnTopCount++;
+            }
+        }
+        if (child.isAlwaysOnTop()) {
+            minPosition = maxPosition - alwaysOnTopCount + 1;
+        } else {
+            maxPosition -= alwaysOnTopCount;
+        }
         return Math.max(Math.min(requestPosition, maxPosition), minPosition);
     }
 
diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java
index 14e2241..7aa0541 100644
--- a/services/core/java/com/android/server/wm/DisplayPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayPolicy.java
@@ -318,12 +318,19 @@
      */
     private final ArrayList<WindowState> mStatusBarBackgroundWindows = new ArrayList<>();
 
+    /**
+     * A collection of {@link LetterboxDetails} of all visible activities to be sent to SysUI in
+     * order to determine status bar appearance
+     */
+    private final ArrayList<LetterboxDetails> mLetterboxDetails = new ArrayList<>();
+
     private String mFocusedApp;
     private int mLastDisableFlags;
     private int mLastAppearance;
     private int mLastBehavior;
     private InsetsVisibilities mRequestedVisibilities = new InsetsVisibilities();
     private AppearanceRegion[] mLastStatusBarAppearanceRegions;
+    private LetterboxDetails[] mLastLetterboxDetails;
 
     /** The union of checked bounds while building {@link #mStatusBarAppearanceRegionList}. */
     private final Rect mStatusBarColorCheckedBounds = new Rect();
@@ -1638,6 +1645,7 @@
         mNavBarColorWindowCandidate = null;
         mNavBarBackgroundWindow = null;
         mStatusBarAppearanceRegionList.clear();
+        mLetterboxDetails.clear();
         mStatusBarBackgroundWindows.clear();
         mStatusBarColorCheckedBounds.setEmpty();
         mStatusBarBackgroundCheckedBounds.setEmpty();
@@ -1717,6 +1725,16 @@
                             win.mAttrs.insetsFlags.appearance & APPEARANCE_LIGHT_STATUS_BARS,
                             new Rect(win.getFrame())));
                     mStatusBarColorCheckedBounds.union(sTmpRect);
+                    // Check if current activity is letterboxed in order create a LetterboxDetails
+                    // component to be passed to SysUI for status bar treatment
+                    final ActivityRecord currentActivity = win.getActivityRecord();
+                    if (currentActivity != null) {
+                        final LetterboxDetails currentLetterboxDetails = currentActivity
+                                .mLetterboxUiController.getLetterboxDetails();
+                        if (currentLetterboxDetails != null) {
+                            mLetterboxDetails.add(currentLetterboxDetails);
+                        }
+                    }
                 }
             }
 
@@ -2404,12 +2422,15 @@
             callStatusBarSafely(statusBar -> statusBar.setDisableFlags(displayId, disableFlags,
                     cause));
         }
+        final LetterboxDetails[] letterboxDetails = new LetterboxDetails[mLetterboxDetails.size()];
+        mLetterboxDetails.toArray(letterboxDetails);
         if (mLastAppearance == appearance
                 && mLastBehavior == behavior
                 && mRequestedVisibilities.equals(win.getRequestedVisibilities())
                 && Objects.equals(mFocusedApp, focusedApp)
                 && mLastFocusIsFullscreen == isFullscreen
-                && Arrays.equals(mLastStatusBarAppearanceRegions, statusBarAppearanceRegions)) {
+                && Arrays.equals(mLastStatusBarAppearanceRegions, statusBarAppearanceRegions)
+                && Arrays.equals(mLastLetterboxDetails, letterboxDetails)) {
             return;
         }
         if (mDisplayContent.isDefaultDisplay && mLastFocusIsFullscreen != isFullscreen
@@ -2425,9 +2446,10 @@
         mFocusedApp = focusedApp;
         mLastFocusIsFullscreen = isFullscreen;
         mLastStatusBarAppearanceRegions = statusBarAppearanceRegions;
+        mLastLetterboxDetails = letterboxDetails;
         callStatusBarSafely(statusBar -> statusBar.onSystemBarAttributesChanged(displayId,
                 appearance, statusBarAppearanceRegions, isNavbarColorManagedByIme, behavior,
-                requestedVisibilities, focusedApp, new LetterboxDetails[]{}));
+                requestedVisibilities, focusedApp, letterboxDetails));
     }
 
     private void callStatusBarSafely(Consumer<StatusBarManagerInternal> consumer) {
@@ -2748,8 +2770,6 @@
     public void takeScreenshot(int screenshotType, int source) {
         if (mScreenshotHelper != null) {
             mScreenshotHelper.takeScreenshot(screenshotType,
-                    getStatusBar() != null && getStatusBar().isVisible(),
-                    getNavigationBar() != null && getNavigationBar().isVisible(),
                     source, mHandler, null /* completionConsumer */);
         }
     }
@@ -2843,6 +2863,12 @@
                 pw.print(prefixInner);  pw.println(mLastStatusBarAppearanceRegions[i]);
             }
         }
+        if (mLastLetterboxDetails != null) {
+            pw.print(prefix); pw.println("mLastLetterboxDetails=");
+            for (int i = mLastLetterboxDetails.length - 1; i >= 0; i--) {
+                pw.print(prefixInner);  pw.println(mLastLetterboxDetails[i]);
+            }
+        }
         if (!mStatusBarBackgroundWindows.isEmpty()) {
             pw.print(prefix); pw.println("mStatusBarBackgroundWindows=");
             for (int i = mStatusBarBackgroundWindows.size() - 1; i >= 0; i--) {
diff --git a/services/core/java/com/android/server/wm/KeyguardController.java b/services/core/java/com/android/server/wm/KeyguardController.java
index d76f6be..1567fa7 100644
--- a/services/core/java/com/android/server/wm/KeyguardController.java
+++ b/services/core/java/com/android/server/wm/KeyguardController.java
@@ -209,8 +209,6 @@
 
         if (keyguardChanged) {
             // Irrelevant to AOD.
-            dismissMultiWindowModeForTaskIfNeeded(displayId,
-                    null /* currentTaskControllsingOcclusion */);
             state.mKeyguardGoingAway = false;
             if (keyguardShowing) {
                 state.mDismissalRequested = false;
diff --git a/services/core/java/com/android/server/wm/Letterbox.java b/services/core/java/com/android/server/wm/Letterbox.java
index b5eff41..df3109a 100644
--- a/services/core/java/com/android/server/wm/Letterbox.java
+++ b/services/core/java/com/android/server/wm/Letterbox.java
@@ -138,6 +138,11 @@
         return mInner;
     }
 
+    /** @return The frame that contains the inner frame and the insets. */
+    Rect getOuterFrame() {
+        return mOuter;
+    }
+
     /**
      * Returns {@code true} if the letterbox does not overlap with the bar, or the letterbox can
      * fully cover the window frame.
diff --git a/services/core/java/com/android/server/wm/LetterboxUiController.java b/services/core/java/com/android/server/wm/LetterboxUiController.java
index d652767..ec9ee29 100644
--- a/services/core/java/com/android/server/wm/LetterboxUiController.java
+++ b/services/core/java/com/android/server/wm/LetterboxUiController.java
@@ -68,6 +68,7 @@
 
 import com.android.internal.R;
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.statusbar.LetterboxDetails;
 import com.android.server.wm.LetterboxConfiguration.LetterboxBackgroundType;
 
 import java.io.PrintWriter;
@@ -141,6 +142,15 @@
         }
     }
 
+    /** Gets the outer bounds of letterbox. The bounds will be empty if there is no letterbox. */
+    private void getLetterboxOuterBounds(Rect outBounds) {
+        if (mLetterbox != null) {
+            outBounds.set(mLetterbox.getOuterFrame());
+        } else {
+            outBounds.setEmpty();
+        }
+    }
+
     /**
      * @return {@code true} if bar shown within a given rectangle is allowed to be fully transparent
      *     when the current activity is displayed.
@@ -683,4 +693,26 @@
         mActivityRecord.mTaskSupervisor.getActivityMetricsLogger()
                 .logLetterboxPositionChange(mActivityRecord, letterboxPositionChange);
     }
+
+    @Nullable
+    LetterboxDetails getLetterboxDetails() {
+        final WindowState w = mActivityRecord.findMainWindow();
+        if (mLetterbox == null || w == null || w.isLetterboxedForDisplayCutout()) {
+            return null;
+        }
+        Rect letterboxInnerBounds = new Rect();
+        Rect letterboxOuterBounds = new Rect();
+        getLetterboxInnerBounds(letterboxInnerBounds);
+        getLetterboxOuterBounds(letterboxOuterBounds);
+
+        if (letterboxInnerBounds.isEmpty() || letterboxOuterBounds.isEmpty()) {
+            return null;
+        }
+
+        return new LetterboxDetails(
+                letterboxInnerBounds,
+                letterboxOuterBounds,
+                w.mAttrs.insetsFlags.appearance
+        );
+    }
 }
diff --git a/services/core/java/com/android/server/wm/RecentsAnimationController.java b/services/core/java/com/android/server/wm/RecentsAnimationController.java
index 53f1fe6..5b702ea 100644
--- a/services/core/java/com/android/server/wm/RecentsAnimationController.java
+++ b/services/core/java/com/android/server/wm/RecentsAnimationController.java
@@ -19,12 +19,10 @@
 import static android.app.ActivityTaskManager.INVALID_TASK_ID;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
 import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
-import static android.hardware.input.InputManager.INJECT_INPUT_EVENT_MODE_ASYNC;
 import static android.view.RemoteAnimationTarget.MODE_CLOSING;
 import static android.view.RemoteAnimationTarget.MODE_OPENING;
 import static android.view.WindowManager.INPUT_CONSUMER_RECENTS_ANIMATION;
 import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
-import static android.window.OnBackInvokedDispatcher.PRIORITY_DEFAULT;
 
 import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_RECENTS_ANIMATIONS;
 import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER;
@@ -41,7 +39,6 @@
 import android.graphics.Point;
 import android.graphics.Rect;
 import android.hardware.HardwareBuffer;
-import android.hardware.input.InputManager;
 import android.os.Binder;
 import android.os.IBinder.DeathRecipient;
 import android.os.RemoteException;
@@ -54,18 +51,12 @@
 import android.util.proto.ProtoOutputStream;
 import android.view.IRecentsAnimationController;
 import android.view.IRecentsAnimationRunner;
-import android.view.InputDevice;
 import android.view.InputWindowHandle;
-import android.view.KeyCharacterMap;
-import android.view.KeyEvent;
 import android.view.RemoteAnimationTarget;
 import android.view.SurfaceControl;
 import android.view.SurfaceControl.Transaction;
 import android.view.SurfaceSession;
 import android.view.WindowInsets.Type;
-import android.window.BackEvent;
-import android.window.IOnBackInvokedCallback;
-import android.window.OnBackInvokedCallbackInfo;
 import android.window.PictureInPictureSurfaceTransaction;
 import android.window.TaskSnapshot;
 
@@ -195,46 +186,6 @@
         }
     };
 
-    /**
-     * Back invoked callback for legacy recents transition with the new back dispatch system.
-     */
-    final IOnBackInvokedCallback mBackCallback = new IOnBackInvokedCallback.Stub() {
-        @Override
-        public void onBackStarted() {
-            // Do nothing
-        }
-
-        @Override
-        public void onBackProgressed(BackEvent backEvent) {
-            // Do nothing
-        }
-
-        @Override
-        public void onBackCancelled() {
-            // Do nothing
-        }
-
-        @Override
-        public void onBackInvoked() {
-            sendBackEvent(KeyEvent.ACTION_DOWN);
-            sendBackEvent(KeyEvent.ACTION_UP);
-        }
-
-        private void sendBackEvent(int action) {
-            if (mTargetActivityRecord == null) {
-                return;
-            }
-            long when = SystemClock.uptimeMillis();
-            final KeyEvent ev = new KeyEvent(when, when, action,
-                    KeyEvent.KEYCODE_BACK, 0 /* repeat */, 0 /* metaState */,
-                    KeyCharacterMap.VIRTUAL_KEYBOARD, 0 /* scancode */,
-                    KeyEvent.FLAG_FROM_SYSTEM | KeyEvent.FLAG_VIRTUAL_HARD_KEY,
-                    InputDevice.SOURCE_KEYBOARD);
-            ev.setDisplayId(mTargetActivityRecord.getDisplayId());
-            InputManager.getInstance().injectInputEvent(ev, INJECT_INPUT_EVENT_MODE_ASYNC);
-        }
-    };
-
     public interface RecentsAnimationCallbacks {
         /** Callback when recents animation is finished. */
         void onAnimationFinished(@ReorderMode int reorderMode, boolean sendUserLeaveHint);
@@ -1112,10 +1063,6 @@
         return mTargetActivityRecord.findMainWindow();
     }
 
-    OnBackInvokedCallbackInfo getBackInvokedInfo() {
-        return new OnBackInvokedCallbackInfo(mBackCallback, PRIORITY_DEFAULT);
-    }
-
     DisplayArea getTargetAppDisplayArea() {
         if (mTargetActivityRecord == null) {
             return null;
@@ -1236,6 +1183,12 @@
                     mLocalBounds, mBounds, mTask.getWindowConfiguration(),
                     mIsRecentTaskInvisible, null, null, mTask.getTaskInfo(),
                     topApp.checkEnterPictureInPictureAppOpsState());
+
+            final ActivityRecord topActivity = mTask.getTopNonFinishingActivity();
+            if (topActivity != null && topActivity.mStartingData != null
+                    && topActivity.mStartingData.hasImeSurface()) {
+                mTarget.setWillShowImeOnTarget(true);
+            }
             return mTarget;
         }
 
diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java
index 4df0a49..589618e 100644
--- a/services/core/java/com/android/server/wm/TaskFragment.java
+++ b/services/core/java/com/android/server/wm/TaskFragment.java
@@ -401,6 +401,10 @@
         mTaskFragmentOrganizerProcessName = processName;
     }
 
+    void onTaskFragmentOrganizerRemoved() {
+        mTaskFragmentOrganizer = null;
+    }
+
     /** Whether this TaskFragment is organized by the given {@code organizer}. */
     boolean hasTaskFragmentOrganizer(ITaskFragmentOrganizer organizer) {
         return organizer != null && mTaskFragmentOrganizer != null
diff --git a/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java b/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java
index d4551be..602579f 100644
--- a/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java
+++ b/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java
@@ -136,6 +136,9 @@
         void dispose() {
             while (!mOrganizedTaskFragments.isEmpty()) {
                 final TaskFragment taskFragment = mOrganizedTaskFragments.get(0);
+                // Cleanup before remove to prevent it from sending any additional event, such as
+                // #onTaskFragmentVanished, to the removed organizer.
+                taskFragment.onTaskFragmentOrganizerRemoved();
                 taskFragment.removeImmediately();
                 mOrganizedTaskFragments.remove(taskFragment);
             }
@@ -512,10 +515,21 @@
         mPendingTaskFragmentEvents.add(pendingEvent);
     }
 
+    boolean isOrganizerRegistered(ITaskFragmentOrganizer organizer) {
+        return mTaskFragmentOrganizerState.containsKey(organizer.asBinder());
+    }
+
     private void removeOrganizer(ITaskFragmentOrganizer organizer) {
         final TaskFragmentOrganizerState state = validateAndGetState(organizer);
         // remove all of the children of the organized TaskFragment
         state.dispose();
+        // Remove any pending event of this organizer.
+        for (int i = mPendingTaskFragmentEvents.size() - 1; i >= 0; i--) {
+            final PendingTaskFragmentEvent event = mPendingTaskFragmentEvents.get(i);
+            if (event.mTaskFragmentOrg.asBinder().equals(organizer.asBinder())) {
+                mPendingTaskFragmentEvents.remove(i);
+            }
+        }
         mTaskFragmentOrganizerState.remove(organizer.asBinder());
     }
 
diff --git a/services/core/java/com/android/server/wm/TaskOrganizerController.java b/services/core/java/com/android/server/wm/TaskOrganizerController.java
index 8b71a34..0a2e877 100644
--- a/services/core/java/com/android/server/wm/TaskOrganizerController.java
+++ b/services/core/java/com/android/server/wm/TaskOrganizerController.java
@@ -632,6 +632,11 @@
         final Rect mainFrame = window.getRelativeFrame();
         final StartingWindowAnimationAdaptor adaptor = new StartingWindowAnimationAdaptor();
         window.startAnimation(t, adaptor, false, ANIMATION_TYPE_STARTING_REVEAL);
+        if (adaptor.mAnimationLeash == null) {
+            Slog.e(TAG, "Cannot start starting window animation, the window " + window
+                    + " was removed");
+            return null;
+        }
         t.setPosition(adaptor.mAnimationLeash, mainFrame.left, mainFrame.top);
         return adaptor.mAnimationLeash;
     }
@@ -679,13 +684,15 @@
         if (topActivity != null) {
             removalInfo.deferRemoveForIme = topActivity.mDisplayContent
                     .mayImeShowOnLaunchingActivity(topActivity);
-            if (removalInfo.playRevealAnimation && playShiftUpAnimation) {
-                final WindowState mainWindow =
-                        topActivity.findMainWindow(false/* includeStartingApp */);
-                if (mainWindow != null) {
-                    removalInfo.windowAnimationLeash = applyStartingWindowAnimation(mainWindow);
-                    removalInfo.mainFrame = mainWindow.getRelativeFrame();
-                }
+            final WindowState mainWindow =
+                    topActivity.findMainWindow(false/* includeStartingApp */);
+            // No app window for this activity, app might be crashed.
+            // Remove starting window immediately without playing reveal animation.
+            if (mainWindow == null || mainWindow.mRemoved) {
+                removalInfo.playRevealAnimation = false;
+            } else if (removalInfo.playRevealAnimation && playShiftUpAnimation) {
+                removalInfo.windowAnimationLeash = applyStartingWindowAnimation(mainWindow);
+                removalInfo.mainFrame = mainWindow.getRelativeFrame();
             }
         }
         try {
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index 31d8eb8..bbc95a1 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -54,6 +54,7 @@
 import static android.window.TransitionInfo.FLAG_SHOW_WALLPAPER;
 import static android.window.TransitionInfo.FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT;
 import static android.window.TransitionInfo.FLAG_TRANSLUCENT;
+import static android.window.TransitionInfo.FLAG_WILL_IME_SHOWN;
 
 import static com.android.server.wm.ActivityRecord.State.RESUMED;
 import static com.android.server.wm.ActivityTaskManagerInternal.APP_TRANSITION_RECENTS_ANIM;
@@ -1728,6 +1729,13 @@
             if (task != null && task.voiceSession != null) {
                 flags |= FLAG_IS_VOICE_INTERACTION;
             }
+            if (task != null) {
+                final ActivityRecord topActivity = task.getTopNonFinishingActivity();
+                if (topActivity != null && topActivity.mStartingData != null
+                        && topActivity.mStartingData.hasImeSurface()) {
+                    flags |= FLAG_WILL_IME_SHOWN;
+                }
+            }
             final ActivityRecord record = wc.asActivityRecord();
             if (record != null) {
                 if (record.mUseTransferredAnimation) {
diff --git a/services/core/java/com/android/server/wm/TransitionController.java b/services/core/java/com/android/server/wm/TransitionController.java
index a02be25..4f324f2 100644
--- a/services/core/java/com/android/server/wm/TransitionController.java
+++ b/services/core/java/com/android/server/wm/TransitionController.java
@@ -45,6 +45,7 @@
 import android.window.TransitionInfo;
 import android.window.TransitionRequestInfo;
 
+import com.android.internal.annotations.GuardedBy;
 import com.android.internal.protolog.ProtoLogGroup;
 import com.android.internal.protolog.common.ProtoLog;
 import com.android.server.LocalServices;
@@ -77,9 +78,10 @@
     final TransitionMetricsReporter mTransitionMetricsReporter = new TransitionMetricsReporter();
     final TransitionTracer mTransitionTracer;
 
-    private IApplicationThread mTransitionPlayerThread;
+    private WindowProcessController mTransitionPlayerProc;
     final ActivityTaskManagerService mAtm;
     final TaskSnapshotController mTaskSnapshotController;
+    final RemotePlayer mRemotePlayer;
 
     private final ArrayList<WindowManagerInternal.AppTransitionListener> mLegacyListeners =
             new ArrayList<>();
@@ -112,6 +114,7 @@
             TaskSnapshotController taskSnapshotController,
             TransitionTracer transitionTracer) {
         mAtm = atm;
+        mRemotePlayer = new RemotePlayer(atm);
         mStatusBar = LocalServices.getService(StatusBarManagerInternal.class);
         mTaskSnapshotController = taskSnapshotController;
         mTransitionTracer = transitionTracer;
@@ -123,6 +126,8 @@
                 }
                 mPlayingTransitions.clear();
                 mTransitionPlayer = null;
+                mTransitionPlayerProc = null;
+                mRemotePlayer.clear();
                 mRunningLock.doNotifyLocked();
             }
         };
@@ -169,7 +174,7 @@
     }
 
     void registerTransitionPlayer(@Nullable ITransitionPlayer player,
-            @Nullable IApplicationThread appThread) {
+            @Nullable WindowProcessController playerProc) {
         try {
             // Note: asBinder() can be null if player is same process (likely in a test).
             if (mTransitionPlayer != null) {
@@ -182,7 +187,7 @@
                 player.asBinder().linkToDeath(mTransitionPlayerDeath, 0);
             }
             mTransitionPlayer = player;
-            mTransitionPlayerThread = appThread;
+            mTransitionPlayerProc = playerProc;
         } catch (RemoteException e) {
             throw new RuntimeException("Unable to set transition player");
         }
@@ -421,6 +426,7 @@
             }
             mTransitionPlayer.requestStartTransition(transition, new TransitionRequestInfo(
                     transition.mType, info, remoteTransition, displayChange));
+            transition.setRemoteTransition(remoteTransition);
         } catch (RemoteException e) {
             Slog.e(TAG, "Error requesting transition", e);
             transition.start();
@@ -473,9 +479,10 @@
         // Collect all visible non-app windows which need to be drawn before the animation starts.
         final DisplayContent dc = wc.asDisplayContent();
         if (dc != null) {
+            final boolean noAsyncRotation = dc.getAsyncRotationController() == null;
             wc.forAllWindows(w -> {
                 if (w.mActivityRecord == null && w.isVisible() && !isCollecting(w.mToken)
-                        && dc.shouldSyncRotationChange(w)) {
+                        && (noAsyncRotation || !AsyncRotationController.canBeAsync(w.mToken))) {
                     transition.collect(w.mToken);
                 }
             }, true /* traverseTopToBottom */);
@@ -537,9 +544,7 @@
         }
         ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "Finish Transition: %s", record);
         mPlayingTransitions.remove(record);
-        if (mPlayingTransitions.isEmpty()) {
-            setAnimationRunning(false /* running */);
-        }
+        updateRunningRemoteAnimation(record, false /* isPlaying */);
         record.finishTransition();
         mRunningLock.doNotifyLocked();
     }
@@ -549,21 +554,27 @@
             throw new IllegalStateException("Trying to move non-collecting transition to playing");
         }
         mCollectingTransition = null;
-        if (mPlayingTransitions.isEmpty()) {
-            setAnimationRunning(true /* running */);
-        }
         mPlayingTransitions.add(transition);
+        updateRunningRemoteAnimation(transition, true /* isPlaying */);
         mTransitionTracer.logState(transition);
     }
 
-    private void setAnimationRunning(boolean running) {
-        if (mTransitionPlayerThread == null) return;
-        final WindowProcessController wpc = mAtm.getProcessController(mTransitionPlayerThread);
-        if (wpc == null) {
-            Slog.w(TAG, "Unable to find process for player thread=" + mTransitionPlayerThread);
+    /** Updates the process state of animation player. */
+    private void updateRunningRemoteAnimation(Transition transition, boolean isPlaying) {
+        if (mTransitionPlayerProc == null) return;
+        if (isPlaying) {
+            mTransitionPlayerProc.setRunningRemoteAnimation(true);
+        } else if (mPlayingTransitions.isEmpty()) {
+            mTransitionPlayerProc.setRunningRemoteAnimation(false);
+            mRemotePlayer.clear();
             return;
         }
-        wpc.setRunningRemoteAnimation(running);
+        final RemoteTransition remote = transition.getRemoteTransition();
+        if (remote == null) return;
+        final IApplicationThread appThread = remote.getAppThread();
+        final WindowProcessController delegate = mAtm.getProcessController(appThread);
+        if (delegate == null) return;
+        mRemotePlayer.update(delegate, isPlaying, true /* predict */);
     }
 
     void abort(Transition transition) {
@@ -666,6 +677,95 @@
         proto.end(token);
     }
 
+    /**
+     * This manages the animating state of processes that are running remote animations for
+     * {@link #mTransitionPlayerProc}.
+     */
+    static class RemotePlayer {
+        private static final long REPORT_RUNNING_GRACE_PERIOD_MS = 100;
+        @GuardedBy("itself")
+        private final ArrayMap<IBinder, DelegateProcess> mDelegateProcesses = new ArrayMap<>();
+        private final ActivityTaskManagerService mAtm;
+
+        private class DelegateProcess implements Runnable {
+            final WindowProcessController mProc;
+            /** Requires {@link RemotePlayer#reportRunning} to confirm it is really running. */
+            boolean mNeedReport;
+
+            DelegateProcess(WindowProcessController proc) {
+                mProc = proc;
+            }
+
+            /** This runs when the remote player doesn't report running in time. */
+            @Override
+            public void run() {
+                synchronized (mAtm.mGlobalLockWithoutBoost) {
+                    update(mProc, false /* running */, false /* predict */);
+                }
+            }
+        }
+
+        RemotePlayer(ActivityTaskManagerService atm) {
+            mAtm = atm;
+        }
+
+        void update(@NonNull WindowProcessController delegate, boolean running, boolean predict) {
+            if (!running) {
+                synchronized (mDelegateProcesses) {
+                    boolean removed = false;
+                    for (int i = mDelegateProcesses.size() - 1; i >= 0; i--) {
+                        if (mDelegateProcesses.valueAt(i).mProc == delegate) {
+                            mDelegateProcesses.removeAt(i);
+                            removed = true;
+                            break;
+                        }
+                    }
+                    if (!removed) return;
+                }
+                delegate.setRunningRemoteAnimation(false);
+                return;
+            }
+            if (delegate.isRunningRemoteTransition() || !delegate.hasThread()) return;
+            delegate.setRunningRemoteAnimation(true);
+            final DelegateProcess delegateProc = new DelegateProcess(delegate);
+            // If "predict" is true, that means the remote animation is set from
+            // ActivityOptions#makeRemoteAnimation(). But it is still up to shell side to decide
+            // whether to use the remote animation, so there is a timeout to cancel the prediction
+            // if the remote animation doesn't happen.
+            if (predict) {
+                delegateProc.mNeedReport = true;
+                mAtm.mH.postDelayed(delegateProc, REPORT_RUNNING_GRACE_PERIOD_MS);
+            }
+            synchronized (mDelegateProcesses) {
+                mDelegateProcesses.put(delegate.getThread().asBinder(), delegateProc);
+            }
+        }
+
+        void clear() {
+            synchronized (mDelegateProcesses) {
+                for (int i = mDelegateProcesses.size() - 1; i >= 0; i--) {
+                    mDelegateProcesses.valueAt(i).mProc.setRunningRemoteAnimation(false);
+                }
+                mDelegateProcesses.clear();
+            }
+        }
+
+        /** Returns {@code true} if the app is known to be running remote transition. */
+        boolean reportRunning(@NonNull IApplicationThread appThread) {
+            final DelegateProcess delegate;
+            synchronized (mDelegateProcesses) {
+                delegate = mDelegateProcesses.get(appThread.asBinder());
+                if (delegate != null && delegate.mNeedReport) {
+                    // It was predicted to run remote transition. Now it is really requesting so
+                    // remove the timeout of restoration.
+                    delegate.mNeedReport = false;
+                    mAtm.mH.removeCallbacks(delegate);
+                }
+            }
+            return delegate != null;
+        }
+    }
+
     static class TransitionMetricsReporter extends ITransitionMetricsReporter.Stub {
         private final ArrayMap<IBinder, LongConsumer> mMetricConsumers = new ArrayMap<>();
 
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index bce131b..adb8bf6 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -3769,9 +3769,9 @@
         // Check if this is changing displays. If so, mark the old display as "ready" for
         // transitions. This is to work around the problem where setting readiness against this
         // container will only set the new display as ready and leave the old display as unready.
-        if (mSyncState != SYNC_STATE_NONE && oldParent != null
-                && oldParent.getDisplayContent() != null && (newParent == null
-                        || oldParent.getDisplayContent() != newParent.getDisplayContent())) {
+        if (mSyncState != SYNC_STATE_NONE && oldParent != null && newParent != null
+                && oldParent.getDisplayContent() != null && newParent.getDisplayContent() != null
+                && oldParent.getDisplayContent() != newParent.getDisplayContent()) {
             mTransitionController.setReady(oldParent.getDisplayContent());
         }
 
diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java
index 0f5655c..95db746 100644
--- a/services/core/java/com/android/server/wm/WindowOrganizerController.java
+++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java
@@ -37,6 +37,7 @@
 import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_RESTORE_TRANSIENT_ORDER;
 import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_SET_ADJACENT_ROOTS;
 import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_SET_ADJACENT_TASK_FRAGMENTS;
+import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_SET_ALWAYS_ON_TOP;
 import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_SET_LAUNCH_ADJACENT_FLAG_ROOT;
 import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_SET_LAUNCH_ROOT;
 import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_START_ACTIVITY_IN_TASK_FRAGMENT;
@@ -55,7 +56,6 @@
 import android.annotation.Nullable;
 import android.app.ActivityManager;
 import android.app.ActivityOptions;
-import android.app.IApplicationThread;
 import android.app.WindowConfiguration;
 import android.content.ActivityNotFoundException;
 import android.content.Intent;
@@ -386,6 +386,12 @@
     private void applyTransaction(@NonNull WindowContainerTransaction t, int syncId,
             @Nullable Transition transition, @NonNull CallerInfo caller,
             @Nullable Transition finishTransition) {
+        if (t.getTaskFragmentOrganizer() != null && !mTaskFragmentOrganizerController
+                .isOrganizerRegistered(t.getTaskFragmentOrganizer())) {
+            Slog.e(TAG, "Caller organizer=" + t.getTaskFragmentOrganizer()
+                    + " is no longer registered");
+            return;
+        }
         int effects = 0;
         ProtoLog.v(WM_DEBUG_WINDOW_ORGANIZER, "Apply window transaction, syncId=%d", syncId);
         mService.deferWindowLayout();
@@ -1068,6 +1074,18 @@
                 WindowContainer.fromBinder(hop.getContainer())
                         .removeLocalInsetsSourceProvider(hop.getInsetsTypes());
                 break;
+            case HIERARCHY_OP_TYPE_SET_ALWAYS_ON_TOP:
+                final WindowContainer container = WindowContainer.fromBinder(hop.getContainer());
+                if (container == null || container.asDisplayArea() == null
+                        || !container.isAttached()) {
+                    Slog.e(TAG, "Attempt to operate on unknown or detached display area: "
+                            + container);
+                    break;
+                }
+                container.setAlwaysOnTop(hop.isAlwaysOnTop());
+                effects |= TRANSACT_EFFECTS_LIFECYCLE;
+                break;
+
         }
         return effects;
     }
@@ -1449,11 +1467,7 @@
             synchronized (mGlobalLock) {
                 final WindowProcessController wpc =
                         mService.getProcessController(callerPid, callerUid);
-                IApplicationThread appThread = null;
-                if (wpc != null) {
-                    appThread = wpc.getThread();
-                }
-                mTransitionController.registerTransitionPlayer(player, appThread);
+                mTransitionController.registerTransitionPlayer(player, wpc);
             }
         } finally {
             Binder.restoreCallingIdentity(ident);
diff --git a/services/core/java/com/android/server/wm/WindowProcessController.java b/services/core/java/com/android/server/wm/WindowProcessController.java
index b39b3e4b..95b0645 100644
--- a/services/core/java/com/android/server/wm/WindowProcessController.java
+++ b/services/core/java/com/android/server/wm/WindowProcessController.java
@@ -79,7 +79,6 @@
 
 import java.io.IOException;
 import java.io.PrintWriter;
-import java.lang.ref.WeakReference;
 import java.util.ArrayList;
 import java.util.List;
 
@@ -229,10 +228,6 @@
     /** Whether our process is currently running a {@link IRemoteAnimationRunner} */
     private boolean mRunningRemoteAnimation;
 
-    /** List of "chained" processes that are running remote animations for this process */
-    private final ArrayList<WeakReference<WindowProcessController>> mRemoteAnimationDelegates =
-            new ArrayList<>();
-
     // The bits used for mActivityStateFlags.
     private static final int ACTIVITY_STATE_FLAG_IS_VISIBLE = 1 << 16;
     private static final int ACTIVITY_STATE_FLAG_IS_PAUSING_OR_PAUSED = 1 << 17;
@@ -1677,30 +1672,7 @@
         updateRunningRemoteOrRecentsAnimation();
     }
 
-    /**
-     * Marks another process as a "delegate" animator. This means that process is doing some part
-     * of a remote animation on behalf of this process.
-     */
-    void addRemoteAnimationDelegate(WindowProcessController delegate) {
-        if (!isRunningRemoteTransition()) {
-            throw new IllegalStateException("Can't add a delegate to a process which isn't itself"
-                    + " running a remote animation");
-        }
-        mRemoteAnimationDelegates.add(new WeakReference<>(delegate));
-    }
-
     void updateRunningRemoteOrRecentsAnimation() {
-        if (!isRunningRemoteTransition()) {
-            // Clean-up any delegates
-            for (int i = 0; i < mRemoteAnimationDelegates.size(); ++i) {
-                final WindowProcessController delegate = mRemoteAnimationDelegates.get(i).get();
-                if (delegate == null) continue;
-                delegate.setRunningRemoteAnimation(false);
-                delegate.setRunningRecentsAnimation(false);
-            }
-            mRemoteAnimationDelegates.clear();
-        }
-
         // Posting on handler so WM lock isn't held when we call into AM.
         mAtm.mH.sendMessage(PooledLambda.obtainMessage(
                 WindowProcessListener::setRunningRemoteAnimation, mListener,
diff --git a/services/tests/PackageManagerServiceTests/appenumeration/Android.bp b/services/tests/PackageManagerServiceTests/appenumeration/Android.bp
index e44ce3c..31c49b3 100644
--- a/services/tests/PackageManagerServiceTests/appenumeration/Android.bp
+++ b/services/tests/PackageManagerServiceTests/appenumeration/Android.bp
@@ -34,5 +34,10 @@
     ],
     platform_apis: true,
     test_suites: ["device-tests"],
-    data: [":AppEnumerationSyncProviderTestApp"],
+    data: [
+        ":AppEnumerationCrossUserPackageVisibilityTestApp",
+        ":AppEnumerationHasAppOpPermissionTestApp",
+        ":AppEnumerationSharedUserTestApp",
+        ":AppEnumerationSyncProviderTestApp",
+    ],
 }
diff --git a/services/tests/servicestests/Android.bp b/services/tests/servicestests/Android.bp
index 5d0554a..a9ee84f 100644
--- a/services/tests/servicestests/Android.bp
+++ b/services/tests/servicestests/Android.bp
@@ -109,7 +109,11 @@
     data: [
         ":BstatsTestApp",
         ":JobTestApp",
+        ":SimpleServiceTestApp1",
+        ":SimpleServiceTestApp2",
+        ":SimpleServiceTestApp3",
         ":StubTestApp",
+        ":SuspendTestApp",
     ],
 
     java_resources: [
diff --git a/services/tests/servicestests/AndroidManifest.xml b/services/tests/servicestests/AndroidManifest.xml
index 0afb182..0483a60 100644
--- a/services/tests/servicestests/AndroidManifest.xml
+++ b/services/tests/servicestests/AndroidManifest.xml
@@ -103,6 +103,7 @@
     <uses-permission android:name="android.permission.MODIFY_AUDIO_ROUTING" />
     <uses-permission android:name="android.permission.PACKAGE_VERIFICATION_AGENT" />
     <uses-permission android:name="android.permission.OBSERVE_ROLE_HOLDERS" />
+    <uses-permission android:name="android.permission.BATTERY_STATS" />
 
     <queries>
         <package android:name="com.android.servicestests.apps.suspendtestapp" />
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/SystemActionPerformerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/SystemActionPerformerTest.java
index 1d6ed03..c15f6a9 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/SystemActionPerformerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/SystemActionPerformerTest.java
@@ -17,6 +17,7 @@
 package com.android.server.accessibility;
 
 import static android.view.WindowManager.ScreenshotSource.SCREENSHOT_ACCESSIBILITY_ACTIONS;
+import static android.view.WindowManager.TAKE_SCREENSHOT_FULLSCREEN;
 
 import static org.hamcrest.Matchers.hasItem;
 import static org.hamcrest.Matchers.is;
@@ -27,7 +28,6 @@
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.never;
@@ -301,8 +301,9 @@
         mSystemActionPerformer.performSystemAction(
                 AccessibilityService.GLOBAL_ACTION_TAKE_SCREENSHOT);
         verify(mMockScreenshotHelper).takeScreenshot(
-                eq(android.view.WindowManager.TAKE_SCREENSHOT_FULLSCREEN), anyBoolean(),
-                anyBoolean(), eq(SCREENSHOT_ACCESSIBILITY_ACTIONS), any(Handler.class), any());
+                eq(TAKE_SCREENSHOT_FULLSCREEN),
+                eq(SCREENSHOT_ACCESSIBILITY_ACTIONS),
+                any(Handler.class), any());
     }
 
     // PendingIntent is a final class and cannot be mocked. So we are using this
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTests.java b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTests.java
index 20cc42c..a4cccb3 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTests.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTests.java
@@ -33,15 +33,18 @@
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.reset;
 import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
 
 import android.app.PropertyInvalidatedCache;
 import android.os.RemoteException;
 import android.platform.test.annotations.Presubmit;
 import android.service.gatekeeper.GateKeeperResponse;
+import android.text.TextUtils;
 
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
+import com.android.internal.widget.LockPatternUtils;
 import com.android.internal.widget.LockscreenCredential;
 import com.android.internal.widget.VerifyCredentialResponse;
 
@@ -450,6 +453,45 @@
                 PRIMARY_USER_ID));
     }
 
+    @Test
+    public void testPasswordHistoryDisabledByDefault() throws Exception {
+        final int userId = PRIMARY_USER_ID;
+        checkPasswordHistoryLength(userId, 0);
+        initializeStorageWithCredential(userId, nonePassword());
+        checkPasswordHistoryLength(userId, 0);
+
+        assertTrue(mService.setLockCredential(newPassword("1234"), nonePassword(), userId));
+        checkPasswordHistoryLength(userId, 0);
+    }
+
+    @Test
+    public void testPasswordHistoryLengthHonored() throws Exception {
+        final int userId = PRIMARY_USER_ID;
+        when(mDevicePolicyManager.getPasswordHistoryLength(any(), eq(userId))).thenReturn(3);
+        checkPasswordHistoryLength(userId, 0);
+        initializeStorageWithCredential(userId, nonePassword());
+        checkPasswordHistoryLength(userId, 0);
+
+        assertTrue(mService.setLockCredential(newPassword("pass1"), nonePassword(), userId));
+        checkPasswordHistoryLength(userId, 1);
+
+        assertTrue(mService.setLockCredential(newPassword("pass2"), newPassword("pass1"), userId));
+        checkPasswordHistoryLength(userId, 2);
+
+        assertTrue(mService.setLockCredential(newPassword("pass3"), newPassword("pass2"), userId));
+        checkPasswordHistoryLength(userId, 3);
+
+        // maximum length should have been reached
+        assertTrue(mService.setLockCredential(newPassword("pass4"), newPassword("pass3"), userId));
+        checkPasswordHistoryLength(userId, 3);
+    }
+
+    private void checkPasswordHistoryLength(int userId, int expectedLen) {
+        String history = mService.getString(LockPatternUtils.PASSWORD_HISTORY_KEY, "", userId);
+        String[] hashes = TextUtils.split(history, LockPatternUtils.PASSWORD_HISTORY_DELIMITER);
+        assertEquals(expectedLen, hashes.length);
+    }
+
     private void testCreateCredential(int userId, LockscreenCredential credential)
             throws RemoteException {
         assertTrue(mService.setLockCredential(credential, nonePassword(), userId));
@@ -504,11 +546,16 @@
                 badCredential, userId, 0 /* flags */).getResponseCode());
     }
 
-    @SuppressWarnings("GuardedBy") // for initializeSyntheticPasswordLocked
     private void initializeStorageWithCredential(int userId, LockscreenCredential credential)
             throws RemoteException {
         assertEquals(0, mGateKeeperService.getSecureUserId(userId));
-        mService.initializeSyntheticPasswordLocked(credential, userId);
-        assertNotEquals(0, mGateKeeperService.getSecureUserId(userId));
+        synchronized (mService.mSpManager) {
+            mService.initializeSyntheticPasswordLocked(credential, userId);
+        }
+        if (credential.isNone()) {
+            assertEquals(0, mGateKeeperService.getSecureUserId(userId));
+        } else {
+            assertNotEquals(0, mGateKeeperService.getSecureUserId(userId));
+        }
     }
 }
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/MockSyntheticPasswordManager.java b/services/tests/servicestests/src/com/android/server/locksettings/MockSyntheticPasswordManager.java
index 0bb2021..186a04f 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/MockSyntheticPasswordManager.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/MockSyntheticPasswordManager.java
@@ -44,12 +44,14 @@
         mGateKeeper = gatekeeper;
     }
 
-    private ArrayMap<String, byte[]> mBlobs = new ArrayMap<>();
+    private final ArrayMap<String, byte[]> mBlobs = new ArrayMap<>();
 
     @Override
-    protected byte[] decryptSPBlob(String blobKeyName, byte[] blob, byte[] protectorSecret) {
-        if (mBlobs.containsKey(blobKeyName) && !Arrays.equals(mBlobs.get(blobKeyName), blob)) {
-            throw new AssertionFailedError("blobKeyName content is overwritten: " + blobKeyName);
+    protected byte[] decryptSpBlob(String protectorKeyAlias, byte[] blob, byte[] protectorSecret) {
+        if (mBlobs.containsKey(protectorKeyAlias) &&
+                !Arrays.equals(mBlobs.get(protectorKeyAlias), blob)) {
+            throw new AssertionFailedError("Blob was overwritten; protectorKeyAlias="
+                    + protectorKeyAlias);
         }
         ByteBuffer buffer = ByteBuffer.allocate(blob.length);
         buffer.put(blob, 0, blob.length);
@@ -72,7 +74,7 @@
     }
 
     @Override
-    protected byte[] createSPBlob(String blobKeyName, byte[] data, byte[] protectorSecret,
+    protected byte[] createSpBlob(String protectorKeyAlias, byte[] data, byte[] protectorSecret,
             long sid) {
         ByteBuffer buffer = ByteBuffer.allocate(Integer.BYTES + data.length + Integer.BYTES
                 + protectorSecret.length + Long.BYTES);
@@ -82,12 +84,12 @@
         buffer.put(protectorSecret);
         buffer.putLong(sid);
         byte[] result = buffer.array();
-        mBlobs.put(blobKeyName, result);
+        mBlobs.put(protectorKeyAlias, result);
         return result;
     }
 
     @Override
-    protected void destroySPBlobKey(String keyAlias) {
+    protected void destroyProtectorKey(String keyAlias) {
     }
 
     @Override
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/KeySyncTaskTest.java b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/KeySyncTaskTest.java
index d9af51f..c2e83f2 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/KeySyncTaskTest.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/KeySyncTaskTest.java
@@ -54,6 +54,7 @@
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
+import com.android.internal.util.ArrayUtils;
 import com.android.server.locksettings.recoverablekeystore.storage.RecoverableKeyStoreDb;
 import com.android.server.locksettings.recoverablekeystore.storage.RecoverySnapshotStorage;
 
@@ -835,7 +836,7 @@
         byte[] locallyEncryptedKey = SecureBox.decrypt(
                 TestData.getInsecurePrivateKeyForEndpoint1(),
                 /*sharedSecret=*/ KeySyncUtils.calculateThmKfHash(lockScreenHash),
-                /*header=*/ KeySyncUtils.concat(THM_ENCRYPTED_RECOVERY_KEY_HEADER, vaultParams),
+                /*header=*/ ArrayUtils.concat(THM_ENCRYPTED_RECOVERY_KEY_HEADER, vaultParams),
                 encryptedKey
         );
         return KeySyncUtils.decryptRecoveryKey(lockScreenHash, locallyEncryptedKey);
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/KeySyncUtilsTest.java b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/KeySyncUtilsTest.java
index 178fd10..19a606e 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/KeySyncUtilsTest.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/KeySyncUtilsTest.java
@@ -27,6 +27,7 @@
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
+import com.android.internal.util.ArrayUtils;
 import com.google.common.collect.ImmutableMap;
 
 import org.junit.Test;
@@ -117,17 +118,6 @@
     }
 
     @Test
-    public void concat_concatenatesArrays() {
-        assertArrayEquals(
-                utf8Bytes("hello, world!"),
-                KeySyncUtils.concat(
-                        utf8Bytes("hello"),
-                        utf8Bytes(", "),
-                        utf8Bytes("world"),
-                        utf8Bytes("!")));
-    }
-
-    @Test
     public void decryptApplicationKey_decryptsAnApplicationKey_nullMetadata() throws Exception {
         String alias = "phoebe";
         SecretKey recoveryKey = KeySyncUtils.generateRecoveryKey();
@@ -253,7 +243,7 @@
         byte[] encryptedPayload = SecureBox.encrypt(
                 /*theirPublicKey=*/ null,
                 /*sharedSecret=*/ keyClaimant,
-                /*header=*/ KeySyncUtils.concat(RECOVERY_RESPONSE_HEADER, vaultParams),
+                /*header=*/ ArrayUtils.concat(RECOVERY_RESPONSE_HEADER, vaultParams),
                 /*payload=*/ recoveryKey);
 
         byte[] decrypted = KeySyncUtils.decryptRecoveryClaimResponse(
@@ -269,7 +259,7 @@
         byte[] encryptedPayload = SecureBox.encrypt(
                 /*theirPublicKey=*/ null,
                 /*sharedSecret=*/ KeySyncUtils.generateKeyClaimant(),
-                /*header=*/ KeySyncUtils.concat(RECOVERY_RESPONSE_HEADER, vaultParams),
+                /*header=*/ ArrayUtils.concat(RECOVERY_RESPONSE_HEADER, vaultParams),
                 /*payload=*/ recoveryKey);
 
         try {
@@ -298,9 +288,9 @@
         byte[] decrypted = SecureBox.decrypt(
                 keyPair.getPrivate(),
                 /*sharedSecret=*/ null,
-                /*header=*/ KeySyncUtils.concat(RECOVERY_CLAIM_HEADER, vaultParams, challenge),
+                /*header=*/ ArrayUtils.concat(RECOVERY_CLAIM_HEADER, vaultParams, challenge),
                 encryptedRecoveryClaim);
-        assertArrayEquals(KeySyncUtils.concat(LOCK_SCREEN_HASH_1, keyClaimant), decrypted);
+        assertArrayEquals(ArrayUtils.concat(LOCK_SCREEN_HASH_1, keyClaimant), decrypted);
     }
 
     @Test
@@ -320,7 +310,7 @@
             SecureBox.decrypt(
                     keyPair.getPrivate(),
                     /*sharedSecret=*/ null,
-                    /*header=*/ KeySyncUtils.concat(
+                    /*header=*/ ArrayUtils.concat(
                             RECOVERY_CLAIM_HEADER, vaultParams, randomBytes(32)),
                     encryptedRecoveryClaim);
             fail("Should throw if challenge is incorrect.");
@@ -346,7 +336,7 @@
             SecureBox.decrypt(
                     SecureBox.genKeyPair().getPrivate(),
                     /*sharedSecret=*/ null,
-                    /*header=*/ KeySyncUtils.concat(
+                    /*header=*/ ArrayUtils.concat(
                             RECOVERY_CLAIM_HEADER, vaultParams, challenge),
                     encryptedRecoveryClaim);
             fail("Should throw if secret key is incorrect.");
@@ -372,7 +362,7 @@
             SecureBox.decrypt(
                     keyPair.getPrivate(),
                     /*sharedSecret=*/ null,
-                    /*header=*/ KeySyncUtils.concat(
+                    /*header=*/ ArrayUtils.concat(
                             RECOVERY_CLAIM_HEADER, randomBytes(100), challenge),
                     encryptedRecoveryClaim);
             fail("Should throw if vault params is incorrect.");
@@ -399,7 +389,7 @@
             SecureBox.decrypt(
                     keyPair.getPrivate(),
                     /*sharedSecret=*/ null,
-                    /*header=*/ KeySyncUtils.concat(randomBytes(10), vaultParams, challenge),
+                    /*header=*/ ArrayUtils.concat(randomBytes(10), vaultParams, challenge),
                     encryptedRecoveryClaim);
             fail("Should throw if header is incorrect.");
         } catch (AEADBadTagException e) {
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManagerTest.java b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManagerTest.java
index 035249e..aceae61 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManagerTest.java
@@ -58,6 +58,7 @@
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
+import com.android.internal.util.ArrayUtils;
 import com.android.internal.widget.LockPatternUtils;
 import com.android.server.locksettings.recoverablekeystore.storage.ApplicationKeyStorage;
 import com.android.server.locksettings.recoverablekeystore.storage.CleanupManager;
@@ -1287,7 +1288,7 @@
         return SecureBox.encrypt(
                 /*theirPublicKey=*/ null,
                 /*sharedSecret=*/ keyClaimant,
-                /*header=*/ KeySyncUtils.concat(RECOVERY_RESPONSE_HEADER, vaultParams),
+                /*header=*/ ArrayUtils.concat(RECOVERY_RESPONSE_HEADER, vaultParams),
                 /*payload=*/ locallyEncryptedRecoveryKey);
     }
 
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/SecureBoxTest.java b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/SecureBoxTest.java
index 15b0708..34235bd 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/SecureBoxTest.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/SecureBoxTest.java
@@ -27,6 +27,8 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import com.android.internal.util.ArrayUtils;
+
 import java.math.BigInteger;
 import java.nio.charset.StandardCharsets;
 import java.security.InvalidKeyException;
@@ -176,9 +178,9 @@
                 SecureBox.decrypt(
                         THM_PRIVATE_KEY,
                         /*sharedSecret=*/ null,
-                        SecureBox.concat(getBytes("V1 KF_claim"), VAULT_PARAMS, VAULT_CHALLENGE),
+                        ArrayUtils.concat(getBytes("V1 KF_claim"), VAULT_PARAMS, VAULT_CHALLENGE),
                         RECOVERY_CLAIM);
-        assertThat(claimContent).isEqualTo(SecureBox.concat(THM_KF_HASH, KEY_CLAIMANT));
+        assertThat(claimContent).isEqualTo(ArrayUtils.concat(THM_KF_HASH, KEY_CLAIMANT));
     }
 
     @Test
@@ -186,7 +188,7 @@
         SecureBox.decrypt(
                 THM_PRIVATE_KEY,
                 THM_KF_HASH,
-                SecureBox.concat(getBytes("V1 THM_encrypted_recovery_key"), VAULT_PARAMS),
+                ArrayUtils.concat(getBytes("V1 THM_encrypted_recovery_key"), VAULT_PARAMS),
                 ENCRYPTED_RECOVERY_KEY);
     }
 
diff --git a/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsManagerTest.java b/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsManagerTest.java
new file mode 100644
index 0000000..7ae1117
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsManagerTest.java
@@ -0,0 +1,68 @@
+/*
+ * 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.power.stats;
+
+import static androidx.test.InstrumentationRegistry.getContext;
+
+import static org.junit.Assert.fail;
+
+import android.os.BatteryConsumer;
+import android.os.BatteryStatsManager;
+import android.os.BatteryUsageStats;
+import android.os.BatteryUsageStatsQuery;
+import android.os.UidBatteryConsumer;
+
+import org.junit.Test;
+
+/**
+ * Test BatteryStatsManager and CellularBatteryStats to ensure that valid data is being reported
+ * and that invalid data is not reported.
+ */
+public class BatteryStatsManagerTest {
+
+    @Test
+    public void testBatteryUsageStatsDataConsistency() {
+        BatteryStatsManager bsm = getContext().getSystemService(BatteryStatsManager.class);
+        BatteryUsageStats stats = bsm.getBatteryUsageStats(
+                new BatteryUsageStatsQuery.Builder().setMaxStatsAgeMs(
+                        0).includeProcessStateData().build());
+        final int[] components =
+                {BatteryConsumer.POWER_COMPONENT_CPU,
+                        BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO,
+                        BatteryConsumer.POWER_COMPONENT_WIFI,
+                        BatteryConsumer.POWER_COMPONENT_BLUETOOTH};
+        final int[] states =
+                {BatteryConsumer.PROCESS_STATE_FOREGROUND,
+                        BatteryConsumer.PROCESS_STATE_BACKGROUND,
+                        BatteryConsumer.PROCESS_STATE_FOREGROUND_SERVICE,
+                        BatteryConsumer.PROCESS_STATE_CACHED};
+        for (UidBatteryConsumer ubc : stats.getUidBatteryConsumers()) {
+            for (int component : components) {
+                double consumedPower = ubc.getConsumedPower(ubc.getKey(component));
+                double sumStates = 0;
+                for (int state : states) {
+                    sumStates += ubc.getConsumedPower(ubc.getKey(component, state));
+                }
+                if (sumStates > consumedPower + 0.1) {
+                    fail("Sum of states exceeds total. UID = " + ubc.getUid() + " "
+                            + BatteryConsumer.powerComponentIdToString(component)
+                            + " total = " + consumedPower + " states = " + sumStates);
+                }
+            }
+        }
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsTests.java b/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsTests.java
index 1b724d0..807df47 100644
--- a/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsTests.java
+++ b/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsTests.java
@@ -34,6 +34,7 @@
         BatteryStatsHistoryIteratorTest.class,
         BatteryStatsHistoryTest.class,
         BatteryStatsImplTest.class,
+        BatteryStatsManagerTest.class,
         BatteryStatsNoteTest.class,
         BatteryStatsSamplingTimerTest.class,
         BatteryStatsSensorTest.class,
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
index 7c46fd6..5146616 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
@@ -2415,7 +2415,7 @@
         activity.removeImmediately();
     }
 
-    @UseTestDisplay(addWindows = W_ACTIVITY)
+    @SetupWindows(addWindows = W_ACTIVITY)
     @Test
     public void testLandscapeSeascapeRotationByApp() {
         final Task task = new TaskBuilder(mSupervisor)
@@ -2448,14 +2448,14 @@
         appWindow.removeImmediately();
     }
 
-    @UseTestDisplay(addWindows = W_ACTIVITY)
+    @SetupWindows(addWindows = W_ACTIVITY)
     @Test
     public void testLandscapeSeascapeRotationByPolicy() {
         final Task task = new TaskBuilder(mSupervisor)
                 .setDisplay(mDisplayContent).setCreateActivity(true).build();
         final ActivityRecord activity = task.getTopNonFinishingActivity();
-        // This instance has been spied in {@link TestDisplayContent}.
         final DisplayRotation displayRotation = mDisplayContent.getDisplayRotation();
+        spyOn(displayRotation);
 
         final WindowManager.LayoutParams attrs = new WindowManager.LayoutParams(
                 TYPE_BASE_APPLICATION);
@@ -2572,7 +2572,6 @@
         mWm.mDisplayFrozen = false;
     }
 
-    @UseTestDisplay
     @Test
     public void testRespectTopFullscreenOrientation() {
         final ActivityRecord activity = new ActivityBuilder(mAtm).setCreateTask(true).build();
@@ -2595,7 +2594,6 @@
         assertEquals(Configuration.ORIENTATION_LANDSCAPE, activityConfig.orientation);
     }
 
-    @UseTestDisplay
     @Test
     public void testReportOrientationChange() {
         final Task task = new TaskBuilder(mSupervisor)
@@ -3119,7 +3117,7 @@
         assertFalse(app.mActivityRecord.mImeInsetsFrozenUntilStartInput);
     }
 
-    @UseTestDisplay(addWindows = W_INPUT_METHOD)
+    @SetupWindows(addWindows = W_INPUT_METHOD)
     @Test
     public void testImeInsetsFrozenFlag_resetWhenResized() {
         final WindowState app = createWindow(null, TYPE_APPLICATION, "app");
@@ -3137,7 +3135,7 @@
         assertFalse(app.mActivityRecord.mImeInsetsFrozenUntilStartInput);
     }
 
-    @UseTestDisplay(addWindows = W_INPUT_METHOD)
+    @SetupWindows(addWindows = W_INPUT_METHOD)
     @Test
     public void testImeInsetsFrozenFlag_resetWhenNoImeFocusableInActivity() {
         final WindowState app = createWindow(null, TYPE_APPLICATION, "app");
@@ -3165,7 +3163,7 @@
         assertFalse(app.mActivityRecord.mImeInsetsFrozenUntilStartInput);
     }
 
-    @UseTestDisplay(addWindows = W_INPUT_METHOD)
+    @SetupWindows(addWindows = W_INPUT_METHOD)
     @Test
     public void testImeInsetsFrozenFlag_resetWhenReportedToBeImeInputTarget() {
         final WindowState app = createWindow(null, TYPE_APPLICATION, "app");
@@ -3202,7 +3200,7 @@
         assertEquals(state.getSource(ITYPE_IME).getFrame(), imeSource.getFrame());
     }
 
-    @UseTestDisplay(addWindows = {W_ACTIVITY, W_INPUT_METHOD})
+    @SetupWindows(addWindows = { W_ACTIVITY, W_INPUT_METHOD })
     @Test
     public void testImeInsetsFrozenFlag_noDispatchVisibleInsetsWhenAppNotRequest()
             throws RemoteException {
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayAreaTest.java b/services/tests/wmtests/src/com/android/server/wm/DisplayAreaTest.java
index d94e6c9..b87c5a3 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayAreaTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayAreaTest.java
@@ -16,6 +16,7 @@
 
 package com.android.server.wm;
 
+import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE;
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
@@ -399,9 +400,9 @@
                 parentBounds.right / 2, parentBounds.bottom);
         final Rect childBounds2 = new Rect(parentBounds.right / 2, parentBounds.top,
                 parentBounds.right, parentBounds.bottom);
-        TestDisplayArea parentDa = new TestDisplayArea(mWm, parentBounds);
-        TestDisplayArea childDa1 = new TestDisplayArea(mWm, childBounds1);
-        TestDisplayArea childDa2 = new TestDisplayArea(mWm, childBounds2);
+        TestDisplayArea parentDa = new TestDisplayArea(mWm, parentBounds, "Parent");
+        TestDisplayArea childDa1 = new TestDisplayArea(mWm, childBounds1, "Child1");
+        TestDisplayArea childDa2 = new TestDisplayArea(mWm, childBounds2, "Child2");
         parentDa.addChild(childDa1, 0);
         parentDa.addChild(childDa2, 1);
 
@@ -619,9 +620,67 @@
         controller.registerOrganizer(mockDisplayAreaOrganizer, FEATURE_VENDOR_FIRST);
     }
 
+    @Test
+    public void testSetAlwaysOnTop_movesDisplayAreaToTop() {
+        final Rect bounds = new Rect(0, 0, 100, 100);
+        DisplayArea<WindowContainer> parent = new TestDisplayArea(mWm, bounds, "Parent");
+        parent.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW);
+        DisplayArea<WindowContainer> child1 = new TestDisplayArea(mWm, bounds, "Child1");
+        child1.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW);
+        DisplayArea<WindowContainer> child2 = new TestDisplayArea(mWm, bounds, "Child2");
+        child2.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW);
+        parent.addChild(child2, 0);
+        parent.addChild(child1, 1);
+
+        child2.setAlwaysOnTop(true);
+
+        assertEquals(parent.getChildAt(1), child2);
+        assertThat(child2.isAlwaysOnTop()).isTrue();
+    }
+
+    @Test
+    public void testDisplayAreaRequestsTopPosition_alwaysOnTopSiblingExists_doesNotMoveToTop() {
+        final Rect bounds = new Rect(0, 0, 100, 100);
+        DisplayArea<WindowContainer> parent = new TestDisplayArea(mWm, bounds, "Parent");
+        parent.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW);
+        DisplayArea<WindowContainer> alwaysOnTopChild = new TestDisplayArea(mWm, bounds,
+                "AlwaysOnTopChild");
+        alwaysOnTopChild.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW);
+        DisplayArea<WindowContainer> child = new TestDisplayArea(mWm, bounds, "Child");
+        child.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW);
+        parent.addChild(alwaysOnTopChild, 0);
+        parent.addChild(child, 1);
+        alwaysOnTopChild.setAlwaysOnTop(true);
+
+        parent.positionChildAt(POSITION_TOP, child, false /* includingParents */);
+
+        assertEquals(parent.getChildAt(1), alwaysOnTopChild);
+        assertEquals(parent.getChildAt(0), child);
+    }
+
+    @Test
+    public void testAlwaysOnTopDisplayArea_requestsNonTopLocation_doesNotMove() {
+        final Rect bounds = new Rect(0, 0, 100, 100);
+        DisplayArea<WindowContainer> parent = new TestDisplayArea(mWm, bounds, "Parent");
+        parent.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW);
+        DisplayArea<WindowContainer> alwaysOnTopChild = new TestDisplayArea(mWm, bounds,
+                "AlwaysOnTopChild");
+        alwaysOnTopChild.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW);
+        DisplayArea<WindowContainer> child = new TestDisplayArea(mWm, bounds, "Child");
+        child.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW);
+        parent.addChild(alwaysOnTopChild, 0);
+        parent.addChild(child, 1);
+        alwaysOnTopChild.setAlwaysOnTop(true);
+
+        parent.positionChildAt(POSITION_BOTTOM, alwaysOnTopChild, false /* includingParents */);
+
+        assertEquals(parent.getChildAt(1), alwaysOnTopChild);
+        assertEquals(parent.getChildAt(0), child);
+    }
+
     private static class TestDisplayArea<T extends WindowContainer> extends DisplayArea<T> {
-        private TestDisplayArea(WindowManagerService wms, Rect bounds) {
-            super(wms, ANY, "half display area");
+        private TestDisplayArea(WindowManagerService wms, Rect bounds, String name) {
+            super(wms, ANY, name);
             setBounds(bounds);
         }
 
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
index d6b807f..641a3ad 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
@@ -170,7 +170,7 @@
 @RunWith(WindowTestRunner.class)
 public class DisplayContentTests extends WindowTestsBase {
 
-    @UseTestDisplay(addAllCommonWindows = true)
+    @SetupWindows(addAllCommonWindows = true)
     @Test
     public void testForAllWindows() {
         final WindowState exitingAppWindow = createWindow(null, TYPE_BASE_APPLICATION,
@@ -203,7 +203,7 @@
         assertForAllWindowsOrder(windows);
     }
 
-    @UseTestDisplay(addAllCommonWindows = true)
+    @SetupWindows(addAllCommonWindows = true)
     @Test
     public void testForAllWindows_WithAppImeTarget() {
         final WindowState imeAppTarget =
@@ -225,7 +225,7 @@
                 mNavBarWindow));
     }
 
-    @UseTestDisplay(addAllCommonWindows = true)
+    @SetupWindows(addAllCommonWindows = true)
     @Test
     public void testForAllWindows_WithChildWindowImeTarget() throws Exception {
         mDisplayContent.setImeLayeringTarget(mChildAppWindowAbove);
@@ -243,7 +243,7 @@
                 mNavBarWindow));
     }
 
-    @UseTestDisplay(addAllCommonWindows = true)
+    @SetupWindows(addAllCommonWindows = true)
     @Test
     public void testForAllWindows_WithStatusBarImeTarget() throws Exception {
         mDisplayContent.setImeLayeringTarget(mStatusBarWindow);
@@ -261,7 +261,7 @@
                 mNavBarWindow));
     }
 
-    @UseTestDisplay(addAllCommonWindows = true)
+    @SetupWindows(addAllCommonWindows = true)
     @Test
     public void testForAllWindows_WithNotificationShadeImeTarget() throws Exception {
         mDisplayContent.setImeLayeringTarget(mNotificationShadeWindow);
@@ -279,7 +279,7 @@
                 mNavBarWindow));
     }
 
-    @UseTestDisplay(addAllCommonWindows = true)
+    @SetupWindows(addAllCommonWindows = true)
     @Test
     public void testForAllWindows_WithInBetweenWindowToken() {
         // This window is set-up to be z-ordered between some windows that go in the same token like
@@ -301,7 +301,7 @@
                 mNavBarWindow));
     }
 
-    @UseTestDisplay(addAllCommonWindows = true)
+    @SetupWindows(addAllCommonWindows = true)
     @Test
     public void testComputeImeTarget() {
         // Verify that an app window can be an ime target.
@@ -321,7 +321,7 @@
         assertEquals(childWin, imeTarget);
     }
 
-    @UseTestDisplay(addAllCommonWindows = true)
+    @SetupWindows(addAllCommonWindows = true)
     @Test
     public void testComputeImeTarget_startingWindow() {
         ActivityRecord activity = createActivityRecord(mDisplayContent);
@@ -985,7 +985,7 @@
         assertFalse(isOptionsPanelAtRight(landscapeDisplay.getDisplayId()));
     }
 
-    @UseTestDisplay(addWindows = W_INPUT_METHOD)
+    @SetupWindows(addWindows = W_INPUT_METHOD)
     @Test
     public void testInputMethodTargetUpdateWhenSwitchingOnDisplays() {
         final DisplayContent newDisplay = createNewDisplay();
@@ -1020,7 +1020,7 @@
                 mDisplayContent.getImeTarget(IME_TARGET_LAYERING));
     }
 
-    @UseTestDisplay(addWindows = W_INPUT_METHOD)
+    @SetupWindows(addWindows = W_INPUT_METHOD)
     @Test
     public void testInputMethodSet_listenOnDisplayAreaConfigurationChanged() {
         spyOn(mAtm);
@@ -1159,7 +1159,7 @@
         assertEquals(dc.getImeContainer().getParentSurfaceControl(), dc.computeImeParent());
     }
 
-    @UseTestDisplay(addWindows = W_ACTIVITY)
+    @SetupWindows(addWindows = W_ACTIVITY)
     @Test
     public void testComputeImeParent_app_notMatchParentBounds() {
         spyOn(mAppWindow.mActivityRecord);
@@ -1178,7 +1178,7 @@
         assertEquals(dc.getImeContainer().getParentSurfaceControl(), dc.computeImeParent());
     }
 
-    @UseTestDisplay(addWindows = W_ACTIVITY)
+    @SetupWindows(addWindows = W_ACTIVITY)
     @Test
     public void testComputeImeParent_inputTargetNotUpdate() throws Exception {
         WindowState app1 = createWindow(null, TYPE_BASE_APPLICATION, "app1");
@@ -1193,7 +1193,7 @@
         assertNull(mDisplayContent.computeImeParent());
     }
 
-    @UseTestDisplay(addWindows = W_ACTIVITY)
+    @SetupWindows(addWindows = W_ACTIVITY)
     @Test
     public void testComputeImeParent_updateParentWhenTargetNotUseIme() throws Exception {
         WindowState overlay = createWindow(null, TYPE_APPLICATION_OVERLAY, "overlay");
@@ -1268,7 +1268,7 @@
                 dc.computeImeControlTarget());
     }
 
-    @UseTestDisplay(addWindows = W_INPUT_METHOD)
+    @SetupWindows(addWindows = W_INPUT_METHOD)
     @Test
     public void testImeSecureFlagGetUpdatedAfterImeInputTarget() {
         // Verify IME window can get up-to-date secure flag update when the IME input target
@@ -1282,7 +1282,7 @@
         verify(t).setSecure(eq(mDisplayContent.mInputMethodWindow.mSurfaceControl), eq(true));
     }
 
-    @UseTestDisplay(addWindows = W_ACTIVITY)
+    @SetupWindows(addWindows = W_ACTIVITY)
     @Test
     public void testComputeImeControlTarget_notMatchParentBounds() throws Exception {
         spyOn(mAppWindow.mActivityRecord);
@@ -1426,7 +1426,7 @@
         win.setHasSurface(false);
     }
 
-    @UseTestDisplay(addWindows = { W_ABOVE_ACTIVITY, W_ACTIVITY})
+    @SetupWindows(addWindows = { W_ABOVE_ACTIVITY, W_ACTIVITY })
     @Test
     public void testRequestResizeForEmptyFrames() {
         final WindowState win = mChildAppWindowAbove;
@@ -1501,11 +1501,12 @@
         assertNull(displayContent.getAsyncRotationController());
     }
 
-    @UseTestDisplay(addWindows = { W_ACTIVITY, W_WALLPAPER, W_STATUS_BAR, W_NAVIGATION_BAR,
+    @SetupWindows(addWindows = { W_ACTIVITY, W_WALLPAPER, W_STATUS_BAR, W_NAVIGATION_BAR,
             W_INPUT_METHOD, W_NOTIFICATION_SHADE })
     @Test
     public void testApplyTopFixedRotationTransform() {
         final DisplayPolicy displayPolicy = mDisplayContent.getDisplayPolicy();
+        spyOn(displayPolicy);
         // Only non-movable (gesture) navigation bar will be animated by fixed rotation animation.
         doReturn(false).when(displayPolicy).navigationBarCanMove();
         displayPolicy.addWindowLw(mStatusBarWindow, mStatusBarWindow.mAttrs);
@@ -1624,6 +1625,8 @@
         // The display should be rotated after the launch is finished.
         doReturn(false).when(app).isAnimating(anyInt(), anyInt());
         mDisplayContent.mAppTransition.notifyAppTransitionFinishedLocked(app.token);
+        mStatusBarWindow.finishSeamlessRotation(t);
+        mNavBarWindow.finishSeamlessRotation(t);
 
         // The fixed rotation should be cleared and the new rotation is applied to display.
         assertFalse(app.hasFixedRotationTransform());
@@ -1655,7 +1658,7 @@
         assertFalse(mDisplayContent.hasTopFixedRotationLaunchingApp());
     }
 
-    @UseTestDisplay(addWindows = W_ACTIVITY)
+    @SetupWindows(addWindows = W_ACTIVITY)
     @Test
     public void testRotateSeamlesslyWithFixedRotation() {
         final DisplayRotation displayRotation = mDisplayContent.getDisplayRotation();
@@ -2081,7 +2084,7 @@
         verifySizes(dc, forcedWidth, forcedHeight, forcedDensity);
     }
 
-    @UseTestDisplay(addWindows = { W_ACTIVITY, W_INPUT_METHOD })
+    @SetupWindows(addWindows = { W_ACTIVITY, W_INPUT_METHOD })
     @Test
     public void testComputeImeTarget_shouldNotCheckOutdatedImeTargetLayerWhenRemoved() {
         final WindowState child1 = createWindow(mAppWindow, FIRST_SUB_WINDOW, "child1");
@@ -2104,7 +2107,7 @@
         verify(child1, never()).needsRelativeLayeringToIme();
     }
 
-    @UseTestDisplay(addWindows = {W_INPUT_METHOD}, addAllCommonWindows = true)
+    @SetupWindows(addWindows = W_INPUT_METHOD)
     @Test
     public void testAttachAndShowImeScreenshotOnTarget() {
         // Preparation: Simulate screen state is on.
@@ -2155,7 +2158,7 @@
         assertNotNull(mDisplayContent.mImeScreenshot);
     }
 
-    @UseTestDisplay(addWindows = {W_INPUT_METHOD}, addAllCommonWindows = true)
+    @SetupWindows(addWindows = W_INPUT_METHOD)
     @Test
     public void testShowImeScreenshot() {
         final Task rootTask = createTask(mDisplayContent);
@@ -2181,7 +2184,7 @@
         verify(mDisplayContent, never()).showImeScreenshot();
     }
 
-    @UseTestDisplay(addWindows = {W_INPUT_METHOD})
+    @SetupWindows(addWindows = W_INPUT_METHOD)
     @Test
     public void testShowImeScreenshot_removeCurSnapshotBeforeCreateNext() {
         final Task rootTask = createTask(mDisplayContent);
@@ -2471,7 +2474,7 @@
                 ACTIVITY_TYPE_STANDARD));
     }
 
-    @UseTestDisplay(addWindows = W_INPUT_METHOD)
+    @SetupWindows(addWindows = W_INPUT_METHOD)
     @Test
     public void testImeChildWindowFocusWhenImeLayeringTargetChanges() {
         final WindowState imeChildWindow =
@@ -2496,7 +2499,7 @@
         assertNotEquals(imeChildWindow, mDisplayContent.findFocusedWindow());
     }
 
-    @UseTestDisplay(addWindows = W_INPUT_METHOD)
+    @SetupWindows(addWindows = W_INPUT_METHOD)
     @Test
     public void testImeMenuDialogFocusWhenImeLayeringTargetChanges() {
         final WindowState imeMenuDialog =
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyInsetsTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyInsetsTests.java
index f41fee7..2158caf 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyInsetsTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyInsetsTests.java
@@ -36,7 +36,7 @@
 
 @SmallTest
 @Presubmit
-@WindowTestsBase.UseTestDisplay(
+@WindowTestsBase.SetupWindows(
         addWindows = { WindowTestsBase.W_STATUS_BAR, WindowTestsBase.W_NAVIGATION_BAR })
 @RunWith(WindowTestRunner.class)
 public class DisplayPolicyInsetsTests extends DisplayPolicyTestsBase {
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java
index 34575ae..6bdc2e3 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java
@@ -67,7 +67,7 @@
  */
 @SmallTest
 @Presubmit
-@WindowTestsBase.UseTestDisplay(
+@WindowTestsBase.SetupWindows(
         addWindows = { WindowTestsBase.W_STATUS_BAR, WindowTestsBase.W_NAVIGATION_BAR })
 @RunWith(WindowTestRunner.class)
 public class DisplayPolicyLayoutTests extends DisplayPolicyTestsBase {
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java
index 8f2e9b4..9cc665b 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java
@@ -167,7 +167,7 @@
                 dimmingNonImTarget, imeNonDrawNavBar, NAV_BAR_BOTTOM));
     }
 
-    @UseTestDisplay(addWindows = { W_NAVIGATION_BAR })
+    @SetupWindows(addWindows = W_NAVIGATION_BAR)
     @Test
     public void testUpdateLightNavigationBarLw() {
         DisplayPolicy displayPolicy = mDisplayContent.getDisplayPolicy();
@@ -204,7 +204,7 @@
                 APPEARANCE_LIGHT_NAVIGATION_BARS, opaqueLightNavBar));
     }
 
-    @UseTestDisplay(addWindows = {W_ACTIVITY, W_STATUS_BAR})
+    @SetupWindows(addWindows = { W_ACTIVITY, W_STATUS_BAR })
     @Test
     public void testComputeTopFullscreenOpaqueWindow() {
         final WindowManager.LayoutParams attrs = mAppWindow.mAttrs;
@@ -292,7 +292,7 @@
         return win;
     }
 
-    @UseTestDisplay(addWindows = { W_NAVIGATION_BAR, W_INPUT_METHOD })
+    @SetupWindows(addWindows = { W_NAVIGATION_BAR, W_INPUT_METHOD })
     @Test
     public void testImeMinimalSourceFrame() {
         final DisplayPolicy displayPolicy = mDisplayContent.getDisplayPolicy();
@@ -328,7 +328,7 @@
         assertTrue(imeSource.getFrame().contains(navBarSource.getFrame()));
     }
 
-    @UseTestDisplay(addWindows = { W_NAVIGATION_BAR })
+    @SetupWindows(addWindows = W_NAVIGATION_BAR)
     @Test
     public void testInsetsGivenContentFrame() {
         final DisplayPolicy displayPolicy = mDisplayContent.getDisplayPolicy();
diff --git a/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java b/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java
index d3282b9..041f298 100644
--- a/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java
@@ -253,7 +253,7 @@
         assertEquals(ITYPE_NAVIGATION_BAR, panelControls[0].getType());
     }
 
-    @UseTestDisplay(addWindows = W_ACTIVITY)
+    @SetupWindows(addWindows = W_ACTIVITY)
     @Test
     public void testShowTransientBars_bothCanBeTransient_appGetsBothFakeControls() {
         final WindowState statusBar = addNonFocusableWindow(TYPE_STATUS_BAR, "statusBar");
@@ -295,7 +295,7 @@
                 .getSource(ITYPE_NAVIGATION_BAR).isVisible());
     }
 
-    @UseTestDisplay(addWindows = W_ACTIVITY)
+    @SetupWindows(addWindows = W_ACTIVITY)
     @Test
     public void testShowTransientBars_statusBarCanBeTransient_appGetsStatusBarFakeControl() {
         addNonFocusableWindow(TYPE_STATUS_BAR, "statusBar")
@@ -325,7 +325,7 @@
         }
     }
 
-    @UseTestDisplay(addWindows = W_ACTIVITY)
+    @SetupWindows(addWindows = W_ACTIVITY)
     @Test
     public void testAbortTransientBars_bothCanBeAborted_appGetsBothRealControls() {
         final InsetsSource statusBarSource = addNonFocusableWindow(TYPE_STATUS_BAR, "statusBar")
diff --git a/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java
index 6c161cf..fe14d8e 100644
--- a/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java
@@ -131,7 +131,7 @@
         assertNull(app.getInsetsState().peekSource(ITYPE_NAVIGATION_BAR));
     }
 
-    @UseTestDisplay(addWindows = W_INPUT_METHOD)
+    @SetupWindows(addWindows = W_INPUT_METHOD)
     @Test
     public void testStripForDispatch_independentSources() {
         getController().getSourceProvider(ITYPE_IME).setWindowContainer(mImeWindow, null, null);
@@ -148,7 +148,7 @@
                 .isVisible());
     }
 
-    @UseTestDisplay(addWindows = W_INPUT_METHOD)
+    @SetupWindows(addWindows = W_INPUT_METHOD)
     @Test
     public void testStripForDispatch_belowIme() {
         getController().getSourceProvider(ITYPE_IME).setWindowContainer(mImeWindow, null, null);
@@ -161,7 +161,7 @@
         assertTrue(app.getInsetsState().getSource(ITYPE_IME).isVisible());
     }
 
-    @UseTestDisplay(addWindows = W_INPUT_METHOD)
+    @SetupWindows(addWindows = W_INPUT_METHOD)
     @Test
     public void testStripForDispatch_aboveIme() {
         getController().getSourceProvider(ITYPE_IME).setWindowContainer(mImeWindow, null, null);
@@ -173,7 +173,7 @@
                 .isVisible());
     }
 
-    @UseTestDisplay(addWindows = W_INPUT_METHOD)
+    @SetupWindows(addWindows = W_INPUT_METHOD)
     @Test
     public void testStripForDispatch_imeOrderChanged() {
         // This can be the IME z-order target while app cannot be the IME z-order target.
@@ -228,7 +228,7 @@
         assertTrue(app.getInsetsState().getSource(ITYPE_IME).isVisible());
     }
 
-    @UseTestDisplay(addWindows = W_INPUT_METHOD)
+    @SetupWindows(addWindows = W_INPUT_METHOD)
     @Test
     public void testStripForDispatch_childWindow_altFocusable() {
         getController().getSourceProvider(ITYPE_IME).setWindowContainer(mImeWindow, null, null);
@@ -249,7 +249,7 @@
                 .isVisible());
     }
 
-    @UseTestDisplay(addWindows = W_INPUT_METHOD)
+    @SetupWindows(addWindows = W_INPUT_METHOD)
     @Test
     public void testStripForDispatch_childWindow_splitScreen() {
         getController().getSourceProvider(ITYPE_IME).setWindowContainer(mImeWindow, null, null);
@@ -456,7 +456,7 @@
         assertNotNull(app.getInsetsState().peekSource(ITYPE_NAVIGATION_BAR));
     }
 
-    @UseTestDisplay(addWindows = W_INPUT_METHOD)
+    @SetupWindows(addWindows = W_INPUT_METHOD)
     @Test
     public void testGetInsetsHintForNewControl() {
         final WindowState app1 = createTestWindow("app1");
diff --git a/services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java
index 027f521..c548dc3 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java
@@ -749,7 +749,7 @@
         }
     }
 
-    @UseTestDisplay(addWindows = W_INPUT_METHOD)
+    @SetupWindows(addWindows = W_INPUT_METHOD)
     @Test
     public void testLaunchRemoteAnimationWithoutImeBehind() {
         final WindowState win1 = createWindow(null /* parent */, TYPE_BASE_APPLICATION, "testWin1");
diff --git a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
index 324e244..21839aa 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
@@ -61,8 +61,10 @@
 import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.ArgumentMatchers.argThat;
+import static org.mockito.ArgumentMatchers.isNull;
 import static org.mockito.ArgumentMatchers.same;
 import static org.mockito.Mockito.atLeastOnce;
 import static org.mockito.Mockito.clearInvocations;
@@ -82,11 +84,14 @@
 import android.platform.test.annotations.Presubmit;
 import android.provider.DeviceConfig;
 import android.provider.DeviceConfig.Properties;
+import android.view.InsetsVisibilities;
 import android.view.WindowManager;
 
 import androidx.test.filters.MediumTest;
 
 import com.android.internal.policy.SystemBarUtils;
+import com.android.internal.statusbar.LetterboxDetails;
+import com.android.server.statusbar.StatusBarManagerInternal;
 
 import libcore.junit.util.compat.CoreCompatChangeRule.DisableCompatChanges;
 import libcore.junit.util.compat.CoreCompatChangeRule.EnableCompatChanges;
@@ -97,6 +102,7 @@
 import org.junit.Test;
 import org.junit.rules.TestRule;
 import org.junit.runner.RunWith;
+import org.mockito.Mockito;
 
 /**
  * Tests for Size Compatibility mode.
@@ -2113,6 +2119,104 @@
         assertLetterboxSurfacesDrawnBetweenActivityAndParentBounds(organizer.mPrimary.getBounds());
     }
 
+    @Test
+    public void testLetterboxDetailsForStatusBar_noLetterbox() {
+        setUpDisplaySizeWithApp(2800, 1000);
+        addStatusBar(mActivity.mDisplayContent);
+        addWindowToActivity(mActivity); // Add a window to the activity so that we can get an
+        // appearance inside letterboxDetails
+
+        DisplayPolicy displayPolicy = mActivity.getDisplayContent().getDisplayPolicy();
+        StatusBarManagerInternal statusBar = displayPolicy.getStatusBarManagerInternal();
+        // We should get a null LetterboxDetails object as there is no letterboxed activity, so
+        // nothing will get passed to SysUI
+        verify(statusBar, never()).onSystemBarAttributesChanged(anyInt(), anyInt(),
+                any(), anyBoolean(), anyInt(),
+                any(InsetsVisibilities.class), isNull(), isNull());
+
+    }
+
+    @Test
+    public void testLetterboxDetailsForStatusBar_letterboxedForMaxAspectRatio() {
+        setUpDisplaySizeWithApp(2800, 1000);
+        addStatusBar(mActivity.mDisplayContent);
+        addWindowToActivity(mActivity); // Add a window to the activity so that we can get an
+        // appearance inside letterboxDetails
+        // Prepare unresizable activity with max aspect ratio
+        prepareUnresizable(mActivity, /* maxAspect */ 1.1f, SCREEN_ORIENTATION_UNSPECIFIED);
+        // Refresh the letterbox
+        mActivity.mRootWindowContainer.performSurfacePlacement();
+
+        Rect mBounds = new Rect(mActivity.getWindowConfiguration().getBounds());
+        assertEquals(mBounds, new Rect(850, 0, 1950, 1000));
+
+        DisplayPolicy displayPolicy = mActivity.getDisplayContent().getDisplayPolicy();
+        LetterboxDetails[] expectedLetterboxDetails = {new LetterboxDetails(
+                mBounds,
+                mActivity.getDisplayContent().getBounds(),
+                mActivity.findMainWindow().mAttrs.insetsFlags.appearance
+        )};
+
+        // Check that letterboxDetails actually gets passed to SysUI
+        StatusBarManagerInternal statusBar = displayPolicy.getStatusBarManagerInternal();
+        verify(statusBar).onSystemBarAttributesChanged(anyInt(), anyInt(),
+                any(), anyBoolean(), anyInt(),
+                any(InsetsVisibilities.class), isNull(), eq(expectedLetterboxDetails));
+    }
+
+    @Test
+    public void testSplitScreenLetterboxDetailsForStatusBar_twoLetterboxedApps() {
+        mAtm.mDevEnableNonResizableMultiWindow = true;
+        setUpDisplaySizeWithApp(2800, 1000);
+        addStatusBar(mActivity.mDisplayContent);
+        // Create another task for the second activity
+        final Task newTask = new TaskBuilder(mSupervisor).setDisplay(mActivity.getDisplayContent())
+                .setCreateActivity(true).build();
+        ActivityRecord newActivity = newTask.getTopNonFinishingActivity();
+
+        final TestSplitOrganizer organizer =
+                new TestSplitOrganizer(mAtm, mActivity.getDisplayContent());
+
+        // Move first activity to split screen which takes half of the screen.
+        organizer.mPrimary.setBounds(0, 0, 1400, 1000);
+        organizer.putTaskToPrimary(mTask, true);
+        // Move second activity to split screen which takes half of the screen.
+        organizer.mSecondary.setBounds(1400, 0, 2800, 1000);
+        organizer.putTaskToSecondary(newTask, true);
+
+        addWindowToActivity(mActivity); // Add a window to the activity so that we can get an
+        // appearance inside letterboxDetails
+        // Prepare unresizable activity with max aspect ratio
+        prepareUnresizable(mActivity, /* maxAspect */ 1.1f, SCREEN_ORIENTATION_UNSPECIFIED);
+        addWindowToActivity(newActivity);
+        prepareUnresizable(newActivity, /* maxAspect */ 1.1f, SCREEN_ORIENTATION_UNSPECIFIED);
+
+        // Refresh the letterboxes
+        newActivity.mRootWindowContainer.performSurfacePlacement();
+
+        Rect mBounds = new Rect(mActivity.getWindowConfiguration().getBounds());
+        assertEquals(mBounds, new Rect(150, 0, 1250, 1000));
+        final Rect newBounds = new Rect(newActivity.getWindowConfiguration().getBounds());
+        assertEquals(newBounds, new Rect(1550, 0, 2650, 1000));
+
+        DisplayPolicy displayPolicy = mActivity.getDisplayContent().getDisplayPolicy();
+        LetterboxDetails[] expectedLetterboxDetails = { new LetterboxDetails(
+                mBounds,
+                organizer.mPrimary.getBounds(),
+                mActivity.findMainWindow().mAttrs.insetsFlags.appearance
+        ), new LetterboxDetails(
+                newBounds,
+                organizer.mSecondary.getBounds(),
+                newActivity.findMainWindow().mAttrs.insetsFlags.appearance
+        )};
+
+        // Check that letterboxDetails actually gets passed to SysUI
+        StatusBarManagerInternal statusBar = displayPolicy.getStatusBarManagerInternal();
+        verify(statusBar).onSystemBarAttributesChanged(anyInt(), anyInt(),
+                any(), anyBoolean(), anyInt(),
+                any(InsetsVisibilities.class), isNull(), eq(expectedLetterboxDetails));
+    }
+
     private void recomputeNaturalConfigurationOfUnresizableActivity() {
         // Recompute the natural configuration of the non-resizable activity and the split screen.
         mActivity.clearSizeCompatMode();
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java
index 97f0918..eba2755 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java
@@ -420,6 +420,7 @@
     @Test
     public void testApplyTransaction_enforceHierarchyChange_setAdjacentRoots()
             throws RemoteException {
+        mAtm.mTaskFragmentOrganizerController.registerOrganizer(mIOrganizer);
         final TaskFragment taskFragment2 =
                 new TaskFragment(mAtm, new Binder(), true /* createdByOrganizer */);
         final WindowContainerToken token2 = taskFragment2.mRemoteToken.toWindowContainerToken();
@@ -595,6 +596,25 @@
     }
 
     @Test
+    public void testApplyTransaction_skipTransactionForUnregisterOrganizer() {
+        final ActivityRecord ownerActivity = createActivityRecord(mDisplayContent);
+        final IBinder fragmentToken = new Binder();
+
+        // Allow organizer to create TaskFragment and start/reparent activity to TaskFragment.
+        createTaskFragmentFromOrganizer(mTransaction, ownerActivity, fragmentToken);
+        mAtm.mWindowOrganizerController.applyTransaction(mTransaction);
+
+        // Nothing should happen as the organizer is not registered.
+        assertNull(mAtm.mWindowOrganizerController.getTaskFragment(fragmentToken));
+
+        mController.registerOrganizer(mIOrganizer);
+        mAtm.mWindowOrganizerController.applyTransaction(mTransaction);
+
+        // Successfully created when the organizer is registered.
+        assertNotNull(mAtm.mWindowOrganizerController.getTaskFragment(fragmentToken));
+    }
+
+    @Test
     public void testTaskFragmentInPip_startActivityInTaskFragment() {
         setupTaskFragmentInPip();
         final ActivityRecord activity = mTaskFragment.getTopMostActivity();
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotControllerTest.java
index e433684..06a176f 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotControllerTest.java
@@ -190,7 +190,7 @@
         }
     }
 
-    @UseTestDisplay(addWindows = {W_ACTIVITY, W_INPUT_METHOD})
+    @SetupWindows(addWindows = { W_ACTIVITY, W_INPUT_METHOD })
     @Test
     public void testCreateTaskSnapshotWithExcludingIme() {
         Task task = mAppWindow.mActivityRecord.getTask();
@@ -209,7 +209,7 @@
         }
     }
 
-    @UseTestDisplay(addWindows = {W_ACTIVITY, W_INPUT_METHOD})
+    @SetupWindows(addWindows = { W_ACTIVITY, W_INPUT_METHOD })
     @Test
     public void testCreateTaskSnapshotWithIncludingIme() {
         Task task = mAppWindow.mActivityRecord.getTask();
@@ -237,7 +237,7 @@
         }
     }
 
-    @UseTestDisplay(addWindows = W_ACTIVITY)
+    @SetupWindows(addWindows = W_ACTIVITY)
     @Test
     public void testPrepareTaskSnapshot() {
         mAppWindow.mWinAnimator.mLastAlpha = 1f;
diff --git a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
index 4227939..5a2d456 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
@@ -63,9 +63,11 @@
 import android.util.ArraySet;
 import android.view.SurfaceControl;
 import android.window.IDisplayAreaOrganizer;
+import android.window.IRemoteTransition;
 import android.window.ITaskFragmentOrganizer;
 import android.window.ITaskOrganizer;
 import android.window.ITransitionPlayer;
+import android.window.RemoteTransition;
 import android.window.TaskFragmentOrganizer;
 import android.window.TransitionInfo;
 
@@ -416,6 +418,38 @@
     }
 
     @Test
+    public void testRunningRemoteTransition() {
+        final TestTransitionPlayer testPlayer = new TestTransitionPlayer(
+                mAtm.getTransitionController(), mAtm.mWindowOrganizerController);
+        final WindowProcessController playerProc = mSystemServicesTestRule.addProcess(
+                "pkg.player", "proc.player", 5000 /* pid */, 5000 /* uid */);
+        testPlayer.mController.registerTransitionPlayer(testPlayer, playerProc);
+        doReturn(mock(IBinder.class)).when(playerProc.getThread()).asBinder();
+        final WindowProcessController delegateProc = mSystemServicesTestRule.addProcess(
+                "pkg.delegate", "proc.delegate", 6000 /* pid */, 6000 /* uid */);
+        doReturn(mock(IBinder.class)).when(delegateProc.getThread()).asBinder();
+        final ActivityRecord app = new ActivityBuilder(mAtm).setCreateTask(true).build();
+        final TransitionController controller = app.mTransitionController;
+        final Transition transition = controller.createTransition(TRANSIT_OPEN);
+        final RemoteTransition remoteTransition = new RemoteTransition(
+                mock(IRemoteTransition.class));
+        remoteTransition.setAppThread(delegateProc.getThread());
+        transition.collectExistenceChange(app.getTask());
+        controller.requestStartTransition(transition, app.getTask(), remoteTransition,
+                null /* displayChange */);
+        testPlayer.startTransition();
+        testPlayer.onTransactionReady(app.getSyncTransaction());
+        assertTrue(playerProc.isRunningRemoteTransition());
+        assertTrue(delegateProc.isRunningRemoteTransition());
+        assertTrue(controller.mRemotePlayer.reportRunning(delegateProc.getThread()));
+
+        testPlayer.finish();
+        assertFalse(playerProc.isRunningRemoteTransition());
+        assertFalse(delegateProc.isRunningRemoteTransition());
+        assertFalse(controller.mRemotePlayer.reportRunning(delegateProc.getThread()));
+    }
+
+    @Test
     public void testOpenActivityInTheSameTaskWithDisplayChange() {
         final ActivityRecord closing = createActivityRecord(mDisplayContent);
         closing.mVisibleRequested = true;
@@ -864,7 +898,7 @@
         final TransitionController controller = new TransitionController(mAtm, snapshotController,
                 mock(TransitionTracer.class));
         final ITransitionPlayer player = new ITransitionPlayer.Default();
-        controller.registerTransitionPlayer(player, null /* appThread */);
+        controller.registerTransitionPlayer(player, null /* playerProc */);
         final Transition openTransition = controller.createTransition(TRANSIT_OPEN);
 
         // Start out with task2 visible and set up a transition that closes task2 and opens task1
@@ -929,7 +963,7 @@
         final TransitionController controller = new TransitionController(mAtm, snapshotController,
                 mock(TransitionTracer.class));
         final ITransitionPlayer player = new ITransitionPlayer.Default();
-        controller.registerTransitionPlayer(player, null /* appThread */);
+        controller.registerTransitionPlayer(player, null /* playerProc */);
         final Transition openTransition = controller.createTransition(TRANSIT_OPEN);
 
         // Start out with task2 visible and set up a transition that closes task2 and opens task1
@@ -993,7 +1027,7 @@
         final TransitionController controller = new TransitionController(mAtm, snapshotController,
                 mock(TransitionTracer.class));
         final ITransitionPlayer player = new ITransitionPlayer.Default();
-        controller.registerTransitionPlayer(player, null /* appThread */);
+        controller.registerTransitionPlayer(player, null /* playerProc */);
         final Transition openTransition = controller.createTransition(TRANSIT_OPEN);
 
         // Start out with task2 visible and set up a transition that closes task2 and opens task1
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowContainerTraversalTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowContainerTraversalTests.java
index 64959f2..593e983 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowContainerTraversalTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowContainerTraversalTests.java
@@ -45,7 +45,7 @@
 @RunWith(WindowTestRunner.class)
 public class WindowContainerTraversalTests extends WindowTestsBase {
 
-    @UseTestDisplay(addWindows = { W_DOCK_DIVIDER, W_INPUT_METHOD })
+    @SetupWindows(addWindows = { W_DOCK_DIVIDER, W_INPUT_METHOD })
     @Test
     public void testDockedDividerPosition() {
         final WindowState splitScreenWindow = createWindow(null,
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
index 84c2c55..9693532 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
@@ -810,7 +810,6 @@
         assertThat(navigationBarInsetsReceiverTask.mLocalInsetsSourceProviders.size()).isEqualTo(0);
     }
 
-    @UseTestDisplay
     @Test
     public void testTaskInfoCallback() {
         final ArrayList<RunningTaskInfo> lastReportedTiles = new ArrayList<>();
@@ -842,8 +841,7 @@
 
         lastReportedTiles.clear();
         called[0] = false;
-        final Task rootTask2 = createTask(
-                mDisplayContent, WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_HOME);
+        final Task rootTask2 = mDisplayContent.getDefaultTaskDisplayArea().getRootHomeTask();
         wct = new WindowContainerTransaction();
         wct.reparent(rootTask2.mRemoteToken.toWindowContainerToken(),
                 info1.token, true /* onTop */);
@@ -869,7 +867,6 @@
         assertEquals(ACTIVITY_TYPE_UNDEFINED, lastReportedTiles.get(0).topActivityType);
     }
 
-    @UseTestDisplay
     @Test
     public void testHierarchyTransaction() {
         final ArrayMap<IBinder, RunningTaskInfo> lastReportedTiles = new ArrayMap<>();
@@ -890,23 +887,22 @@
         // Ensure events dispatch to organizer.
         mWm.mAtmService.mTaskOrganizerController.dispatchPendingEvents();
 
+        // 2 + 1 (home) = 3
         final int initialRootTaskCount = mWm.mAtmService.mTaskOrganizerController.getRootTasks(
                 mDisplayContent.mDisplayId, null /* activityTypes */).size();
-
         final Task rootTask = createTask(
                 mDisplayContent, WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_STANDARD);
-        final Task rootTask2 = createTask(
-                mDisplayContent, WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_HOME);
 
         // Check getRootTasks works
         List<RunningTaskInfo> roots = mWm.mAtmService.mTaskOrganizerController.getRootTasks(
                 mDisplayContent.mDisplayId, null /* activityTypes */);
-        assertEquals(initialRootTaskCount + 2, roots.size());
+        assertEquals(initialRootTaskCount + 1, roots.size());
 
         lastReportedTiles.clear();
         WindowContainerTransaction wct = new WindowContainerTransaction();
         wct.reparent(rootTask.mRemoteToken.toWindowContainerToken(),
                 info1.token, true /* onTop */);
+        final Task rootTask2 = mDisplayContent.getDefaultTaskDisplayArea().getRootHomeTask();
         wct.reparent(rootTask2.mRemoteToken.toWindowContainerToken(),
                 info2.token, true /* onTop */);
         mWm.mAtmService.mWindowOrganizerController.applyTransaction(wct);
@@ -940,7 +936,8 @@
         // Check that getRootTasks doesn't include children of tiles
         roots = mWm.mAtmService.mTaskOrganizerController.getRootTasks(mDisplayContent.mDisplayId,
                 null /* activityTypes */);
-        assertEquals(initialRootTaskCount, roots.size());
+        // Home (rootTask2) was moved into task1, so only remain 2 roots: task1 and task2.
+        assertEquals(initialRootTaskCount - 1, roots.size());
 
         lastReportedTiles.clear();
         wct = new WindowContainerTransaction();
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
index 638ca05..5c7b882 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
@@ -418,7 +418,7 @@
         assertFalse(app.canAffectSystemUiFlags());
     }
 
-    @UseTestDisplay(addWindows = {W_ACTIVITY, W_STATUS_BAR})
+    @SetupWindows(addWindows = { W_ACTIVITY, W_STATUS_BAR })
     @Test
     public void testVisibleWithInsetsProvider() {
         final WindowState statusBar = mStatusBarWindow;
@@ -694,7 +694,7 @@
         verify(t).setMatrix(child2.mSurfaceControl, w.mInvGlobalScale, 0, 0, w.mInvGlobalScale);
     }
 
-    @UseTestDisplay(addWindows = {W_ABOVE_ACTIVITY, W_NOTIFICATION_SHADE})
+    @SetupWindows(addWindows = { W_ABOVE_ACTIVITY, W_NOTIFICATION_SHADE })
     @Test
     public void testRequestDrawIfNeeded() {
         final WindowState startingApp = createWindow(null /* parent */,
@@ -739,7 +739,7 @@
         assertFalse(startingApp.getOrientationChanging());
     }
 
-    @UseTestDisplay(addWindows = W_ABOVE_ACTIVITY)
+    @SetupWindows(addWindows = W_ABOVE_ACTIVITY)
     @Test
     public void testReportResizedWithRemoteException() {
         final WindowState win = mChildAppWindowAbove;
@@ -769,7 +769,7 @@
         assertFalse(win.getOrientationChanging());
     }
 
-    @UseTestDisplay(addWindows = W_ABOVE_ACTIVITY)
+    @SetupWindows(addWindows = W_ABOVE_ACTIVITY)
     @Test
     public void testRequestResizeForBlastSync() {
         final WindowState win = mChildAppWindowAbove;
@@ -907,7 +907,7 @@
         assertTrue(mAtm.mActiveUids.hasNonAppVisibleWindow(uid));
     }
 
-    @UseTestDisplay(addWindows = {W_ACTIVITY, W_INPUT_METHOD})
+    @SetupWindows(addWindows = { W_ACTIVITY, W_INPUT_METHOD })
     @Test
     public void testNeedsRelativeLayeringToIme_notAttached() {
         WindowState sameTokenWindow = createWindow(null, TYPE_BASE_APPLICATION, mAppWindow.mToken,
@@ -920,7 +920,7 @@
         assertFalse(sameTokenWindow.needsRelativeLayeringToIme());
     }
 
-    @UseTestDisplay(addWindows = {W_ACTIVITY, W_INPUT_METHOD})
+    @SetupWindows(addWindows = { W_ACTIVITY, W_INPUT_METHOD })
     @Test
     public void testNeedsRelativeLayeringToIme_startingWindow() {
         WindowState sameTokenWindow = createWindow(null, TYPE_APPLICATION_STARTING,
@@ -963,7 +963,7 @@
         assertFalse(overlay.getWindowFrames().hasInsetsChanged());
     }
 
-    @UseTestDisplay(addWindows = {W_INPUT_METHOD, W_ACTIVITY})
+    @SetupWindows(addWindows = { W_INPUT_METHOD, W_ACTIVITY })
     @Test
     public void testImeAlwaysReceivesVisibleNavigationBarInsets() {
         final InsetsSource navSource = new InsetsSource(ITYPE_NAVIGATION_BAR);
@@ -1053,7 +1053,7 @@
         assertFalse(app2.getInsetsState().getSource(ITYPE_IME).isVisible());
     }
 
-    @UseTestDisplay(addWindows = {W_ACTIVITY})
+    @SetupWindows(addWindows = W_ACTIVITY)
     @Test
     public void testUpdateImeControlTargetWhenLeavingMultiWindow() {
         WindowState app = createWindow(null, TYPE_BASE_APPLICATION,
@@ -1079,7 +1079,7 @@
         assertEquals(mAppWindow, mDisplayContent.getImeTarget(IME_TARGET_CONTROL).getWindow());
     }
 
-    @UseTestDisplay(addWindows = {W_ACTIVITY, W_INPUT_METHOD, W_NOTIFICATION_SHADE})
+    @SetupWindows(addWindows = { W_ACTIVITY, W_INPUT_METHOD, W_NOTIFICATION_SHADE })
     @Test
     public void testNotificationShadeHasImeInsetsWhenMultiWindow() {
         WindowState app = createWindow(null, TYPE_BASE_APPLICATION,
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
index 0cbf1b2..564d3ca 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
@@ -110,6 +110,7 @@
 import org.junit.runner.Description;
 import org.mockito.Mockito;
 
+import java.lang.annotation.Annotation;
 import java.lang.annotation.ElementType;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -151,7 +152,8 @@
      */
     DisplayContent mDisplayContent;
 
-    // The following fields are only available depending on the usage of annotation UseTestDisplay.
+    // The following fields are only available depending on the usage of annotation UseTestDisplay
+    // and UseCommonWindows.
     WindowState mWallpaperWindow;
     WindowState mImeWindow;
     WindowState mImeDialogWindow;
@@ -214,14 +216,15 @@
         // Only create an additional test display for annotated test class/method because it may
         // significantly increase the execution time.
         final Description description = mSystemServicesTestRule.getDescription();
-        UseTestDisplay testDisplayAnnotation = description.getAnnotation(UseTestDisplay.class);
-        if (testDisplayAnnotation == null) {
-            testDisplayAnnotation = description.getTestClass().getAnnotation(UseTestDisplay.class);
-        }
-        if (testDisplayAnnotation != null) {
-            createTestDisplay(testDisplayAnnotation);
+        final UseTestDisplay useTestDisplay = getAnnotation(description, UseTestDisplay.class);
+        if (useTestDisplay != null) {
+            createTestDisplay(useTestDisplay);
         } else {
             mDisplayContent = mDefaultDisplay;
+            final SetupWindows setupWindows = getAnnotation(description, SetupWindows.class);
+            if (setupWindows != null) {
+                addCommonWindows(setupWindows.addAllCommonWindows(), setupWindows.addWindows());
+            }
         }
 
         // Ensure letterbox aspect ratio is not overridden on any device target.
@@ -290,10 +293,15 @@
     private void createTestDisplay(UseTestDisplay annotation) {
         beforeCreateTestDisplay();
         mDisplayContent = createNewDisplayWithImeSupport(DISPLAY_IME_POLICY_LOCAL);
+        addCommonWindows(annotation.addAllCommonWindows(), annotation.addWindows());
+        mDisplayContent.getInsetsPolicy().setRemoteInsetsControllerControlsSystemBars(false);
 
-        final boolean addAll = annotation.addAllCommonWindows();
-        final @CommonTypes int[] requestedWindows = annotation.addWindows();
+        // Adding a display will cause freezing the display. Make sure to wait until it's
+        // unfrozen to not run into race conditions with the tests.
+        waitUntilHandlersIdle();
+    }
 
+    private void addCommonWindows(boolean addAll, @CommonTypes int[] requestedWindows) {
         if (addAll || ArrayUtils.contains(requestedWindows, W_WALLPAPER)) {
             mWallpaperWindow = createCommonWindow(null, TYPE_WALLPAPER, "wallpaperWindow");
         }
@@ -346,12 +354,6 @@
             mChildAppWindowBelow = createCommonWindow(mAppWindow, TYPE_APPLICATION_MEDIA_OVERLAY,
                     "mChildAppWindowBelow");
         }
-
-        mDisplayContent.getInsetsPolicy().setRemoteInsetsControllerControlsSystemBars(false);
-
-        // Adding a display will cause freezing the display. Make sure to wait until it's
-        // unfrozen to not run into race conditions with the tests.
-        waitUntilHandlersIdle();
     }
 
     private WindowManager.LayoutParams getNavBarLayoutParamsForRotation(int rotation) {
@@ -830,7 +832,7 @@
     TestTransitionPlayer registerTestTransitionPlayer() {
         final TestTransitionPlayer testPlayer = new TestTransitionPlayer(
                 mAtm.getTransitionController(), mAtm.mWindowOrganizerController);
-        testPlayer.mController.registerTransitionPlayer(testPlayer, null /* appThread */);
+        testPlayer.mController.registerTransitionPlayer(testPlayer, null /* playerProc */);
         return testPlayer;
     }
 
@@ -877,11 +879,21 @@
     }
 
     /**
+     * The annotation to provide common windows on default display. This is mutually exclusive
+     * with {@link UseTestDisplay}.
+     */
+    @Target({ ElementType.METHOD, ElementType.TYPE })
+    @Retention(RetentionPolicy.RUNTIME)
+    @interface SetupWindows {
+        boolean addAllCommonWindows() default false;
+        @CommonTypes int[] addWindows() default {};
+    }
+
+    /**
      * The annotation for class and method (higher priority) to create a non-default display that
      * will be assigned to {@link #mDisplayContent}. It is used if the test needs
      * <ul>
      * <li>Pure empty display.</li>
-     * <li>Configured common windows.</li>
      * <li>Independent and customizable orientation.</li>
      * <li>Cross display operation.</li>
      * </ul>
@@ -896,6 +908,12 @@
         @CommonTypes int[] addWindows() default {};
     }
 
+    static <T extends Annotation> T getAnnotation(Description desc, Class<T> type) {
+        final T annotation = desc.getAnnotation(type);
+        if (annotation != null) return annotation;
+        return desc.getTestClass().getAnnotation(type);
+    }
+
     /** Creates and adds a {@link TestDisplayContent} to supervisor at the given position. */
     TestDisplayContent addNewDisplayContentAt(int position) {
         return new TestDisplayContent.Builder(mAtm, 1000, 1500).setPosition(position).build();
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTokenTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowTokenTests.java
index 4b5f330a..3ff791b 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowTokenTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowTokenTests.java
@@ -258,7 +258,7 @@
      * states for its children windows and by default it shouldn't let IME window setting
      * the frozen insets state even the window of the window token is the IME layering target.
      */
-    @UseTestDisplay(addWindows = W_INPUT_METHOD)
+    @SetupWindows(addWindows = W_INPUT_METHOD)
     @Test
     public void testSetInsetsFrozen_notAffectImeWindowState() {
         // Pre-condition: make the IME window be controlled by IME insets provider.
diff --git a/startop/view_compiler/dex_builder_test/AndroidTest.xml b/startop/view_compiler/dex_builder_test/AndroidTest.xml
index 82509b9..59093c7 100644
--- a/startop/view_compiler/dex_builder_test/AndroidTest.xml
+++ b/startop/view_compiler/dex_builder_test/AndroidTest.xml
@@ -21,7 +21,7 @@
         <option name="test-file-name" value="dex-builder-test.apk" />
     </target_preparer>
 
-    <target_preparer class="com.android.compatibility.common.tradefed.targetprep.FilePusher">
+    <target_preparer class="com.android.tradefed.targetprep.PushFilePreparer">
         <option name="cleanup" value="true" />
         <option name="push" value="trivial.dex->/data/local/tmp/dex-builder-test/trivial.dex" />
         <option name="push" value="simple.dex->/data/local/tmp/dex-builder-test/simple.dex" />
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index 0bf4088..6deb6f8 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -39,7 +39,6 @@
 import android.telecom.TelecomManager;
 import android.telephony.AccessNetworkConstants.AccessNetworkType;
 import android.telephony.data.ApnSetting;
-import android.telephony.data.DataCallResponse;
 import android.telephony.gba.TlsParams;
 import android.telephony.gba.UaSecurityProtocolIdentifier;
 import android.telephony.ims.ImsReasonInfo;
@@ -1126,27 +1125,6 @@
     public static final String KEY_DEFAULT_MTU_INT = "default_mtu_int";
 
     /**
-     * The data call retry configuration for different types of APN.
-     * @hide
-     */
-    public static final String KEY_CARRIER_DATA_CALL_RETRY_CONFIG_STRINGS =
-            "carrier_data_call_retry_config_strings";
-
-    /**
-     * Delay in milliseconds between trying APN from the pool
-     * @hide
-     */
-    public static final String KEY_CARRIER_DATA_CALL_APN_DELAY_DEFAULT_LONG =
-            "carrier_data_call_apn_delay_default_long";
-
-    /**
-     * Faster delay in milliseconds between trying APN from the pool
-     * @hide
-     */
-    public static final String KEY_CARRIER_DATA_CALL_APN_DELAY_FASTER_LONG =
-            "carrier_data_call_apn_delay_faster_long";
-
-    /**
      * Delay in milliseconds for retrying APN after disconnect
      * @hide
      */
@@ -1154,21 +1132,6 @@
             "carrier_data_call_apn_retry_after_disconnect_long";
 
     /**
-     * The maximum times for telephony to retry data setup on the same APN requested by
-     * network through the data setup response retry timer
-     * {@link DataCallResponse#getRetryDurationMillis()}. This is to prevent that network keeps
-     * asking device to retry data setup forever and causes power consumption issue. For infinite
-     * retring same APN, configure this as 2147483647 (i.e. {@link Integer#MAX_VALUE}).
-     *
-     * Note if network does not suggest any retry timer, frameworks uses the retry configuration
-     * from {@link #KEY_CARRIER_DATA_CALL_RETRY_CONFIG_STRINGS}, and the maximum retry times could
-     * be configured there.
-     * @hide
-     */
-    public static final String KEY_CARRIER_DATA_CALL_RETRY_NETWORK_REQUESTED_MAX_COUNT_INT =
-            "carrier_data_call_retry_network_requested_max_count_int";
-
-    /**
      * Data call setup permanent failure causes by the carrier
      */
     public static final String KEY_CARRIER_DATA_CALL_PERMANENT_FAILURE_STRINGS =
@@ -1188,19 +1151,6 @@
             "carrier_metered_roaming_apn_types_strings";
 
     /**
-     * APN types that are not allowed on cellular
-     * @hide
-     */
-    public static final String KEY_CARRIER_WWAN_DISALLOWED_APN_TYPES_STRING_ARRAY =
-            "carrier_wwan_disallowed_apn_types_string_array";
-
-    /**
-     * APN types that are not allowed on IWLAN
-     * @hide
-     */
-    public static final String KEY_CARRIER_WLAN_DISALLOWED_APN_TYPES_STRING_ARRAY =
-            "carrier_wlan_disallowed_apn_types_string_array";
-    /**
      * CDMA carrier ERI (Enhanced Roaming Indicator) file name
      * @hide
      */
@@ -8419,7 +8369,6 @@
      * "1800000, maximum_retries=20" means for those capabilities, retry happens in 2.5s, 3s, 5s,
      * 10s, 15s, 20s, 40s, 1m, 2m, 4m, 10m, 20m, 30m, 30m, 30m, until reaching 20 retries.
      *
-     * // TODO: remove KEY_CARRIER_DATA_CALL_RETRY_CONFIG_STRINGS
      * @hide
      */
     public static final String KEY_TELEPHONY_DATA_SETUP_RETRY_RULES_STRING_ARRAY =
@@ -8814,27 +8763,13 @@
         sDefaults.putBoolean(KEY_BROADCAST_EMERGENCY_CALL_STATE_CHANGES_BOOL, false);
         sDefaults.putBoolean(KEY_ALWAYS_SHOW_EMERGENCY_ALERT_ONOFF_BOOL, false);
         sDefaults.putInt(KEY_DEFAULT_MTU_INT, 1500);
-        sDefaults.putStringArray(KEY_CARRIER_DATA_CALL_RETRY_CONFIG_STRINGS, new String[]{
-                "default:default_randomization=2000,5000,10000,20000,40000,80000:5000,160000:5000,"
-                        + "320000:5000,640000:5000,1280000:5000,1800000:5000",
-                "mms:default_randomization=2000,5000,10000,20000,40000,80000:5000,160000:5000,"
-                        + "320000:5000,640000:5000,1280000:5000,1800000:5000",
-                "ims:max_retries=10, 5000, 5000, 5000",
-                "others:max_retries=3, 5000, 5000, 5000"});
-        sDefaults.putLong(KEY_CARRIER_DATA_CALL_APN_DELAY_DEFAULT_LONG, 20000);
-        sDefaults.putLong(KEY_CARRIER_DATA_CALL_APN_DELAY_FASTER_LONG, 3000);
         sDefaults.putLong(KEY_CARRIER_DATA_CALL_APN_RETRY_AFTER_DISCONNECT_LONG, 3000);
-        sDefaults.putInt(KEY_CARRIER_DATA_CALL_RETRY_NETWORK_REQUESTED_MAX_COUNT_INT, 3);
         sDefaults.putString(KEY_CARRIER_ERI_FILE_NAME_STRING, "eri.xml");
         sDefaults.putInt(KEY_DURATION_BLOCKING_DISABLED_AFTER_EMERGENCY_INT, 7200);
         sDefaults.putStringArray(KEY_CARRIER_METERED_APN_TYPES_STRINGS,
                 new String[]{"default", "mms", "dun", "supl"});
         sDefaults.putStringArray(KEY_CARRIER_METERED_ROAMING_APN_TYPES_STRINGS,
                 new String[]{"default", "mms", "dun", "supl"});
-        sDefaults.putStringArray(KEY_CARRIER_WWAN_DISALLOWED_APN_TYPES_STRING_ARRAY,
-                new String[]{""});
-        sDefaults.putStringArray(KEY_CARRIER_WLAN_DISALLOWED_APN_TYPES_STRING_ARRAY,
-                new String[]{""});
         sDefaults.putIntArray(KEY_ONLY_SINGLE_DC_ALLOWED_INT_ARRAY,
                 new int[] {TelephonyManager.NETWORK_TYPE_CDMA, TelephonyManager.NETWORK_TYPE_1xRTT,
                         TelephonyManager.NETWORK_TYPE_EVDO_0, TelephonyManager.NETWORK_TYPE_EVDO_A,
diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl
index 0ce6b14..da1ffcd 100644
--- a/telephony/java/com/android/internal/telephony/ITelephony.aidl
+++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl
@@ -2510,9 +2510,6 @@
     CellIdentity getLastKnownCellIdentity(int subId, String callingPackage,
             String callingFeatureId);
 
-    /** Check if telephony new data stack is enabled. */
-    boolean isUsingNewDataStack();
-
     /**
      *  @return true if the modem service is set successfully, false otherwise.
      */
diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ImeEditorPopupDialogActivity.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ImeEditorPopupDialogActivity.java
index a8613f5..95f933f 100644
--- a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ImeEditorPopupDialogActivity.java
+++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ImeEditorPopupDialogActivity.java
@@ -16,6 +16,8 @@
 
 package com.android.server.wm.flicker.testapp;
 
+import static android.view.WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN;
+
 import android.app.Activity;
 import android.app.AlertDialog;
 import android.os.Bundle;
@@ -30,6 +32,7 @@
         WindowManager.LayoutParams p = getWindow().getAttributes();
         p.layoutInDisplayCutoutMode = WindowManager.LayoutParams
                 .LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES;
+        p.softInputMode = SOFT_INPUT_STATE_ALWAYS_HIDDEN;
         getWindow().setAttributes(p);
         LinearLayout layout = new LinearLayout(this);
         layout.setOrientation(LinearLayout.VERTICAL);