Merge "Add CUJ for dismiss split-screen by divider"
diff --git a/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java b/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java
index 5d9f335..f1c4eb4 100644
--- a/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java
+++ b/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java
@@ -1543,8 +1543,10 @@
     @Override
     @StandbyBuckets public int getAppStandbyBucket(String packageName, int userId,
             long elapsedRealtime, boolean shouldObfuscateInstantApps) {
-        if (!mAppIdleEnabled || (shouldObfuscateInstantApps
-                && mInjector.isPackageEphemeral(userId, packageName))) {
+        if (!mAppIdleEnabled) {
+            return STANDBY_BUCKET_EXEMPTED;
+        }
+        if (shouldObfuscateInstantApps && mInjector.isPackageEphemeral(userId, packageName)) {
             return STANDBY_BUCKET_ACTIVE;
         }
 
diff --git a/boot/hiddenapi/hiddenapi-max-target-r-loprio.txt b/boot/hiddenapi/hiddenapi-max-target-r-loprio.txt
index dbc3b51..f5184e7 100644
--- a/boot/hiddenapi/hiddenapi-max-target-r-loprio.txt
+++ b/boot/hiddenapi/hiddenapi-max-target-r-loprio.txt
@@ -39,7 +39,6 @@
 Lcom/android/ims/internal/uce/presence/IPresenceService$Stub;-><init>()V
 Lcom/android/ims/internal/uce/uceservice/IUceListener$Stub;-><init>()V
 Lcom/android/ims/internal/uce/uceservice/IUceService$Stub;-><init>()V
-Lcom/android/internal/app/IVoiceInteractionManagerService$Stub$Proxy;->showSessionFromSession(Landroid/os/IBinder;Landroid/os/Bundle;I)Z
 Lcom/android/internal/appwidget/IAppWidgetService$Stub;->TRANSACTION_bindAppWidgetId:I
 Lcom/android/internal/telephony/ITelephony$Stub;->DESCRIPTOR:Ljava/lang/String;
 Lcom/android/internal/telephony/ITelephony$Stub;->TRANSACTION_dial:I
diff --git a/core/api/current.txt b/core/api/current.txt
index bd2a438..dc54661 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -18904,6 +18904,7 @@
     method public void onUnbindInput();
     method @Deprecated public void onUpdateCursor(android.graphics.Rect);
     method public void onUpdateCursorAnchorInfo(android.view.inputmethod.CursorAnchorInfo);
+    method public void onUpdateEditorToolType(int);
     method public void onUpdateExtractedText(int, android.view.inputmethod.ExtractedText);
     method public void onUpdateExtractingViews(android.view.inputmethod.EditorInfo);
     method public void onUpdateExtractingVisibility(android.view.inputmethod.EditorInfo);
@@ -45091,6 +45092,7 @@
     ctor protected Layout(CharSequence, android.text.TextPaint, int, android.text.Layout.Alignment, float, float);
     method public void draw(android.graphics.Canvas);
     method public void draw(android.graphics.Canvas, android.graphics.Path, android.graphics.Paint, int);
+    method public void fillCharacterBounds(@IntRange(from=0) int, @IntRange(from=0) int, @NonNull float[], @IntRange(from=0) int);
     method public final android.text.Layout.Alignment getAlignment();
     method public abstract int getBottomPadding();
     method public void getCursorPath(int, android.graphics.Path, CharSequence);
@@ -52967,9 +52969,11 @@
     method @Nullable public android.view.inputmethod.SurroundingText getInitialSurroundingText(@IntRange(from=0) int, @IntRange(from=0) int, int);
     method @Nullable public CharSequence getInitialTextAfterCursor(@IntRange(from=0) int, int);
     method @Nullable public CharSequence getInitialTextBeforeCursor(@IntRange(from=0) int, int);
+    method public int getInitialToolType();
     method public final void makeCompatible(int);
     method public void setInitialSurroundingSubText(@NonNull CharSequence, int);
     method public void setInitialSurroundingText(@NonNull CharSequence);
+    method public void setInitialToolType(int);
     method public void writeToParcel(android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.view.inputmethod.EditorInfo> CREATOR;
     field public static final int IME_ACTION_DONE = 6; // 0x6
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index fccb3c0..09d4ba6 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -670,6 +670,7 @@
 
   public final class UsageStatsManager {
     method public void forceUsageSourceSettingRead();
+    method public boolean isAppStandbyEnabled();
   }
 
 }
@@ -1796,6 +1797,7 @@
   }
 
   public final class PowerManager {
+    method public boolean areAutoPowerSaveModesEnabled();
     method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_LOW_POWER_STANDBY, android.Manifest.permission.DEVICE_POWER}) public void forceLowPowerStandbyActive(boolean);
     field public static final String ACTION_ENHANCED_DISCHARGE_PREDICTION_CHANGED = "android.os.action.ENHANCED_DISCHARGE_PREDICTION_CHANGED";
     field @RequiresPermission(android.Manifest.permission.DEVICE_POWER) public static final int SYSTEM_WAKELOCK = -2147483648; // 0x80000000
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/ActivityOptions.java b/core/java/android/app/ActivityOptions.java
index 1a06c6f..f362204 100644
--- a/core/java/android/app/ActivityOptions.java
+++ b/core/java/android/app/ActivityOptions.java
@@ -1453,6 +1453,11 @@
     }
 
     /** @hide */
+    public void setRemoteTransition(@Nullable RemoteTransition remoteTransition) {
+        mRemoteTransition = remoteTransition;
+    }
+
+    /** @hide */
     public static ActivityOptions fromBundle(Bundle bOptions) {
         return bOptions != null ? new ActivityOptions(bOptions) : null;
     }
diff --git a/core/java/android/app/IActivityTaskManager.aidl b/core/java/android/app/IActivityTaskManager.aidl
index 0bf16a0..3459d0e 100644
--- a/core/java/android/app/IActivityTaskManager.aidl
+++ b/core/java/android/app/IActivityTaskManager.aidl
@@ -231,7 +231,7 @@
             in IBinder activityToken, int flags);
     boolean isAssistDataAllowedOnCurrentActivity();
     boolean requestAssistDataForTask(in IAssistDataReceiver receiver, int taskId,
-            in String callingPackageName);
+            in String callingPackageName, String callingAttributionTag);
 
     /**
      * Notify the system that the keyguard is going away.
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/app/prediction/AppPredictor.java b/core/java/android/app/prediction/AppPredictor.java
index db3a192..2581daa 100644
--- a/core/java/android/app/prediction/AppPredictor.java
+++ b/core/java/android/app/prediction/AppPredictor.java
@@ -74,7 +74,6 @@
 
     private static final String TAG = AppPredictor.class.getSimpleName();
 
-
     private final IPredictionManager mPredictionManager;
     private final CloseGuard mCloseGuard = CloseGuard.get();
     private final AtomicBoolean mIsClosed = new AtomicBoolean(false);
@@ -82,8 +81,6 @@
     private final AppPredictionSessionId mSessionId;
     private final ArrayMap<Callback, CallbackWrapper> mRegisteredCallbacks = new ArrayMap<>();
 
-    private final IBinder mToken = new Binder();
-
     /**
      * Creates a new Prediction client.
      * <p>
@@ -99,7 +96,7 @@
         mSessionId = new AppPredictionSessionId(
                 context.getPackageName() + ":" + UUID.randomUUID().toString(), context.getUserId());
         try {
-            mPredictionManager.createPredictionSession(predictionContext, mSessionId, mToken);
+            mPredictionManager.createPredictionSession(predictionContext, mSessionId, getToken());
         } catch (RemoteException e) {
             Log.e(TAG, "Failed to create predictor", e);
             e.rethrowAsRuntimeException();
@@ -324,4 +321,12 @@
             }
         }
     }
+
+    private static class Token {
+        static final IBinder sBinder = new Binder(TAG);
+    }
+
+    private static IBinder getToken() {
+        return Token.sBinder;
+    }
 }
diff --git a/core/java/android/app/smartspace/SmartspaceSession.java b/core/java/android/app/smartspace/SmartspaceSession.java
index b523be2..3658ffc 100644
--- a/core/java/android/app/smartspace/SmartspaceSession.java
+++ b/core/java/android/app/smartspace/SmartspaceSession.java
@@ -83,7 +83,6 @@
     private final SmartspaceSessionId mSessionId;
     private final ArrayMap<OnTargetsAvailableListener, CallbackWrapper> mRegisteredCallbacks =
             new ArrayMap<>();
-    private final IBinder mToken = new Binder();
 
     /**
      * Creates a new Smartspace ui client.
@@ -101,7 +100,7 @@
         mSessionId = new SmartspaceSessionId(
                 context.getPackageName() + ":" + UUID.randomUUID().toString(), context.getUser());
         try {
-            mInterface.createSmartspaceSession(smartspaceConfig, mSessionId, mToken);
+            mInterface.createSmartspaceSession(smartspaceConfig, mSessionId, getToken());
         } catch (RemoteException e) {
             Log.e(TAG, "Failed to create Smartspace session", e);
             e.rethrowFromSystemServer();
@@ -283,4 +282,12 @@
             }
         }
     }
+
+    private static class Token {
+        static final IBinder sBinder = new Binder(TAG);
+    }
+
+    private static IBinder getToken() {
+        return Token.sBinder;
+    }
 }
diff --git a/core/java/android/app/usage/IUsageStatsManager.aidl b/core/java/android/app/usage/IUsageStatsManager.aidl
index cc78f1a..4295517 100644
--- a/core/java/android/app/usage/IUsageStatsManager.aidl
+++ b/core/java/android/app/usage/IUsageStatsManager.aidl
@@ -44,6 +44,7 @@
     UsageEvents queryEventsForPackageForUser(long beginTime, long endTime, int userId, String pkg, String callingPackage);
     @UnsupportedAppUsage(maxTargetSdk = 30, trackingBug = 170729553)
     void setAppInactive(String packageName, boolean inactive, int userId);
+    boolean isAppStandbyEnabled();
     @UnsupportedAppUsage(maxTargetSdk = 30, trackingBug = 170729553)
     boolean isAppInactive(String packageName, int userId, String callingPackage);
     void onCarrierPrivilegedAppsChanged();
diff --git a/core/java/android/app/usage/UsageStatsManager.java b/core/java/android/app/usage/UsageStatsManager.java
index 1dfc7d4..4b0ca28 100644
--- a/core/java/android/app/usage/UsageStatsManager.java
+++ b/core/java/android/app/usage/UsageStatsManager.java
@@ -642,6 +642,19 @@
     }
 
     /**
+     * Returns whether the app standby bucket feature is enabled.
+     * @hide
+     */
+    @TestApi
+    public boolean isAppStandbyEnabled() {
+        try {
+            return mService.isAppStandbyEnabled();
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
      * Returns whether the specified app is currently considered inactive. This will be true if the
      * app hasn't been used directly or indirectly for a period of time defined by the system. This
      * could be of the order of several hours or days. Apps are not considered inactive when the
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/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/inputmethodservice/IInputMethodWrapper.java b/core/java/android/inputmethodservice/IInputMethodWrapper.java
index e960df1..3d59387 100644
--- a/core/java/android/inputmethodservice/IInputMethodWrapper.java
+++ b/core/java/android/inputmethodservice/IInputMethodWrapper.java
@@ -80,6 +80,7 @@
     private static final int DO_START_STYLUS_HANDWRITING = 110;
     private static final int DO_INIT_INK_WINDOW = 120;
     private static final int DO_FINISH_STYLUS_HANDWRITING = 130;
+    private static final int DO_UPDATE_TOOL_TYPE = 140;
 
     final WeakReference<InputMethodServiceInternal> mTarget;
     final Context mContext;
@@ -234,6 +235,10 @@
                 inputMethod.canStartStylusHandwriting(msg.arg1);
                 return;
             }
+            case DO_UPDATE_TOOL_TYPE: {
+                inputMethod.updateEditorToolType(msg.arg1);
+                return;
+            }
             case DO_START_STYLUS_HANDWRITING: {
                 final SomeArgs args = (SomeArgs) msg.obj;
                 inputMethod.startStylusHandwriting(msg.arg1, (InputChannel) args.arg1,
@@ -402,6 +407,14 @@
 
     @BinderThread
     @Override
+    public void updateEditorToolType(int toolType)
+            throws RemoteException {
+        mCaller.executeOrSendMessage(
+                mCaller.obtainMessageI(DO_UPDATE_TOOL_TYPE, toolType));
+    }
+
+    @BinderThread
+    @Override
     public void startStylusHandwriting(int requestId, @NonNull InputChannel channel,
             @Nullable List<MotionEvent> stylusEvents)
             throws RemoteException {
diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java
index b108490..c2027b1 100644
--- a/core/java/android/inputmethodservice/InputMethodService.java
+++ b/core/java/android/inputmethodservice/InputMethodService.java
@@ -953,6 +953,15 @@
          * @hide
          */
         @Override
+        public void updateEditorToolType(int toolType) {
+            onUpdateEditorToolType(toolType);
+        }
+
+        /**
+         * {@inheritDoc}
+         * @hide
+         */
+        @Override
         public void canStartStylusHandwriting(int requestId) {
             if (DEBUG) Log.v(TAG, "canStartStylusHandwriting()");
             if (mHandwritingRequestId.isPresent()) {
@@ -3012,11 +3021,14 @@
      * not call to inform the IME of this interaction.
      * @param focusChanged true if the user changed the focused view by this click.
      * @see InputMethodManager#viewClicked(View)
+     * @see #onUpdateEditorToolType(int)
      * @deprecated The method may not be called for composite {@link View} that works as a giant
      *             "Canvas", which can host its own UI hierarchy and sub focus state.
      *             {@link android.webkit.WebView} is a good example. Application / IME developers
      *             should not rely on this method. If your goal is just being notified when an
      *             on-going input is interrupted, simply monitor {@link #onFinishInput()}.
+     *             If your goal is to know what {@link MotionEvent#getToolType(int)} clicked on
+     *             editor, use {@link #onUpdateEditorToolType(int)} instead.
      */
     @Deprecated
     public void onViewClicked(boolean focusChanged) {
@@ -3024,6 +3036,19 @@
     }
 
     /**
+     * Called when the user tapped or clicked an {@link android.widget.Editor}.
+     * This can be useful when IME makes a decision of showing Virtual keyboard based on what
+     * {@link MotionEvent#getToolType(int)} was used to click the editor.
+     * e.g. when toolType is {@link MotionEvent#TOOL_TYPE_STYLUS}, IME may choose to show a
+     * companion widget instead of normal virtual keyboard.
+     * <p> Default implementation does nothing. </p>
+     * @param toolType what {@link MotionEvent#getToolType(int)} was used to click on editor.
+     */
+    public void onUpdateEditorToolType(int toolType) {
+        // Intentionally empty
+    }
+
+    /**
      * Called when the application has reported a new location of its text
      * cursor.  This is only called if explicitly requested by the input method.
      * The default implementation does nothing.
diff --git a/core/java/android/os/IPowerManager.aidl b/core/java/android/os/IPowerManager.aidl
index 4fe6524..f1e3ab0 100644
--- a/core/java/android/os/IPowerManager.aidl
+++ b/core/java/android/os/IPowerManager.aidl
@@ -53,6 +53,7 @@
     float getBrightnessConstraint(int constraint);
     @UnsupportedAppUsage
     boolean isInteractive();
+    boolean areAutoPowerSaveModesEnabled();
     boolean isPowerSaveMode();
     PowerSaveState getPowerSaveState(int serviceType);
     boolean setPowerSaveModeEnabled(boolean mode);
diff --git a/core/java/android/os/Parcel.java b/core/java/android/os/Parcel.java
index aa61558..d553132 100644
--- a/core/java/android/os/Parcel.java
+++ b/core/java/android/os/Parcel.java
@@ -767,7 +767,7 @@
     }
 
     /**
-     * Set the bytes in data to be the raw bytes of this Parcel.
+     * Fills the raw bytes of this Parcel with the supplied data.
      */
     public final void unmarshall(@NonNull byte[] data, int offset, int length) {
         nativeUnmarshall(mNativePtr, data, offset, length);
diff --git a/core/java/android/os/PowerManager.java b/core/java/android/os/PowerManager.java
index 3063d3a..01b75d1 100644
--- a/core/java/android/os/PowerManager.java
+++ b/core/java/android/os/PowerManager.java
@@ -1810,6 +1810,21 @@
     }
 
     /**
+     * Returns true if the platform has auto power save modes (eg. Doze & app standby) enabled.
+     * This doesn't necessarily mean that the individual features are enabled. For example, if this
+     * returns true, Doze might be enabled while app standby buckets remain disabled.
+     * @hide
+     */
+    @TestApi
+    public boolean areAutoPowerSaveModesEnabled() {
+        try {
+            return mService.areAutoPowerSaveModesEnabled();
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
      * Returns true if the device is currently in power save mode.  When in this mode,
      * applications should reduce their functionality in order to conserve battery as
      * much as possible.  You can monitor for changes to this state with
diff --git a/core/java/android/provider/DeviceConfig.java b/core/java/android/provider/DeviceConfig.java
index 063762c7..9cd2325 100644
--- a/core/java/android/provider/DeviceConfig.java
+++ b/core/java/android/provider/DeviceConfig.java
@@ -761,6 +761,13 @@
      */
     public static final String NAMESPACE_MEMORY_SAFETY_NATIVE = "memory_safety_native";
 
+    /**
+     * Namespace for wear OS platform features.
+     *
+     * @hide
+     */
+    public static final String NAMESPACE_WEAR = "wear";
+
     private static final Object sLock = new Object();
     @GuardedBy("sLock")
     private static ArrayMap<OnPropertiesChangedListener, Pair<String, Executor>> sListeners =
diff --git a/core/java/android/service/voice/VoiceInteractionManagerInternal.java b/core/java/android/service/voice/VoiceInteractionManagerInternal.java
index b20dccc..c47fdd3 100644
--- a/core/java/android/service/voice/VoiceInteractionManagerInternal.java
+++ b/core/java/android/service/voice/VoiceInteractionManagerInternal.java
@@ -16,7 +16,9 @@
 
 package android.service.voice;
 
+import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.UserIdInt;
 import android.os.Bundle;
 import android.os.IBinder;
 
@@ -32,9 +34,12 @@
      * Start a new voice interaction session when requested from within an activity
      * by Activity.startLocalVoiceInteraction()
      * @param callingActivity The binder token representing the calling activity.
-     * @param options 
+     * @param attributionTag The attribution tag of the calling context or {@code null} for default
+     *                       attribution
+     * @param options A Bundle of private arguments to the current voice interaction service
      */
-    public abstract void startLocalVoiceInteraction(IBinder callingActivity, Bundle options);
+    public abstract void startLocalVoiceInteraction(@NonNull IBinder callingActivity,
+            @Nullable String attributionTag, @NonNull Bundle options);
 
     /**
      * Returns whether the currently selected voice interaction service supports local voice
@@ -65,6 +70,13 @@
     public abstract HotwordDetectionServiceIdentity getHotwordDetectionServiceIdentity();
 
     /**
+     * Called by {@code UMS.convertPreCreatedUserIfPossible()} when a new user is not created from
+     * scratched, but converted from the pool of existing pre-created users.
+     */
+    // TODO(b/226201975): remove method once RoleService supports pre-created users
+    public abstract void onPreCreatedUserConversion(@UserIdInt int userId);
+
+    /**
      * Provides the uids of the currently active
      * {@link android.service.voice.HotwordDetectionService} and its owning package. The
      * HotwordDetectionService is an isolated service, so it has a separate uid.
diff --git a/core/java/android/service/voice/VoiceInteractionService.java b/core/java/android/service/voice/VoiceInteractionService.java
index 1170237..1b46107 100644
--- a/core/java/android/service/voice/VoiceInteractionService.java
+++ b/core/java/android/service/voice/VoiceInteractionService.java
@@ -242,7 +242,7 @@
             throw new IllegalStateException("Not available until onReady() is called");
         }
         try {
-            mSystemService.showSession(args, flags);
+            mSystemService.showSession(args, flags, getAttributionTag());
         } catch (RemoteException e) {
         }
     }
diff --git a/core/java/android/service/voice/VoiceInteractionSession.java b/core/java/android/service/voice/VoiceInteractionSession.java
index b7ec486..d08a67e 100644
--- a/core/java/android/service/voice/VoiceInteractionSession.java
+++ b/core/java/android/service/voice/VoiceInteractionSession.java
@@ -1384,7 +1384,8 @@
             throw new IllegalStateException("Can't call before onCreate()");
         }
         try {
-            mSystemService.showSessionFromSession(mToken, args, flags);
+            mSystemService.showSessionFromSession(mToken, args, flags,
+                    mContext.getAttributionTag());
         } catch (RemoteException e) {
         }
     }
diff --git a/core/java/android/text/Layout.java b/core/java/android/text/Layout.java
index 95adb77..4efc838 100644
--- a/core/java/android/text/Layout.java
+++ b/core/java/android/text/Layout.java
@@ -18,6 +18,7 @@
 
 import android.annotation.IntDef;
 import android.annotation.IntRange;
+import android.annotation.NonNull;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.graphics.Canvas;
 import android.graphics.Paint;
@@ -1312,6 +1313,101 @@
     }
 
     /**
+     * Return the characters' bounds in the given range. The {@code bounds} array will be filled
+     * starting from {@code boundsStart} (inclusive). The coordinates are in local text layout.
+     *
+     * @param start the start index to compute the character bounds, inclusive.
+     * @param end the end index to compute the character bounds, exclusive.
+     * @param bounds the array to fill in the character bounds. The array is divided into segments
+     *               of four where each index in that segment represents left, top, right and
+     *               bottom of the character.
+     * @param boundsStart the inclusive start index in the array to start filling in the values
+     *                    from.
+     *
+     * @throws IndexOutOfBoundsException if the range defined by {@code start} and {@code end}
+     * exceeds the range of the text, or {@code bounds} doesn't have enough space to store the
+     * result.
+     * @throws IllegalArgumentException if {@code bounds} is null.
+     */
+    public void fillCharacterBounds(@IntRange(from = 0) int start, @IntRange(from = 0) int end,
+            @NonNull float[] bounds, @IntRange(from = 0) int boundsStart) {
+        if (start < 0 || end < start || end > mText.length()) {
+            throw new IndexOutOfBoundsException("given range: " + start + ", " + end + " is "
+                    + "out of the text range: 0, " + mText.length());
+        }
+
+        if (bounds == null) {
+            throw  new IllegalArgumentException("bounds can't be null.");
+        }
+
+        final int neededLength = 4 * (end - start);
+        if (neededLength > bounds.length - boundsStart) {
+            throw new IndexOutOfBoundsException("bounds doesn't have enough space to store the "
+                    + "result, needed: " + neededLength + " had: "
+                    + (bounds.length - boundsStart));
+        }
+
+        if (start == end) {
+            return;
+        }
+
+        final int startLine = getLineForOffset(start);
+        final int endLine = getLineForOffset(end - 1);
+        float[] horizontalBounds = null;
+        for (int line = startLine; line <= endLine; ++line) {
+            final int lineStart = getLineStart(line);
+            final int lineEnd = getLineEnd(line);
+            final int lineLength = lineEnd - lineStart;
+
+            final int dir = getParagraphDirection(line);
+            final boolean hasTab = getLineContainsTab(line);
+            final Directions directions = getLineDirections(line);
+
+            TabStops tabStops = null;
+            if (hasTab && mText instanceof Spanned) {
+                // Just checking this line should be good enough, tabs should be
+                // consistent across all lines in a paragraph.
+                TabStopSpan[] tabs = getParagraphSpans((Spanned) mText, lineStart, lineEnd,
+                        TabStopSpan.class);
+                if (tabs.length > 0) {
+                    tabStops = new TabStops(TAB_INCREMENT, tabs); // XXX should reuse
+                }
+            }
+
+            final TextLine tl = TextLine.obtain();
+            tl.set(mPaint, mText, lineStart, lineEnd, dir, directions, hasTab, tabStops,
+                    getEllipsisStart(line), getEllipsisStart(line) + getEllipsisCount(line),
+                    isFallbackLineSpacingEnabled());
+            if (horizontalBounds == null || horizontalBounds.length < 2 * lineLength) {
+                horizontalBounds = new float[2 * lineLength];
+            }
+
+            tl.measureAllBounds(horizontalBounds, null);
+            TextLine.recycle(tl);
+            final int lineLeft = getParagraphLeft(line);
+            final int lineRight = getParagraphRight(line);
+            final int lineStartPos = getLineStartPos(line, lineLeft, lineRight);
+
+            final int lineTop = getLineTop(line);
+            final int lineBottom = getLineBottom(line);
+
+            final int startIndex = Math.max(start, lineStart);
+            final int endIndex = Math.min(end, lineEnd);
+            for (int index = startIndex; index < endIndex; ++index) {
+                final int offset = index - lineStart;
+                final float left = horizontalBounds[offset * 2] + lineStartPos;
+                final float right = horizontalBounds[offset * 2 + 1] + lineStartPos;
+
+                final int boundsIndex = boundsStart + 4 * (index - start);
+                bounds[boundsIndex] = left;
+                bounds[boundsIndex + 1] = lineTop;
+                bounds[boundsIndex + 2] = right;
+                bounds[boundsIndex + 3] = lineBottom;
+            }
+        }
+    }
+
+    /**
      * Get the leftmost position that should be exposed for horizontal
      * scrolling on the specified line.
      */
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..e1966a0 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -734,6 +734,8 @@
     final PointF mDragPoint = new PointF();
     final PointF mLastTouchPoint = new PointF();
     int mLastTouchSource;
+    /** Tracks last {@link MotionEvent#getToolType(int)} with {@link MotionEvent#ACTION_UP}. **/
+    private int mLastClickToolType;
 
     private boolean mProfileRendering;
     private Choreographer.FrameCallback mRenderProfiler;
@@ -848,6 +850,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 +1416,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);
+            }
+
         }
     }
 
@@ -6411,11 +6419,16 @@
                 event.offsetLocation(0, mCurScrollY);
             }
 
-            // Remember the touch position for possible drag-initiation.
             if (event.isTouchEvent()) {
+                // Remember the touch position for possible drag-initiation.
                 mLastTouchPoint.x = event.getRawX();
                 mLastTouchPoint.y = event.getRawY();
                 mLastTouchSource = event.getSource();
+
+                // Register last ACTION_UP. This will be propagated to IME.
+                if (event.getActionMasked() == MotionEvent.ACTION_UP) {
+                    mLastClickToolType = event.getToolType(event.getActionIndex());
+                }
             }
             return FORWARD;
         }
@@ -8011,6 +8024,14 @@
         return mLastTouchSource;
     }
 
+    /**
+     * Used by InputMethodManager.
+     * @hide
+     */
+    public int getLastClickToolType() {
+        return mLastClickToolType;
+    }
+
     public void setDragFocus(View newDragTarget, DragEvent event) {
         if (mCurrentDragView != newDragTarget && !View.sCascadedDragDrop) {
             // Send EXITED and ENTERED notifications to the old and new drag focus views.
@@ -10365,6 +10386,7 @@
                     != AccessibilityWindowInfo.UNDEFINED_WINDOW_ID;
             if (registered) {
                 mAttachInfo.mAccessibilityWindowId = AccessibilityWindowInfo.UNDEFINED_WINDOW_ID;
+                mAccessibilityWindowAttributes = null;
                 mAccessibilityManager.removeAccessibilityInteractionConnection(mWindow);
             }
         }
@@ -10927,8 +10949,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/view/inputmethod/EditorInfo.java b/core/java/android/view/inputmethod/EditorInfo.java
index fdff7a3..55a2d22 100644
--- a/core/java/android/view/inputmethod/EditorInfo.java
+++ b/core/java/android/view/inputmethod/EditorInfo.java
@@ -30,6 +30,7 @@
 import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
 import android.content.res.Configuration;
+import android.inputmethodservice.InputMethodService;
 import android.os.Build.VERSION_CODES;
 import android.os.Bundle;
 import android.os.LocaleList;
@@ -40,6 +41,7 @@
 import android.text.TextUtils;
 import android.util.Printer;
 import android.util.proto.ProtoOutputStream;
+import android.view.MotionEvent;
 import android.view.View;
 import android.view.autofill.AutofillId;
 
@@ -564,6 +566,12 @@
     @Nullable
     private SurroundingText mInitialSurroundingText = null;
 
+    /**
+     * Initial {@link MotionEvent#ACTION_UP} tool type {@link MotionEvent#getToolType(int)} that
+     * was used to focus this editor.
+     */
+    private int mInitialToolType = MotionEvent.TOOL_TYPE_UNKNOWN;
+
 
     /**
      * Editors may use this method to provide initial input text to IMEs. As the surrounding text
@@ -937,6 +945,31 @@
     }
 
     /**
+     * Returns the initial {@link MotionEvent#ACTION_UP} tool type
+     * {@link MotionEvent#getToolType(int)} responsible for focus on the current editor.
+     *
+     * @see #setInitialToolType(int)
+     * @see MotionEvent#getToolType(int)
+     * @see InputMethodService#onUpdateEditorToolType(int)
+     * @return toolType {@link MotionEvent#getToolType(int)}.
+     */
+    public int getInitialToolType() {
+        return mInitialToolType;
+    }
+
+    /**
+     * Set the initial {@link MotionEvent#ACTION_UP} tool type {@link MotionEvent#getToolType(int)}.
+     * that brought focus to the view.
+     *
+     * @see #getInitialToolType()
+     * @see MotionEvent#getToolType(int)
+     * @see InputMethodService#onUpdateEditorToolType(int)
+     */
+    public void setInitialToolType(int toolType) {
+        mInitialToolType = toolType;
+    }
+
+    /**
      * Export the state of {@link EditorInfo} into a protocol buffer output stream.
      *
      * @param proto Stream to write the state to
@@ -972,6 +1005,7 @@
                 + " actionId=" + actionId);
         pw.println(prefix + "initialSelStart=" + initialSelStart
                 + " initialSelEnd=" + initialSelEnd
+                + " initialToolType=" + mInitialToolType
                 + " initialCapsMode=0x"
                 + Integer.toHexString(initialCapsMode));
         pw.println(prefix + "hintText=" + hintText
@@ -1006,6 +1040,7 @@
         newEditorInfo.initialSelStart = initialSelStart;
         newEditorInfo.initialSelEnd = initialSelEnd;
         newEditorInfo.initialCapsMode = initialCapsMode;
+        newEditorInfo.mInitialToolType = mInitialToolType;
         newEditorInfo.hintText = TextUtils.stringOrSpannedString(hintText);
         newEditorInfo.label = TextUtils.stringOrSpannedString(label);
         newEditorInfo.packageName = packageName;
@@ -1036,6 +1071,7 @@
         dest.writeInt(initialSelStart);
         dest.writeInt(initialSelEnd);
         dest.writeInt(initialCapsMode);
+        dest.writeInt(mInitialToolType);
         TextUtils.writeToParcel(hintText, dest, flags);
         TextUtils.writeToParcel(label, dest, flags);
         dest.writeString(packageName);
@@ -1072,6 +1108,7 @@
                     res.initialSelStart = source.readInt();
                     res.initialSelEnd = source.readInt();
                     res.initialCapsMode = source.readInt();
+                    res.mInitialToolType = source.readInt();
                     res.hintText = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(source);
                     res.label = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(source);
                     res.packageName = source.readString();
diff --git a/core/java/android/view/inputmethod/IInputMethodManagerInvoker.java b/core/java/android/view/inputmethod/IInputMethodManagerInvoker.java
index 78a90d9..429b0b8 100644
--- a/core/java/android/view/inputmethod/IInputMethodManagerInvoker.java
+++ b/core/java/android/view/inputmethod/IInputMethodManagerInvoker.java
@@ -111,10 +111,11 @@
 
     @AnyThread
     boolean showSoftInput(@NonNull IInputMethodClient client, @Nullable IBinder windowToken,
-            int flags, @Nullable ResultReceiver resultReceiver,
+            int flags, int lastClickToolType, @Nullable ResultReceiver resultReceiver,
             @SoftInputShowHideReason int reason) {
         try {
-            return mTarget.showSoftInput(client, windowToken, flags, resultReceiver, reason);
+            return mTarget.showSoftInput(
+                    client, windowToken, flags, lastClickToolType, resultReceiver, reason);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
diff --git a/core/java/android/view/inputmethod/InputMethod.java b/core/java/android/view/inputmethod/InputMethod.java
index 95add29..bfe6ae6 100644
--- a/core/java/android/view/inputmethod/InputMethod.java
+++ b/core/java/android/view/inputmethod/InputMethod.java
@@ -377,6 +377,15 @@
     }
 
     /**
+     * This method is called when the user tapped or clicked an {@link android.widget.Editor}.
+     * @param toolType {@link android.view.MotionEvent#getToolType(int)} used for clicking editor.
+     * @hide
+     */
+    default void updateEditorToolType(int toolType) {
+        // intentionally empty
+    }
+
+    /**
      * Start stylus handwriting session.
      * @hide
      */
diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java
index 899e5fc..0b4a298 100644
--- a/core/java/android/view/inputmethod/InputMethodManager.java
+++ b/core/java/android/view/inputmethod/InputMethodManager.java
@@ -1929,6 +1929,7 @@
                     mClient,
                     view.getWindowToken(),
                     flags,
+                    mCurRootView.getLastClickToolType(),
                     resultReceiver,
                     reason);
         }
@@ -1960,6 +1961,7 @@
                     mClient,
                     mCurRootView.getView().getWindowToken(),
                     flags,
+                    mCurRootView.getLastClickToolType(),
                     resultReceiver,
                     SoftInputShowHideReason.SHOW_SOFT_INPUT);
         }
@@ -2339,6 +2341,9 @@
         editorInfo.packageName = view.getContext().getOpPackageName();
         editorInfo.autofillId = view.getAutofillId();
         editorInfo.fieldId = view.getId();
+        synchronized (mH) {
+            editorInfo.setInitialToolType(mCurRootView.getLastClickToolType());
+        }
         InputConnection ic = view.onCreateInputConnection(editorInfo);
         if (DEBUG) Log.v(TAG, "Starting input: editorInfo=" + editorInfo + " ic=" + ic);
 
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index b7bb091..fe0cbcb 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -12530,64 +12530,38 @@
     public void populateCharacterBounds(CursorAnchorInfo.Builder builder,
             int startIndex, int endIndex, float viewportToContentHorizontalOffset,
             float viewportToContentVerticalOffset) {
-        final int minLine = mLayout.getLineForOffset(startIndex);
-        final int maxLine = mLayout.getLineForOffset(endIndex - 1);
         final Rect rect = new Rect();
         getLocalVisibleRect(rect);
         final RectF visibleRect = new RectF(rect);
-        for (int line = minLine; line <= maxLine; ++line) {
-            final int lineStart = mLayout.getLineStart(line);
-            final int lineEnd = mLayout.getLineEnd(line);
-            final int offsetStart = Math.max(lineStart, startIndex);
-            final int offsetEnd = Math.min(lineEnd, endIndex);
-            final boolean ltrLine =
-                    mLayout.getParagraphDirection(line) == Layout.DIR_LEFT_TO_RIGHT;
-            final float[] widths = new float[offsetEnd - offsetStart];
-            mLayout.getPaint().getTextWidths(mTransformed, offsetStart, offsetEnd, widths);
-            final float top = mLayout.getLineTop(line);
-            final float bottom = mLayout.getLineBottom(line);
-            for (int offset = offsetStart; offset < offsetEnd; ++offset) {
-                final float charWidth = widths[offset - offsetStart];
-                final boolean isRtl = mLayout.isRtlCharAt(offset);
-                // TODO: This doesn't work perfectly for text with custom styles and
-                // TAB chars.
-                final float left;
-                if (ltrLine) {
-                    if (isRtl) {
-                        left = mLayout.getSecondaryHorizontal(offset) - charWidth;
-                    } else {
-                        left = mLayout.getPrimaryHorizontal(offset);
-                    }
-                } else {
-                    if (!isRtl) {
-                        left = mLayout.getSecondaryHorizontal(offset);
-                    } else {
-                        left = mLayout.getPrimaryHorizontal(offset) - charWidth;
-                    }
-                }
-                final float right = left + charWidth;
-                // TODO: Check top-right and bottom-left as well.
-                final float localLeft = left + viewportToContentHorizontalOffset;
-                final float localRight = right + viewportToContentHorizontalOffset;
-                final float localTop = top + viewportToContentVerticalOffset;
-                final float localBottom = bottom + viewportToContentVerticalOffset;
-                final boolean isTopLeftVisible = visibleRect.contains(localLeft, localTop);
-                final boolean isBottomRightVisible =
-                        visibleRect.contains(localRight, localBottom);
-                int characterBoundsFlags = 0;
-                if (isTopLeftVisible || isBottomRightVisible) {
-                    characterBoundsFlags |= FLAG_HAS_VISIBLE_REGION;
-                }
-                if (!isTopLeftVisible || !isBottomRightVisible) {
-                    characterBoundsFlags |= CursorAnchorInfo.FLAG_HAS_INVISIBLE_REGION;
-                }
-                if (isRtl) {
-                    characterBoundsFlags |= CursorAnchorInfo.FLAG_IS_RTL;
-                }
-                // Here offset is the index in Java chars.
-                builder.addCharacterBounds(offset, localLeft, localTop, localRight,
-                        localBottom, characterBoundsFlags);
+
+        final float[] characterBounds = new float[4 * (endIndex - startIndex)];
+        mLayout.fillCharacterBounds(startIndex, endIndex, characterBounds, 0);
+        final int limit = endIndex - startIndex;
+        for (int offset = 0; offset < limit; ++offset) {
+            final float left =
+                    characterBounds[offset * 4] + viewportToContentHorizontalOffset;
+            final float top =
+                    characterBounds[offset * 4 + 1] + viewportToContentVerticalOffset;
+            final float right =
+                    characterBounds[offset * 4 + 2] + viewportToContentHorizontalOffset;
+            final float bottom =
+                    characterBounds[offset * 4 + 3] + viewportToContentVerticalOffset;
+
+            final boolean hasVisibleRegion = visibleRect.intersects(left, top, right, bottom);
+            final boolean hasInVisibleRegion = !visibleRect.contains(left, top, right, bottom);
+            int characterBoundsFlags = 0;
+            if (hasVisibleRegion) {
+                characterBoundsFlags |= FLAG_HAS_VISIBLE_REGION;
             }
+            if (hasInVisibleRegion) {
+                characterBoundsFlags |= CursorAnchorInfo.FLAG_HAS_INVISIBLE_REGION;
+            }
+
+            if (mLayout.isRtlCharAt(offset)) {
+                characterBoundsFlags |= CursorAnchorInfo.FLAG_IS_RTL;
+            }
+            builder.addCharacterBounds(offset + startIndex, left, top, right, bottom,
+                    characterBoundsFlags);
         }
     }
 
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/com/android/internal/app/AssistUtils.java b/core/java/com/android/internal/app/AssistUtils.java
index c056e26..f843318 100644
--- a/core/java/com/android/internal/app/AssistUtils.java
+++ b/core/java/com/android/internal/app/AssistUtils.java
@@ -17,6 +17,7 @@
 package com.android.internal.app;
 
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.ComponentName;
 import android.content.Context;
@@ -67,12 +68,53 @@
                 ServiceManager.getService(Context.VOICE_INTERACTION_MANAGER_SERVICE));
     }
 
-    public boolean showSessionForActiveService(Bundle args, int sourceFlags,
-            IVoiceInteractionSessionShowCallback showCallback, IBinder activityToken) {
+    /**
+     * Shows the session for the currently active service. Used to start a new session from system
+     * affordances.
+     *
+     * @param args the bundle to pass as arguments to the voice interaction session
+     * @param sourceFlags flags indicating the source of this show
+     * @param showCallback optional callback to be notified when the session was shown
+     * @param activityToken optional token of activity that needs to be on top
+     *
+     * @deprecated Use {@link #showSessionForActiveService(Bundle, int, String,
+     *             IVoiceInteractionSessionShowCallback, IBinder)} instead
+     */
+    @Deprecated
+    public boolean showSessionForActiveService(@NonNull Bundle args, int sourceFlags,
+            @Nullable IVoiceInteractionSessionShowCallback showCallback,
+            @Nullable IBinder activityToken) {
+        return showSessionForActiveServiceInternal(args, sourceFlags, /* attributionTag */ null,
+                showCallback, activityToken);
+    }
+
+    /**
+     * Shows the session for the currently active service. Used to start a new session from system
+     * affordances.
+     *
+     * @param args the bundle to pass as arguments to the voice interaction session
+     * @param sourceFlags flags indicating the source of this show
+     * @param attributionTag the attribution tag of the calling context or {@code null} for default
+     *                       attribution
+     * @param showCallback optional callback to be notified when the session was shown
+     * @param activityToken optional token of activity that needs to be on top
+     */
+    public boolean showSessionForActiveService(@NonNull Bundle args, int sourceFlags,
+            @Nullable String attributionTag,
+            @Nullable IVoiceInteractionSessionShowCallback showCallback,
+            @Nullable IBinder activityToken) {
+        return showSessionForActiveServiceInternal(args, sourceFlags, attributionTag, showCallback,
+                activityToken);
+    }
+
+    private boolean showSessionForActiveServiceInternal(@NonNull Bundle args, int sourceFlags,
+            @Nullable String attributionTag,
+            @Nullable IVoiceInteractionSessionShowCallback showCallback,
+            @Nullable IBinder activityToken) {
         try {
             if (mVoiceInteractionManagerService != null) {
                 return mVoiceInteractionManagerService.showSessionForActiveService(args,
-                        sourceFlags, showCallback, activityToken);
+                        sourceFlags, attributionTag, showCallback, activityToken);
             }
         } catch (RemoteException e) {
             Log.w(TAG, "Failed to call showSessionForActiveService", e);
diff --git a/core/java/com/android/internal/app/BlockedAppStreamingActivity.java b/core/java/com/android/internal/app/BlockedAppStreamingActivity.java
index d35f665..ec2d2ef 100644
--- a/core/java/com/android/internal/app/BlockedAppStreamingActivity.java
+++ b/core/java/com/android/internal/app/BlockedAppStreamingActivity.java
@@ -36,6 +36,7 @@
     private static final String EXTRA_BLOCKED_ACTIVITY_INFO =
             PACKAGE_NAME + ".extra.BLOCKED_ACTIVITY_INFO";
     private static final String EXTRA_STREAMED_DEVICE = PACKAGE_NAME + ".extra.STREAMED_DEVICE";
+    private static final String BLOCKED_COMPONENT_PLAYSTORE = "com.android.vending";
     private static final String BLOCKED_COMPONENT_SETTINGS = "com.android.settings";
 
     @Override
@@ -62,21 +63,25 @@
                 mAlertParams.mTitle =
                         getString(R.string.app_streaming_blocked_title_for_permission_dialog);
                 mAlertParams.mMessage =
-                        getString(R.string.app_streaming_blocked_message_for_permission_dialog,
-                                streamedDeviceName);
+                        getString(R.string.app_streaming_blocked_message, streamedDeviceName);
+            } else if (TextUtils.equals(activityInfo.packageName, BLOCKED_COMPONENT_PLAYSTORE)) {
+                mAlertParams.mTitle =
+                        getString(R.string.app_streaming_blocked_title_for_playstore_dialog);
+                mAlertParams.mMessage =
+                        getString(R.string.app_streaming_blocked_message, streamedDeviceName);
             } else if (TextUtils.equals(activityInfo.packageName, BLOCKED_COMPONENT_SETTINGS)) {
                 mAlertParams.mTitle =
                         getString(R.string.app_streaming_blocked_title_for_settings_dialog);
                 mAlertParams.mMessage =
-                        getString(R.string.app_streaming_blocked_message, streamedDeviceName);
+                        getString(R.string.app_streaming_blocked_message_for_settings_dialog,
+                                streamedDeviceName);
             } else {
-                mAlertParams.mTitle =
-                        getString(R.string.app_streaming_blocked_title, appLabel);
+                // No title required
                 mAlertParams.mMessage =
                         getString(R.string.app_streaming_blocked_message, streamedDeviceName);
             }
         } else {
-            mAlertParams.mTitle = getString(R.string.app_blocked_title);
+            // No title required
             mAlertParams.mMessage = getString(R.string.app_blocked_message, appLabel);
         }
         mAlertParams.mPositiveButtonText = getString(android.R.string.ok);
diff --git a/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl b/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl
index 4b01357..83bf801 100644
--- a/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl
+++ b/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl
@@ -39,15 +39,16 @@
 import android.service.voice.IMicrophoneHotwordDetectionVoiceInteractionCallback;
 
 interface IVoiceInteractionManagerService {
-    void showSession(in Bundle sessionArgs, int flags);
+    void showSession(in Bundle sessionArgs, int flags, String attributionTag);
     boolean deliverNewSession(IBinder token, IVoiceInteractionSession session,
             IVoiceInteractor interactor);
-    boolean showSessionFromSession(IBinder token, in Bundle sessionArgs, int flags);
+    boolean showSessionFromSession(IBinder token, in Bundle sessionArgs, int flags,
+            String attributionTag);
     boolean hideSessionFromSession(IBinder token);
     int startVoiceActivity(IBinder token, in Intent intent, String resolvedType,
-            String callingFeatureId);
+            String attributionTag);
     int startAssistantActivity(IBinder token, in Intent intent, String resolvedType,
-            String callingFeatureId);
+            String attributionTag);
     void setKeepAwake(IBinder token, boolean keepAwake);
     void closeSystemDialogs(IBinder token);
     void finish(IBinder token);
@@ -125,12 +126,14 @@
      *
      * @param args the bundle to pass as arguments to the voice interaction session
      * @param sourceFlags flags indicating the source of this show
+     * @param attributionTag the attribution tag of the calling context or {@code null} for default
+     *                       attribution
      * @param showCallback optional callback to be notified when the session was shown
      * @param activityToken optional token of activity that needs to be on top
      * @RequiresPermission Manifest.permission.ACCESS_VOICE_INTERACTION_SERVICE
      */
     @EnforcePermission("ACCESS_VOICE_INTERACTION_SERVICE")
-    boolean showSessionForActiveService(in Bundle args, int sourceFlags,
+    boolean showSessionForActiveService(in Bundle args, int sourceFlags, String attributionTag,
             IVoiceInteractionSessionShowCallback showCallback, IBinder activityToken);
 
     /**
diff --git a/core/java/com/android/internal/inputmethod/IInputMethod.aidl b/core/java/com/android/internal/inputmethod/IInputMethod.aidl
index 8bab5c3..5db2e84 100644
--- a/core/java/com/android/internal/inputmethod/IInputMethod.aidl
+++ b/core/java/com/android/internal/inputmethod/IInputMethod.aidl
@@ -74,6 +74,8 @@
 
     void hideSoftInput(in IBinder hideInputToken, int flags, in ResultReceiver resultReceiver);
 
+    void updateEditorToolType(int toolType);
+
     void changeInputMethodSubtype(in InputMethodSubtype subtype);
 
     void canStartStylusHandwriting(int requestId);
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/view/IInputMethodManager.aidl b/core/java/com/android/internal/view/IInputMethodManager.aidl
index 5f5886e..9471fae 100644
--- a/core/java/com/android/internal/view/IInputMethodManager.aidl
+++ b/core/java/com/android/internal/view/IInputMethodManager.aidl
@@ -55,7 +55,7 @@
     InputMethodSubtype getLastInputMethodSubtype(int userId);
 
     boolean showSoftInput(in IInputMethodClient client, @nullable IBinder windowToken, int flags,
-            in @nullable ResultReceiver resultReceiver, int reason);
+            int lastClickToolType, in @nullable ResultReceiver resultReceiver, int reason);
     boolean hideSoftInput(in IInputMethodClient client, @nullable IBinder windowToken, int flags,
             in @nullable ResultReceiver resultReceiver, int reason);
 
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 f93e280..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;
@@ -280,7 +281,7 @@
             sha256.update(hashFactor);
             sha256.update(passwordToHash);
             sha256.update(salt);
-            return new String(HexEncoding.encode(sha256.digest()));
+            return HexEncoding.encodeToString(sha256.digest());
         } catch (NoSuchAlgorithmException e) {
             throw new AssertionError("Missing digest algorithm: ", e);
         }
@@ -302,21 +303,12 @@
         }
 
         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/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 646dedd..d3f2607 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -5477,6 +5477,8 @@
     <string name="app_streaming_blocked_title_for_fingerprint_dialog">Continue on phone</string>
     <!-- Title of the dialog shown when the microphone permission is blocked from being streamed to a remote device. [CHAR LIMIT=NONE] -->
     <string name="app_streaming_blocked_title_for_microphone_dialog">Microphone unavailable</string>
+    <!-- Title of the dialog shown when the play store is blocked from being streamed to a remote device. [CHAR LIMIT=NONE] -->
+    <string name="app_streaming_blocked_title_for_playstore_dialog">Play Store unavailable</string>
     <!-- Title of the dialog shown when the settings is blocked from being streamed to a remote device. [CHAR LIMIT=NONE] -->
     <string name="app_streaming_blocked_title_for_settings_dialog" product="tv">Android TV settings unavailable</string>
     <!-- Title of the dialog shown when the settings is blocked from being streamed to a remote device. [CHAR LIMIT=NONE] -->
@@ -5484,23 +5486,23 @@
     <!-- Title of the dialog shown when the settings is blocked from being streamed to a remote device. [CHAR LIMIT=NONE] -->
     <string name="app_streaming_blocked_title_for_settings_dialog" product="default">Phone settings unavailable</string>
     <!-- Message shown when an app is blocked from being streamed to a remote device. [CHAR LIMIT=NONE] -->
-    <string name="app_streaming_blocked_message" product="tv">This can’t be accessed on your <xliff:g id="device" example="Chromebook">%1$s</xliff:g>. Try on your Android TV device instead.</string>
+    <string name="app_streaming_blocked_message" product="tv">This can’t be accessed on your <xliff:g id="device" example="Chromebook">%1$s</xliff:g> at this time. Try on your Android TV device instead.</string>
     <!-- Message shown when an app is blocked from being streamed to a remote device. [CHAR LIMIT=NONE] -->
-    <string name="app_streaming_blocked_message" product="tablet">This can’t be accessed on your <xliff:g id="device" example="Chromebook">%1$s</xliff:g>. Try on your tablet instead.</string>
+    <string name="app_streaming_blocked_message" product="tablet">This can’t be accessed on your <xliff:g id="device" example="Chromebook">%1$s</xliff:g> at this time. Try on your tablet instead.</string>
     <!-- Message shown when an app is blocked from being streamed to a remote device. [CHAR LIMIT=NONE] -->
-    <string name="app_streaming_blocked_message" product="default">This can’t be accessed on your <xliff:g id="device" example="Chromebook">%1$s</xliff:g>. Try on your phone instead.</string>
-    <!-- Message shown when an app is blocked from being streamed to a remote device. [CHAR LIMIT=NONE] -->
-    <string name="app_streaming_blocked_message_for_permission_dialog" product="tv">This can’t be accessed on your <xliff:g id="device" example="Chromebook">%1$s</xliff:g> at this time. Try on your Android TV device instead.</string>
-    <!-- Message shown when an app is blocked from being streamed to a remote device. [CHAR LIMIT=NONE] -->
-    <string name="app_streaming_blocked_message_for_permission_dialog" product="tablet">This can’t be accessed on your <xliff:g id="device" example="Chromebook">%1$s</xliff:g> at this time. Try on your tablet instead.</string>
-    <!-- Message shown when an app is blocked from being streamed to a remote device. [CHAR LIMIT=NONE] -->
-    <string name="app_streaming_blocked_message_for_permission_dialog" product="default">This can’t be accessed on your <xliff:g id="device" example="Chromebook">%1$s</xliff:g> at this time. Try on your phone instead.</string>
+    <string name="app_streaming_blocked_message" product="default">This can’t be accessed on your <xliff:g id="device" example="Chromebook">%1$s</xliff:g> at this time. Try on your phone instead.</string>
     <!-- Message shown when the fingerprint permission is blocked from being streamed to a remote device. [CHAR LIMIT=NONE] -->
     <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="tv">This app is requesting additional security. Try on your Android TV device instead.</string>
     <!-- Message shown when the fingerprint permission is blocked from being streamed to a remote device. [CHAR LIMIT=NONE] -->
     <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="tablet">This app is requesting additional security. Try on your tablet instead.</string>
     <!-- Message shown when the fingerprint permission is blocked from being streamed to a remote device. [CHAR LIMIT=NONE] -->
     <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="default">This app is requesting additional security. Try on your phone instead.</string>
+    <!-- Message shown when the settings is blocked from being streamed to a remote device. [CHAR LIMIT=NONE] -->
+    <string name="app_streaming_blocked_message_for_settings_dialog" product="tv">This can’t be accessed on your <xliff:g id="device" example="Chromebook">%1$s</xliff:g>. Try on your Android TV device instead.</string>
+    <!-- Message shown when the settings is blocked from being streamed to a remote device. [CHAR LIMIT=NONE] -->
+    <string name="app_streaming_blocked_message_for_settings_dialog" product="tablet">This can’t be accessed on your <xliff:g id="device" example="Chromebook">%1$s</xliff:g>. Try on your tablet instead.</string>
+    <!-- Message shown when the settings is blocked from being streamed to a remote device. [CHAR LIMIT=NONE] -->
+    <string name="app_streaming_blocked_message_for_settings_dialog" product="default">This can’t be accessed on your <xliff:g id="device" example="Chromebook">%1$s</xliff:g>. Try on your phone instead.</string>
 
     <!-- Message displayed in dialog when app is too old to run on this verison of android. [CHAR LIMIT=NONE] -->
     <string name="deprecated_target_sdk_message">This app was built for an older version of Android and may not work properly. Try checking for updates, or contact the developer.</string>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index bcf9759..cb0393f 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -3309,10 +3309,11 @@
   <java-symbol type="string" name="app_streaming_blocked_title_for_camera_dialog" />
   <java-symbol type="string" name="app_streaming_blocked_title_for_fingerprint_dialog" />
   <java-symbol type="string" name="app_streaming_blocked_title_for_microphone_dialog" />
+  <java-symbol type="string" name="app_streaming_blocked_title_for_playstore_dialog" />
   <java-symbol type="string" name="app_streaming_blocked_title_for_settings_dialog" />
   <java-symbol type="string" name="app_streaming_blocked_message" />
-  <java-symbol type="string" name="app_streaming_blocked_message_for_permission_dialog" />
   <java-symbol type="string" name="app_streaming_blocked_message_for_fingerprint_dialog" />
+  <java-symbol type="string" name="app_streaming_blocked_message_for_settings_dialog" />
 
   <!-- Used internally for assistant to launch activity transitions -->
   <java-symbol type="id" name="cross_task_transition" />
diff --git a/core/tests/coretests/assets/fonts/StaticLayoutLineBreakingTestFont.ttf b/core/tests/coretests/assets/fonts/StaticLayoutLineBreakingTestFont.ttf
index 36ed024d..dfb1f0b 100644
--- a/core/tests/coretests/assets/fonts/StaticLayoutLineBreakingTestFont.ttf
+++ b/core/tests/coretests/assets/fonts/StaticLayoutLineBreakingTestFont.ttf
Binary files differ
diff --git a/core/tests/coretests/assets/fonts/StaticLayoutLineBreakingTestFont.ttx b/core/tests/coretests/assets/fonts/StaticLayoutLineBreakingTestFont.ttx
index feefed3..622b861 100644
--- a/core/tests/coretests/assets/fonts/StaticLayoutLineBreakingTestFont.ttx
+++ b/core/tests/coretests/assets/fonts/StaticLayoutLineBreakingTestFont.ttx
@@ -144,6 +144,7 @@
       <map code="0x0056" name="5em" />  <!-- V -->
       <map code="0x0058" name="10em" />  <!-- X -->
       <map code="0x005f" name="0em" /> <!-- _ -->
+      <map code="0x000a" name="0em" /> <!-- NEW_LINE -->
       <map code="0x05D0" name="1em" /> <!-- HEBREW LETTER ALEF -->
       <map code="0x05D1" name="5em" /> <!-- HEBREW LETTER BET -->
       <map code="0xfffd" name="7em" /> <!-- REPLACEMENT CHAR -->
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/android/view/inputmethod/EditorInfoTest.java b/core/tests/coretests/src/android/view/inputmethod/EditorInfoTest.java
index b867e44..9637de8 100644
--- a/core/tests/coretests/src/android/view/inputmethod/EditorInfoTest.java
+++ b/core/tests/coretests/src/android/view/inputmethod/EditorInfoTest.java
@@ -42,6 +42,7 @@
 import android.text.style.MaskFilterSpan;
 import android.text.style.UnderlineSpan;
 import android.util.StringBuilderPrinter;
+import android.view.MotionEvent;
 import android.view.autofill.AutofillId;
 
 import androidx.test.filters.SmallTest;
@@ -490,7 +491,8 @@
         assertThat(sb.toString()).isEqualTo(
                 "prefix: inputType=0x0 imeOptions=0x0 privateImeOptions=null\n"
                 + "prefix: actionLabel=null actionId=0\n"
-                + "prefix: initialSelStart=-1 initialSelEnd=-1 initialCapsMode=0x0\n"
+                + "prefix: initialSelStart=-1 initialSelEnd=-1 initialToolType=0"
+                        + " initialCapsMode=0x0\n"
                 + "prefix: hintText=null label=null\n"
                 + "prefix: packageName=null autofillId=null fieldId=0 fieldName=null\n"
                 + "prefix: extras=null\n"
@@ -509,6 +511,7 @@
         info.initialCapsMode = TextUtils.CAP_MODE_CHARACTERS; // 0x1000
         info.hintText = "testHintText";
         info.label = "testLabel";
+        info.setInitialToolType(MotionEvent.TOOL_TYPE_STYLUS);
         info.packageName = "android.view.inputmethod";
         info.autofillId = new AutofillId(123);
         info.fieldId = 456;
@@ -523,7 +526,8 @@
         assertThat(sb.toString()).isEqualTo(
                 "prefix2: inputType=0x1 imeOptions=0x2 privateImeOptions=testOptions\n"
                         + "prefix2: actionLabel=null actionId=0\n"
-                        + "prefix2: initialSelStart=0 initialSelEnd=1 initialCapsMode=0x1000\n"
+                        + "prefix2: initialSelStart=0 initialSelEnd=1 initialToolType=2"
+                                + " initialCapsMode=0x1000\n"
                         + "prefix2: hintText=testHintText label=testLabel\n"
                         + "prefix2: packageName=android.view.inputmethod autofillId=123"
                         + " fieldId=456 fieldName=testField\n"
@@ -543,7 +547,8 @@
         assertThat(sb.toString()).isEqualTo(
                 "prefix: inputType=0x0 imeOptions=0x0 privateImeOptions=null\n"
                         + "prefix: actionLabel=null actionId=0\n"
-                        + "prefix: initialSelStart=-1 initialSelEnd=-1 initialCapsMode=0x0\n"
+                        + "prefix: initialSelStart=-1 initialSelEnd=-1 initialToolType=0"
+                                + " initialCapsMode=0x0\n"
                         + "prefix: hintText=null label=null\n"
                         + "prefix: packageName=null autofillId=null fieldId=0 fieldName=null\n"
                         + "prefix: hintLocales=null\n"
diff --git a/core/tests/coretests/src/android/widget/TextViewPopulateCharacterBoundsTest.java b/core/tests/coretests/src/android/widget/TextViewPopulateCharacterBoundsTest.java
new file mode 100644
index 0000000..d31a6a9
--- /dev/null
+++ b/core/tests/coretests/src/android/widget/TextViewPopulateCharacterBoundsTest.java
@@ -0,0 +1,422 @@
+/*
+ * 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 android.widget;
+
+import static android.view.inputmethod.CursorAnchorInfo.FLAG_HAS_INVISIBLE_REGION;
+import static android.view.inputmethod.CursorAnchorInfo.FLAG_HAS_VISIBLE_REGION;
+import static android.view.inputmethod.CursorAnchorInfo.FLAG_IS_RTL;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.assertArrayEquals;
+
+import android.app.Activity;
+import android.app.Instrumentation;
+import android.graphics.Matrix;
+import android.graphics.RectF;
+import android.graphics.Typeface;
+import android.text.Layout;
+import android.util.TypedValue;
+import android.view.View;
+import android.view.inputmethod.CursorAnchorInfo;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.platform.app.InstrumentationRegistry;
+import androidx.test.rule.ActivityTestRule;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class TextViewPopulateCharacterBoundsTest {
+    @Rule
+    public ActivityTestRule<TextViewActivity> mActivityRule = new ActivityTestRule<>(
+            TextViewActivity.class);
+    private Activity mActivity;
+    private Instrumentation mInstrumentation;
+    private Typeface mTypeface;
+    @Before
+    public void setup() {
+        mActivity = mActivityRule.getActivity();
+        mInstrumentation = InstrumentationRegistry.getInstrumentation();
+
+        // The test font has following coverage and width.
+        // U+0020: 10em
+        // U+002E (.): 10em
+        // U+0043 (C): 100em
+        // U+0049 (I): 1em
+        // U+004C (L): 50em
+        // U+0056 (V): 5em
+        // U+0058 (X): 10em
+        // U+005F (_): 0em
+        // U+05D0    : 1em  // HEBREW LETTER ALEF
+        // U+05D1    : 5em  // HEBREW LETTER BET
+        // U+FFFD (invalid surrogate will be replaced to this): 7em
+        // U+10331 (\uD800\uDF31): 10em
+        // Undefined : 0.5em
+        mTypeface = Typeface.createFromAsset(mInstrumentation.getTargetContext().getAssets(),
+                "fonts/StaticLayoutLineBreakingTestFont.ttf");
+    }
+
+    private TextView createTextView(String text, float textSize, int width, int height) {
+        final TextView textView = new TextView(mActivity);
+        textView.setTypeface(mTypeface);
+
+        textView.setText(text);
+        // Make 1 em equal to 10 pixels.
+        textView.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize);
+        textView.measure(
+                View.MeasureSpec.makeMeasureSpec(width, View.MeasureSpec.EXACTLY),
+                View.MeasureSpec.makeMeasureSpec(height, View.MeasureSpec.EXACTLY));
+        textView.layout(0, 0, width, height);
+        return textView;
+    }
+
+    @Test
+    public void testPopulateCharacterBounds_LTR() {
+        final String text = "IIVX";
+        final TextView textView = createTextView(text, 10.0f, 200, 1000);
+
+        final CursorAnchorInfo.Builder builder = new CursorAnchorInfo.Builder();
+        builder.setMatrix(Matrix.IDENTITY_MATRIX);
+        textView.populateCharacterBounds(builder, 0, text.length(), 0, 0);
+
+        final CursorAnchorInfo cursorAnchorInfo = builder.build();
+
+        final Layout layout = textView.getLayout();
+        final RectF[] expectedCharacterBounds = new RectF[] {
+                new RectF(0.0f, layout.getLineTop(0), 10.0f, layout.getLineBottom(0)),
+                new RectF(10.0f, layout.getLineTop(0), 20.0f, layout.getLineBottom(0)),
+                new RectF(20.0f, layout.getLineTop(0), 70.0f, layout.getLineBottom(0)),
+                new RectF(70.0f, layout.getLineTop(0), 170.0f, layout.getLineBottom(0))
+        };
+        assertCharacterBounds(expectedCharacterBounds, cursorAnchorInfo);
+
+        final int[] expectedCharacterBoundsFlags = new int[] {
+                FLAG_HAS_VISIBLE_REGION,
+                FLAG_HAS_VISIBLE_REGION,
+                FLAG_HAS_VISIBLE_REGION,
+                FLAG_HAS_VISIBLE_REGION
+        };
+        assertCharacterBoundsFlags(expectedCharacterBoundsFlags, cursorAnchorInfo);
+    }
+
+    @Test
+    public void testPopulateCharacterBounds_LTR_multiline() {
+        final String text = "IVVI";
+        final TextView textView = createTextView(text, 10.0f, 100, 1000);
+
+        final CursorAnchorInfo.Builder builder = new CursorAnchorInfo.Builder();
+        builder.setMatrix(Matrix.IDENTITY_MATRIX);
+        textView.populateCharacterBounds(builder, 0, text.length(), 0, 0);
+
+        final CursorAnchorInfo cursorAnchorInfo = builder.build();
+
+        final Layout layout = textView.getLayout();
+        final RectF[] expectedCharacterBounds = new RectF[] {
+                new RectF(0.0f, layout.getLineTop(0), 10.0f, layout.getLineBottom(0)),
+                new RectF(10.0f, layout.getLineTop(0), 60.0f, layout.getLineBottom(0)),
+                // The second line.
+                new RectF(0.0f, layout.getLineTop(1), 50.0f, layout.getLineBottom(1)),
+                new RectF(50.0f, layout.getLineTop(1), 60.0f, layout.getLineBottom(1))
+        };
+        assertCharacterBounds(expectedCharacterBounds, cursorAnchorInfo);
+
+        final int[] expectedCharacterBoundsFlags = new int[] {
+                FLAG_HAS_VISIBLE_REGION,
+                FLAG_HAS_VISIBLE_REGION,
+                FLAG_HAS_VISIBLE_REGION,
+                FLAG_HAS_VISIBLE_REGION
+        };
+        assertCharacterBoundsFlags(expectedCharacterBoundsFlags, cursorAnchorInfo);
+    }
+
+    @Test
+    public void testPopulateCharacterBounds_LTR_newline() {
+        final String text = "IV\nVI";
+        final TextView textView = createTextView(text, 10.0f, 100, 1000);
+
+        final CursorAnchorInfo.Builder builder = new CursorAnchorInfo.Builder();
+        builder.setMatrix(Matrix.IDENTITY_MATRIX);
+        textView.populateCharacterBounds(builder, 0, text.length(), 0, 0);
+
+        final CursorAnchorInfo cursorAnchorInfo = builder.build();
+
+        final Layout layout = textView.getLayout();
+        final RectF[] expectedCharacterBounds = new RectF[] {
+                new RectF(0.0f, layout.getLineTop(0), 10.0f, layout.getLineBottom(0)),
+                new RectF(10.0f, layout.getLineTop(0), 60.0f, layout.getLineBottom(0)),
+                // Newline belongs to the first line, and it has 0 width in the font.
+                new RectF(60.0f, layout.getLineTop(0), 60.0f, layout.getLineBottom(0)),
+                // The second line.
+                new RectF(0.0f, layout.getLineTop(1), 50.0f, layout.getLineBottom(1)),
+                new RectF(50.0f, layout.getLineTop(1), 60.0f, layout.getLineBottom(1))
+        };
+        assertCharacterBounds(expectedCharacterBounds, cursorAnchorInfo);
+
+        final int[] expectedCharacterBoundsFlags = new int[] {
+                FLAG_HAS_VISIBLE_REGION,
+                FLAG_HAS_VISIBLE_REGION,
+                FLAG_HAS_VISIBLE_REGION,
+                FLAG_HAS_VISIBLE_REGION,
+                FLAG_HAS_VISIBLE_REGION
+        };
+        assertCharacterBoundsFlags(expectedCharacterBoundsFlags, cursorAnchorInfo);
+    }
+
+    @Test
+    public void testPopulateCharacterBounds_RTL() {
+        final String text = "\u05D0\u05D0\u05D1\u05D1";
+        final TextView textView = createTextView(text, 10.0f, 200, 1000);
+
+        final CursorAnchorInfo.Builder builder = new CursorAnchorInfo.Builder();
+        builder.setMatrix(Matrix.IDENTITY_MATRIX);
+        textView.populateCharacterBounds(builder, 0, text.length(), 0, 0);
+
+        final CursorAnchorInfo cursorAnchorInfo = builder.build();
+
+        final Layout layout = textView.getLayout();
+        final RectF[] expectedCharacterBounds = new RectF[] {
+                new RectF(190.0f, layout.getLineTop(0), 200.0f, layout.getLineBottom(0)),
+                new RectF(180.0f, layout.getLineTop(0), 190.0f, layout.getLineBottom(0)),
+                new RectF(130.0f, layout.getLineTop(0), 180.0f, layout.getLineBottom(0)),
+                new RectF(80.0f, layout.getLineTop(0), 130.0f, layout.getLineBottom(0))
+        };
+        assertCharacterBounds(expectedCharacterBounds, cursorAnchorInfo);
+
+        final int[] expectedCharacterBoundsFlags = new int[] {
+                FLAG_HAS_VISIBLE_REGION | FLAG_IS_RTL,
+                FLAG_HAS_VISIBLE_REGION | FLAG_IS_RTL,
+                FLAG_HAS_VISIBLE_REGION | FLAG_IS_RTL,
+                FLAG_HAS_VISIBLE_REGION | FLAG_IS_RTL
+        };
+        assertCharacterBoundsFlags(expectedCharacterBoundsFlags, cursorAnchorInfo);
+    }
+
+    @Test
+    public void testPopulateCharacterBounds_RTL_multiline() {
+        final String text = "\u05D0\u05D1\u05D1\u05D0";
+        final TextView textView = createTextView(text, 10.0f, 100, 1000);
+
+        final CursorAnchorInfo.Builder builder = new CursorAnchorInfo.Builder();
+        builder.setMatrix(Matrix.IDENTITY_MATRIX);
+        textView.populateCharacterBounds(builder, 0, text.length(), 0, 0);
+
+        final CursorAnchorInfo cursorAnchorInfo = builder.build();
+
+        final Layout layout = textView.getLayout();
+        final RectF[] expectedCharacterBounds = new RectF[] {
+                new RectF(90.0f, layout.getLineTop(0), 100.0f, layout.getLineBottom(0)),
+                new RectF(40.0f, layout.getLineTop(0), 90.0f, layout.getLineBottom(0)),
+                // The second line
+                new RectF(50.0f, layout.getLineTop(1), 100.0f, layout.getLineBottom(1)),
+                new RectF(40.0f, layout.getLineTop(1), 50.0f, layout.getLineBottom(1))
+        };
+        assertCharacterBounds(expectedCharacterBounds, cursorAnchorInfo);
+
+        final int[] expectedCharacterBoundsFlags = new int[] {
+                FLAG_HAS_VISIBLE_REGION | FLAG_IS_RTL,
+                FLAG_HAS_VISIBLE_REGION | FLAG_IS_RTL,
+                FLAG_HAS_VISIBLE_REGION | FLAG_IS_RTL,
+                FLAG_HAS_VISIBLE_REGION | FLAG_IS_RTL
+        };
+        assertCharacterBoundsFlags(expectedCharacterBoundsFlags, cursorAnchorInfo);
+    }
+
+    @Test
+    public void testPopulateCharacterBounds_RTL_newline() {
+        final String text = "\u05D0\u05D1\n\u05D1\u05D0";
+        final TextView textView = createTextView(text, 10.0f, 100, 1000);
+
+        final CursorAnchorInfo.Builder builder = new CursorAnchorInfo.Builder();
+        builder.setMatrix(Matrix.IDENTITY_MATRIX);
+        textView.populateCharacterBounds(builder, 0, text.length(), 0, 0);
+
+        final CursorAnchorInfo cursorAnchorInfo = builder.build();
+
+        final Layout layout = textView.getLayout();
+        final RectF[] expectedCharacterBounds = new RectF[] {
+                new RectF(90.0f, layout.getLineTop(0), 100.0f, layout.getLineBottom(0)),
+                new RectF(40.0f, layout.getLineTop(0), 90.0f, layout.getLineBottom(0)),
+                // Newline belongs to the first line, and it has 0 width in the font.
+                new RectF(40.0f, layout.getLineTop(0), 40.0f, layout.getLineBottom(0)),
+                // The second line
+                new RectF(50.0f, layout.getLineTop(1), 100.0f, layout.getLineBottom(1)),
+                new RectF(40.0f, layout.getLineTop(1), 50.0f, layout.getLineBottom(1))
+        };
+        assertCharacterBounds(expectedCharacterBounds, cursorAnchorInfo);
+
+        final int[] expectedCharacterBoundsFlags = new int[] {
+                FLAG_HAS_VISIBLE_REGION | FLAG_IS_RTL,
+                FLAG_HAS_VISIBLE_REGION | FLAG_IS_RTL,
+                // Newline is in an RTL run.
+                FLAG_HAS_VISIBLE_REGION | FLAG_IS_RTL,
+                FLAG_HAS_VISIBLE_REGION | FLAG_IS_RTL,
+                FLAG_HAS_VISIBLE_REGION | FLAG_IS_RTL
+        };
+        assertCharacterBoundsFlags(expectedCharacterBoundsFlags, cursorAnchorInfo);
+    }
+
+    @Test
+    public void testPopulateCharacterBounds_BiDi() {
+        final String text = "IV\u05D0\u05D1IV";
+        final TextView textView = createTextView(text, 10.0f, 200, 1000);
+
+        final CursorAnchorInfo.Builder builder = new CursorAnchorInfo.Builder();
+        builder.setMatrix(Matrix.IDENTITY_MATRIX);
+        textView.populateCharacterBounds(builder, 0, text.length(), 0, 0);
+
+        final CursorAnchorInfo cursorAnchorInfo = builder.build();
+
+        final Layout layout = textView.getLayout();
+        final RectF[] expectedCharacterBounds = new RectF[] {
+                new RectF(0.0f, layout.getLineTop(0), 10.0f, layout.getLineBottom(0)),
+                new RectF(10.0f, layout.getLineTop(0), 60.0f, layout.getLineBottom(0)),
+                new RectF(110.0f, layout.getLineTop(0), 120.0f, layout.getLineBottom(0)),
+                new RectF(60.0f, layout.getLineTop(0), 110.0f, layout.getLineBottom(0)),
+                new RectF(120.0f, layout.getLineTop(0), 130.0f, layout.getLineBottom(0)),
+                new RectF(130.0f, layout.getLineTop(0), 180.0f, layout.getLineBottom(0))
+        };
+        assertCharacterBounds(expectedCharacterBounds, cursorAnchorInfo);
+
+        final int[] expectedCharacterBoundsFlags = new int[] {
+                FLAG_HAS_VISIBLE_REGION,
+                FLAG_HAS_VISIBLE_REGION,
+                FLAG_HAS_VISIBLE_REGION | FLAG_IS_RTL,
+                FLAG_HAS_VISIBLE_REGION | FLAG_IS_RTL,
+                FLAG_HAS_VISIBLE_REGION,
+                FLAG_HAS_VISIBLE_REGION
+        };
+        assertCharacterBoundsFlags(expectedCharacterBoundsFlags, cursorAnchorInfo);
+    }
+
+    @Test
+    public void testPopulateCharacterBounds_BiDi_multiline() {
+        final String text = "IV\u05D0\u05D1IV";
+        final TextView textView = createTextView(text, 10.0f, 100, 1000);
+
+        final CursorAnchorInfo.Builder builder = new CursorAnchorInfo.Builder();
+        builder.setMatrix(Matrix.IDENTITY_MATRIX);
+        textView.populateCharacterBounds(builder, 0, text.length(), 0, 0);
+
+        final CursorAnchorInfo cursorAnchorInfo = builder.build();
+
+        final Layout layout = textView.getLayout();
+        final RectF[] expectedCharacterBounds = new RectF[] {
+                new RectF(0.0f, layout.getLineTop(0), 10.0f, layout.getLineBottom(0)),
+                new RectF(10.0f, layout.getLineTop(0), 60.0f, layout.getLineBottom(0)),
+                new RectF(60.0f, layout.getLineTop(0), 70.0f, layout.getLineBottom(0)),
+                // The second line.
+                new RectF(0.0f, layout.getLineTop(1), 50.0f, layout.getLineBottom(1)),
+                new RectF(50.0f, layout.getLineTop(1), 60.0f, layout.getLineBottom(1)),
+                // The third line
+                new RectF(0.0f, layout.getLineTop(2), 50.0f, layout.getLineBottom(2))
+        };
+        assertCharacterBounds(expectedCharacterBounds, cursorAnchorInfo);
+
+        final int[] expectedCharacterBoundsFlags = new int[] {
+                FLAG_HAS_VISIBLE_REGION,
+                FLAG_HAS_VISIBLE_REGION,
+                FLAG_HAS_VISIBLE_REGION | FLAG_IS_RTL,
+                FLAG_HAS_VISIBLE_REGION | FLAG_IS_RTL,
+                FLAG_HAS_VISIBLE_REGION,
+                FLAG_HAS_VISIBLE_REGION
+        };
+        assertCharacterBoundsFlags(expectedCharacterBoundsFlags, cursorAnchorInfo);
+    }
+
+    @Test
+    public void testPopulateCharacterBounds_charactersWithInvisibleRegion() {
+        final String text = "IVVI";
+        final TextView textView = createTextView(text, 10.0f, 100, 1000);
+        final Layout layout = textView.getLayout();
+
+        final CursorAnchorInfo.Builder builder = new CursorAnchorInfo.Builder();
+        builder.setMatrix(Matrix.IDENTITY_MATRIX);
+        final int verticalOffset = -50;
+        // Make viewToContentVerticalOffset -50px to simulate the case where TextView is scrolled.
+        textView.populateCharacterBounds(builder, 0, text.length(), 0, verticalOffset);
+
+        final CursorAnchorInfo cursorAnchorInfo = builder.build();
+
+        final float firstLineTop = layout.getLineTop(0) + verticalOffset;
+        final float firstLineBottom = layout.getLineBottom(0) + verticalOffset;
+
+        final float secondLineTop = layout.getLineTop(1) + verticalOffset;
+        final float secondLineBottom = layout.getLineBottom(1) + verticalOffset;
+        final RectF[] expectedCharacterBounds = new RectF[] {
+                new RectF(0.0f, firstLineTop, 10.0f, firstLineBottom),
+                new RectF(10.0f, firstLineTop, 60.0f, firstLineBottom),
+                new RectF(0.0f, secondLineTop, 50.0f, secondLineBottom),
+                new RectF(50.0f, secondLineTop, 60.0f, secondLineBottom)
+        };
+
+        assertCharacterBounds(expectedCharacterBounds, cursorAnchorInfo);
+
+        final int[] expectedCharacterBoundsFlags = new int[] {
+                FLAG_HAS_VISIBLE_REGION | FLAG_HAS_INVISIBLE_REGION,
+                FLAG_HAS_VISIBLE_REGION | FLAG_HAS_INVISIBLE_REGION,
+                // The second line is visible.
+                FLAG_HAS_VISIBLE_REGION,
+                FLAG_HAS_VISIBLE_REGION
+        };
+        assertCharacterBoundsFlags(expectedCharacterBoundsFlags, cursorAnchorInfo);
+    }
+
+    @Test
+    public void testPopulateCharacterBounds_withinRange() {
+        final String text = "IVVI";
+        final TextView textView = createTextView(text, 10.0f, 100, 1000);
+        final Layout layout = textView.getLayout();
+
+        final CursorAnchorInfo.Builder builder = new CursorAnchorInfo.Builder();
+        builder.setMatrix(Matrix.IDENTITY_MATRIX);
+        // Only query for character bounds within the range [2, 4).
+        textView.populateCharacterBounds(builder, 2, 4, 0, 0);
+
+        final CursorAnchorInfo cursorAnchorInfo = builder.build();
+
+        assertThat(cursorAnchorInfo.getCharacterBounds(2)).isEqualTo(
+                new RectF(0.0f, layout.getLineTop(1), 50.0f, layout.getLineBottom(1)));
+        assertThat(cursorAnchorInfo.getCharacterBounds(3)).isEqualTo(
+                new RectF(50.0f, layout.getLineTop(1), 60.0f, layout.getLineBottom(1)));
+
+        assertThat(cursorAnchorInfo.getCharacterBoundsFlags(2)).isEqualTo(FLAG_HAS_VISIBLE_REGION);
+        assertThat(cursorAnchorInfo.getCharacterBoundsFlags(3)).isEqualTo(FLAG_HAS_VISIBLE_REGION);
+    }
+
+    private static void assertCharacterBounds(RectF[] expected,
+            CursorAnchorInfo cursorAnchorInfo) {
+        final RectF[] characterBounds = new RectF[expected.length];
+        for (int i = 0; i < expected.length; ++i) {
+            characterBounds[i] = cursorAnchorInfo.getCharacterBounds(i);
+        }
+        assertArrayEquals(expected, characterBounds);
+    }
+
+    private static void assertCharacterBoundsFlags(int[] expected,
+            CursorAnchorInfo cursorAnchorInfo) {
+        final int[] characterBoundsFlags = new int[expected.length];
+        for (int i = 0; i < expected.length; ++i) {
+            characterBoundsFlags[i] = cursorAnchorInfo.getCharacterBoundsFlags(i);
+        }
+        assertArrayEquals(expected, characterBoundsFlags);
+    }
+
+}
diff --git a/core/tests/overlaytests/device/AndroidTest.xml b/core/tests/overlaytests/device/AndroidTest.xml
index 2d7d9b4..4099ec1 100644
--- a/core/tests/overlaytests/device/AndroidTest.xml
+++ b/core/tests/overlaytests/device/AndroidTest.xml
@@ -20,7 +20,7 @@
     <option name="test-suite-tag" value="apct-instrumentation" />
 
     <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer" />
-    <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="remount-system" value="true" />
         <option name="push" value="OverlayDeviceTests.apk->/system/app/OverlayDeviceTests.apk" />
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/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
index 4080b99..5cba3b4 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
@@ -31,7 +31,6 @@
 import android.app.ActivityManager.RunningTaskInfo;
 import android.app.TaskInfo;
 import android.app.WindowConfiguration;
-import android.content.Context;
 import android.content.LocusId;
 import android.content.pm.ActivityInfo;
 import android.graphics.Rect;
@@ -56,6 +55,7 @@
 import com.android.wm.shell.compatui.CompatUIController;
 import com.android.wm.shell.recents.RecentTasksController;
 import com.android.wm.shell.startingsurface.StartingWindowController;
+import com.android.wm.shell.sysui.ShellInit;
 import com.android.wm.shell.unfold.UnfoldAnimationController;
 
 import java.io.PrintWriter;
@@ -186,41 +186,45 @@
     @Nullable
     private RunningTaskInfo mLastFocusedTaskInfo;
 
-    public ShellTaskOrganizer(ShellExecutor mainExecutor, Context context) {
-        this(null /* taskOrganizerController */, mainExecutor, context, null /* compatUI */,
+    public ShellTaskOrganizer(ShellExecutor mainExecutor) {
+        this(null /* shellInit */, null /* taskOrganizerController */, null /* compatUI */,
                 Optional.empty() /* unfoldAnimationController */,
-                Optional.empty() /* recentTasksController */);
+                Optional.empty() /* recentTasksController */,
+                mainExecutor);
     }
 
-    public ShellTaskOrganizer(ShellExecutor mainExecutor, Context context, @Nullable
-            CompatUIController compatUI) {
-        this(null /* taskOrganizerController */, mainExecutor, context, compatUI,
-                Optional.empty() /* unfoldAnimationController */,
-                Optional.empty() /* recentTasksController */);
-    }
-
-    public ShellTaskOrganizer(ShellExecutor mainExecutor, Context context, @Nullable
-            CompatUIController compatUI,
+    public ShellTaskOrganizer(ShellInit shellInit,
+            @Nullable CompatUIController compatUI,
             Optional<UnfoldAnimationController> unfoldAnimationController,
-            Optional<RecentTasksController> recentTasks) {
-        this(null /* taskOrganizerController */, mainExecutor, context, compatUI,
-                unfoldAnimationController, recentTasks);
+            Optional<RecentTasksController> recentTasks,
+            ShellExecutor mainExecutor) {
+        this(shellInit, null /* taskOrganizerController */, compatUI,
+                unfoldAnimationController, recentTasks, mainExecutor);
     }
 
     @VisibleForTesting
-    protected ShellTaskOrganizer(ITaskOrganizerController taskOrganizerController,
-            ShellExecutor mainExecutor, Context context, @Nullable CompatUIController compatUI,
+    protected ShellTaskOrganizer(ShellInit shellInit,
+            ITaskOrganizerController taskOrganizerController,
+            @Nullable CompatUIController compatUI,
             Optional<UnfoldAnimationController> unfoldAnimationController,
-            Optional<RecentTasksController> recentTasks) {
+            Optional<RecentTasksController> recentTasks,
+            ShellExecutor mainExecutor) {
         super(taskOrganizerController, mainExecutor);
         mCompatUI = compatUI;
         mRecentTasks = recentTasks;
         mUnfoldAnimationController = unfoldAnimationController.orElse(null);
-        if (compatUI != null) {
-            compatUI.setCompatUICallback(this);
+        if (shellInit != null) {
+            shellInit.addInitCallback(this::onInit, this);
         }
     }
 
+    private void onInit() {
+        if (mCompatUI != null) {
+            mCompatUI.setCompatUICallback(this);
+        }
+        registerOrganizer();
+    }
+
     @Override
     public List<TaskAppearedInfo> registerOrganizer() {
         synchronized (mLock) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingController.java
index 82b0270..b305897 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingController.java
@@ -28,6 +28,7 @@
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 
+import com.android.wm.shell.sysui.ShellInit;
 import com.android.wm.shell.transition.Transitions;
 
 /**
@@ -38,13 +39,17 @@
     private final Context mContext;
     private final Transitions mTransitions;
 
-    public ActivityEmbeddingController(Context context, Transitions transitions) {
+    public ActivityEmbeddingController(Context context, ShellInit shellInit,
+            Transitions transitions) {
         mContext = context;
         mTransitions = transitions;
+        if (Transitions.ENABLE_SHELL_TRANSITIONS) {
+            shellInit.addInitCallback(this::onInit, this);
+        }
     }
 
     /** Registers to handle transitions. */
-    public void init() {
+    public void onInit() {
         mTransitions.addHandler(this);
     }
 
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 1c2f0d8..3982616 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
@@ -101,6 +101,7 @@
 import com.android.wm.shell.pip.PinnedStackListenerForwarder;
 import com.android.wm.shell.sysui.ConfigurationChangeListener;
 import com.android.wm.shell.sysui.ShellController;
+import com.android.wm.shell.sysui.ShellInit;
 
 import java.io.PrintWriter;
 import java.util.ArrayList;
@@ -227,6 +228,7 @@
 
   
     public BubbleController(Context context,
+            ShellInit shellInit,
             ShellController shellController,
             BubbleData data,
             @Nullable BubbleStackView.SurfaceSynchronizer synchronizer,
@@ -279,6 +281,7 @@
         mOneHandedOptional = oneHandedOptional;
         mDragAndDropController = dragAndDropController;
         mSyncQueue = syncQueue;
+        shellInit.addInitCallback(this::onInit, this);
     }
 
     private void registerOneHandedState(OneHandedController oneHanded) {
@@ -300,7 +303,7 @@
                 });
     }
 
-    public void initialize() {
+    protected void onInit() {
         mBubbleData.setListener(mBubbleDataListener);
         mBubbleData.setSuppressionChangedListener(this::onBubbleMetadataFlagChanged);
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayChangeController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayChangeController.java
index 28c7367..ae1f433 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayChangeController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayChangeController.java
@@ -28,6 +28,7 @@
 import androidx.annotation.BinderThread;
 
 import com.android.wm.shell.common.annotations.ShellMainThread;
+import com.android.wm.shell.sysui.ShellInit;
 
 import java.util.concurrent.CopyOnWriteArrayList;
 
@@ -47,10 +48,15 @@
     private final CopyOnWriteArrayList<OnDisplayChangingListener> mDisplayChangeListener =
             new CopyOnWriteArrayList<>();
 
-    public DisplayChangeController(IWindowManager wmService, ShellExecutor mainExecutor) {
+    public DisplayChangeController(IWindowManager wmService, ShellInit shellInit,
+            ShellExecutor mainExecutor) {
         mMainExecutor = mainExecutor;
         mWmService = wmService;
         mControllerImpl = new DisplayChangeWindowControllerImpl();
+        shellInit.addInitCallback(this::onInit, this);
+    }
+
+    private void onInit() {
         try {
             mWmService.setDisplayChangeWindowController(mControllerImpl);
         } catch (RemoteException e) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayController.java
index 764936c..f07ea75 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayController.java
@@ -34,6 +34,7 @@
 
 import com.android.wm.shell.common.DisplayChangeController.OnDisplayChangingListener;
 import com.android.wm.shell.common.annotations.ShellMainThread;
+import com.android.wm.shell.sysui.ShellInit;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -57,19 +58,23 @@
     private final SparseArray<DisplayRecord> mDisplays = new SparseArray<>();
     private final ArrayList<OnDisplaysChangedListener> mDisplayChangedListeners = new ArrayList<>();
 
-    public DisplayController(Context context, IWindowManager wmService,
+    public DisplayController(Context context, IWindowManager wmService, ShellInit shellInit,
             ShellExecutor mainExecutor) {
         mMainExecutor = mainExecutor;
         mContext = context;
         mWmService = wmService;
-        mChangeController = new DisplayChangeController(mWmService, mainExecutor);
+        // TODO: Inject this instead
+        mChangeController = new DisplayChangeController(mWmService, shellInit, mainExecutor);
         mDisplayContainerListener = new DisplayWindowListenerImpl();
+        // Note, add this after DisplaceChangeController is constructed to ensure that is
+        // initialized first
+        shellInit.addInitCallback(this::onInit, this);
     }
 
     /**
      * Initializes the window listener.
      */
-    public void initialize() {
+    public void onInit() {
         try {
             int[] displayIds = mWmService.registerDisplayWindowListener(mDisplayContainerListener);
             for (int i = 0; i < displayIds.length; i++) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java
index b3f6247..266cf29 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java
@@ -44,6 +44,7 @@
 import androidx.annotation.VisibleForTesting;
 
 import com.android.internal.view.IInputMethodManager;
+import com.android.wm.shell.sysui.ShellInit;
 
 import java.util.ArrayList;
 import java.util.concurrent.Executor;
@@ -74,18 +75,24 @@
     private final ArrayList<ImePositionProcessor> mPositionProcessors = new ArrayList<>();
 
 
-    public DisplayImeController(IWindowManager wmService, DisplayController displayController,
+    public DisplayImeController(IWindowManager wmService,
+            ShellInit shellInit,
+            DisplayController displayController,
             DisplayInsetsController displayInsetsController,
-            Executor mainExecutor, TransactionPool transactionPool) {
+            TransactionPool transactionPool,
+            Executor mainExecutor) {
         mWmService = wmService;
         mDisplayController = displayController;
         mDisplayInsetsController = displayInsetsController;
         mMainExecutor = mainExecutor;
         mTransactionPool = transactionPool;
+        shellInit.addInitCallback(this::onInit, this);
     }
 
-    /** Starts monitor displays changes and set insets controller for each displays. */
-    public void startMonitorDisplays() {
+    /**
+     * Starts monitor displays changes and set insets controller for each displays.
+     */
+    public void onInit() {
         mDisplayController.addDisplayWindowListener(this);
     }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayInsetsController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayInsetsController.java
index f546f11..90a01f8 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayInsetsController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayInsetsController.java
@@ -29,6 +29,7 @@
 import androidx.annotation.BinderThread;
 
 import com.android.wm.shell.common.annotations.ShellMainThread;
+import com.android.wm.shell.sysui.ShellInit;
 
 import java.util.concurrent.CopyOnWriteArrayList;
 
@@ -45,17 +46,20 @@
     private final SparseArray<CopyOnWriteArrayList<OnInsetsChangedListener>> mListeners =
             new SparseArray<>();
 
-    public DisplayInsetsController(IWindowManager wmService, DisplayController displayController,
+    public DisplayInsetsController(IWindowManager wmService,
+            ShellInit shellInit,
+            DisplayController displayController,
             ShellExecutor mainExecutor) {
         mWmService = wmService;
         mDisplayController = displayController;
         mMainExecutor = mainExecutor;
+        shellInit.addInitCallback(this::onInit, this);
     }
 
     /**
      * Starts listening for insets for each display.
      **/
-    public void initialize() {
+    public void onInit() {
         mDisplayController.addDisplayWindowListener(this);
     }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/DynamicOverride.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/DynamicOverride.java
index 806f795..10b121b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/DynamicOverride.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/DynamicOverride.java
@@ -92,6 +92,8 @@
  *
  * For example, this uses the same setup as above, but the interface provided (if bound) is used
  * otherwise the default is created:
+ *
+ * BaseModule:
  *   @BindsOptionalOf
  *   @DynamicOverride
  *   abstract Interface dynamicInterface();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/ShellCreateTrigger.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/ShellCreateTrigger.java
new file mode 100644
index 0000000..482b199
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/ShellCreateTrigger.java
@@ -0,0 +1,38 @@
+/*
+ * 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.dagger;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Inherited;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+import javax.inject.Qualifier;
+
+/**
+ * An annotation to specifically mark the provider that is triggering the creation of independent
+ * shell components that are not created as a part of the dependencies for interfaces passed to
+ * SysUI.
+ *
+ * TODO: This will be removed once we have a more explicit method for specifying components to start
+ *       with SysUI
+ */
+@Documented
+@Inherited
+@Qualifier
+@Retention(RetentionPolicy.RUNTIME)
+public @interface ShellCreateTrigger {}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/ShellCreateTriggerOverride.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/ShellCreateTriggerOverride.java
new file mode 100644
index 0000000..31c6789
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/ShellCreateTriggerOverride.java
@@ -0,0 +1,38 @@
+/*
+ * 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.dagger;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Inherited;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+import javax.inject.Qualifier;
+
+/**
+ * An annotation for non-base modules to specifically mark the provider that is triggering the
+ * creation of independent shell components that are not created as a part of the dependencies for
+ * interfaces passed to SysUI.
+ *
+ * TODO: This will be removed once we have a more explicit method for specifying components to start
+ *       with SysUI
+ */
+@Documented
+@Inherited
+@Qualifier
+@Retention(RetentionPolicy.RUNTIME)
+public @interface ShellCreateTriggerOverride {}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
index a168cb2..3add417 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
@@ -112,16 +112,20 @@
     @WMSingleton
     @Provides
     static DisplayController provideDisplayController(Context context,
-            IWindowManager wmService, @ShellMainThread ShellExecutor mainExecutor) {
-        return new DisplayController(context, wmService, mainExecutor);
+            IWindowManager wmService,
+            ShellInit shellInit,
+            @ShellMainThread ShellExecutor mainExecutor) {
+        return new DisplayController(context, wmService, shellInit, mainExecutor);
     }
 
     @WMSingleton
     @Provides
-    static DisplayInsetsController provideDisplayInsetsController( IWindowManager wmService,
+    static DisplayInsetsController provideDisplayInsetsController(IWindowManager wmService,
+            ShellInit shellInit,
             DisplayController displayController,
             @ShellMainThread ShellExecutor mainExecutor) {
-        return new DisplayInsetsController(wmService, displayController, mainExecutor);
+        return new DisplayInsetsController(wmService, shellInit, displayController,
+                mainExecutor);
     }
 
     // Workaround for dynamic overriding with a default implementation, see {@link DynamicOverride}
@@ -134,16 +138,17 @@
     static DisplayImeController provideDisplayImeController(
             @DynamicOverride Optional<DisplayImeController> overrideDisplayImeController,
             IWindowManager wmService,
+            ShellInit shellInit,
             DisplayController displayController,
             DisplayInsetsController displayInsetsController,
-            @ShellMainThread ShellExecutor mainExecutor,
-            TransactionPool transactionPool
+            TransactionPool transactionPool,
+            @ShellMainThread ShellExecutor mainExecutor
     ) {
         if (overrideDisplayImeController.isPresent()) {
             return overrideDisplayImeController.get();
         }
-        return new DisplayImeController(wmService, displayController, displayInsetsController,
-                mainExecutor, transactionPool);
+        return new DisplayImeController(wmService, shellInit, displayController,
+                displayInsetsController, transactionPool, mainExecutor);
     }
 
     @WMSingleton
@@ -155,42 +160,45 @@
     @WMSingleton
     @Provides
     static DragAndDropController provideDragAndDropController(Context context,
+            ShellInit shellInit,
             ShellController shellController,
             DisplayController displayController,
             UiEventLogger uiEventLogger,
             IconProvider iconProvider,
             @ShellMainThread ShellExecutor mainExecutor) {
-        return new DragAndDropController(context, shellController, displayController, uiEventLogger,
-                iconProvider, mainExecutor);
+        return new DragAndDropController(context, shellInit, shellController, displayController,
+                uiEventLogger, iconProvider, mainExecutor);
     }
 
     @WMSingleton
     @Provides
-    static ShellTaskOrganizer provideShellTaskOrganizer(@ShellMainThread ShellExecutor mainExecutor,
-            Context context,
+    static ShellTaskOrganizer provideShellTaskOrganizer(
+            ShellInit shellInit,
             CompatUIController compatUI,
             Optional<UnfoldAnimationController> unfoldAnimationController,
-            Optional<RecentTasksController> recentTasksOptional
+            Optional<RecentTasksController> recentTasksOptional,
+            @ShellMainThread ShellExecutor mainExecutor
     ) {
-        return new ShellTaskOrganizer(mainExecutor, context, compatUI, unfoldAnimationController,
-                recentTasksOptional);
+        return new ShellTaskOrganizer(shellInit, compatUI, unfoldAnimationController,
+                recentTasksOptional, mainExecutor);
     }
 
     @WMSingleton
     @Provides
     static KidsModeTaskOrganizer provideKidsModeTaskOrganizer(
-            @ShellMainThread ShellExecutor mainExecutor,
-            @ShellMainThread Handler mainHandler,
             Context context,
+            ShellInit shellInit,
             SyncTransactionQueue syncTransactionQueue,
             DisplayController displayController,
             DisplayInsetsController displayInsetsController,
             Optional<UnfoldAnimationController> unfoldAnimationController,
-            Optional<RecentTasksController> recentTasksOptional
+            Optional<RecentTasksController> recentTasksOptional,
+            @ShellMainThread ShellExecutor mainExecutor,
+            @ShellMainThread Handler mainHandler
     ) {
-        return new KidsModeTaskOrganizer(mainExecutor, mainHandler, context, syncTransactionQueue,
+        return new KidsModeTaskOrganizer(context, shellInit, syncTransactionQueue,
                 displayController, displayInsetsController, unfoldAnimationController,
-                recentTasksOptional);
+                recentTasksOptional, mainExecutor, mainHandler);
     }
 
     @WMSingleton
@@ -256,6 +264,20 @@
         return backAnimationController.map(BackAnimationController::getBackAnimationImpl);
     }
 
+    @WMSingleton
+    @Provides
+    static Optional<BackAnimationController> provideBackAnimationController(
+            Context context,
+            @ShellMainThread ShellExecutor shellExecutor,
+            @ShellBackgroundThread Handler backgroundHandler
+    ) {
+        if (BackAnimationController.IS_ENABLED) {
+            return Optional.of(
+                    new BackAnimationController(shellExecutor, backgroundHandler, context));
+        }
+        return Optional.empty();
+    }
+
     //
     // Bubbles (optional feature)
     //
@@ -282,12 +304,15 @@
     @Provides
     static FullscreenTaskListener provideFullscreenTaskListener(
             @DynamicOverride Optional<FullscreenTaskListener> fullscreenTaskListener,
+            ShellInit shellInit,
+            ShellTaskOrganizer shellTaskOrganizer,
             SyncTransactionQueue syncQueue,
             Optional<RecentTasksController> recentTasksOptional) {
         if (fullscreenTaskListener.isPresent()) {
             return fullscreenTaskListener.get();
         } else {
-            return new FullscreenTaskListener(syncQueue, recentTasksOptional);
+            return new FullscreenTaskListener(shellInit, shellTaskOrganizer, syncQueue,
+                    recentTasksOptional);
         }
     }
 
@@ -343,7 +368,7 @@
 
     @WMSingleton
     @Provides
-    static Optional<FreeformComponents> provideFreeformTaskListener(
+    static Optional<FreeformComponents> provideFreeformComponents(
             @DynamicOverride Optional<FreeformComponents> freeformComponents,
             Context context) {
         if (FreeformComponents.isFreeformEnabled(context)) {
@@ -440,11 +465,13 @@
     @Provides
     static Optional<RecentTasksController> provideRecentTasksController(
             Context context,
+            ShellInit shellInit,
             TaskStackListenerImpl taskStackListener,
             @ShellMainThread ShellExecutor mainExecutor
     ) {
         return Optional.ofNullable(
-                RecentTasksController.create(context, taskStackListener, mainExecutor));
+                RecentTasksController.create(context, shellInit, taskStackListener,
+                        mainExecutor));
     }
 
     //
@@ -459,12 +486,15 @@
 
     @WMSingleton
     @Provides
-    static Transitions provideTransitions(ShellTaskOrganizer organizer, TransactionPool pool,
-            DisplayController displayController, Context context,
+    static Transitions provideTransitions(Context context,
+            ShellInit shellInit,
+            ShellTaskOrganizer organizer,
+            TransactionPool pool,
+            DisplayController displayController,
             @ShellMainThread ShellExecutor mainExecutor,
             @ShellMainThread Handler mainHandler,
             @ShellAnimationThread ShellExecutor animExecutor) {
-        return new Transitions(organizer, pool, displayController, context, mainExecutor,
+        return new Transitions(context, shellInit, organizer, pool, displayController, mainExecutor,
                 mainHandler, animExecutor);
     }
 
@@ -542,11 +572,13 @@
     @WMSingleton
     @Provides
     static StartingWindowController provideStartingWindowController(Context context,
+            ShellInit shellInit,
+            ShellTaskOrganizer shellTaskOrganizer,
             @ShellSplashscreenThread ShellExecutor splashScreenExecutor,
             StartingWindowTypeAlgorithm startingWindowTypeAlgorithm, IconProvider iconProvider,
             TransactionPool pool) {
-        return new StartingWindowController(context, splashScreenExecutor,
-                startingWindowTypeAlgorithm, iconProvider, pool);
+        return new StartingWindowController(context, shellInit, shellTaskOrganizer,
+                splashScreenExecutor, startingWindowTypeAlgorithm, iconProvider, pool);
     }
 
     // Workaround for dynamic overriding with a default implementation, see {@link DynamicOverride}
@@ -595,9 +627,11 @@
 
     @WMSingleton
     @Provides
-    static Optional<ActivityEmbeddingController> provideActivityEmbeddingController(
-            Context context, Transitions transitions) {
-        return Optional.of(new ActivityEmbeddingController(context, transitions));
+    static ActivityEmbeddingController provideActivityEmbeddingController(
+            Context context,
+            ShellInit shellInit,
+            Transitions transitions) {
+        return new ActivityEmbeddingController(context, shellInit, transitions);
     }
 
     //
@@ -606,24 +640,34 @@
 
     @WMSingleton
     @Provides
-    static ShellInterface provideShellSysuiCallbacks(ShellController shellController) {
+    static ShellInterface provideShellSysuiCallbacks(
+            @ShellCreateTrigger Object createTrigger,
+            ShellController shellController) {
         return shellController.asShell();
     }
 
     @WMSingleton
     @Provides
-    static ShellController provideShellController(@ShellMainThread ShellExecutor mainExecutor) {
-        return new ShellController(mainExecutor);
+    static ShellController provideShellController(ShellInit shellInit,
+            @ShellMainThread ShellExecutor mainExecutor) {
+        return new ShellController(shellInit, mainExecutor);
     }
 
     //
     // Misc
     //
 
+    // Workaround for dynamic overriding with a default implementation, see {@link DynamicOverride}
+    @BindsOptionalOf
+    @ShellCreateTriggerOverride
+    abstract Object provideIndependentShellComponentsToCreateOverride();
+
+    // TODO: Temporarily move dependencies to this instead of ShellInit since that is needed to add
+    // the callback. We will be moving to a different explicit startup mechanism in a follow- up CL.
     @WMSingleton
+    @ShellCreateTrigger
     @Provides
-    static ShellInit provideShellInitImpl(
-            ShellController shellController,
+    static Object provideIndependentShellComponentsToCreate(
             DisplayController displayController,
             DisplayImeController displayImeController,
             DisplayInsetsController displayInsetsController,
@@ -638,29 +682,17 @@
             Optional<UnfoldTransitionHandler> unfoldTransitionHandler,
             Optional<FreeformComponents> freeformComponents,
             Optional<RecentTasksController> recentTasksOptional,
-            Optional<ActivityEmbeddingController> activityEmbeddingOptional,
+            ActivityEmbeddingController activityEmbeddingOptional,
             Transitions transitions,
             StartingWindowController startingWindow,
-            @ShellMainThread ShellExecutor mainExecutor) {
-        return new ShellInit(shellController,
-                displayController,
-                displayImeController,
-                displayInsetsController,
-                dragAndDropController,
-                shellTaskOrganizer,
-                kidsModeTaskOrganizer,
-                bubblesOptional,
-                splitScreenOptional,
-                pipTouchHandlerOptional,
-                fullscreenTaskListener,
-                unfoldAnimationController,
-                unfoldTransitionHandler,
-                freeformComponents,
-                recentTasksOptional,
-                activityEmbeddingOptional,
-                transitions,
-                startingWindow,
-                mainExecutor);
+            @ShellCreateTriggerOverride Optional<Object> overriddenCreateTrigger) {
+        return new Object();
+    }
+
+    @WMSingleton
+    @Provides
+    static ShellInit provideShellInit(@ShellMainThread ShellExecutor mainExecutor) {
+        return new ShellInit(mainExecutor);
     }
 
     @WMSingleton
@@ -679,18 +711,4 @@
                 kidsModeTaskOrganizer, splitScreenOptional, pipOptional, oneHandedOptional,
                 hideDisplayCutout, recentTasksOptional, mainExecutor);
     }
-
-    @WMSingleton
-    @Provides
-    static Optional<BackAnimationController> provideBackAnimationController(
-            Context context,
-            @ShellMainThread ShellExecutor shellExecutor,
-            @ShellBackgroundThread Handler backgroundHandler
-    ) {
-        if (BackAnimationController.IS_ENABLED) {
-            return Optional.of(
-                    new BackAnimationController(shellExecutor, backgroundHandler, context));
-        }
-        return Optional.empty();
-    }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
index 588a1aa..e2bf767 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
@@ -75,6 +75,8 @@
 import com.android.wm.shell.recents.RecentTasksController;
 import com.android.wm.shell.splitscreen.SplitScreenController;
 import com.android.wm.shell.sysui.ShellController;
+import com.android.wm.shell.sysui.ShellInit;
+import com.android.wm.shell.transition.SplitscreenPipMixedHandler;
 import com.android.wm.shell.transition.Transitions;
 import com.android.wm.shell.unfold.ShellUnfoldProgressProvider;
 import com.android.wm.shell.unfold.UnfoldAnimationController;
@@ -138,6 +140,7 @@
     @WMSingleton
     @Provides
     static BubbleController provideBubbleController(Context context,
+            ShellInit shellInit,
             ShellController shellController,
             BubbleData data,
             FloatingContentCoordinator floatingContentCoordinator,
@@ -158,8 +161,8 @@
             @ShellBackgroundThread ShellExecutor bgExecutor,
             TaskViewTransitions taskViewTransitions,
             SyncTransactionQueue syncQueue) {
-        return new BubbleController(context, shellController, data, null /* synchronizer */,
-                floatingContentCoordinator,
+        return new BubbleController(context, shellInit, shellController, data,
+                null /* synchronizer */, floatingContentCoordinator,
                 new BubbleDataRepository(context, launcherApps, mainExecutor),
                 statusBarService, windowManager, windowManagerShellWrapper, userManager,
                 launcherApps, logger, taskStackListener, organizer, positioner, displayController,
@@ -205,18 +208,34 @@
     @WMSingleton
     @Provides
     static FreeformTaskListener<?> provideFreeformTaskListener(
+            Context context,
+            ShellInit shellInit,
+            ShellTaskOrganizer shellTaskOrganizer,
             WindowDecorViewModel<?> windowDecorViewModel) {
-        return new FreeformTaskListener<>(windowDecorViewModel);
+        // TODO(b/238217847): Temporarily add this check here until we can remove the dynamic
+        //                    override for this controller from the base module
+        ShellInit init = FreeformComponents.isFreeformEnabled(context)
+                ? shellInit
+                : null;
+        return new FreeformTaskListener<>(init, shellTaskOrganizer,
+                windowDecorViewModel);
     }
 
     @WMSingleton
     @Provides
-    static FreeformTaskTransitionHandler provideTaskTransitionHandler(
+    static FreeformTaskTransitionHandler provideFreeformTaskTransitionHandler(
+            Context context,
+            ShellInit shellInit,
             Transitions transitions,
             WindowDecorViewModel<?> windowDecorViewModel,
             FreeformTaskListener<?> freeformTaskListener) {
-        return new FreeformTaskTransitionHandler(transitions, windowDecorViewModel,
-                freeformTaskListener);
+        // TODO(b/238217847): Temporarily add this check here until we can remove the dynamic
+        //                    override for this controller from the base module
+        ShellInit init = FreeformComponents.isFreeformEnabled(context)
+                ? shellInit
+                : null;
+        return new FreeformTaskTransitionHandler(init, transitions,
+                windowDecorViewModel, freeformTaskListener);
     }
 
     //
@@ -247,20 +266,25 @@
     @Provides
     @DynamicOverride
     static SplitScreenController provideSplitScreenController(
+            Context context,
+            ShellInit shellInit,
             ShellController shellController,
             ShellTaskOrganizer shellTaskOrganizer,
-            SyncTransactionQueue syncQueue, Context context,
+            SyncTransactionQueue syncQueue,
             RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer,
-            @ShellMainThread ShellExecutor mainExecutor,
             DisplayController displayController,
             DisplayImeController displayImeController,
-            DisplayInsetsController displayInsetsController, Transitions transitions,
-            TransactionPool transactionPool, IconProvider iconProvider,
-            Optional<RecentTasksController> recentTasks) {
-        return new SplitScreenController(shellController, shellTaskOrganizer, syncQueue, context,
-                rootTaskDisplayAreaOrganizer, mainExecutor, displayController, displayImeController,
-                displayInsetsController, transitions, transactionPool, iconProvider,
-                recentTasks);
+            DisplayInsetsController displayInsetsController,
+            DragAndDropController dragAndDropController,
+            Transitions transitions,
+            TransactionPool transactionPool,
+            IconProvider iconProvider,
+            Optional<RecentTasksController> recentTasks,
+            @ShellMainThread ShellExecutor mainExecutor) {
+        return new SplitScreenController(context, shellInit, shellController, shellTaskOrganizer,
+                syncQueue, rootTaskDisplayAreaOrganizer, displayController, displayImeController,
+                displayInsetsController, dragAndDropController, transitions, transactionPool,
+                iconProvider, recentTasks, mainExecutor);
     }
 
     //
@@ -332,14 +356,16 @@
     @WMSingleton
     @Provides
     static PipTouchHandler providePipTouchHandler(Context context,
-            PhonePipMenuController menuPhoneController, PipBoundsAlgorithm pipBoundsAlgorithm,
+            ShellInit shellInit,
+            PhonePipMenuController menuPhoneController,
+            PipBoundsAlgorithm pipBoundsAlgorithm,
             PipBoundsState pipBoundsState,
             PipTaskOrganizer pipTaskOrganizer,
             PipMotionHelper pipMotionHelper,
             FloatingContentCoordinator floatingContentCoordinator,
             PipUiEventLogger pipUiEventLogger,
             @ShellMainThread ShellExecutor mainExecutor) {
-        return new PipTouchHandler(context, menuPhoneController, pipBoundsAlgorithm,
+        return new PipTouchHandler(context, shellInit, menuPhoneController, pipBoundsAlgorithm,
                 pipBoundsState, pipTaskOrganizer, pipMotionHelper,
                 floatingContentCoordinator, pipUiEventLogger, mainExecutor);
     }
@@ -414,9 +440,31 @@
                 floatingContentCoordinator);
     }
 
+    @WMSingleton
+    @Provides
+    static PipParamsChangedForwarder providePipParamsChangedForwarder() {
+        return new PipParamsChangedForwarder();
+    }
+
+    //
+    // Transitions
+    //
+
+    @WMSingleton
+    @Provides
+    static SplitscreenPipMixedHandler provideSplitscreenPipMixedHandler(
+            ShellInit shellInit,
+            Optional<SplitScreenController> splitScreenOptional,
+            Optional<PipTouchHandler> pipTouchHandlerOptional,
+            Transitions transitions) {
+        return new SplitscreenPipMixedHandler(shellInit, splitScreenOptional,
+                pipTouchHandlerOptional, transitions);
+    }
+
     //
     // Unfold transition
     //
+
     @WMSingleton
     @Provides
     @DynamicOverride
@@ -426,6 +474,7 @@
             @UnfoldTransition SplitTaskUnfoldAnimator splitAnimator,
             FullscreenUnfoldTaskAnimator fullscreenAnimator,
             Lazy<Optional<UnfoldTransitionHandler>> unfoldTransitionHandler,
+            ShellInit shellInit,
             @ShellMainThread ShellExecutor mainExecutor
     ) {
         final List<UnfoldTaskAnimator> animators = new ArrayList<>();
@@ -433,6 +482,7 @@
         animators.add(fullscreenAnimator);
 
         return new UnfoldAnimationController(
+                        shellInit,
                         transactionPool,
                         progressProvider.get(),
                         animators,
@@ -441,7 +491,6 @@
                 );
     }
 
-
     @Provides
     static FullscreenUnfoldTaskAnimator provideFullscreenUnfoldTaskAnimator(
             Context context,
@@ -460,6 +509,10 @@
             Lazy<Optional<SplitScreenController>> splitScreenOptional,
             DisplayInsetsController displayInsetsController
     ) {
+        // TODO(b/238217847): The lazy reference here causes some dependency issues since it
+        // immediately registers a listener on that controller on init.  We should reference the
+        // controller directly once we refactor ShellTaskOrganizer to not depend on the unfold
+        // animation controller directly.
         return new SplitTaskUnfoldAnimator(context, executor, splitScreenOptional,
                 backgroundController, displayInsetsController);
     }
@@ -485,8 +538,9 @@
             @UnfoldShellTransition SplitTaskUnfoldAnimator unfoldAnimator,
             TransactionPool transactionPool,
             Transitions transitions,
-            @ShellMainThread ShellExecutor executor) {
-        return new UnfoldTransitionHandler(progressProvider.get(), animator,
+            @ShellMainThread ShellExecutor executor,
+            ShellInit shellInit) {
+        return new UnfoldTransitionHandler(shellInit, progressProvider.get(), animator,
                 unfoldAnimator, transactionPool, executor, transitions);
     }
 
@@ -502,9 +556,17 @@
         );
     }
 
+    //
+    // Misc
+    //
+
+    // TODO: Temporarily move dependencies to this instead of ShellInit since that is needed to add
+    // the callback. We will be moving to a different explicit startup mechanism in a follow- up CL.
     @WMSingleton
+    @ShellCreateTriggerOverride
     @Provides
-    static PipParamsChangedForwarder providePipParamsChangedForwarder() {
-        return new PipParamsChangedForwarder();
+    static Object provideIndependentShellComponentsToCreate(
+            SplitscreenPipMixedHandler splitscreenPipMixedHandler) {
+        return new Object();
     }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/docs/changes.md b/libs/WindowManager/Shell/src/com/android/wm/shell/docs/changes.md
index f4e2f20..2aa933d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/docs/changes.md
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/docs/changes.md
@@ -44,10 +44,24 @@
 
 ### Component initialization
 To initialize the component:
-- On the Shell side, update `ShellInitImpl` to get a signal to initialize when the SysUI is started
+- On the Shell side, you potentially need to do two things to initialize the component:
+  - Inject `ShellInit` into your component and add an init callback
+  - Ensure that your component is a part of the dagger dependency graph, either by:
+    - Making this component a dependency of an existing component already exposed to SystemUI
+    - Explicitly add this component to the WMShellBaseModule @ShellCreateTrigger provider or
+      the @ShellCreateTriggerOverride provider for your product module to expose it explicitly 
+      if it is a completely independent component
 - On the SysUI side, update `WMShell` to setup any bindings for the component that depend on
   SysUI code
 
+To verify that your component is being initialized at startup, you can enable the `WM_SHELL_INIT` 
+protolog group and restart the SysUI process:
+```shell
+adb shell wm logging enable-text WM_SHELL_INIT
+adb shell kill `pid com.android.systemui`
+adb logcat *:S WindowManagerShell
+```
+
 ### General Do's & Dont's
 Do:
 - Do add unit tests for all new components
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropController.java
index c5df53b..4697a01 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropController.java
@@ -62,9 +62,9 @@
 import com.android.wm.shell.splitscreen.SplitScreenController;
 import com.android.wm.shell.sysui.ConfigurationChangeListener;
 import com.android.wm.shell.sysui.ShellController;
+import com.android.wm.shell.sysui.ShellInit;
 
 import java.util.ArrayList;
-import java.util.Optional;
 
 /**
  * Handles the global drag and drop handling for the Shell.
@@ -94,6 +94,7 @@
     }
 
     public DragAndDropController(Context context,
+            ShellInit shellInit,
             ShellController shellController,
             DisplayController displayController,
             UiEventLogger uiEventLogger,
@@ -105,14 +106,29 @@
         mLogger = new DragAndDropEventLogger(uiEventLogger);
         mIconProvider = iconProvider;
         mMainExecutor = mainExecutor;
+        shellInit.addInitCallback(this::onInit, this);
     }
 
-    public void initialize(Optional<SplitScreenController> splitscreen) {
-        mSplitScreen = splitscreen.orElse(null);
-        mDisplayController.addDisplayWindowListener(this);
+    /**
+     * Called when the controller is initialized.
+     */
+    public void onInit() {
+        // TODO(b/238217847): The dependency from SplitscreenController on DragAndDropController is
+        // inverted, which leads to SplitscreenController not setting its instance until after
+        // onDisplayAdded.  We can remove this post once we fix that dependency.
+        mMainExecutor.executeDelayed(() -> {
+            mDisplayController.addDisplayWindowListener(this);
+        }, 0);
         mShellController.addConfigurationChangeListener(this);
     }
 
+    /**
+     * Sets the splitscreen controller to use if the feature is available.
+     */
+    public void setSplitScreenController(SplitScreenController splitscreen) {
+        mSplitScreen = splitscreen;
+    }
+
     /** Adds a listener to be notified of drag and drop events. */
     public void addListener(DragAndDropListener listener) {
         mListeners.add(listener);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java
index ba093a5..ab66107 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java
@@ -16,6 +16,8 @@
 
 package com.android.wm.shell.freeform;
 
+import static com.android.wm.shell.ShellTaskOrganizer.TASK_LISTENER_TYPE_FREEFORM;
+
 import android.app.ActivityManager.RunningTaskInfo;
 import android.util.Log;
 import android.util.SparseArray;
@@ -27,6 +29,7 @@
 import com.android.internal.protolog.common.ProtoLog;
 import com.android.wm.shell.ShellTaskOrganizer;
 import com.android.wm.shell.protolog.ShellProtoLogGroup;
+import com.android.wm.shell.sysui.ShellInit;
 import com.android.wm.shell.transition.Transitions;
 import com.android.wm.shell.windowdecor.WindowDecorViewModel;
 
@@ -42,6 +45,7 @@
         implements ShellTaskOrganizer.TaskListener {
     private static final String TAG = "FreeformTaskListener";
 
+    private final ShellTaskOrganizer mShellTaskOrganizer;
     private final WindowDecorViewModel<T> mWindowDecorationViewModel;
 
     private final SparseArray<State<T>> mTasks = new SparseArray<>();
@@ -53,8 +57,19 @@
         T mWindowDecoration;
     }
 
-    public FreeformTaskListener(WindowDecorViewModel<T> windowDecorationViewModel) {
+    public FreeformTaskListener(
+            ShellInit shellInit,
+            ShellTaskOrganizer shellTaskOrganizer,
+            WindowDecorViewModel<T> windowDecorationViewModel) {
+        mShellTaskOrganizer = shellTaskOrganizer;
         mWindowDecorationViewModel = windowDecorationViewModel;
+        if (shellInit != null) {
+            shellInit.addInitCallback(this::onInit, this);
+        }
+    }
+
+    private void onInit() {
+        mShellTaskOrganizer.addListenerForType(this, TASK_LISTENER_TYPE_FREEFORM);
     }
 
     @Override
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionHandler.java
index 8731eb6..20d7725 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionHandler.java
@@ -32,6 +32,7 @@
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 
+import com.android.wm.shell.sysui.ShellInit;
 import com.android.wm.shell.transition.Transitions;
 import com.android.wm.shell.windowdecor.WindowDecorViewModel;
 
@@ -49,17 +50,26 @@
 
     private final Transitions mTransitions;
     private final FreeformTaskListener<?> mFreeformTaskListener;
+    private final WindowDecorViewModel<?> mWindowDecorViewModel;
 
     private final List<IBinder> mPendingTransitionTokens = new ArrayList<>();
 
     public FreeformTaskTransitionHandler(
+            ShellInit shellInit,
             Transitions transitions,
             WindowDecorViewModel<?> windowDecorViewModel,
             FreeformTaskListener<?> freeformTaskListener) {
         mTransitions = transitions;
         mFreeformTaskListener = freeformTaskListener;
+        mWindowDecorViewModel = windowDecorViewModel;
+        if (shellInit != null && Transitions.ENABLE_SHELL_TRANSITIONS) {
+            shellInit.addInitCallback(this::onInit, this);
+        }
+    }
 
-        windowDecorViewModel.setFreeformTaskTransitionStarter(this);
+    private void onInit() {
+        mWindowDecorViewModel.setFreeformTaskTransitionStarter(this);
+        mTransitions.addHandler(this);
     }
 
     @Override
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/fullscreen/FullscreenTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/fullscreen/FullscreenTaskListener.java
index 79e363b..0ba4afc 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/fullscreen/FullscreenTaskListener.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/fullscreen/FullscreenTaskListener.java
@@ -32,6 +32,7 @@
 import com.android.wm.shell.common.SyncTransactionQueue;
 import com.android.wm.shell.protolog.ShellProtoLogGroup;
 import com.android.wm.shell.recents.RecentTasksController;
+import com.android.wm.shell.sysui.ShellInit;
 import com.android.wm.shell.transition.Transitions;
 
 import java.io.PrintWriter;
@@ -43,19 +44,34 @@
 public class FullscreenTaskListener implements ShellTaskOrganizer.TaskListener {
     private static final String TAG = "FullscreenTaskListener";
 
+    private final ShellTaskOrganizer mShellTaskOrganizer;
     private final SyncTransactionQueue mSyncQueue;
     private final Optional<RecentTasksController> mRecentTasksOptional;
 
     private final SparseArray<TaskData> mDataByTaskId = new SparseArray<>();
 
+    /**
+     * This constructor is used by downstream products.
+     */
     public FullscreenTaskListener(SyncTransactionQueue syncQueue) {
-        this(syncQueue, Optional.empty());
+        this(null /* shellInit */, null /* shellTaskOrganizer */, syncQueue, Optional.empty());
     }
 
-    public FullscreenTaskListener(SyncTransactionQueue syncQueue,
-            Optional<RecentTasksController> recentTasks) {
+    public FullscreenTaskListener(ShellInit shellInit,
+            ShellTaskOrganizer shellTaskOrganizer,
+            SyncTransactionQueue syncQueue,
+            Optional<RecentTasksController> recentTasksOptional) {
+        mShellTaskOrganizer = shellTaskOrganizer;
         mSyncQueue = syncQueue;
-        mRecentTasksOptional = recentTasks;
+        mRecentTasksOptional = recentTasksOptional;
+        // Note: Some derivative FullscreenTaskListener implementations do not use ShellInit
+        if (shellInit != null) {
+            shellInit.addInitCallback(this::onInit, this);
+        }
+    }
+
+    private void onInit() {
+        mShellTaskOrganizer.addListenerForType(this, TASK_LISTENER_TYPE_FULLSCREEN);
     }
 
     @Override
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/kidsmode/KidsModeTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/kidsmode/KidsModeTaskOrganizer.java
index 2c8ba09..73b9b89 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/kidsmode/KidsModeTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/kidsmode/KidsModeTaskOrganizer.java
@@ -50,7 +50,7 @@
 import com.android.wm.shell.common.ShellExecutor;
 import com.android.wm.shell.common.SyncTransactionQueue;
 import com.android.wm.shell.recents.RecentTasksController;
-import com.android.wm.shell.startingsurface.StartingWindowController;
+import com.android.wm.shell.sysui.ShellInit;
 import com.android.wm.shell.unfold.UnfoldAnimationController;
 
 import java.io.PrintWriter;
@@ -140,18 +140,18 @@
 
     @VisibleForTesting
     KidsModeTaskOrganizer(
-            ITaskOrganizerController taskOrganizerController,
-            ShellExecutor mainExecutor,
-            Handler mainHandler,
             Context context,
+            ITaskOrganizerController taskOrganizerController,
             SyncTransactionQueue syncTransactionQueue,
             DisplayController displayController,
             DisplayInsetsController displayInsetsController,
             Optional<UnfoldAnimationController> unfoldAnimationController,
             Optional<RecentTasksController> recentTasks,
-            KidsModeSettingsObserver kidsModeSettingsObserver) {
-        super(taskOrganizerController, mainExecutor, context, /* compatUI= */ null,
-                unfoldAnimationController, recentTasks);
+            KidsModeSettingsObserver kidsModeSettingsObserver,
+            ShellExecutor mainExecutor,
+            Handler mainHandler) {
+        super(/* shellInit= */ null, taskOrganizerController, /* compatUI= */ null,
+                unfoldAnimationController, recentTasks, mainExecutor);
         mContext = context;
         mMainHandler = mainHandler;
         mSyncQueue = syncTransactionQueue;
@@ -161,27 +161,30 @@
     }
 
     public KidsModeTaskOrganizer(
-            ShellExecutor mainExecutor,
-            Handler mainHandler,
             Context context,
+            ShellInit shellInit,
             SyncTransactionQueue syncTransactionQueue,
             DisplayController displayController,
             DisplayInsetsController displayInsetsController,
             Optional<UnfoldAnimationController> unfoldAnimationController,
-            Optional<RecentTasksController> recentTasks) {
-        super(mainExecutor, context, /* compatUI= */ null, unfoldAnimationController, recentTasks);
+            Optional<RecentTasksController> recentTasks,
+            ShellExecutor mainExecutor,
+            Handler mainHandler) {
+        // Note: we don't call super with the shell init because we will be initializing manually
+        super(/* shellInit= */ null, /* compatUI= */ null, unfoldAnimationController, recentTasks,
+                mainExecutor);
         mContext = context;
         mMainHandler = mainHandler;
         mSyncQueue = syncTransactionQueue;
         mDisplayController = displayController;
         mDisplayInsetsController = displayInsetsController;
+        shellInit.addInitCallback(this::onInit, this);
     }
 
     /**
      * Initializes kids mode status.
      */
-    public void initialize(StartingWindowController startingWindowController) {
-        initStartingWindow(startingWindowController);
+    public void onInit() {
         if (mKidsModeSettingsObserver == null) {
             mKidsModeSettingsObserver = new KidsModeSettingsObserver(mMainHandler, mContext);
         }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java
index a2ff972..c86c136 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java
@@ -57,6 +57,7 @@
 import com.android.wm.shell.pip.PipTransitionController;
 import com.android.wm.shell.pip.PipUiEventLogger;
 import com.android.wm.shell.protolog.ShellProtoLogGroup;
+import com.android.wm.shell.sysui.ShellInit;
 
 import java.io.PrintWriter;
 
@@ -165,6 +166,7 @@
 
     @SuppressLint("InflateParams")
     public PipTouchHandler(Context context,
+            ShellInit shellInit,
             PhonePipMenuController menuController,
             PipBoundsAlgorithm pipBoundsAlgorithm,
             @NonNull PipBoundsState pipBoundsState,
@@ -173,7 +175,6 @@
             FloatingContentCoordinator floatingContentCoordinator,
             PipUiEventLogger pipUiEventLogger,
             ShellExecutor mainExecutor) {
-        // Initialize the Pip input consumer
         mContext = context;
         mMainExecutor = mainExecutor;
         mAccessibilityManager = context.getSystemService(AccessibilityManager.class);
@@ -213,9 +214,17 @@
                 mMotionHelper, pipTaskOrganizer, mPipBoundsAlgorithm.getSnapAlgorithm(),
                 this::onAccessibilityShowMenu, this::updateMovementBounds,
                 this::animateToUnStashedState, mainExecutor);
+
+        // TODO(b/181599115): This should really be initializes as part of the pip controller, but
+        // until all PIP implementations derive from the controller, just initialize the touch handler
+        // if it is needed
+        shellInit.addInitCallback(this::onInit, this);
     }
 
-    public void init() {
+    /**
+     * Called when the touch handler is initialized.
+     */
+    public void onInit() {
         Resources res = mContext.getResources();
         mEnableResize = res.getBoolean(R.bool.config_pipEnableResizeForMenu);
         reloadResources();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java
index 49042e6..3d1a7e9 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java
@@ -44,6 +44,7 @@
 import com.android.wm.shell.common.annotations.ExternalThread;
 import com.android.wm.shell.common.annotations.ShellMainThread;
 import com.android.wm.shell.protolog.ShellProtoLogGroup;
+import com.android.wm.shell.sysui.ShellInit;
 import com.android.wm.shell.util.GroupedRecentTaskInfo;
 import com.android.wm.shell.util.SplitBounds;
 
@@ -85,28 +86,32 @@
     @Nullable
     public static RecentTasksController create(
             Context context,
+            ShellInit shellInit,
             TaskStackListenerImpl taskStackListener,
             @ShellMainThread ShellExecutor mainExecutor
     ) {
         if (!context.getResources().getBoolean(com.android.internal.R.bool.config_hasRecents)) {
             return null;
         }
-        return new RecentTasksController(context, taskStackListener, mainExecutor);
+        return new RecentTasksController(context, shellInit, taskStackListener, mainExecutor);
     }
 
-    RecentTasksController(Context context, TaskStackListenerImpl taskStackListener,
+    RecentTasksController(Context context,
+            ShellInit shellInit,
+            TaskStackListenerImpl taskStackListener,
             ShellExecutor mainExecutor) {
         mContext = context;
         mIsDesktopMode = mContext.getPackageManager().hasSystemFeature(FEATURE_PC);
         mTaskStackListener = taskStackListener;
         mMainExecutor = mainExecutor;
+        shellInit.addInitCallback(this::onInit, this);
     }
 
     public RecentTasks asRecentTasks() {
         return mImpl;
     }
 
-    public void init() {
+    private void onInit() {
         mTaskStackListener.addListener(this);
     }
 
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..1be17f9 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;
@@ -76,12 +78,14 @@
 import com.android.wm.shell.common.annotations.ExternalThread;
 import com.android.wm.shell.common.split.SplitLayout;
 import com.android.wm.shell.common.split.SplitScreenConstants.SplitPosition;
+import com.android.wm.shell.draganddrop.DragAndDropController;
 import com.android.wm.shell.draganddrop.DragAndDropPolicy;
 import com.android.wm.shell.protolog.ShellProtoLogGroup;
 import com.android.wm.shell.recents.RecentTasksController;
 import com.android.wm.shell.splitscreen.SplitScreen.StageType;
 import com.android.wm.shell.sysui.KeyguardChangeListener;
 import com.android.wm.shell.sysui.ShellController;
+import com.android.wm.shell.sysui.ShellInit;
 import com.android.wm.shell.transition.LegacyTransitions;
 import com.android.wm.shell.transition.Transitions;
 
@@ -139,6 +143,7 @@
     private final DisplayController mDisplayController;
     private final DisplayImeController mDisplayImeController;
     private final DisplayInsetsController mDisplayInsetsController;
+    private final DragAndDropController mDragAndDropController;
     private final Transitions mTransitions;
     private final TransactionPool mTransactionPool;
     private final SplitscreenEventLogger mLogger;
@@ -150,15 +155,21 @@
     // outside the bounds of the roots by being reparented into a higher level fullscreen container
     private SurfaceControl mSplitTasksContainerLayer;
 
-    public SplitScreenController(ShellController shellController,
+    public SplitScreenController(Context context,
+            ShellInit shellInit,
+            ShellController shellController,
             ShellTaskOrganizer shellTaskOrganizer,
-            SyncTransactionQueue syncQueue, Context context,
+            SyncTransactionQueue syncQueue,
             RootTaskDisplayAreaOrganizer rootTDAOrganizer,
-            ShellExecutor mainExecutor, DisplayController displayController,
+            DisplayController displayController,
             DisplayImeController displayImeController,
             DisplayInsetsController displayInsetsController,
-            Transitions transitions, TransactionPool transactionPool, IconProvider iconProvider,
-            Optional<RecentTasksController> recentTasks) {
+            DragAndDropController dragAndDropController,
+            Transitions transitions,
+            TransactionPool transactionPool,
+            IconProvider iconProvider,
+            Optional<RecentTasksController> recentTasks,
+            ShellExecutor mainExecutor) {
         mShellController = shellController;
         mTaskOrganizer = shellTaskOrganizer;
         mSyncQueue = syncQueue;
@@ -168,17 +179,40 @@
         mDisplayController = displayController;
         mDisplayImeController = displayImeController;
         mDisplayInsetsController = displayInsetsController;
+        mDragAndDropController = dragAndDropController;
         mTransitions = transitions;
         mTransactionPool = transactionPool;
         mLogger = new SplitscreenEventLogger();
         mIconProvider = iconProvider;
         mRecentTasksOptional = recentTasks;
+        // TODO(b/238217847): Temporarily add this check here until we can remove the dynamic
+        //                    override for this controller from the base module
+        if (ActivityTaskManager.supportsSplitScreenMultiWindow(context)) {
+            shellInit.addInitCallback(this::onInit, this);
+        }
     }
 
     public SplitScreen asSplitScreen() {
         return mImpl;
     }
 
+    /**
+     * This will be called after ShellTaskOrganizer has initialized/registered because of the
+     * dependency order.
+     */
+    @VisibleForTesting
+    void onInit() {
+        mShellController.addKeyguardChangeListener(this);
+        if (mStageCoordinator == null) {
+            // TODO: Multi-display
+            mStageCoordinator = new StageCoordinator(mContext, DEFAULT_DISPLAY, mSyncQueue,
+                    mTaskOrganizer, mDisplayController, mDisplayImeController,
+                    mDisplayInsetsController, mTransitions, mTransactionPool, mLogger,
+                    mIconProvider, mMainExecutor, mRecentTasksOptional);
+        }
+        mDragAndDropController.setSplitScreenController(this);
+    }
+
     @Override
     public Context getContext() {
         return mContext;
@@ -189,17 +223,6 @@
         return mMainExecutor;
     }
 
-    public void onOrganizerRegistered() {
-        mShellController.addKeyguardChangeListener(this);
-        if (mStageCoordinator == null) {
-            // TODO: Multi-display
-            mStageCoordinator = new StageCoordinator(mContext, DEFAULT_DISPLAY, mSyncQueue,
-                    mTaskOrganizer, mDisplayController, mDisplayImeController,
-                    mDisplayInsetsController, mTransitions, mTransactionPool, mLogger,
-                    mIconProvider, mMainExecutor, mRecentTasksOptional);
-        }
-    }
-
     public boolean isSplitScreenVisible() {
         return mStageCoordinator.isSplitScreenVisible();
     }
@@ -334,17 +357,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 66f5bb6..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
@@ -610,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/startingsurface/StartingWindowController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingWindowController.java
index fbc9923..379af21 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingWindowController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingWindowController.java
@@ -42,10 +42,12 @@
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.util.function.TriConsumer;
 import com.android.launcher3.icons.IconProvider;
+import com.android.wm.shell.ShellTaskOrganizer;
 import com.android.wm.shell.common.RemoteCallable;
 import com.android.wm.shell.common.ShellExecutor;
 import com.android.wm.shell.common.SingleInstanceRemoteListener;
 import com.android.wm.shell.common.TransactionPool;
+import com.android.wm.shell.sysui.ShellInit;
 
 /**
  * Implementation to draw the starting window to an application, and remove the starting window
@@ -74,6 +76,7 @@
     private TriConsumer<Integer, Integer, Integer> mTaskLaunchingCallback;
     private final StartingSurfaceImpl mImpl = new StartingSurfaceImpl();
     private final Context mContext;
+    private final ShellTaskOrganizer mShellTaskOrganizer;
     private final ShellExecutor mSplashScreenExecutor;
     /**
      * Need guarded because it has exposed to StartingSurface
@@ -81,14 +84,20 @@
     @GuardedBy("mTaskBackgroundColors")
     private final SparseIntArray mTaskBackgroundColors = new SparseIntArray();
 
-    public StartingWindowController(Context context, ShellExecutor splashScreenExecutor,
-            StartingWindowTypeAlgorithm startingWindowTypeAlgorithm, IconProvider iconProvider,
+    public StartingWindowController(Context context,
+            ShellInit shellInit,
+            ShellTaskOrganizer shellTaskOrganizer,
+            ShellExecutor splashScreenExecutor,
+            StartingWindowTypeAlgorithm startingWindowTypeAlgorithm,
+            IconProvider iconProvider,
             TransactionPool pool) {
         mContext = context;
+        mShellTaskOrganizer = shellTaskOrganizer;
         mStartingSurfaceDrawer = new StartingSurfaceDrawer(context, splashScreenExecutor,
                 iconProvider, pool);
         mStartingWindowTypeAlgorithm = startingWindowTypeAlgorithm;
         mSplashScreenExecutor = splashScreenExecutor;
+        shellInit.addInitCallback(this::onInit, this);
     }
 
     /**
@@ -98,6 +107,10 @@
         return mImpl;
     }
 
+    private void onInit() {
+        mShellTaskOrganizer.initStartingWindow(this);
+    }
+
     @Override
     public Context getContext() {
         return mContext;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellCommandHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellCommandHandler.java
index 0427efb..f4fc0c4 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellCommandHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellCommandHandler.java
@@ -45,7 +45,6 @@
     private final Optional<RecentTasksController> mRecentTasks;
     private final ShellTaskOrganizer mShellTaskOrganizer;
     private final KidsModeTaskOrganizer mKidsModeTaskOrganizer;
-    private final ShellExecutor mMainExecutor;
 
     public ShellCommandHandler(
             ShellController shellController,
@@ -64,7 +63,6 @@
         mPipOptional = pipOptional;
         mOneHandedOptional = oneHandedOptional;
         mHideDisplayCutout = hideDisplayCutout;
-        mMainExecutor = mainExecutor;
         // TODO(238217847): To be removed once the command handler dependencies are inverted
         shellController.setShellCommandHandler(this);
     }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellController.java
index 618028c..f1f317f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellController.java
@@ -44,10 +44,10 @@
 public class ShellController {
     private static final String TAG = ShellController.class.getSimpleName();
 
+    private final ShellInit mShellInit;
     private final ShellExecutor mMainExecutor;
     private final ShellInterfaceImpl mImpl = new ShellInterfaceImpl();
 
-    private ShellInit mShellInit;
     private ShellCommandHandler mShellCommandHandler;
 
     private final CopyOnWriteArrayList<ConfigurationChangeListener> mConfigChangeListeners =
@@ -57,7 +57,8 @@
     private Configuration mLastConfiguration;
 
 
-    public ShellController(ShellExecutor mainExecutor) {
+    public ShellController(ShellInit shellInit, ShellExecutor mainExecutor) {
+        mShellInit = shellInit;
         mMainExecutor = mainExecutor;
     }
 
@@ -69,15 +70,6 @@
     }
 
     /**
-     * Sets the init handler to call back to.
-     * TODO(238217847): This is only exposed this way until we can remove the dependencies from the
-     *                  init handler to other classes.
-     */
-    public void setShellInit(ShellInit shellInit) {
-        mShellInit = shellInit;
-    }
-
-    /**
      * Sets the command handler to call back to.
      * TODO(238217847): This is only exposed this way until we can remove the dependencies from the
      *                  command handler to other classes.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellInit.java b/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellInit.java
index dd7fab7..c250e03 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellInit.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellInit.java
@@ -16,7 +16,6 @@
 
 package com.android.wm.shell.sysui;
 
-import static com.android.wm.shell.ShellTaskOrganizer.TASK_LISTENER_TYPE_FULLSCREEN;
 import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_INIT;
 
 import android.os.Build;
@@ -26,149 +25,27 @@
 import androidx.annotation.VisibleForTesting;
 
 import com.android.internal.protolog.common.ProtoLog;
-import com.android.wm.shell.ShellTaskOrganizer;
-import com.android.wm.shell.activityembedding.ActivityEmbeddingController;
-import com.android.wm.shell.bubbles.BubbleController;
-import com.android.wm.shell.common.DisplayController;
-import com.android.wm.shell.common.DisplayImeController;
-import com.android.wm.shell.common.DisplayInsetsController;
 import com.android.wm.shell.common.ShellExecutor;
-import com.android.wm.shell.draganddrop.DragAndDropController;
-import com.android.wm.shell.freeform.FreeformComponents;
-import com.android.wm.shell.fullscreen.FullscreenTaskListener;
-import com.android.wm.shell.kidsmode.KidsModeTaskOrganizer;
-import com.android.wm.shell.pip.phone.PipTouchHandler;
-import com.android.wm.shell.recents.RecentTasksController;
-import com.android.wm.shell.splitscreen.SplitScreenController;
-import com.android.wm.shell.startingsurface.StartingWindowController;
-import com.android.wm.shell.transition.DefaultMixedHandler;
-import com.android.wm.shell.transition.Transitions;
-import com.android.wm.shell.unfold.UnfoldAnimationController;
-import com.android.wm.shell.unfold.UnfoldTransitionHandler;
 
 import java.util.ArrayList;
-import java.util.Optional;
 
 /**
  * The entry point implementation into the shell for initializing shell internal state.  Classes
- * which need to setup on start should inject an instance of this class and add an init callback.
+ * which need to initialize on start of the host SysUI should inject an instance of this class and
+ * add an init callback.
  */
 public class ShellInit {
     private static final String TAG = ShellInit.class.getSimpleName();
 
-    private final DisplayController mDisplayController;
-    private final DisplayImeController mDisplayImeController;
-    private final DisplayInsetsController mDisplayInsetsController;
-    private final DragAndDropController mDragAndDropController;
-    private final ShellTaskOrganizer mShellTaskOrganizer;
-    private final KidsModeTaskOrganizer mKidsModeTaskOrganizer;
-    private final Optional<BubbleController> mBubblesOptional;
-    private final Optional<SplitScreenController> mSplitScreenOptional;
-    private final Optional<PipTouchHandler> mPipTouchHandlerOptional;
-    private final FullscreenTaskListener mFullscreenTaskListener;
-    private final Optional<UnfoldAnimationController> mUnfoldController;
-    private final Optional<UnfoldTransitionHandler> mUnfoldTransitionHandler;
-    private final Optional<FreeformComponents> mFreeformComponentsOptional;
     private final ShellExecutor mMainExecutor;
-    private final Transitions mTransitions;
-    private final StartingWindowController mStartingWindow;
-    private final Optional<RecentTasksController> mRecentTasks;
-    private final Optional<ActivityEmbeddingController> mActivityEmbeddingOptional;
 
     // An ordered list of init callbacks to be made once shell is first started
     private final ArrayList<Pair<String, Runnable>> mInitCallbacks = new ArrayList<>();
     private boolean mHasInitialized;
 
-    public ShellInit(
-            ShellController shellController,
-            DisplayController displayController,
-            DisplayImeController displayImeController,
-            DisplayInsetsController displayInsetsController,
-            DragAndDropController dragAndDropController,
-            ShellTaskOrganizer shellTaskOrganizer,
-            KidsModeTaskOrganizer kidsModeTaskOrganizer,
-            Optional<BubbleController> bubblesOptional,
-            Optional<SplitScreenController> splitScreenOptional,
-            Optional<PipTouchHandler> pipTouchHandlerOptional,
-            FullscreenTaskListener fullscreenTaskListener,
-            Optional<UnfoldAnimationController> unfoldAnimationController,
-            Optional<UnfoldTransitionHandler> unfoldTransitionHandler,
-            Optional<FreeformComponents> freeformComponentsOptional,
-            Optional<RecentTasksController> recentTasks,
-            Optional<ActivityEmbeddingController> activityEmbeddingOptional,
-            Transitions transitions,
-            StartingWindowController startingWindow,
-            ShellExecutor mainExecutor) {
-        mDisplayController = displayController;
-        mDisplayImeController = displayImeController;
-        mDisplayInsetsController = displayInsetsController;
-        mDragAndDropController = dragAndDropController;
-        mShellTaskOrganizer = shellTaskOrganizer;
-        mKidsModeTaskOrganizer = kidsModeTaskOrganizer;
-        mBubblesOptional = bubblesOptional;
-        mSplitScreenOptional = splitScreenOptional;
-        mFullscreenTaskListener = fullscreenTaskListener;
-        mPipTouchHandlerOptional = pipTouchHandlerOptional;
-        mUnfoldController = unfoldAnimationController;
-        mUnfoldTransitionHandler = unfoldTransitionHandler;
-        mFreeformComponentsOptional = freeformComponentsOptional;
-        mRecentTasks = recentTasks;
-        mActivityEmbeddingOptional = activityEmbeddingOptional;
-        mTransitions = transitions;
+
+    public ShellInit(ShellExecutor mainExecutor) {
         mMainExecutor = mainExecutor;
-        mStartingWindow = startingWindow;
-        // TODO(238217847): To be removed once the init dependencies are inverted
-        shellController.setShellInit(this);
-    }
-
-    private void legacyInit() {
-        // Start listening for display and insets changes
-        mDisplayController.initialize();
-        mDisplayInsetsController.initialize();
-        mDisplayImeController.startMonitorDisplays();
-
-        // Setup the shell organizer
-        mShellTaskOrganizer.addListenerForType(
-                mFullscreenTaskListener, TASK_LISTENER_TYPE_FULLSCREEN);
-        mShellTaskOrganizer.initStartingWindow(mStartingWindow);
-        mShellTaskOrganizer.registerOrganizer();
-
-        mSplitScreenOptional.ifPresent(SplitScreenController::onOrganizerRegistered);
-        mBubblesOptional.ifPresent(BubbleController::initialize);
-
-        // Bind the splitscreen impl to the drag drop controller
-        mDragAndDropController.initialize(mSplitScreenOptional);
-
-        if (Transitions.ENABLE_SHELL_TRANSITIONS) {
-            mTransitions.register(mShellTaskOrganizer);
-            mActivityEmbeddingOptional.ifPresent(ActivityEmbeddingController::init);
-            mUnfoldTransitionHandler.ifPresent(UnfoldTransitionHandler::init);
-            mFreeformComponentsOptional.flatMap(f -> f.mTaskTransitionHandler)
-                    .ifPresent(mTransitions::addHandler);
-            if (mSplitScreenOptional.isPresent() && mPipTouchHandlerOptional.isPresent()) {
-                final DefaultMixedHandler mixedHandler = new DefaultMixedHandler(mTransitions,
-                        mPipTouchHandlerOptional.get().getTransitionHandler(),
-                        mSplitScreenOptional.get().getTransitionHandler());
-                // Added at end so that it has highest priority.
-                mTransitions.addHandler(mixedHandler);
-            }
-        }
-
-        // TODO(b/181599115): This should really be the pip controller, but until we can provide the
-        // controller instead of the feature interface, can just initialize the touch handler if
-        // needed
-        mPipTouchHandlerOptional.ifPresent((handler) -> handler.init());
-
-        // Initialize optional freeform
-        mFreeformComponentsOptional.ifPresent(f ->
-                mShellTaskOrganizer.addListenerForType(
-                        f.mTaskListener, ShellTaskOrganizer.TASK_LISTENER_TYPE_FREEFORM));
-
-        mUnfoldController.ifPresent(UnfoldAnimationController::init);
-        mRecentTasks.ifPresent(RecentTasksController::init);
-
-        // Initialize kids mode task organizer
-        mKidsModeTaskOrganizer.initialize(mStartingWindow);
     }
 
     /**
@@ -201,13 +78,9 @@
             final long t1 = SystemClock.uptimeMillis();
             info.second.run();
             final long t2 = SystemClock.uptimeMillis();
-            ProtoLog.v(WM_SHELL_INIT, "\t%s took %dms", info.first, (t2 - t1));
+            ProtoLog.v(WM_SHELL_INIT, "\t%s init took %dms", info.first, (t2 - t1));
         }
         mInitCallbacks.clear();
-
-        // TODO: To be removed
-        legacyInit();
-
         mHasInitialized = true;
     }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
index 0bec543..afc70a44 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
@@ -109,6 +109,7 @@
 import com.android.wm.shell.common.ShellExecutor;
 import com.android.wm.shell.common.TransactionPool;
 import com.android.wm.shell.protolog.ShellProtoLogGroup;
+import com.android.wm.shell.sysui.ShellInit;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -136,6 +137,7 @@
     private final TransactionPool mTransactionPool;
     private final DisplayController mDisplayController;
     private final Context mContext;
+    private final Handler mMainHandler;
     private final ShellExecutor mMainExecutor;
     private final ShellExecutor mAnimExecutor;
     private final TransitionAnimation mTransitionAnimation;
@@ -167,27 +169,33 @@
         }
     };
 
-    DefaultTransitionHandler(@NonNull DisplayController displayController,
-            @NonNull TransactionPool transactionPool, Context context,
+    DefaultTransitionHandler(@NonNull Context context,
+            @NonNull ShellInit shellInit,
+            @NonNull DisplayController displayController,
+            @NonNull TransactionPool transactionPool,
             @NonNull ShellExecutor mainExecutor, @NonNull Handler mainHandler,
             @NonNull ShellExecutor animExecutor) {
         mDisplayController = displayController;
         mTransactionPool = transactionPool;
         mContext = context;
+        mMainHandler = mainHandler;
         mMainExecutor = mainExecutor;
         mAnimExecutor = animExecutor;
         mTransitionAnimation = new TransitionAnimation(context, false /* debug */, Transitions.TAG);
         mCurrentUserId = UserHandle.myUserId();
+        mDevicePolicyManager = mContext.getSystemService(DevicePolicyManager.class);
+        shellInit.addInitCallback(this::onInit, this);
+    }
 
-        mDevicePolicyManager = context.getSystemService(DevicePolicyManager.class);
+    private void onInit() {
         updateEnterpriseThumbnailDrawable();
         mContext.registerReceiver(
                 mEnterpriseResourceUpdatedReceiver,
                 new IntentFilter(ACTION_DEVICE_POLICY_RESOURCE_UPDATED),
                 /* broadcastPermission = */ null,
-                mainHandler);
+                mMainHandler);
 
-        AttributeCache.init(context);
+        AttributeCache.init(mContext);
     }
 
     private void updateEnterpriseThumbnailDrawable() {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/SplitscreenPipMixedHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/SplitscreenPipMixedHandler.java
new file mode 100644
index 0000000..678e91f
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/SplitscreenPipMixedHandler.java
@@ -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.wm.shell.transition;
+
+import com.android.wm.shell.pip.phone.PipTouchHandler;
+import com.android.wm.shell.splitscreen.SplitScreenController;
+import com.android.wm.shell.sysui.ShellInit;
+
+import java.util.Optional;
+
+/**
+ * Handles transitions between the Splitscreen and PIP components.
+ */
+public class SplitscreenPipMixedHandler {
+
+    private final Optional<SplitScreenController> mSplitScreenOptional;
+    private final Optional<PipTouchHandler> mPipTouchHandlerOptional;
+    private final Transitions mTransitions;
+
+    public SplitscreenPipMixedHandler(ShellInit shellInit,
+            Optional<SplitScreenController> splitScreenControllerOptional,
+            Optional<PipTouchHandler> pipTouchHandlerOptional,
+            Transitions transitions) {
+        mSplitScreenOptional = splitScreenControllerOptional;
+        mPipTouchHandlerOptional = pipTouchHandlerOptional;
+        mTransitions = transitions;
+        if (Transitions.ENABLE_SHELL_TRANSITIONS
+                && mSplitScreenOptional.isPresent() && mPipTouchHandlerOptional.isPresent()) {
+            shellInit.addInitCallback(this::onInit, this);
+        }
+    }
+
+    private void onInit() {
+        // Special handling for initializing based on multiple components
+        final DefaultMixedHandler mixedHandler = new DefaultMixedHandler(mTransitions,
+                mPipTouchHandlerOptional.get().getTransitionHandler(),
+                mSplitScreenOptional.get().getTransitionHandler());
+        // Added at end so that it has highest priority.
+        mTransitions.addHandler(mixedHandler);
+    }
+}
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 881b7a1..e6006c4 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
@@ -59,13 +59,13 @@
 import com.android.internal.R;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.protolog.common.ProtoLog;
-import com.android.wm.shell.ShellTaskOrganizer;
 import com.android.wm.shell.common.DisplayController;
 import com.android.wm.shell.common.RemoteCallable;
 import com.android.wm.shell.common.ShellExecutor;
 import com.android.wm.shell.common.TransactionPool;
 import com.android.wm.shell.common.annotations.ExternalThread;
 import com.android.wm.shell.protolog.ShellProtoLogGroup;
+import com.android.wm.shell.sysui.ShellInit;
 
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -111,6 +111,7 @@
     private final ShellExecutor mMainExecutor;
     private final ShellExecutor mAnimExecutor;
     private final TransitionPlayerImpl mPlayerImpl;
+    private final DefaultTransitionHandler mDefaultTransitionHandler;
     private final RemoteTransitionHandler mRemoteTransitionHandler;
     private final DisplayController mDisplayController;
     private final ShellTransitionImpl mImpl = new ShellTransitionImpl();
@@ -136,8 +137,11 @@
     /** Keeps track of currently playing transitions in the order of receipt. */
     private final ArrayList<ActiveTransition> mActiveTransitions = new ArrayList<>();
 
-    public Transitions(@NonNull WindowOrganizer organizer, @NonNull TransactionPool pool,
-            @NonNull DisplayController displayController, @NonNull Context context,
+    public Transitions(@NonNull Context context,
+            @NonNull ShellInit shellInit,
+            @NonNull WindowOrganizer organizer,
+            @NonNull TransactionPool pool,
+            @NonNull DisplayController displayController,
             @NonNull ShellExecutor mainExecutor, @NonNull Handler mainHandler,
             @NonNull ShellExecutor animExecutor) {
         mOrganizer = organizer;
@@ -146,33 +150,35 @@
         mAnimExecutor = animExecutor;
         mDisplayController = displayController;
         mPlayerImpl = new TransitionPlayerImpl();
+        mDefaultTransitionHandler = new DefaultTransitionHandler(context, shellInit,
+                displayController, pool, mainExecutor, mainHandler, animExecutor);
+        mRemoteTransitionHandler = new RemoteTransitionHandler(mMainExecutor);
+        shellInit.addInitCallback(this::onInit, this);
+    }
+
+    private void onInit() {
         // The very last handler (0 in the list) should be the default one.
-        mHandlers.add(new DefaultTransitionHandler(displayController, pool, context, mainExecutor,
-                mainHandler, animExecutor));
+        mHandlers.add(mDefaultTransitionHandler);
         // Next lowest priority is remote transitions.
-        mRemoteTransitionHandler = new RemoteTransitionHandler(mainExecutor);
         mHandlers.add(mRemoteTransitionHandler);
 
-        ContentResolver resolver = context.getContentResolver();
+        ContentResolver resolver = mContext.getContentResolver();
         mTransitionAnimationScaleSetting = Settings.Global.getFloat(resolver,
                 Settings.Global.TRANSITION_ANIMATION_SCALE,
-                context.getResources().getFloat(
+                mContext.getResources().getFloat(
                         R.dimen.config_appTransitionAnimationDurationScaleDefault));
         dispatchAnimScaleSetting(mTransitionAnimationScaleSetting);
 
         resolver.registerContentObserver(
                 Settings.Global.getUriFor(Settings.Global.TRANSITION_ANIMATION_SCALE), false,
                 new SettingsObserver());
-    }
 
-    private Transitions() {
-        mOrganizer = null;
-        mContext = null;
-        mMainExecutor = null;
-        mAnimExecutor = null;
-        mDisplayController = null;
-        mPlayerImpl = null;
-        mRemoteTransitionHandler = null;
+        if (Transitions.ENABLE_SHELL_TRANSITIONS) {
+            // Register this transition handler with Core
+            mOrganizer.registerTransitionPlayer(mPlayerImpl);
+            // Pre-load the instance.
+            TransitionMetrics.getInstance();
+        }
     }
 
     public ShellTransitions asRemoteTransitions() {
@@ -195,14 +201,6 @@
         }
     }
 
-    /** Register this transition handler with Core */
-    public void register(ShellTaskOrganizer taskOrganizer) {
-        if (mPlayerImpl == null) return;
-        taskOrganizer.registerTransitionPlayer(mPlayerImpl);
-        // Pre-load the instance.
-        TransitionMetrics.getInstance();
-    }
-
     /**
      * Adds a handler candidate.
      * @see TransitionHandler
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldAnimationController.java
index 05a024a..6b59e31 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldAnimationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldAnimationController.java
@@ -24,13 +24,14 @@
 import android.util.SparseArray;
 import android.view.SurfaceControl;
 
+import com.android.wm.shell.common.ShellExecutor;
 import com.android.wm.shell.common.TransactionPool;
+import com.android.wm.shell.sysui.ShellInit;
 import com.android.wm.shell.unfold.ShellUnfoldProgressProvider.UnfoldListener;
 import com.android.wm.shell.unfold.animation.UnfoldTaskAnimator;
 
 import java.util.List;
 import java.util.Optional;
-import java.util.concurrent.Executor;
 
 import dagger.Lazy;
 
@@ -47,7 +48,7 @@
 public class UnfoldAnimationController implements UnfoldListener {
 
     private final ShellUnfoldProgressProvider mUnfoldProgressProvider;
-    private final Executor mExecutor;
+    private final ShellExecutor mExecutor;
     private final TransactionPool mTransactionPool;
     private final List<UnfoldTaskAnimator> mAnimators;
     private final Lazy<Optional<UnfoldTransitionHandler>> mUnfoldTransitionHandler;
@@ -55,28 +56,36 @@
     private final SparseArray<SurfaceControl> mTaskSurfaces = new SparseArray<>();
     private final SparseArray<UnfoldTaskAnimator> mAnimatorsByTaskId = new SparseArray<>();
 
-    public UnfoldAnimationController(@NonNull TransactionPool transactionPool,
+    public UnfoldAnimationController(
+            @NonNull ShellInit shellInit,
+            @NonNull TransactionPool transactionPool,
             @NonNull ShellUnfoldProgressProvider unfoldProgressProvider,
             @NonNull List<UnfoldTaskAnimator> animators,
             @NonNull Lazy<Optional<UnfoldTransitionHandler>> unfoldTransitionHandler,
-            @NonNull Executor executor) {
+            @NonNull ShellExecutor executor) {
         mUnfoldProgressProvider = unfoldProgressProvider;
         mUnfoldTransitionHandler = unfoldTransitionHandler;
         mTransactionPool = transactionPool;
         mExecutor = executor;
         mAnimators = animators;
+        // TODO(b/238217847): Temporarily add this check here until we can remove the dynamic
+        //                    override for this controller from the base module
+        if (unfoldProgressProvider != ShellUnfoldProgressProvider.NO_PROVIDER) {
+            shellInit.addInitCallback(this::onInit, this);
+        }
     }
 
     /**
      * Initializes the controller, starts listening for the external events
      */
-    public void init() {
+    public void onInit() {
         mUnfoldProgressProvider.addListener(mExecutor, this);
 
         for (int i = 0; i < mAnimators.size(); i++) {
             final UnfoldTaskAnimator animator = mAnimators.get(i);
             animator.init();
-            animator.start();
+            // TODO(b/238217847): See #provideSplitTaskUnfoldAnimatorBase
+            mExecutor.executeDelayed(animator::start, 0);
         }
     }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldTransitionHandler.java
index 9bf32fa..5d7b629 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldTransitionHandler.java
@@ -28,6 +28,7 @@
 import androidx.annotation.Nullable;
 
 import com.android.wm.shell.common.TransactionPool;
+import com.android.wm.shell.sysui.ShellInit;
 import com.android.wm.shell.transition.Transitions;
 import com.android.wm.shell.transition.Transitions.TransitionFinishCallback;
 import com.android.wm.shell.transition.Transitions.TransitionHandler;
@@ -59,11 +60,13 @@
 
     private final List<UnfoldTaskAnimator> mAnimators = new ArrayList<>();
 
-    public UnfoldTransitionHandler(ShellUnfoldProgressProvider unfoldProgressProvider,
+    public UnfoldTransitionHandler(ShellInit shellInit,
+            ShellUnfoldProgressProvider unfoldProgressProvider,
             FullscreenUnfoldTaskAnimator fullscreenUnfoldAnimator,
             SplitTaskUnfoldAnimator splitUnfoldTaskAnimator,
             TransactionPool transactionPool,
-            Executor executor, Transitions transitions) {
+            Executor executor,
+            Transitions transitions) {
         mUnfoldProgressProvider = unfoldProgressProvider;
         mTransactionPool = transactionPool;
         mExecutor = executor;
@@ -71,9 +74,18 @@
 
         mAnimators.add(splitUnfoldTaskAnimator);
         mAnimators.add(fullscreenUnfoldAnimator);
+        // TODO(b/238217847): Temporarily add this check here until we can remove the dynamic
+        //                    override for this controller from the base module
+        if (unfoldProgressProvider != ShellUnfoldProgressProvider.NO_PROVIDER
+                && Transitions.ENABLE_SHELL_TRANSITIONS) {
+            shellInit.addInitCallback(this::onInit, this);
+        }
     }
 
-    public void init() {
+    /**
+     * Called when the transition handler is initialized.
+     */
+    public void onInit() {
         for (int i = 0; i < mAnimators.size(); i++) {
             mAnimators.get(i).init();
         }
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipWithDismissButtonTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipWithDismissButtonTest.kt
index 77dcbe0..b71a9d8 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipWithDismissButtonTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipWithDismissButtonTest.kt
@@ -69,7 +69,7 @@
      */
     @Presubmit
     @Test
-    fun focusDoesNotChange() {
+    fun focusChanges() {
         testSpec.assertEventLog {
             this.focusChanges("PipMenuView", "NexusLauncherActivity")
         }
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromHome.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromHome.kt
new file mode 100644
index 0000000..c48f3f7
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromHome.kt
@@ -0,0 +1,195 @@
+/*
+ * 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.appWindowBecomesVisible
+import com.android.wm.shell.flicker.appWindowIsVisibleAtEnd
+import com.android.wm.shell.flicker.helpers.SplitScreenHelper
+import com.android.wm.shell.flicker.layerBecomesVisible
+import com.android.wm.shell.flicker.layerIsVisibleAtEnd
+import com.android.wm.shell.flicker.splitAppLayerBoundsBecomesVisible
+import com.android.wm.shell.flicker.splitAppLayerBoundsIsVisibleAtEnd
+import com.android.wm.shell.flicker.splitScreenDividerBecomesVisible
+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 switch back to split pair after go home
+ *
+ * To run this test: `atest WMShellFlickerTests:SwitchBackToSplitFromHome`
+ */
+@RequiresDevice
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+@Group1
+class SwitchBackToSplitFromHome(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)
+
+                tapl.goHome()
+                wmHelper.StateSyncBuilder()
+                    .withAppTransitionIdle()
+                    .withHomeActivityVisible()
+                    .waitForAndVerify()
+            }
+        }
+        transitions {
+            tapl.workspace.quickSwitchToPreviousApp()
+            SplitScreenHelper.waitForSplitComplete(wmHelper, primaryApp, secondaryApp)
+        }
+    }
+
+    @Presubmit
+    @Test
+    fun splitScreenDividerBecomesVisible() = testSpec.splitScreenDividerBecomesVisible()
+
+    @Presubmit
+    @Test
+    fun primaryAppLayerIsVisibleAtEnd() = testSpec.layerIsVisibleAtEnd(primaryApp)
+
+    @Presubmit
+    @Test
+    fun secondaryAppLayerBecomesVisible() = testSpec.layerBecomesVisible(secondaryApp)
+
+    @Presubmit
+    @Test
+    fun primaryAppBoundsIsVisibleAtEnd() = testSpec.splitAppLayerBoundsIsVisibleAtEnd(
+        primaryApp, splitLeftTop = false)
+
+    @Presubmit
+    @Test
+    fun secondaryAppBoundsBecomesVisible() = testSpec.splitAppLayerBoundsBecomesVisible(
+        secondaryApp, splitLeftTop = true)
+
+    @Presubmit
+    @Test
+    fun primaryAppWindowIsVisibleAtEnd() = testSpec.appWindowIsVisibleAtEnd(primaryApp)
+
+    @Presubmit
+    @Test
+    fun secondaryAppWindowBecomesVisible() = testSpec.appWindowBecomesVisible(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/unittest/src/com/android/wm/shell/ShellInitTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellInitTest.java
index 6cd5677..4bcdcaa 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellInitTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellInitTest.java
@@ -24,25 +24,8 @@
 
 import androidx.test.filters.SmallTest;
 
-import com.android.wm.shell.activityembedding.ActivityEmbeddingController;
-import com.android.wm.shell.bubbles.BubbleController;
-import com.android.wm.shell.common.DisplayController;
-import com.android.wm.shell.common.DisplayImeController;
-import com.android.wm.shell.common.DisplayInsetsController;
 import com.android.wm.shell.common.ShellExecutor;
-import com.android.wm.shell.draganddrop.DragAndDropController;
-import com.android.wm.shell.freeform.FreeformComponents;
-import com.android.wm.shell.fullscreen.FullscreenTaskListener;
-import com.android.wm.shell.kidsmode.KidsModeTaskOrganizer;
-import com.android.wm.shell.pip.phone.PipTouchHandler;
-import com.android.wm.shell.recents.RecentTasksController;
-import com.android.wm.shell.splitscreen.SplitScreenController;
-import com.android.wm.shell.startingsurface.StartingWindowController;
-import com.android.wm.shell.sysui.ShellController;
 import com.android.wm.shell.sysui.ShellInit;
-import com.android.wm.shell.transition.Transitions;
-import com.android.wm.shell.unfold.UnfoldAnimationController;
-import com.android.wm.shell.unfold.UnfoldTransitionHandler;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -51,31 +34,12 @@
 import org.mockito.MockitoAnnotations;
 
 import java.util.ArrayList;
-import java.util.Optional;
 
 @SmallTest
 @RunWith(AndroidTestingRunner.class)
 @TestableLooper.RunWithLooper(setAsMainLooper = true)
 public class ShellInitTest extends ShellTestCase {
 
-    @Mock private ShellController mShellController;
-    @Mock private DisplayController mDisplayController;
-    @Mock private DisplayImeController mDisplayImeController;
-    @Mock private DisplayInsetsController mDisplayInsetsController;
-    @Mock private DragAndDropController mDragAndDropController;
-    @Mock private ShellTaskOrganizer mShellTaskOrganizer;
-    @Mock private KidsModeTaskOrganizer mKidsModeTaskOrganizer;
-    @Mock private Optional<BubbleController> mBubblesOptional;
-    @Mock private Optional<SplitScreenController> mSplitScreenOptional;
-    @Mock private Optional<PipTouchHandler> mPipTouchHandlerOptional;
-    @Mock private FullscreenTaskListener mFullscreenTaskListener;
-    @Mock private Optional<UnfoldAnimationController> mUnfoldAnimationController;
-    @Mock private Optional<UnfoldTransitionHandler> mUnfoldTransitionHandler;
-    @Mock private Optional<FreeformComponents> mFreeformComponentsOptional;
-    @Mock private Optional<RecentTasksController> mRecentTasks;
-    @Mock private Optional<ActivityEmbeddingController> mActivityEmbeddingController;
-    @Mock private Transitions mTransitions;
-    @Mock private StartingWindowController mStartingWindow;
     @Mock private ShellExecutor mMainExecutor;
 
     private ShellInit mImpl;
@@ -83,12 +47,7 @@
     @Before
     public void setUp() {
         MockitoAnnotations.initMocks(this);
-        mImpl = new ShellInit(mShellController, mDisplayController, mDisplayImeController,
-                mDisplayInsetsController, mDragAndDropController, mShellTaskOrganizer,
-                mKidsModeTaskOrganizer, mBubblesOptional, mSplitScreenOptional,
-                mPipTouchHandlerOptional, mFullscreenTaskListener, mUnfoldAnimationController,
-                mUnfoldTransitionHandler, mFreeformComponentsOptional, mRecentTasks,
-                mActivityEmbeddingController, mTransitions, mStartingWindow, mMainExecutor);
+        mImpl = new ShellInit(mMainExecutor);
     }
 
     @Test
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java
index 3dd0032..7517e8a 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java
@@ -36,11 +36,11 @@
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.Mockito.clearInvocations;
 import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 
 import android.app.ActivityManager.RunningTaskInfo;
 import android.app.TaskInfo;
-import android.content.Context;
 import android.content.LocusId;
 import android.content.pm.ParceledListSlice;
 import android.os.Binder;
@@ -58,9 +58,8 @@
 import androidx.test.filters.SmallTest;
 
 import com.android.wm.shell.common.ShellExecutor;
-import com.android.wm.shell.common.SyncTransactionQueue;
-import com.android.wm.shell.common.TransactionPool;
 import com.android.wm.shell.compatui.CompatUIController;
+import com.android.wm.shell.sysui.ShellInit;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -84,13 +83,11 @@
     @Mock
     private ITaskOrganizerController mTaskOrganizerController;
     @Mock
-    private Context mContext;
-    @Mock
     private CompatUIController mCompatUI;
+    @Mock
+    private ShellInit mShellInit;
 
     ShellTaskOrganizer mOrganizer;
-    private final SyncTransactionQueue mSyncTransactionQueue = mock(SyncTransactionQueue.class);
-    private final TransactionPool mTransactionPool = mock(TransactionPool.class);
     private final ShellExecutor mTestExecutor = mock(ShellExecutor.class);
 
     private class TrackingTaskListener implements ShellTaskOrganizer.TaskListener {
@@ -135,8 +132,13 @@
             doReturn(ParceledListSlice.<TaskAppearedInfo>emptyList())
                     .when(mTaskOrganizerController).registerTaskOrganizer(any());
         } catch (RemoteException e) {}
-        mOrganizer = spy(new ShellTaskOrganizer(mTaskOrganizerController, mTestExecutor, mContext,
-                mCompatUI, Optional.empty(), Optional.empty()));
+        mOrganizer = spy(new ShellTaskOrganizer(mShellInit, mTaskOrganizerController,
+                mCompatUI, Optional.empty(), Optional.empty(), mTestExecutor));
+    }
+
+    @Test
+    public void instantiate_addInitCallback() {
+        verify(mShellInit, times(1)).addInitCallback(any(), any());
     }
 
     @Test
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingControllerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingControllerTests.java
new file mode 100644
index 0000000..bfe3b54
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingControllerTests.java
@@ -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.wm.shell.activityembedding;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.spy;
+
+import static org.junit.Assume.assumeTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import android.content.Context;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import com.android.wm.shell.ShellTestCase;
+import com.android.wm.shell.sysui.ShellInit;
+import com.android.wm.shell.transition.Transitions;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+/**
+ * Tests for the activity embedding controller.
+ *
+ * Build/Install/Run:
+ *  atest WMShellUnitTests:ActivityEmbeddingControllerTests
+ */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class ActivityEmbeddingControllerTests extends ShellTestCase {
+
+    private @Mock Context mContext;
+    private @Mock ShellInit mShellInit;
+    private @Mock Transitions mTransitions;
+    private ActivityEmbeddingController mController;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        mController = spy(new ActivityEmbeddingController(mContext, mShellInit, mTransitions));
+    }
+
+    @Test
+    public void instantiate_addInitCallback() {
+        assumeTrue(Transitions.ENABLE_SHELL_TRANSITIONS);
+        verify(mShellInit, times(1)).addInitCallback(any(), any());
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayChangeControllerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayChangeControllerTests.java
new file mode 100644
index 0000000..b8aa8e7
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayChangeControllerTests.java
@@ -0,0 +1,64 @@
+/*
+ * 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.common;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.spy;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import android.view.IWindowManager;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import com.android.wm.shell.ShellTestCase;
+import com.android.wm.shell.sysui.ShellInit;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+/**
+ * Tests for the display change controller.
+ *
+ * Build/Install/Run:
+ *  atest WMShellUnitTests:DisplayChangeControllerTests
+ */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class DisplayChangeControllerTests extends ShellTestCase {
+
+    private @Mock IWindowManager mWM;
+    private @Mock ShellInit mShellInit;
+    private @Mock ShellExecutor mMainExecutor;
+    private DisplayChangeController mController;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        mController = spy(new DisplayChangeController(mWM, mShellInit, mMainExecutor));
+    }
+
+    @Test
+    public void instantiate_addInitCallback() {
+        verify(mShellInit, times(1)).addInitCallback(any(), any());
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayControllerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayControllerTests.java
new file mode 100644
index 0000000..1e5e153
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayControllerTests.java
@@ -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.wm.shell.common;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import android.content.Context;
+import android.view.IWindowManager;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import com.android.wm.shell.ShellTestCase;
+import com.android.wm.shell.sysui.ShellInit;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+/**
+ * Tests for the display controller.
+ *
+ * Build/Install/Run:
+ *  atest WMShellUnitTests:DisplayControllerTests
+ */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class DisplayControllerTests extends ShellTestCase {
+
+    private @Mock Context mContext;
+    private @Mock IWindowManager mWM;
+    private @Mock ShellInit mShellInit;
+    private @Mock ShellExecutor mMainExecutor;
+    private DisplayController mController;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        mController = new DisplayController(mContext, mWM, mShellInit, mMainExecutor);
+    }
+
+    @Test
+    public void instantiateController_addInitCallback() {
+        verify(mShellInit, times(1)).addInitCallback(any(), eq(mController));
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayImeControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayImeControllerTest.java
index 5b691f2..9967e5f 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayImeControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayImeControllerTest.java
@@ -26,6 +26,7 @@
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.verifyZeroInteractions;
 
@@ -40,26 +41,32 @@
 
 import com.android.internal.view.IInputMethodManager;
 import com.android.wm.shell.ShellTestCase;
+import com.android.wm.shell.sysui.ShellInit;
 
 import org.junit.Before;
 import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
 
 import java.util.concurrent.Executor;
 
 @SmallTest
 public class DisplayImeControllerTest extends ShellTestCase {
 
+    @Mock
     private SurfaceControl.Transaction mT;
-    private DisplayImeController.PerDisplay mPerDisplay;
+    @Mock
     private IInputMethodManager mMock;
+    @Mock
+    private ShellInit mShellInit;
+    private DisplayImeController.PerDisplay mPerDisplay;
     private Executor mExecutor;
 
     @Before
     public void setUp() throws Exception {
-        mT = mock(SurfaceControl.Transaction.class);
-        mMock = mock(IInputMethodManager.class);
+        MockitoAnnotations.initMocks(this);
         mExecutor = spy(Runnable::run);
-        mPerDisplay = new DisplayImeController(null, null, null, mExecutor, new TransactionPool() {
+        mPerDisplay = new DisplayImeController(null, mShellInit, null, null, new TransactionPool() {
             @Override
             public SurfaceControl.Transaction acquire() {
                 return mT;
@@ -68,7 +75,7 @@
             @Override
             public void release(SurfaceControl.Transaction t) {
             }
-        }) {
+        }, mExecutor) {
             @Override
             public IInputMethodManager getImms() {
                 return mMock;
@@ -79,6 +86,11 @@
     }
 
     @Test
+    public void instantiateController_addInitCallback() {
+        verify(mShellInit, times(1)).addInitCallback(any(), any());
+    }
+
+    @Test
     public void insetsControlChanged_schedulesNoWorkOnExecutor() {
         mPerDisplay.insetsControlChanged(insetsStateWithIme(false), insetsSourceControl());
         verifyZeroInteractions(mExecutor);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayInsetsControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayInsetsControllerTest.java
index 4a7fd3d..5f5a3c5 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayInsetsControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayInsetsControllerTest.java
@@ -19,6 +19,7 @@
 import static android.view.Display.DEFAULT_DISPLAY;
 
 import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.ArgumentMatchers.notNull;
 import static org.mockito.Mockito.times;
@@ -37,6 +38,7 @@
 
 import com.android.wm.shell.ShellTestCase;
 import com.android.wm.shell.TestShellExecutor;
+import com.android.wm.shell.sysui.ShellInit;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -55,6 +57,8 @@
     private IWindowManager mWm;
     @Mock
     private DisplayController mDisplayController;
+    @Mock
+    private ShellInit mShellInit;
     private DisplayInsetsController mController;
     private SparseArray<IDisplayWindowInsetsController> mInsetsControllersByDisplayId;
     private TestShellExecutor mExecutor;
@@ -69,11 +73,16 @@
         mInsetsControllersByDisplayId = new SparseArray<>();
         mDisplayIdCaptor =  ArgumentCaptor.forClass(Integer.class);
         mInsetsControllerCaptor = ArgumentCaptor.forClass(IDisplayWindowInsetsController.class);
-        mController = new DisplayInsetsController(mWm, mDisplayController, mExecutor);
+        mController = new DisplayInsetsController(mWm, mShellInit, mDisplayController, mExecutor);
         addDisplay(DEFAULT_DISPLAY);
     }
 
     @Test
+    public void instantiateController_addInitCallback() {
+        verify(mShellInit, times(1)).addInitCallback(any(), any());
+    }
+
+    @Test
     public void testOnDisplayAdded_setsDisplayWindowInsetsControllerOnWMService()
             throws RemoteException {
         addDisplay(SECOND_DISPLAY);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropControllerTest.java
index e20997199..b6dbcf2 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropControllerTest.java
@@ -50,6 +50,7 @@
 import com.android.wm.shell.common.ShellExecutor;
 import com.android.wm.shell.splitscreen.SplitScreenController;
 import com.android.wm.shell.sysui.ShellController;
+import com.android.wm.shell.sysui.ShellInit;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -57,8 +58,6 @@
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
-import java.util.Optional;
-
 /**
  * Tests for the drag and drop controller.
  */
@@ -69,6 +68,8 @@
     @Mock
     private Context mContext;
     @Mock
+    private ShellInit mShellInit;
+    @Mock
     private ShellController mShellController;
     @Mock
     private DisplayController mDisplayController;
@@ -88,9 +89,14 @@
     @Before
     public void setUp() throws RemoteException {
         MockitoAnnotations.initMocks(this);
-        mController = new DragAndDropController(mContext, mShellController, mDisplayController,
-                mUiEventLogger, mIconProvider, mMainExecutor);
-        mController.initialize(Optional.of(mSplitScreenController));
+        mController = new DragAndDropController(mContext, mShellInit, mShellController,
+                mDisplayController, mUiEventLogger, mIconProvider, mMainExecutor);
+        mController.onInit();
+    }
+
+    @Test
+    public void instantiateController_addInitCallback() {
+        verify(mShellInit, times(1)).addInitCallback(any(), any());
     }
 
     @Test
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/kidsmode/KidsModeTaskOrganizerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/kidsmode/KidsModeTaskOrganizerTest.java
index 184a8df..a919ad0 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/kidsmode/KidsModeTaskOrganizerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/kidsmode/KidsModeTaskOrganizerTest.java
@@ -49,7 +49,7 @@
 import com.android.wm.shell.common.DisplayInsetsController;
 import com.android.wm.shell.common.ShellExecutor;
 import com.android.wm.shell.common.SyncTransactionQueue;
-import com.android.wm.shell.startingsurface.StartingWindowController;
+import com.android.wm.shell.sysui.ShellInit;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -73,7 +73,7 @@
     @Mock private WindowContainerToken mToken;
     @Mock private WindowContainerTransaction mTransaction;
     @Mock private KidsModeSettingsObserver mObserver;
-    @Mock private StartingWindowController mStartingWindowController;
+    @Mock private ShellInit mShellInit;
     @Mock private DisplayInsetsController mDisplayInsetsController;
 
     KidsModeTaskOrganizer mOrganizer;
@@ -87,10 +87,9 @@
         } catch (RemoteException e) {
         }
         // NOTE: KidsModeTaskOrganizer should have a null CompatUIController.
-        mOrganizer = spy(new KidsModeTaskOrganizer(mTaskOrganizerController, mTestExecutor,
-                mHandler, mContext, mSyncTransactionQueue, mDisplayController,
-                mDisplayInsetsController, Optional.empty(), Optional.empty(), mObserver));
-        mOrganizer.initialize(mStartingWindowController);
+        mOrganizer = spy(new KidsModeTaskOrganizer(mContext, mTaskOrganizerController,
+                mSyncTransactionQueue, mDisplayController, mDisplayInsetsController,
+                Optional.empty(), Optional.empty(), mObserver, mTestExecutor, mHandler));
         doReturn(mTransaction).when(mOrganizer).getWindowContainerTransaction();
         doReturn(new InsetsState()).when(mDisplayController).getInsetsState(DEFAULT_DISPLAY);
     }
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipTouchHandlerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipTouchHandlerTest.java
index 74519ea..ecefd89 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipTouchHandlerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipTouchHandlerTest.java
@@ -38,6 +38,7 @@
 import com.android.wm.shell.pip.PipTaskOrganizer;
 import com.android.wm.shell.pip.PipTransitionController;
 import com.android.wm.shell.pip.PipUiEventLogger;
+import com.android.wm.shell.sysui.ShellInit;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -78,6 +79,9 @@
     private PipUiEventLogger mPipUiEventLogger;
 
     @Mock
+    private ShellInit mShellInit;
+
+    @Mock
     private ShellExecutor mMainExecutor;
 
     private PipBoundsState mPipBoundsState;
@@ -104,11 +108,11 @@
         PipMotionHelper pipMotionHelper = new PipMotionHelper(mContext, mPipBoundsState,
                 mPipTaskOrganizer, mPhonePipMenuController, mPipSnapAlgorithm,
                 mMockPipTransitionController, mFloatingContentCoordinator);
-        mPipTouchHandler = new PipTouchHandler(mContext, mPhonePipMenuController,
-                mPipBoundsAlgorithm, mPipBoundsState, mPipTaskOrganizer,
-                pipMotionHelper, mFloatingContentCoordinator, mPipUiEventLogger,
-                mMainExecutor);
-        mPipTouchHandler.init();
+        mPipTouchHandler = new PipTouchHandler(mContext, mShellInit, mPhonePipMenuController,
+                mPipBoundsAlgorithm, mPipBoundsState, mPipTaskOrganizer, pipMotionHelper,
+                mFloatingContentCoordinator, mPipUiEventLogger, mMainExecutor);
+        // We aren't actually using ShellInit, so just call init directly
+        mPipTouchHandler.onInit();
         mMotionHelper = Mockito.spy(mPipTouchHandler.getMotionHelper());
         mPipResizeGestureHandler = Mockito.spy(mPipTouchHandler.getPipResizeGestureHandler());
         mPipTouchHandler.setPipMotionHelper(mMotionHelper);
@@ -133,6 +137,11 @@
     }
 
     @Test
+    public void instantiate_addInitCallback() {
+        verify(mShellInit, times(1)).addInitCallback(any(), any());
+    }
+
+    @Test
     public void updateMovementBounds_minMaxBounds() {
         final int shorterLength = Math.min(mPipBoundsState.getDisplayBounds().width(),
                 mPipBoundsState.getDisplayBounds().height());
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java
index b1e0911..d406a4e 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java
@@ -48,6 +48,7 @@
 import com.android.wm.shell.TestShellExecutor;
 import com.android.wm.shell.common.ShellExecutor;
 import com.android.wm.shell.common.TaskStackListenerImpl;
+import com.android.wm.shell.sysui.ShellInit;
 import com.android.wm.shell.util.GroupedRecentTaskInfo;
 import com.android.wm.shell.util.SplitBounds;
 
@@ -71,6 +72,8 @@
     private Context mContext;
     @Mock
     private TaskStackListenerImpl mTaskStackListener;
+    @Mock
+    private ShellInit mShellInit;
 
     private ShellTaskOrganizer mShellTaskOrganizer;
     private RecentTasksController mRecentTasksController;
@@ -80,10 +83,11 @@
     public void setUp() {
         mMainExecutor = new TestShellExecutor();
         when(mContext.getPackageManager()).thenReturn(mock(PackageManager.class));
-        mRecentTasksController = spy(new RecentTasksController(mContext, mTaskStackListener,
-                mMainExecutor));
-        mShellTaskOrganizer = new ShellTaskOrganizer(mMainExecutor, mContext,
-                null /* sizeCompatUI */, Optional.empty(), Optional.of(mRecentTasksController));
+        mRecentTasksController = spy(new RecentTasksController(mContext, mShellInit,
+                mTaskStackListener, mMainExecutor));
+        mShellTaskOrganizer = new ShellTaskOrganizer(mShellInit,
+                null /* sizeCompatUI */, Optional.empty(), Optional.of(mRecentTasksController),
+                mMainExecutor);
     }
 
     @Test
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java
index c7a261f..10788f9 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java
@@ -51,8 +51,10 @@
 import com.android.wm.shell.common.ShellExecutor;
 import com.android.wm.shell.common.SyncTransactionQueue;
 import com.android.wm.shell.common.TransactionPool;
+import com.android.wm.shell.draganddrop.DragAndDropController;
 import com.android.wm.shell.recents.RecentTasksController;
 import com.android.wm.shell.sysui.ShellController;
+import com.android.wm.shell.sysui.ShellInit;
 import com.android.wm.shell.transition.Transitions;
 
 import org.junit.Before;
@@ -71,6 +73,7 @@
 public class SplitScreenControllerTests extends ShellTestCase {
 
     @Mock ShellController mShellController;
+    @Mock ShellInit mShellInit;
     @Mock ShellTaskOrganizer mTaskOrganizer;
     @Mock SyncTransactionQueue mSyncQueue;
     @Mock RootTaskDisplayAreaOrganizer mRootTDAOrganizer;
@@ -78,6 +81,7 @@
     @Mock DisplayController mDisplayController;
     @Mock DisplayImeController mDisplayImeController;
     @Mock DisplayInsetsController mDisplayInsetsController;
+    @Mock DragAndDropController mDragAndDropController;
     @Mock Transitions mTransitions;
     @Mock TransactionPool mTransactionPool;
     @Mock IconProvider mIconProvider;
@@ -88,16 +92,21 @@
     @Before
     public void setup() {
         MockitoAnnotations.initMocks(this);
-        mSplitScreenController = spy(new SplitScreenController(mShellController, mTaskOrganizer,
-                mSyncQueue, mContext, mRootTDAOrganizer, mMainExecutor, mDisplayController,
-                mDisplayImeController, mDisplayInsetsController, mTransitions, mTransactionPool,
-                mIconProvider, mRecentTasks));
+        mSplitScreenController = spy(new SplitScreenController(mContext, mShellInit,
+                mShellController, mTaskOrganizer, mSyncQueue, mRootTDAOrganizer, mDisplayController,
+                mDisplayImeController, mDisplayInsetsController, mDragAndDropController,
+                mTransitions, mTransactionPool, mIconProvider, mRecentTasks, mMainExecutor));
+    }
+
+    @Test
+    public void instantiateController_addInitCallback() {
+        verify(mShellInit, times(1)).addInitCallback(any(), any());
     }
 
     @Test
     public void testControllerRegistersKeyguardChangeListener() {
         when(mDisplayController.getDisplayLayout(anyInt())).thenReturn(new DisplayLayout());
-        mSplitScreenController.onOrganizerRegistered();
+        mSplitScreenController.onInit();
         verify(mShellController, times(1)).addKeyguardChangeListener(any());
     }
 
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingWindowControllerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingWindowControllerTests.java
new file mode 100644
index 0000000..35515e3
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingWindowControllerTests.java
@@ -0,0 +1,80 @@
+/*
+ * 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.startingsurface;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import android.content.Context;
+import android.hardware.display.DisplayManager;
+import android.view.Display;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import com.android.launcher3.icons.IconProvider;
+import com.android.wm.shell.ShellTaskOrganizer;
+import com.android.wm.shell.ShellTestCase;
+import com.android.wm.shell.common.ShellExecutor;
+import com.android.wm.shell.common.TransactionPool;
+import com.android.wm.shell.sysui.ShellInit;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+/**
+ * Tests for the starting window controller.
+ *
+ * Build/Install/Run:
+ *  atest WMShellUnitTests:StartingWindowControllerTests
+ */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class StartingWindowControllerTests extends ShellTestCase {
+
+    private @Mock Context mContext;
+    private @Mock DisplayManager mDisplayManager;
+    private @Mock ShellInit mShellInit;
+    private @Mock ShellTaskOrganizer mTaskOrganizer;
+    private @Mock ShellExecutor mMainExecutor;
+    private @Mock StartingWindowTypeAlgorithm mTypeAlgorithm;
+    private @Mock IconProvider mIconProvider;
+    private @Mock TransactionPool mTransactionPool;
+    private StartingWindowController mController;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        doReturn(mock(Display.class)).when(mDisplayManager).getDisplay(anyInt());
+        doReturn(mDisplayManager).when(mContext).getSystemService(eq(DisplayManager.class));
+        mController = new StartingWindowController(mContext, mShellInit, mTaskOrganizer,
+                mMainExecutor, mTypeAlgorithm, mIconProvider, mTransactionPool);
+    }
+
+    @Test
+    public void instantiate_addInitCallback() {
+        verify(mShellInit, times(1)).addInitCallback(any(), any());
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/sysui/ShellControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/sysui/ShellControllerTest.java
index 1c0e46f..02311ba 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/sysui/ShellControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/sysui/ShellControllerTest.java
@@ -43,6 +43,8 @@
 public class ShellControllerTest extends ShellTestCase {
 
     @Mock
+    private ShellInit mShellInit;
+    @Mock
     private ShellExecutor mExecutor;
 
     private ShellController mController;
@@ -54,7 +56,7 @@
         MockitoAnnotations.initMocks(this);
         mKeyguardChangeListener = new TestKeyguardChangeListener();
         mConfigChangeListener = new TestConfigurationChangeListener();
-        mController = new ShellController(mExecutor);
+        mController = new ShellController(mShellInit, mExecutor);
         mController.onConfigurationChanged(getConfigurationCopy());
     }
 
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java
index e2f2b71..388792b 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java
@@ -84,6 +84,7 @@
 import com.android.wm.shell.common.DisplayController;
 import com.android.wm.shell.common.ShellExecutor;
 import com.android.wm.shell.common.TransactionPool;
+import com.android.wm.shell.sysui.ShellInit;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -117,6 +118,14 @@
     }
 
     @Test
+    public void instantiate_addInitCallback() {
+        ShellInit shellInit = mock(ShellInit.class);
+        final Transitions t = new Transitions(mContext, shellInit, mOrganizer, mTransactionPool,
+                createTestDisplayController(), mMainExecutor, mMainHandler, mAnimExecutor);
+        verify(shellInit, times(1)).addInitCallback(any(), eq(t));
+    }
+
+    @Test
     public void testBasicTransitionFlow() {
         Transitions transitions = createTestTransitions();
         transitions.replaceDefaultHandlerForTest(mDefaultHandler);
@@ -832,14 +841,18 @@
         } catch (RemoteException e) {
             // No remote stuff happening, so this can't be hit
         }
-        DisplayController out = new DisplayController(mContext, mockWM, mMainExecutor);
-        out.initialize();
+        ShellInit shellInit = new ShellInit(mMainExecutor);
+        DisplayController out = new DisplayController(mContext, mockWM, shellInit, mMainExecutor);
+        shellInit.init();
         return out;
     }
 
     private Transitions createTestTransitions() {
-        return new Transitions(mOrganizer, mTransactionPool, createTestDisplayController(),
-                mContext, mMainExecutor, mMainHandler, mAnimExecutor);
+        ShellInit shellInit = new ShellInit(mMainExecutor);
+        final Transitions t = new Transitions(mContext, shellInit, mOrganizer, mTransactionPool,
+                createTestDisplayController(), mMainExecutor, mMainHandler, mAnimExecutor);
+        shellInit.init();
+        return t;
     }
 //
 //    private class TestDisplayController extends DisplayController {
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/unfold/UnfoldAnimationControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/unfold/UnfoldAnimationControllerTest.java
index 46de607..81eefe2 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/unfold/UnfoldAnimationControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/unfold/UnfoldAnimationControllerTest.java
@@ -20,7 +20,10 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
+import static org.mockito.ArgumentMatchers.any;
 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.app.ActivityManager.RunningTaskInfo;
@@ -33,6 +36,7 @@
 import com.android.wm.shell.TestRunningTaskInfoBuilder;
 import com.android.wm.shell.TestShellExecutor;
 import com.android.wm.shell.common.TransactionPool;
+import com.android.wm.shell.sysui.ShellInit;
 import com.android.wm.shell.unfold.animation.UnfoldTaskAnimator;
 
 import org.junit.Before;
@@ -65,6 +69,8 @@
     @Mock
     private UnfoldTransitionHandler mUnfoldTransitionHandler;
     @Mock
+    private ShellInit mShellInit;
+    @Mock
     private SurfaceControl mLeash;
 
     private UnfoldAnimationController mUnfoldAnimationController;
@@ -85,6 +91,7 @@
         animators.add(mTaskAnimator1);
         animators.add(mTaskAnimator2);
         mUnfoldAnimationController = new UnfoldAnimationController(
+                mShellInit,
                 mTransactionPool,
                 mProgressProvider,
                 animators,
@@ -94,6 +101,11 @@
     }
 
     @Test
+    public void instantiateController_addInitCallback() {
+        verify(mShellInit, times(1)).addInitCallback(any(), any());
+    }
+
+    @Test
     public void testAppearedMatchingTask_appliesUnfoldProgress() {
         mTaskAnimator1.setTaskMatcher((info) -> info.getWindowingMode() == 2);
         RunningTaskInfo taskInfo = new TestRunningTaskInfoBuilder()
@@ -244,7 +256,8 @@
 
     @Test
     public void testInit_initsAndStartsAnimators() {
-        mUnfoldAnimationController.init();
+        mUnfoldAnimationController.onInit();
+        mShellExecutor.flushAll();
 
         assertThat(mTaskAnimator1.mInitialized).isTrue();
         assertThat(mTaskAnimator1.mStarted).isTrue();
diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml
index f1745ec..a79cbb6 100644
--- a/packages/SettingsLib/res/values/strings.xml
+++ b/packages/SettingsLib/res/values/strings.xml
@@ -1608,6 +1608,8 @@
     <string name="dream_complication_title_aqi">Air Quality</string>
     <!-- Screensaver overlay which displays cast info. [CHAR LIMIT=20] -->
     <string name="dream_complication_title_cast_info">Cast Info</string>
+    <!-- Screensaver overlay which displays home controls. [CHAR LIMIT=20] -->
+    <string name="dream_complication_title_home_controls">Home Controls</string>
 
     <!-- Title for a screen allowing the user to choose a profile picture. [CHAR LIMIT=NONE] -->
     <string name="avatar_picker_title">Choose a profile picture</string>
diff --git a/packages/SettingsLib/src/com/android/settingslib/dream/DreamBackend.java b/packages/SettingsLib/src/com/android/settingslib/dream/DreamBackend.java
index 01d0cc4..a46e232 100644
--- a/packages/SettingsLib/src/com/android/settingslib/dream/DreamBackend.java
+++ b/packages/SettingsLib/src/com/android/settingslib/dream/DreamBackend.java
@@ -91,7 +91,8 @@
             COMPLICATION_TYPE_DATE,
             COMPLICATION_TYPE_WEATHER,
             COMPLICATION_TYPE_AIR_QUALITY,
-            COMPLICATION_TYPE_CAST_INFO
+            COMPLICATION_TYPE_CAST_INFO,
+            COMPLICATION_TYPE_HOME_CONTROLS
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface ComplicationType {}
@@ -101,6 +102,7 @@
     public static final int COMPLICATION_TYPE_WEATHER = 3;
     public static final int COMPLICATION_TYPE_AIR_QUALITY = 4;
     public static final int COMPLICATION_TYPE_CAST_INFO = 5;
+    public static final int COMPLICATION_TYPE_HOME_CONTROLS = 6;
 
     private final Context mContext;
     private final IDreamManager mDreamManager;
@@ -346,6 +348,9 @@
             case COMPLICATION_TYPE_CAST_INFO:
                 res = R.string.dream_complication_title_cast_info;
                 break;
+            case COMPLICATION_TYPE_HOME_CONTROLS:
+                res = R.string.dream_complication_title_home_controls;
+                break;
             default:
                 return null;
         }
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 8265b74..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",
diff --git a/packages/SystemUI/docs/device-entry/keyguard.md b/packages/SystemUI/docs/device-entry/keyguard.md
index 337f73b..8634c95 100644
--- a/packages/SystemUI/docs/device-entry/keyguard.md
+++ b/packages/SystemUI/docs/device-entry/keyguard.md
@@ -30,6 +30,10 @@
 
 ### How the device locks
 
+### Quick Affordances
+
+These are interactive UI elements that appear on the lockscreen when the device is locked. They allow the user to perform quick actions without unlocking their device. To learn more about them, please see [this dedicated document](quickaffordance.md)
+
 ## Debugging Tips
 Enable verbose keyguard logs that will print to logcat. Should only be used temporarily for debugging. See [KeyguardConstants][5].
 ```
diff --git a/packages/SystemUI/docs/device-entry/quickaffordance.md b/packages/SystemUI/docs/device-entry/quickaffordance.md
new file mode 100644
index 0000000..a96e5339
--- /dev/null
+++ b/packages/SystemUI/docs/device-entry/quickaffordance.md
@@ -0,0 +1,25 @@
+# Keyguard Quick Affordances
+These are interactive UI elements that appear at the bottom of the lockscreen when the device is
+locked. They allow the user to perform quick actions without unlocking their device. For example:
+opening an screen that lets them control the smart devices in their home, access their touch-to-pay
+credit card, etc.
+
+## Adding a new Quick Affordance
+### Step 1: create a new quick affordance config
+* Create a new class under the [systemui/keyguard/data/quickaffordance](../../src/com/android/systemui/keyguard/data/quickaffordance) directory
+* Please make sure that the class is injected through the Dagger dependency injection system by using the `@Inject` annotation on its main constructor and the `@SysUISingleton` annotation at class level, to make sure only one instance of the class is ever instantiated
+* Have the class implement the [KeyguardQuickAffordanceConfig](../../src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceConfig.kt) interface, notes:
+  * The `state` Flow property must emit `State.Hidden` when the feature is not enabled!
+  * It is safe to assume that `onQuickAffordanceClicked` will not be invoked if-and-only-if the previous rule is followed
+  * When implementing `onQuickAffordanceClicked`, the implementation can do something or it can ask the framework to start an activity using an `Intent` provided by the implementation
+* Please include a unit test for your new implementation under [the correct directory](../../tests/src/com/android/systemui/keyguard/data/quickaffordance)
+
+### Step 2: choose a position and priority
+* Add the new class as a dependency in the constructor of [KeyguardQuickAffordanceConfigs](../../src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceConfigs.kt)
+* Place the new class in one of the available positions in the `configsByPosition` property, note:
+  * In each position, there is a list. The order matters. The order of that list is the priority order in which the framework considers each config. The first config whose state property returns `State.Visible` determines the button that is shown for that position
+  * Please only add to one position. The framework treats each position individually and there is no good way to prevent the same config from making its button appear in more than one position at the same time
+
+### Step 3: manually verify the new quick affordance
+* Build and launch SysUI on a device
+* Verify that the quick affordance button for the new implementation is correctly visible and clicking it does the right thing
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/ActivityManagerWrapper.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java
index 916526d..ee30972 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java
@@ -19,7 +19,6 @@
 import static android.app.ActivityManager.LOCK_TASK_MODE_LOCKED;
 import static android.app.ActivityManager.LOCK_TASK_MODE_NONE;
 import static android.app.ActivityManager.LOCK_TASK_MODE_PINNED;
-import static android.app.ActivityManager.RECENT_IGNORE_UNAVAILABLE;
 import static android.app.ActivityTaskManager.getService;
 
 import android.annotation.NonNull;
@@ -27,7 +26,6 @@
 import android.app.Activity;
 import android.app.ActivityClient;
 import android.app.ActivityManager;
-import android.app.ActivityManager.RecentTaskInfo;
 import android.app.ActivityManager.RunningTaskInfo;
 import android.app.ActivityOptions;
 import android.app.ActivityTaskManager;
@@ -337,7 +335,8 @@
      * Shows a voice session identified by {@code token}
      * @return true if the session was shown, false otherwise
      */
-    public boolean showVoiceSession(IBinder token, Bundle args, int flags) {
+    public boolean showVoiceSession(@NonNull IBinder token, @NonNull Bundle args, int flags,
+            @Nullable String attributionTag) {
         IVoiceInteractionManagerService service = IVoiceInteractionManagerService.Stub.asInterface(
                 ServiceManager.getService(Context.VOICE_INTERACTION_MANAGER_SERVICE));
         if (service == null) {
@@ -346,7 +345,7 @@
         args.putLong(INVOCATION_TIME_MS_KEY, SystemClock.elapsedRealtime());
 
         try {
-            return service.showSessionFromSession(token, args, flags);
+            return service.showSessionFromSession(token, args, flags, attributionTag);
         } catch (RemoteException e) {
             return false;
         }
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/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.java
index 609846e..7c1ef8c 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.java
@@ -230,6 +230,7 @@
         private IBinder mTransition = null;
         private boolean mKeyguardLocked = false;
         private RemoteAnimationTargetCompat[] mAppearedTargets;
+        private boolean mWillFinishToHome = false;
 
         void setup(RecentsAnimationControllerCompat wrapped, TransitionInfo info,
                 IRemoteTransitionFinishedCallback finishCB,
@@ -392,7 +393,7 @@
                 if (toHome) wct.reorder(mRecentsTask, true /* toTop */);
                 else wct.restoreTransientOrder(mRecentsTask);
             }
-            if (!toHome && mPausingTasks != null && mOpeningLeashes == null) {
+            if (!toHome && !mWillFinishToHome && mPausingTasks != null && mOpeningLeashes == null) {
                 // The gesture went back to opening the app rather than continuing with
                 // recents, so end the transition by moving the app back to the top (and also
                 // re-showing it's task).
@@ -476,6 +477,7 @@
         }
 
         @Override public void setWillFinishToHome(boolean willFinishToHome) {
+            mWillFinishToHome = willFinishToHome;
             if (mWrapped != null) mWrapped.setWillFinishToHome(willFinishToHome);
         }
 
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/SystemUIInitializer.java b/packages/SystemUI/src/com/android/systemui/SystemUIInitializer.java
index e9ca0fd..5f586c9 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIInitializer.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIInitializer.java
@@ -79,11 +79,6 @@
 
         // Stand up WMComponent
         setupWmComponent(mContext);
-        if (initializeComponents) {
-            // Only initialize when not starting from tests since this currently initializes some
-            // components that shouldn't be run in the test environment
-            mWMComponent.init();
-        }
 
         // And finally, retrieve whatever SysUI needs from WMShell and build SysUI.
         SysUIComponent.Builder builder = mRootComponent.getSysUIComponent();
@@ -102,6 +97,10 @@
                     .setDisplayAreaHelper(mWMComponent.getDisplayAreaHelper())
                     .setRecentTasks(mWMComponent.getRecentTasks())
                     .setBackAnimation(mWMComponent.getBackAnimation());
+
+            // Only initialize when not starting from tests since this currently initializes some
+            // components that shouldn't be run in the test environment
+            mWMComponent.init();
         } else {
             // TODO: Call on prepareSysUIComponentBuilder but not with real components. Other option
             // is separating this logic into newly creating SystemUITestsFactory.
diff --git a/packages/SystemUI/src/com/android/systemui/assist/AssistManager.java b/packages/SystemUI/src/com/android/systemui/assist/AssistManager.java
index 7c2673c..57ffdab 100644
--- a/packages/SystemUI/src/com/android/systemui/assist/AssistManager.java
+++ b/packages/SystemUI/src/com/android/systemui/assist/AssistManager.java
@@ -310,7 +310,8 @@
 
     private void startVoiceInteractor(Bundle args) {
         mAssistUtils.showSessionForActiveService(args,
-                VoiceInteractionSession.SHOW_SOURCE_ASSIST_GESTURE, null, null);
+                VoiceInteractionSession.SHOW_SOURCE_ASSIST_GESTURE, mContext.getAttributionTag(),
+                null, null);
     }
 
     public void launchVoiceAssistFromKeyguard() {
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/Complication.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/Complication.java
index 51bd311..5457144 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/complication/Complication.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/Complication.java
@@ -162,7 +162,8 @@
             COMPLICATION_TYPE_DATE,
             COMPLICATION_TYPE_WEATHER,
             COMPLICATION_TYPE_AIR_QUALITY,
-            COMPLICATION_TYPE_CAST_INFO
+            COMPLICATION_TYPE_CAST_INFO,
+            COMPLICATION_TYPE_HOME_CONTROLS
     })
     @Retention(RetentionPolicy.SOURCE)
     @interface ComplicationType {}
@@ -173,6 +174,7 @@
     int COMPLICATION_TYPE_WEATHER = 1 << 2;
     int COMPLICATION_TYPE_AIR_QUALITY = 1 << 3;
     int COMPLICATION_TYPE_CAST_INFO = 1 << 4;
+    int COMPLICATION_TYPE_HOME_CONTROLS = 1 << 5;
 
     /**
      * The {@link Host} interface specifies a way a {@link Complication} to communicate with its
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationUtils.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationUtils.java
index a4a0075..dcab90f 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationUtils.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationUtils.java
@@ -19,6 +19,7 @@
 import static com.android.systemui.dreams.complication.Complication.COMPLICATION_TYPE_AIR_QUALITY;
 import static com.android.systemui.dreams.complication.Complication.COMPLICATION_TYPE_CAST_INFO;
 import static com.android.systemui.dreams.complication.Complication.COMPLICATION_TYPE_DATE;
+import static com.android.systemui.dreams.complication.Complication.COMPLICATION_TYPE_HOME_CONTROLS;
 import static com.android.systemui.dreams.complication.Complication.COMPLICATION_TYPE_NONE;
 import static com.android.systemui.dreams.complication.Complication.COMPLICATION_TYPE_TIME;
 import static com.android.systemui.dreams.complication.Complication.COMPLICATION_TYPE_WEATHER;
@@ -48,6 +49,8 @@
                 return COMPLICATION_TYPE_AIR_QUALITY;
             case DreamBackend.COMPLICATION_TYPE_CAST_INFO:
                 return COMPLICATION_TYPE_CAST_INFO;
+            case DreamBackend.COMPLICATION_TYPE_HOME_CONTROLS:
+                return COMPLICATION_TYPE_HOME_CONTROLS;
             default:
                 return COMPLICATION_TYPE_NONE;
         }
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..1c72e49 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;
@@ -62,7 +62,7 @@
 
     @Override
     public int getRequiredTypeAvailability() {
-        return COMPLICATION_TYPE_NONE;
+        return COMPLICATION_TYPE_HOME_CONTROLS;
     }
 
     /**
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..3d40e67 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.java
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.java
@@ -92,7 +92,10 @@
      * Whether the KeyguardBottomArea(View|Controller) should use the modern architecture or the old
      * one.
      */
-    public static final BooleanFlag MODERN_BOTTOM_AREA = new BooleanFlag(206, false);
+    public static final BooleanFlag MODERN_BOTTOM_AREA = new BooleanFlag(
+            206,
+            /* default= */ false,
+            /* teamfood= */ true);
 
     public static final BooleanFlag LOCKSCREEN_CUSTOM_CLOCKS = new BooleanFlag(207, false);
 
@@ -204,6 +207,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/keyguard/data/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfig.kt
index 758e411..ea6497e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfig.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfig.kt
@@ -17,14 +17,12 @@
 
 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
@@ -35,12 +33,9 @@
 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 {
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
index c686e27..cc5a997 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfig.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfig.kt
@@ -29,32 +29,59 @@
 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 val state: Flow<KeyguardQuickAffordanceConfig.State> = 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
+            )
+        }
+    }
+
     override fun onQuickAffordanceClicked(
         animationController: ActivityLaunchAnimator.Controller?,
     ): KeyguardQuickAffordanceConfig.OnClickedResult {
@@ -66,53 +93,6 @@
         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,
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
index be91e51..62cf1a6 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
@@ -21,6 +21,7 @@
 import com.android.systemui.common.data.model.Position
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.statusbar.policy.KeyguardStateController
 import javax.inject.Inject
 import kotlinx.coroutines.channels.awaitClose
 import kotlinx.coroutines.flow.Flow
@@ -50,6 +51,15 @@
     val clockPosition: StateFlow<Position>
 
     /**
+     * Observable for whether the keyguard is showing.
+     *
+     * Note: this is also `true` when the lock-screen is occluded with an `Activity` "above" it in
+     * the z-order (which is not really above the system UI window, but rather - the lock-screen
+     * becomes invisible to reveal the "occluding activity").
+     */
+    val isKeyguardShowing: Flow<Boolean>
+
+    /**
      * 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
@@ -91,6 +101,7 @@
 @Inject
 constructor(
     statusBarStateController: StatusBarStateController,
+    keyguardStateController: KeyguardStateController,
 ) : KeyguardRepository {
     private val _animateBottomAreaDozingTransitions = MutableStateFlow(false)
     override val animateBottomAreaDozingTransitions =
@@ -102,6 +113,29 @@
     private val _clockPosition = MutableStateFlow(Position(0, 0))
     override val clockPosition = _clockPosition.asStateFlow()
 
+    override val isKeyguardShowing: Flow<Boolean> = conflatedCallbackFlow {
+        val callback =
+            object : KeyguardStateController.Callback {
+                override fun onKeyguardShowingChanged() {
+                    trySendWithFailureLogging(
+                        keyguardStateController.isShowing,
+                        TAG,
+                        "updated isKeyguardShowing"
+                    )
+                }
+            }
+
+        keyguardStateController.addCallback(callback)
+        // Adding the callback does not send an initial update.
+        trySendWithFailureLogging(
+            keyguardStateController.isShowing,
+            TAG,
+            "initial isKeyguardShowing"
+        )
+
+        awaitClose { keyguardStateController.removeCallback(callback) }
+    }
+
     override val isDozing: Flow<Boolean> = conflatedCallbackFlow {
         val callback =
             object : StatusBarStateController.StateListener {
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
index d2ab3e9..1a5670c 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryModule.kt
@@ -30,7 +30,8 @@
         impl: KeyguardQuickAffordanceRepositoryImpl
     ): KeyguardQuickAffordanceRepository
 
-    @Binds fun keyguardQuickAffordanceConfigs(
+    @Binds
+    fun keyguardQuickAffordanceConfigs(
         impl: KeyguardQuickAffordanceConfigsImpl
     ): KeyguardQuickAffordanceConfigs
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/ObserveIsKeyguardShowingUseCase.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/ObserveIsKeyguardShowingUseCase.kt
new file mode 100644
index 0000000..11af123
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/ObserveIsKeyguardShowingUseCase.kt
@@ -0,0 +1,39 @@
+/*
+ *  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 the keyguard is currently being shown.
+ *
+ * Note: this is also `true` when the lock-screen is occluded with an `Activity` "above" it in the
+ * z-order (which is not really above the system UI window, but rather - the lock-screen becomes
+ * invisible to reveal the "occluding activity").
+ */
+class ObserveIsKeyguardShowingUseCase
+@Inject
+constructor(
+    private val repository: KeyguardRepository,
+) {
+    operator fun invoke(): Flow<Boolean> {
+        return repository.isKeyguardShowing
+    }
+}
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
index aac3d75..eef8ec3 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/ObserveKeyguardQuickAffordanceUseCase.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/ObserveKeyguardQuickAffordanceUseCase.kt
@@ -29,7 +29,7 @@
 constructor(
     private val repository: KeyguardQuickAffordanceRepository,
     private val isDozingUseCase: ObserveIsDozingUseCase,
-    private val dozeAmountUseCase: ObserveDozeAmountUseCase,
+    private val isKeyguardShowingUseCase: ObserveIsKeyguardShowingUseCase,
 ) {
     operator fun invoke(
         position: KeyguardQuickAffordancePosition
@@ -37,9 +37,9 @@
         return combine(
             repository.affordance(position),
             isDozingUseCase(),
-            dozeAmountUseCase(),
-        ) { affordance, isDozing, dozeAmount ->
-            if (!isDozing && dozeAmount == 0f) {
+            isKeyguardShowingUseCase(),
+        ) { affordance, isDozing, isKeyguardShowing ->
+            if (!isDozing && isKeyguardShowing) {
                 affordance
             } else {
                 KeyguardQuickAffordanceModel.Hidden
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/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/ChipInfoCommon.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/ChipInfoCommon.kt
index e95976f..a29c588 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/ChipInfoCommon.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/ChipInfoCommon.kt
@@ -27,4 +27,4 @@
     fun getTimeoutMs(): Long
 }
 
-const val DEFAULT_TIMEOUT_MILLIS = 4000L
+const val DEFAULT_TIMEOUT_MILLIS = 10000L
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 79e1fb9..5f478ce 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
@@ -26,7 +26,6 @@
 import android.os.SystemClock
 import android.util.Log
 import android.view.LayoutInflater
-import android.view.MotionEvent
 import android.view.ViewGroup
 import android.view.WindowManager
 import android.view.accessibility.AccessibilityManager
@@ -38,7 +37,6 @@
 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
@@ -61,7 +59,6 @@
         @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,
 ) {
@@ -111,13 +108,16 @@
         } else {
             // The chip is new, so set up all our callbacks and inflate the view
             configurationController.addCallback(displayScaleListener)
-            tapGestureDetector.addOnGestureDetectedCallback(TAG, this::onScreenTapped)
-            // 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"
-            )
+            // Wake the screen if necessary so the user will see the chip. (Per b/239426653, we want
+            // the chip to show over the dream state, so we should only wake up if the screen is
+            // completely off.)
+            if (!powerManager.isScreenOn) {
+                powerManager.wakeUp(
+                        SystemClock.uptimeMillis(),
+                        PowerManager.WAKE_REASON_APPLICATION,
+                        "com.android.systemui:media_tap_to_transfer_activated"
+                )
+            }
 
             inflateAndUpdateChip(newChipInfo)
         }
@@ -172,7 +172,6 @@
         if (chipView == null) { return }
         logger.logChipRemoval(removalReason)
         configurationController.removeCallback(displayScaleListener)
-        tapGestureDetector.removeOnGestureDetectedCallback(TAG)
         windowManager.removeView(chipView)
         chipView = null
         chipInfo = null
@@ -257,15 +256,6 @@
             isAppIcon = false
         )
     }
-
-    private fun onScreenTapped(e: MotionEvent) {
-        val view = chipView ?: return
-        // If the tap is within the chip bounds, we shouldn't hide the chip (in case users think the
-        // chip is tappable).
-        if (!viewUtil.touchIsWithinView(view, e.x, e.y)) {
-            removeChip(MediaTttRemovalReason.REASON_SCREEN_TAP)
-        }
-    }
 }
 
 // Used in CTS tests UpdateMediaTapToTransferSenderDisplayTest and
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 f0e5a3a..495f697 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
@@ -41,7 +41,6 @@
 import com.android.systemui.media.taptotransfer.common.MediaTttChipControllerCommon
 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
@@ -63,7 +62,6 @@
         mainExecutor: DelayableExecutor,
         accessibilityManager: AccessibilityManager,
         configurationController: ConfigurationController,
-        tapGestureDetector: TapGestureDetector,
         powerManager: PowerManager,
         @Main private val mainHandler: Handler,
         private val uiEventLogger: MediaTttReceiverUiEventLogger,
@@ -75,7 +73,6 @@
         mainExecutor,
         accessibilityManager,
         configurationController,
-        tapGestureDetector,
         powerManager,
         R.layout.media_ttt_chip_receiver,
 ) {
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/ChipStateSender.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/ChipStateSender.kt
index cd86fff..a153cb6 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/ChipStateSender.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/ChipStateSender.kt
@@ -243,6 +243,6 @@
 // Give the Transfer*Triggered states a longer timeout since those states represent an active
 // process and we should keep the user informed about it as long as possible (but don't allow it to
 // continue indefinitely).
-private const val TRANSFER_TRIGGERED_TIMEOUT_MILLIS = 15000L
+private const val TRANSFER_TRIGGERED_TIMEOUT_MILLIS = 30000L
 
 private const val TAG = "ChipStateSender"
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 540798a..3ea11b8 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
@@ -38,7 +38,6 @@
 import com.android.systemui.media.taptotransfer.common.MediaTttLogger
 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
@@ -58,7 +57,6 @@
         @Main mainExecutor: DelayableExecutor,
         accessibilityManager: AccessibilityManager,
         configurationController: ConfigurationController,
-        tapGestureDetector: TapGestureDetector,
         powerManager: PowerManager,
         private val uiEventLogger: MediaTttSenderUiEventLogger
 ) : MediaTttChipControllerCommon<ChipSenderInfo>(
@@ -69,7 +67,6 @@
         mainExecutor,
         accessibilityManager,
         configurationController,
-        tapGestureDetector,
         powerManager,
         R.layout.media_ttt_chip,
 ) {
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/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/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/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/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/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
deleted file mode 100644
index b3f1eeb..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerExt.kt
+++ /dev/null
@@ -1,46 +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.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/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/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/dreams/complication/ComplicationUtilsTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationUtilsTest.java
index 365c529..2915f5a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationUtilsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationUtilsTest.java
@@ -19,6 +19,7 @@
 import static com.android.systemui.dreams.complication.Complication.COMPLICATION_TYPE_AIR_QUALITY;
 import static com.android.systemui.dreams.complication.Complication.COMPLICATION_TYPE_CAST_INFO;
 import static com.android.systemui.dreams.complication.Complication.COMPLICATION_TYPE_DATE;
+import static com.android.systemui.dreams.complication.Complication.COMPLICATION_TYPE_HOME_CONTROLS;
 import static com.android.systemui.dreams.complication.Complication.COMPLICATION_TYPE_TIME;
 import static com.android.systemui.dreams.complication.Complication.COMPLICATION_TYPE_WEATHER;
 import static com.android.systemui.dreams.complication.ComplicationUtils.convertComplicationType;
@@ -57,6 +58,8 @@
                 .isEqualTo(COMPLICATION_TYPE_AIR_QUALITY);
         assertThat(convertComplicationType(DreamBackend.COMPLICATION_TYPE_CAST_INFO))
                 .isEqualTo(COMPLICATION_TYPE_CAST_INFO);
+        assertThat(convertComplicationType(DreamBackend.COMPLICATION_TYPE_HOME_CONTROLS))
+                .isEqualTo(COMPLICATION_TYPE_HOME_CONTROLS);
     }
 
     @Test
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/complication/DreamHomeControlsComplicationTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/DreamHomeControlsComplicationTest.java
index 5191f63..04ff7ae 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/DreamHomeControlsComplicationTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/DreamHomeControlsComplicationTest.java
@@ -17,6 +17,9 @@
 package com.android.systemui.dreams.complication;
 
 import static com.android.systemui.controls.dagger.ControlsComponent.Visibility.AVAILABLE;
+import static com.android.systemui.dreams.complication.Complication.COMPLICATION_TYPE_HOME_CONTROLS;
+
+import static com.google.common.truth.Truth.assertThat;
 
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.Mockito.mock;
@@ -36,6 +39,7 @@
 import com.android.systemui.controls.dagger.ControlsComponent;
 import com.android.systemui.controls.management.ControlsListingController;
 import com.android.systemui.dreams.DreamOverlayStateController;
+import com.android.systemui.dreams.complication.dagger.DreamHomeControlsComplicationComponent;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -69,6 +73,9 @@
     @Mock
     private ControlsListingController mControlsListingController;
 
+    @Mock
+    private DreamHomeControlsComplicationComponent.Factory mComponentFactory;
+
     @Captor
     private ArgumentCaptor<ControlsListingController.ControlsListingCallback> mCallbackCaptor;
 
@@ -85,6 +92,14 @@
     }
 
     @Test
+    public void complicationType() {
+        final DreamHomeControlsComplication complication =
+                new DreamHomeControlsComplication(mComponentFactory);
+        assertThat(complication.getRequiredTypeAvailability()).isEqualTo(
+                COMPLICATION_TYPE_HOME_CONTROLS);
+    }
+
+    @Test
     public void complicationAvailability_serviceNotAvailable_noFavorites_doNotAddComplication() {
         final DreamHomeControlsComplication.Registrant registrant =
                 new DreamHomeControlsComplication.Registrant(mContext, mComplication,
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/keyguard/data/repository/HomeControlsKeyguardQuickAffordanceConfigTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfigTest.kt
similarity index 80%
rename from packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/HomeControlsKeyguardQuickAffordanceConfigTest.kt
rename to packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfigTest.kt
index 9ce5724..592e80b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/HomeControlsKeyguardQuickAffordanceConfigTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfigTest.kt
@@ -1,20 +1,21 @@
 /*
- * Copyright (C) 2022 The Android Open Source Project
+ *  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
+ *  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
+ *       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.
+ *  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
+package com.android.systemui.keyguard.data.quickaffordance
 
 import androidx.test.filters.SmallTest
 import com.android.systemui.R
@@ -22,8 +23,6 @@
 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
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/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfigTest.kt
similarity index 85%
rename from packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/QrCodeScannerKeyguardQuickAffordanceConfigTest.kt
rename to packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfigTest.kt
index 4bef00a..6fd04de 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/QrCodeScannerKeyguardQuickAffordanceConfigTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfigTest.kt
@@ -1,27 +1,26 @@
 /*
- * Copyright (C) 2022 The Android Open Source Project
+ *  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
+ *  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
+ *       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.
+ *  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
+package com.android.systemui.keyguard.data.quickaffordance
 
 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
@@ -51,7 +50,7 @@
         MockitoAnnotations.initMocks(this)
         whenever(controller.intent).thenReturn(INTENT_1)
 
-        underTest = QrCodeScannerKeyguardQuickAffordanceConfig(context, controller)
+        underTest = QrCodeScannerKeyguardQuickAffordanceConfig(controller)
     }
 
     @Test
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/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfigTest.kt
similarity index 66%
rename from packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/QuickAccessWalletKeyguardQuickAffordanceConfigTest.kt
rename to packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfigTest.kt
index ee1d4d8..345c51f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/QuickAccessWalletKeyguardQuickAffordanceConfigTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfigTest.kt
@@ -1,20 +1,21 @@
 /*
- * Copyright (C) 2022 The Android Open Source Project
+ *  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
+ *  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
+ *       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.
+ *  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
+package com.android.systemui.keyguard.data.quickaffordance
 
 import android.graphics.drawable.Drawable
 import android.service.quickaccesswallet.GetWalletCardsResponse
@@ -23,10 +24,7 @@
 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
@@ -48,7 +46,6 @@
 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
@@ -59,7 +56,6 @@
 
         underTest =
             QuickAccessWalletKeyguardQuickAffordanceConfig(
-                keyguardStateController,
                 walletController,
                 activityStarter,
             )
@@ -67,7 +63,7 @@
 
     @Test
     fun `affordance - keyguard showing - has wallet card - visible model`() = runBlockingTest {
-        val callback = setUpState()
+        setUpState()
         var latest: KeyguardQuickAffordanceConfig.State? = null
 
         val job = underTest.state.onEach { latest = it }.launchIn(this)
@@ -76,25 +72,11 @@
         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)
+        setUpState(isWalletEnabled = false)
         var latest: KeyguardQuickAffordanceConfig.State? = null
 
         val job = underTest.state.onEach { latest = it }.launchIn(this)
@@ -102,12 +84,11 @@
         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)
+        setUpState(isWalletQuerySuccessful = false)
         var latest: KeyguardQuickAffordanceConfig.State? = null
 
         val job = underTest.state.onEach { latest = it }.launchIn(this)
@@ -115,12 +96,11 @@
         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)
+        setUpState(hasWalletIcon = false)
         var latest: KeyguardQuickAffordanceConfig.State? = null
 
         val job = underTest.state.onEach { latest = it }.launchIn(this)
@@ -128,12 +108,11 @@
         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)
+        setUpState(hasWalletIcon = false)
         var latest: KeyguardQuickAffordanceConfig.State? = null
 
         val job = underTest.state.onEach { latest = it }.launchIn(this)
@@ -141,7 +120,6 @@
         assertThat(latest).isEqualTo(KeyguardQuickAffordanceConfig.State.Hidden)
 
         job.cancel()
-        callback?.let { verify(keyguardStateController).removeCallback(it) }
     }
 
     @Test
@@ -159,21 +137,11 @@
     }
 
     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()
@@ -203,8 +171,6 @@
                 }
             }
         }
-
-        return returnedCallback
     }
 
     companion object {
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
index 7d1cccb..10d2e4d 100644
--- 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
@@ -27,7 +27,7 @@
 
     private val modelByPosition =
         mutableMapOf<
-                KeyguardQuickAffordancePosition, MutableStateFlow<KeyguardQuickAffordanceModel>>()
+            KeyguardQuickAffordancePosition, MutableStateFlow<KeyguardQuickAffordanceModel>>()
 
     init {
         KeyguardQuickAffordancePosition.values().forEach { value ->
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
index c82803a..d40b985 100644
--- 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
@@ -17,9 +17,7 @@
 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
 
@@ -36,18 +34,13 @@
     private val _clockPosition = MutableStateFlow(Position(0, 0))
     override val clockPosition: StateFlow<Position> = _clockPosition
 
-    private val _isDozing =
-        MutableSharedFlow<Boolean>(
-            replay = 1,
-            onBufferOverflow = BufferOverflow.DROP_OLDEST,
-        )
+    private val _isKeyguardShowing = MutableStateFlow(false)
+    override val isKeyguardShowing: Flow<Boolean> = _isKeyguardShowing
+
+    private val _isDozing = MutableStateFlow(false)
     override val isDozing: Flow<Boolean> = _isDozing
 
-    private val _dozeAmount =
-        MutableSharedFlow<Float>(
-            replay = 1,
-            onBufferOverflow = BufferOverflow.DROP_OLDEST,
-        )
+    private val _dozeAmount = MutableStateFlow(0f)
     override val dozeAmount: Flow<Float> = _dozeAmount
 
     init {
@@ -67,11 +60,15 @@
         _clockPosition.value = Position(x, y)
     }
 
+    fun setKeyguardShowing(isShowing: Boolean) {
+        _isKeyguardShowing.value = isShowing
+    }
+
     fun setDozing(isDozing: Boolean) {
-        _isDozing.tryEmit(isDozing)
+        _isDozing.value = isDozing
     }
 
     fun setDozeAmount(dozeAmount: Float) {
-        _dozeAmount.tryEmit(dozeAmount)
+        _dozeAmount.value = dozeAmount
     }
 }
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
index 2ee8034..3d2c51a 100644
--- 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
@@ -20,6 +20,7 @@
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.common.data.model.Position
 import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.statusbar.policy.KeyguardStateController
 import com.android.systemui.util.mockito.argumentCaptor
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.flow.launchIn
@@ -31,6 +32,7 @@
 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
@@ -38,6 +40,7 @@
 class KeyguardRepositoryImplTest : SysuiTestCase() {
 
     @Mock private lateinit var statusBarStateController: StatusBarStateController
+    @Mock private lateinit var keyguardStateController: KeyguardStateController
 
     private lateinit var underTest: KeyguardRepositoryImpl
 
@@ -45,7 +48,7 @@
     fun setUp() {
         MockitoAnnotations.initMocks(this)
 
-        underTest = KeyguardRepositoryImpl(statusBarStateController)
+        underTest = KeyguardRepositoryImpl(statusBarStateController, keyguardStateController)
     }
 
     @Test
@@ -100,6 +103,28 @@
     }
 
     @Test
+    fun isKeyguardShowing() = runBlockingTest {
+        whenever(keyguardStateController.isShowing).thenReturn(false)
+        var latest: Boolean? = null
+        val job = underTest.isKeyguardShowing.onEach { latest = it }.launchIn(this)
+
+        assertThat(latest).isFalse()
+
+        val captor = argumentCaptor<KeyguardStateController.Callback>()
+        verify(keyguardStateController).addCallback(captor.capture())
+
+        whenever(keyguardStateController.isShowing).thenReturn(true)
+        captor.value.onKeyguardShowingChanged()
+        assertThat(latest).isTrue()
+
+        whenever(keyguardStateController.isShowing).thenReturn(false)
+        captor.value.onKeyguardShowingChanged()
+        assertThat(latest).isFalse()
+
+        job.cancel()
+    }
+
+    @Test
     fun isDozing() = runBlockingTest {
         var latest: Boolean? = null
         val job = underTest.isDozing.onEach { latest = it }.launchIn(this)
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
index a60657c5..b90400be 100644
--- 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
@@ -43,20 +43,21 @@
     private lateinit var repository: FakeKeyguardRepository
     private lateinit var quickAffordanceRepository: FakeKeyguardQuickAffordanceRepository
     private lateinit var isDozingUseCase: ObserveIsDozingUseCase
-    private lateinit var dozeAmountUseCase: ObserveDozeAmountUseCase
+    private lateinit var isKeyguardShowingUseCase: ObserveIsKeyguardShowingUseCase
 
     @Before
     fun setUp() {
         repository = FakeKeyguardRepository()
+        repository.setKeyguardShowing(true)
         isDozingUseCase = ObserveIsDozingUseCase(repository)
-        dozeAmountUseCase = ObserveDozeAmountUseCase(repository)
+        isKeyguardShowingUseCase = ObserveIsKeyguardShowingUseCase(repository)
         quickAffordanceRepository = FakeKeyguardQuickAffordanceRepository()
 
         underTest =
             ObserveKeyguardQuickAffordanceUseCase(
                 repository = quickAffordanceRepository,
                 isDozingUseCase = isDozingUseCase,
-                dozeAmountUseCase = dozeAmountUseCase,
+                isKeyguardShowingUseCase = isKeyguardShowingUseCase,
             )
     }
 
@@ -75,9 +76,10 @@
         )
 
         var latest: KeyguardQuickAffordanceModel? = null
-        val job = underTest(KeyguardQuickAffordancePosition.BOTTOM_END)
-            .onEach { latest = it }
-            .launchIn(this)
+        val job =
+            underTest(KeyguardQuickAffordancePosition.BOTTOM_END)
+                .onEach { latest = it }
+                .launchIn(this)
 
         assertThat(latest).isInstanceOf(KeyguardQuickAffordanceModel.Visible::class.java)
         val visibleModel = latest as KeyguardQuickAffordanceModel.Visible
@@ -104,16 +106,17 @@
         )
 
         var latest: KeyguardQuickAffordanceModel? = null
-        val job = underTest(KeyguardQuickAffordancePosition.BOTTOM_END)
-            .onEach { latest = it }
-            .launchIn(this)
+        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)
+    fun `invoke - affordance not visible when lockscreen is not showing`() = runBlockingTest {
+        repository.setKeyguardShowing(false)
         val configKey = HomeControlsKeyguardQuickAffordanceConfig::class
         val model =
             KeyguardQuickAffordanceModel.Visible(
@@ -127,9 +130,10 @@
         )
 
         var latest: KeyguardQuickAffordanceModel? = null
-        val job = underTest(KeyguardQuickAffordancePosition.BOTTOM_END)
-            .onEach { latest = it }
-            .launchIn(this)
+        val job =
+            underTest(KeyguardQuickAffordancePosition.BOTTOM_END)
+                .onEach { latest = it }
+                .launchIn(this)
         assertThat(latest).isEqualTo(KeyguardQuickAffordanceModel.Hidden)
         job.cancel()
     }
@@ -142,9 +146,10 @@
         )
 
         var latest: KeyguardQuickAffordanceModel? = null
-        val job = underTest(KeyguardQuickAffordancePosition.BOTTOM_START)
-            .onEach { latest = it }
-            .launchIn(this)
+        val job =
+            underTest(KeyguardQuickAffordancePosition.BOTTOM_START)
+                .onEach { latest = it }
+                .launchIn(this)
         assertThat(latest).isEqualTo(KeyguardQuickAffordanceModel.Hidden)
         job.cancel()
     }
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..00dd58e
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt
@@ -0,0 +1,503 @@
+/*
+ * 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.ObserveIsKeyguardShowingUseCase
+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 isKeyguardShowingUseCase: ObserveIsKeyguardShowingUseCase
+    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,
+            )
+        isKeyguardShowingUseCase =
+            ObserveIsKeyguardShowingUseCase(
+                repository = repository,
+            )
+        launchQuickAffordanceUseCase = FakeLaunchKeyguardQuickAffordanceUseCase()
+        homeControlsQuickAffordanceConfig = object : FakeKeyguardQuickAffordanceConfig() {}
+        quickAccessWalletAffordanceConfig = object : FakeKeyguardQuickAffordanceConfig() {}
+        qrCodeScannerAffordanceConfig = object : FakeKeyguardQuickAffordanceConfig() {}
+
+        underTest =
+            KeyguardBottomAreaViewModel(
+                observeQuickAffordanceUseCase =
+                    ObserveKeyguardQuickAffordanceUseCase(
+                        repository = affordanceRepository,
+                        isDozingUseCase = isDozingUseCase,
+                        isKeyguardShowingUseCase = isKeyguardShowingUseCase,
+                    ),
+                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 - not dozing - lockscreen showing - visible model - starts activity on click`() = // ktlint-disable max-line-length
+        runBlockingTest {
+            var latest: KeyguardQuickAffordanceViewModel? = null
+            val job = underTest.startButton.onEach { latest = it }.launchIn(this)
+
+            repository.setDozing(false)
+            repository.setKeyguardShowing(true)
+            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 - not dozing - lockscreen showing - visible model - do nothing on click`() = // ktlint-disable max-line-length
+        runBlockingTest {
+            var latest: KeyguardQuickAffordanceViewModel? = null
+            val job = underTest.endButton.onEach { latest = it }.launchIn(this)
+
+            repository.setDozing(false)
+            repository.setKeyguardShowing(true)
+            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 - not dozing - lockscreen showing - model is none`() =
+        runBlockingTest {
+            var latest: KeyguardQuickAffordanceViewModel? = null
+            val job = underTest.startButton.onEach { latest = it }.launchIn(this)
+
+            repository.setDozing(false)
+            repository.setKeyguardShowing(true)
+            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 - dozing - lockscreen showing - model is none`() = runBlockingTest {
+        var latest: KeyguardQuickAffordanceViewModel? = null
+        val job = underTest.startButton.onEach { latest = it }.launchIn(this)
+
+        repository.setDozing(true)
+        repository.setKeyguardShowing(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 `startButton - present - not dozing - lockscreen not showing - model is none`() =
+        runBlockingTest {
+            var latest: KeyguardQuickAffordanceViewModel? = null
+            val job = underTest.startButton.onEach { latest = it }.launchIn(this)
+
+            repository.setDozing(false)
+            repository.setKeyguardShowing(false)
+            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 {
+        repository.setKeyguardShowing(true)
+        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/media/taptotransfer/common/MediaTttChipControllerCommonTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttChipControllerCommonTest.kt
index 5539786..f133068 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
@@ -21,7 +21,6 @@
 import android.content.pm.PackageManager
 import android.graphics.drawable.Drawable
 import android.os.PowerManager
-import android.view.MotionEvent
 import android.view.View
 import android.view.ViewGroup
 import android.view.WindowManager
@@ -31,7 +30,6 @@
 import com.android.systemui.R
 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
@@ -76,8 +74,6 @@
     @Mock
     private lateinit var viewUtil: ViewUtil
     @Mock
-    private lateinit var tapGestureDetector: TapGestureDetector
-    @Mock
     private lateinit var powerManager: PowerManager
 
     @Before
@@ -109,29 +105,42 @@
                 fakeExecutor,
                 accessibilityManager,
                 configurationController,
-                tapGestureDetector,
                 powerManager,
         )
     }
 
     @Test
-    fun displayChip_chipAddedAndGestureDetectionStartedAndScreenOn() {
+    fun displayChip_chipAdded() {
         controllerCommon.displayChip(getState())
 
         verify(windowManager).addView(any(), any())
-        verify(tapGestureDetector).addOnGestureDetectedCallback(any(), any())
+    }
+
+    @Test
+    fun displayChip_screenOff_screenWakes() {
+        whenever(powerManager.isScreenOn).thenReturn(false)
+
+        controllerCommon.displayChip(getState())
+
         verify(powerManager).wakeUp(any(), any(), any())
     }
 
     @Test
-    fun displayChip_twice_chipAndGestureDetectionNotAddedTwice() {
+    fun displayChip_screenAlreadyOn_screenNotWoken() {
+        whenever(powerManager.isScreenOn).thenReturn(true)
+
+        controllerCommon.displayChip(getState())
+
+        verify(powerManager, never()).wakeUp(any(), any(), any())
+    }
+
+    @Test
+    fun displayChip_twice_chipNotAddedTwice() {
         controllerCommon.displayChip(getState())
         reset(windowManager)
-        reset(tapGestureDetector)
 
         controllerCommon.displayChip(getState())
         verify(windowManager, never()).addView(any(), any())
-        verify(tapGestureDetector, never()).addOnGestureDetectedCallback(any(), any())
     }
 
     @Test
@@ -204,7 +213,7 @@
     }
 
     @Test
-    fun removeChip_chipRemovedAndGestureDetectionStoppedAndRemovalLogged() {
+    fun removeChip_chipRemovedAndRemovalLogged() {
         // First, add the chip
         controllerCommon.displayChip(getState())
 
@@ -213,7 +222,6 @@
         controllerCommon.removeChip(reason)
 
         verify(windowManager).removeView(any())
-        verify(tapGestureDetector).removeOnGestureDetectedCallback(any())
         verify(logger).logChipRemoval(reason)
     }
 
@@ -325,40 +333,6 @@
         assertThat(chipView.getAppIconView().measuredHeight).isEqualTo(ICON_SIZE)
     }
 
-    @Test
-    fun tapGestureDetected_outsideViewBounds_viewHidden() {
-        controllerCommon.displayChip(getState())
-        whenever(viewUtil.touchIsWithinView(any(), any(), any())).thenReturn(false)
-        val gestureCallbackCaptor = argumentCaptor<(MotionEvent) -> Unit>()
-        verify(tapGestureDetector).addOnGestureDetectedCallback(
-            any(), capture(gestureCallbackCaptor)
-        )
-        val callback = gestureCallbackCaptor.value!!
-
-        callback.invoke(
-            MotionEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, 0f, 0f, 0)
-        )
-
-        verify(windowManager).removeView(any())
-    }
-
-    @Test
-    fun tapGestureDetected_insideViewBounds_viewNotHidden() {
-        controllerCommon.displayChip(getState())
-        whenever(viewUtil.touchIsWithinView(any(), any(), any())).thenReturn(true)
-        val gestureCallbackCaptor = argumentCaptor<(MotionEvent) -> Unit>()
-        verify(tapGestureDetector).addOnGestureDetectedCallback(
-            any(), capture(gestureCallbackCaptor)
-        )
-        val callback = gestureCallbackCaptor.value!!
-
-        callback.invoke(
-            MotionEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, 0f, 0f, 0)
-        )
-
-        verify(windowManager, never()).removeView(any())
-    }
-
     private fun getState(name: String = "name") = ChipInfo(name)
 
     private fun getChipView(): ViewGroup {
@@ -383,7 +357,6 @@
         @Main mainExecutor: DelayableExecutor,
         accessibilityManager: AccessibilityManager,
         configurationController: ConfigurationController,
-        tapGestureDetector: TapGestureDetector,
         powerManager: PowerManager,
     ) : MediaTttChipControllerCommon<ChipInfo>(
         context,
@@ -393,7 +366,6 @@
         mainExecutor,
         accessibilityManager,
         configurationController,
-        tapGestureDetector,
         powerManager,
         R.layout.media_ttt_chip,
     ) {
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 7c5d077..dbc5f7c 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
@@ -36,7 +36,6 @@
 import com.android.systemui.SysuiTestCase
 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
@@ -107,7 +106,6 @@
             FakeExecutor(FakeSystemClock()),
             accessibilityManager,
             configurationController,
-            TapGestureDetector(context),
             powerManager,
             Handler.getMain(),
             receiverUiEventLogger
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 e06a27d..cd8ee73 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
@@ -37,7 +37,6 @@
 import com.android.systemui.SysuiTestCase
 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
@@ -115,7 +114,6 @@
             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/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/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/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 d009280..fee17c7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
@@ -134,6 +134,7 @@
 import com.android.wm.shell.draganddrop.DragAndDropController;
 import com.android.wm.shell.onehanded.OneHandedController;
 import com.android.wm.shell.sysui.ShellController;
+import com.android.wm.shell.sysui.ShellInit;
 
 import org.junit.Before;
 import org.junit.Ignore;
@@ -218,6 +219,8 @@
     private BubbleEntry mBubbleEntry2User11;
 
     @Mock
+    private ShellInit mShellInit;
+    @Mock
     private ShellController mShellController;
     @Mock
     private Bubbles.BubbleExpandListener mBubbleExpandListener;
@@ -339,6 +342,7 @@
         when(mShellTaskOrganizer.getExecutor()).thenReturn(syncExecutor);
         mBubbleController = new TestableBubbleController(
                 mContext,
+                mShellInit,
                 mShellController,
                 mBubbleData,
                 mFloatingContentCoordinator,
@@ -389,6 +393,11 @@
     }
 
     @Test
+    public void instantiateController_addInitCallback() {
+        verify(mShellInit, times(1)).addInitCallback(any(), any());
+    }
+
+    @Test
     public void instantiateController_registerConfigChangeListener() {
         verify(mShellController, times(1)).addConfigurationChangeListener(any());
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/TestableBubbleController.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/TestableBubbleController.java
index f901c32..880ad187 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/TestableBubbleController.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/TestableBubbleController.java
@@ -39,6 +39,7 @@
 import com.android.wm.shell.draganddrop.DragAndDropController;
 import com.android.wm.shell.onehanded.OneHandedController;
 import com.android.wm.shell.sysui.ShellController;
+import com.android.wm.shell.sysui.ShellInit;
 
 import java.util.Optional;
 
@@ -49,6 +50,7 @@
 
     // Let's assume surfaces can be synchronized immediately.
     TestableBubbleController(Context context,
+            ShellInit shellInit,
             ShellController shellController,
             BubbleData data,
             FloatingContentCoordinator floatingContentCoordinator,
@@ -69,13 +71,13 @@
             Handler shellMainHandler,
             TaskViewTransitions taskViewTransitions,
             SyncTransactionQueue syncQueue) {
-        super(context, shellController, data, Runnable::run, floatingContentCoordinator,
+        super(context, shellInit, shellController, data, Runnable::run, floatingContentCoordinator,
                 dataRepository, statusBarService, windowManager, windowManagerShellWrapper,
                 userManager, launcherApps, bubbleLogger, taskStackListener, shellTaskOrganizer,
                 positioner, displayController, oneHandedOptional, dragAndDropController,
                 shellMainExecutor, shellMainHandler, new SyncExecutor(), taskViewTransitions,
                 syncQueue);
         setInflateSynchronously(true);
-        initialize();
+        onInit();
     }
 }
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/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/AssistDataRequester.java b/services/core/java/com/android/server/am/AssistDataRequester.java
index 98ad81d..98129ed 100644
--- a/services/core/java/com/android/server/am/AssistDataRequester.java
+++ b/services/core/java/com/android/server/am/AssistDataRequester.java
@@ -23,6 +23,8 @@
 
 import static com.android.server.wm.ActivityTaskManagerInternal.ASSIST_KEY_RECEIVER_EXTRAS;
 
+import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.app.ActivityTaskManager;
 import android.app.AppOpsManager;
 import android.app.IActivityTaskManager;
@@ -41,6 +43,7 @@
 import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Objects;
 
 /**
  * Helper class to asynchronously fetch the assist data and screenshot from the current running
@@ -141,50 +144,35 @@
     }
 
     /**
-     * Request that autofill data be loaded asynchronously. The resulting data will be provided
-     * through the {@link AssistDataRequesterCallbacks}.
-     *
-     * See {@link #requestData(List, boolean, boolean, boolean, boolean, boolean, boolean, int,
-     * String, boolean)}.
-     */
-    public void requestAutofillData(List<IBinder> activityTokens, int callingUid,
-            String callingPackage) {
-        requestData(activityTokens, true /* requestAutofillData */,
-                true /* fetchData */, false /* fetchScreenshot */,
-                true /* fetchStructure */, true /* allowFetchData */,
-                false /* allowFetchScreenshot */, false /* ignoreTopActivityCheck */, callingUid,
-                callingPackage);
-    }
-
-    /**
      * Request that assist data be loaded asynchronously. The resulting data will be provided
      * through the {@link AssistDataRequesterCallbacks}.
      *
-     * See {@link #requestData(List, boolean, boolean, boolean, boolean, boolean, boolean, int,
-     * String, boolean)}.
+     * See {@link #requestData(List, boolean, boolean, boolean, boolean, boolean, boolean, boolean,
+     * int, String, String)}.
      */
-    public void requestAssistData(List<IBinder> activityTokens, final boolean fetchData,
+    public void requestAssistData(@NonNull List<IBinder> activityTokens, final boolean fetchData,
             final boolean fetchScreenshot, boolean allowFetchData, boolean allowFetchScreenshot,
-            int callingUid, String callingPackage) {
+            int callingUid, @NonNull String callingPackage,
+            @Nullable String callingAttributionTag) {
         requestAssistData(activityTokens, fetchData, fetchScreenshot, true /* fetchStructure */,
                 allowFetchData, allowFetchScreenshot, false /* ignoreTopActivityCheck */,
-                callingUid, callingPackage);
+                callingUid, callingPackage, callingAttributionTag);
     }
 
     /**
      * Request that assist data be loaded asynchronously. The resulting data will be provided
      * through the {@link AssistDataRequesterCallbacks}.
      *
-     * See {@link #requestData(List, boolean, boolean, boolean, boolean, boolean, boolean, int,
-     * String, boolean)}.
+     * See {@link #requestData(List, boolean, boolean, boolean, boolean, boolean, boolean, boolean,
+     * int, String, String)}.
      */
-    public void requestAssistData(List<IBinder> activityTokens, final boolean fetchData,
+    public void requestAssistData(@NonNull List<IBinder> activityTokens, final boolean fetchData,
             final boolean fetchScreenshot, final boolean fetchStructure, boolean allowFetchData,
             boolean allowFetchScreenshot, boolean ignoreTopActivityCheck, int callingUid,
-            String callingPackage) {
+            @NonNull String callingPackage, @Nullable String callingAttributionTag) {
         requestData(activityTokens, false /* requestAutofillData */, fetchData, fetchScreenshot,
                 fetchStructure, allowFetchData, allowFetchScreenshot, ignoreTopActivityCheck,
-                callingUid, callingPackage);
+                callingUid, callingPackage, callingAttributionTag);
     }
 
     /**
@@ -208,14 +196,22 @@
      *     requester is allowed to fetch the assist screenshot
      * @param ignoreTopActivityCheck overrides the check for whether the activity is in focus when
      *     making the request. Used when passing an activity from Recents.
+     * @param callingUid the uid of the real caller
+     * @param callingPackage the package name of the real caller
+     * @param callingAttributionTag The {@link Context#createAttributionContext attribution tag}
+     *     of the calling context or {@code null} for default attribution
      */
-    private void requestData(List<IBinder> activityTokens, final boolean requestAutofillData,
-            final boolean fetchData, final boolean fetchScreenshot, final boolean fetchStructure,
-            boolean allowFetchData, boolean allowFetchScreenshot, boolean ignoreTopActivityCheck,
-            int callingUid, String callingPackage) {
+    private void requestData(@NonNull List<IBinder> activityTokens,
+            final boolean requestAutofillData, final boolean fetchData,
+            final boolean fetchScreenshot, final boolean fetchStructure, boolean allowFetchData,
+            boolean allowFetchScreenshot, boolean ignoreTopActivityCheck, int callingUid,
+            @NonNull String callingPackage, @Nullable String callingAttributionTag) {
         // TODO(b/34090158): Known issue, if the assist data is not allowed on the current activity,
         //                   then no assist data is requested for any of the other activities
 
+        Objects.requireNonNull(activityTokens);
+        Objects.requireNonNull(callingPackage);
+
         // Early exit if there are no activity to fetch for
         if (activityTokens.isEmpty()) {
             // No activities, just dispatch request-complete
@@ -241,8 +237,9 @@
         mAssistScreenshot.clear();
 
         if (fetchData) {
-            if (mAppOpsManager.checkOpNoThrow(mRequestStructureAppOps, callingUid, callingPackage)
-                    == MODE_ALLOWED && allowFetchData) {
+            if (mAppOpsManager.noteOpNoThrow(mRequestStructureAppOps, callingUid,
+                    callingPackage, callingAttributionTag, /* message */ null) == MODE_ALLOWED
+                    && allowFetchData) {
                 final int numActivities = activityTokens.size();
                 for (int i = 0; i < numActivities; i++) {
                     IBinder topActivity = activityTokens.get(i);
@@ -291,8 +288,9 @@
         }
 
         if (fetchScreenshot) {
-            if (mAppOpsManager.checkOpNoThrow(mRequestScreenshotAppOps, callingUid, callingPackage)
-                    == MODE_ALLOWED && allowFetchScreenshot) {
+            if (mAppOpsManager.noteOpNoThrow(mRequestScreenshotAppOps, callingUid,
+                    callingPackage, callingAttributionTag, /* message */ null) == MODE_ALLOWED
+                    && allowFetchScreenshot) {
                 try {
                     MetricsLogger.count(mContext, "assist_with_screen", 1);
                     mPendingScreenshotCount++;
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index f0a7df4..298ee97 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -3942,14 +3942,12 @@
             case AudioSystem.MODE_IN_COMMUNICATION:
             case AudioSystem.MODE_IN_CALL:
             case AudioSystem.MODE_NORMAL:
+            case AudioSystem.MODE_CALL_SCREENING:
+            case AudioSystem.MODE_CALL_REDIRECT:
+            case AudioSystem.MODE_COMMUNICATION_REDIRECT:
                 break;
-            case AudioSystem.MODE_RINGTONE:
-                // not changing anything for ringtone
-                return;
-            case AudioSystem.MODE_CURRENT:
-            case AudioSystem.MODE_INVALID:
             default:
-                // don't know what to do in this case, better bail
+                // no-op is enough for all other values
                 return;
         }
 
@@ -3977,14 +3975,12 @@
             case AudioSystem.MODE_IN_COMMUNICATION:
             case AudioSystem.MODE_IN_CALL:
             case AudioSystem.MODE_NORMAL:
+            case AudioSystem.MODE_CALL_SCREENING:
+            case AudioSystem.MODE_CALL_REDIRECT:
+            case AudioSystem.MODE_COMMUNICATION_REDIRECT:
                 break;
-            case AudioSystem.MODE_RINGTONE:
-                // not changing anything for ringtone
-                return;
-            case AudioSystem.MODE_CURRENT:
-            case AudioSystem.MODE_INVALID:
             default:
-                // don't know what to do in this case, better bail
+                // no-op is enough for all other values
                 return;
         }
 
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/AutomaticBrightnessController.java b/services/core/java/com/android/server/display/AutomaticBrightnessController.java
index 114690a..a817cea 100644
--- a/services/core/java/com/android/server/display/AutomaticBrightnessController.java
+++ b/services/core/java/com/android/server/display/AutomaticBrightnessController.java
@@ -345,7 +345,9 @@
             brightnessEvent.setFlags(brightnessEvent.getFlags()
                     | (!mAmbientLuxValid ? BrightnessEvent.FLAG_INVALID_LUX : 0)
                     | (mDisplayPolicy == DisplayPowerRequest.POLICY_DOZE
-                        ? BrightnessEvent.FLAG_DOZE_SCALE : 0));
+                        ? BrightnessEvent.FLAG_DOZE_SCALE : 0)
+                    | (mCurrentBrightnessMapper.isForIdleMode()
+                        ? BrightnessEvent.FLAG_IDLE_CURVE : 0));
         }
 
         if (!mAmbientLuxValid) {
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/display/brightness/BrightnessEvent.java b/services/core/java/com/android/server/display/brightness/BrightnessEvent.java
index 6698612..d831dbd 100644
--- a/services/core/java/com/android/server/display/brightness/BrightnessEvent.java
+++ b/services/core/java/com/android/server/display/brightness/BrightnessEvent.java
@@ -27,8 +27,9 @@
 public final class BrightnessEvent {
     public static final int FLAG_RBC = 0x1;
     public static final int FLAG_INVALID_LUX = 0x2;
-    public static final int FLAG_DOZE_SCALE = 0x3;
-    public static final int FLAG_USER_SET = 0x4;
+    public static final int FLAG_DOZE_SCALE = 0x4;
+    public static final int FLAG_USER_SET = 0x8;
+    public static final int FLAG_IDLE_CURVE = 0x16;
 
     private BrightnessReason mReason = new BrightnessReason();
     private int mDisplayId;
@@ -257,6 +258,7 @@
         return ((mFlags & FLAG_USER_SET) != 0 ? "user_set " : "")
                 + ((mFlags & FLAG_RBC) != 0 ? "rbc " : "")
                 + ((mFlags & FLAG_INVALID_LUX) != 0 ? "invalid_lux " : "")
-                + ((mFlags & FLAG_DOZE_SCALE) != 0 ? "doze_scale " : "");
+                + ((mFlags & FLAG_DOZE_SCALE) != 0 ? "doze_scale " : "")
+                + ((mFlags & FLAG_IDLE_CURVE) != 0 ? "idle_curve " : "");
     }
 }
diff --git a/services/core/java/com/android/server/inputmethod/IInputMethodInvoker.java b/services/core/java/com/android/server/inputmethod/IInputMethodInvoker.java
index 6c75dbf..eb73234 100644
--- a/services/core/java/com/android/server/inputmethod/IInputMethodInvoker.java
+++ b/services/core/java/com/android/server/inputmethod/IInputMethodInvoker.java
@@ -223,6 +223,17 @@
     }
 
     @AnyThread
+    boolean updateEditorToolType(int toolType) {
+        try {
+            mTarget.updateEditorToolType(toolType);
+        } catch (RemoteException e) {
+            logRemoteException(e);
+            return false;
+        }
+        return true;
+    }
+
+    @AnyThread
     void changeInputMethodSubtype(InputMethodSubtype subtype) {
         try {
             mTarget.changeInputMethodSubtype(subtype);
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index 6f37b51..d48acb1 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -132,6 +132,7 @@
 import android.view.IWindowManager;
 import android.view.InputChannel;
 import android.view.InputDevice;
+import android.view.MotionEvent;
 import android.view.View;
 import android.view.WindowManager;
 import android.view.WindowManager.DisplayImePolicy;
@@ -3320,7 +3321,8 @@
 
     @Override
     public boolean showSoftInput(IInputMethodClient client, IBinder windowToken, int flags,
-            ResultReceiver resultReceiver, @SoftInputShowHideReason int reason) {
+            int lastClickTooType, ResultReceiver resultReceiver,
+            @SoftInputShowHideReason int reason) {
         Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "IMMS.showSoftInput");
         int uid = Binder.getCallingUid();
         ImeTracing.getInstance().triggerManagerServiceDump(
@@ -3332,7 +3334,8 @@
             final long ident = Binder.clearCallingIdentity();
             try {
                 if (DEBUG) Slog.v(TAG, "Client requesting input be shown");
-                return showCurrentInputLocked(windowToken, flags, resultReceiver, reason);
+                return showCurrentInputLocked(
+                        windowToken, flags, lastClickTooType, resultReceiver, reason);
             } finally {
                 Binder.restoreCallingIdentity(ident);
                 Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
@@ -3404,8 +3407,15 @@
     }
 
     @GuardedBy("ImfLock.class")
-    boolean showCurrentInputLocked(IBinder windowToken, int flags, ResultReceiver resultReceiver,
-            @SoftInputShowHideReason int reason) {
+    boolean showCurrentInputLocked(IBinder windowToken, int flags,
+            ResultReceiver resultReceiver, @SoftInputShowHideReason int reason) {
+        return showCurrentInputLocked(
+                windowToken, flags, MotionEvent.TOOL_TYPE_UNKNOWN, resultReceiver, reason);
+    }
+
+    @GuardedBy("ImfLock.class")
+    private boolean showCurrentInputLocked(IBinder windowToken, int flags, int lastClickToolType,
+            ResultReceiver resultReceiver, @SoftInputShowHideReason int reason) {
         mShowRequested = true;
         if (mAccessibilityRequestingNoSoftKeyboard || mImeHiddenByDisplayPolicy) {
             return false;
@@ -3434,6 +3444,10 @@
                         + ", " + showFlags + ", " + resultReceiver + ") for reason: "
                         + InputMethodDebug.softInputDisplayReasonToString(reason));
             }
+
+            if (lastClickToolType != MotionEvent.TOOL_TYPE_UNKNOWN) {
+                curMethod.updateEditorToolType(lastClickToolType);
+            }
             // TODO(b/192412909): Check if we can always call onShowHideSoftInputRequested() or not.
             if (curMethod.showSoftInput(showInputToken, showFlags, resultReceiver)) {
                 onShowHideSoftInputRequested(true /* show */, windowToken, reason);
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsService.java b/services/core/java/com/android/server/locksettings/LockSettingsService.java
index 9b6d0e7..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();
@@ -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) {
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 8ee2151..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;
@@ -94,10 +95,7 @@
         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) {
@@ -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_GCM_KEY_SIZE * 8, new SecureRandom());
-            SecretKey keyStoreKey = keyGenerator.generateKey();
+            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 1f807f9..d9cf353 100644
--- a/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java
+++ b/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java
@@ -135,6 +135,8 @@
     private static final byte PROTECTOR_TYPE_STRONG_TOKEN_BASED = 1;
     private static final byte PROTECTOR_TYPE_WEAK_TOKEN_BASED = 2;
 
+    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;
 
@@ -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/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index b7a5f70..e022f15 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -89,6 +89,7 @@
 import android.provider.Settings;
 import android.security.GateKeeper;
 import android.service.gatekeeper.IGateKeeperService;
+import android.service.voice.VoiceInteractionManagerInternal;
 import android.stats.devicepolicy.DevicePolicyEnums;
 import android.text.TextUtils;
 import android.util.ArrayMap;
@@ -4366,6 +4367,11 @@
         Binder.withCleanCallingIdentity(() -> {
             mPm.onNewUserCreated(preCreatedUser.id, /* convertedFromPreCreated= */ true);
             dispatchUserAdded(preCreatedUser, token);
+            VoiceInteractionManagerInternal vimi = LocalServices
+                    .getService(VoiceInteractionManagerInternal.class);
+            if (vimi != null) {
+                vimi.onPreCreatedUserConversion(preCreatedUser.id);
+            }
         });
         return preCreatedUser;
     }
diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java
index 988b4eb..2a1748c 100644
--- a/services/core/java/com/android/server/power/PowerManagerService.java
+++ b/services/core/java/com/android/server/power/PowerManagerService.java
@@ -5788,6 +5788,17 @@
         }
 
         @Override // Binder call
+        public boolean areAutoPowerSaveModesEnabled() {
+            final long ident = Binder.clearCallingIdentity();
+            try {
+                return mContext.getResources().getBoolean(
+                        com.android.internal.R.bool.config_enableAutoPowerModes);
+            } finally {
+                Binder.restoreCallingIdentity(ident);
+            }
+        }
+
+        @Override // Binder call
         public boolean isPowerSaveMode() {
             final long ident = Binder.clearCallingIdentity();
             try {
diff --git a/services/core/java/com/android/server/wm/ActivityClientController.java b/services/core/java/com/android/server/wm/ActivityClientController.java
index d2a00af..cceb58d 100644
--- a/services/core/java/com/android/server/wm/ActivityClientController.java
+++ b/services/core/java/com/android/server/wm/ActivityClientController.java
@@ -1020,6 +1020,7 @@
     public boolean showAssistFromActivity(IBinder token, Bundle args) {
         final long ident = Binder.clearCallingIdentity();
         try {
+            final String callingAttributionTag;
             synchronized (mGlobalLock) {
                 final ActivityRecord caller = ActivityRecord.forTokenLocked(token);
                 final Task topRootTask = mService.getTopDisplayFocusedRootTask();
@@ -1035,9 +1036,10 @@
                             + " is not visible");
                     return false;
                 }
+                callingAttributionTag = top.launchedFromFeatureId;
             }
             return mAssistUtils.showSessionForActiveService(args, SHOW_SOURCE_APPLICATION,
-                    null /* showCallback */, token);
+                    callingAttributionTag, null /* showCallback */, token);
         } finally {
             Binder.restoreCallingIdentity(ident);
         }
@@ -1054,6 +1056,7 @@
     @Override
     public void startLocalVoiceInteraction(IBinder callingActivity, Bundle options) {
         Slog.i(TAG, "Activity tried to startLocalVoiceInteraction");
+        final String callingAttributionTag;
         synchronized (mGlobalLock) {
             final Task topRootTask = mService.getTopDisplayFocusedRootTask();
             final ActivityRecord activity = topRootTask != null
@@ -1071,9 +1074,10 @@
                 return;
             }
             activity.pendingVoiceInteractionStart = true;
+            callingAttributionTag = activity.launchedFromFeatureId;
         }
         LocalServices.getService(VoiceInteractionManagerInternal.class)
-                .startLocalVoiceInteraction(callingActivity, options);
+                .startLocalVoiceInteraction(callingActivity, callingAttributionTag, options);
     }
 
     @Override
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 2b3a685..de2bbb6 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();
@@ -4736,6 +4736,9 @@
             mPendingRemoteAnimation = options.getRemoteAnimationAdapter();
         }
         mPendingRemoteTransition = options.getRemoteTransition();
+        // Since options gets sent to client apps, remove transition information from it.
+        options.setRemoteTransition(null);
+        options.setRemoteAnimationAdapter(null);
     }
 
     void applyOptionsAnimation() {
@@ -9744,6 +9747,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/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index 471424b..3aa7489 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -2986,7 +2986,7 @@
 
     @Override
     public boolean requestAssistDataForTask(IAssistDataReceiver receiver, int taskId,
-            String callingPackageName) {
+            String callingPackageName, @Nullable String callingAttributionTag) {
         mAmInternal.enforceCallingPermission(android.Manifest.permission.GET_TOP_ACTIVITY_INFO,
                 "requestAssistDataForTask()");
         final long callingId = Binder.clearCallingIdentity();
@@ -3013,7 +3013,7 @@
         requester.requestAssistData(topActivityToken, true /* fetchData */,
                 false /* fetchScreenshot */, false /* fetchStructure */, true /* allowFetchData */,
                 false /* allowFetchScreenshot*/, true /* ignoreFocusCheck */,
-                Binder.getCallingUid(), callingPackageName);
+                Binder.getCallingUid(), callingPackageName, callingAttributionTag);
 
         return true;
     }
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/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java
index 693f9c4..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) {
@@ -2841,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/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 5c20258..4f324f2 100644
--- a/services/core/java/com/android/server/wm/TransitionController.java
+++ b/services/core/java/com/android/server/wm/TransitionController.java
@@ -479,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 */);
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/wmtests/src/com/android/server/wm/AssistDataRequesterTest.java b/services/tests/wmtests/src/com/android/server/wm/AssistDataRequesterTest.java
index 2e85897..06b4ad9 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AssistDataRequesterTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AssistDataRequesterTest.java
@@ -87,6 +87,7 @@
 
     private static final int TEST_UID = 0;
     private static final String TEST_PACKAGE = "";
+    private static final String TEST_ATTRIBUTION_TAG = "";
 
     private AssistDataRequester mDataRequester;
     private Callbacks mCallbacks;
@@ -148,9 +149,9 @@
             boolean assistScreenshotAllowed) throws Exception {
         doReturn(currentActivityAssistAllowed).when(mAtm).isAssistDataAllowedOnCurrentActivity();
         doReturn(assistStructureAllowed ? MODE_ALLOWED : MODE_ERRORED).when(mAppOpsManager)
-                .checkOpNoThrow(eq(OP_ASSIST_STRUCTURE), anyInt(), anyString());
+                .noteOpNoThrow(eq(OP_ASSIST_STRUCTURE), anyInt(), anyString(), any(), any());
         doReturn(assistScreenshotAllowed ? MODE_ALLOWED : MODE_ERRORED).when(mAppOpsManager)
-                .checkOpNoThrow(eq(OP_ASSIST_SCREENSHOT), anyInt(), anyString());
+                .noteOpNoThrow(eq(OP_ASSIST_SCREENSHOT), anyInt(), anyString(), any(), any());
     }
 
     @Test
@@ -160,7 +161,8 @@
                 CALLER_ASSIST_SCREENSHOT_ALLOWED);
 
         mDataRequester.requestAssistData(createActivityList(5), FETCH_DATA, FETCH_SCREENSHOTS,
-                ALLOW_FETCH_DATA, ALLOW_FETCH_SCREENSHOTS, TEST_UID, TEST_PACKAGE);
+                ALLOW_FETCH_DATA, ALLOW_FETCH_SCREENSHOTS, TEST_UID, TEST_PACKAGE,
+                TEST_ATTRIBUTION_TAG);
         assertReceivedDataCount(5, 5, 1, 1);
     }
 
@@ -170,7 +172,8 @@
                 CALLER_ASSIST_SCREENSHOT_ALLOWED);
 
         mDataRequester.requestAssistData(createActivityList(0), FETCH_DATA, FETCH_SCREENSHOTS,
-                ALLOW_FETCH_DATA, ALLOW_FETCH_SCREENSHOTS, TEST_UID, TEST_PACKAGE);
+                ALLOW_FETCH_DATA, ALLOW_FETCH_SCREENSHOTS, TEST_UID, TEST_PACKAGE,
+                TEST_ATTRIBUTION_TAG);
         assertReceivedDataCount(0, 0, 0, 0);
     }
 
@@ -180,7 +183,8 @@
                 CALLER_ASSIST_SCREENSHOT_ALLOWED);
 
         mDataRequester.requestAssistData(createActivityList(5), FETCH_DATA, FETCH_SCREENSHOTS,
-                ALLOW_FETCH_DATA, ALLOW_FETCH_SCREENSHOTS, TEST_UID, TEST_PACKAGE);
+                ALLOW_FETCH_DATA, ALLOW_FETCH_SCREENSHOTS, TEST_UID, TEST_PACKAGE,
+                TEST_ATTRIBUTION_TAG);
         assertReceivedDataCount(0, 1, 0, 1);
     }
 
@@ -191,7 +195,8 @@
 
         mCallbacks.mCanHandleReceivedData = false;
         mDataRequester.requestAssistData(createActivityList(5), FETCH_DATA, FETCH_SCREENSHOTS,
-                ALLOW_FETCH_DATA, ALLOW_FETCH_SCREENSHOTS, TEST_UID, TEST_PACKAGE);
+                ALLOW_FETCH_DATA, ALLOW_FETCH_SCREENSHOTS, TEST_UID, TEST_PACKAGE,
+                TEST_ATTRIBUTION_TAG);
         assertEquals(5, mDataRequester.getPendingDataCount());
         assertEquals(1, mDataRequester.getPendingScreenshotCount());
         mGate.countDown();
@@ -226,7 +231,8 @@
                 CALLER_ASSIST_SCREENSHOT_ALLOWED);
 
         mDataRequester.requestAssistData(createActivityList(5), !FETCH_DATA, FETCH_SCREENSHOTS,
-                ALLOW_FETCH_DATA, ALLOW_FETCH_SCREENSHOTS, TEST_UID, TEST_PACKAGE);
+                ALLOW_FETCH_DATA, ALLOW_FETCH_SCREENSHOTS, TEST_UID, TEST_PACKAGE,
+                TEST_ATTRIBUTION_TAG);
         assertReceivedDataCount(0, 0, 0, 1);
     }
 
@@ -236,7 +242,8 @@
                 CALLER_ASSIST_SCREENSHOT_ALLOWED);
 
         mDataRequester.requestAssistData(createActivityList(5), FETCH_DATA, FETCH_SCREENSHOTS,
-                ALLOW_FETCH_DATA, ALLOW_FETCH_SCREENSHOTS, TEST_UID, TEST_PACKAGE);
+                ALLOW_FETCH_DATA, ALLOW_FETCH_SCREENSHOTS, TEST_UID, TEST_PACKAGE,
+                TEST_ATTRIBUTION_TAG);
         // Expect a single null data when the appops is denied
         assertReceivedDataCount(0, 1, 0, 1);
     }
@@ -249,7 +256,8 @@
                 anyBoolean(), anyBoolean());
 
         mDataRequester.requestAssistData(createActivityList(5), FETCH_DATA, FETCH_SCREENSHOTS,
-                ALLOW_FETCH_DATA, ALLOW_FETCH_SCREENSHOTS, TEST_UID, TEST_PACKAGE);
+                ALLOW_FETCH_DATA, ALLOW_FETCH_SCREENSHOTS, TEST_UID, TEST_PACKAGE,
+                TEST_ATTRIBUTION_TAG);
         // Expect a single null data when requestAssistContextExtras() fails
         assertReceivedDataCount(0, 1, 0, 1);
     }
@@ -261,7 +269,8 @@
                 CALLER_ASSIST_SCREENSHOT_ALLOWED);
 
         mDataRequester.requestAssistData(createActivityList(5), FETCH_DATA, !FETCH_SCREENSHOTS,
-                ALLOW_FETCH_DATA, ALLOW_FETCH_SCREENSHOTS, TEST_UID, TEST_PACKAGE);
+                ALLOW_FETCH_DATA, ALLOW_FETCH_SCREENSHOTS, TEST_UID, TEST_PACKAGE,
+                TEST_ATTRIBUTION_TAG);
         assertReceivedDataCount(5, 5, 0, 0);
     }
 
@@ -272,7 +281,8 @@
                 !CALLER_ASSIST_SCREENSHOT_ALLOWED);
 
         mDataRequester.requestAssistData(createActivityList(5), FETCH_DATA, FETCH_SCREENSHOTS,
-                ALLOW_FETCH_DATA, ALLOW_FETCH_SCREENSHOTS, TEST_UID, TEST_PACKAGE);
+                ALLOW_FETCH_DATA, ALLOW_FETCH_SCREENSHOTS, TEST_UID, TEST_PACKAGE,
+                TEST_ATTRIBUTION_TAG);
         // Expect a single null screenshot when the appops is denied
         assertReceivedDataCount(5, 5, 0, 1);
     }
@@ -284,7 +294,8 @@
 
         mCallbacks.mCanHandleReceivedData = false;
         mDataRequester.requestAssistData(createActivityList(5), FETCH_DATA, FETCH_SCREENSHOTS,
-                ALLOW_FETCH_DATA, ALLOW_FETCH_SCREENSHOTS, TEST_UID, TEST_PACKAGE);
+                ALLOW_FETCH_DATA, ALLOW_FETCH_SCREENSHOTS, TEST_UID, TEST_PACKAGE,
+                TEST_ATTRIBUTION_TAG);
         mGate.countDown();
         waitForIdle(mHandler);
         assertThat(mCallbacks.mReceivedData).isEmpty();
@@ -297,7 +308,8 @@
                 CALLER_ASSIST_SCREENSHOT_ALLOWED);
 
         mDataRequester.requestAssistData(createActivityList(5), FETCH_DATA, FETCH_SCREENSHOTS,
-                !ALLOW_FETCH_DATA, !ALLOW_FETCH_SCREENSHOTS, TEST_UID, TEST_PACKAGE);
+                !ALLOW_FETCH_DATA, !ALLOW_FETCH_SCREENSHOTS, TEST_UID, TEST_PACKAGE,
+                TEST_ATTRIBUTION_TAG);
         assertReceivedDataCount(0, 1, 0, 1);
     }
 
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/usage/java/com/android/server/usage/UsageStatsService.java b/services/usage/java/com/android/server/usage/UsageStatsService.java
index 959429e..046ff66 100644
--- a/services/usage/java/com/android/server/usage/UsageStatsService.java
+++ b/services/usage/java/com/android/server/usage/UsageStatsService.java
@@ -2294,6 +2294,11 @@
         }
 
         @Override
+        public boolean isAppStandbyEnabled() {
+            return mAppStandby.isAppIdleEnabled();
+        }
+
+        @Override
         public boolean isAppInactive(String packageName, int userId, String callingPackage) {
             final int callingUid = Binder.getCallingUid();
             try {
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
index 7699262..4b9b4a9 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
@@ -102,6 +102,7 @@
 import com.android.server.pm.UserManagerInternal;
 import com.android.server.pm.permission.LegacyPermissionManagerInternal;
 import com.android.server.soundtrigger.SoundTriggerInternal;
+import com.android.server.utils.Slogf;
 import com.android.server.utils.TimingsTraceAndSlog;
 import com.android.server.wm.ActivityTaskManagerInternal;
 
@@ -134,18 +135,21 @@
     private final RemoteCallbackList<IVoiceInteractionSessionListener>
             mVoiceInteractionSessionListeners = new RemoteCallbackList<>();
 
+    // TODO(b/226201975): remove once RoleService supports pre-created users
+    private final ArrayList<UserHandle> mIgnoredPreCreatedUsers = new ArrayList<>();
+
     public VoiceInteractionManagerService(Context context) {
         super(context);
         mContext = context;
         mResolver = context.getContentResolver();
+        mUserManagerInternal = Objects.requireNonNull(
+                LocalServices.getService(UserManagerInternal.class));
         mDbHelper = new DatabaseHelper(context);
         mServiceStub = new VoiceInteractionManagerServiceStub();
         mAmInternal = Objects.requireNonNull(
                 LocalServices.getService(ActivityManagerInternal.class));
         mAtmInternal = Objects.requireNonNull(
                 LocalServices.getService(ActivityTaskManagerInternal.class));
-        mUserManagerInternal = Objects.requireNonNull(
-                LocalServices.getService(UserManagerInternal.class));
 
         LegacyPermissionManagerInternal permissionManagerInternal = LocalServices.getService(
                 LegacyPermissionManagerInternal.class);
@@ -223,12 +227,13 @@
 
     class LocalService extends VoiceInteractionManagerInternal {
         @Override
-        public void startLocalVoiceInteraction(IBinder callingActivity, Bundle options) {
+        public void startLocalVoiceInteraction(@NonNull IBinder callingActivity,
+                @Nullable String attributionTag, @NonNull Bundle options) {
             if (DEBUG) {
                 Slog.i(TAG, "startLocalVoiceInteraction " + callingActivity);
             }
             VoiceInteractionManagerService.this.mServiceStub.startLocalVoiceInteraction(
-                    callingActivity, options);
+                    callingActivity, attributionTag, options);
         }
 
         @Override
@@ -300,6 +305,25 @@
             }
             return hotwordDetectionConnection.mIdentity;
         }
+
+        @Override
+        public void onPreCreatedUserConversion(int userId) {
+            Slogf.d(TAG, "onPreCreatedUserConversion(%d)", userId);
+
+            for (int i = 0; i < mIgnoredPreCreatedUsers.size(); i++) {
+                UserHandle preCreatedUser = mIgnoredPreCreatedUsers.get(i);
+                if (preCreatedUser.getIdentifier() == userId) {
+                    Slogf.d(TAG, "Updating role on pre-created user %d", userId);
+                    mServiceStub.mRoleObserver.onRoleHoldersChanged(RoleManager.ROLE_ASSISTANT,
+                            preCreatedUser);
+                    mIgnoredPreCreatedUsers.remove(i);
+                    return;
+                }
+            }
+            Slogf.w(TAG, "onPreCreatedUserConversion(%d): not available on "
+                    + "mIgnoredPreCreatedUserIds (%s)", userId, mIgnoredPreCreatedUsers);
+        }
+
     }
 
     // implementation entry point and binder service
@@ -317,10 +341,12 @@
         private boolean mTemporarilyDisabled;
 
         private final boolean mEnableService;
+        // TODO(b/226201975): remove reference once RoleService supports pre-created users
+        private final RoleObserver mRoleObserver;
 
         VoiceInteractionManagerServiceStub() {
             mEnableService = shouldEnableService(mContext);
-            new RoleObserver(mContext.getMainExecutor());
+            mRoleObserver = new RoleObserver(mContext.getMainExecutor());
         }
 
         void handleUserStop(String packageName, int userHandle) {
@@ -383,14 +409,15 @@
         }
 
         // TODO: VI Make sure the caller is the current user or profile
-        void startLocalVoiceInteraction(final IBinder token, Bundle options) {
+        void startLocalVoiceInteraction(@NonNull final IBinder token,
+                @Nullable String attributionTag, @NonNull Bundle options) {
             if (mImpl == null) return;
 
             final int callingUid = Binder.getCallingUid();
             final long caller = Binder.clearCallingIdentity();
             try {
                 mImpl.showSessionLocked(options,
-                        VoiceInteractionSession.SHOW_SOURCE_ACTIVITY,
+                        VoiceInteractionSession.SHOW_SOURCE_ACTIVITY, attributionTag,
                         new IVoiceInteractionSessionShowCallback.Stub() {
                             @Override
                             public void onFailed() {
@@ -898,13 +925,13 @@
         }
 
         @Override
-        public void showSession(Bundle args, int flags) {
+        public void showSession(@NonNull Bundle args, int flags, @Nullable String attributionTag) {
             synchronized (this) {
                 enforceIsCurrentVoiceInteractionService();
 
                 final long caller = Binder.clearCallingIdentity();
                 try {
-                    mImpl.showSessionLocked(args, flags, null, null);
+                    mImpl.showSessionLocked(args, flags, attributionTag, null, null);
                 } finally {
                     Binder.restoreCallingIdentity(caller);
                 }
@@ -929,7 +956,8 @@
         }
 
         @Override
-        public boolean showSessionFromSession(IBinder token, Bundle sessionArgs, int flags) {
+        public boolean showSessionFromSession(@NonNull IBinder token, @NonNull Bundle sessionArgs,
+                int flags, @Nullable String attributionTag) {
             synchronized (this) {
                 if (mImpl == null) {
                     Slog.w(TAG, "showSessionFromSession without running voice interaction service");
@@ -937,7 +965,7 @@
                 }
                 final long caller = Binder.clearCallingIdentity();
                 try {
-                    return mImpl.showSessionLocked(sessionArgs, flags, null, null);
+                    return mImpl.showSessionLocked(sessionArgs, flags, attributionTag, null, null);
                 } finally {
                     Binder.restoreCallingIdentity(caller);
                 }
@@ -961,8 +989,8 @@
         }
 
         @Override
-        public int startVoiceActivity(IBinder token, Intent intent, String resolvedType,
-                String callingFeatureId) {
+        public int startVoiceActivity(@NonNull IBinder token, @NonNull Intent intent,
+                @Nullable String resolvedType, @Nullable String attributionTag) {
             synchronized (this) {
                 if (mImpl == null) {
                     Slog.w(TAG, "startVoiceActivity without running voice interaction service");
@@ -980,8 +1008,8 @@
                     } else {
                         Slog.w(TAG, "Cannot find ActivityInfo in startVoiceActivity.");
                     }
-                    return mImpl.startVoiceActivityLocked(
-                            callingFeatureId, callingPid, callingUid, token, intent, resolvedType);
+                    return mImpl.startVoiceActivityLocked(attributionTag, callingPid, callingUid,
+                            token, intent, resolvedType);
                 } finally {
                     Binder.restoreCallingIdentity(caller);
                 }
@@ -989,8 +1017,8 @@
         }
 
         @Override
-        public int startAssistantActivity(IBinder token, Intent intent, String resolvedType,
-                String callingFeatureId) {
+        public int startAssistantActivity(@NonNull IBinder token, @NonNull Intent intent,
+                @Nullable String resolvedType, @Nullable String attributionTag) {
             synchronized (this) {
                 if (mImpl == null) {
                     Slog.w(TAG, "startAssistantActivity without running voice interaction service");
@@ -1000,7 +1028,7 @@
                 final int callingUid = Binder.getCallingUid();
                 final long caller = Binder.clearCallingIdentity();
                 try {
-                    return mImpl.startAssistantActivityLocked(callingFeatureId, callingPid,
+                    return mImpl.startAssistantActivityLocked(attributionTag, callingPid,
                             callingUid, token, intent, resolvedType);
                 } finally {
                     Binder.restoreCallingIdentity(caller);
@@ -1669,8 +1697,10 @@
 
         @android.annotation.EnforcePermission(android.Manifest.permission.ACCESS_VOICE_INTERACTION_SERVICE)
         @Override
-        public boolean showSessionForActiveService(Bundle args, int sourceFlags,
-                IVoiceInteractionSessionShowCallback showCallback, IBinder activityToken) {
+        public boolean showSessionForActiveService(@NonNull Bundle args, int sourceFlags,
+                @Nullable String attributionTag,
+                @Nullable IVoiceInteractionSessionShowCallback showCallback,
+                @Nullable IBinder activityToken) {
             if (DEBUG_USER) Slog.d(TAG, "showSessionForActiveService()");
 
             synchronized (this) {
@@ -1691,7 +1721,7 @@
                             sourceFlags
                                     | VoiceInteractionSession.SHOW_WITH_ASSIST
                                     | VoiceInteractionSession.SHOW_WITH_SCREENSHOT,
-                            showCallback, activityToken);
+                            attributionTag, showCallback, activityToken);
                 } finally {
                     Binder.restoreCallingIdentity(caller);
                 }
@@ -1882,6 +1912,7 @@
                 pw.println("  mTemporarilyDisabled: " + mTemporarilyDisabled);
                 pw.println("  mCurUser: " + mCurUser);
                 pw.println("  mCurUserSupported: " + mCurUserSupported);
+                pw.println("  mIgnoredPreCreatedUsers: " + mIgnoredPreCreatedUsers);
                 dumpSupportedUsers(pw, "  ");
                 mDbHelper.dump(pw);
                 if (mImpl == null) {
@@ -1995,6 +2026,23 @@
 
                 List<String> roleHolders = mRm.getRoleHoldersAsUser(roleName, user);
 
+                // TODO(b/226201975): this method is beling called when a pre-created user is added,
+                // at which point it doesn't have any role holders. But it's not called again when
+                // the actual user is added (i.e., when the  pre-created user is converted), so we
+                // need to save the user id and call this method again when it's converted
+                // (at onPreCreatedUserConversion()).
+                // Once RoleService properly handles pre-created users, this workaround should be
+                // removed.
+                if (roleHolders.isEmpty()) {
+                    UserInfo userInfo = mUserManagerInternal.getUserInfo(user.getIdentifier());
+                    if (userInfo != null && userInfo.preCreated) {
+                        Slogf.d(TAG, "onRoleHoldersChanged(): ignoring pre-created user %s for now",
+                                userInfo.toFullString());
+                        mIgnoredPreCreatedUsers.add(user);
+                        return;
+                    }
+                }
+
                 int userId = user.getIdentifier();
                 if (roleHolders.isEmpty()) {
                     Settings.Secure.putStringForUser(getContext().getContentResolver(),
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java
index b9793ca..9f66059 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java
@@ -244,8 +244,10 @@
                 /* direct= */ true);
     }
 
-    public boolean showSessionLocked(Bundle args, int flags,
-            IVoiceInteractionSessionShowCallback showCallback, IBinder activityToken) {
+    public boolean showSessionLocked(@NonNull Bundle args, int flags,
+            @Nullable String attributionTag,
+            @Nullable IVoiceInteractionSessionShowCallback showCallback,
+            @Nullable IBinder activityToken) {
         if (mActiveSession == null) {
             mActiveSession = new VoiceInteractionSessionConnection(mServiceStub,
                     mSessionComponentName, mUser, mContext, this,
@@ -269,8 +271,8 @@
         } else {
             visibleActivities = allVisibleActivities;
         }
-        return mActiveSession.showLocked(args, flags, mDisabledShowContext, showCallback,
-                visibleActivities);
+        return mActiveSession.showLocked(args, flags, attributionTag, mDisabledShowContext,
+                showCallback, visibleActivities);
     }
 
     public void getActiveServiceSupportedActions(List<String> commands,
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceShellCommand.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceShellCommand.java
index 9bdf4e4..80f5dc5 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceShellCommand.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceShellCommand.java
@@ -114,8 +114,8 @@
 
         try {
             Bundle args = new Bundle();
-            boolean ok = mService.showSessionForActiveService(args, /* sourceFlags= */ 0, callback,
-                    /* activityToken= */ null);
+            boolean ok = mService.showSessionForActiveService(args, /* sourceFlags= */ 0,
+                     /* attributionTag= */ null, callback, /* activityToken= */ null);
 
             if (!ok) {
                 pw.println("showSessionForActiveService() returned false");
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionSessionConnection.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionSessionConnection.java
index 093e976..63781cc 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionSessionConnection.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionSessionConnection.java
@@ -29,6 +29,8 @@
 import static com.android.server.wm.ActivityTaskManagerInternal.ASSIST_KEY_STRUCTURE;
 import static com.android.server.wm.ActivityTaskManagerInternal.ASSIST_TASK_ID;
 
+import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.app.ActivityManager;
 import android.app.ActivityTaskManager;
 import android.app.AppOpsManager;
@@ -263,9 +265,9 @@
         return flags;
     }
 
-    public boolean showLocked(Bundle args, int flags, int disabledContext,
-            IVoiceInteractionSessionShowCallback showCallback,
-            List<ActivityAssistInfo> topActivities) {
+    public boolean showLocked(@NonNull Bundle args, int flags, @Nullable String attributionTag,
+            int disabledContext, @Nullable IVoiceInteractionSessionShowCallback showCallback,
+            @NonNull List<ActivityAssistInfo> topActivities) {
         if (mBound) {
             if (!mFullyBound) {
                 mFullyBound = mContext.bindServiceAsUser(mBindIntent, mFullConnection,
@@ -291,12 +293,15 @@
                 for (int i = 0; i < topActivitiesCount; i++) {
                     topActivitiesToken.add(topActivities.get(i).getActivityToken());
                 }
+
                 mAssistDataRequester.requestAssistData(topActivitiesToken,
                         fetchData,
                         fetchScreenshot,
                         (disabledContext & VoiceInteractionSession.SHOW_WITH_ASSIST) == 0,
                         (disabledContext & VoiceInteractionSession.SHOW_WITH_SCREENSHOT) == 0,
-                        mCallingUid, mSessionComponentName.getPackageName());
+                        mCallingUid,
+                        mSessionComponentName.getPackageName(),
+                        attributionTag);
 
                 boolean needDisclosure = mAssistDataRequester.getPendingDataCount() > 0
                         || mAssistDataRequester.getPendingScreenshotCount() > 0;
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/src/com/android/server/wm/flicker/helpers/CameraAppHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/CameraAppHelper.kt
new file mode 100644
index 0000000..bd0ae4d
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/CameraAppHelper.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.server.wm.flicker.helpers
+
+import android.app.Instrumentation
+import android.content.Intent
+import android.content.pm.PackageManager
+import android.content.pm.ResolveInfo
+import android.provider.MediaStore
+import com.android.server.wm.traces.common.ComponentMatcher
+import com.android.server.wm.traces.common.IComponentMatcher
+
+class CameraAppHelper @JvmOverloads constructor(
+        instrumentation: Instrumentation,
+        private val pkgManager: PackageManager = instrumentation.context.packageManager
+) : StandardAppHelper(instrumentation, getCameraLauncherName(pkgManager),
+        getCameraComponent(pkgManager)){
+    companion object{
+        private fun getCameraIntent(): Intent {
+            return Intent(MediaStore.ACTION_IMAGE_CAPTURE)
+        }
+
+        private fun getResolveInfo(pkgManager: PackageManager): ResolveInfo {
+            val intent = getCameraIntent()
+            return pkgManager.resolveActivity(intent, PackageManager.MATCH_DEFAULT_ONLY)
+                    ?: error("unable to resolve camera activity")
+        }
+
+        private fun getCameraComponent(pkgManager: PackageManager): IComponentMatcher {
+            val resolveInfo = getResolveInfo(pkgManager)
+            return ComponentMatcher(resolveInfo.activityInfo.packageName,
+                    className = resolveInfo.activityInfo.name)
+        }
+
+        private fun getCameraLauncherName(pkgManager: PackageManager): String {
+            val resolveInfo = getResolveInfo(pkgManager)
+            return resolveInfo.activityInfo.name
+        }
+    }
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppAfterCameraTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppAfterCameraTest.kt
new file mode 100644
index 0000000..2c2e860
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppAfterCameraTest.kt
@@ -0,0 +1,97 @@
+/*
+ * 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.wm.flicker.launch
+
+import android.platform.test.annotations.FlakyTest
+import android.platform.test.annotations.Presubmit
+import android.platform.test.annotations.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.server.wm.flicker.helpers.CameraAppHelper
+import org.junit.FixMethodOrder
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+/**
+ * Test launching an app after cold opening camera
+ *
+ * To run this test: `atest FlickerTests:OpenAppAfterCameraTest`
+ *
+ * Notes:
+ * Some default assertions are inherited [OpenAppTransition]
+ */
+@RequiresDevice
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+@Group1
+open class OpenAppAfterCameraTest(
+    testSpec: FlickerTestParameter
+) : OpenAppFromLauncherTransition(testSpec) {
+    private val cameraApp = CameraAppHelper(instrumentation) /** {@inheritDoc} */
+    override val transition: FlickerBuilder.() -> Unit
+        get() = {
+            super.transition(this)
+            setup {
+                eachRun {
+                    // 1. Open camera - cold -> close it first
+                    cameraApp.exit(wmHelper)
+                    cameraApp.launchViaIntent(wmHelper)
+                    // 2. Press home button (button nav mode) / swipe up to home (gesture nav mode)
+                    tapl.goHome()
+                }
+            }
+            teardown {
+                eachRun {
+                    testApp.exit(wmHelper)
+                }
+            }
+            transitions {
+                testApp.launchViaIntent(wmHelper)
+            }
+        }
+
+    /** {@inheritDoc} */
+    @FlakyTest(bugId = 206753786)
+    @Test
+    override fun navBarLayerPositionAtStartAndEnd() = super.navBarLayerPositionAtStartAndEnd()
+
+    /** {@inheritDoc} */
+    @Presubmit
+    @Test
+    override fun appLayerReplacesLauncher() = super.appLayerReplacesLauncher()
+
+    companion object {
+        /**
+         * Creates the test configurations.
+         *
+         * See [FlickerTestParameterFactory.getConfigNonRotationTests] for configuring
+         * repetitions, screen orientation and navigation modes.
+         */
+        @Parameterized.Parameters(name = "{0}")
+        @JvmStatic
+        fun getParams(): Collection<FlickerTestParameter> {
+            return FlickerTestParameterFactory.getInstance()
+                    .getConfigNonRotationTests()
+        }
+    }
+}
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);