Merge "Add name space "wear" to DeviceConfig"
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/core/api/current.txt b/core/api/current.txt
index bd2a438..af18792 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -45091,6 +45091,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);
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/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/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/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/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/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/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/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/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/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/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 2008a75..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
@@ -78,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;
 
@@ -141,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;
@@ -152,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;
@@ -170,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;
@@ -191,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();
     }
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/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/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/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/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/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/DreamHomeControlsComplication.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/DreamHomeControlsComplication.java
index 02c5de3..1c72e49 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/complication/DreamHomeControlsComplication.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/DreamHomeControlsComplication.java
@@ -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/flags/Flags.java b/packages/SystemUI/src/com/android/systemui/flags/Flags.java
index 20c8971..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);
 
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/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/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/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/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/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
index cb9cbba..00dd58e 100644
--- 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
@@ -33,6 +33,7 @@
 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
@@ -65,7 +66,7 @@
     private lateinit var affordanceRepository: FakeKeyguardQuickAffordanceRepository
     private lateinit var repository: FakeKeyguardRepository
     private lateinit var isDozingUseCase: ObserveIsDozingUseCase
-    private lateinit var dozeAmountUseCase: ObserveDozeAmountUseCase
+    private lateinit var isKeyguardShowingUseCase: ObserveIsKeyguardShowingUseCase
     private lateinit var launchQuickAffordanceUseCase: FakeLaunchKeyguardQuickAffordanceUseCase
     private lateinit var homeControlsQuickAffordanceConfig: FakeKeyguardQuickAffordanceConfig
     private lateinit var quickAccessWalletAffordanceConfig: FakeKeyguardQuickAffordanceConfig
@@ -83,8 +84,8 @@
             ObserveIsDozingUseCase(
                 repository = repository,
             )
-        dozeAmountUseCase =
-            ObserveDozeAmountUseCase(
+        isKeyguardShowingUseCase =
+            ObserveIsKeyguardShowingUseCase(
                 repository = repository,
             )
         launchQuickAffordanceUseCase = FakeLaunchKeyguardQuickAffordanceUseCase()
@@ -98,7 +99,7 @@
                     ObserveKeyguardQuickAffordanceUseCase(
                         repository = affordanceRepository,
                         isDozingUseCase = isDozingUseCase,
-                        dozeAmountUseCase = dozeAmountUseCase,
+                        isKeyguardShowingUseCase = isKeyguardShowingUseCase,
                     ),
                 onQuickAffordanceClickedUseCase =
                     OnKeyguardQuickAffordanceClickedUseCase(
@@ -140,12 +141,13 @@
     }
 
     @Test
-    fun `startButton - present and not dozing - visible model - starts activity on click`() =
+    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,
@@ -168,12 +170,13 @@
         }
 
     @Test
-    fun `endButton - present and not dozing - visible model - do nothing on click`() =
+    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,
@@ -197,35 +200,38 @@
         }
 
     @Test
-    fun `startButton - not present and not dozing - model is none`() = runBlockingTest {
-        var latest: KeyguardQuickAffordanceViewModel? = null
-        val job = underTest.startButton.onEach { latest = it }.launchIn(this)
+    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)
-        val config =
-            TestConfig(
-                isVisible = false,
-            )
-        val configKey =
-            setUpQuickAffordanceModel(
-                position = KeyguardQuickAffordancePosition.BOTTOM_START,
+            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,
             )
-
-        assertQuickAffordanceViewModel(
-            viewModel = latest,
-            testConfig = config,
-            configKey = configKey,
-        )
-        job.cancel()
-    }
+            job.cancel()
+        }
 
     @Test
-    fun `startButton - present but dozing - model is none`() = runBlockingTest {
+    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,
@@ -248,6 +254,35 @@
     }
 
     @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)
@@ -287,6 +322,7 @@
 
     @Test
     fun isIndicationAreaPadded() = runBlockingTest {
+        repository.setKeyguardShowing(true)
         val values = mutableListOf<Boolean>()
         val job = underTest.isIndicationAreaPadded.onEach(values::add).launchIn(this)
 
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/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/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/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 111d8a0..de2bbb6 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -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() {
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 {