Merge "Assign all flags during initial package scan or update." into tm-qpr-dev
diff --git a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
index 0de0a1c..e8bcfd2 100644
--- a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
@@ -112,6 +112,7 @@
 import android.text.format.DateFormat;
 import android.util.ArrayMap;
 import android.util.ArraySet;
+import android.util.EventLog;
 import android.util.IndentingPrintWriter;
 import android.util.Log;
 import android.util.LongArrayQueue;
@@ -334,12 +335,18 @@
             "REORDER_ALARMS_FOR_TARE",
     });
 
-    BroadcastOptions mOptsWithFgs = BroadcastOptions.makeBasic();
-    BroadcastOptions mOptsWithFgsForAlarmClock = BroadcastOptions.makeBasic();
-    BroadcastOptions mOptsWithoutFgs = BroadcastOptions.makeBasic();
-    BroadcastOptions mOptsTimeBroadcast = BroadcastOptions.makeBasic();
+    BroadcastOptions mOptsWithFgs = makeBasicAlarmBroadcastOptions();
+    BroadcastOptions mOptsWithFgsForAlarmClock = makeBasicAlarmBroadcastOptions();
+    BroadcastOptions mOptsWithoutFgs = makeBasicAlarmBroadcastOptions();
+    BroadcastOptions mOptsTimeBroadcast = makeBasicAlarmBroadcastOptions();
     ActivityOptions mActivityOptsRestrictBal = ActivityOptions.makeBasic();
-    BroadcastOptions mBroadcastOptsRestrictBal = BroadcastOptions.makeBasic();
+    BroadcastOptions mBroadcastOptsRestrictBal = makeBasicAlarmBroadcastOptions();
+
+    private static BroadcastOptions makeBasicAlarmBroadcastOptions() {
+        final BroadcastOptions b = BroadcastOptions.makeBasic();
+        b.setAlarmBroadcast(true);
+        return b;
+    }
 
     // TODO(b/172085676): Move inside alarm store.
     private final SparseArray<AlarmManager.AlarmClockInfo> mNextAlarmClockForUser =
@@ -2299,7 +2306,11 @@
                                 + " reached for uid: " + UserHandle.formatUid(callingUid)
                                 + ", callingPackage: " + callingPackage;
                 Slog.w(TAG, errorMsg);
-                throw new IllegalStateException(errorMsg);
+                if (callingUid != Process.SYSTEM_UID) {
+                    throw new IllegalStateException(errorMsg);
+                } else {
+                    EventLog.writeEvent(0x534e4554, "234441463", -1, errorMsg);
+                }
             }
             setImplLocked(type, triggerAtTime, triggerElapsed, windowLength, interval, operation,
                     directReceiver, listenerTag, flags, workSource, alarmClock, callingUid,
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index ec4ad8b..0126199 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -5956,7 +5956,7 @@
     method public int getEphemerisSource();
     method @FloatRange public double getIonoDelayMeters();
     method @IntRange(from=0, to=1023) public int getIssueOfDataClock();
-    method @IntRange(from=0, to=255) public int getIssueOfDataEphemeris();
+    method @IntRange(from=0, to=1023) public int getIssueOfDataEphemeris();
     method @Nullable public android.location.SatellitePvt.PositionEcef getPositionEcef();
     method @IntRange(from=0) public long getTimeOfClockSeconds();
     method @IntRange(from=0) public long getTimeOfEphemerisSeconds();
@@ -5984,7 +5984,7 @@
     method @NonNull public android.location.SatellitePvt.Builder setEphemerisSource(int);
     method @NonNull public android.location.SatellitePvt.Builder setIonoDelayMeters(@FloatRange(from=0.0f, to=100.0f) double);
     method @NonNull public android.location.SatellitePvt.Builder setIssueOfDataClock(@IntRange(from=0, to=1023) int);
-    method @NonNull public android.location.SatellitePvt.Builder setIssueOfDataEphemeris(@IntRange(from=0, to=255) int);
+    method @NonNull public android.location.SatellitePvt.Builder setIssueOfDataEphemeris(@IntRange(from=0, to=1023) int);
     method @NonNull public android.location.SatellitePvt.Builder setPositionEcef(@NonNull android.location.SatellitePvt.PositionEcef);
     method @NonNull public android.location.SatellitePvt.Builder setTimeOfClockSeconds(@IntRange(from=0) long);
     method @NonNull public android.location.SatellitePvt.Builder setTimeOfEphemerisSeconds(@IntRange(from=0) long);
diff --git a/core/java/android/app/BroadcastOptions.java b/core/java/android/app/BroadcastOptions.java
index 56f8760..f0e1448 100644
--- a/core/java/android/app/BroadcastOptions.java
+++ b/core/java/android/app/BroadcastOptions.java
@@ -53,6 +53,7 @@
     private String[] mRequireNoneOfPermissions;
     private long mRequireCompatChangeId = CHANGE_INVALID;
     private boolean mRequireCompatChangeEnabled = true;
+    private boolean mIsAlarmBroadcast = false;
     private long mIdForResponseEvent;
 
     /**
@@ -149,6 +150,13 @@
             "android:broadcast.requireCompatChangeEnabled";
 
     /**
+     * Corresponds to {@link #setAlarmBroadcast(boolean)}
+     * @hide
+     */
+    public static final String KEY_ALARM_BROADCAST =
+            "android:broadcast.is_alarm";
+
+    /**
      * @hide
      * @deprecated Use {@link android.os.PowerExemptionManager#
      * TEMPORARY_ALLOW_LIST_TYPE_FOREGROUND_SERVICE_ALLOWED} instead.
@@ -207,6 +215,7 @@
         mRequireCompatChangeId = opts.getLong(KEY_REQUIRE_COMPAT_CHANGE_ID, CHANGE_INVALID);
         mRequireCompatChangeEnabled = opts.getBoolean(KEY_REQUIRE_COMPAT_CHANGE_ENABLED, true);
         mIdForResponseEvent = opts.getLong(KEY_ID_FOR_RESPONSE_EVENT);
+        mIsAlarmBroadcast = opts.getBoolean(KEY_ALARM_BROADCAST, false);
     }
 
     /**
@@ -498,6 +507,27 @@
         mRequireCompatChangeEnabled = true;
     }
 
+    /**
+     * When set, this broadcast will be understood as having originated from an
+     * alarm going off.  Only the OS itself can use this option; uses by other
+     * senders will be ignored.
+     * @hide
+     *
+     * @param senderIsAlarm Whether the broadcast is alarm-triggered.
+     */
+    public void setAlarmBroadcast(boolean senderIsAlarm) {
+        mIsAlarmBroadcast = senderIsAlarm;
+    }
+
+    /**
+     * Did this broadcast originate from an alarm triggering?
+     * @return true if this broadcast is an alarm message, false otherwise
+     * @hide
+     */
+    public boolean isAlarmBroadcast() {
+        return mIsAlarmBroadcast;
+    }
+
     /** {@hide} */
     public long getRequireCompatChangeId() {
         return mRequireCompatChangeId;
@@ -560,6 +590,9 @@
             b.putInt(KEY_TEMPORARY_APP_ALLOWLIST_REASON_CODE, mTemporaryAppAllowlistReasonCode);
             b.putString(KEY_TEMPORARY_APP_ALLOWLIST_REASON, mTemporaryAppAllowlistReason);
         }
+        if (mIsAlarmBroadcast) {
+            b.putBoolean(KEY_ALARM_BROADCAST, true);
+        }
         if (mMinManifestReceiverApiLevel != 0) {
             b.putInt(KEY_MIN_MANIFEST_RECEIVER_API_LEVEL, mMinManifestReceiverApiLevel);
         }
diff --git a/core/java/android/app/Instrumentation.java b/core/java/android/app/Instrumentation.java
index 8984c42..556058b 100644
--- a/core/java/android/app/Instrumentation.java
+++ b/core/java/android/app/Instrumentation.java
@@ -783,6 +783,17 @@
             return null;
         }
 
+        /**
+         * This is called after starting an Activity and provides the result code that defined in
+         * {@link ActivityManager}, like {@link ActivityManager#START_SUCCESS}.
+         *
+         * @param result the result code that returns after starting an Activity.
+         * @param bOptions the bundle generated from {@link ActivityOptions} that originally
+         *                 being used to start the Activity.
+         * @hide
+         */
+        public void onStartActivityResult(int result, @NonNull Bundle bOptions) {}
+
         final boolean match(Context who,
                             Activity activity,
                             Intent intent) {
@@ -1344,6 +1355,28 @@
         return apk.getAppFactory();
     }
 
+    /**
+     * This should be called before {@link #checkStartActivityResult(int, Object)}, because
+     * exceptions might be thrown while checking the results.
+     */
+    private void notifyStartActivityResult(int result, @Nullable Bundle options) {
+        if (mActivityMonitors == null) {
+            return;
+        }
+        synchronized (mSync) {
+            final int size = mActivityMonitors.size();
+            for (int i = 0; i < size; i++) {
+                final ActivityMonitor am = mActivityMonitors.get(i);
+                if (am.ignoreMatchingSpecificIntents()) {
+                    if (options == null) {
+                        options = ActivityOptions.makeBasic().toBundle();
+                    }
+                    am.onStartActivityResult(result, options);
+                }
+            }
+        }
+    }
+
     private void prePerformCreate(Activity activity) {
         if (mWaitingActivities != null) {
             synchronized (mSync) {
@@ -1802,6 +1835,7 @@
                     who.getOpPackageName(), who.getAttributionTag(), intent,
                     intent.resolveTypeIfNeeded(who.getContentResolver()), token,
                     target != null ? target.mEmbeddedID : null, requestCode, 0, null, options);
+            notifyStartActivityResult(result, options);
             checkStartActivityResult(result, intent);
         } catch (RemoteException e) {
             throw new RuntimeException("Failure from system", e);
@@ -1876,6 +1910,7 @@
             int result = ActivityTaskManager.getService().startActivities(whoThread,
                     who.getOpPackageName(), who.getAttributionTag(), intents, resolvedTypes,
                     token, options, userId);
+            notifyStartActivityResult(result, options);
             checkStartActivityResult(result, intents[0]);
             return result;
         } catch (RemoteException e) {
@@ -1947,6 +1982,7 @@
                     who.getOpPackageName(), who.getAttributionTag(), intent,
                     intent.resolveTypeIfNeeded(who.getContentResolver()), token, target,
                     requestCode, 0, null, options);
+            notifyStartActivityResult(result, options);
             checkStartActivityResult(result, intent);
         } catch (RemoteException e) {
             throw new RuntimeException("Failure from system", e);
@@ -2017,6 +2053,7 @@
                     who.getOpPackageName(), who.getAttributionTag(), intent,
                     intent.resolveTypeIfNeeded(who.getContentResolver()), token, resultWho,
                     requestCode, 0, null, options, user.getIdentifier());
+            notifyStartActivityResult(result, options);
             checkStartActivityResult(result, intent);
         } catch (RemoteException e) {
             throw new RuntimeException("Failure from system", e);
@@ -2068,6 +2105,7 @@
                             token, target != null ? target.mEmbeddedID : null,
                             requestCode, 0, null, options,
                             ignoreTargetSecurity, userId);
+            notifyStartActivityResult(result, options);
             checkStartActivityResult(result, intent);
         } catch (RemoteException e) {
             throw new RuntimeException("Failure from system", e);
@@ -2115,6 +2153,7 @@
             int result = appTask.startActivity(whoThread.asBinder(), who.getOpPackageName(),
                     who.getAttributionTag(), intent,
                     intent.resolveTypeIfNeeded(who.getContentResolver()), options);
+            notifyStartActivityResult(result, options);
             checkStartActivityResult(result, intent);
         } catch (RemoteException e) {
             throw new RuntimeException("Failure from system", e);
diff --git a/core/java/android/hardware/camera2/CameraCharacteristics.java b/core/java/android/hardware/camera2/CameraCharacteristics.java
index dac55ae..8db298f 100644
--- a/core/java/android/hardware/camera2/CameraCharacteristics.java
+++ b/core/java/android/hardware/camera2/CameraCharacteristics.java
@@ -1325,7 +1325,8 @@
      * flashlight brightness level via
      * {@link android.hardware.camera2.CameraManager#turnOnTorchWithStrengthLevel }.
      * If this value is equal to 1, flashlight brightness control is not supported.
-     * The value for this key will be null for devices with no flash unit.</p>
+     * The value for this key will be null for devices with no flash unit.
+     * This level must be set to a safe value to prevent any burn out issues.</p>
      * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p>
      */
     @PublicKey
diff --git a/core/java/android/hardware/camera2/marshal/impl/MarshalQueryableArray.java b/core/java/android/hardware/camera2/marshal/impl/MarshalQueryableArray.java
index 6cf5d60..cbd8066 100644
--- a/core/java/android/hardware/camera2/marshal/impl/MarshalQueryableArray.java
+++ b/core/java/android/hardware/camera2/marshal/impl/MarshalQueryableArray.java
@@ -45,46 +45,41 @@
     private static final boolean DEBUG = false;
 
     private static interface PrimitiveArrayFiller {
-        public void fillPosition(Object arr, int index, ByteBuffer buffer);
+        public void fillArray(Object arr, int size, ByteBuffer buffer);
         static PrimitiveArrayFiller getPrimitiveArrayFiller(Class<?> componentType) {
             if (componentType == int.class) {
                 return new PrimitiveArrayFiller() {
                       @Override
-                      public void fillPosition(Object arr, int index, ByteBuffer buffer) {
-                          int i = buffer.getInt();
-                          Array.setInt(arr, index, i);
+                      public void fillArray(Object arr, int size, ByteBuffer buffer) {
+                          buffer.asIntBuffer().get(int[].class.cast(arr), 0, size);
                       }
                 };
             } else if (componentType == float.class) {
                 return new PrimitiveArrayFiller() {
                       @Override
-                      public void fillPosition(Object arr, int index, ByteBuffer buffer) {
-                          float i = buffer.getFloat();
-                          Array.setFloat(arr, index, i);
+                      public void fillArray(Object arr, int size, ByteBuffer buffer) {
+                          buffer.asFloatBuffer().get(float[].class.cast(arr), 0, size);
                       }
                 };
             } else if (componentType == long.class) {
                 return new PrimitiveArrayFiller() {
                       @Override
-                      public void fillPosition(Object arr, int index, ByteBuffer buffer) {
-                          long i = buffer.getLong();
-                          Array.setLong(arr, index, i);
+                      public void fillArray(Object arr, int size, ByteBuffer buffer) {
+                          buffer.asLongBuffer().get(long[].class.cast(arr), 0, size);
                       }
                 };
             } else if (componentType == double.class) {
                 return new PrimitiveArrayFiller() {
                       @Override
-                      public void fillPosition(Object arr, int index, ByteBuffer buffer) {
-                          double i = buffer.getDouble();
-                          Array.setDouble(arr, index, i);
+                      public void fillArray(Object arr, int size, ByteBuffer buffer) {
+                          buffer.asDoubleBuffer().get(double[].class.cast(arr), 0, size);
                       }
                 };
             } else if (componentType == byte.class) {
                 return new PrimitiveArrayFiller() {
                       @Override
-                      public void fillPosition(Object arr, int index, ByteBuffer buffer) {
-                          byte i = buffer.get();
-                          Array.setByte(arr, index, i);
+                      public void fillArray(Object arr, int size, ByteBuffer buffer) {
+                          buffer.get(byte[].class.cast(arr), 0, size);
                       }
                 };
             }
@@ -93,13 +88,6 @@
         }
     };
 
-    static void unmarshalPrimitiveArray(Object arr, int size, ByteBuffer buffer,
-            PrimitiveArrayFiller filler) {
-        for (int i = 0; i < size; i++) {
-            filler.fillPosition(arr, i, buffer);
-        }
-    }
-
     private class MarshalerArray extends Marshaler<T> {
         private final Class<T> mClass;
         private final Marshaler<?> mComponentMarshaler;
@@ -150,8 +138,8 @@
                 array = Array.newInstance(mComponentClass, arraySize);
                 if (isUnwrappedPrimitiveClass(mComponentClass) &&
                         mComponentClass == getPrimitiveTypeClass(mNativeType)) {
-                    unmarshalPrimitiveArray(array, arraySize, buffer,
-                            PrimitiveArrayFiller.getPrimitiveArrayFiller(mComponentClass));
+                    PrimitiveArrayFiller.getPrimitiveArrayFiller(mComponentClass).fillArray(array,
+                            arraySize, buffer);
                 } else {
                     for (int i = 0; i < arraySize; ++i) {
                         Object elem = mComponentMarshaler.unmarshal(buffer);
diff --git a/core/java/android/view/IDisplayWindowInsetsController.aidl b/core/java/android/view/IDisplayWindowInsetsController.aidl
index f4a0dfa..1940042 100644
--- a/core/java/android/view/IDisplayWindowInsetsController.aidl
+++ b/core/java/android/view/IDisplayWindowInsetsController.aidl
@@ -16,6 +16,7 @@
 
 package android.view;
 
+import android.content.ComponentName;
 import android.view.InsetsSourceControl;
 import android.view.InsetsState;
 import android.view.InsetsVisibilities;
@@ -30,10 +31,11 @@
     /**
      * Called when top focused window changes to determine whether or not to take over insets
      * control. Won't be called if config_remoteInsetsControllerControlsSystemBars is false.
-     * @param packageName: Passes the top package name
+     * @param component: Passes the top application component in the focused window.
      * @param requestedVisibilities The insets visibilities requested by the focussed window.
      */
-    void topFocusedWindowChanged(String packageName, in InsetsVisibilities insetsVisibilities);
+    void topFocusedWindowChanged(in ComponentName component,
+            in InsetsVisibilities insetsVisibilities);
 
     /**
      * @see IWindow#insetsChanged
diff --git a/core/java/android/view/IRecentsAnimationController.aidl b/core/java/android/view/IRecentsAnimationController.aidl
index 61f524f..c4d3070 100644
--- a/core/java/android/view/IRecentsAnimationController.aidl
+++ b/core/java/android/view/IRecentsAnimationController.aidl
@@ -80,11 +80,6 @@
     void setAnimationTargetsBehindSystemBars(boolean behindSystemBars);
 
     /**
-     * Hides the current input method if one is showing.
-     */
-    void hideCurrentInputMethod();
-
-    /**
      * Clean up the screenshot of previous task which was created during recents animation that
      * was cancelled by a stack order change.
      *
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 8ec32a6..7be8dff 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -3759,6 +3759,7 @@
             }
         }
         mFirstInputStage.onWindowFocusChanged(hasWindowFocus);
+        mOnBackInvokedDispatcher.onWindowFocusChanged(hasWindowFocus);
 
         // NOTE: there's no view visibility (appeared / disapparead) events when the windows focus
         // is lost, so we don't need to to force a flush - there might be other events such as
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index 23393ff..2268bef 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -6440,9 +6440,8 @@
     public void setText(CharSequence text, BufferType type) {
         setText(text, type, true, 0);
 
-        if (mCharWrapper != null) {
-            mCharWrapper.mChars = null;
-        }
+        // drop any potential mCharWrappper leaks
+        mCharWrapper = null;
     }
 
     @UnsupportedAppUsage
@@ -6653,11 +6652,14 @@
      * since the TextView has no way to know that the text
      * has changed and that it needs to invalidate and re-layout.
      *
+     * @throws NullPointerException if text is null
+     * @throws IndexOutOfBoundsException if start or start+len are not in 0 to text.length
+     *
      * @param text char array to be displayed
      * @param start start index in the char array
      * @param len length of char count after {@code start}
      */
-    public final void setText(char[] text, int start, int len) {
+    public final void setText(/* @NonNull */ char[] text, int start, int len) {
         int oldlen = 0;
 
         if (start < 0 || len < 0 || start + len > text.length) {
@@ -13888,16 +13890,17 @@
     }
 
     private static class CharWrapper implements CharSequence, GetChars, GraphicsOperations {
+        @NonNull
         private char[] mChars;
         private int mStart, mLength;
 
-        public CharWrapper(char[] chars, int start, int len) {
+        CharWrapper(@NonNull char[] chars, int start, int len) {
             mChars = chars;
             mStart = start;
             mLength = len;
         }
 
-        /* package */ void set(char[] chars, int start, int len) {
+        /* package */ void set(@NonNull char[] chars, int start, int len) {
             mChars = chars;
             mStart = start;
             mLength = len;
diff --git a/core/java/android/window/ImeOnBackInvokedDispatcher.java b/core/java/android/window/ImeOnBackInvokedDispatcher.java
index 5924844..f1a052b 100644
--- a/core/java/android/window/ImeOnBackInvokedDispatcher.java
+++ b/core/java/android/window/ImeOnBackInvokedDispatcher.java
@@ -81,8 +81,10 @@
             @OnBackInvokedDispatcher.Priority int priority,
             @NonNull OnBackInvokedCallback callback) {
         final Bundle bundle = new Bundle();
+        // Always invoke back for ime without checking the window focus.
         final IOnBackInvokedCallback iCallback =
-                new WindowOnBackInvokedDispatcher.OnBackInvokedCallbackWrapper(callback);
+                new WindowOnBackInvokedDispatcher.OnBackInvokedCallbackWrapper(callback,
+                        () -> true);
         bundle.putBinder(RESULT_KEY_CALLBACK, iCallback.asBinder());
         bundle.putInt(RESULT_KEY_PRIORITY, priority);
         bundle.putInt(RESULT_KEY_ID, callback.hashCode());
diff --git a/core/java/android/window/WindowOnBackInvokedDispatcher.java b/core/java/android/window/WindowOnBackInvokedDispatcher.java
index d147524d..02c5ebc 100644
--- a/core/java/android/window/WindowOnBackInvokedDispatcher.java
+++ b/core/java/android/window/WindowOnBackInvokedDispatcher.java
@@ -32,6 +32,7 @@
 import java.util.HashMap;
 import java.util.Objects;
 import java.util.TreeMap;
+import java.util.function.Supplier;
 
 /**
  * Provides window based implementation of {@link OnBackInvokedDispatcher}.
@@ -64,6 +65,7 @@
     private final TreeMap<Integer, ArrayList<OnBackInvokedCallback>>
             mOnBackInvokedCallbacks = new TreeMap<>();
     private final Checker mChecker;
+    private boolean mHasFocus;
 
     public WindowOnBackInvokedDispatcher(boolean applicationCallBackEnabled) {
         mChecker = new Checker(applicationCallBackEnabled);
@@ -189,7 +191,7 @@
                                     .ImeOnBackInvokedCallback
                                 ? ((ImeOnBackInvokedDispatcher.ImeOnBackInvokedCallback)
                                         callback).getIOnBackInvokedCallback()
-                                : new OnBackInvokedCallbackWrapper(callback);
+                                : new OnBackInvokedCallbackWrapper(callback, this::hasFocus);
                 callbackInfo = new OnBackInvokedCallbackInfo(iCallback, priority);
             }
             mWindowSession.setOnBackInvokedCallbackInfo(mWindow, callbackInfo);
@@ -198,6 +200,17 @@
         }
     }
 
+    /**
+     * Called when window focus changed.
+     */
+    public void onWindowFocusChanged(boolean hasFocus) {
+        mHasFocus = hasFocus;
+    }
+
+    private boolean hasFocus() {
+        return mHasFocus;
+    }
+
     public OnBackInvokedCallback getTopCallback() {
         if (mAllCallbacks.isEmpty()) {
             return null;
@@ -221,9 +234,11 @@
 
     static class OnBackInvokedCallbackWrapper extends IOnBackInvokedCallback.Stub {
         private final WeakReference<OnBackInvokedCallback> mCallback;
-
-        OnBackInvokedCallbackWrapper(@NonNull OnBackInvokedCallback callback) {
+        private final Supplier<Boolean> mHasFocus;
+        OnBackInvokedCallbackWrapper(@NonNull OnBackInvokedCallback callback,
+                @NonNull Supplier<Boolean> hasFocus) {
             mCallback = new WeakReference<>(callback);
+            mHasFocus = hasFocus;
         }
 
         @Override
@@ -263,7 +278,10 @@
                 if (callback == null) {
                     return;
                 }
-
+                if (!mHasFocus.get()) {
+                    Log.w(TAG, "Skip back invoke due to current focus has lost.");
+                    return;
+                }
                 callback.onBackInvoked();
             });
         }
diff --git a/core/tests/coretests/src/android/widget/TextViewTest.java b/core/tests/coretests/src/android/widget/TextViewTest.java
index 47ce2d8..cc4fbab 100644
--- a/core/tests/coretests/src/android/widget/TextViewTest.java
+++ b/core/tests/coretests/src/android/widget/TextViewTest.java
@@ -304,6 +304,23 @@
         assertFalse(mTextView.isCursorVisible());
     }
 
+    @Test(expected = NullPointerException.class)
+    @UiThreadTest
+    public void setTextCharArrayNullThrows() {
+        mTextView = new TextView(mActivity);
+        mTextView.setText((char[]) null, 0, 0);
+    }
+
+    @Test
+    @UiThreadTest
+    public void setTextCharArrayValidAfterSetTextString() {
+        mTextView = new TextView(mActivity);
+        mTextView.setText(new char[] { 'h', 'i'}, 0, 2);
+        CharSequence charWrapper = mTextView.getText();
+        mTextView.setText("null out char wrapper");
+        assertEquals("hi", charWrapper.toString());
+    }
+
     private String createLongText() {
         int size = 600 * 1000;
         final StringBuilder builder = new StringBuilder(size);
diff --git a/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java b/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java
index f448cb3..b5194f6 100644
--- a/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java
+++ b/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java
@@ -66,6 +66,7 @@
         MockitoAnnotations.initMocks(this);
         mDispatcher = new WindowOnBackInvokedDispatcher(true /* applicationCallbackEnabled */);
         mDispatcher.attachToWindow(mWindowSession, mWindow);
+        mDispatcher.onWindowFocusChanged(true);
     }
 
     private void waitForIdle() {
@@ -152,4 +153,31 @@
         waitForIdle();
         verify(mCallback2).onBackStarted();
     }
+
+    @Test
+    public void skipBackInvokeWhenNoFocus() throws RemoteException {
+        ArgumentCaptor<OnBackInvokedCallbackInfo> captor =
+                ArgumentCaptor.forClass(OnBackInvokedCallbackInfo.class);
+
+        mDispatcher.registerOnBackInvokedCallback(
+                OnBackInvokedDispatcher.PRIORITY_DEFAULT, mCallback1);
+
+        verify(mWindowSession, times(1)).setOnBackInvokedCallbackInfo(
+                Mockito.eq(mWindow),
+                captor.capture());
+
+        verify(mWindowSession).setOnBackInvokedCallbackInfo(Mockito.eq(mWindow), captor.capture());
+
+        // Should invoke back if it's still in focused.
+        captor.getValue().getCallback().onBackInvoked();
+        waitForIdle();
+        verify(mCallback1).onBackInvoked();
+
+        // In case the focus has lost during back gesture.
+        mDispatcher.onWindowFocusChanged(false);
+
+        captor.getValue().getCallback().onBackInvoked();
+        waitForIdle();
+        verifyZeroInteractions(mCallback1);
+    }
 }
diff --git a/data/fonts/fonts.xml b/data/fonts/fonts.xml
index 84e949a..f8c015f 100644
--- a/data/fonts/fonts.xml
+++ b/data/fonts/fonts.xml
@@ -932,7 +932,7 @@
         <font weight="700" style="normal" fallbackFor="serif">NotoSerifLao-Bold.ttf</font>
     </family>
     <family lang="und-Laoo" variant="compact">
-        <font weight="400" style="normal">NotoSansLaoUI-Regular.ttf
+        <font weight="400" style="normal" postScriptName="NotoSansLaoUI">NotoSansLaoUI-Regular.ttf
         </font>
         <font weight="700" style="normal">NotoSansLaoUI-Bold.ttf</font>
     </family>
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
index 2f79cae..da9fd0c2 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
@@ -16,6 +16,7 @@
 
 package androidx.window.extensions.embedding;
 
+import static android.app.ActivityManager.START_SUCCESS;
 import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
 import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
 
@@ -97,6 +98,7 @@
     private final List<SplitInfo> mLastReportedSplitStates = new ArrayList<>();
     private final Handler mHandler;
     private final Object mLock = new Object();
+    private final ActivityStartMonitor mActivityStartMonitor;
 
     public SplitController() {
         final MainThreadExecutor executor = new MainThreadExecutor();
@@ -108,7 +110,8 @@
                 new LifecycleCallbacks());
         // Intercept activity starts to route activities to new containers if necessary.
         Instrumentation instrumentation = activityThread.getInstrumentation();
-        instrumentation.addMonitor(new ActivityStartMonitor());
+        mActivityStartMonitor = new ActivityStartMonitor();
+        instrumentation.addMonitor(mActivityStartMonitor);
     }
 
     /** Updates the embedding rules applied to future activity launches. */
@@ -1385,6 +1388,11 @@
         return ActivityThread.currentActivityThread().getActivity(activityToken);
     }
 
+    @VisibleForTesting
+    ActivityStartMonitor getActivityStartMonitor() {
+        return mActivityStartMonitor;
+    }
+
     /**
      * Gets the token of the initial TaskFragment that embedded this activity. Do not rely on it
      * after creation because the activity could be reparented.
@@ -1536,7 +1544,10 @@
      * A monitor that intercepts all activity start requests originating in the client process and
      * can amend them to target a specific task fragment to form a split.
      */
-    private class ActivityStartMonitor extends Instrumentation.ActivityMonitor {
+    @VisibleForTesting
+    class ActivityStartMonitor extends Instrumentation.ActivityMonitor {
+        @VisibleForTesting
+        Intent mCurrentIntent;
 
         @Override
         public Instrumentation.ActivityResult onStartActivity(@NonNull Context who,
@@ -1564,11 +1575,29 @@
                     // the dedicated container.
                     options.putBinder(ActivityOptions.KEY_LAUNCH_TASK_FRAGMENT_TOKEN,
                             launchedInTaskFragment.getTaskFragmentToken());
+                    mCurrentIntent = intent;
                 }
             }
 
             return super.onStartActivity(who, intent, options);
         }
+
+        @Override
+        public void onStartActivityResult(int result, @NonNull Bundle bOptions) {
+            super.onStartActivityResult(result, bOptions);
+            if (mCurrentIntent != null && result != START_SUCCESS) {
+                // Clear the pending appeared intent if the activity was not started successfully.
+                final IBinder token = bOptions.getBinder(
+                        ActivityOptions.KEY_LAUNCH_TASK_FRAGMENT_TOKEN);
+                if (token != null) {
+                    final TaskFragmentContainer container = getContainer(token);
+                    if (container != null) {
+                        container.clearPendingAppearedIntentIfNeeded(mCurrentIntent);
+                    }
+                }
+            }
+            mCurrentIntent = null;
+        }
     }
 
     /**
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java
index abf32a2..a188e2b 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java
@@ -198,6 +198,22 @@
         return mPendingAppearedIntent;
     }
 
+    void setPendingAppearedIntent(@Nullable Intent intent) {
+        mPendingAppearedIntent = intent;
+    }
+
+    /**
+     * Clears the pending appeared Intent if it is the same as given Intent. Otherwise, the
+     * pending appeared Intent is cleared when TaskFragmentInfo is set and is not empty (has
+     * running activities).
+     */
+    void clearPendingAppearedIntentIfNeeded(@NonNull Intent intent) {
+        if (mPendingAppearedIntent == null || mPendingAppearedIntent != intent) {
+            return;
+        }
+        mPendingAppearedIntent = null;
+    }
+
     boolean hasActivity(@NonNull IBinder token) {
         if (mInfo != null && mInfo.getActivities().contains(token)) {
             return true;
@@ -230,13 +246,18 @@
 
     void setInfo(@NonNull TaskFragmentInfo info) {
         if (!mIsFinished && mInfo == null && info.isEmpty()) {
-            // onTaskFragmentAppeared with empty info. We will remove the TaskFragment if it is
-            // still empty after timeout.
+            // onTaskFragmentAppeared with empty info. We will remove the TaskFragment if no
+            // pending appeared intent/activities. Otherwise, wait and removing the TaskFragment if
+            // it is still empty after timeout.
             mAppearEmptyTimeout = () -> {
                 mAppearEmptyTimeout = null;
                 mController.onTaskFragmentAppearEmptyTimeout(this);
             };
-            mController.getHandler().postDelayed(mAppearEmptyTimeout, APPEAR_EMPTY_TIMEOUT_MS);
+            if (mPendingAppearedIntent != null || !mPendingAppearedActivities.isEmpty()) {
+                mController.getHandler().postDelayed(mAppearEmptyTimeout, APPEAR_EMPTY_TIMEOUT_MS);
+            } else {
+                mAppearEmptyTimeout.run();
+            }
         } else if (mAppearEmptyTimeout != null && !info.isEmpty()) {
             mController.getHandler().removeCallbacks(mAppearEmptyTimeout);
             mAppearEmptyTimeout = null;
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java
index 042547f..4bc5033 100644
--- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java
@@ -16,6 +16,7 @@
 
 package androidx.window.extensions.embedding;
 
+import static android.app.ActivityManager.START_CANCELED;
 import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
 import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
 
@@ -293,6 +294,26 @@
     }
 
     @Test
+    public void testOnStartActivityResultError() {
+        final Intent intent = new Intent();
+        final TaskContainer taskContainer = new TaskContainer(TASK_ID);
+        final TaskFragmentContainer container = new TaskFragmentContainer(null /* activity */,
+                intent, taskContainer, mSplitController);
+        final SplitController.ActivityStartMonitor monitor =
+                mSplitController.getActivityStartMonitor();
+
+        container.setPendingAppearedIntent(intent);
+        final Bundle bundle = new Bundle();
+        bundle.putBinder(ActivityOptions.KEY_LAUNCH_TASK_FRAGMENT_TOKEN,
+                container.getTaskFragmentToken());
+        monitor.mCurrentIntent = intent;
+        doReturn(container).when(mSplitController).getContainer(any());
+
+        monitor.onStartActivityResult(START_CANCELED, bundle);
+        assertNull(container.getPendingAppearedIntent());
+    }
+
+    @Test
     public void testOnActivityCreated() {
         mSplitController.onActivityCreated(mActivity);
 
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskFragmentContainerTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskFragmentContainerTest.java
index 28c2773..44c7e6c 100644
--- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskFragmentContainerTest.java
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskFragmentContainerTest.java
@@ -209,21 +209,21 @@
 
         assertNull(container.mAppearEmptyTimeout);
 
-        // Not set if it is not appeared empty.
-        final TaskFragmentInfo info = mock(TaskFragmentInfo.class);
-        doReturn(new ArrayList<>()).when(info).getActivities();
-        doReturn(false).when(info).isEmpty();
-        container.setInfo(info);
-
-        assertNull(container.mAppearEmptyTimeout);
-
         // Set timeout if the first info set is empty.
+        final TaskFragmentInfo info = mock(TaskFragmentInfo.class);
         container.mInfo = null;
         doReturn(true).when(info).isEmpty();
         container.setInfo(info);
 
         assertNotNull(container.mAppearEmptyTimeout);
 
+        // Not set if it is not appeared empty.
+        doReturn(new ArrayList<>()).when(info).getActivities();
+        doReturn(false).when(info).isEmpty();
+        container.setInfo(info);
+
+        assertNull(container.mAppearEmptyTimeout);
+
         // Remove timeout after the container becomes non-empty.
         doReturn(false).when(info).isEmpty();
         container.setInfo(info);
@@ -232,6 +232,7 @@
 
         // Running the timeout will call into SplitController.onTaskFragmentAppearEmptyTimeout.
         container.mInfo = null;
+        container.setPendingAppearedIntent(mIntent);
         doReturn(true).when(info).isEmpty();
         container.setInfo(info);
         container.mAppearEmptyTimeout.run();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimation.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimation.java
index 8c0affb..86f9d5b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimation.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimation.java
@@ -29,6 +29,13 @@
 public interface BackAnimation {
 
     /**
+     * Returns a binder that can be passed to an external process to update back animations.
+     */
+    default IBackAnimation createExternalInterface() {
+        return null;
+    }
+
+    /**
      * Called when a {@link MotionEvent} is generated by a back gesture.
      *
      * @param touchX the X touch position of the {@link MotionEvent}.
@@ -47,13 +54,6 @@
     void setTriggerBack(boolean triggerBack);
 
     /**
-     * Returns a binder that can be passed to an external process to update back animations.
-     */
-    default IBackAnimation createExternalInterface() {
-        return null;
-    }
-
-    /**
      * Sets the threshold values that defining edge swipe behavior.
      * @param triggerThreshold the min threshold to trigger back.
      * @param progressThreshold the max threshold to keep progressing back animation.
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 6a2acf4..b3f6247 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
@@ -20,6 +20,7 @@
 import android.animation.AnimatorListenerAdapter;
 import android.animation.ValueAnimator;
 import android.annotation.IntDef;
+import android.content.ComponentName;
 import android.content.Context;
 import android.content.res.Configuration;
 import android.graphics.Point;
@@ -324,7 +325,7 @@
         }
 
         @Override
-        public void topFocusedWindowChanged(String packageName,
+        public void topFocusedWindowChanged(ComponentName component,
                 InsetsVisibilities requestedVisibilities) {
             // Do nothing
         }
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 b670544..f546f11 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
@@ -16,6 +16,7 @@
 
 package com.android.wm.shell.common;
 
+import android.content.ComponentName;
 import android.os.RemoteException;
 import android.util.Slog;
 import android.util.SparseArray;
@@ -171,14 +172,14 @@
             }
         }
 
-        private void topFocusedWindowChanged(String packageName,
+        private void topFocusedWindowChanged(ComponentName component,
                 InsetsVisibilities requestedVisibilities) {
             CopyOnWriteArrayList<OnInsetsChangedListener> listeners = mListeners.get(mDisplayId);
             if (listeners == null) {
                 return;
             }
             for (OnInsetsChangedListener listener : listeners) {
-                listener.topFocusedWindowChanged(packageName, requestedVisibilities);
+                listener.topFocusedWindowChanged(component, requestedVisibilities);
             }
         }
 
@@ -186,10 +187,10 @@
         private class DisplayWindowInsetsControllerImpl
                 extends IDisplayWindowInsetsController.Stub {
             @Override
-            public void topFocusedWindowChanged(String packageName,
+            public void topFocusedWindowChanged(ComponentName component,
                     InsetsVisibilities requestedVisibilities) throws RemoteException {
                 mMainExecutor.execute(() -> {
-                    PerDisplay.this.topFocusedWindowChanged(packageName, requestedVisibilities);
+                    PerDisplay.this.topFocusedWindowChanged(component, requestedVisibilities);
                 });
             }
 
@@ -234,10 +235,10 @@
         /**
          * Called when top focused window changes to determine whether or not to take over insets
          * control. Won't be called if config_remoteInsetsControllerControlsSystemBars is false.
-         * @param packageName The name of the package that is open in the top focussed window.
+         * @param component The application component that is open in the top focussed window.
          * @param requestedVisibilities The insets visibilities requested by the focussed window.
          */
-        default void topFocusedWindowChanged(String packageName,
+        default void topFocusedWindowChanged(ComponentName component,
                 InsetsVisibilities requestedVisibilities) {}
 
         /**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
index b69cbfa..40cf9a3 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
@@ -727,31 +727,23 @@
             ActivityManager.RunningTaskInfo taskInfo1, ActivityManager.RunningTaskInfo taskInfo2) {
         if (offsetX == 0 && offsetY == 0) {
             wct.setBounds(taskInfo1.token, mBounds1);
-            wct.setAppBounds(taskInfo1.token, null);
             wct.setScreenSizeDp(taskInfo1.token,
                     SCREEN_WIDTH_DP_UNDEFINED, SCREEN_HEIGHT_DP_UNDEFINED);
 
             wct.setBounds(taskInfo2.token, mBounds2);
-            wct.setAppBounds(taskInfo2.token, null);
             wct.setScreenSizeDp(taskInfo2.token,
                     SCREEN_WIDTH_DP_UNDEFINED, SCREEN_HEIGHT_DP_UNDEFINED);
         } else {
-            mTempRect.set(taskInfo1.configuration.windowConfiguration.getBounds());
+            getBounds1(mTempRect);
             mTempRect.offset(offsetX, offsetY);
             wct.setBounds(taskInfo1.token, mTempRect);
-            mTempRect.set(taskInfo1.configuration.windowConfiguration.getAppBounds());
-            mTempRect.offset(offsetX, offsetY);
-            wct.setAppBounds(taskInfo1.token, mTempRect);
             wct.setScreenSizeDp(taskInfo1.token,
                     taskInfo1.configuration.screenWidthDp,
                     taskInfo1.configuration.screenHeightDp);
 
-            mTempRect.set(taskInfo2.configuration.windowConfiguration.getBounds());
+            getBounds2(mTempRect);
             mTempRect.offset(offsetX, offsetY);
             wct.setBounds(taskInfo2.token, mTempRect);
-            mTempRect.set(taskInfo2.configuration.windowConfiguration.getAppBounds());
-            mTempRect.offset(offsetX, offsetY);
-            wct.setAppBounds(taskInfo2.token, mTempRect);
             wct.setScreenSizeDp(taskInfo2.token,
                     taskInfo2.configuration.screenWidthDp,
                     taskInfo2.configuration.screenHeightDp);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUI.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUI.java
deleted file mode 100644
index b87cf47..0000000
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUI.java
+++ /dev/null
@@ -1,35 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.wm.shell.compatui;
-
-import com.android.wm.shell.common.annotations.ExternalThread;
-
-/**
- * Interface to engage compat UI.
- */
-@ExternalThread
-public interface CompatUI {
-    /**
-     * Called when the keyguard showing state changes. Removes all compat UIs if the
-     * keyguard is now showing.
-     *
-     * <p>Note that if the keyguard is occluded it will also be considered showing.
-     *
-     * @param showing indicates if the keyguard is now showing.
-     */
-    void onKeyguardShowingChanged(boolean showing);
-}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java
index 99b32a6..db8d9d4 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java
@@ -39,9 +39,10 @@
 import com.android.wm.shell.common.DisplayLayout;
 import com.android.wm.shell.common.ShellExecutor;
 import com.android.wm.shell.common.SyncTransactionQueue;
-import com.android.wm.shell.common.annotations.ExternalThread;
 import com.android.wm.shell.compatui.CompatUIWindowManager.CompatUIHintsState;
 import com.android.wm.shell.compatui.letterboxedu.LetterboxEduWindowManager;
+import com.android.wm.shell.sysui.KeyguardChangeListener;
+import com.android.wm.shell.sysui.ShellController;
 import com.android.wm.shell.transition.Transitions;
 
 import java.lang.ref.WeakReference;
@@ -58,7 +59,7 @@
  * activities are in compatibility mode.
  */
 public class CompatUIController implements OnDisplaysChangedListener,
-        DisplayImeController.ImePositionProcessor {
+        DisplayImeController.ImePositionProcessor, KeyguardChangeListener {
 
     /** Callback for compat UI interaction. */
     public interface CompatUICallback {
@@ -100,13 +101,13 @@
     private final SparseArray<WeakReference<Context>> mDisplayContextCache = new SparseArray<>(0);
 
     private final Context mContext;
+    private final ShellController mShellController;
     private final DisplayController mDisplayController;
     private final DisplayInsetsController mDisplayInsetsController;
     private final DisplayImeController mImeController;
     private final SyncTransactionQueue mSyncQueue;
     private final ShellExecutor mMainExecutor;
     private final Lazy<Transitions> mTransitionsLazy;
-    private final CompatUIImpl mImpl = new CompatUIImpl();
 
     private CompatUICallback mCallback;
 
@@ -118,6 +119,7 @@
     private boolean mKeyguardShowing;
 
     public CompatUIController(Context context,
+            ShellController shellController,
             DisplayController displayController,
             DisplayInsetsController displayInsetsController,
             DisplayImeController imeController,
@@ -125,6 +127,7 @@
             ShellExecutor mainExecutor,
             Lazy<Transitions> transitionsLazy) {
         mContext = context;
+        mShellController = shellController;
         mDisplayController = displayController;
         mDisplayInsetsController = displayInsetsController;
         mImeController = imeController;
@@ -134,11 +137,7 @@
         mDisplayController.addDisplayWindowListener(this);
         mImeController.addPositionProcessor(this);
         mCompatUIHintsState = new CompatUIHintsState();
-    }
-
-    /** Returns implementation of {@link CompatUI}. */
-    public CompatUI asCompatUI() {
-        return mImpl;
+        shellController.addKeyguardChangeListener(this);
     }
 
     /** Sets the callback for UI interactions. */
@@ -223,9 +222,10 @@
                 layout -> layout.updateVisibility(showOnDisplay(displayId)));
     }
 
-    @VisibleForTesting
-    void onKeyguardShowingChanged(boolean showing) {
-        mKeyguardShowing = showing;
+    @Override
+    public void onKeyguardVisibilityChanged(boolean visible, boolean occluded,
+            boolean animatingDismiss) {
+        mKeyguardShowing = visible;
         // Hide the compat UIs when keyguard is showing.
         forAllLayouts(layout -> layout.updateVisibility(showOnDisplay(layout.getDisplayId())));
     }
@@ -373,19 +373,6 @@
         }
     }
 
-    /**
-     * The interface for calls from outside the Shell, within the host process.
-     */
-    @ExternalThread
-    private class CompatUIImpl implements CompatUI {
-        @Override
-        public void onKeyguardShowingChanged(boolean showing) {
-            mMainExecutor.execute(() -> {
-                CompatUIController.this.onKeyguardShowingChanged(showing);
-            });
-        }
-    }
-
     /** An implementation of {@link OnInsetsChangedListener} for a given display id. */
     private class PerDisplayOnInsetsChangedListener implements OnInsetsChangedListener {
         final int mDisplayId;
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 ed5e247..586eab0 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
@@ -57,15 +57,12 @@
 import com.android.wm.shell.common.annotations.ShellBackgroundThread;
 import com.android.wm.shell.common.annotations.ShellMainThread;
 import com.android.wm.shell.common.annotations.ShellSplashscreenThread;
-import com.android.wm.shell.compatui.CompatUI;
 import com.android.wm.shell.compatui.CompatUIController;
 import com.android.wm.shell.displayareahelper.DisplayAreaHelper;
 import com.android.wm.shell.displayareahelper.DisplayAreaHelperController;
-import com.android.wm.shell.draganddrop.DragAndDrop;
 import com.android.wm.shell.draganddrop.DragAndDropController;
 import com.android.wm.shell.freeform.FreeformTaskListener;
 import com.android.wm.shell.fullscreen.FullscreenTaskListener;
-import com.android.wm.shell.hidedisplaycutout.HideDisplayCutout;
 import com.android.wm.shell.hidedisplaycutout.HideDisplayCutoutController;
 import com.android.wm.shell.kidsmode.KidsModeTaskOrganizer;
 import com.android.wm.shell.onehanded.OneHanded;
@@ -85,8 +82,6 @@
 import com.android.wm.shell.startingsurface.phone.PhoneStartingWindowTypeAlgorithm;
 import com.android.wm.shell.sysui.ShellController;
 import com.android.wm.shell.sysui.ShellInterface;
-import com.android.wm.shell.tasksurfacehelper.TaskSurfaceHelper;
-import com.android.wm.shell.tasksurfacehelper.TaskSurfaceHelperController;
 import com.android.wm.shell.transition.ShellTransitions;
 import com.android.wm.shell.transition.Transitions;
 import com.android.wm.shell.unfold.ShellUnfoldProgressProvider;
@@ -173,12 +168,6 @@
 
     @WMSingleton
     @Provides
-    static Optional<DragAndDrop> provideDragAndDrop(DragAndDropController dragAndDropController) {
-        return Optional.of(dragAndDropController.asDragAndDrop());
-    }
-
-    @WMSingleton
-    @Provides
     static ShellTaskOrganizer provideShellTaskOrganizer(@ShellMainThread ShellExecutor mainExecutor,
             Context context,
             CompatUIController compatUI,
@@ -207,18 +196,14 @@
     }
 
     @WMSingleton
-    @Provides static Optional<CompatUI> provideCompatUI(CompatUIController compatUIController) {
-        return Optional.of(compatUIController.asCompatUI());
-    }
-
-    @WMSingleton
     @Provides
     static CompatUIController provideCompatUIController(Context context,
+            ShellController shellController,
             DisplayController displayController, DisplayInsetsController displayInsetsController,
             DisplayImeController imeController, SyncTransactionQueue syncQueue,
             @ShellMainThread ShellExecutor mainExecutor, Lazy<Transitions> transitionsLazy) {
-        return new CompatUIController(context, displayController, displayInsetsController,
-                imeController, syncQueue, mainExecutor, transitionsLazy);
+        return new CompatUIController(context, shellController, displayController,
+                displayInsetsController, imeController, syncQueue, mainExecutor, transitionsLazy);
     }
 
     @WMSingleton
@@ -375,13 +360,6 @@
 
     @WMSingleton
     @Provides
-    static Optional<HideDisplayCutout> provideHideDisplayCutout(
-            Optional<HideDisplayCutoutController> hideDisplayCutoutController) {
-        return hideDisplayCutoutController.map((controller) -> controller.asHideDisplayCutout());
-    }
-
-    @WMSingleton
-    @Provides
     static Optional<HideDisplayCutoutController> provideHideDisplayCutoutController(Context context,
             ShellController shellController, DisplayController displayController,
             @ShellMainThread ShellExecutor mainExecutor) {
@@ -416,23 +394,6 @@
     }
 
     //
-    // Task to Surface communication
-    //
-
-    @WMSingleton
-    @Provides
-    static Optional<TaskSurfaceHelper> provideTaskSurfaceHelper(
-            Optional<TaskSurfaceHelperController> taskSurfaceController) {
-        return taskSurfaceController.map((controller) -> controller.asTaskSurfaceHelper());
-    }
-
-    @Provides
-    static Optional<TaskSurfaceHelperController> provideTaskSurfaceHelperController(
-            ShellTaskOrganizer taskOrganizer, @ShellMainThread ShellExecutor mainExecutor) {
-        return Optional.ofNullable(new TaskSurfaceHelperController(taskOrganizer, mainExecutor));
-    }
-
-    //
     // Pip (optional feature)
     //
 
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 fb51473..0f33f4c 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
@@ -225,6 +225,7 @@
     @Provides
     @DynamicOverride
     static SplitScreenController provideSplitScreenController(
+            ShellController shellController,
             ShellTaskOrganizer shellTaskOrganizer,
             SyncTransactionQueue syncQueue, Context context,
             RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer,
@@ -234,7 +235,7 @@
             DisplayInsetsController displayInsetsController, Transitions transitions,
             TransactionPool transactionPool, IconProvider iconProvider,
             Optional<RecentTasksController> recentTasks) {
-        return new SplitScreenController(shellTaskOrganizer, syncQueue, context,
+        return new SplitScreenController(shellController, shellTaskOrganizer, syncQueue, context,
                 rootTaskDisplayAreaOrganizer, mainExecutor, displayController, displayImeController,
                 displayInsetsController, transitions, transactionPool, iconProvider,
                 recentTasks);
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 0d0961c..c5df53b 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
@@ -81,11 +81,9 @@
     private final IconProvider mIconProvider;
     private SplitScreenController mSplitScreen;
     private ShellExecutor mMainExecutor;
-    private DragAndDropImpl mImpl;
     private ArrayList<DragAndDropListener> mListeners = new ArrayList<>();
 
     private final SparseArray<PerDisplay> mDisplayDropTargets = new SparseArray<>();
-    private final SurfaceControl.Transaction mTransaction = new SurfaceControl.Transaction();
 
     /**
      * Listener called during drag events, currently just onDragStarted.
@@ -107,11 +105,6 @@
         mLogger = new DragAndDropEventLogger(uiEventLogger);
         mIconProvider = iconProvider;
         mMainExecutor = mainExecutor;
-        mImpl = new DragAndDropImpl();
-    }
-
-    public DragAndDrop asDragAndDrop() {
-        return mImpl;
     }
 
     public void initialize(Optional<SplitScreenController> splitscreen) {
@@ -353,8 +346,4 @@
             dragLayout = dl;
         }
     }
-
-    private class DragAndDropImpl implements DragAndDrop {
-        // TODO: To be removed
-    }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutout.java
deleted file mode 100644
index dd1c8d6..0000000
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutout.java
+++ /dev/null
@@ -1,26 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.wm.shell.hidedisplaycutout;
-
-import com.android.wm.shell.common.annotations.ExternalThread;
-
-/**
- * Interface to engage hide display cutout feature.
- */
-@ExternalThread
-public interface HideDisplayCutout {
-}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutoutController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutoutController.java
index b091ab8..665b035 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutoutController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutoutController.java
@@ -40,8 +40,6 @@
     private final Context mContext;
     private final ShellController mShellController;
     private final HideDisplayCutoutOrganizer mOrganizer;
-    private final ShellExecutor mMainExecutor;
-    private final HideDisplayCutoutImpl mImpl = new HideDisplayCutoutImpl();
     @VisibleForTesting
     boolean mEnabled;
 
@@ -62,23 +60,18 @@
 
         HideDisplayCutoutOrganizer organizer =
                 new HideDisplayCutoutOrganizer(context, displayController, mainExecutor);
-        return new HideDisplayCutoutController(context, shellController, organizer, mainExecutor);
+        return new HideDisplayCutoutController(context, shellController, organizer);
     }
 
     HideDisplayCutoutController(Context context, ShellController shellController,
-            HideDisplayCutoutOrganizer organizer, ShellExecutor mainExecutor) {
+            HideDisplayCutoutOrganizer organizer) {
         mContext = context;
         mShellController = shellController;
         mOrganizer = organizer;
-        mMainExecutor = mainExecutor;
         updateStatus();
         mShellController.addConfigurationChangeListener(this);
     }
 
-    public HideDisplayCutout asHideDisplayCutout() {
-        return mImpl;
-    }
-
     @VisibleForTesting
     void updateStatus() {
         // The config value is used for controlling enabling/disabling status of the feature and is
@@ -112,8 +105,4 @@
         pw.println(mEnabled);
         mOrganizer.dump(pw);
     }
-
-    private class HideDisplayCutoutImpl implements HideDisplayCutout {
-        // TODO: To be removed
-    }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHanded.java b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHanded.java
index ee99f327..76c0f41 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHanded.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHanded.java
@@ -85,9 +85,4 @@
      * Notifies when user switch complete
      */
     void onUserSwitch(int userId);
-
-    /**
-     * Notifies when keyguard visibility changed
-     */
-    void onKeyguardVisibilityChanged(boolean showing);
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java
index ea2d508..24f02ac 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java
@@ -55,6 +55,7 @@
 import com.android.wm.shell.common.TaskStackListenerImpl;
 import com.android.wm.shell.common.annotations.ExternalThread;
 import com.android.wm.shell.sysui.ConfigurationChangeListener;
+import com.android.wm.shell.sysui.KeyguardChangeListener;
 import com.android.wm.shell.sysui.ShellController;
 
 import java.io.PrintWriter;
@@ -63,7 +64,8 @@
  * Manages and manipulates the one handed states, transitions, and gesture for phones.
  */
 public class OneHandedController implements RemoteCallable<OneHandedController>,
-        DisplayChangeController.OnDisplayChangingListener, ConfigurationChangeListener {
+        DisplayChangeController.OnDisplayChangingListener, ConfigurationChangeListener,
+        KeyguardChangeListener {
     private static final String TAG = "OneHandedController";
 
     private static final String ONE_HANDED_MODE_OFFSET_PERCENTAGE =
@@ -279,6 +281,7 @@
 
         mState.addSListeners(mTutorialHandler);
         mShellController.addConfigurationChangeListener(this);
+        mShellController.addKeyguardChangeListener(this);
     }
 
     public OneHanded asOneHanded() {
@@ -605,8 +608,11 @@
         mTutorialHandler.onConfigurationChanged();
     }
 
-    private void onKeyguardVisibilityChanged(boolean showing) {
-        mKeyguardShowing = showing;
+    @Override
+    public void onKeyguardVisibilityChanged(boolean visible, boolean occluded,
+            boolean animatingDismiss) {
+        mKeyguardShowing = visible;
+        stopOneHanded();
     }
 
     private void onUserSwitch(int newUserId) {
@@ -675,9 +681,12 @@
             return;
         }
 
+        if (mState.getState() == STATE_ACTIVE) {
+            mOneHandedUiEventLogger.writeEvent(
+                    OneHandedUiEventLogger.EVENT_ONE_HANDED_TRIGGER_ROTATION_OUT);
+        }
+
         mDisplayAreaOrganizer.onRotateDisplay(mContext, toRotation, wct);
-        mOneHandedUiEventLogger.writeEvent(
-                OneHandedUiEventLogger.EVENT_ONE_HANDED_TRIGGER_ROTATION_OUT);
     }
 
     /**
@@ -756,13 +765,6 @@
                 OneHandedController.this.onUserSwitch(userId);
             });
         }
-
-        @Override
-        public void onKeyguardVisibilityChanged(boolean showing) {
-            mMainExecutor.execute(() -> {
-                OneHandedController.this.onKeyguardVisibilityChanged(showing);
-            });
-        }
     }
 
     /**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/Pip.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/Pip.java
index 2ac1129..38631ce 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/Pip.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/Pip.java
@@ -101,23 +101,6 @@
     default void removePipExclusionBoundsChangeListener(Consumer<Rect> listener) { }
 
     /**
-     * Called when the visibility of keyguard is changed.
-     * @param showing {@code true} if keyguard is now showing, {@code false} otherwise.
-     * @param animating {@code true} if system is animating between keyguard and surface behind,
-     *                              this only makes sense when showing is {@code false}.
-     */
-    default void onKeyguardVisibilityChanged(boolean showing, boolean animating) { }
-
-    /**
-     * Called when the dismissing animation keyguard and surfaces behind is finished.
-     * See also {@link #onKeyguardVisibilityChanged(boolean, boolean)}.
-     *
-     * TODO(b/206741900) deprecate this path once we're able to animate the PiP window as part of
-     * keyguard dismiss animation.
-     */
-    default void onKeyguardDismissAnimationFinished() { }
-
-    /**
      * Dump the current state and information if need.
      *
      * @param pw The stream to dump information to.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
index 5ae4602..420d606 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
@@ -88,6 +88,7 @@
 import com.android.wm.shell.pip.PipUtils;
 import com.android.wm.shell.protolog.ShellProtoLogGroup;
 import com.android.wm.shell.sysui.ConfigurationChangeListener;
+import com.android.wm.shell.sysui.KeyguardChangeListener;
 import com.android.wm.shell.sysui.ShellController;
 import com.android.wm.shell.transition.Transitions;
 
@@ -102,7 +103,7 @@
  * Manages the picture-in-picture (PIP) UI and states for Phones.
  */
 public class PipController implements PipTransitionController.PipTransitionCallback,
-        RemoteCallable<PipController>, ConfigurationChangeListener {
+        RemoteCallable<PipController>, ConfigurationChangeListener, KeyguardChangeListener {
     private static final String TAG = "PipController";
 
     private Context mContext;
@@ -527,6 +528,7 @@
         });
 
         mShellController.addConfigurationChangeListener(this);
+        mShellController.addKeyguardChangeListener(this);
     }
 
     @Override
@@ -661,21 +663,24 @@
      * finished first to reset the visibility of PiP window.
      * See also {@link #onKeyguardDismissAnimationFinished()}
      */
-    private void onKeyguardVisibilityChanged(boolean keyguardShowing, boolean animating) {
+    @Override
+    public void onKeyguardVisibilityChanged(boolean visible, boolean occluded,
+            boolean animatingDismiss) {
         if (!mPipTaskOrganizer.isInPip()) {
             return;
         }
-        if (keyguardShowing) {
+        if (visible) {
             mIsKeyguardShowingOrAnimating = true;
             hidePipMenu(null /* onStartCallback */, null /* onEndCallback */);
             mPipTaskOrganizer.setPipVisibility(false);
-        } else if (!animating) {
+        } else if (!animatingDismiss) {
             mIsKeyguardShowingOrAnimating = false;
             mPipTaskOrganizer.setPipVisibility(true);
         }
     }
 
-    private void onKeyguardDismissAnimationFinished() {
+    @Override
+    public void onKeyguardDismissAnimationFinished() {
         if (mPipTaskOrganizer.isInPip()) {
             mIsKeyguardShowingOrAnimating = false;
             mPipTaskOrganizer.setPipVisibility(true);
@@ -996,18 +1001,6 @@
         }
 
         @Override
-        public void onKeyguardVisibilityChanged(boolean showing, boolean animating) {
-            mMainExecutor.execute(() -> {
-                PipController.this.onKeyguardVisibilityChanged(showing, animating);
-            });
-        }
-
-        @Override
-        public void onKeyguardDismissAnimationFinished() {
-            mMainExecutor.execute(PipController.this::onKeyguardDismissAnimationFinished);
-        }
-
-        @Override
         public void dump(PrintWriter pw) {
             try {
                 mMainExecutor.executeBlocking(() -> {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java
index 29b6311..e73b799 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java
@@ -77,12 +77,6 @@
         return null;
     }
 
-    /**
-     * Called when the visibility of the keyguard changes.
-     * @param showing Indicates if the keyguard is now visible.
-     */
-    void onKeyguardVisibilityChanged(boolean showing);
-
     /** Called when device waking up finished. */
     void onFinishedWakingUp();
 
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 21bea46..53ec39d 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
@@ -80,6 +80,8 @@
 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.transition.LegacyTransitions;
 import com.android.wm.shell.transition.Transitions;
 
@@ -99,7 +101,7 @@
  */
 // TODO(b/198577848): Implement split screen flicker test to consolidate CUJ of split screen.
 public class SplitScreenController implements DragAndDropPolicy.Starter,
-        RemoteCallable<SplitScreenController> {
+        RemoteCallable<SplitScreenController>, KeyguardChangeListener {
     private static final String TAG = SplitScreenController.class.getSimpleName();
 
     public static final int EXIT_REASON_UNKNOWN = 0;
@@ -127,6 +129,7 @@
     @Retention(RetentionPolicy.SOURCE)
     @interface ExitReason{}
 
+    private final ShellController mShellController;
     private final ShellTaskOrganizer mTaskOrganizer;
     private final SyncTransactionQueue mSyncQueue;
     private final Context mContext;
@@ -147,7 +150,8 @@
     // outside the bounds of the roots by being reparented into a higher level fullscreen container
     private SurfaceControl mSplitTasksContainerLayer;
 
-    public SplitScreenController(ShellTaskOrganizer shellTaskOrganizer,
+    public SplitScreenController(ShellController shellController,
+            ShellTaskOrganizer shellTaskOrganizer,
             SyncTransactionQueue syncQueue, Context context,
             RootTaskDisplayAreaOrganizer rootTDAOrganizer,
             ShellExecutor mainExecutor, DisplayController displayController,
@@ -155,6 +159,7 @@
             DisplayInsetsController displayInsetsController,
             Transitions transitions, TransactionPool transactionPool, IconProvider iconProvider,
             Optional<RecentTasksController> recentTasks) {
+        mShellController = shellController;
         mTaskOrganizer = shellTaskOrganizer;
         mSyncQueue = syncQueue;
         mContext = context;
@@ -185,6 +190,7 @@
     }
 
     public void onOrganizerRegistered() {
+        mShellController.addKeyguardChangeListener(this);
         if (mStageCoordinator == null) {
             // TODO: Multi-display
             mStageCoordinator = new StageCoordinator(mContext, DEFAULT_DISPLAY, mSyncQueue,
@@ -283,8 +289,10 @@
         mStageCoordinator.exitSplitScreen(toTopTaskId, exitReason);
     }
 
-    public void onKeyguardVisibilityChanged(boolean showing) {
-        mStageCoordinator.onKeyguardVisibilityChanged(showing);
+    @Override
+    public void onKeyguardVisibilityChanged(boolean visible, boolean occluded,
+            boolean animatingDismiss) {
+        mStageCoordinator.onKeyguardVisibilityChanged(visible);
     }
 
     public void onFinishedWakingUp() {
@@ -658,13 +666,6 @@
         }
 
         @Override
-        public void onKeyguardVisibilityChanged(boolean showing) {
-            mMainExecutor.execute(() -> {
-                SplitScreenController.this.onKeyguardVisibilityChanged(showing);
-            });
-        }
-
-        @Override
         public void onFinishedWakingUp() {
             mMainExecutor.execute(() -> {
                 SplitScreenController.this.onFinishedWakingUp();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/KeyguardChangeListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/KeyguardChangeListener.java
new file mode 100644
index 0000000..1c0b358
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/KeyguardChangeListener.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.sysui;
+
+/**
+ * Callbacks for when the keyguard changes.
+ */
+public interface KeyguardChangeListener {
+    /**
+     * Notifies the Shell that the keyguard is showing (and if so, whether it is occluded).
+     */
+    default void onKeyguardVisibilityChanged(boolean visible, boolean occluded,
+            boolean animatingDismiss) {}
+
+    /**
+     * Notifies the Shell when the keyguard dismiss animation has finished.
+     *
+     * TODO(b/206741900) deprecate this path once we're able to animate the PiP window as part of
+     * keyguard dismiss animation.
+     */
+    default void onKeyguardDismissAnimationFinished() {}
+}
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 28a30f4..837acec 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
@@ -47,7 +47,9 @@
     private final ShellExecutor mMainExecutor;
     private final ShellInterfaceImpl mImpl = new ShellInterfaceImpl();
 
-    private final CopyOnWriteArrayList<ConfigurationChangeListener> mListeners =
+    private final CopyOnWriteArrayList<ConfigurationChangeListener> mConfigChangeListeners =
+            new CopyOnWriteArrayList<>();
+    private final CopyOnWriteArrayList<KeyguardChangeListener> mKeyguardChangeListeners =
             new CopyOnWriteArrayList<>();
     private Configuration mLastConfiguration;
 
@@ -68,15 +70,31 @@
      * particular order.
      */
     public void addConfigurationChangeListener(ConfigurationChangeListener listener) {
-        mListeners.remove(listener);
-        mListeners.add(listener);
+        mConfigChangeListeners.remove(listener);
+        mConfigChangeListeners.add(listener);
     }
 
     /**
      * Removes an existing configuration listener.
      */
     public void removeConfigurationChangeListener(ConfigurationChangeListener listener) {
-        mListeners.remove(listener);
+        mConfigChangeListeners.remove(listener);
+    }
+
+    /**
+     * Adds a new Keyguard listener. The Keyguard change callbacks are not made in any
+     * particular order.
+     */
+    public void addKeyguardChangeListener(KeyguardChangeListener listener) {
+        mKeyguardChangeListeners.remove(listener);
+        mKeyguardChangeListeners.add(listener);
+    }
+
+    /**
+     * Removes an existing Keyguard listener.
+     */
+    public void removeKeyguardChangeListener(KeyguardChangeListener listener) {
+        mKeyguardChangeListeners.remove(listener);
     }
 
     @VisibleForTesting
@@ -102,7 +120,7 @@
 
         // Update the last configuration and call listeners
         mLastConfiguration.updateFrom(newConfig);
-        for (ConfigurationChangeListener listener : mListeners) {
+        for (ConfigurationChangeListener listener : mConfigChangeListeners) {
             listener.onConfigurationChanged(newConfig);
             if (densityFontScaleChanged) {
                 listener.onDensityOrFontScaleChanged();
@@ -119,11 +137,26 @@
         }
     }
 
+    @VisibleForTesting
+    void onKeyguardVisibilityChanged(boolean visible, boolean occluded, boolean animatingDismiss) {
+        for (KeyguardChangeListener listener : mKeyguardChangeListeners) {
+            listener.onKeyguardVisibilityChanged(visible, occluded, animatingDismiss);
+        }
+    }
+
+    @VisibleForTesting
+    void onKeyguardDismissAnimationFinished() {
+        for (KeyguardChangeListener listener : mKeyguardChangeListeners) {
+            listener.onKeyguardDismissAnimationFinished();
+        }
+    }
+
     public void dump(@NonNull PrintWriter pw, String prefix) {
         final String innerPrefix = prefix + "  ";
         pw.println(prefix + TAG);
-        pw.println(innerPrefix + "mListeners=" + mListeners.size());
+        pw.println(innerPrefix + "mConfigChangeListeners=" + mConfigChangeListeners.size());
         pw.println(innerPrefix + "mLastConfiguration=" + mLastConfiguration);
+        pw.println(innerPrefix + "mKeyguardChangeListeners=" + mKeyguardChangeListeners.size());
     }
 
     /**
@@ -136,5 +169,19 @@
             mMainExecutor.execute(() ->
                     ShellController.this.onConfigurationChanged(newConfiguration));
         }
+
+        @Override
+        public void onKeyguardVisibilityChanged(boolean visible, boolean occluded,
+                boolean animatingDismiss) {
+            mMainExecutor.execute(() ->
+                    ShellController.this.onKeyguardVisibilityChanged(visible, occluded,
+                            animatingDismiss));
+        }
+
+        @Override
+        public void onKeyguardDismissAnimationFinished() {
+            mMainExecutor.execute(() ->
+                    ShellController.this.onKeyguardDismissAnimationFinished());
+        }
     }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellInterface.java b/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellInterface.java
index a2d072c..a15ce5d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellInterface.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellInterface.java
@@ -30,4 +30,16 @@
      * Notifies the Shell that the configuration has changed.
      */
     default void onConfigurationChanged(Configuration newConfiguration) {}
+
+    /**
+     * Notifies the Shell that the keyguard is showing (and if so, whether it is occluded) or not
+     * showing, and whether it is animating a dismiss.
+     */
+    default void onKeyguardVisibilityChanged(boolean visible, boolean occluded,
+            boolean animatingDismiss) {}
+
+    /**
+     * Notifies the Shell when the keyguard dismiss animation has finished.
+     */
+    default void onKeyguardDismissAnimationFinished() {}
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/tasksurfacehelper/TaskSurfaceHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/tasksurfacehelper/TaskSurfaceHelper.java
deleted file mode 100644
index ad9dda6..0000000
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/tasksurfacehelper/TaskSurfaceHelper.java
+++ /dev/null
@@ -1,37 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.wm.shell.tasksurfacehelper;
-
-import android.app.ActivityManager.RunningTaskInfo;
-import android.graphics.Rect;
-import android.view.SurfaceControl;
-
-import java.util.concurrent.Executor;
-import java.util.function.Consumer;
-
-/**
- * Interface to communicate with a Task's SurfaceControl.
- */
-public interface TaskSurfaceHelper {
-
-    /** Sets the METADATA_GAME_MODE for the layer corresponding to the task **/
-    default void setGameModeForTask(int taskId, int gameMode) {}
-
-    /** Takes a screenshot for a task **/
-    default void screenshotTask(RunningTaskInfo taskInfo, Rect crop, Executor executor,
-            Consumer<SurfaceControl.ScreenshotHardwareBuffer> consumer) {}
-}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/tasksurfacehelper/TaskSurfaceHelperController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/tasksurfacehelper/TaskSurfaceHelperController.java
deleted file mode 100644
index 064d9d1..0000000
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/tasksurfacehelper/TaskSurfaceHelperController.java
+++ /dev/null
@@ -1,82 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.wm.shell.tasksurfacehelper;
-
-import android.app.ActivityManager.RunningTaskInfo;
-import android.graphics.Rect;
-import android.view.SurfaceControl;
-
-import com.android.wm.shell.ShellTaskOrganizer;
-import com.android.wm.shell.common.ShellExecutor;
-
-import java.util.concurrent.Executor;
-import java.util.function.Consumer;
-
-/**
- * Intermediary controller that communicates with {@link ShellTaskOrganizer} to send commands
- * to SurfaceControl.
- */
-public class TaskSurfaceHelperController {
-
-    private final ShellTaskOrganizer mTaskOrganizer;
-    private final ShellExecutor mMainExecutor;
-    private final TaskSurfaceHelperImpl mImpl = new TaskSurfaceHelperImpl();
-
-    public TaskSurfaceHelperController(ShellTaskOrganizer taskOrganizer,
-            ShellExecutor mainExecutor) {
-        mTaskOrganizer = taskOrganizer;
-        mMainExecutor = mainExecutor;
-    }
-
-    public TaskSurfaceHelper asTaskSurfaceHelper() {
-        return mImpl;
-    }
-
-    /**
-     * Sends a Transaction to set the game mode metadata on the
-     * corresponding SurfaceControl
-     */
-    public void setGameModeForTask(int taskId, int gameMode) {
-        mTaskOrganizer.setSurfaceMetadata(taskId, SurfaceControl.METADATA_GAME_MODE, gameMode);
-    }
-
-    /**
-     * Take screenshot of the specified task.
-     */
-    public void screenshotTask(RunningTaskInfo taskInfo, Rect crop,
-            Consumer<SurfaceControl.ScreenshotHardwareBuffer> consumer) {
-        mTaskOrganizer.screenshotTask(taskInfo, crop, consumer);
-    }
-
-    private class TaskSurfaceHelperImpl implements TaskSurfaceHelper {
-        @Override
-        public void setGameModeForTask(int taskId, int gameMode) {
-            mMainExecutor.execute(() -> {
-                TaskSurfaceHelperController.this.setGameModeForTask(taskId, gameMode);
-            });
-        }
-
-        @Override
-        public void screenshotTask(RunningTaskInfo taskInfo, Rect crop, Executor executor,
-                Consumer<SurfaceControl.ScreenshotHardwareBuffer> consumer) {
-            mMainExecutor.execute(() -> {
-                TaskSurfaceHelperController.this.screenshotTask(taskInfo, crop,
-                        (t) -> executor.execute(() -> consumer.accept(t)));
-            });
-        }
-    }
-}
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 3ef3a1f..4a7fd3d 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
@@ -24,6 +24,7 @@
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 
+import android.content.ComponentName;
 import android.os.RemoteException;
 import android.util.SparseArray;
 import android.view.IDisplayWindowInsetsController;
@@ -165,7 +166,7 @@
         int hideInsetsCount = 0;
 
         @Override
-        public void topFocusedWindowChanged(String packageName,
+        public void topFocusedWindowChanged(ComponentName component,
                 InsetsVisibilities requestedVisibilities) {
             topFocusedWindowChangedCount++;
         }
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIControllerTest.java
index 596100d..828c13e 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIControllerTest.java
@@ -53,6 +53,7 @@
 import com.android.wm.shell.common.ShellExecutor;
 import com.android.wm.shell.common.SyncTransactionQueue;
 import com.android.wm.shell.compatui.letterboxedu.LetterboxEduWindowManager;
+import com.android.wm.shell.sysui.ShellController;
 import com.android.wm.shell.transition.Transitions;
 
 import org.junit.Before;
@@ -78,6 +79,7 @@
     private static final int TASK_ID = 12;
 
     private CompatUIController mController;
+    private @Mock ShellController mMockShellController;
     private @Mock DisplayController mMockDisplayController;
     private @Mock DisplayInsetsController mMockDisplayInsetsController;
     private @Mock DisplayLayout mMockDisplayLayout;
@@ -105,7 +107,7 @@
         doReturn(TASK_ID).when(mMockLetterboxEduLayout).getTaskId();
         doReturn(true).when(mMockLetterboxEduLayout).createLayout(anyBoolean());
         doReturn(true).when(mMockLetterboxEduLayout).updateCompatInfo(any(), any(), anyBoolean());
-        mController = new CompatUIController(mContext, mMockDisplayController,
+        mController = new CompatUIController(mContext, mMockShellController, mMockDisplayController,
                 mMockDisplayInsetsController, mMockImeController, mMockSyncQueue, mMockExecutor,
                 mMockTransitionsLazy) {
             @Override
@@ -124,6 +126,11 @@
     }
 
     @Test
+    public void instantiateController_registerKeyguardChangeListener() {
+        verify(mMockShellController, times(1)).addKeyguardChangeListener(any());
+    }
+
+    @Test
     public void testListenerRegistered() {
         verify(mMockDisplayController).addDisplayWindowListener(mController);
         verify(mMockImeController).addPositionProcessor(mController);
@@ -324,7 +331,7 @@
                 /* hasSizeCompat= */ true, CAMERA_COMPAT_CONTROL_HIDDEN), mMockTaskListener);
 
         // Verify that the restart button is hidden after keyguard becomes showing.
-        mController.onKeyguardShowingChanged(true);
+        mController.onKeyguardVisibilityChanged(true, false, false);
 
         verify(mMockCompatLayout).updateVisibility(false);
         verify(mMockLetterboxEduLayout).updateVisibility(false);
@@ -340,7 +347,7 @@
                 false);
 
         // Verify button is shown after keyguard becomes not showing.
-        mController.onKeyguardShowingChanged(false);
+        mController.onKeyguardVisibilityChanged(false, false, false);
 
         verify(mMockCompatLayout).updateVisibility(true);
         verify(mMockLetterboxEduLayout).updateVisibility(true);
@@ -352,7 +359,7 @@
                 /* hasSizeCompat= */ true, CAMERA_COMPAT_CONTROL_HIDDEN), mMockTaskListener);
 
         mController.onImeVisibilityChanged(DISPLAY_ID, /* isShowing= */ true);
-        mController.onKeyguardShowingChanged(true);
+        mController.onKeyguardVisibilityChanged(true, false, false);
 
         verify(mMockCompatLayout, times(2)).updateVisibility(false);
         verify(mMockLetterboxEduLayout, times(2)).updateVisibility(false);
@@ -360,7 +367,7 @@
         clearInvocations(mMockCompatLayout, mMockLetterboxEduLayout);
 
         // Verify button remains hidden after keyguard becomes not showing since IME is showing.
-        mController.onKeyguardShowingChanged(false);
+        mController.onKeyguardVisibilityChanged(false, false, false);
 
         verify(mMockCompatLayout).updateVisibility(false);
         verify(mMockLetterboxEduLayout).updateVisibility(false);
@@ -378,7 +385,7 @@
                 /* hasSizeCompat= */ true, CAMERA_COMPAT_CONTROL_HIDDEN), mMockTaskListener);
 
         mController.onImeVisibilityChanged(DISPLAY_ID, /* isShowing= */ true);
-        mController.onKeyguardShowingChanged(true);
+        mController.onKeyguardVisibilityChanged(true, false, false);
 
         verify(mMockCompatLayout, times(2)).updateVisibility(false);
         verify(mMockLetterboxEduLayout, times(2)).updateVisibility(false);
@@ -392,7 +399,7 @@
         verify(mMockLetterboxEduLayout).updateVisibility(false);
 
         // Verify button is shown after keyguard becomes not showing.
-        mController.onKeyguardShowingChanged(false);
+        mController.onKeyguardVisibilityChanged(false, false, false);
 
         verify(mMockCompatLayout).updateVisibility(true);
         verify(mMockLetterboxEduLayout).updateVisibility(true);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutoutControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutoutControllerTest.java
index 68e955e..dcc504a 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutoutControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutoutControllerTest.java
@@ -29,7 +29,6 @@
 import androidx.test.platform.app.InstrumentationRegistry;
 
 import com.android.wm.shell.ShellTestCase;
-import com.android.wm.shell.common.ShellExecutor;
 import com.android.wm.shell.sysui.ShellController;
 
 import org.junit.Before;
@@ -49,8 +48,6 @@
     private ShellController mShellController;
     @Mock
     private HideDisplayCutoutOrganizer mMockDisplayAreaOrganizer;
-    @Mock
-    private ShellExecutor mMockMainExecutor;
 
     private HideDisplayCutoutController mHideDisplayCutoutController;
 
@@ -58,7 +55,7 @@
     public void setUp() throws Exception {
         MockitoAnnotations.initMocks(this);
         mHideDisplayCutoutController = new HideDisplayCutoutController(
-                mContext, mShellController, mMockDisplayAreaOrganizer, mMockMainExecutor);
+                mContext, mShellController, mMockDisplayAreaOrganizer);
     }
 
     @Test
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedControllerTest.java
index 958969d..dbf93ae 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedControllerTest.java
@@ -149,6 +149,11 @@
     }
 
     @Test
+    public void testControllerRegistersKeyguardChangeListener() {
+        verify(mMockShellController, times(1)).addKeyguardChangeListener(any());
+    }
+
+    @Test
     public void testDefaultShouldNotInOneHanded() {
         // Assert default transition state is STATE_NONE
         assertThat(mSpiedTransitionState.getState()).isEqualTo(STATE_NONE);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java
index 827785b..f192514 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java
@@ -123,6 +123,11 @@
     }
 
     @Test
+    public void instantiatePipController_registerKeyguardChangeListener() {
+        verify(mMockShellController, times(1)).addKeyguardChangeListener(any());
+    }
+
+    @Test
     public void instantiatePipController_registersPipTransitionCallback() {
         verify(mMockPipTransitionController).registerPipTransitionCallback(any());
     }
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 c10e4a1..c7a261f 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
@@ -28,6 +28,9 @@
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
 
 import android.app.ActivityManager;
 import android.content.ComponentName;
@@ -44,10 +47,12 @@
 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.DisplayLayout;
 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.recents.RecentTasksController;
+import com.android.wm.shell.sysui.ShellController;
 import com.android.wm.shell.transition.Transitions;
 
 import org.junit.Before;
@@ -65,6 +70,7 @@
 @RunWith(AndroidJUnit4.class)
 public class SplitScreenControllerTests extends ShellTestCase {
 
+    @Mock ShellController mShellController;
     @Mock ShellTaskOrganizer mTaskOrganizer;
     @Mock SyncTransactionQueue mSyncQueue;
     @Mock RootTaskDisplayAreaOrganizer mRootTDAOrganizer;
@@ -82,10 +88,17 @@
     @Before
     public void setup() {
         MockitoAnnotations.initMocks(this);
-        mSplitScreenController = spy(new SplitScreenController(mTaskOrganizer, mSyncQueue, mContext,
-                mRootTDAOrganizer, mMainExecutor, mDisplayController, mDisplayImeController,
-                mDisplayInsetsController, mTransitions, mTransactionPool, mIconProvider,
-                mRecentTasks));
+        mSplitScreenController = spy(new SplitScreenController(mShellController, mTaskOrganizer,
+                mSyncQueue, mContext, mRootTDAOrganizer, mMainExecutor, mDisplayController,
+                mDisplayImeController, mDisplayInsetsController, mTransitions, mTransactionPool,
+                mIconProvider, mRecentTasks));
+    }
+
+    @Test
+    public void testControllerRegistersKeyguardChangeListener() {
+        when(mDisplayController.getDisplayLayout(anyInt())).thenReturn(new DisplayLayout());
+        mSplitScreenController.onOrganizerRegistered();
+        verify(mShellController, times(1)).addKeyguardChangeListener(any());
     }
 
     @Test
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 6cc3ae8..1c0e46f 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
@@ -46,12 +46,14 @@
     private ShellExecutor mExecutor;
 
     private ShellController mController;
-    private TestConfigurationChangeListener mListener;
+    private TestConfigurationChangeListener mConfigChangeListener;
+    private TestKeyguardChangeListener mKeyguardChangeListener;
 
     @Before
     public void setUp() {
         MockitoAnnotations.initMocks(this);
-        mListener = new TestConfigurationChangeListener();
+        mKeyguardChangeListener = new TestKeyguardChangeListener();
+        mConfigChangeListener = new TestConfigurationChangeListener();
         mController = new ShellController(mExecutor);
         mController.onConfigurationChanged(getConfigurationCopy());
     }
@@ -62,47 +64,97 @@
     }
 
     @Test
+    public void testAddKeyguardChangeListener_ensureCallback() {
+        mController.addKeyguardChangeListener(mKeyguardChangeListener);
+
+        mController.onKeyguardVisibilityChanged(true, false, false);
+        assertTrue(mKeyguardChangeListener.visibilityChanged == 1);
+        assertTrue(mKeyguardChangeListener.dismissAnimationFinished == 0);
+    }
+
+    @Test
+    public void testDoubleAddKeyguardChangeListener_ensureSingleCallback() {
+        mController.addKeyguardChangeListener(mKeyguardChangeListener);
+        mController.addKeyguardChangeListener(mKeyguardChangeListener);
+
+        mController.onKeyguardVisibilityChanged(true, false, false);
+        assertTrue(mKeyguardChangeListener.visibilityChanged == 1);
+        assertTrue(mKeyguardChangeListener.dismissAnimationFinished == 0);
+    }
+
+    @Test
+    public void testAddRemoveKeyguardChangeListener_ensureNoCallback() {
+        mController.addKeyguardChangeListener(mKeyguardChangeListener);
+        mController.removeKeyguardChangeListener(mKeyguardChangeListener);
+
+        mController.onKeyguardVisibilityChanged(true, false, false);
+        assertTrue(mKeyguardChangeListener.visibilityChanged == 0);
+        assertTrue(mKeyguardChangeListener.dismissAnimationFinished == 0);
+    }
+
+    @Test
+    public void testKeyguardVisibilityChanged() {
+        mController.addKeyguardChangeListener(mKeyguardChangeListener);
+
+        mController.onKeyguardVisibilityChanged(true, true, true);
+        assertTrue(mKeyguardChangeListener.visibilityChanged == 1);
+        assertTrue(mKeyguardChangeListener.lastAnimatingDismiss);
+        assertTrue(mKeyguardChangeListener.lastOccluded);
+        assertTrue(mKeyguardChangeListener.lastAnimatingDismiss);
+        assertTrue(mKeyguardChangeListener.dismissAnimationFinished == 0);
+    }
+
+    @Test
+    public void testKeyguardDismissAnimationFinished() {
+        mController.addKeyguardChangeListener(mKeyguardChangeListener);
+
+        mController.onKeyguardDismissAnimationFinished();
+        assertTrue(mKeyguardChangeListener.visibilityChanged == 0);
+        assertTrue(mKeyguardChangeListener.dismissAnimationFinished == 1);
+    }
+
+    @Test
     public void testAddConfigurationChangeListener_ensureCallback() {
-        mController.addConfigurationChangeListener(mListener);
+        mController.addConfigurationChangeListener(mConfigChangeListener);
 
         Configuration newConfig = getConfigurationCopy();
         newConfig.densityDpi = 200;
         mController.onConfigurationChanged(newConfig);
-        assertTrue(mListener.configChanges == 1);
+        assertTrue(mConfigChangeListener.configChanges == 1);
     }
 
     @Test
     public void testDoubleAddConfigurationChangeListener_ensureSingleCallback() {
-        mController.addConfigurationChangeListener(mListener);
-        mController.addConfigurationChangeListener(mListener);
+        mController.addConfigurationChangeListener(mConfigChangeListener);
+        mController.addConfigurationChangeListener(mConfigChangeListener);
 
         Configuration newConfig = getConfigurationCopy();
         newConfig.densityDpi = 200;
         mController.onConfigurationChanged(newConfig);
-        assertTrue(mListener.configChanges == 1);
+        assertTrue(mConfigChangeListener.configChanges == 1);
     }
 
     @Test
     public void testAddRemoveConfigurationChangeListener_ensureNoCallback() {
-        mController.addConfigurationChangeListener(mListener);
-        mController.removeConfigurationChangeListener(mListener);
+        mController.addConfigurationChangeListener(mConfigChangeListener);
+        mController.removeConfigurationChangeListener(mConfigChangeListener);
 
         Configuration newConfig = getConfigurationCopy();
         newConfig.densityDpi = 200;
         mController.onConfigurationChanged(newConfig);
-        assertTrue(mListener.configChanges == 0);
+        assertTrue(mConfigChangeListener.configChanges == 0);
     }
 
     @Test
     public void testMultipleConfigurationChangeListeners() {
         TestConfigurationChangeListener listener2 = new TestConfigurationChangeListener();
-        mController.addConfigurationChangeListener(mListener);
+        mController.addConfigurationChangeListener(mConfigChangeListener);
         mController.addConfigurationChangeListener(listener2);
 
         Configuration newConfig = getConfigurationCopy();
         newConfig.densityDpi = 200;
         mController.onConfigurationChanged(newConfig);
-        assertTrue(mListener.configChanges == 1);
+        assertTrue(mConfigChangeListener.configChanges == 1);
         assertTrue(listener2.configChanges == 1);
     }
 
@@ -115,7 +167,7 @@
             }
         };
         mController.addConfigurationChangeListener(badListener);
-        mController.addConfigurationChangeListener(mListener);
+        mController.addConfigurationChangeListener(mConfigChangeListener);
 
         // Ensure we don't fail just because a listener was removed mid-callback
         Configuration newConfig = getConfigurationCopy();
@@ -125,77 +177,77 @@
 
     @Test
     public void testDensityChangeCallback() {
-        mController.addConfigurationChangeListener(mListener);
+        mController.addConfigurationChangeListener(mConfigChangeListener);
 
         Configuration newConfig = getConfigurationCopy();
         newConfig.densityDpi = 200;
         mController.onConfigurationChanged(newConfig);
-        assertTrue(mListener.configChanges == 1);
-        assertTrue(mListener.densityChanges == 1);
-        assertTrue(mListener.smallestWidthChanges == 0);
-        assertTrue(mListener.themeChanges == 0);
-        assertTrue(mListener.localeChanges == 0);
+        assertTrue(mConfigChangeListener.configChanges == 1);
+        assertTrue(mConfigChangeListener.densityChanges == 1);
+        assertTrue(mConfigChangeListener.smallestWidthChanges == 0);
+        assertTrue(mConfigChangeListener.themeChanges == 0);
+        assertTrue(mConfigChangeListener.localeChanges == 0);
     }
 
     @Test
     public void testFontScaleChangeCallback() {
-        mController.addConfigurationChangeListener(mListener);
+        mController.addConfigurationChangeListener(mConfigChangeListener);
 
         Configuration newConfig = getConfigurationCopy();
         newConfig.fontScale = 2;
         mController.onConfigurationChanged(newConfig);
-        assertTrue(mListener.configChanges == 1);
-        assertTrue(mListener.densityChanges == 1);
-        assertTrue(mListener.smallestWidthChanges == 0);
-        assertTrue(mListener.themeChanges == 0);
-        assertTrue(mListener.localeChanges == 0);
+        assertTrue(mConfigChangeListener.configChanges == 1);
+        assertTrue(mConfigChangeListener.densityChanges == 1);
+        assertTrue(mConfigChangeListener.smallestWidthChanges == 0);
+        assertTrue(mConfigChangeListener.themeChanges == 0);
+        assertTrue(mConfigChangeListener.localeChanges == 0);
     }
 
     @Test
     public void testSmallestWidthChangeCallback() {
-        mController.addConfigurationChangeListener(mListener);
+        mController.addConfigurationChangeListener(mConfigChangeListener);
 
         Configuration newConfig = getConfigurationCopy();
         newConfig.smallestScreenWidthDp = 100;
         mController.onConfigurationChanged(newConfig);
-        assertTrue(mListener.configChanges == 1);
-        assertTrue(mListener.densityChanges == 0);
-        assertTrue(mListener.smallestWidthChanges == 1);
-        assertTrue(mListener.themeChanges == 0);
-        assertTrue(mListener.localeChanges == 0);
+        assertTrue(mConfigChangeListener.configChanges == 1);
+        assertTrue(mConfigChangeListener.densityChanges == 0);
+        assertTrue(mConfigChangeListener.smallestWidthChanges == 1);
+        assertTrue(mConfigChangeListener.themeChanges == 0);
+        assertTrue(mConfigChangeListener.localeChanges == 0);
     }
 
     @Test
     public void testThemeChangeCallback() {
-        mController.addConfigurationChangeListener(mListener);
+        mController.addConfigurationChangeListener(mConfigChangeListener);
 
         Configuration newConfig = getConfigurationCopy();
         newConfig.assetsSeq++;
         mController.onConfigurationChanged(newConfig);
-        assertTrue(mListener.configChanges == 1);
-        assertTrue(mListener.densityChanges == 0);
-        assertTrue(mListener.smallestWidthChanges == 0);
-        assertTrue(mListener.themeChanges == 1);
-        assertTrue(mListener.localeChanges == 0);
+        assertTrue(mConfigChangeListener.configChanges == 1);
+        assertTrue(mConfigChangeListener.densityChanges == 0);
+        assertTrue(mConfigChangeListener.smallestWidthChanges == 0);
+        assertTrue(mConfigChangeListener.themeChanges == 1);
+        assertTrue(mConfigChangeListener.localeChanges == 0);
     }
 
     @Test
     public void testNightModeChangeCallback() {
-        mController.addConfigurationChangeListener(mListener);
+        mController.addConfigurationChangeListener(mConfigChangeListener);
 
         Configuration newConfig = getConfigurationCopy();
         newConfig.uiMode = Configuration.UI_MODE_NIGHT_YES;
         mController.onConfigurationChanged(newConfig);
-        assertTrue(mListener.configChanges == 1);
-        assertTrue(mListener.densityChanges == 0);
-        assertTrue(mListener.smallestWidthChanges == 0);
-        assertTrue(mListener.themeChanges == 1);
-        assertTrue(mListener.localeChanges == 0);
+        assertTrue(mConfigChangeListener.configChanges == 1);
+        assertTrue(mConfigChangeListener.densityChanges == 0);
+        assertTrue(mConfigChangeListener.smallestWidthChanges == 0);
+        assertTrue(mConfigChangeListener.themeChanges == 1);
+        assertTrue(mConfigChangeListener.localeChanges == 0);
     }
 
     @Test
     public void testLocaleChangeCallback() {
-        mController.addConfigurationChangeListener(mListener);
+        mController.addConfigurationChangeListener(mConfigChangeListener);
 
         Configuration newConfig = getConfigurationCopy();
         // Just change the locales to be different
@@ -205,11 +257,11 @@
             newConfig.locale = Locale.CANADA;
         }
         mController.onConfigurationChanged(newConfig);
-        assertTrue(mListener.configChanges == 1);
-        assertTrue(mListener.densityChanges == 0);
-        assertTrue(mListener.smallestWidthChanges == 0);
-        assertTrue(mListener.themeChanges == 0);
-        assertTrue(mListener.localeChanges == 1);
+        assertTrue(mConfigChangeListener.configChanges == 1);
+        assertTrue(mConfigChangeListener.densityChanges == 0);
+        assertTrue(mConfigChangeListener.smallestWidthChanges == 0);
+        assertTrue(mConfigChangeListener.themeChanges == 0);
+        assertTrue(mConfigChangeListener.localeChanges == 1);
     }
 
     private Configuration getConfigurationCopy() {
@@ -253,4 +305,27 @@
             localeChanges++;
         }
     }
+
+    private class TestKeyguardChangeListener implements KeyguardChangeListener {
+        // Counts of number of times each of the callbacks are called
+        public int visibilityChanged;
+        public boolean lastVisibility;
+        public boolean lastOccluded;
+        public boolean lastAnimatingDismiss;
+        public int dismissAnimationFinished;
+
+        @Override
+        public void onKeyguardVisibilityChanged(boolean visible, boolean occluded,
+                boolean animatingDismiss) {
+            lastVisibility = visible;
+            lastOccluded = occluded;
+            lastAnimatingDismiss = animatingDismiss;
+            visibilityChanged++;
+        }
+
+        @Override
+        public void onKeyguardDismissAnimationFinished() {
+            dismissAnimationFinished++;
+        }
+    }
 }
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/tasksurfacehelper/TaskSurfaceHelperControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/tasksurfacehelper/TaskSurfaceHelperControllerTest.java
deleted file mode 100644
index 7583418..0000000
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/tasksurfacehelper/TaskSurfaceHelperControllerTest.java
+++ /dev/null
@@ -1,57 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.wm.shell.tasksurfacehelper;
-
-import static org.mockito.Mockito.verify;
-
-import android.testing.AndroidTestingRunner;
-import android.view.SurfaceControl;
-
-import androidx.test.filters.SmallTest;
-
-import com.android.wm.shell.ShellTaskOrganizer;
-import com.android.wm.shell.ShellTestCase;
-import com.android.wm.shell.common.ShellExecutor;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-@RunWith(AndroidTestingRunner.class)
-@SmallTest
-public class TaskSurfaceHelperControllerTest extends ShellTestCase {
-    private TaskSurfaceHelperController mTaskSurfaceHelperController;
-    @Mock
-    private ShellTaskOrganizer mMockTaskOrganizer;
-    @Mock
-    private ShellExecutor mMockShellExecutor;
-
-    @Before
-    public void setUp() throws Exception {
-        MockitoAnnotations.initMocks(this);
-        mTaskSurfaceHelperController = new TaskSurfaceHelperController(
-                mMockTaskOrganizer, mMockShellExecutor);
-    }
-
-    @Test
-    public void testSetGameModeForTask() {
-        mTaskSurfaceHelperController.setGameModeForTask(/*taskId*/1, /*gameMode*/3);
-        verify(mMockTaskOrganizer).setSurfaceMetadata(1, SurfaceControl.METADATA_GAME_MODE, 3);
-    }
-}
diff --git a/location/java/android/location/SatellitePvt.java b/location/java/android/location/SatellitePvt.java
index f3e1508..2031929 100644
--- a/location/java/android/location/SatellitePvt.java
+++ b/location/java/android/location/SatellitePvt.java
@@ -539,7 +539,7 @@
      *
      * <p>This field is valid if {@link #hasIssueOfDataEphemeris()} is true.
      */
-    @IntRange(from = 0, to = 255)
+    @IntRange(from = 0, to = 1023)
     public int getIssueOfDataEphemeris() {
         return mIssueOfDataEphemeris;
     }
@@ -847,8 +847,8 @@
          */
         @NonNull
         public Builder setIssueOfDataEphemeris(
-                @IntRange(from = 0, to = 255) int issueOfDataEphemeris) {
-            Preconditions.checkArgumentInRange(issueOfDataEphemeris, 0, 255,
+                @IntRange(from = 0, to = 1023) int issueOfDataEphemeris) {
+            Preconditions.checkArgumentInRange(issueOfDataEphemeris, 0, 1023,
                     "issueOfDataEphemeris");
             mIssueOfDataEphemeris = issueOfDataEphemeris;
             mFlags = (byte) (mFlags | HAS_ISSUE_OF_DATA_EPHEMERIS);
diff --git a/packages/SettingsLib/LayoutPreference/res/layout/settings_entity_header.xml b/packages/SettingsLib/LayoutPreference/res/layout/settings_entity_header.xml
index 50f69d1..c629d96 100644
--- a/packages/SettingsLib/LayoutPreference/res/layout/settings_entity_header.xml
+++ b/packages/SettingsLib/LayoutPreference/res/layout/settings_entity_header.xml
@@ -71,6 +71,8 @@
         <TextView
             android:id="@+id/entity_header_second_summary"
             style="@style/TextAppearance.EntityHeaderSummary"
+            android:singleLine="false"
+            android:maxLines="4"
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"/>
 
diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml
index f92cc8e..4f84c8c 100644
--- a/packages/SettingsLib/res/values/strings.xml
+++ b/packages/SettingsLib/res/values/strings.xml
@@ -246,9 +246,9 @@
     <!-- Bluetooth settings. The user-visible string that is used whenever referring to the PAN profile (accessing Internet through remote device). [CHAR LIMIT=40] -->
     <string name="bluetooth_profile_pan">Internet access</string>
     <!-- Bluetooth settings. The user-visible string that is used whenever referring to the PBAP profile. [CHAR LIMIT=40] -->
-    <string name="bluetooth_profile_pbap">Contact sharing</string>
+    <string name="bluetooth_profile_pbap">Contacts and call history sharing</string>
     <!-- Bluetooth settings. The user-visible summary string that is used whenever referring to the PBAP profile (sharing contacts). [CHAR LIMIT=60] -->
-    <string name="bluetooth_profile_pbap_summary">Use for contact sharing</string>
+    <string name="bluetooth_profile_pbap_summary">Use for contacts and call history sharing</string>
     <!-- Bluetooth settings. The user-visible string that is used whenever referring to the PAN profile (sharing this device's Internet connection). [CHAR LIMIT=40] -->
     <string name="bluetooth_profile_pan_nap">Internet connection sharing</string>
     <!-- Bluetooth settings. The user-visible string that is used whenever referring to the map profile. -->
diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp
index fa87de2..ffd6b52 100644
--- a/packages/SystemUI/Android.bp
+++ b/packages/SystemUI/Android.bp
@@ -110,6 +110,7 @@
         "androidx.arch.core_core-runtime",
         "androidx.lifecycle_lifecycle-common-java8",
         "androidx.lifecycle_lifecycle-extensions",
+        "androidx.lifecycle_lifecycle-runtime-ktx",
         "androidx.dynamicanimation_dynamicanimation",
         "androidx-constraintlayout_constraintlayout",
         "androidx.exifinterface_exifinterface",
@@ -218,6 +219,7 @@
         "androidx.arch.core_core-runtime",
         "androidx.lifecycle_lifecycle-common-java8",
         "androidx.lifecycle_lifecycle-extensions",
+        "androidx.lifecycle_lifecycle-runtime-ktx",
         "androidx.dynamicanimation_dynamicanimation",
         "androidx-constraintlayout_constraintlayout",
         "androidx.exifinterface_exifinterface",
diff --git a/packages/SystemUI/TEST_MAPPING b/packages/SystemUI/TEST_MAPPING
index f9a9ef6..154a6fc 100644
--- a/packages/SystemUI/TEST_MAPPING
+++ b/packages/SystemUI/TEST_MAPPING
@@ -102,7 +102,7 @@
   //
   // If you don't use @Staging or @Postsubmit, your new test will immediately
   // block presubmit, which is probably not what you want!
-  "platinum-postsubmit": [
+  "sysui-platinum-postsubmit": [
     {
       "name": "PlatformScenarioTests",
       "options": [
@@ -121,7 +121,7 @@
       ]
     }
   ],
-  "staged-platinum-postsubmit": [
+  "sysui-staged-platinum-postsubmit": [
     {
       "name": "PlatformScenarioTests",
       "options": [
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt
index fbe3356..8ddd430 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt
@@ -523,9 +523,10 @@
             state: LaunchAnimator.State,
             linearProgress: Float,
         ) {
-            if (transactionApplierView.viewRootImpl == null) {
-                // If the view root we synchronize with was detached, don't apply any transaction
-                // (as [SyncRtSurfaceTransactionApplier.scheduleApply] would otherwise throw).
+            if (transactionApplierView.viewRootImpl == null || !window.leash.isValid) {
+                // Don't apply any transaction if the view root we synchronize with was detached or
+                // if the SurfaceControl associated with [window] is not valid, as
+                // [SyncRtSurfaceTransactionApplier.scheduleApply] would otherwise throw.
                 return
             }
 
@@ -605,9 +606,10 @@
             state: LaunchAnimator.State,
             linearProgress: Float
         ) {
-            if (transactionApplierView.viewRootImpl == null) {
-                // If the view root we synchronize with was detached, don't apply any transaction
-                // (as [SyncRtSurfaceTransactionApplier.scheduleApply] would otherwise throw).
+            if (transactionApplierView.viewRootImpl == null || !navigationBar.leash.isValid) {
+                // Don't apply any transaction if the view root we synchronize with was detached or
+                // if the SurfaceControl associated with [navigationBar] is not valid, as
+                // [SyncRtSurfaceTransactionApplier.scheduleApply] would otherwise throw.
                 return
             }
 
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewLaunchAnimatorController.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewLaunchAnimatorController.kt
index 47f448d..eb000ad 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewLaunchAnimatorController.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewLaunchAnimatorController.kt
@@ -49,17 +49,16 @@
  * Note: Avoid instantiating this directly and call [ActivityLaunchAnimator.Controller.fromView]
  * whenever possible instead.
  */
-open class GhostedViewLaunchAnimatorController(
+open class GhostedViewLaunchAnimatorController @JvmOverloads constructor(
     /** The view that will be ghosted and from which the background will be extracted. */
     private val ghostedView: View,
 
     /** The [InteractionJankMonitor.CujType] associated to this animation. */
     private val cujType: Int? = null,
-    private var interactionJankMonitor: InteractionJankMonitor? = null
+    private var interactionJankMonitor: InteractionJankMonitor =
+        InteractionJankMonitor.getInstance(),
 ) : ActivityLaunchAnimator.Controller {
 
-    constructor(view: View, type: Int) : this(view, type, null)
-
     /** The container to which we will add the ghost view and expanding background. */
     override var launchContainer = ghostedView.rootView as ViewGroup
     private val launchContainerOverlay: ViewGroupOverlay
@@ -203,7 +202,7 @@
         val matrix = ghostView?.animationMatrix ?: Matrix.IDENTITY_MATRIX
         matrix.getValues(initialGhostViewMatrixValues)
 
-        cujType?.let { interactionJankMonitor?.begin(ghostedView, it) }
+        cujType?.let { interactionJankMonitor.begin(ghostedView, it) }
     }
 
     override fun onLaunchAnimationProgress(
@@ -289,7 +288,7 @@
             return
         }
 
-        cujType?.let { interactionJankMonitor?.end(it) }
+        cujType?.let { interactionJankMonitor.end(it) }
 
         backgroundDrawable?.wrapped?.alpha = startBackgroundAlpha
 
diff --git a/packages/SystemUI/docs/user-file-manager.md b/packages/SystemUI/docs/user-file-manager.md
index 64f1694..52fa206 100644
--- a/packages/SystemUI/docs/user-file-manager.md
+++ b/packages/SystemUI/docs/user-file-manager.md
@@ -1,10 +1,30 @@
 # UserFileManager
 
-This class is used to generate file paths and SharedPreferences that is compatible for multiple
+This class is used to generate file paths and SharedPreferences that is compatible for specific OS
 users in SystemUI. Due to constraints in SystemUI, we can only read/write files as the system user.
 Therefore, for secondary users, we want to store secondary user specific files into the system user
 directory.
 
+
+## Usages
+
+Inject UserFileManager into your class.
+
+### fun getFile(fileName: String, userId: Int): File
+Add a file name and user id. You can retrieve the current user id from UserTracker. This will
+return a java.io File object that contains the file path to write/read to.
+
+i.e. `fileManager.getFile("example.xml", userTracker.userId)`
+
+### fun getSharedPreferences(fileName: String, mode: Int, userId: Int): SharedPreferences
+Add a file name, user id, and PreferencesMode. You can retrieve the current user id from
+UserTracker. This returns SharedPreferences object that is tied to the specific user. Note that if
+the SharedPreferences file does not exist, one will be created automatically. See
+[SharedPreferences documentation](https://developer.android.com/reference/android/content/Context#getSharedPreferences(java.lang.String,%20int))
+for more details.
+
+i.e. `fileManager.getSharedPreferences("prefs.xml", userTracker.userId, 0)`
+
 ## Handling User Removal
 
 This class will listen for Intent.ACTION_USER_REMOVED and remove directories that no longer
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockPlugin.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockPlugin.java
index 3058d94..bef61b8 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockPlugin.java
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockPlugin.java
@@ -23,7 +23,9 @@
 
 /**
  * Plugin used to replace main clock in keyguard.
+ * @deprecated Migrating to ClockProviderPlugin
  */
+@Deprecated
 @ProvidesInterface(action = ClockPlugin.ACTION, version = ClockPlugin.VERSION)
 public interface ClockPlugin extends Plugin {
 
diff --git a/packages/SystemUI/res/drawable/ic_media_pause_container.xml b/packages/SystemUI/res/drawable/ic_media_pause_container.xml
index b92e635..ea9eb8c 100644
--- a/packages/SystemUI/res/drawable/ic_media_pause_container.xml
+++ b/packages/SystemUI/res/drawable/ic_media_pause_container.xml
@@ -22,11 +22,6 @@
                 android:viewportHeight="48"

                 android:viewportWidth="48">

             <group android:name="_R_G">

-                <group android:name="_R_G_L_1_G"

-                       android:translateX="24"

-                       android:translateY="24"

-                       android:scaleX="0.5"

-                       android:scaleY="0.5"/>

                 <group android:name="_R_G_L_0_G"

                        android:translateX="24"

                        android:translateY="24"

@@ -46,7 +41,7 @@
         <aapt:attr name="android:animation">

             <set android:ordering="together">

                 <objectAnimator android:propertyName="pathData"

-                                android:duration="500"

+                                android:duration="250"

                                 android:startOffset="0"

                                 android:valueFrom="M48 -16 C48,-16 48,16 48,16 C48,33.67 33.67,48 16,48 C16,48 -16,48 -16,48 C-33.67,48 -48,33.67 -48,16 C-48,16 -48,-16 -48,-16 C-48,-33.67 -33.67,-48 -16,-48 C-16,-48 16,-48 16,-48 C33.67,-48 48,-33.67 48,-16c "

                                 android:valueTo="M48 0.25 C48,0.25 48,0 48,0 C47.75,26 31.25,48 0,48 C0,48 0,48 0,48 C-30,48 -48,25.75 -48,-0.25 C-48,-0.25 -48,-0.25 -48,-0.25 C-47.75,-23.5 -32.25,-47.75 0.5,-48 C0.5,-48 0.5,-48 0.5,-48 C28,-47.75 47.75,-29.75 48,0.25c "

@@ -62,7 +57,7 @@
         <aapt:attr name="android:animation">

             <set android:ordering="together">

                 <objectAnimator android:propertyName="translateX"

-                                android:duration="517"

+                                android:duration="267"

                                 android:startOffset="0"

                                 android:valueFrom="0"

                                 android:valueTo="1"

@@ -70,4 +65,4 @@
             </set>

         </aapt:attr>

     </target>

-</animated-vector>
\ No newline at end of file
+</animated-vector>

diff --git a/packages/SystemUI/res/drawable/ic_media_play_container.xml b/packages/SystemUI/res/drawable/ic_media_play_container.xml
index 2fc9fc8..4cb011a 100644
--- a/packages/SystemUI/res/drawable/ic_media_play_container.xml
+++ b/packages/SystemUI/res/drawable/ic_media_play_container.xml
@@ -41,7 +41,7 @@
         <aapt:attr name="android:animation">

             <set android:ordering="together">

                 <objectAnimator android:propertyName="pathData"

-                                android:duration="500"

+                                android:duration="250"

                                 android:startOffset="0"

                                 android:valueFrom="M48 0.25 C48,0.25 48,0 48,0 C47.75,26 31.25,48 0,48 C0,48 0,48 0,48 C-30,48 -48,25.75 -48,-0.25 C-48,-0.25 -48,-0.25 -48,-0.25 C-47.75,-23.5 -32.25,-47.75 0.5,-48 C0.5,-48 0.5,-48 0.5,-48 C28,-47.75 47.75,-29.75 48,0.25c "

                                 android:valueTo="M48 -16 C48,-16 48,16 48,16 C48,33.67 33.67,48 16,48 C16,48 -16,48 -16,48 C-33.67,48 -48,33.67 -48,16 C-48,16 -48,-16 -48,-16 C-48,-33.67 -33.67,-48 -16,-48 C-16,-48 16,-48 16,-48 C33.67,-48 48,-33.67 48,-16c "

@@ -57,7 +57,7 @@
         <aapt:attr name="android:animation">

             <set android:ordering="together">

                 <objectAnimator android:propertyName="translateX"

-                                android:duration="517"

+                                android:duration="267"

                                 android:startOffset="0"

                                 android:valueFrom="0"

                                 android:valueTo="1"

@@ -65,4 +65,4 @@
             </set>

         </aapt:attr>

     </target>

-</animated-vector>
\ No newline at end of file
+</animated-vector>

diff --git a/packages/SystemUI/res/layout/clipboard_overlay.xml b/packages/SystemUI/res/layout/clipboard_overlay.xml
index 99a5a2e..1a1fc75 100644
--- a/packages/SystemUI/res/layout/clipboard_overlay.xml
+++ b/packages/SystemUI/res/layout/clipboard_overlay.xml
@@ -115,6 +115,7 @@
                   android:autoSizeMinTextSize="@dimen/clipboard_overlay_min_font"
                   android:autoSizeMaxTextSize="@dimen/clipboard_overlay_max_font"
                   android:textColor="?attr/overlayButtonTextColor"
+                  android:textColorLink="?attr/overlayButtonTextColor"
                   android:background="?androidprv:attr/colorAccentSecondary"
                   android:layout_width="@dimen/clipboard_preview_size"
                   android:layout_height="@dimen/clipboard_preview_size"/>
diff --git a/packages/SystemUI/res/layout/keyguard_status_bar.xml b/packages/SystemUI/res/layout/keyguard_status_bar.xml
index e47eed9..d27fa19 100644
--- a/packages/SystemUI/res/layout/keyguard_status_bar.xml
+++ b/packages/SystemUI/res/layout/keyguard_status_bar.xml
@@ -60,9 +60,8 @@
         </com.android.systemui.statusbar.phone.userswitcher.StatusBarUserSwitcherContainer>
 
         <FrameLayout android:id="@+id/system_icons_container"
-            android:layout_width="0dp"
+            android:layout_width="wrap_content"
             android:layout_height="match_parent"
-            android:layout_weight="1"
             android:layout_marginEnd="@dimen/status_bar_padding_end"
             android:gravity="center_vertical|end">
             <include layout="@layout/system_icons" />
diff --git a/packages/SystemUI/res/layout/notification_icon_area.xml b/packages/SystemUI/res/layout/notification_icon_area.xml
index fa696cc..aadfae8 100644
--- a/packages/SystemUI/res/layout/notification_icon_area.xml
+++ b/packages/SystemUI/res/layout/notification_icon_area.xml
@@ -14,18 +14,9 @@
   ~ See the License for the specific language governing permissions and
   ~ limitations under the License
   -->
-<com.android.keyguard.AlphaOptimizedLinearLayout
+<com.android.systemui.statusbar.phone.NotificationIconContainer
     xmlns:android="http://schemas.android.com/apk/res/android"
-    android:id="@+id/notification_icon_area_inner"
-    android:layout_width="match_parent"
+    android:id="@+id/notificationIcons"
+    android:layout_width="wrap_content"
     android:layout_height="match_parent"
-    android:clipChildren="false">
-    <com.android.systemui.statusbar.phone.NotificationIconContainer
-        android:id="@+id/notificationIcons"
-        android:layout_width="match_parent"
-        android:layout_height="match_parent"
-        android:layout_alignParentStart="true"
-        android:gravity="center_vertical"
-        android:orientation="horizontal"
-        android:clipChildren="false"/>
-</com.android.keyguard.AlphaOptimizedLinearLayout>
\ No newline at end of file
+    android:clipChildren="false"/>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/people_space_activity.xml b/packages/SystemUI/res/layout/people_space_activity.xml
index 7102375..f45cc7c 100644
--- a/packages/SystemUI/res/layout/people_space_activity.xml
+++ b/packages/SystemUI/res/layout/people_space_activity.xml
@@ -13,103 +13,11 @@
   ~ See the License for the specific language governing permissions and
   ~ limitations under the License.
   -->
-<LinearLayout
+<FrameLayout
     xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
-    android:id="@+id/top_level"
+    android:id="@+id/container"
     android:layout_width="match_parent"
-    android:layout_height="match_parent"
-    android:orientation="vertical"
-    android:padding="8dp">
-    <TextView
-        android:id="@+id/select_conversation_title"
-        android:text="@string/select_conversation_title"
-        android:gravity="center"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:layout_gravity="center_horizontal"
-        android:textAppearance="@*android:style/TextAppearance.DeviceDefault.ListItem"
-        android:textColor="?android:attr/textColorPrimary"
-        android:textSize="24sp"/>
-
-    <TextView
-        android:id="@+id/select_conversation"
-        android:text="@string/select_conversation_text"
-        android:gravity="center"
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:layout_gravity="center_horizontal"
-        android:textAppearance="@*android:style/TextAppearance.DeviceDefault.ListItem"
-        android:textColor="?android:attr/textColorPrimary"
-        android:textSize="16sp"
-        android:paddingVertical="24dp"
-        android:paddingHorizontal="48dp"/>
-
-    <androidx.core.widget.NestedScrollView
-        android:id="@+id/scroll_view"
-        android:layout_width="match_parent"
-        android:layout_height="match_parent">
-
-        <LinearLayout
-            android:id="@+id/scroll_layout"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:layout_marginTop="16dp"
-            android:orientation="vertical">
-
-            <LinearLayout
-                android:id="@+id/priority"
-                android:orientation="vertical"
-                android:layout_width="match_parent"
-                android:layout_height="wrap_content"
-                android:layout_marginBottom="35dp">
-                <TextView
-                    android:id="@+id/priority_header"
-                    android:text="@string/priority_conversations"
-                    android:layout_width="wrap_content"
-                    android:textAppearance="@*android:style/TextAppearance.DeviceDefault.Notification.Title"
-                    android:textColor="?androidprv:attr/colorAccentPrimaryVariant"
-                    android:textSize="14sp"
-                    android:paddingStart="16dp"
-                    android:layout_height="wrap_content"/>
-
-                <LinearLayout
-                    android:id="@+id/priority_tiles"
-                    android:layout_width="match_parent"
-                    android:layout_height="wrap_content"
-                    android:layout_marginTop="10dp"
-                    android:orientation="vertical"
-                    android:background="@drawable/rounded_bg_full_large_radius"
-                    android:clipToOutline="true">
-                </LinearLayout>
-            </LinearLayout>
-
-            <LinearLayout
-                android:id="@+id/recent"
-                android:orientation="vertical"
-                android:layout_width="match_parent"
-                android:layout_height="wrap_content">
-                <TextView
-                    android:id="@+id/recent_header"
-                    android:gravity="start"
-                    android:text="@string/recent_conversations"
-                    android:layout_width="wrap_content"
-                    android:textAppearance="@*android:style/TextAppearance.DeviceDefault.Notification.Title"
-                    android:textColor="?androidprv:attr/colorAccentPrimaryVariant"
-                    android:textSize="14sp"
-                    android:paddingStart="16dp"
-                    android:layout_height="wrap_content"/>
-
-                <LinearLayout
-                    android:id="@+id/recent_tiles"
-                    android:layout_width="match_parent"
-                    android:layout_height="wrap_content"
-                    android:layout_marginTop="10dp"
-                    android:orientation="vertical"
-                    android:background="@drawable/rounded_bg_full_large_radius"
-                    android:clipToOutline="true">
-                </LinearLayout>
-            </LinearLayout>
-        </LinearLayout>
-    </androidx.core.widget.NestedScrollView>
-</LinearLayout>
\ No newline at end of file
+    android:layout_height="match_parent">
+    <!-- The content of people_space_activity_(no|with)_conversations.xml will be added here at
+         runtime depending on the number of conversations to show. -->
+</FrameLayout>
diff --git a/packages/SystemUI/res/layout/people_space_activity_no_conversations.xml b/packages/SystemUI/res/layout/people_space_activity_no_conversations.xml
index 2e9ff07..e929169 100644
--- a/packages/SystemUI/res/layout/people_space_activity_no_conversations.xml
+++ b/packages/SystemUI/res/layout/people_space_activity_no_conversations.xml
@@ -16,7 +16,7 @@
 <RelativeLayout
     xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
-    android:id="@+id/top_level"
+    android:id="@+id/top_level_no_conversations"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
     android:padding="24dp"
diff --git a/packages/SystemUI/res/layout/people_space_activity_with_conversations.xml b/packages/SystemUI/res/layout/people_space_activity_with_conversations.xml
new file mode 100644
index 0000000..2384963
--- /dev/null
+++ b/packages/SystemUI/res/layout/people_space_activity_with_conversations.xml
@@ -0,0 +1,115 @@
+<!--
+  ~ 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.
+  -->
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+    android:id="@+id/top_level_with_conversations"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:orientation="vertical"
+    android:padding="8dp">
+    <TextView
+        android:id="@+id/select_conversation_title"
+        android:text="@string/select_conversation_title"
+        android:gravity="center"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_gravity="center_horizontal"
+        android:textAppearance="@*android:style/TextAppearance.DeviceDefault.ListItem"
+        android:textColor="?android:attr/textColorPrimary"
+        android:textSize="24sp"/>
+
+    <TextView
+        android:id="@+id/select_conversation"
+        android:text="@string/select_conversation_text"
+        android:gravity="center"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_gravity="center_horizontal"
+        android:textAppearance="@*android:style/TextAppearance.DeviceDefault.ListItem"
+        android:textColor="?android:attr/textColorPrimary"
+        android:textSize="16sp"
+        android:paddingVertical="24dp"
+        android:paddingHorizontal="48dp"/>
+
+    <androidx.core.widget.NestedScrollView
+        android:id="@+id/scroll_view"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent">
+
+        <LinearLayout
+            android:id="@+id/scroll_layout"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_marginTop="16dp"
+            android:orientation="vertical">
+
+            <LinearLayout
+                android:id="@+id/priority"
+                android:orientation="vertical"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_marginBottom="35dp">
+                <TextView
+                    android:id="@+id/priority_header"
+                    android:text="@string/priority_conversations"
+                    android:layout_width="wrap_content"
+                    android:textAppearance="@*android:style/TextAppearance.DeviceDefault.Notification.Title"
+                    android:textColor="?androidprv:attr/colorAccentPrimaryVariant"
+                    android:textSize="14sp"
+                    android:paddingStart="16dp"
+                    android:layout_height="wrap_content"/>
+
+                <LinearLayout
+                    android:id="@+id/priority_tiles"
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:layout_marginTop="10dp"
+                    android:orientation="vertical"
+                    android:background="@drawable/rounded_bg_full_large_radius"
+                    android:clipToOutline="true">
+                </LinearLayout>
+            </LinearLayout>
+
+            <LinearLayout
+                android:id="@+id/recent"
+                android:orientation="vertical"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content">
+                <TextView
+                    android:id="@+id/recent_header"
+                    android:gravity="start"
+                    android:text="@string/recent_conversations"
+                    android:layout_width="wrap_content"
+                    android:textAppearance="@*android:style/TextAppearance.DeviceDefault.Notification.Title"
+                    android:textColor="?androidprv:attr/colorAccentPrimaryVariant"
+                    android:textSize="14sp"
+                    android:paddingStart="16dp"
+                    android:layout_height="wrap_content"/>
+
+                <LinearLayout
+                    android:id="@+id/recent_tiles"
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:layout_marginTop="10dp"
+                    android:orientation="vertical"
+                    android:background="@drawable/rounded_bg_full_large_radius"
+                    android:clipToOutline="true">
+                </LinearLayout>
+            </LinearLayout>
+        </LinearLayout>
+    </androidx.core.widget.NestedScrollView>
+</LinearLayout>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/people_space_tile_view.xml b/packages/SystemUI/res/layout/people_space_tile_view.xml
index 2a2c35d..b0599ca 100644
--- a/packages/SystemUI/res/layout/people_space_tile_view.xml
+++ b/packages/SystemUI/res/layout/people_space_tile_view.xml
@@ -37,8 +37,8 @@
 
             <ImageView
                 android:id="@+id/tile_view_person_icon"
-                android:layout_width="52dp"
-                android:layout_height="52dp" />
+                android:layout_width="@dimen/avatar_size_for_medium"
+                android:layout_height="@dimen/avatar_size_for_medium" />
 
             <LinearLayout
                 android:orientation="horizontal"
diff --git a/packages/SystemUI/res/layout/status_bar.xml b/packages/SystemUI/res/layout/status_bar.xml
index deab1eb..e281511 100644
--- a/packages/SystemUI/res/layout/status_bar.xml
+++ b/packages/SystemUI/res/layout/status_bar.xml
@@ -47,52 +47,63 @@
         android:paddingStart="@dimen/status_bar_padding_start"
         android:paddingEnd="@dimen/status_bar_padding_end"
         android:paddingTop="@dimen/status_bar_padding_top"
-        android:orientation="horizontal"
-        >
+        android:orientation="horizontal">
+
+        <!-- Container for the entire start half of the status bar. It will always use the same
+             width, independent of the number of visible children and sub-children. -->
         <FrameLayout
+            android:id="@+id/status_bar_start_side_container"
             android:layout_height="match_parent"
             android:layout_width="0dp"
             android:layout_weight="1">
 
-            <include layout="@layout/heads_up_status_bar_layout" />
+            <!-- Container that is wrapped around the views on the start half of the status bar.
+                 Its width will change with the number of visible children and sub-children.
+                 It is useful when we want to know the visible bounds of the content. -->
+            <FrameLayout
+                android:id="@+id/status_bar_start_side_content"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:clipChildren="false">
 
-            <!-- The alpha of the left side is controlled by PhoneStatusBarTransitions, and the
-             individual views are controlled by StatusBarManager disable flags DISABLE_CLOCK and
-             DISABLE_NOTIFICATION_ICONS, respectively -->
-            <LinearLayout
-                android:id="@+id/status_bar_left_side"
-                android:layout_height="match_parent"
-                android:layout_width="match_parent"
-                android:clipChildren="false"
-            >
-                <ViewStub
-                    android:id="@+id/operator_name"
-                    android:layout_width="wrap_content"
-                    android:layout_height="match_parent"
-                    android:layout="@layout/operator_name" />
+                <include layout="@layout/heads_up_status_bar_layout" />
 
-                <com.android.systemui.statusbar.policy.Clock
-                    android:id="@+id/clock"
-                    android:layout_width="wrap_content"
-                    android:layout_height="match_parent"
-                    android:textAppearance="@style/TextAppearance.StatusBar.Clock"
-                    android:singleLine="true"
-                    android:paddingStart="@dimen/status_bar_left_clock_starting_padding"
-                    android:paddingEnd="@dimen/status_bar_left_clock_end_padding"
-                    android:gravity="center_vertical|start"
-                />
+                <!-- The alpha of the start side is controlled by PhoneStatusBarTransitions, and the
+                     individual views are controlled by StatusBarManager disable flags DISABLE_CLOCK
+                     and DISABLE_NOTIFICATION_ICONS, respectively -->
+                <LinearLayout
+                    android:id="@+id/status_bar_start_side_except_heads_up"
+                    android:layout_height="wrap_content"
+                    android:layout_width="match_parent"
+                    android:clipChildren="false">
+                    <ViewStub
+                        android:id="@+id/operator_name"
+                        android:layout_width="wrap_content"
+                        android:layout_height="match_parent"
+                        android:layout="@layout/operator_name" />
 
-                <include layout="@layout/ongoing_call_chip" />
+                    <com.android.systemui.statusbar.policy.Clock
+                        android:id="@+id/clock"
+                        android:layout_width="wrap_content"
+                        android:layout_height="match_parent"
+                        android:textAppearance="@style/TextAppearance.StatusBar.Clock"
+                        android:singleLine="true"
+                        android:paddingStart="@dimen/status_bar_left_clock_starting_padding"
+                        android:paddingEnd="@dimen/status_bar_left_clock_end_padding"
+                        android:gravity="center_vertical|start"
+                    />
 
-                <com.android.systemui.statusbar.AlphaOptimizedFrameLayout
-                    android:id="@+id/notification_icon_area"
-                    android:layout_width="0dp"
-                    android:layout_height="match_parent"
-                    android:layout_weight="1"
-                    android:orientation="horizontal"
-                    android:clipChildren="false"/>
+                    <include layout="@layout/ongoing_call_chip" />
 
-            </LinearLayout>
+                    <com.android.systemui.statusbar.AlphaOptimizedFrameLayout
+                        android:id="@+id/notification_icon_area"
+                        android:layout_width="wrap_content"
+                        android:layout_height="match_parent"
+                        android:orientation="horizontal"
+                        android:clipChildren="false"/>
+
+                </LinearLayout>
+            </FrameLayout>
         </FrameLayout>
 
         <!-- Space should cover the notch (if it exists) and let other views lay out around it -->
@@ -103,42 +114,57 @@
             android:gravity="center_horizontal|center_vertical"
         />
 
-        <com.android.keyguard.AlphaOptimizedLinearLayout android:id="@+id/system_icon_area"
+        <!-- Container for the entire end half of the status bar. It will always use the same
+             width, independent of the number of visible children and sub-children. -->
+        <FrameLayout
+            android:id="@+id/status_bar_end_side_container"
             android:layout_width="0dp"
             android:layout_height="match_parent"
             android:layout_weight="1"
-            android:orientation="horizontal"
-            android:gravity="center_vertical|end"
-            >
+            android:clipChildren="false">
 
-            <com.android.systemui.statusbar.phone.userswitcher.StatusBarUserSwitcherContainer
-                android:id="@+id/user_switcher_container"
+            <!-- Container that is wrapped around the views on the end half of the
+                 status bar. Its width will change with the number of visible children and
+                 sub-children.
+                 It is useful when we want know the visible bounds of the content.-->
+            <com.android.keyguard.AlphaOptimizedLinearLayout
+                android:id="@+id/status_bar_end_side_content"
                 android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                android:gravity="center"
+                android:layout_height="match_parent"
+                android:layout_gravity="end"
                 android:orientation="horizontal"
-                android:paddingTop="4dp"
-                android:paddingBottom="4dp"
-                android:paddingStart="8dp"
-                android:paddingEnd="8dp"
-                android:layout_marginEnd="16dp"
-                android:background="@drawable/status_bar_user_chip_bg"
-                android:visibility="visible" >
-                <ImageView android:id="@+id/current_user_avatar"
-                    android:layout_width="@dimen/multi_user_avatar_keyguard_size"
-                    android:layout_height="@dimen/multi_user_avatar_keyguard_size"
-                    android:scaleType="centerInside"
-                    android:paddingEnd="4dp" />
+                android:gravity="center_vertical|end"
+                android:clipChildren="false">
 
-                <TextView android:id="@+id/current_user_name"
+                <com.android.systemui.statusbar.phone.userswitcher.StatusBarUserSwitcherContainer
+                    android:id="@+id/user_switcher_container"
                     android:layout_width="wrap_content"
                     android:layout_height="wrap_content"
-                    android:textAppearance="@style/TextAppearance.StatusBar.Clock"
-                    />
-            </com.android.systemui.statusbar.phone.userswitcher.StatusBarUserSwitcherContainer>
+                    android:gravity="center"
+                    android:orientation="horizontal"
+                    android:paddingTop="4dp"
+                    android:paddingBottom="4dp"
+                    android:paddingStart="8dp"
+                    android:paddingEnd="8dp"
+                    android:layout_marginEnd="16dp"
+                    android:background="@drawable/status_bar_user_chip_bg"
+                    android:visibility="visible" >
+                    <ImageView android:id="@+id/current_user_avatar"
+                        android:layout_width="@dimen/multi_user_avatar_keyguard_size"
+                        android:layout_height="@dimen/multi_user_avatar_keyguard_size"
+                        android:scaleType="centerInside"
+                        android:paddingEnd="4dp" />
 
-            <include layout="@layout/system_icons" />
-        </com.android.keyguard.AlphaOptimizedLinearLayout>
+                    <TextView android:id="@+id/current_user_name"
+                        android:layout_width="wrap_content"
+                        android:layout_height="wrap_content"
+                        android:textAppearance="@style/TextAppearance.StatusBar.Clock"
+                        />
+                </com.android.systemui.statusbar.phone.userswitcher.StatusBarUserSwitcherContainer>
+
+                <include layout="@layout/system_icons" />
+            </com.android.keyguard.AlphaOptimizedLinearLayout>
+        </FrameLayout>
     </LinearLayout>
 
     <ViewStub
diff --git a/packages/SystemUI/screenshot/Android.bp b/packages/SystemUI/screenshot/Android.bp
index 601e92f..f449398 100644
--- a/packages/SystemUI/screenshot/Android.bp
+++ b/packages/SystemUI/screenshot/Android.bp
@@ -38,6 +38,7 @@
         "androidx.test.espresso.core",
         "androidx.appcompat_appcompat",
         "platform-screenshot-diff-core",
+        "guava",
     ],
 
     kotlincflags: ["-Xjvm-default=all"],
diff --git a/packages/SystemUI/screenshot/res/values/themes.xml b/packages/SystemUI/screenshot/res/values/themes.xml
index 40e50bb..a7f8a26 100644
--- a/packages/SystemUI/screenshot/res/values/themes.xml
+++ b/packages/SystemUI/screenshot/res/values/themes.xml
@@ -19,6 +19,12 @@
         <item name="android:windowActionBar">false</item>
         <item name="android:windowNoTitle">true</item>
 
+        <!-- We make the status and navigation bars transparent so that the screenshotted content is
+             not clipped by the status bar height when drawn into the Bitmap (which is what happens
+             given that we draw the view into the Bitmap using hardware acceleration). -->
+        <item name="android:statusBarColor">@android:color/transparent</item>
+        <item name="android:navigationBarColor">@android:color/transparent</item>
+
         <!-- Make sure that device specific cutouts don't impact the outcome of screenshot tests -->
         <item name="android:windowLayoutInDisplayCutoutMode">shortEdges</item>
     </style>
diff --git a/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/Bitmap.kt b/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/Bitmap.kt
index 3d26cda..a4a70a4 100644
--- a/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/Bitmap.kt
+++ b/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/Bitmap.kt
@@ -24,6 +24,8 @@
 import platform.test.screenshot.matchers.PixelPerfectMatcher
 
 /** Draw this [View] into a [Bitmap]. */
+// TODO(b/195673633): Remove this once Compose screenshot tests use hardware rendering for their
+// tests.
 fun View.drawIntoBitmap(): Bitmap {
     val bitmap =
         Bitmap.createBitmap(
diff --git a/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ViewCapture.kt b/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ViewCapture.kt
new file mode 100644
index 0000000..c609e6f
--- /dev/null
+++ b/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ViewCapture.kt
@@ -0,0 +1,180 @@
+package com.android.systemui.testing.screenshot
+
+import android.app.Activity
+import android.content.Context
+import android.content.ContextWrapper
+import android.graphics.Bitmap
+import android.graphics.Canvas
+import android.graphics.Rect
+import android.os.Build
+import android.os.Handler
+import android.os.Looper
+import android.util.Log
+import android.view.PixelCopy
+import android.view.SurfaceView
+import android.view.View
+import android.view.ViewTreeObserver
+import android.view.Window
+import androidx.annotation.RequiresApi
+import androidx.concurrent.futures.ResolvableFuture
+import androidx.test.annotation.ExperimentalTestApi
+import androidx.test.core.internal.os.HandlerExecutor
+import androidx.test.platform.graphics.HardwareRendererCompat
+import com.google.common.util.concurrent.ListenableFuture
+
+/*
+ * This file was forked from androidx/test/core/view/ViewCapture.kt to add [Window] parameter to
+ * [View.captureToBitmap].
+ * TODO(b/195673633): Remove this fork and use the AndroidX version instead.
+ */
+
+/**
+ * Asynchronously captures an image of the underlying view into a [Bitmap].
+ *
+ * For devices below [Build.VERSION_CODES#O] (or if the view's window cannot be determined), the
+ * image is obtained using [View#draw]. Otherwise, [PixelCopy] is used.
+ *
+ * This method will also enable [HardwareRendererCompat#setDrawingEnabled(boolean)] if required.
+ *
+ * This API is primarily intended for use in lower layer libraries or frameworks. For test authors,
+ * its recommended to use espresso or compose's captureToImage.
+ *
+ * This API is currently experimental and subject to change or removal.
+ */
+@ExperimentalTestApi
+@RequiresApi(Build.VERSION_CODES.JELLY_BEAN)
+fun View.captureToBitmap(window: Window? = null): ListenableFuture<Bitmap> {
+    val bitmapFuture: ResolvableFuture<Bitmap> = ResolvableFuture.create()
+    val mainExecutor = HandlerExecutor(Handler(Looper.getMainLooper()))
+
+    // disable drawing again if necessary once work is complete
+    if (!HardwareRendererCompat.isDrawingEnabled()) {
+        HardwareRendererCompat.setDrawingEnabled(true)
+        bitmapFuture.addListener({ HardwareRendererCompat.setDrawingEnabled(false) }, mainExecutor)
+    }
+
+    mainExecutor.execute {
+        val forceRedrawFuture = forceRedraw()
+        forceRedrawFuture.addListener({ generateBitmap(bitmapFuture, window) }, mainExecutor)
+    }
+
+    return bitmapFuture
+}
+
+/**
+ * Trigger a redraw of the given view.
+ *
+ * Should only be called on UI thread.
+ *
+ * @return a [ListenableFuture] that will be complete once ui drawing is complete
+ */
+// NoClassDefFoundError occurs on API 15
+@RequiresApi(Build.VERSION_CODES.JELLY_BEAN)
+// @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+@ExperimentalTestApi
+fun View.forceRedraw(): ListenableFuture<Void> {
+    val future: ResolvableFuture<Void> = ResolvableFuture.create()
+
+    if (Build.VERSION.SDK_INT >= 29 && isHardwareAccelerated) {
+        viewTreeObserver.registerFrameCommitCallback() { future.set(null) }
+    } else {
+        viewTreeObserver.addOnDrawListener(
+            object : ViewTreeObserver.OnDrawListener {
+                var handled = false
+                override fun onDraw() {
+                    if (!handled) {
+                        handled = true
+                        future.set(null)
+                        // cannot remove on draw listener inside of onDraw
+                        Handler(Looper.getMainLooper()).post {
+                            viewTreeObserver.removeOnDrawListener(this)
+                        }
+                    }
+                }
+            }
+        )
+    }
+    invalidate()
+    return future
+}
+
+private fun View.generateBitmap(
+    bitmapFuture: ResolvableFuture<Bitmap>,
+    window: Window? = null,
+) {
+    if (bitmapFuture.isCancelled) {
+        return
+    }
+    val destBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888)
+    when {
+        Build.VERSION.SDK_INT < 26 -> generateBitmapFromDraw(destBitmap, bitmapFuture)
+        this is SurfaceView -> generateBitmapFromSurfaceViewPixelCopy(destBitmap, bitmapFuture)
+        else -> {
+            val window = window ?: getActivity()?.window
+            if (window != null) {
+                generateBitmapFromPixelCopy(window, destBitmap, bitmapFuture)
+            } else {
+                Log.i(
+                    "View.captureToImage",
+                    "Could not find window for view. Falling back to View#draw instead of PixelCopy"
+                )
+                generateBitmapFromDraw(destBitmap, bitmapFuture)
+            }
+        }
+    }
+}
+
+@SuppressWarnings("NewApi")
+private fun SurfaceView.generateBitmapFromSurfaceViewPixelCopy(
+    destBitmap: Bitmap,
+    bitmapFuture: ResolvableFuture<Bitmap>
+) {
+    val onCopyFinished =
+        PixelCopy.OnPixelCopyFinishedListener { result ->
+            if (result == PixelCopy.SUCCESS) {
+                bitmapFuture.set(destBitmap)
+            } else {
+                bitmapFuture.setException(
+                    RuntimeException(String.format("PixelCopy failed: %d", result))
+                )
+            }
+        }
+    PixelCopy.request(this, null, destBitmap, onCopyFinished, handler)
+}
+
+internal fun View.generateBitmapFromDraw(
+    destBitmap: Bitmap,
+    bitmapFuture: ResolvableFuture<Bitmap>
+) {
+    destBitmap.density = resources.displayMetrics.densityDpi
+    computeScroll()
+    val canvas = Canvas(destBitmap)
+    canvas.translate((-scrollX).toFloat(), (-scrollY).toFloat())
+    draw(canvas)
+    bitmapFuture.set(destBitmap)
+}
+
+private fun View.getActivity(): Activity? {
+    fun Context.getActivity(): Activity? {
+        return when (this) {
+            is Activity -> this
+            is ContextWrapper -> this.baseContext.getActivity()
+            else -> null
+        }
+    }
+    return context.getActivity()
+}
+
+private fun View.generateBitmapFromPixelCopy(
+    window: Window,
+    destBitmap: Bitmap,
+    bitmapFuture: ResolvableFuture<Bitmap>
+) {
+    val locationInWindow = intArrayOf(0, 0)
+    getLocationInWindow(locationInWindow)
+    val x = locationInWindow[0]
+    val y = locationInWindow[1]
+    val boundsInWindow = Rect(x, y, x + width, y + height)
+
+    return window.generateBitmapFromPixelCopy(boundsInWindow, destBitmap, bitmapFuture)
+}
diff --git a/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ViewScreenshotTestRule.kt b/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ViewScreenshotTestRule.kt
index 3209c8b..60130e1 100644
--- a/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ViewScreenshotTestRule.kt
+++ b/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ViewScreenshotTestRule.kt
@@ -18,10 +18,22 @@
 
 import android.app.Activity
 import android.app.Dialog
+import android.graphics.Bitmap
+import android.graphics.HardwareRenderer
+import android.os.Looper
 import android.view.View
 import android.view.ViewGroup
 import android.view.ViewGroup.LayoutParams
+import android.view.ViewGroup.LayoutParams.MATCH_PARENT
+import android.view.ViewGroup.LayoutParams.WRAP_CONTENT
+import android.view.Window
+import androidx.activity.ComponentActivity
+import androidx.test.espresso.Espresso
 import androidx.test.ext.junit.rules.ActivityScenarioRule
+import com.google.common.util.concurrent.FutureCallback
+import com.google.common.util.concurrent.Futures
+import kotlin.coroutines.suspendCoroutine
+import kotlinx.coroutines.runBlocking
 import org.junit.Assert.assertEquals
 import org.junit.rules.RuleChain
 import org.junit.rules.TestRule
@@ -59,29 +71,39 @@
      */
     fun screenshotTest(
         goldenIdentifier: String,
-        layoutParams: LayoutParams =
-            LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT),
-        viewProvider: (Activity) -> View,
+        mode: Mode = Mode.WrapContent,
+        viewProvider: (ComponentActivity) -> View,
     ) {
         activityRule.scenario.onActivity { activity ->
             // Make sure that the activity draws full screen and fits the whole display instead of
             // the system bars.
-            activity.window.setDecorFitsSystemWindows(false)
-            activity.setContentView(viewProvider(activity), layoutParams)
+            val window = activity.window
+            window.setDecorFitsSystemWindows(false)
+
+            // Set the content.
+            activity.setContentView(viewProvider(activity), mode.layoutParams)
+
+            // Elevation/shadows is not deterministic when doing hardware rendering, so we disable
+            // it for any view in the hierarchy.
+            window.decorView.removeElevationRecursively()
         }
 
         // We call onActivity again because it will make sure that our Activity is done measuring,
         // laying out and drawing its content (that we set in the previous onActivity lambda).
+        var contentView: View? = null
         activityRule.scenario.onActivity { activity ->
             // Check that the content is what we expected.
             val content = activity.requireViewById<ViewGroup>(android.R.id.content)
             assertEquals(1, content.childCount)
-            screenshotRule.assertBitmapAgainstGolden(
-                content.getChildAt(0).drawIntoBitmap(),
-                goldenIdentifier,
-                matcher
-            )
+            contentView = content.getChildAt(0)
         }
+
+        val bitmap = contentView?.toBitmap() ?: error("contentView is null")
+        screenshotRule.assertBitmapAgainstGolden(
+            bitmap,
+            goldenIdentifier,
+            matcher,
+        )
     }
 
     /**
@@ -104,25 +126,78 @@
                     create()
                     window.setWindowAnimations(0)
 
+                    // Elevation/shadows is not deterministic when doing hardware rendering, so we
+                    // disable it for any view in the hierarchy.
+                    window.decorView.removeElevationRecursively()
+
                     // Show the dialog.
                     show()
                 }
         }
 
-        // We call onActivity again because it will make sure that our Dialog is done measuring,
-        // laying out and drawing its content (that we set in the previous onActivity lambda).
-        activityRule.scenario.onActivity {
-            // Check that the content is what we expected.
-            val dialog = dialog ?: error("dialog is null")
-            try {
-                screenshotRule.assertBitmapAgainstGolden(
-                    dialog.window.decorView.drawIntoBitmap(),
-                    goldenIdentifier,
-                    matcher,
+        try {
+            val bitmap = dialog?.toBitmap() ?: error("dialog is null")
+            screenshotRule.assertBitmapAgainstGolden(
+                bitmap,
+                goldenIdentifier,
+                matcher,
+            )
+        } finally {
+            dialog?.dismiss()
+        }
+    }
+
+    private fun View.removeElevationRecursively() {
+        this.elevation = 0f
+
+        if (this is ViewGroup) {
+            repeat(childCount) { i -> getChildAt(i).removeElevationRecursively() }
+        }
+    }
+
+    private fun Dialog.toBitmap(): Bitmap {
+        val window = window
+        return window.decorView.toBitmap(window)
+    }
+
+    private fun View.toBitmap(window: Window? = null): Bitmap {
+        if (Looper.getMainLooper() == Looper.myLooper()) {
+            error("toBitmap() can't be called from the main thread")
+        }
+
+        if (!HardwareRenderer.isDrawingEnabled()) {
+            error("Hardware rendering is not enabled")
+        }
+
+        // Make sure we are idle.
+        Espresso.onIdle()
+
+        val mainExecutor = context.mainExecutor
+        return runBlocking {
+            suspendCoroutine { continuation ->
+                Futures.addCallback(
+                    captureToBitmap(window),
+                    object : FutureCallback<Bitmap> {
+                        override fun onSuccess(result: Bitmap?) {
+                            continuation.resumeWith(Result.success(result!!))
+                        }
+
+                        override fun onFailure(t: Throwable) {
+                            continuation.resumeWith(Result.failure(t))
+                        }
+                    },
+                    // We know that we are not on the main thread, so we can block the current
+                    // thread and wait for the result in the main thread.
+                    mainExecutor,
                 )
-            } finally {
-                dialog.dismiss()
             }
         }
     }
+
+    enum class Mode(val layoutParams: LayoutParams) {
+        WrapContent(LayoutParams(WRAP_CONTENT, WRAP_CONTENT)),
+        MatchSize(LayoutParams(MATCH_PARENT, MATCH_PARENT)),
+        MatchWidth(LayoutParams(MATCH_PARENT, WRAP_CONTENT)),
+        MatchHeight(LayoutParams(WRAP_CONTENT, MATCH_PARENT)),
+    }
 }
diff --git a/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/WindowCapture.kt b/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/WindowCapture.kt
new file mode 100644
index 0000000..d34f46b
--- /dev/null
+++ b/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/WindowCapture.kt
@@ -0,0 +1,37 @@
+package com.android.systemui.testing.screenshot
+
+import android.graphics.Bitmap
+import android.graphics.Rect
+import android.os.Handler
+import android.os.Looper
+import android.view.PixelCopy
+import android.view.Window
+import androidx.concurrent.futures.ResolvableFuture
+
+/*
+ * This file was forked from androidx/test/core/view/WindowCapture.kt.
+ * TODO(b/195673633): Remove this fork and use the AndroidX version instead.
+ */
+fun Window.generateBitmapFromPixelCopy(
+    boundsInWindow: Rect? = null,
+    destBitmap: Bitmap,
+    bitmapFuture: ResolvableFuture<Bitmap>
+) {
+    val onCopyFinished =
+        PixelCopy.OnPixelCopyFinishedListener { result ->
+            if (result == PixelCopy.SUCCESS) {
+                bitmapFuture.set(destBitmap)
+            } else {
+                bitmapFuture.setException(
+                    RuntimeException(String.format("PixelCopy failed: %d", result))
+                )
+            }
+        }
+    PixelCopy.request(
+        this,
+        boundsInWindow,
+        destBitmap,
+        onCopyFinished,
+        Handler(Looper.getMainLooper())
+    )
+}
diff --git a/packages/SystemUI/shared/Android.bp b/packages/SystemUI/shared/Android.bp
index 9f790c6..114ea65 100644
--- a/packages/SystemUI/shared/Android.bp
+++ b/packages/SystemUI/shared/Android.bp
@@ -50,6 +50,7 @@
         "SystemUIUnfoldLib",
         "androidx.dynamicanimation_dynamicanimation",
         "androidx.concurrent_concurrent-futures",
+        "gson-prebuilt-jar",
         "dagger2",
         "jsr330",
     ],
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/ClockProviderPlugin.kt b/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/ClockProviderPlugin.kt
new file mode 100644
index 0000000..916a557
--- /dev/null
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/ClockProviderPlugin.kt
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+package com.android.systemui.shared.clocks
+
+import com.android.systemui.plugins.Plugin
+import com.android.systemui.plugins.annotations.ProvidesInterface
+import android.annotation.FloatRange
+import android.graphics.drawable.Drawable
+import android.view.View
+
+/** Identifies a clock design */
+typealias ClockId = String
+
+/** A Plugin which exposes the ClockProvider interface */
+@ProvidesInterface(action = ClockProviderPlugin.ACTION, version = ClockProviderPlugin.VERSION)
+interface ClockProviderPlugin : Plugin, ClockProvider {
+    companion object {
+        const val ACTION = "com.android.systemui.action.PLUGIN_CLOCK_PROVIDER"
+        const val VERSION = 1
+    }
+}
+
+/** Interface for building clocks and providing information about those clocks */
+interface ClockProvider {
+    /** Returns metadata for all clocks this provider knows about */
+    fun getClocks(): List<ClockMetadata>
+
+    /** Initializes and returns the target clock design */
+    fun createClock(id: ClockId): Clock
+
+    /** A static thumbnail for rendering in some examples */
+    fun getClockThumbnail(id: ClockId): Drawable?
+}
+
+/** Interface for controlling an active clock */
+interface Clock {
+    /** A small version of the clock, appropriate for smaller viewports */
+    val smallClock: View
+
+    /** A large version of the clock, appropriate when a bigger viewport is available */
+    val largeClock: View
+
+    /** Callback to update the clock view to the current time */
+    fun onTimeTick()
+
+    /** Sets the level of the AOD transition */
+    fun setAodFraction(@FloatRange(from = 0.0, to = 1.0) fraction: Float)
+}
+
+/** Some data about a clock design */
+data class ClockMetadata(
+    val clockId: ClockId,
+    val name: String
+)
\ No newline at end of file
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/ClockRegistry.kt b/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/ClockRegistry.kt
new file mode 100644
index 0000000..3245966
--- /dev/null
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/ClockRegistry.kt
@@ -0,0 +1,146 @@
+/*
+ * 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.shared.clocks
+
+import android.content.Context
+import android.database.ContentObserver
+import android.graphics.drawable.Drawable
+import android.net.Uri
+import android.os.Handler
+import android.os.UserHandle
+import android.provider.Settings
+import android.util.Log
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.plugins.PluginListener
+import com.android.systemui.shared.plugins.PluginManager
+import com.google.gson.Gson
+import javax.inject.Inject
+
+private val TAG = ClockRegistry::class.simpleName
+private val DEBUG = true
+const val DEFAULT_CLOCK_ID = "DEFAULT"
+
+typealias ClockChangeListener = () -> Unit
+
+/** ClockRegistry aggregates providers and plugins */
+open class ClockRegistry @Inject constructor(
+    val context: Context,
+    val pluginManager: PluginManager,
+    @Main val handler: Handler
+) {
+    private val gson = Gson()
+    private val availableClocks = mutableMapOf<ClockId, ClockInfo>()
+    private val clockChangeListeners = mutableListOf<ClockChangeListener>()
+    private val settingObserver = object : ContentObserver(handler) {
+        override fun onChange(selfChange: Boolean, uris: Collection<Uri>, flags: Int, userId: Int) =
+            clockChangeListeners.forEach { it() }
+    }
+
+    private val pluginListener = object : PluginListener<ClockProviderPlugin> {
+        override fun onPluginConnected(plugin: ClockProviderPlugin, context: Context) {
+            val currentId = currentClockId
+            for (clock in plugin.getClocks()) {
+                val id = clock.clockId
+                val current = availableClocks[id]
+                if (current != null) {
+                    Log.e(TAG, "Clock Id conflict: $id is registered by both " +
+                            "${plugin::class.simpleName} and ${current.provider::class.simpleName}")
+                    return
+                }
+
+                availableClocks[id] = ClockInfo(clock, plugin)
+
+                if (currentId == id) {
+                    if (DEBUG) {
+                        Log.i(TAG, "Current clock ($currentId) was connected")
+                    }
+                    clockChangeListeners.forEach { it() }
+                }
+            }
+        }
+
+        override fun onPluginDisconnected(plugin: ClockProviderPlugin) {
+            val currentId = currentClockId
+            for (clock in plugin.getClocks()) {
+                availableClocks.remove(clock.clockId)
+
+                if (currentId == clock.clockId) {
+                    Log.w(TAG, "Current clock ($currentId) was disconnected")
+                    clockChangeListeners.forEach { it() }
+                }
+            }
+        }
+    }
+
+    open var currentClockId: ClockId
+        get() {
+            val json = Settings.Secure.getString(context.contentResolver,
+                Settings.Secure.LOCK_SCREEN_CUSTOM_CLOCK_FACE)
+            return gson.fromJson(json, ClockSetting::class.java).clockId
+        }
+        set(value) {
+            val json = gson.toJson(ClockSetting(value, System.currentTimeMillis()))
+            Settings.Secure.putString(context.contentResolver,
+                Settings.Secure.LOCK_SCREEN_CUSTOM_CLOCK_FACE, json)
+        }
+
+    init {
+        pluginManager.addPluginListener(pluginListener, ClockProviderPlugin::class.java)
+        context.contentResolver.registerContentObserver(
+            Settings.Secure.getUriFor(Settings.Secure.LOCK_SCREEN_CUSTOM_CLOCK_FACE),
+            false,
+            settingObserver,
+            UserHandle.USER_ALL)
+    }
+
+    fun getClocks(): List<ClockMetadata> = availableClocks.map { (_, clock) -> clock.metadata }
+
+    fun getClockThumbnail(clockId: ClockId): Drawable? =
+        availableClocks[clockId]?.provider?.getClockThumbnail(clockId)
+
+    fun createExampleClock(clockId: ClockId): Clock? = createClock(clockId)
+
+    fun registerClockChangeListener(listener: ClockChangeListener) =
+        clockChangeListeners.add(listener)
+
+    fun unregisterClockChangeListener(listener: ClockChangeListener) =
+        clockChangeListeners.remove(listener)
+
+    fun createCurrentClock(): Clock {
+        val clockId = currentClockId
+        if (!clockId.isNullOrEmpty()) {
+            val clock = createClock(clockId)
+            if (clock != null) {
+                return clock
+            } else {
+                Log.e(TAG, "Clock $clockId not found; using default")
+            }
+        }
+
+        return createClock(DEFAULT_CLOCK_ID)!!
+    }
+
+    private fun createClock(clockId: ClockId): Clock? =
+        availableClocks[clockId]?.provider?.createClock(clockId)
+
+    private data class ClockInfo(
+        val metadata: ClockMetadata,
+        val provider: ClockProvider
+    )
+
+    private data class ClockSetting(
+        val clockId: ClockId,
+        val _applied_timestamp: Long
+    )
+}
\ No newline at end of file
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RecentsAnimationControllerCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RecentsAnimationControllerCompat.java
index 13f1db4a..0094820 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RecentsAnimationControllerCompat.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RecentsAnimationControllerCompat.java
@@ -65,14 +65,6 @@
         }
     }
 
-    public void hideCurrentInputMethod() {
-        try {
-            mAnimationController.hideCurrentInputMethod();
-        } catch (RemoteException e) {
-            Log.e(TAG, "Failed to set hide input method", e);
-        }
-    }
-
     /**
      * Sets the final surface transaction on a Task. This is used by Launcher to notify the system
      * that animating Activity to PiP has completed and the associated task surface should be
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 ff2a7a1..609846e 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
@@ -369,10 +369,6 @@
             if (mWrapped != null) mWrapped.setAnimationTargetsBehindSystemBars(behindSystemBars);
         }
 
-        @Override public void hideCurrentInputMethod() {
-            mWrapped.hideCurrentInputMethod();
-        }
-
         @Override public void setFinishTaskTransaction(int taskId,
                 PictureInPictureSurfaceTransaction finishTransaction, SurfaceControl overlay) {
             mPipTransaction = finishTransaction;
diff --git a/packages/SystemUI/src/com/android/keyguard/clock/ClockManager.java b/packages/SystemUI/src/com/android/keyguard/clock/ClockManager.java
index 013cdac..9a0bfc1 100644
--- a/packages/SystemUI/src/com/android/keyguard/clock/ClockManager.java
+++ b/packages/SystemUI/src/com/android/keyguard/clock/ClockManager.java
@@ -53,8 +53,11 @@
 
 /**
  * Manages custom clock faces for AOD and lock screen.
+ *
+ * @deprecated Migrate to ClockRegistry
  */
 @SysUISingleton
+@Deprecated
 public final class ClockManager {
 
     private static final String TAG = "ClockOptsProvider";
diff --git a/packages/SystemUI/src/com/android/systemui/Somnambulator.java b/packages/SystemUI/src/com/android/systemui/Somnambulator.java
index 0dd6d92..25801cf 100644
--- a/packages/SystemUI/src/com/android/systemui/Somnambulator.java
+++ b/packages/SystemUI/src/com/android/systemui/Somnambulator.java
@@ -17,12 +17,15 @@
 package com.android.systemui;
 
 import android.app.Activity;
-import android.content.Intent;
 import android.service.dreams.Sandman;
 
 /**
  * A simple activity that launches a dream.
  * <p>
+ *
+ * This activity has been deprecated and no longer used. The system uses its presence to determine
+ * whether a dock app should be started on dock through intent resolution.
+ *
  * Note: This Activity is special.  If this class is moved to another package or
  * renamed, be sure to update the component name in {@link Sandman}.
  * </p>
@@ -34,27 +37,6 @@
     @Override
     public void onStart() {
         super.onStart();
-
-        final Intent launchIntent = getIntent();
-        final String action = launchIntent.getAction();
-        if (Intent.ACTION_CREATE_SHORTCUT.equals(action)) {
-            Intent shortcutIntent = new Intent(this, Somnambulator.class);
-            shortcutIntent.setFlags(Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS
-                    | Intent.FLAG_ACTIVITY_NEW_TASK);
-            Intent resultIntent = new Intent();
-            resultIntent.putExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE,
-                    Intent.ShortcutIconResource.fromContext(this, R.mipmap.ic_launcher_dreams));
-            resultIntent.putExtra(Intent.EXTRA_SHORTCUT_INTENT, shortcutIntent);
-            resultIntent.putExtra(Intent.EXTRA_SHORTCUT_NAME, getString(R.string.start_dreams));
-            setResult(RESULT_OK, resultIntent);
-        } else {
-            boolean docked = launchIntent.hasCategory(Intent.CATEGORY_DESK_DOCK);
-            if (docked) {
-                Sandman.startDreamWhenDockedIfAppropriate(this);
-            } else {
-                Sandman.startDreamByUserRequest(this);
-            }
-        }
         finish();
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIInitializer.java b/packages/SystemUI/src/com/android/systemui/SystemUIInitializer.java
index 08096b0..24fcf15 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIInitializer.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIInitializer.java
@@ -96,16 +96,12 @@
                     .setSplitScreen(mWMComponent.getSplitScreen())
                     .setOneHanded(mWMComponent.getOneHanded())
                     .setBubbles(mWMComponent.getBubbles())
-                    .setHideDisplayCutout(mWMComponent.getHideDisplayCutout())
                     .setShellCommandHandler(mWMComponent.getShellCommandHandler())
                     .setTaskViewFactory(mWMComponent.getTaskViewFactory())
                     .setTransitions(mWMComponent.getTransitions())
                     .setStartingSurface(mWMComponent.getStartingSurface())
                     .setDisplayAreaHelper(mWMComponent.getDisplayAreaHelper())
-                    .setTaskSurfaceHelper(mWMComponent.getTaskSurfaceHelper())
                     .setRecentTasks(mWMComponent.getRecentTasks())
-                    .setCompatUI(mWMComponent.getCompatUI())
-                    .setDragAndDrop(mWMComponent.getDragAndDrop())
                     .setBackAnimation(mWMComponent.getBackAnimation());
         } else {
             // TODO: Call on prepareSysUIComponentBuilder but not with real components. Other option
@@ -116,16 +112,12 @@
                     .setSplitScreen(Optional.ofNullable(null))
                     .setOneHanded(Optional.ofNullable(null))
                     .setBubbles(Optional.ofNullable(null))
-                    .setHideDisplayCutout(Optional.ofNullable(null))
                     .setShellCommandHandler(Optional.ofNullable(null))
                     .setTaskViewFactory(Optional.ofNullable(null))
                     .setTransitions(new ShellTransitions() {})
                     .setDisplayAreaHelper(Optional.ofNullable(null))
                     .setStartingSurface(Optional.ofNullable(null))
-                    .setTaskSurfaceHelper(Optional.ofNullable(null))
                     .setRecentTasks(Optional.ofNullable(null))
-                    .setCompatUI(Optional.ofNullable(null))
-                    .setDragAndDrop(Optional.ofNullable(null))
                     .setBackAnimation(Optional.ofNullable(null));
         }
         mSysUIComponent = builder.build();
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuView.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuView.java
index d2703f5..aff0b1f 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuView.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuView.java
@@ -353,6 +353,7 @@
         }
 
         mIsShowing = false;
+        mDragAnimator.cancel();
         mWindowManager.removeView(this);
 
         setOnApplyWindowInsetsListener(null);
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
index fb502e5..cf50f7f 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
@@ -609,7 +609,7 @@
             @NonNull SystemUIDialogManager dialogManager,
             @NonNull LatencyTracker latencyTracker,
             @NonNull ActivityLaunchAnimator activityLaunchAnimator,
-            @NonNull Optional<AlternateUdfpsTouchProvider> aternateTouchProvider,
+            @NonNull Optional<AlternateUdfpsTouchProvider> alternateTouchProvider,
             @BiometricsBackground Executor biometricsExecutor) {
         mContext = context;
         mExecution = execution;
@@ -639,7 +639,7 @@
         mUnlockedScreenOffAnimationController = unlockedScreenOffAnimationController;
         mLatencyTracker = latencyTracker;
         mActivityLaunchAnimator = activityLaunchAnimator;
-        mAlternateTouchProvider = aternateTouchProvider.orElse(null);
+        mAlternateTouchProvider = alternateTouchProvider.orElse(null);
         mBiometricExecutor = biometricsExecutor;
 
         mOrientationListener = new BiometricDisplayListener(
diff --git a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt
index bed553e..50ce9d4 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt
@@ -20,13 +20,11 @@
 import android.app.backup.BackupManager
 import android.content.BroadcastReceiver
 import android.content.ComponentName
-import android.content.ContentResolver
 import android.content.Context
 import android.content.Intent
 import android.content.IntentFilter
 import android.database.ContentObserver
 import android.net.Uri
-import android.os.Environment
 import android.os.UserHandle
 import android.service.controls.Control
 import android.service.controls.actions.ControlAction
@@ -43,6 +41,7 @@
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.dump.DumpManager
+import com.android.systemui.settings.UserFileManager
 import com.android.systemui.settings.UserTracker
 import com.android.systemui.statusbar.policy.DeviceControlsControllerImpl.Companion.PREFS_CONTROLS_FILE
 import com.android.systemui.statusbar.policy.DeviceControlsControllerImpl.Companion.PREFS_CONTROLS_SEEDING_COMPLETED
@@ -61,6 +60,7 @@
     private val bindingController: ControlsBindingController,
     private val listingController: ControlsListingController,
     private val broadcastDispatcher: BroadcastDispatcher,
+    private val userFileManager: UserFileManager,
     optionalWrapper: Optional<ControlsFavoritePersistenceWrapper>,
     dumpManager: DumpManager,
     userTracker: UserTracker
@@ -84,15 +84,12 @@
     override val currentUserId
         get() = currentUser.identifier
 
-    private val contentResolver: ContentResolver
-        get() = context.contentResolver
-
     private val persistenceWrapper: ControlsFavoritePersistenceWrapper
     @VisibleForTesting
     internal var auxiliaryPersistenceWrapper: AuxiliaryPersistenceWrapper
 
     init {
-        userStructure = UserStructure(context, currentUser)
+        userStructure = UserStructure(context, currentUser, userFileManager)
 
         persistenceWrapper = optionalWrapper.orElseGet {
             ControlsFavoritePersistenceWrapper(
@@ -111,7 +108,7 @@
     private fun setValuesForUser(newUser: UserHandle) {
         Log.d(TAG, "Changing to user: $newUser")
         currentUser = newUser
-        userStructure = UserStructure(context, currentUser)
+        userStructure = UserStructure(context, currentUser, userFileManager)
         persistenceWrapper.changeFileAndBackupManager(
                 userStructure.file,
                 BackupManager(userStructure.userContext)
@@ -187,8 +184,11 @@
 
                 // When a component is uninstalled, allow seeding to happen again if the user
                 // reinstalls the app
-                val prefs = userStructure.userContext.getSharedPreferences(
-                    PREFS_CONTROLS_FILE, Context.MODE_PRIVATE)
+                val prefs = userFileManager.getSharedPreferences(
+                    PREFS_CONTROLS_FILE,
+                    Context.MODE_PRIVATE,
+                    userTracker.userId
+                )
                 val completedSeedingPackageSet = prefs.getStringSet(
                     PREFS_CONTROLS_SEEDING_COMPLETED, mutableSetOf<String>())
                 val servicePackageSet = serviceInfoSet.map { it.packageName }
@@ -575,18 +575,12 @@
     }
 }
 
-class UserStructure(context: Context, user: UserHandle) {
+class UserStructure(context: Context, user: UserHandle, userFileManager: UserFileManager) {
     val userContext = context.createContextAsUser(user, 0)
-
-    val file = Environment.buildPath(
-            userContext.filesDir,
-            ControlsFavoritePersistenceWrapper.FILE_NAME
-    )
-
-    val auxiliaryFile = Environment.buildPath(
-            userContext.filesDir,
-            AuxiliaryPersistenceWrapper.AUXILIARY_FILE_NAME
-    )
+    val file = userFileManager.getFile(ControlsFavoritePersistenceWrapper.FILE_NAME,
+        user.identifier)
+    val auxiliaryFile = userFileManager.getFile(AuxiliaryPersistenceWrapper.AUXILIARY_FILE_NAME,
+        user.identifier)
 }
 
 /**
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java b/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java
index adeafd5..fd7680f 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java
@@ -41,17 +41,13 @@
 import com.android.wm.shell.TaskViewFactory;
 import com.android.wm.shell.back.BackAnimation;
 import com.android.wm.shell.bubbles.Bubbles;
-import com.android.wm.shell.compatui.CompatUI;
 import com.android.wm.shell.displayareahelper.DisplayAreaHelper;
-import com.android.wm.shell.draganddrop.DragAndDrop;
-import com.android.wm.shell.hidedisplaycutout.HideDisplayCutout;
 import com.android.wm.shell.onehanded.OneHanded;
 import com.android.wm.shell.pip.Pip;
 import com.android.wm.shell.recents.RecentTasks;
 import com.android.wm.shell.splitscreen.SplitScreen;
 import com.android.wm.shell.startingsurface.StartingSurface;
 import com.android.wm.shell.sysui.ShellInterface;
-import com.android.wm.shell.tasksurfacehelper.TaskSurfaceHelper;
 import com.android.wm.shell.transition.ShellTransitions;
 
 import java.util.Map;
@@ -100,9 +96,6 @@
         Builder setTaskViewFactory(Optional<TaskViewFactory> t);
 
         @BindsInstance
-        Builder setHideDisplayCutout(Optional<HideDisplayCutout> h);
-
-        @BindsInstance
         Builder setShellCommandHandler(Optional<ShellCommandHandler> shellDump);
 
         @BindsInstance
@@ -115,18 +108,9 @@
         Builder setDisplayAreaHelper(Optional<DisplayAreaHelper> h);
 
         @BindsInstance
-        Builder setTaskSurfaceHelper(Optional<TaskSurfaceHelper> t);
-
-        @BindsInstance
         Builder setRecentTasks(Optional<RecentTasks> r);
 
         @BindsInstance
-        Builder setCompatUI(Optional<CompatUI> s);
-
-        @BindsInstance
-        Builder setDragAndDrop(Optional<DragAndDrop> d);
-
-        @BindsInstance
         Builder setBackAnimation(Optional<BackAnimation> b);
 
         SysUIComponent build();
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
index 0e0073a..fe96222 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
@@ -45,6 +45,7 @@
 import com.android.systemui.media.dagger.MediaProjectionModule;
 import com.android.systemui.model.SysUiState;
 import com.android.systemui.navigationbar.NavigationBarComponent;
+import com.android.systemui.people.PeopleModule;
 import com.android.systemui.plugins.BcSmartspaceDataPlugin;
 import com.android.systemui.privacy.PrivacyModule;
 import com.android.systemui.recents.Recents;
@@ -124,6 +125,7 @@
             LogModule.class,
             MediaProjectionModule.class,
             PeopleHubModule.class,
+            PeopleModule.class,
             PluginModule.class,
             PrivacyModule.class,
             QsFrameTranslateModule.class,
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java b/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java
index b6003e9..e4c0325 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java
@@ -29,20 +29,16 @@
 import com.android.wm.shell.back.BackAnimation;
 import com.android.wm.shell.bubbles.Bubbles;
 import com.android.wm.shell.common.annotations.ShellMainThread;
-import com.android.wm.shell.compatui.CompatUI;
 import com.android.wm.shell.dagger.TvWMShellModule;
 import com.android.wm.shell.dagger.WMShellModule;
 import com.android.wm.shell.dagger.WMSingleton;
 import com.android.wm.shell.displayareahelper.DisplayAreaHelper;
-import com.android.wm.shell.draganddrop.DragAndDrop;
-import com.android.wm.shell.hidedisplaycutout.HideDisplayCutout;
 import com.android.wm.shell.onehanded.OneHanded;
 import com.android.wm.shell.pip.Pip;
 import com.android.wm.shell.recents.RecentTasks;
 import com.android.wm.shell.splitscreen.SplitScreen;
 import com.android.wm.shell.startingsurface.StartingSurface;
 import com.android.wm.shell.sysui.ShellInterface;
-import com.android.wm.shell.tasksurfacehelper.TaskSurfaceHelper;
 import com.android.wm.shell.transition.ShellTransitions;
 
 import java.util.Optional;
@@ -104,9 +100,6 @@
     Optional<Bubbles> getBubbles();
 
     @WMSingleton
-    Optional<HideDisplayCutout> getHideDisplayCutout();
-
-    @WMSingleton
     Optional<TaskViewFactory> getTaskViewFactory();
 
     @WMSingleton
@@ -119,17 +112,8 @@
     Optional<DisplayAreaHelper> getDisplayAreaHelper();
 
     @WMSingleton
-    Optional<TaskSurfaceHelper> getTaskSurfaceHelper();
-
-    @WMSingleton
     Optional<RecentTasks> getRecentTasks();
 
     @WMSingleton
-    Optional<CompatUI> getCompatUI();
-
-    @WMSingleton
-    Optional<DragAndDrop> getDragAndDrop();
-
-    @WMSingleton
     Optional<BackAnimation> getBackAnimation();
 }
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/AirQualityColorPicker.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/AirQualityColorPicker.java
deleted file mode 100644
index 328753f..0000000
--- a/packages/SystemUI/src/com/android/systemui/dreams/complication/AirQualityColorPicker.java
+++ /dev/null
@@ -1,70 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.dreams.complication;
-
-import static com.android.systemui.dreams.complication.dagger.DreamAirQualityComplicationComponent.DreamAirQualityComplicationModule.DREAM_AQI_COLOR_DEFAULT;
-import static com.android.systemui.dreams.complication.dagger.DreamAirQualityComplicationComponent.DreamAirQualityComplicationModule.DREAM_AQI_COLOR_THRESHOLDS;
-import static com.android.systemui.dreams.complication.dagger.DreamAirQualityComplicationComponent.DreamAirQualityComplicationModule.DREAM_AQI_COLOR_VALUES;
-
-import android.util.Log;
-
-import androidx.annotation.ColorInt;
-
-import javax.inject.Inject;
-import javax.inject.Named;
-
-final class AirQualityColorPicker {
-    private static final String TAG = "AirQualityColorPicker";
-    private final int[] mThresholds;
-    private final int[] mColorValues;
-    private final int mDefaultColor;
-
-    @Inject
-    AirQualityColorPicker(@Named(DREAM_AQI_COLOR_THRESHOLDS) int[] thresholds,
-            @Named(DREAM_AQI_COLOR_VALUES) int[] colorValues,
-            @Named(DREAM_AQI_COLOR_DEFAULT) @ColorInt int defaultColor) {
-        mThresholds = thresholds;
-        mColorValues = colorValues;
-        mDefaultColor = defaultColor;
-    }
-
-    @ColorInt
-    int getColorForValue(String aqiString) {
-        int size = mThresholds.length;
-        if (mThresholds.length != mColorValues.length) {
-            size = Math.min(mThresholds.length, mColorValues.length);
-            Log.e(TAG,
-                    "Threshold size ("
-                            + mThresholds.length + ") does not match color value size ("
-                            + mColorValues.length
-                            + "). Taking the minimum, some values may be ignored.");
-
-        }
-        try {
-            final int value = Integer.parseInt(aqiString.replaceAll("[^0-9]", ""));
-            for (int i = size - 1; i >= 0; i--) {
-                if (value > mThresholds[i]) {
-                    return mColorValues[i];
-                }
-            }
-            Log.e(TAG, "No matching AQI color for value: " + value);
-        } catch (NumberFormatException e) {
-            Log.e(TAG, "Could not read AQI value from:" + aqiString);
-        }
-        return mDefaultColor;
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/DreamAirQualityComplication.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/DreamAirQualityComplication.java
deleted file mode 100644
index ba63303..0000000
--- a/packages/SystemUI/src/com/android/systemui/dreams/complication/DreamAirQualityComplication.java
+++ /dev/null
@@ -1,199 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.dreams.complication;
-
-import static com.android.systemui.dreams.complication.dagger.DreamAirQualityComplicationComponent.DreamAirQualityComplicationModule.DREAM_AQI_COMPLICATION_LAYOUT_PARAMS;
-import static com.android.systemui.dreams.complication.dagger.DreamAirQualityComplicationComponent.DreamAirQualityComplicationModule.DREAM_AQI_COMPLICATION_VIEW;
-import static com.android.systemui.dreams.complication.dagger.RegisteredComplicationsModule.SMARTSPACE_TRAMPOLINE_ACTIVITY_COMPONENT;
-
-import android.app.smartspace.SmartspaceAction;
-import android.app.smartspace.SmartspaceTarget;
-import android.content.Context;
-import android.content.Intent;
-import android.graphics.drawable.Drawable;
-import android.graphics.drawable.GradientDrawable;
-import android.graphics.drawable.ShapeDrawable;
-import android.text.TextUtils;
-import android.view.View;
-import android.widget.TextView;
-
-import androidx.annotation.Nullable;
-
-import com.android.systemui.CoreStartable;
-import com.android.systemui.dreams.DreamOverlayStateController;
-import com.android.systemui.dreams.complication.dagger.DreamAirQualityComplicationComponent;
-import com.android.systemui.dreams.smartspace.DreamSmartspaceController;
-import com.android.systemui.plugins.ActivityStarter;
-import com.android.systemui.plugins.BcSmartspaceDataPlugin.SmartspaceTargetListener;
-import com.android.systemui.util.ViewController;
-
-import javax.inject.Inject;
-import javax.inject.Named;
-
-/**
- * Air quality complication which produces view holder responsible for showing AQI over dreams.
- */
-public class DreamAirQualityComplication implements Complication {
-    // TODO(b/236024839): Move to SmartspaceTarget
-    public static final int FEATURE_AIR_QUALITY = 46;
-
-    private final DreamAirQualityComplicationComponent.Factory mComponentFactory;
-
-    @Inject
-    public DreamAirQualityComplication(
-            DreamAirQualityComplicationComponent.Factory componentFactory) {
-        mComponentFactory = componentFactory;
-    }
-
-    @Override
-    public int getRequiredTypeAvailability() {
-        return COMPLICATION_TYPE_AIR_QUALITY;
-    }
-
-    @Override
-    public ViewHolder createView(ComplicationViewModel model) {
-        return mComponentFactory.create().getViewHolder();
-    }
-
-    /**
-     * {@link CoreStartable} for registering {@link DreamAirQualityComplication} with SystemUI.
-     */
-    public static class Registrant extends CoreStartable {
-        private final DreamOverlayStateController mDreamOverlayStateController;
-        private final DreamAirQualityComplication mComplication;
-
-        /**
-         * Default constructor to register {@link DreamAirQualityComplication}.
-         */
-        @Inject
-        public Registrant(Context context,
-                DreamOverlayStateController dreamOverlayStateController,
-                DreamAirQualityComplication complication) {
-            super(context);
-            mDreamOverlayStateController = dreamOverlayStateController;
-            mComplication = complication;
-        }
-
-        @Override
-        public void start() {
-            // TODO(b/221500478): Only add complication once we have data to show.
-            mDreamOverlayStateController.addComplication(mComplication);
-        }
-    }
-
-    /**
-     * ViewHolder to contain value/logic associated with the AQI complication view.
-     */
-    public static class DreamAirQualityViewHolder implements ViewHolder {
-        private final TextView mView;
-        private final DreamAirQualityViewController mController;
-        private final ComplicationLayoutParams mLayoutParams;
-
-        @Inject
-        DreamAirQualityViewHolder(@Named(DREAM_AQI_COMPLICATION_VIEW) TextView view,
-                DreamAirQualityViewController controller,
-                @Named(DREAM_AQI_COMPLICATION_LAYOUT_PARAMS)
-                        ComplicationLayoutParams layoutParams) {
-            mView = view;
-            mLayoutParams = layoutParams;
-            mController = controller;
-            mController.init();
-        }
-
-        @Override
-        public View getView() {
-            return mView;
-        }
-
-        @Override
-        public ComplicationLayoutParams getLayoutParams() {
-            return mLayoutParams;
-        }
-    }
-
-    static class DreamAirQualityViewController extends ViewController<TextView> {
-        private final DreamSmartspaceController mSmartspaceController;
-        private final String mSmartspaceTrampolineComponent;
-        private final ActivityStarter mActivityStarter;
-        private final AirQualityColorPicker mAirQualityColorPicker;
-
-        private final SmartspaceTargetListener mSmartspaceTargetListener = targets -> {
-            final SmartspaceTarget target = targets.stream()
-                    .filter(t -> t instanceof SmartspaceTarget)
-                    .map(t -> (SmartspaceTarget) t)
-                    .filter(t -> t.getFeatureType() == FEATURE_AIR_QUALITY)
-                    .findFirst()
-                    .orElse(null);
-            updateView(target);
-        };
-
-        @Inject
-        DreamAirQualityViewController(@Named(DREAM_AQI_COMPLICATION_VIEW) TextView view,
-                DreamSmartspaceController smartspaceController,
-                @Named(SMARTSPACE_TRAMPOLINE_ACTIVITY_COMPONENT)
-                        String smartspaceTrampolineComponent,
-                ActivityStarter activityStarter,
-                AirQualityColorPicker airQualityColorPicker) {
-            super(view);
-            mSmartspaceController = smartspaceController;
-            mSmartspaceTrampolineComponent = smartspaceTrampolineComponent;
-            mActivityStarter = activityStarter;
-            mAirQualityColorPicker = airQualityColorPicker;
-        }
-
-        @Override
-        protected void onViewAttached() {
-            mSmartspaceController.addUnfilteredListener(mSmartspaceTargetListener);
-        }
-
-        @Override
-        protected void onViewDetached() {
-            mSmartspaceController.removeUnfilteredListener(mSmartspaceTargetListener);
-        }
-
-        private void updateView(@Nullable SmartspaceTarget target) {
-            final SmartspaceAction headerAction = target == null ? null : target.getHeaderAction();
-            if (headerAction == null || TextUtils.isEmpty(headerAction.getTitle())) {
-                mView.setVisibility(View.GONE);
-                return;
-            }
-            mView.setVisibility(View.VISIBLE);
-
-            final String airQuality = headerAction.getTitle().toString();
-            mView.setText(airQuality);
-
-            final Drawable background = mView.getBackground().mutate();
-            final int color = mAirQualityColorPicker.getColorForValue(airQuality);
-
-            if (background instanceof ShapeDrawable) {
-                ((ShapeDrawable) background).getPaint().setColor(color);
-            } else if (background instanceof GradientDrawable) {
-                ((GradientDrawable) background).setColor(color);
-            }
-            mView.setBackground(background);
-
-            final Intent intent = headerAction.getIntent();
-            if (intent != null && intent.getComponent() != null
-                    && intent.getComponent().getClassName().equals(
-                    mSmartspaceTrampolineComponent)) {
-                mView.setOnClickListener(v -> {
-                    mActivityStarter.postStartActivityDismissingKeyguard(intent, /* delay= */ 0);
-                });
-            }
-        }
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/DreamWeatherComplication.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/DreamWeatherComplication.java
deleted file mode 100644
index ce61b16..0000000
--- a/packages/SystemUI/src/com/android/systemui/dreams/complication/DreamWeatherComplication.java
+++ /dev/null
@@ -1,221 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.dreams.complication;
-
-import static com.android.systemui.dreams.complication.dagger.DreamWeatherComplicationComponent.DreamWeatherComplicationModule.DREAM_WEATHER_COMPLICATION_LAYOUT_PARAMS;
-import static com.android.systemui.dreams.complication.dagger.DreamWeatherComplicationComponent.DreamWeatherComplicationModule.DREAM_WEATHER_COMPLICATION_VIEW;
-import static com.android.systemui.dreams.complication.dagger.RegisteredComplicationsModule.SMARTSPACE_TRAMPOLINE_ACTIVITY_COMPONENT;
-
-import android.app.smartspace.SmartspaceAction;
-import android.app.smartspace.SmartspaceTarget;
-import android.content.Context;
-import android.content.Intent;
-import android.content.res.Resources;
-import android.graphics.drawable.Drawable;
-import android.graphics.drawable.Icon;
-import android.text.TextUtils;
-import android.widget.TextView;
-
-import com.android.systemui.CoreStartable;
-import com.android.systemui.R;
-import com.android.systemui.dreams.DreamOverlayStateController;
-import com.android.systemui.dreams.complication.dagger.DreamWeatherComplicationComponent;
-import com.android.systemui.dreams.smartspace.DreamSmartspaceController;
-import com.android.systemui.plugins.ActivityStarter;
-import com.android.systemui.plugins.BcSmartspaceDataPlugin.SmartspaceTargetListener;
-import com.android.systemui.util.ViewController;
-
-import javax.inject.Inject;
-import javax.inject.Named;
-
-/**
- * Weather Complication that produce Weather view holder.
- */
-public class DreamWeatherComplication implements Complication {
-    DreamWeatherComplicationComponent.Factory mComponentFactory;
-
-    /**
-     * Default constructor for {@link DreamWeatherComplication}.
-     */
-    @Inject
-    public DreamWeatherComplication(
-            DreamWeatherComplicationComponent.Factory componentFactory) {
-        mComponentFactory = componentFactory;
-    }
-
-    @Override
-    public int getRequiredTypeAvailability() {
-        return COMPLICATION_TYPE_WEATHER;
-    }
-
-    /**
-     * Create {@link DreamWeatherViewHolder}.
-     */
-    @Override
-    public ViewHolder createView(ComplicationViewModel model) {
-        return mComponentFactory.create().getViewHolder();
-    }
-
-    /**
-     * {@link CoreStartable} for registering {@link DreamWeatherComplication} with SystemUI.
-     */
-    public static class Registrant extends CoreStartable {
-        private final DreamOverlayStateController mDreamOverlayStateController;
-        private final DreamWeatherComplication mComplication;
-
-        /**
-         * Default constructor to register {@link DreamWeatherComplication}.
-         */
-        @Inject
-        public Registrant(Context context,
-                DreamOverlayStateController dreamOverlayStateController,
-                DreamWeatherComplication dreamWeatherComplication) {
-            super(context);
-            mDreamOverlayStateController = dreamOverlayStateController;
-            mComplication = dreamWeatherComplication;
-        }
-
-        @Override
-        public void start() {
-            mDreamOverlayStateController.addComplication(mComplication);
-        }
-    }
-
-    /**
-     * ViewHolder to contain value/logic associated with a Weather Complication View.
-     */
-    public static class DreamWeatherViewHolder implements ViewHolder {
-        private final TextView mView;
-        private final ComplicationLayoutParams mLayoutParams;
-        private final DreamWeatherViewController mViewController;
-
-        @Inject
-        DreamWeatherViewHolder(
-                @Named(DREAM_WEATHER_COMPLICATION_VIEW) TextView view,
-                DreamWeatherViewController controller,
-                @Named(DREAM_WEATHER_COMPLICATION_LAYOUT_PARAMS)
-                        ComplicationLayoutParams layoutParams) {
-            mView = view;
-            mLayoutParams = layoutParams;
-            mViewController = controller;
-            mViewController.init();
-        }
-
-        @Override
-        public TextView getView() {
-            return mView;
-        }
-
-        @Override
-        public ComplicationLayoutParams getLayoutParams() {
-            return mLayoutParams;
-        }
-    }
-
-    /**
-     * ViewController to contain value/logic associated with a Weather Complication View.
-     */
-    static class DreamWeatherViewController extends ViewController<TextView> {
-        private final DreamSmartspaceController mSmartSpaceController;
-        private final ActivityStarter mActivityStarter;
-        private final String mSmartspaceTrampolineActivityComponent;
-        private SmartspaceTargetListener mSmartspaceTargetListener;
-        private final Resources mResources;
-
-        @Inject
-        DreamWeatherViewController(
-                @Named(DREAM_WEATHER_COMPLICATION_VIEW) TextView view,
-                @Named(SMARTSPACE_TRAMPOLINE_ACTIVITY_COMPONENT) String smartspaceTrampoline,
-                ActivityStarter activityStarter,
-                DreamSmartspaceController smartspaceController,
-                Resources resources
-        ) {
-            super(view);
-            mActivityStarter = activityStarter;
-            mResources = resources;
-            mSmartSpaceController = smartspaceController;
-            mSmartspaceTrampolineActivityComponent = smartspaceTrampoline;
-        }
-
-        @Override
-        protected void onViewAttached() {
-            mSmartspaceTargetListener = targets -> targets.forEach(
-                    t -> {
-                        if (t instanceof SmartspaceTarget
-                                && ((SmartspaceTarget) t).getFeatureType()
-                                == SmartspaceTarget.FEATURE_WEATHER) {
-                            final SmartspaceTarget target = (SmartspaceTarget) t;
-                            final SmartspaceAction headerAction = target.getHeaderAction();
-                            if (headerAction == null || TextUtils.isEmpty(
-                                    headerAction.getTitle())) {
-                                return;
-                            }
-
-                            final CharSequence temperature = headerAction.getTitle();
-                            mView.setText(temperature);
-                            mView.setContentDescription(getFormattedContentDescription(temperature,
-                                    headerAction.getContentDescription()));
-                            final Icon icon = headerAction.getIcon();
-                            if (icon != null) {
-                                final int iconSize =
-                                        getResources().getDimensionPixelSize(
-                                                R.dimen.smart_action_button_icon_size);
-                                final Drawable iconDrawable = icon.loadDrawable(getContext());
-                                iconDrawable.setBounds(0, 0, iconSize, iconSize);
-                                mView.setCompoundDrawables(iconDrawable, null, null, null);
-                                mView.setCompoundDrawablePadding(
-                                        getResources().getDimensionPixelSize(
-                                                R.dimen.smart_action_button_icon_padding));
-                            }
-                            mView.setOnClickListener(v -> {
-                                final Intent intent = headerAction.getIntent();
-                                if (intent != null && intent.getComponent() != null
-                                        && intent.getComponent().getClassName()
-                                        .equals(mSmartspaceTrampolineActivityComponent)) {
-                                    mActivityStarter.postStartActivityDismissingKeyguard(
-                                            intent, 0 /*delay*/);
-                                }
-                            });
-                        }
-                    });
-            // We need to use an unfiltered listener here since weather is filtered from showing
-            // in the dream smartspace.
-            mSmartSpaceController.addUnfilteredListener(mSmartspaceTargetListener);
-        }
-
-        @Override
-        protected void onViewDetached() {
-            mSmartSpaceController.removeUnfilteredListener(mSmartspaceTargetListener);
-        }
-
-        /**
-         * Returns a formatted content description for accessibility of the weather condition and
-         * temperature.
-         */
-        private CharSequence getFormattedContentDescription(CharSequence temperature,
-                CharSequence weatherCondition) {
-            if (TextUtils.isEmpty(temperature)) {
-                return weatherCondition;
-            } else if (TextUtils.isEmpty(weatherCondition)) {
-                return temperature;
-            }
-
-            return mResources.getString(R.string.dream_overlay_weather_complication_desc,
-                    weatherCondition, temperature);
-        }
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/DreamAirQualityComplicationComponent.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/DreamAirQualityComplicationComponent.java
deleted file mode 100644
index 112a1ce..0000000
--- a/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/DreamAirQualityComplicationComponent.java
+++ /dev/null
@@ -1,137 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.dreams.complication.dagger;
-
-import android.content.Context;
-import android.content.res.Resources;
-import android.view.LayoutInflater;
-import android.view.ViewGroup;
-import android.widget.TextView;
-
-import androidx.annotation.ColorInt;
-
-import com.android.systemui.R;
-import com.android.systemui.dagger.qualifiers.Main;
-import com.android.systemui.dreams.complication.ComplicationLayoutParams;
-import com.android.systemui.dreams.complication.DreamAirQualityComplication;
-import com.android.systemui.dreams.complication.DreamAirQualityComplication.DreamAirQualityViewHolder;
-
-import java.lang.annotation.Documented;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.util.Objects;
-
-import javax.inject.Named;
-import javax.inject.Scope;
-
-import dagger.Module;
-import dagger.Provides;
-import dagger.Subcomponent;
-
-/**
- * Component responsible for generating dependencies for the {@link DreamAirQualityComplication},
- * such as the layout details.
- */
-@Subcomponent(modules = {
-        DreamAirQualityComplicationComponent.DreamAirQualityComplicationModule.class,
-})
-@DreamAirQualityComplicationComponent.DreamAirQualityComplicationScope
-public interface DreamAirQualityComplicationComponent {
-
-    @Documented
-    @Retention(RetentionPolicy.RUNTIME)
-    @Scope
-    @interface DreamAirQualityComplicationScope {
-    }
-
-    /**
-     * Generates {@link DreamAirQualityComplicationComponent}.
-     */
-    @Subcomponent.Factory
-    interface Factory {
-        DreamAirQualityComplicationComponent create();
-    }
-
-    /**
-     * Creates {@link DreamAirQualityViewHolder}.
-     */
-    DreamAirQualityViewHolder getViewHolder();
-
-    /**
-     * Scoped values for {@link DreamAirQualityComplicationComponent}.
-     */
-    @Module
-    interface DreamAirQualityComplicationModule {
-        String DREAM_AQI_COMPLICATION_VIEW = "aqi_complication_view";
-        String DREAM_AQI_COMPLICATION_LAYOUT_PARAMS = "aqi_complication_layout_params";
-        String DREAM_AQI_COLOR_THRESHOLDS = "aqi_color_thresholds";
-        String DREAM_AQI_COLOR_VALUES = "aqi_color_values";
-        String DREAM_AQI_COLOR_DEFAULT = "aqi_color_default";
-        // Order weight of insert into parent container
-        int INSERT_ORDER_WEIGHT = 1;
-
-        /**
-         * Provides the complication view.
-         */
-        @Provides
-        @DreamAirQualityComplicationScope
-        @Named(DREAM_AQI_COMPLICATION_VIEW)
-        static TextView provideComplicationView(LayoutInflater layoutInflater) {
-            return Objects.requireNonNull((TextView)
-                            layoutInflater.inflate(R.layout.dream_overlay_complication_aqi,
-                                    null, false),
-                    "R.layout.dream_overlay_complication_aqi did not properly inflated");
-        }
-
-        /**
-         * Provides the layout parameters for the complication view.
-         */
-        @Provides
-        @DreamAirQualityComplicationScope
-        @Named(DREAM_AQI_COMPLICATION_LAYOUT_PARAMS)
-        static ComplicationLayoutParams provideLayoutParams() {
-            return new ComplicationLayoutParams(0,
-                    ViewGroup.LayoutParams.WRAP_CONTENT,
-                    ComplicationLayoutParams.POSITION_BOTTOM
-                            | ComplicationLayoutParams.POSITION_START,
-                    ComplicationLayoutParams.DIRECTION_END,
-                    INSERT_ORDER_WEIGHT, /* snapToGuide= */ true);
-        }
-
-        @Provides
-        @DreamAirQualityComplicationScope
-        @Named(DREAM_AQI_COLOR_THRESHOLDS)
-        static int[] provideAqiColorThresholds(@Main Resources resources) {
-            return resources.getIntArray(R.array.config_dreamAqiThresholds);
-        }
-
-        @Provides
-        @DreamAirQualityComplicationScope
-        @Named(DREAM_AQI_COLOR_VALUES)
-        static int[] provideAqiColorValues(@Main Resources resources) {
-            return resources.getIntArray(R.array.config_dreamAqiColorValues);
-        }
-
-        @Provides
-        @DreamAirQualityComplicationScope
-        @Named(DREAM_AQI_COLOR_DEFAULT)
-        @ColorInt
-        static int provideDefaultAqiColor(Context context) {
-            return context.getColor(R.color.dream_overlay_aqi_unknown);
-        }
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/DreamWeatherComplicationComponent.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/DreamWeatherComplicationComponent.java
deleted file mode 100644
index f1a1689..0000000
--- a/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/DreamWeatherComplicationComponent.java
+++ /dev/null
@@ -1,120 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.dreams.complication.dagger;
-
-
-import static java.lang.annotation.RetentionPolicy.RUNTIME;
-
-import android.content.res.Resources;
-import android.view.LayoutInflater;
-import android.view.ViewGroup;
-import android.widget.TextView;
-
-import com.android.internal.util.Preconditions;
-import com.android.systemui.R;
-import com.android.systemui.dagger.qualifiers.Main;
-import com.android.systemui.dreams.complication.ComplicationLayoutParams;
-import com.android.systemui.dreams.complication.DreamWeatherComplication.DreamWeatherViewHolder;
-
-import java.lang.annotation.Documented;
-import java.lang.annotation.Retention;
-
-import javax.inject.Named;
-import javax.inject.Scope;
-
-import dagger.Binds;
-import dagger.Module;
-import dagger.Provides;
-import dagger.Subcomponent;
-
-/**
- * {@link DreamWeatherComplicationComponent} is responsible for generating dependencies surrounding
- * the
- * Clock Date {@link com.android.systemui.dreams.complication.Complication}, such as the layout
- * details.
- */
-@Subcomponent(modules = {
-        DreamWeatherComplicationComponent.DreamWeatherComplicationModule.class,
-})
-@DreamWeatherComplicationComponent.DreamWeatherComplicationScope
-public interface DreamWeatherComplicationComponent {
-    /**
-     * Creates {@link DreamWeatherViewHolder}.
-     */
-    DreamWeatherViewHolder getViewHolder();
-
-    @Documented
-    @Retention(RUNTIME)
-    @Scope
-    @interface DreamWeatherComplicationScope {
-    }
-
-    /**
-     * Generates {@link DreamWeatherComplicationComponent}.
-     */
-    @Subcomponent.Factory
-    interface Factory {
-        DreamWeatherComplicationComponent create();
-    }
-
-    /**
-     * Scoped values for {@link DreamWeatherComplicationComponent}.
-     */
-    @Module
-    interface DreamWeatherComplicationModule {
-        String DREAM_WEATHER_COMPLICATION_VIEW = "weather_complication_view";
-        String DREAM_WEATHER_COMPLICATION_LAYOUT_PARAMS =
-                "weather_complication_layout_params";
-        // Order weight of insert into parent container
-        int INSERT_ORDER_WEIGHT = 2;
-
-        /**
-         * Provides the complication view.
-         */
-        @Provides
-        @DreamWeatherComplicationScope
-        @Named(DREAM_WEATHER_COMPLICATION_VIEW)
-        static TextView provideComplicationView(LayoutInflater layoutInflater) {
-            return Preconditions.checkNotNull((TextView)
-                            layoutInflater.inflate(R.layout.dream_overlay_complication_weather,
-                                    null, false),
-                    "R.layout.dream_overlay_complication_weather did not properly inflated");
-        }
-
-        /**
-         * Provides the layout parameters for the complication view.
-         */
-        @Provides
-        @DreamWeatherComplicationScope
-        @Named(DREAM_WEATHER_COMPLICATION_LAYOUT_PARAMS)
-        static ComplicationLayoutParams provideLayoutParams() {
-            return new ComplicationLayoutParams(0,
-                    ViewGroup.LayoutParams.WRAP_CONTENT,
-                    ComplicationLayoutParams.POSITION_BOTTOM
-                            | ComplicationLayoutParams.POSITION_START,
-                    ComplicationLayoutParams.DIRECTION_END,
-                    INSERT_ORDER_WEIGHT, /* snapToGuide= */ true);
-        }
-
-        /**
-         * Binds resources in the dream weather complication scope.
-         */
-        @Binds
-        @DreamWeatherComplicationScope
-        Resources getResources(@Main Resources resources);
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/RegisteredComplicationsModule.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/RegisteredComplicationsModule.java
index 98344aa..e45437d 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/RegisteredComplicationsModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/RegisteredComplicationsModule.java
@@ -16,15 +16,9 @@
 
 package com.android.systemui.dreams.complication.dagger;
 
-import android.content.Context;
-
-import com.android.systemui.R;
 import com.android.systemui.dagger.SystemUIBinder;
 
-import javax.inject.Named;
-
 import dagger.Module;
-import dagger.Provides;
 
 /**
  * Module for all components with corresponding dream layer complications registered in
@@ -33,20 +27,6 @@
 @Module(includes = {
                 DreamClockDateComplicationModule.class,
                 DreamClockTimeComplicationModule.class,
-        },
-        subcomponents = {
-                DreamWeatherComplicationComponent.class,
-                DreamAirQualityComplicationComponent.class,
         })
 public interface RegisteredComplicationsModule {
-    String SMARTSPACE_TRAMPOLINE_ACTIVITY_COMPONENT = "smartspace_trampoline_activity";
-
-    /**
-     * Provides the smartspace trampoline activity component.
-     */
-    @Provides
-    @Named(SMARTSPACE_TRAMPOLINE_ACTIVITY_COMPONENT)
-    static String provideSmartspaceTrampolineActivityComponent(Context context) {
-        return context.getString(R.string.config_smartspaceTrampolineActivityComponent);
-    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.java b/packages/SystemUI/src/com/android/systemui/flags/Flags.java
index 245ea21..46173b8 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.java
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.java
@@ -142,6 +142,9 @@
     public static final ResourceBooleanFlag STATUS_BAR_USER_SWITCHER =
             new ResourceBooleanFlag(602, R.bool.flag_user_switcher_chip);
 
+    public static final BooleanFlag STATUS_BAR_LETTERBOX_APPEARANCE =
+            new BooleanFlag(603, false);
+
     /***************************************/
     // 700 - dialer/calls
     public static final BooleanFlag ONGOING_CALL_STATUS_BAR_CHIP =
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java b/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java
index aeff2d4..012d766 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java
@@ -682,9 +682,22 @@
             Drawable artwork;
             boolean isArtworkBound;
             Icon artworkIcon = data.getArtwork();
+            WallpaperColors wallpaperColors = null;
             if (artworkIcon != null) {
-                WallpaperColors wallpaperColors = WallpaperColors
-                        .fromBitmap(artworkIcon.getBitmap());
+                if (artworkIcon.getType() == Icon.TYPE_BITMAP
+                        || artworkIcon.getType() == Icon.TYPE_ADAPTIVE_BITMAP) {
+                    // Avoids extra processing if this is already a valid bitmap
+                    wallpaperColors = WallpaperColors
+                            .fromBitmap(artworkIcon.getBitmap());
+                } else {
+                    Drawable artworkDrawable = artworkIcon.loadDrawable(mContext);
+                    if (artworkDrawable != null) {
+                        wallpaperColors = WallpaperColors
+                                .fromDrawable(artworkIcon.loadDrawable(mContext));
+                    }
+                }
+            }
+            if (wallpaperColors != null) {
                 mutableColorScheme = new ColorScheme(wallpaperColors, true, Style.CONTENT);
                 Drawable albumArt = getScaledBackground(artworkIcon, width, height);
                 GradientDrawable gradient = (GradientDrawable) mContext
diff --git a/packages/SystemUI/src/com/android/systemui/people/PeopleModule.kt b/packages/SystemUI/src/com/android/systemui/people/PeopleModule.kt
new file mode 100644
index 0000000..dd35445
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/people/PeopleModule.kt
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.people
+
+import com.android.systemui.people.data.repository.PeopleTileRepository
+import com.android.systemui.people.data.repository.PeopleTileRepositoryImpl
+import com.android.systemui.people.data.repository.PeopleWidgetRepository
+import com.android.systemui.people.data.repository.PeopleWidgetRepositoryImpl
+import dagger.Binds
+import dagger.Module
+
+/** Dagger module to provide/bind people space dependencies. */
+@Module
+interface PeopleModule {
+    @Binds fun bindTileRepository(impl: PeopleTileRepositoryImpl): PeopleTileRepository
+
+    @Binds fun bindWidgetRepository(impl: PeopleWidgetRepositoryImpl): PeopleWidgetRepository
+}
diff --git a/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceActivity.java b/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceActivity.java
index 93a3f81..e845aa8 100644
--- a/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceActivity.java
@@ -19,144 +19,52 @@
 import static android.appwidget.AppWidgetManager.EXTRA_APPWIDGET_ID;
 import static android.appwidget.AppWidgetManager.INVALID_APPWIDGET_ID;
 
-import static com.android.systemui.people.PeopleTileViewHelper.getPersonIconBitmap;
-import static com.android.systemui.people.PeopleTileViewHelper.getSizeInDp;
-
-import android.app.Activity;
-import android.app.people.PeopleSpaceTile;
-import android.content.Context;
 import android.content.Intent;
-import android.content.res.TypedArray;
-import android.graphics.Color;
-import android.graphics.Outline;
-import android.graphics.drawable.GradientDrawable;
 import android.os.Bundle;
 import android.util.Log;
 import android.view.View;
 import android.view.ViewGroup;
-import android.view.ViewOutlineProvider;
-import android.widget.LinearLayout;
 
-import com.android.systemui.R;
-import com.android.systemui.people.widget.PeopleSpaceWidgetManager;
-import com.android.systemui.people.widget.PeopleTileKey;
+import androidx.activity.ComponentActivity;
+import androidx.lifecycle.ViewModelProvider;
 
-import java.util.ArrayList;
-import java.util.List;
+import com.android.systemui.people.ui.view.PeopleViewBinder;
+import com.android.systemui.people.ui.viewmodel.PeopleViewModel;
 
 import javax.inject.Inject;
 
 /** People Tile Widget configuration activity that shows the user their conversation tiles. */
-public class PeopleSpaceActivity extends Activity {
+public class PeopleSpaceActivity extends ComponentActivity {
 
     private static final String TAG = "PeopleSpaceActivity";
     private static final boolean DEBUG = PeopleSpaceUtils.DEBUG;
 
-    private PeopleSpaceWidgetManager mPeopleSpaceWidgetManager;
-    private Context mContext;
-    private int mAppWidgetId;
+    private final PeopleViewModel.Factory mViewModelFactory;
+    private PeopleViewModel mViewModel;
 
     @Inject
-    public PeopleSpaceActivity(PeopleSpaceWidgetManager peopleSpaceWidgetManager) {
+    public PeopleSpaceActivity(PeopleViewModel.Factory viewModelFactory) {
         super();
-        mPeopleSpaceWidgetManager = peopleSpaceWidgetManager;
-
+        mViewModelFactory = viewModelFactory;
     }
 
     @Override
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
-        mContext = getApplicationContext();
-        mAppWidgetId = getIntent().getIntExtra(EXTRA_APPWIDGET_ID,
-                INVALID_APPWIDGET_ID);
         setResult(RESULT_CANCELED);
-    }
+        mViewModel = new ViewModelProvider(this, mViewModelFactory).get(PeopleViewModel.class);
 
-    /** Builds the conversation selection activity. */
-    private void buildActivity() {
-        List<PeopleSpaceTile> priorityTiles = new ArrayList<>();
-        List<PeopleSpaceTile> recentTiles = new ArrayList<>();
-        try {
-            priorityTiles = mPeopleSpaceWidgetManager.getPriorityTiles();
-            recentTiles = mPeopleSpaceWidgetManager.getRecentTiles();
-        } catch (Exception e) {
-            Log.e(TAG, "Couldn't retrieve conversations", e);
-        }
+        // Update the widget ID coming from the intent.
+        int widgetId = getIntent().getIntExtra(EXTRA_APPWIDGET_ID, INVALID_APPWIDGET_ID);
+        mViewModel.onWidgetIdChanged(widgetId);
 
-        // If no conversations, render activity without conversations
-        if (recentTiles.isEmpty() && priorityTiles.isEmpty()) {
-            setContentView(R.layout.people_space_activity_no_conversations);
-
-            // The Tile preview has colorBackground as its background. Change it so it's different
-            // than the activity's background.
-            LinearLayout item = findViewById(android.R.id.background);
-            GradientDrawable shape = (GradientDrawable) item.getBackground();
-            final TypedArray ta = mContext.getTheme().obtainStyledAttributes(
-                    new int[]{com.android.internal.R.attr.colorSurface});
-            shape.setColor(ta.getColor(0, Color.WHITE));
-            return;
-        }
-
-        setContentView(R.layout.people_space_activity);
-        setTileViews(R.id.priority, R.id.priority_tiles, priorityTiles);
-        setTileViews(R.id.recent, R.id.recent_tiles, recentTiles);
-    }
-
-    private ViewOutlineProvider mViewOutlineProvider = new ViewOutlineProvider() {
-        @Override
-        public void getOutline(View view, Outline outline) {
-            outline.setRoundRect(0, 0, view.getWidth(), view.getHeight(),
-                    mContext.getResources().getDimension(R.dimen.people_space_widget_radius));
-        }
-    };
-
-    /** Sets a {@link PeopleSpaceTileView}s for each conversation. */
-    private void setTileViews(int viewId, int tilesId, List<PeopleSpaceTile> tiles) {
-        if (tiles.isEmpty()) {
-            LinearLayout view = findViewById(viewId);
-            view.setVisibility(View.GONE);
-            return;
-        }
-
-        ViewGroup layout = findViewById(tilesId);
-        layout.setClipToOutline(true);
-        layout.setOutlineProvider(mViewOutlineProvider);
-        for (int i = 0; i < tiles.size(); ++i) {
-            PeopleSpaceTile tile = tiles.get(i);
-            PeopleSpaceTileView tileView = new PeopleSpaceTileView(mContext,
-                    layout, tile.getId(), i == (tiles.size() - 1));
-            setTileView(tileView, tile);
-        }
-    }
-
-    /** Sets {@code tileView} with the data in {@code conversation}. */
-    private void setTileView(PeopleSpaceTileView tileView, PeopleSpaceTile tile) {
-        try {
-            if (tile.getUserName() != null) {
-                tileView.setName(tile.getUserName().toString());
-            }
-            tileView.setPersonIcon(getPersonIconBitmap(mContext, tile,
-                    getSizeInDp(mContext, R.dimen.avatar_size_for_medium,
-                            mContext.getResources().getDisplayMetrics().density)));
-
-            PeopleTileKey key = new PeopleTileKey(tile);
-            tileView.setOnClickListener(v -> storeWidgetConfiguration(tile, key));
-        } catch (Exception e) {
-            Log.e(TAG, "Couldn't retrieve shortcut information", e);
-        }
-    }
-
-    /** Stores the user selected configuration for {@code mAppWidgetId}. */
-    private void storeWidgetConfiguration(PeopleSpaceTile tile, PeopleTileKey key) {
-        if (PeopleSpaceUtils.DEBUG) {
-            if (DEBUG) {
-                Log.d(TAG, "Put " + tile.getUserName() + "'s shortcut ID: "
-                        + tile.getId() + " for widget ID: "
-                        + mAppWidgetId);
-            }
-        }
-        mPeopleSpaceWidgetManager.addNewWidget(mAppWidgetId, key);
-        finishActivity();
+        ViewGroup view = PeopleViewBinder.create(this);
+        PeopleViewBinder.bind(view, mViewModel, /* lifecycleOwner= */ this,
+                () -> {
+                    finishActivity();
+                    return null;
+                });
+        setContentView(view);
     }
 
     /** Finish activity with a successful widget configuration result. */
@@ -169,19 +77,13 @@
     /** Finish activity without choosing a widget. */
     public void dismissActivity(View v) {
         if (DEBUG) Log.d(TAG, "Activity dismissed with no widgets added!");
+        setResult(RESULT_CANCELED);
         finish();
     }
 
     private void setActivityResult(int result) {
         Intent resultValue = new Intent();
-        resultValue.putExtra(EXTRA_APPWIDGET_ID, mAppWidgetId);
+        resultValue.putExtra(EXTRA_APPWIDGET_ID, mViewModel.getAppWidgetId().getValue());
         setResult(result, resultValue);
     }
-
-    @Override
-    protected void onResume() {
-        super.onResume();
-        // Refresh tile views to sync new conversations.
-        buildActivity();
-    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/people/PeopleStoryIconFactory.java b/packages/SystemUI/src/com/android/systemui/people/PeopleStoryIconFactory.java
index 4ee951f..58e700f 100644
--- a/packages/SystemUI/src/com/android/systemui/people/PeopleStoryIconFactory.java
+++ b/packages/SystemUI/src/com/android/systemui/people/PeopleStoryIconFactory.java
@@ -28,6 +28,7 @@
 import android.graphics.drawable.Drawable;
 import android.util.IconDrawableFactory;
 import android.util.Log;
+import android.view.ContextThemeWrapper;
 
 import androidx.core.graphics.drawable.RoundedBitmapDrawable;
 
@@ -52,16 +53,15 @@
 
     PeopleStoryIconFactory(Context context, PackageManager pm,
             IconDrawableFactory iconDrawableFactory, int iconSizeDp) {
-        context.setTheme(android.R.style.Theme_DeviceDefault_DayNight);
-        mIconBitmapSize = (int) (iconSizeDp * context.getResources().getDisplayMetrics().density);
-        mDensity = context.getResources().getDisplayMetrics().density;
+        mContext = new ContextThemeWrapper(context, android.R.style.Theme_DeviceDefault_DayNight);
+        mIconBitmapSize = (int) (iconSizeDp * mContext.getResources().getDisplayMetrics().density);
+        mDensity = mContext.getResources().getDisplayMetrics().density;
         mIconSize = mDensity * iconSizeDp;
         mPackageManager = pm;
         mIconDrawableFactory = iconDrawableFactory;
-        mImportantConversationColor = context.getColor(R.color.important_conversation);
-        mAccentColor = Utils.getColorAttr(context,
+        mImportantConversationColor = mContext.getColor(R.color.important_conversation);
+        mAccentColor = Utils.getColorAttr(mContext,
                 com.android.internal.R.attr.colorAccentPrimaryVariant).getDefaultColor();
-        mContext = context;
     }
 
 
diff --git a/packages/SystemUI/src/com/android/systemui/people/PeopleTileViewHelper.java b/packages/SystemUI/src/com/android/systemui/people/PeopleTileViewHelper.java
index 00aa138..be82b1f 100644
--- a/packages/SystemUI/src/com/android/systemui/people/PeopleTileViewHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/people/PeopleTileViewHelper.java
@@ -75,6 +75,7 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.systemui.R;
+import com.android.systemui.people.data.model.PeopleTileModel;
 import com.android.systemui.people.widget.LaunchConversationActivity;
 import com.android.systemui.people.widget.PeopleSpaceWidgetProvider;
 import com.android.systemui.people.widget.PeopleTileKey;
@@ -299,7 +300,8 @@
         return createLastInteractionRemoteViews();
     }
 
-    private static boolean isDndBlockingTileData(@Nullable PeopleSpaceTile tile) {
+    /** Whether the conversation associated with {@code tile} can bypass DND. */
+    public static boolean isDndBlockingTileData(@Nullable PeopleSpaceTile tile) {
         if (tile == null) return false;
 
         int notificationPolicyState = tile.getNotificationPolicyState();
@@ -536,7 +538,8 @@
         return views;
     }
 
-    private static boolean getHasNewStory(PeopleSpaceTile tile) {
+    /** Whether {@code tile} has a new story. */
+    public static boolean getHasNewStory(PeopleSpaceTile tile) {
         return tile.getStatuses() != null && tile.getStatuses().stream().anyMatch(
                 c -> c.getActivity() == ACTIVITY_NEW_STORY);
     }
@@ -1250,16 +1253,24 @@
     }
 
     /** Returns a bitmap with the user icon and package icon. */
-    public static Bitmap getPersonIconBitmap(Context context, PeopleSpaceTile tile,
+    public static Bitmap getPersonIconBitmap(Context context, PeopleTileModel tile,
             int maxAvatarSize) {
-        boolean hasNewStory = getHasNewStory(tile);
-        return getPersonIconBitmap(context, tile, maxAvatarSize, hasNewStory);
+        return getPersonIconBitmap(context, maxAvatarSize, tile.getHasNewStory(),
+                tile.getUserIcon(), tile.getKey().getPackageName(), tile.getKey().getUserId(),
+                tile.isImportant(),  tile.isDndBlocking());
     }
 
     /** Returns a bitmap with the user icon and package icon. */
     private static Bitmap getPersonIconBitmap(
             Context context, PeopleSpaceTile tile, int maxAvatarSize, boolean hasNewStory) {
-        Icon icon = tile.getUserIcon();
+        return getPersonIconBitmap(context, maxAvatarSize, hasNewStory, tile.getUserIcon(),
+                tile.getPackageName(), getUserId(tile),
+                tile.isImportantConversation(), isDndBlockingTileData(tile));
+    }
+
+    private static Bitmap getPersonIconBitmap(
+            Context context, int maxAvatarSize, boolean hasNewStory, Icon icon, String packageName,
+            int userId, boolean importantConversation, boolean dndBlockingTileData) {
         if (icon == null) {
             Drawable placeholder = context.getDrawable(R.drawable.ic_avatar_with_badge).mutate();
             placeholder.setColorFilter(getDisabledColorFilter());
@@ -1272,10 +1283,10 @@
         RoundedBitmapDrawable roundedDrawable = RoundedBitmapDrawableFactory.create(
                 context.getResources(), icon.getBitmap());
         Drawable personDrawable = storyIcon.getPeopleTileDrawable(roundedDrawable,
-                tile.getPackageName(), getUserId(tile), tile.isImportantConversation(),
+                packageName, userId, importantConversation,
                 hasNewStory);
 
-        if (isDndBlockingTileData(tile)) {
+        if (dndBlockingTileData) {
             personDrawable.setColorFilter(getDisabledColorFilter());
         }
 
diff --git a/packages/SystemUI/src/com/android/systemui/people/data/model/PeopleTileModel.kt b/packages/SystemUI/src/com/android/systemui/people/data/model/PeopleTileModel.kt
new file mode 100644
index 0000000..5d8539f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/people/data/model/PeopleTileModel.kt
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.people.data.model
+
+import android.graphics.drawable.Icon
+import com.android.systemui.people.widget.PeopleTileKey
+
+/** Models a tile/conversation. */
+data class PeopleTileModel(
+    val key: PeopleTileKey,
+    val username: String,
+    val userIcon: Icon,
+    val hasNewStory: Boolean,
+    val isImportant: Boolean,
+    val isDndBlocking: Boolean,
+)
diff --git a/packages/SystemUI/src/com/android/systemui/people/data/repository/PeopleTileRepository.kt b/packages/SystemUI/src/com/android/systemui/people/data/repository/PeopleTileRepository.kt
new file mode 100644
index 0000000..01b43d5
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/people/data/repository/PeopleTileRepository.kt
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.people.data.repository
+
+import android.app.people.PeopleSpaceTile
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.people.PeopleTileViewHelper
+import com.android.systemui.people.data.model.PeopleTileModel
+import com.android.systemui.people.widget.PeopleSpaceWidgetManager
+import com.android.systemui.people.widget.PeopleTileKey
+import javax.inject.Inject
+
+/** A Repository to fetch the current tiles/conversations. */
+// TODO(b/238993727): Make the tiles API reactive.
+interface PeopleTileRepository {
+    /* The current priority tiles. */
+    fun priorityTiles(): List<PeopleTileModel>
+
+    /* The current recent tiles. */
+    fun recentTiles(): List<PeopleTileModel>
+}
+
+@SysUISingleton
+class PeopleTileRepositoryImpl
+@Inject
+constructor(
+    private val peopleSpaceWidgetManager: PeopleSpaceWidgetManager,
+) : PeopleTileRepository {
+    override fun priorityTiles(): List<PeopleTileModel> {
+        return peopleSpaceWidgetManager.priorityTiles.map { it.toModel() }
+    }
+
+    override fun recentTiles(): List<PeopleTileModel> {
+        return peopleSpaceWidgetManager.recentTiles.map { it.toModel() }
+    }
+
+    private fun PeopleSpaceTile.toModel(): PeopleTileModel {
+        return PeopleTileModel(
+            PeopleTileKey(this),
+            userName.toString(),
+            userIcon,
+            PeopleTileViewHelper.getHasNewStory(this),
+            isImportantConversation,
+            PeopleTileViewHelper.isDndBlockingTileData(this),
+        )
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/people/data/repository/PeopleWidgetRepository.kt b/packages/SystemUI/src/com/android/systemui/people/data/repository/PeopleWidgetRepository.kt
new file mode 100644
index 0000000..f2b6cb1
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/people/data/repository/PeopleWidgetRepository.kt
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.people.data.repository
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.people.widget.PeopleSpaceWidgetManager
+import com.android.systemui.people.widget.PeopleTileKey
+import javax.inject.Inject
+
+interface PeopleWidgetRepository {
+    /**
+     * Bind the widget with ID [widgetId] to the tile keyed by [tileKey].
+     *
+     * If there is already a widget with [widgetId], this existing widget will be reconfigured and
+     * associated to this tile. If there is no widget with [widgetId], a new one will be created.
+     */
+    fun setWidgetTile(widgetId: Int, tileKey: PeopleTileKey)
+}
+
+@SysUISingleton
+class PeopleWidgetRepositoryImpl
+@Inject
+constructor(
+    private val peopleSpaceWidgetManager: PeopleSpaceWidgetManager,
+) : PeopleWidgetRepository {
+    override fun setWidgetTile(widgetId: Int, tileKey: PeopleTileKey) {
+        peopleSpaceWidgetManager.addNewWidget(widgetId, tileKey)
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/people/ui/view/PeopleViewBinder.kt b/packages/SystemUI/src/com/android/systemui/people/ui/view/PeopleViewBinder.kt
new file mode 100644
index 0000000..bc982cc
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/people/ui/view/PeopleViewBinder.kt
@@ -0,0 +1,243 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.people.ui.view
+
+import android.content.Context
+import android.graphics.Color
+import android.graphics.Outline
+import android.graphics.drawable.GradientDrawable
+import android.util.Log
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.view.ViewOutlineProvider
+import android.widget.LinearLayout
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.Lifecycle.State.CREATED
+import androidx.lifecycle.LifecycleOwner
+import androidx.lifecycle.lifecycleScope
+import androidx.lifecycle.repeatOnLifecycle
+import com.android.systemui.R
+import com.android.systemui.people.PeopleSpaceTileView
+import com.android.systemui.people.ui.viewmodel.PeopleTileViewModel
+import com.android.systemui.people.ui.viewmodel.PeopleViewModel
+import kotlinx.coroutines.flow.collect
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.launch
+
+/** A ViewBinder for [PeopleViewModel]. */
+object PeopleViewBinder {
+    private const val TAG = "PeopleSpaceViewBinder"
+
+    /**
+     * The [ViewOutlineProvider] used to clip the corner radius of the recent and priority lists.
+     */
+    private val ViewOutlineProvider =
+        object : ViewOutlineProvider() {
+            override fun getOutline(view: View, outline: Outline) {
+                outline.setRoundRect(
+                    0,
+                    0,
+                    view.width,
+                    view.height,
+                    view.context.resources.getDimension(R.dimen.people_space_widget_radius),
+                )
+            }
+        }
+
+    /** Create a [View] that can later be [bound][bind] to a [PeopleViewModel]. */
+    @JvmStatic
+    fun create(context: Context): ViewGroup {
+        return LayoutInflater.from(context)
+            .inflate(R.layout.people_space_activity, /* root= */ null) as ViewGroup
+    }
+
+    /** Bind [view] to [viewModel]. */
+    @JvmStatic
+    fun bind(
+        view: ViewGroup,
+        viewModel: PeopleViewModel,
+        lifecycleOwner: LifecycleOwner,
+        onFinish: () -> Unit,
+    ) {
+        // Call [onFinish] this activity when the ViewModel tells us so.
+        lifecycleOwner.lifecycleScope.launch {
+            lifecycleOwner.repeatOnLifecycle(CREATED) {
+                viewModel.isFinished.collect { isFinished ->
+                    if (isFinished) {
+                        viewModel.clearIsFinished()
+                        onFinish()
+                    }
+                }
+            }
+        }
+
+        // Start collecting the UI data once the Activity is STARTED.
+        lifecycleOwner.lifecycleScope.launch {
+            lifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
+                combine(
+                        viewModel.priorityTiles,
+                        viewModel.recentTiles,
+                    ) { priority, recent ->
+                        priority to recent
+                    }
+                    .collect { (priorityTiles, recentTiles) ->
+                        if (priorityTiles.isNotEmpty() || recentTiles.isNotEmpty()) {
+                            setConversationsContent(
+                                view,
+                                priorityTiles,
+                                recentTiles,
+                                viewModel::onTileClicked,
+                            )
+                        } else {
+                            setNoConversationsContent(view)
+                        }
+                    }
+            }
+        }
+
+        // Make sure to refresh the tiles/conversations when the Activity is resumed, so that it
+        // updates them when going back to the Activity after leaving it.
+        lifecycleOwner.lifecycleScope.launch {
+            lifecycleOwner.repeatOnLifecycle(Lifecycle.State.RESUMED) {
+                viewModel.onTileRefreshRequested()
+            }
+        }
+    }
+
+    private fun setNoConversationsContent(view: ViewGroup) {
+        // This should never happen.
+        if (view.childCount > 1) {
+            error("view has ${view.childCount} children, it should have maximum 1")
+        }
+
+        // The static content for no conversations is already shown.
+        if (view.findViewById<View>(R.id.top_level_no_conversations) != null) {
+            return
+        }
+
+        // If we were showing the content with conversations earlier, remove it.
+        if (view.childCount == 1) {
+            view.removeViewAt(0)
+        }
+
+        val context = view.context
+        val noConversationsView =
+            LayoutInflater.from(context)
+                .inflate(R.layout.people_space_activity_no_conversations, /* root= */ view)
+
+        // The Tile preview has colorBackground as its background. Change it so it's different than
+        // the activity's background.
+        val item = noConversationsView.findViewById<LinearLayout>(android.R.id.background)
+        val shape = item.background as GradientDrawable
+        val ta =
+            context.theme.obtainStyledAttributes(
+                intArrayOf(com.android.internal.R.attr.colorSurface)
+            )
+        shape.setColor(ta.getColor(0, Color.WHITE))
+        ta.recycle()
+    }
+
+    private fun setConversationsContent(
+        view: ViewGroup,
+        priorityTiles: List<PeopleTileViewModel>,
+        recentTiles: List<PeopleTileViewModel>,
+        onTileClicked: (PeopleTileViewModel) -> Unit,
+    ) {
+        // This should never happen.
+        if (view.childCount > 1) {
+            error("view has ${view.childCount} children, it should have maximum 1")
+        }
+
+        // Inflate the content with conversations, if it's not already.
+        if (view.findViewById<View>(R.id.top_level_with_conversations) == null) {
+            // If we were showing the content without conversations earlier, remove it.
+            if (view.childCount == 1) {
+                view.removeViewAt(0)
+            }
+
+            LayoutInflater.from(view.context)
+                .inflate(R.layout.people_space_activity_with_conversations, /* root= */ view)
+        }
+
+        // TODO(b/193782241): Replace the NestedScrollView + 2x LinearLayout from this layout into a
+        // single RecyclerView once this screen is tested by screenshot tests. Introduce a
+        // PeopleSpaceTileViewBinder that will properly create and bind the View associated to a
+        // PeopleSpaceTileViewModel (and remove the PeopleSpaceTileView class).
+        val conversationsView = view.requireViewById<View>(R.id.top_level_with_conversations)
+        setTileViews(
+            conversationsView,
+            R.id.priority,
+            R.id.priority_tiles,
+            priorityTiles,
+            onTileClicked,
+        )
+
+        setTileViews(
+            conversationsView,
+            R.id.recent,
+            R.id.recent_tiles,
+            recentTiles,
+            onTileClicked,
+        )
+    }
+
+    /** Sets a [PeopleSpaceTileView]s for each conversation. */
+    private fun setTileViews(
+        root: View,
+        tilesListId: Int,
+        tilesId: Int,
+        tiles: List<PeopleTileViewModel>,
+        onTileClicked: (PeopleTileViewModel) -> Unit,
+    ) {
+        // Remove any previously added tile.
+        // TODO(b/193782241): Once this list is a big RecyclerView, set the current list and use
+        // DiffUtil to do as less addView/removeView as possible.
+        val layout = root.requireViewById<ViewGroup>(tilesId)
+        layout.removeAllViews()
+        layout.outlineProvider = ViewOutlineProvider
+
+        val tilesListView = root.requireViewById<LinearLayout>(tilesListId)
+        if (tiles.isEmpty()) {
+            tilesListView.visibility = View.GONE
+            return
+        }
+        tilesListView.visibility = View.VISIBLE
+
+        // Add each tile.
+        tiles.forEachIndexed { i, tile ->
+            val tileView =
+                PeopleSpaceTileView(root.context, layout, tile.key.shortcutId, i == tiles.size - 1)
+            bindTileView(tileView, tile, onTileClicked)
+        }
+    }
+
+    /** Sets [tileView] with the data in [conversation]. */
+    private fun bindTileView(
+        tileView: PeopleSpaceTileView,
+        tile: PeopleTileViewModel,
+        onTileClicked: (PeopleTileViewModel) -> Unit,
+    ) {
+        try {
+            tileView.setName(tile.username)
+            tileView.setPersonIcon(tile.icon)
+            tileView.setOnClickListener { onTileClicked(tile) }
+        } catch (e: Exception) {
+            Log.e(TAG, "Couldn't retrieve shortcut information", e)
+        }
+    }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDrop.java b/packages/SystemUI/src/com/android/systemui/people/ui/viewmodel/PeopleTileViewModel.kt
similarity index 60%
rename from libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDrop.java
rename to packages/SystemUI/src/com/android/systemui/people/ui/viewmodel/PeopleTileViewModel.kt
index 0335187..40205ce 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDrop.java
+++ b/packages/SystemUI/src/com/android/systemui/people/ui/viewmodel/PeopleTileViewModel.kt
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2020 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.
@@ -14,13 +14,14 @@
  * limitations under the License.
  */
 
-package com.android.wm.shell.draganddrop;
+package com.android.systemui.people.ui.viewmodel
 
-import com.android.wm.shell.common.annotations.ExternalThread;
+import android.graphics.Bitmap
+import com.android.systemui.people.widget.PeopleTileKey
 
-/**
- * Interface for telling DragAndDrop stuff.
- */
-@ExternalThread
-public interface DragAndDrop {
-}
+/** Models UI state for a single tile/conversation. */
+data class PeopleTileViewModel(
+    val key: PeopleTileKey,
+    val icon: Bitmap,
+    val username: String?,
+)
diff --git a/packages/SystemUI/src/com/android/systemui/people/ui/viewmodel/PeopleViewModel.kt b/packages/SystemUI/src/com/android/systemui/people/ui/viewmodel/PeopleViewModel.kt
new file mode 100644
index 0000000..17de991
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/people/ui/viewmodel/PeopleViewModel.kt
@@ -0,0 +1,149 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.people.ui.viewmodel
+
+import android.appwidget.AppWidgetManager.INVALID_APPWIDGET_ID
+import android.content.Context
+import android.util.Log
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.ViewModelProvider
+import com.android.systemui.R
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.people.PeopleSpaceUtils
+import com.android.systemui.people.PeopleTileViewHelper
+import com.android.systemui.people.data.model.PeopleTileModel
+import com.android.systemui.people.data.repository.PeopleTileRepository
+import com.android.systemui.people.data.repository.PeopleWidgetRepository
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+
+/**
+ * Models UI state for the people space, allowing the user to select which conversation should be
+ * associated to a new or existing Conversation widget.
+ */
+class PeopleViewModel(
+    @Application private val context: Context,
+    private val tileRepository: PeopleTileRepository,
+    private val widgetRepository: PeopleWidgetRepository,
+) : ViewModel() {
+    /**
+     * The list of the priority tiles/conversations.
+     *
+     * Important: Even though this is a Flow, the underlying API used to populate this Flow is not
+     * reactive and you have to manually call [onTileRefreshRequested] to refresh the tiles.
+     */
+    private val _priorityTiles = MutableStateFlow(priorityTiles())
+    val priorityTiles: Flow<List<PeopleTileViewModel>> = _priorityTiles
+
+    /**
+     * The list of the priority tiles/conversations.
+     *
+     * Important: Even though this is a Flow, the underlying API used to populate this Flow is not
+     * reactive and you have to manually call [onTileRefreshRequested] to refresh the tiles.
+     */
+    private val _recentTiles = MutableStateFlow(recentTiles())
+    val recentTiles: Flow<List<PeopleTileViewModel>> = _recentTiles
+
+    /** The ID of the widget currently being edited/added. */
+    private val _appWidgetId = MutableStateFlow(INVALID_APPWIDGET_ID)
+    val appWidgetId: StateFlow<Int> = _appWidgetId
+
+    /** Whether the user journey is complete. */
+    private val _isFinished = MutableStateFlow(false)
+    val isFinished: StateFlow<Boolean> = _isFinished
+
+    /** Refresh the [priorityTiles] and [recentTiles]. */
+    fun onTileRefreshRequested() {
+        _priorityTiles.value = priorityTiles()
+        _recentTiles.value = recentTiles()
+    }
+
+    /** Called when the [appWidgetId] should be changed to [widgetId]. */
+    fun onWidgetIdChanged(widgetId: Int) {
+        _appWidgetId.value = widgetId
+    }
+
+    /** Clear [isFinished], setting it to false. */
+    fun clearIsFinished() {
+        _isFinished.value = false
+    }
+
+    /** Called when a tile is clicked. */
+    fun onTileClicked(tile: PeopleTileViewModel) {
+        if (PeopleSpaceUtils.DEBUG) {
+            Log.d(
+                TAG,
+                "Put ${tile.username}'s shortcut ID: ${tile.key.shortcutId} for widget ID: " +
+                    _appWidgetId.value
+            )
+        }
+        widgetRepository.setWidgetTile(_appWidgetId.value, tile.key)
+        _isFinished.value = true
+    }
+
+    private fun priorityTiles(): List<PeopleTileViewModel> {
+        return try {
+            tileRepository.priorityTiles().map { it.toViewModel() }
+        } catch (e: Exception) {
+            Log.e(TAG, "Couldn't retrieve priority conversations", e)
+            emptyList()
+        }
+    }
+
+    private fun recentTiles(): List<PeopleTileViewModel> {
+        return try {
+            tileRepository.recentTiles().map { it.toViewModel() }
+        } catch (e: Exception) {
+            Log.e(TAG, "Couldn't retrieve recent conversations", e)
+            emptyList()
+        }
+    }
+
+    private fun PeopleTileModel.toViewModel(): PeopleTileViewModel {
+        val icon =
+            PeopleTileViewHelper.getPersonIconBitmap(
+                context,
+                this,
+                PeopleTileViewHelper.getSizeInDp(
+                    context,
+                    R.dimen.avatar_size_for_medium,
+                    context.resources.displayMetrics.density,
+                )
+            )
+        return PeopleTileViewModel(key, icon, username)
+    }
+
+    /** The Factory that should be used to create a [PeopleViewModel]. */
+    class Factory
+    @Inject
+    constructor(
+        @Application private val context: Context,
+        private val tileRepository: PeopleTileRepository,
+        private val widgetRepository: PeopleWidgetRepository,
+    ) : ViewModelProvider.Factory {
+        override fun <T : ViewModel> create(modelClass: Class<T>): T {
+            check(modelClass == PeopleViewModel::class.java)
+            return PeopleViewModel(context, tileRepository, widgetRepository) as T
+        }
+    }
+
+    companion object {
+        private const val TAG = "PeopleSpaceViewModel"
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/people/widget/LaunchConversationActivity.java b/packages/SystemUI/src/com/android/systemui/people/widget/LaunchConversationActivity.java
index e1b97a4..20c6c556 100644
--- a/packages/SystemUI/src/com/android/systemui/people/widget/LaunchConversationActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/people/widget/LaunchConversationActivity.java
@@ -21,6 +21,7 @@
 import android.content.Intent;
 import android.content.pm.LauncherApps;
 import android.os.Bundle;
+import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.os.UserHandle;
 import android.os.UserManager;
@@ -34,6 +35,7 @@
 import com.android.internal.logging.UiEventLoggerImpl;
 import com.android.internal.statusbar.IStatusBarService;
 import com.android.internal.statusbar.NotificationVisibility;
+import com.android.systemui.dagger.qualifiers.Background;
 import com.android.systemui.people.PeopleSpaceUtils;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
@@ -43,6 +45,7 @@
 import com.android.wm.shell.bubbles.Bubble;
 
 import java.util.Optional;
+import java.util.concurrent.Executor;
 
 import javax.inject.Inject;
 
@@ -58,6 +61,7 @@
     private boolean mIsForTesting;
     private IStatusBarService mIStatusBarService;
     private CommandQueue mCommandQueue;
+    private Executor mBgExecutor;
     private Bubble mBubble;
     private NotificationEntry mEntryToBubble;
 
@@ -67,7 +71,8 @@
             CommonNotifCollection commonNotifCollection,
             Optional<BubblesManager> bubblesManagerOptional,
             UserManager userManager,
-            CommandQueue commandQueue
+            CommandQueue commandQueue,
+            @Background Executor bgExecutor
     ) {
         super();
         mVisibilityProvider = visibilityProvider;
@@ -91,6 +96,7 @@
                 mCommandQueue.removeCallback(this);
             }
         });
+        mBgExecutor = bgExecutor;
     }
 
     @Override
@@ -172,34 +178,36 @@
             return;
         }
 
-        try {
-            if (mIStatusBarService == null || mCommonNotifCollection == null) {
-                if (DEBUG) {
-                    Log.d(TAG, "Skipping clear notification: null services, key: " + notifKey);
-                }
-                return;
+        if (mIStatusBarService == null || mCommonNotifCollection == null) {
+            if (DEBUG) {
+                Log.d(TAG, "Skipping clear notification: null services, key: " + notifKey);
             }
-
-            NotificationEntry entry = mCommonNotifCollection.getEntry(notifKey);
-            if (entry == null || entry.getRanking() == null) {
-                if (DEBUG) {
-                    Log.d(TAG, "Skipping clear notification: NotificationEntry or its Ranking"
-                            + " is null, key: " + notifKey);
-                }
-                return;
-            }
-
-            NotificationVisibility notifVisibility = mVisibilityProvider.obtain(entry, true);
-            int rank = notifVisibility.rank;
-
-            if (DEBUG) Log.d(TAG, "Clearing notification, key: " + notifKey + ", rank: " + rank);
-            mIStatusBarService.onNotificationClear(
-                    packageName, userHandle.getIdentifier(), notifKey,
-                    NotificationStats.DISMISSAL_OTHER,
-                    NotificationStats.DISMISS_SENTIMENT_POSITIVE, notifVisibility);
-        } catch (Exception e) {
-            Log.e(TAG, "Exception cancelling notification:" + e);
+            return;
         }
+
+        NotificationEntry entry = mCommonNotifCollection.getEntry(notifKey);
+        if (entry == null || entry.getRanking() == null) {
+            if (DEBUG) {
+                Log.d(TAG, "Skipping clear notification: NotificationEntry or its Ranking"
+                        + " is null, key: " + notifKey);
+            }
+            return;
+        }
+
+        NotificationVisibility notifVisibility = mVisibilityProvider.obtain(entry, true);
+        int rank = notifVisibility.rank;
+
+        if (DEBUG) Log.d(TAG, "Clearing notification, key: " + notifKey + ", rank: " + rank);
+        mBgExecutor.execute(() -> {
+            try {
+                mIStatusBarService.onNotificationClear(
+                        packageName, userHandle.getIdentifier(), notifKey,
+                        NotificationStats.DISMISSAL_OTHER,
+                        NotificationStats.DISMISS_SENTIMENT_POSITIVE, notifVisibility);
+            } catch (RemoteException e) {
+                Log.e(TAG, "Exception cancelling notification:" + e);
+            }
+        });
     }
 
     @VisibleForTesting
diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java
index 5bb3413..a837cbb 100644
--- a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java
+++ b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java
@@ -29,6 +29,7 @@
 import android.media.MediaRecorder;
 import android.net.Uri;
 import android.os.Bundle;
+import android.os.Handler;
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.os.UserHandle;
@@ -40,6 +41,8 @@
 import com.android.internal.logging.UiEventLogger;
 import com.android.systemui.R;
 import com.android.systemui.dagger.qualifiers.LongRunning;
+import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.screenrecord.ScreenMediaRecorder.ScreenMediaRecorderListener;
 import com.android.systemui.settings.UserContextProvider;
 import com.android.systemui.statusbar.phone.KeyguardDismissUtil;
 
@@ -51,9 +54,10 @@
 /**
  * A service which records the device screen and optionally microphone input.
  */
-public class RecordingService extends Service implements MediaRecorder.OnInfoListener {
+public class RecordingService extends Service implements ScreenMediaRecorderListener {
     public static final int REQUEST_CODE = 2;
 
+    private static final int USER_ID_NOT_SPECIFIED = -1;
     private static final int NOTIFICATION_RECORDING_ID = 4274;
     private static final int NOTIFICATION_PROCESSING_ID = 4275;
     private static final int NOTIFICATION_VIEW_ID = 4273;
@@ -73,6 +77,7 @@
 
     private final RecordingController mController;
     private final KeyguardDismissUtil mKeyguardDismissUtil;
+    private final Handler mMainHandler;
     private ScreenRecordingAudioSource mAudioSource;
     private boolean mShowTaps;
     private boolean mOriginalShowTaps;
@@ -84,10 +89,12 @@
 
     @Inject
     public RecordingService(RecordingController controller, @LongRunning Executor executor,
-            UiEventLogger uiEventLogger, NotificationManager notificationManager,
+            @Main Handler handler, UiEventLogger uiEventLogger,
+            NotificationManager notificationManager,
             UserContextProvider userContextTracker, KeyguardDismissUtil keyguardDismissUtil) {
         mController = controller;
         mLongExecutor = executor;
+        mMainHandler = handler;
         mUiEventLogger = uiEventLogger;
         mNotificationManager = notificationManager;
         mUserContextTracker = userContextTracker;
@@ -138,6 +145,7 @@
 
                 mRecorder = new ScreenMediaRecorder(
                         mUserContextTracker.getUserContext(),
+                        mMainHandler,
                         currentUserId,
                         mAudioSource,
                         this
@@ -166,14 +174,8 @@
                 }
                 // Check user ID - we may be getting a stop intent after user switch, in which case
                 // we want to post the notifications for that user, which is NOT current user
-                int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1);
-                if (userId == -1) {
-                    userId = mUserContextTracker.getUserContext().getUserId();
-                }
-                Log.d(TAG, "notifying for user " + userId);
-                stopRecording(userId);
-                mNotificationManager.cancel(NOTIFICATION_RECORDING_ID);
-                stopSelf();
+                int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, USER_ID_NOT_SPECIFIED);
+                stopService(userId);
                 break;
 
             case ACTION_SHARE:
@@ -378,15 +380,39 @@
         return builder.build();
     }
 
-    private void stopRecording(int userId) {
+    private void stopService() {
+        stopService(USER_ID_NOT_SPECIFIED);
+    }
+
+    private void stopService(int userId) {
+        if (userId == USER_ID_NOT_SPECIFIED) {
+            userId = mUserContextTracker.getUserContext().getUserId();
+        }
+        Log.d(TAG, "notifying for user " + userId);
         setTapsVisible(mOriginalShowTaps);
         if (getRecorder() != null) {
-            getRecorder().end();
-            saveRecording(userId);
+            try {
+                getRecorder().end();
+                saveRecording(userId);
+            } catch (RuntimeException exception) {
+                // RuntimeException could happen if the recording stopped immediately after starting
+                // let's release the recorder and delete all temporary files in this case
+                getRecorder().release();
+                showErrorToast(R.string.screenrecord_start_error);
+                Log.e(TAG, "stopRecording called, but there was an error when ending"
+                        + "recording");
+                exception.printStackTrace();
+            } catch (Throwable throwable) {
+                // Something unexpected happen, SystemUI will crash but let's delete
+                // the temporary files anyway
+                getRecorder().release();
+                throw new RuntimeException(throwable);
+            }
         } else {
             Log.e(TAG, "stopRecording called, but recorder was null");
         }
         updateState(false);
+        stopSelf();
     }
 
     private void saveRecording(int userId) {
@@ -446,4 +472,12 @@
         Log.d(TAG, "Media recorder info: " + what);
         onStartCommand(getStopIntent(this), 0, 0);
     }
+
+    @Override
+    public void onStopped() {
+        if (mController.isRecording()) {
+            Log.d(TAG, "Stopping recording because the system requested the stop");
+            stopService();
+        }
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenMediaRecorder.java b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenMediaRecorder.java
index 2133cf6..d098b4b 100644
--- a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenMediaRecorder.java
+++ b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenMediaRecorder.java
@@ -40,6 +40,7 @@
 import android.media.projection.MediaProjection;
 import android.media.projection.MediaProjectionManager;
 import android.net.Uri;
+import android.os.Handler;
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.os.ServiceManager;
@@ -51,16 +52,19 @@
 import android.view.WindowManager;
 
 import java.io.File;
+import java.io.Closeable;
 import java.io.IOException;
 import java.io.OutputStream;
 import java.nio.file.Files;
 import java.text.SimpleDateFormat;
+import java.util.ArrayList;
 import java.util.Date;
+import java.util.List;
 
 /**
  * Recording screen and mic/internal audio
  */
-public class ScreenMediaRecorder {
+public class ScreenMediaRecorder extends MediaProjection.Callback {
     private static final int TOTAL_NUM_TRACKS = 1;
     private static final int VIDEO_FRAME_RATE = 30;
     private static final int VIDEO_FRAME_RATE_TO_RESOLUTION_RATIO = 6;
@@ -81,14 +85,16 @@
     private ScreenRecordingMuxer mMuxer;
     private ScreenInternalAudioRecorder mAudio;
     private ScreenRecordingAudioSource mAudioSource;
+    private final Handler mHandler;
 
     private Context mContext;
-    MediaRecorder.OnInfoListener mListener;
+    ScreenMediaRecorderListener mListener;
 
-    public ScreenMediaRecorder(Context context,
+    public ScreenMediaRecorder(Context context, Handler handler,
             int user, ScreenRecordingAudioSource audioSource,
-            MediaRecorder.OnInfoListener listener) {
+            ScreenMediaRecorderListener listener) {
         mContext = context;
+        mHandler = handler;
         mUser = user;
         mListener = listener;
         mAudioSource = audioSource;
@@ -105,6 +111,7 @@
         IBinder projection = proj.asBinder();
         mMediaProjection = new MediaProjection(mContext,
                 IMediaProjection.Stub.asInterface(projection));
+        mMediaProjection.registerCallback(this, mHandler);
 
         File cacheDir = mContext.getCacheDir();
         cacheDir.mkdirs();
@@ -162,10 +169,15 @@
                 metrics.densityDpi,
                 DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR,
                 mInputSurface,
-                null,
-                null);
+                new VirtualDisplay.Callback() {
+                    @Override
+                    public void onStopped() {
+                        onStop();
+                    }
+                },
+                mHandler);
 
-        mMediaRecorder.setOnInfoListener(mListener);
+        mMediaRecorder.setOnInfoListener((mr, what, extra) -> mListener.onInfo(mr, what, extra));
         if (mAudioSource == INTERNAL ||
                 mAudioSource == MIC_AND_INTERNAL) {
             mTempAudioFile = File.createTempFile("temp", ".aac",
@@ -259,21 +271,34 @@
     }
 
     /**
-     * End screen recording
+     * End screen recording, throws an exception if stopping recording failed
      */
-    void end() {
-        mMediaRecorder.stop();
-        mMediaRecorder.release();
-        mInputSurface.release();
-        mVirtualDisplay.release();
-        mMediaProjection.stop();
+    void end() throws IOException {
+        Closer closer = new Closer();
+
+        // MediaRecorder might throw RuntimeException if stopped immediately after starting
+        // We should remove the recording in this case as it will be invalid
+        closer.register(mMediaRecorder::stop);
+        closer.register(mMediaRecorder::release);
+        closer.register(mInputSurface::release);
+        closer.register(mVirtualDisplay::release);
+        closer.register(mMediaProjection::stop);
+        closer.register(this::stopInternalAudioRecording);
+
+        closer.close();
+
         mMediaRecorder = null;
         mMediaProjection = null;
-        stopInternalAudioRecording();
 
         Log.d(TAG, "end recording");
     }
 
+    @Override
+    public void onStop() {
+        Log.d(TAG, "The system notified about stopping the projection");
+        mListener.onStopped();
+    }
+
     private void stopInternalAudioRecording() {
         if (mAudioSource == INTERNAL || mAudioSource == MIC_AND_INTERNAL) {
             mAudio.end();
@@ -337,6 +362,18 @@
     }
 
     /**
+     * Release the resources without saving the data
+     */
+    protected void release() {
+        if (mTempVideoFile != null) {
+            mTempVideoFile.delete();
+        }
+        if (mTempAudioFile != null) {
+            mTempAudioFile.delete();
+        }
+    }
+
+    /**
     * Object representing the recording
     */
     public class SavedRecording {
@@ -362,4 +399,66 @@
             return mThumbnailBitmap;
         }
     }
+
+    interface ScreenMediaRecorderListener {
+        /**
+         * Called to indicate an info or a warning during recording.
+         * See {@link MediaRecorder.OnInfoListener} for the full description.
+         */
+        void onInfo(MediaRecorder mr, int what, int extra);
+
+        /**
+         * Called when the recording stopped by the system.
+         * For example, this might happen when doing partial screen sharing of an app
+         * and the app that is being captured is closed.
+         */
+        void onStopped();
+    }
+
+    /**
+     * Allows to register multiple {@link Closeable} objects and close them all by calling
+     * {@link Closer#close}. If there is an exception thrown during closing of one
+     * of the registered closeables it will continue trying closing the rest closeables.
+     * If there are one or more exceptions thrown they will be re-thrown at the end.
+     * In case of multiple exceptions only the first one will be thrown and all the rest
+     * will be printed.
+     */
+    private static class Closer implements Closeable {
+        private final List<Closeable> mCloseables = new ArrayList<>();
+
+        void register(Closeable closeable) {
+            mCloseables.add(closeable);
+        }
+
+        @Override
+        public void close() throws IOException {
+            Throwable throwable = null;
+
+            for (int i = 0; i < mCloseables.size(); i++) {
+                Closeable closeable = mCloseables.get(i);
+
+                try {
+                    closeable.close();
+                } catch (Throwable e) {
+                    if (throwable == null) {
+                        throwable = e;
+                    } else {
+                        e.printStackTrace();
+                    }
+                }
+            }
+
+            if (throwable != null) {
+                if (throwable instanceof IOException) {
+                    throw (IOException) throwable;
+                }
+
+                if (throwable instanceof RuntimeException) {
+                    throw (RuntimeException) throwable;
+                }
+
+                throw (Error) throwable;
+            }
+        }
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/shade/transition/ShadeTransitionController.kt b/packages/SystemUI/src/com/android/systemui/shade/transition/ShadeTransitionController.kt
index 1c4911d..58acfb4 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/transition/ShadeTransitionController.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/transition/ShadeTransitionController.kt
@@ -7,6 +7,8 @@
 import com.android.systemui.dump.DumpManager
 import com.android.systemui.plugins.qs.QS
 import com.android.systemui.shade.NotificationPanelViewController
+import com.android.systemui.statusbar.StatusBarState
+import com.android.systemui.statusbar.SysuiStatusBarStateController
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController
 import com.android.systemui.statusbar.phone.panelstate.PanelExpansionChangeEvent
 import com.android.systemui.statusbar.phone.panelstate.PanelExpansionStateManager
@@ -27,7 +29,8 @@
     private val context: Context,
     private val splitShadeOverScrollerFactory: SplitShadeOverScroller.Factory,
     private val noOpOverScroller: NoOpOverScroller,
-    private val scrimShadeTransitionController: ScrimShadeTransitionController
+    private val scrimShadeTransitionController: ScrimShadeTransitionController,
+    private val statusBarStateController: SysuiStatusBarStateController,
 ) {
 
     lateinit var notificationPanelViewController: NotificationPanelViewController
@@ -43,7 +46,7 @@
     }
     private val shadeOverScroller: ShadeOverScroller
         get() =
-            if (inSplitShade && propertiesInitialized()) {
+            if (inSplitShade && isScreenUnlocked() && propertiesInitialized()) {
                 splitShadeOverScroller
             } else {
                 noOpOverScroller
@@ -90,6 +93,7 @@
             """
             ShadeTransitionController:
                 inSplitShade: $inSplitShade
+                isScreenUnlocked: ${isScreenUnlocked()}
                 currentPanelState: ${currentPanelState?.panelStateToString()}
                 lastPanelExpansionChangeEvent: $lastPanelExpansionChangeEvent
                 qs.isInitialized: ${this::qs.isInitialized}
@@ -97,4 +101,7 @@
                 nssl.isInitialized: ${this::notificationStackScrollLayoutController.isInitialized}
             """.trimIndent())
     }
+
+    private fun isScreenUnlocked() =
+        statusBarStateController.currentOrUpcomingState == StatusBarState.SHADE
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java
index 587c23b..9e77dbc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java
@@ -68,6 +68,7 @@
 import com.android.systemui.statusbar.phone.ManagedProfileControllerImpl;
 import com.android.systemui.statusbar.phone.StatusBarIconController;
 import com.android.systemui.statusbar.phone.StatusBarIconControllerImpl;
+import com.android.systemui.statusbar.phone.StatusBarIconList;
 import com.android.systemui.statusbar.phone.StatusBarRemoteInputCallback;
 import com.android.systemui.statusbar.phone.ongoingcall.OngoingCallController;
 import com.android.systemui.statusbar.phone.ongoingcall.OngoingCallFlags;
@@ -256,6 +257,16 @@
      */
     @Provides
     @SysUISingleton
+    static StatusBarIconList provideStatusBarIconList(Context context) {
+        return new StatusBarIconList(
+                context.getResources().getStringArray(
+                        com.android.internal.R.array.config_statusBarIcons));
+    }
+
+    /**
+     */
+    @Provides
+    @SysUISingleton
     static OngoingCallController provideOngoingCallController(
             Context context,
             CommonNotifCollection notifCollection,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java
index c86bf93..852d5f7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java
@@ -41,6 +41,7 @@
 import com.android.internal.statusbar.IStatusBarService;
 import com.android.internal.statusbar.NotificationVisibility;
 import com.android.systemui.Dumpable;
+import com.android.systemui.dagger.qualifiers.Background;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.statusbar.NotificationLifetimeExtender;
 import com.android.systemui.statusbar.NotificationListener;
@@ -73,6 +74,7 @@
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
+import java.util.concurrent.Executor;
 
 import dagger.Lazy;
 
@@ -113,6 +115,7 @@
     private final IStatusBarService mStatusBarService;
     private final NotifLiveDataStoreImpl mNotifLiveDataStore;
     private final DumpManager mDumpManager;
+    private final Executor mBgExecutor;
 
     private final Set<NotificationEntry> mAllNotifications = new ArraySet<>();
     private final Set<NotificationEntry> mReadOnlyAllNotifications =
@@ -159,7 +162,8 @@
             LeakDetector leakDetector,
             IStatusBarService statusBarService,
             NotifLiveDataStoreImpl notifLiveDataStore,
-            DumpManager dumpManager
+            DumpManager dumpManager,
+            @Background Executor bgExecutor
     ) {
         mLogger = logger;
         mGroupManager = groupManager;
@@ -170,6 +174,7 @@
         mStatusBarService = statusBarService;
         mNotifLiveDataStore = notifLiveDataStore;
         mDumpManager = dumpManager;
+        mBgExecutor = bgExecutor;
     }
 
     /** Once called, the NEM will start processing notification events from system server. */
@@ -566,17 +571,19 @@
     private void sendNotificationRemovalToServer(
             StatusBarNotification notification,
             DismissedByUserStats dismissedByUserStats) {
-        try {
-            mStatusBarService.onNotificationClear(
-                    notification.getPackageName(),
-                    notification.getUser().getIdentifier(),
-                    notification.getKey(),
-                    dismissedByUserStats.dismissalSurface,
-                    dismissedByUserStats.dismissalSentiment,
-                    dismissedByUserStats.notificationVisibility);
-        } catch (RemoteException ex) {
-            // system process is dead if we're here.
-        }
+        mBgExecutor.execute(() -> {
+            try {
+                mStatusBarService.onNotificationClear(
+                        notification.getPackageName(),
+                        notification.getUser().getIdentifier(),
+                        notification.getKey(),
+                        dismissedByUserStats.dismissalSurface,
+                        dismissedByUserStats.dismissalSentiment,
+                        dismissedByUserStats.notificationVisibility);
+            } catch (RemoteException ex) {
+                // system process is dead if we're here.
+            }
+        });
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java
index 2c85fdd..351a4be 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java
@@ -70,6 +70,7 @@
 import com.android.internal.statusbar.IStatusBarService;
 import com.android.systemui.Dumpable;
 import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.dagger.qualifiers.Background;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.dump.LogBufferEulogizer;
@@ -112,6 +113,7 @@
 import java.util.Objects;
 import java.util.Queue;
 import java.util.Set;
+import java.util.concurrent.Executor;
 import java.util.concurrent.TimeUnit;
 
 import javax.inject.Inject;
@@ -146,6 +148,7 @@
     private final NotifPipelineFlags mNotifPipelineFlags;
     private final NotifCollectionLogger mLogger;
     private final Handler mMainHandler;
+    private final Executor mBgExecutor;
     private final LogBufferEulogizer mEulogizer;
     private final DumpManager mDumpManager;
 
@@ -174,6 +177,7 @@
             NotifPipelineFlags notifPipelineFlags,
             NotifCollectionLogger logger,
             @Main Handler mainHandler,
+            @Background Executor bgExecutor,
             LogBufferEulogizer logBufferEulogizer,
             DumpManager dumpManager) {
         mStatusBarService = statusBarService;
@@ -181,6 +185,7 @@
         mNotifPipelineFlags = notifPipelineFlags;
         mLogger = logger;
         mMainHandler = mainHandler;
+        mBgExecutor = bgExecutor;
         mEulogizer = logBufferEulogizer;
         mDumpManager = dumpManager;
     }
@@ -294,18 +299,20 @@
             entriesToLocallyDismiss.add(entry);
             if (!isCanceled(entry)) {
                 // send message to system server if this notification hasn't already been cancelled
-                try {
-                    mStatusBarService.onNotificationClear(
-                            entry.getSbn().getPackageName(),
-                            entry.getSbn().getUser().getIdentifier(),
-                            entry.getSbn().getKey(),
-                            stats.dismissalSurface,
-                            stats.dismissalSentiment,
-                            stats.notificationVisibility);
-                } catch (RemoteException e) {
-                    // system process is dead if we're here.
-                    mLogger.logRemoteExceptionOnNotificationClear(entry, e);
-                }
+                mBgExecutor.execute(() -> {
+                    try {
+                        mStatusBarService.onNotificationClear(
+                                entry.getSbn().getPackageName(),
+                                entry.getSbn().getUser().getIdentifier(),
+                                entry.getSbn().getKey(),
+                                stats.dismissalSurface,
+                                stats.dismissalSentiment,
+                                stats.notificationVisibility);
+                    } catch (RemoteException e) {
+                        // system process is dead if we're here.
+                        mLogger.logRemoteExceptionOnNotificationClear(entry, e);
+                    }
+                });
             }
         }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java
index 4956a276..bf08fc7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java
@@ -128,7 +128,8 @@
             LeakDetector leakDetector,
             IStatusBarService statusBarService,
             NotifLiveDataStoreImpl notifLiveDataStore,
-            DumpManager dumpManager) {
+            DumpManager dumpManager,
+            @Background Executor bgExecutor) {
         return new NotificationEntryManager(
                 logger,
                 groupManager,
@@ -138,7 +139,8 @@
                 leakDetector,
                 statusBarService,
                 notifLiveDataStore,
-                dumpManager);
+                dumpManager,
+                bgExecutor);
     }
 
     /** Provides an instance of {@link NotificationGutsManager} */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptLogger.kt
index 016b388..99d320d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptLogger.kt
@@ -19,6 +19,7 @@
 import com.android.systemui.log.LogBuffer
 import com.android.systemui.log.LogLevel.DEBUG
 import com.android.systemui.log.LogLevel.INFO
+import com.android.systemui.log.LogLevel.WARNING
 import com.android.systemui.log.dagger.NotificationInterruptLog
 import com.android.systemui.statusbar.notification.collection.NotificationEntry
 import com.android.systemui.statusbar.notification.logKey
@@ -211,6 +212,33 @@
         })
     }
 
+    fun logNoFullscreen(entry: NotificationEntry, reason: String) {
+        buffer.log(TAG, DEBUG, {
+            str1 = entry.logKey
+            str2 = reason
+        }, {
+            "No FullScreenIntent: $str2: $str1"
+        })
+    }
+
+    fun logNoFullscreenWarning(entry: NotificationEntry, reason: String) {
+        buffer.log(TAG, WARNING, {
+            str1 = entry.logKey
+            str2 = reason
+        }, {
+            "No FullScreenIntent: WARNING: $str2: $str1"
+        })
+    }
+
+    fun logFullscreen(entry: NotificationEntry, reason: String) {
+        buffer.log(TAG, DEBUG, {
+            str1 = entry.logKey
+            str2 = reason
+        }, {
+            "FullScreenIntent: $str2: $str1"
+        })
+    }
+
     fun keyguardHideNotification(entry: NotificationEntry) {
         buffer.log(TAG, DEBUG, {
             str1 = entry.logKey
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java
index 2dd95a3..535dc6e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java
@@ -177,9 +177,69 @@
      */
     @Override
     public boolean shouldLaunchFullScreenIntentWhenAdded(NotificationEntry entry) {
-        return entry.getSbn().getNotification().fullScreenIntent != null
-                && (!shouldHeadsUp(entry)
-                || mStatusBarStateController.getState() == StatusBarState.KEYGUARD);
+        if (entry.getSbn().getNotification().fullScreenIntent == null) {
+            return false;
+        }
+
+        // Never show FSI when suppressed by DND
+        if (entry.shouldSuppressFullScreenIntent()) {
+            mLogger.logNoFullscreen(entry, "Suppressed by DND");
+            return false;
+        }
+
+        // Never show FSI if importance is not HIGH
+        if (entry.getImportance() < NotificationManager.IMPORTANCE_HIGH) {
+            mLogger.logNoFullscreen(entry, "Not important enough");
+            return false;
+        }
+
+        // If the notification has suppressive GroupAlertBehavior, block FSI and warn.
+        StatusBarNotification sbn = entry.getSbn();
+        if (sbn.isGroup() && sbn.getNotification().suppressAlertingDueToGrouping()) {
+            // b/231322873: Detect and report an event when a notification has both an FSI and a
+            // suppressive groupAlertBehavior, and now correctly block the FSI from firing.
+            final int uid = entry.getSbn().getUid();
+            android.util.EventLog.writeEvent(0x534e4554, "231322873", uid, "groupAlertBehavior");
+            mLogger.logNoFullscreenWarning(entry, "GroupAlertBehavior will prevent HUN");
+            return false;
+        }
+
+        // If the screen is off, then launch the FullScreenIntent
+        if (!mPowerManager.isInteractive()) {
+            mLogger.logFullscreen(entry, "Device is not interactive");
+            return true;
+        }
+
+        // If the device is currently dreaming, then launch the FullScreenIntent
+        if (isDreaming()) {
+            mLogger.logFullscreen(entry, "Device is dreaming");
+            return true;
+        }
+
+        // If the keyguard is showing, then launch the FullScreenIntent
+        if (mStatusBarStateController.getState() == StatusBarState.KEYGUARD) {
+            mLogger.logFullscreen(entry, "Keyguard is showing");
+            return true;
+        }
+
+        // If the notification should HUN, then we don't need FSI
+        if (shouldHeadsUp(entry)) {
+            mLogger.logNoFullscreen(entry, "Expected to HUN");
+            return false;
+        }
+
+        // If the notification won't HUN for some other reason (DND/snooze/etc), launch FSI.
+        mLogger.logFullscreen(entry, "Expected not to HUN");
+        return true;
+    }
+
+    private boolean isDreaming() {
+        try {
+            return mDreamManager.isDreaming();
+        } catch (RemoteException e) {
+            Log.e(TAG, "Failed to query dream manager.", e);
+            return false;
+        }
     }
 
     private boolean shouldHeadsUpWhenAwake(NotificationEntry entry) {
@@ -223,13 +283,7 @@
             return false;
         }
 
-        boolean isDreaming = false;
-        try {
-            isDreaming = mDreamManager.isDreaming();
-        } catch (RemoteException e) {
-            Log.e(TAG, "Failed to query dream manager.", e);
-        }
-        boolean inUse = mPowerManager.isScreenOn() && !isDreaming;
+        boolean inUse = mPowerManager.isScreenOn() && !isDreaming();
 
         if (!inUse) {
             mLogger.logNoHeadsUpNotInUse(entry);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
index fff7b6a..2d6d846 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
@@ -1709,7 +1709,7 @@
             // Wrap the animation controller to dismiss the shade and set
             // mIsLaunchingActivityOverLockscreen during the animation.
             ActivityLaunchAnimator.Controller delegate = wrapAnimationController(
-                    animationController, dismissShade);
+                    animationController, dismissShade, /* isLaunchForActivity= */ true);
             controller = new DelegateLaunchAnimatorController(delegate) {
                 @Override
                 public void onIntentStarted(boolean willAnimate) {
@@ -2457,7 +2457,7 @@
                         true /* isActivityIntent */);
         ActivityLaunchAnimator.Controller animController =
                 animationController != null ? wrapAnimationController(animationController,
-                        dismissShade) : null;
+                        dismissShade, /* isLaunchForActivity= */ true) : null;
 
         // If we animate, we will dismiss the shade only once the animation is done. This is taken
         // care of by the StatusBarLaunchAnimationController.
@@ -2535,9 +2535,25 @@
                 willLaunchResolverActivity, deferred /* deferred */, animate);
     }
 
+    /**
+     * Return a {@link ActivityLaunchAnimator.Controller} wrapping {@code animationController} so
+     * that:
+     *  - if it launches in the notification shade window and {@code dismissShade} is true, then
+     *    the shade will be instantly dismissed at the end of the animation.
+     *  - if it launches in status bar window, it will make the status bar window match the device
+     *    size during the animation (that way, the animation won't be clipped by the status bar
+     *    size).
+     *
+     * @param animationController the controller that is wrapped and will drive the main animation.
+     * @param dismissShade whether the notification shade will be dismissed at the end of the
+     *                     animation. This is ignored if {@code animationController} is not
+     *                     animating in the shade window.
+     * @param isLaunchForActivity whether the launch is for an activity.
+     */
     @Nullable
     private ActivityLaunchAnimator.Controller wrapAnimationController(
-            ActivityLaunchAnimator.Controller animationController, boolean dismissShade) {
+            ActivityLaunchAnimator.Controller animationController, boolean dismissShade,
+            boolean isLaunchForActivity) {
         View rootView = animationController.getLaunchContainer().getRootView();
 
         Optional<ActivityLaunchAnimator.Controller> controllerFromStatusBar =
@@ -2551,7 +2567,7 @@
             // If the view is not in the status bar, then we are animating a view in the shade.
             // We have to make sure that we collapse it when the animation ends or is cancelled.
             return new StatusBarLaunchAnimatorController(animationController, this,
-                    true /* isLaunchForActivity */);
+                    isLaunchForActivity);
         }
 
         return animationController;
@@ -4083,8 +4099,9 @@
                 // We wrap animationCallback with a StatusBarLaunchAnimatorController so that the
                 // shade is collapsed after the animation (or when it is cancelled, aborted, etc).
                 ActivityLaunchAnimator.Controller controller =
-                        animationController != null ? new StatusBarLaunchAnimatorController(
-                                animationController, this, intent.isActivity()) : null;
+                        animationController != null ? wrapAnimationController(
+                                animationController, /* dismissShade= */ true, intent.isActivity())
+                                : null;
 
                 mActivityLaunchAnimator.startPendingIntentWithAnimation(
                         controller, animate, intent.getCreatorPackage(),
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java
index 2dc3261..a2140c6ab 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java
@@ -13,7 +13,6 @@
  * See the License for the specific language governing permissions and
  * limitations under the License
  */
-
 package com.android.systemui.statusbar.phone;
 
 import static com.android.systemui.statusbar.phone.HeadsUpAppearanceController.CONTENT_FADE_DELAY;
@@ -31,6 +30,7 @@
 import android.util.Property;
 import android.view.ContextThemeWrapper;
 import android.view.View;
+import android.view.ViewGroup;
 import android.view.animation.Interpolator;
 
 import androidx.annotation.VisibleForTesting;
@@ -40,7 +40,6 @@
 import com.android.settingslib.Utils;
 import com.android.systemui.R;
 import com.android.systemui.animation.Interpolators;
-import com.android.systemui.statusbar.AlphaOptimizedFrameLayout;
 import com.android.systemui.statusbar.StatusBarIconView;
 import com.android.systemui.statusbar.notification.stack.AnimationFilter;
 import com.android.systemui.statusbar.notification.stack.AnimationProperties;
@@ -54,7 +53,7 @@
  * A container for notification icons. It handles overflowing icons properly and positions them
  * correctly on the screen.
  */
-public class NotificationIconContainer extends AlphaOptimizedFrameLayout {
+public class NotificationIconContainer extends ViewGroup {
     /**
      * A float value indicating how much before the overflow start the icons should transform into
      * a dot. A value of 0 means that they are exactly at the end and a value of 1 means it starts
@@ -232,6 +231,31 @@
     }
 
     @Override
+    public boolean hasOverlappingRendering() {
+        // Does the same as "AlphaOptimizedFrameLayout".
+        return false;
+    }
+
+    @Override
+    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        final int childCount = getChildCount();
+        final int maxVisibleIcons = getMaxVisibleIcons(childCount);
+        final int width = MeasureSpec.getSize(widthMeasureSpec);
+        final int childWidthSpec = MeasureSpec.makeMeasureSpec(width, MeasureSpec.UNSPECIFIED);
+        int totalWidth = (int) (getActualPaddingStart() + getActualPaddingEnd());
+        for (int i = 0; i < childCount; i++) {
+            View child = getChildAt(i);
+            measureChild(child, childWidthSpec, heightMeasureSpec);
+            if (i <= maxVisibleIcons) {
+                totalWidth += child.getMeasuredWidth();
+            }
+        }
+        final int measuredWidth = resolveSize(totalWidth, widthMeasureSpec);
+        final int measuredHeight = MeasureSpec.getSize(heightMeasureSpec);
+        setMeasuredDimension(measuredWidth, measuredHeight);
+    }
+
+    @Override
     protected void onLayout(boolean changed, int l, int t, int r, int b) {
         float centerY = getHeight() / 2.0f;
         // we layout all our children on the left at the top
@@ -408,8 +432,7 @@
         float translationX = getActualPaddingStart();
         int firstOverflowIndex = -1;
         int childCount = getChildCount();
-        int maxVisibleIcons = mOnLockScreen ? MAX_ICONS_ON_AOD :
-                mIsStaticLayout ? MAX_STATIC_ICONS : childCount;
+        int maxVisibleIcons = getMaxVisibleIcons(childCount);
         float layoutEnd = getLayoutEnd();
         mVisualOverflowStart = 0;
         mFirstVisibleIconState = null;
@@ -493,6 +516,11 @@
         }
     }
 
+    private int getMaxVisibleIcons(int childCount) {
+        return mOnLockScreen ? MAX_ICONS_ON_AOD :
+                mIsStaticLayout ? MAX_STATIC_ICONS : childCount;
+    }
+
     private float getLayoutEnd() {
         return getActualWidth() - getActualPaddingEnd();
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarTransitions.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarTransitions.java
index 2052ee6..15c6dcf 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarTransitions.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarTransitions.java
@@ -31,7 +31,7 @@
 
     private final float mIconAlphaWhenOpaque;
 
-    private View mLeftSide, mStatusIcons, mBattery;
+    private View mStartSide, mStatusIcons, mBattery;
     private Animator mCurrentAnimation;
 
     /**
@@ -41,7 +41,7 @@
         super(backgroundView, R.drawable.status_background);
         final Resources res = statusBarView.getContext().getResources();
         mIconAlphaWhenOpaque = res.getFraction(R.dimen.status_bar_icon_drawing_alpha, 1, 1);
-        mLeftSide = statusBarView.findViewById(R.id.status_bar_left_side);
+        mStartSide = statusBarView.findViewById(R.id.status_bar_start_side_except_heads_up);
         mStatusIcons = statusBarView.findViewById(R.id.statusIcons);
         mBattery = statusBarView.findViewById(R.id.battery);
         applyModeBackground(-1, getMode(), false /*animate*/);
@@ -75,7 +75,7 @@
     }
 
     private void applyMode(int mode, boolean animate) {
-        if (mLeftSide == null) return; // pre-init
+        if (mStartSide == null) return; // pre-init
         float newAlpha = getNonBatteryClockAlphaFor(mode);
         float newAlphaBC = getBatteryClockAlpha(mode);
         if (mCurrentAnimation != null) {
@@ -84,7 +84,7 @@
         if (animate) {
             AnimatorSet anims = new AnimatorSet();
             anims.playTogether(
-                    animateTransitionTo(mLeftSide, newAlpha),
+                    animateTransitionTo(mStartSide, newAlpha),
                     animateTransitionTo(mStatusIcons, newAlpha),
                     animateTransitionTo(mBattery, newAlphaBC)
                     );
@@ -94,7 +94,7 @@
             anims.start();
             mCurrentAnimation = anims;
         } else {
-            mLeftSide.setAlpha(newAlpha);
+            mStartSide.setAlpha(newAlpha);
             mStatusIcons.setAlpha(newAlpha);
             mBattery.setAlpha(newAlphaBC);
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt
index 9da2ef73..f9c4c8f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt
@@ -21,7 +21,6 @@
 import android.view.View
 import android.view.ViewGroup
 import android.view.ViewTreeObserver
-
 import com.android.systemui.R
 import com.android.systemui.shared.animation.UnfoldMoveFromCenterAnimator
 import com.android.systemui.statusbar.phone.userswitcher.StatusBarUserSwitcherController
@@ -32,9 +31,7 @@
 import com.android.systemui.util.ViewController
 import com.android.systemui.util.kotlin.getOrNull
 import com.android.systemui.util.view.ViewUtil
-
 import java.util.Optional
-
 import javax.inject.Inject
 import javax.inject.Named
 
@@ -58,8 +55,8 @@
     override fun onViewAttached() {
         if (moveFromCenterAnimationController == null) return
 
-        val statusBarLeftSide: View = mView.findViewById(R.id.status_bar_left_side)
-        val systemIconArea: ViewGroup = mView.findViewById(R.id.system_icon_area)
+        val statusBarLeftSide: View = mView.findViewById(R.id.status_bar_start_side_except_heads_up)
+        val systemIconArea: ViewGroup = mView.findViewById(R.id.status_bar_end_side_content)
 
         val viewsToAnimate = arrayOf(
             statusBarLeftSide,
@@ -126,11 +123,11 @@
     class StatusBarViewsCenterProvider : UnfoldMoveFromCenterAnimator.ViewCenterProvider {
         override fun getViewCenter(view: View, outPoint: Point) =
             when (view.id) {
-                R.id.status_bar_left_side -> {
+                R.id.status_bar_start_side_except_heads_up -> {
                     // items aligned to the start, return start center point
                     getViewEdgeCenter(view, outPoint, isStart = true)
                 }
-                R.id.system_icon_area -> {
+                R.id.status_bar_end_side_content -> {
                     // items aligned to the end, return end center point
                     getViewEdgeCenter(view, outPoint, isStart = false)
                 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java
index a94c2b7..7c31366 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java
@@ -16,6 +16,8 @@
 
 package com.android.systemui.statusbar.phone;
 
+import static com.android.systemui.statusbar.phone.StatusBarIconList.Slot;
+
 import android.annotation.NonNull;
 import android.content.Context;
 import android.graphics.drawable.Icon;
@@ -56,11 +58,12 @@
  * registered with it.
  */
 @SysUISingleton
-public class StatusBarIconControllerImpl extends StatusBarIconList implements Tunable,
+public class StatusBarIconControllerImpl implements Tunable,
         ConfigurationListener, Dumpable, CommandQueue.Callbacks, StatusBarIconController, DemoMode {
 
     private static final String TAG = "StatusBarIconController";
 
+    private final StatusBarIconList mStatusBarIconList;
     private final ArrayList<IconManager> mIconGroups = new ArrayList<>();
     private final ArraySet<String> mIconHideList = new ArraySet<>();
 
@@ -74,15 +77,12 @@
             DemoModeController demoModeController,
             ConfigurationController configurationController,
             TunerService tunerService,
-            DumpManager dumpManager) {
-        super(context.getResources().getStringArray(
-                com.android.internal.R.array.config_statusBarIcons));
-        configurationController.addCallback(this);
-
+            DumpManager dumpManager,
+            StatusBarIconList statusBarIconList) {
+        mStatusBarIconList = statusBarIconList;
         mContext = context;
 
-        loadDimens();
-
+        configurationController.addCallback(this);
         commandQueue.addCallback(this);
         tunerService.addTunable(this, ICON_HIDE_LIST);
         demoModeController.addCallback(this);
@@ -101,15 +101,14 @@
 
         group.setController(this);
         mIconGroups.add(group);
-        List<Slot> allSlots = getSlots();
+        List<Slot> allSlots = mStatusBarIconList.getSlots();
         for (int i = 0; i < allSlots.size(); i++) {
             Slot slot = allSlots.get(i);
             List<StatusBarIconHolder> holders = slot.getHolderListInViewOrder();
             boolean hidden = mIconHideList.contains(slot.getName());
 
             for (StatusBarIconHolder holder : holders) {
-                int tag = holder.getTag();
-                int viewIndex = getViewIndex(getSlotIndex(slot.getName()), holder.getTag());
+                int viewIndex = mStatusBarIconList.getViewIndex(slot.getName(), holder.getTag());
                 group.onIconAdded(viewIndex, slot.getName(), hidden, holder);
             }
         }
@@ -144,7 +143,7 @@
         }
         mIconHideList.clear();
         mIconHideList.addAll(StatusBarIconController.getIconHideList(mContext, newValue));
-        ArrayList<Slot> currentSlots = getSlots();
+        List<Slot> currentSlots = mStatusBarIconList.getSlots();
         ArrayMap<Slot, List<StatusBarIconHolder>> slotsToReAdd = new ArrayMap<>();
 
         // This is a little hacky... Peel off all of the holders on all of the slots
@@ -163,17 +162,13 @@
             List<StatusBarIconHolder> iconsForSlot = slotsToReAdd.get(item);
             if (iconsForSlot == null) continue;
             for (StatusBarIconHolder holder : iconsForSlot) {
-                setIcon(getSlotIndex(item.getName()), holder);
+                setIcon(item.getName(), holder);
             }
         }
     }
 
-    private void loadDimens() {
-    }
-
-    private void addSystemIcon(int index, StatusBarIconHolder holder) {
-        String slot = getSlotName(index);
-        int viewIndex = getViewIndex(index, holder.getTag());
+    private void addSystemIcon(String slot, StatusBarIconHolder holder) {
+        int viewIndex = mStatusBarIconList.getViewIndex(slot, holder.getTag());
         boolean hidden = mIconHideList.contains(slot);
 
         mIconGroups.forEach(l -> l.onIconAdded(viewIndex, slot, hidden, holder));
@@ -182,18 +177,17 @@
     /** */
     @Override
     public void setIcon(String slot, int resourceId, CharSequence contentDescription) {
-        int index = getSlotIndex(slot);
-        StatusBarIconHolder holder = getIcon(index, 0);
+        StatusBarIconHolder holder = mStatusBarIconList.getIconHolder(slot, 0);
         if (holder == null) {
             StatusBarIcon icon = new StatusBarIcon(UserHandle.SYSTEM, mContext.getPackageName(),
                     Icon.createWithResource(
                             mContext, resourceId), 0, 0, contentDescription);
             holder = StatusBarIconHolder.fromIcon(icon);
-            setIcon(index, holder);
+            setIcon(slot, holder);
         } else {
             holder.getIcon().icon = Icon.createWithResource(mContext, resourceId);
             holder.getIcon().contentDescription = contentDescription;
-            handleSet(index, holder);
+            handleSet(slot, holder);
         }
     }
 
@@ -203,21 +197,18 @@
      */
     @Override
     public void setSignalIcon(String slot, WifiIconState state) {
-
-        int index = getSlotIndex(slot);
-
         if (state == null) {
-            removeIcon(index, 0);
+            removeIcon(slot, 0);
             return;
         }
 
-        StatusBarIconHolder holder = getIcon(index, 0);
+        StatusBarIconHolder holder = mStatusBarIconList.getIconHolder(slot, 0);
         if (holder == null) {
             holder = StatusBarIconHolder.fromWifiIconState(state);
-            setIcon(index, holder);
+            setIcon(slot, holder);
         } else {
             holder.setWifiState(state);
-            handleSet(index, holder);
+            handleSet(slot, holder);
         }
     }
 
@@ -229,8 +220,7 @@
      */
     @Override
     public void setMobileIcons(String slot, List<MobileIconState> iconStates) {
-        Slot mobileSlot = getSlot(slot);
-        int slotIndex = getSlotIndex(slot);
+        Slot mobileSlot = mStatusBarIconList.getSlot(slot);
 
         // Reverse the sort order to show icons with left to right([Slot1][Slot2]..).
         // StatusBarIconList has UI design that first items go to the right of second items.
@@ -241,10 +231,10 @@
             StatusBarIconHolder holder = mobileSlot.getHolderForTag(state.subId);
             if (holder == null) {
                 holder = StatusBarIconHolder.fromMobileIconState(state);
-                setIcon(slotIndex, holder);
+                setIcon(slot, holder);
             } else {
                 holder.setMobileState(state);
-                handleSet(slotIndex, holder);
+                handleSet(slot, holder);
             }
         }
     }
@@ -256,21 +246,19 @@
      */
     @Override
     public void setCallStrengthIcons(String slot, List<CallIndicatorIconState> states) {
-        Slot callStrengthSlot = getSlot(slot);
-        int callStrengthSlotIndex = getSlotIndex(slot);
+        Slot callStrengthSlot = mStatusBarIconList.getSlot(slot);
         Collections.reverse(states);
         for (CallIndicatorIconState state : states) {
             if (!state.isNoCalling) {
                 StatusBarIconHolder holder = callStrengthSlot.getHolderForTag(state.subId);
                 if (holder == null) {
                     holder = StatusBarIconHolder.fromCallIndicatorState(mContext, state);
-                    setIcon(callStrengthSlotIndex, holder);
                 } else {
                     holder.setIcon(new StatusBarIcon(UserHandle.SYSTEM, mContext.getPackageName(),
                             Icon.createWithResource(mContext, state.callStrengthResId), 0, 0,
                             state.callStrengthDescription));
-                    setIcon(callStrengthSlotIndex, holder);
                 }
+                setIcon(slot, holder);
             }
             setIconVisibility(slot, !state.isNoCalling, state.subId);
         }
@@ -283,21 +271,19 @@
      */
     @Override
     public void setNoCallingIcons(String slot, List<CallIndicatorIconState> states) {
-        Slot noCallingSlot = getSlot(slot);
-        int noCallingSlotIndex = getSlotIndex(slot);
+        Slot noCallingSlot = mStatusBarIconList.getSlot(slot);
         Collections.reverse(states);
         for (CallIndicatorIconState state : states) {
             if (state.isNoCalling) {
                 StatusBarIconHolder holder = noCallingSlot.getHolderForTag(state.subId);
                 if (holder == null) {
                     holder = StatusBarIconHolder.fromCallIndicatorState(mContext, state);
-                    setIcon(noCallingSlotIndex, holder);
                 } else {
                     holder.setIcon(new StatusBarIcon(UserHandle.SYSTEM, mContext.getPackageName(),
                             Icon.createWithResource(mContext, state.noCallingResId), 0, 0,
                             state.noCallingDescription));
-                    setIcon(noCallingSlotIndex, holder);
                 }
+                setIcon(slot, holder);
             }
             setIconVisibility(slot, state.isNoCalling, state.subId);
         }
@@ -305,42 +291,31 @@
 
     @Override
     public void setExternalIcon(String slot) {
-        int viewIndex = getViewIndex(getSlotIndex(slot), 0);
+        int viewIndex = mStatusBarIconList.getViewIndex(slot, 0);
         int height = mContext.getResources().getDimensionPixelSize(
                 R.dimen.status_bar_icon_drawing_size);
         mIconGroups.forEach(l -> l.onIconExternal(viewIndex, height));
     }
 
     //TODO: remove this (used in command queue and for 3rd party tiles?)
-    @Override
     public void setIcon(String slot, StatusBarIcon icon) {
-        setIcon(getSlotIndex(slot), icon);
-    }
-
-    /**
-     * For backwards compatibility, in the event that someone gives us a slot and a status bar icon
-     */
-    private void setIcon(int index, StatusBarIcon icon) {
-        String slot = getSlotName(index);
         if (icon == null) {
             removeAllIconsForSlot(slot);
             return;
         }
 
         StatusBarIconHolder holder = StatusBarIconHolder.fromIcon(icon);
-        setIcon(index, holder);
+        setIcon(slot, holder);
     }
 
-    /** */
-    @Override
-    public void setIcon(int index, @NonNull StatusBarIconHolder holder) {
-        boolean isNew = getIcon(index, holder.getTag()) == null;
-        super.setIcon(index, holder);
+    private void setIcon(String slot, @NonNull StatusBarIconHolder holder) {
+        boolean isNew = mStatusBarIconList.getIconHolder(slot, holder.getTag()) == null;
+        mStatusBarIconList.setIcon(slot, holder);
 
         if (isNew) {
-            addSystemIcon(index, holder);
+            addSystemIcon(slot, holder);
         } else {
-            handleSet(index, holder);
+            handleSet(slot, holder);
         }
     }
 
@@ -351,34 +326,33 @@
 
     /** */
     public void setIconVisibility(String slot, boolean visibility, int tag) {
-        int index = getSlotIndex(slot);
-        StatusBarIconHolder holder = getIcon(index, tag);
+        StatusBarIconHolder holder = mStatusBarIconList.getIconHolder(slot, tag);
         if (holder == null || holder.isVisible() == visibility) {
             return;
         }
 
         holder.setVisible(visibility);
-        handleSet(index, holder);
+        handleSet(slot, holder);
     }
 
     /** */
     @Override
     public void setIconAccessibilityLiveRegion(String slotName, int accessibilityLiveRegion) {
-        Slot slot = getSlot(slotName);
+        Slot slot = mStatusBarIconList.getSlot(slotName);
         if (!slot.hasIconsInSlot()) {
             return;
         }
 
-        int slotIndex = getSlotIndex(slotName);
         List<StatusBarIconHolder> iconsToUpdate = slot.getHolderListInViewOrder();
         for (StatusBarIconHolder holder : iconsToUpdate) {
-            int viewIndex = getViewIndex(slotIndex, holder.getTag());
+            int viewIndex = mStatusBarIconList.getViewIndex(slotName, holder.getTag());
             mIconGroups.forEach(l -> l.mGroup.getChildAt(viewIndex)
                     .setAccessibilityLiveRegion(accessibilityLiveRegion));
         }
     }
 
     /** */
+    @Override
     public void removeIcon(String slot) {
         removeAllIconsForSlot(slot);
     }
@@ -386,39 +360,34 @@
     /** */
     @Override
     public void removeIcon(String slot, int tag) {
-        removeIcon(getSlotIndex(slot), tag);
+        if (mStatusBarIconList.getIconHolder(slot, tag) == null) {
+            return;
+        }
+        int viewIndex = mStatusBarIconList.getViewIndex(slot, tag);
+        mStatusBarIconList.removeIcon(slot, tag);
+        mIconGroups.forEach(l -> l.onRemoveIcon(viewIndex));
     }
 
     /** */
     @Override
     public void removeAllIconsForSlot(String slotName) {
-        Slot slot = getSlot(slotName);
+        Slot slot = mStatusBarIconList.getSlot(slotName);
         if (!slot.hasIconsInSlot()) {
             return;
         }
 
-        int slotIndex = getSlotIndex(slotName);
         List<StatusBarIconHolder> iconsToRemove = slot.getHolderListInViewOrder();
         for (StatusBarIconHolder holder : iconsToRemove) {
-            int viewIndex = getViewIndex(slotIndex, holder.getTag());
+            int viewIndex = mStatusBarIconList.getViewIndex(slotName, holder.getTag());
             slot.removeForTag(holder.getTag());
             mIconGroups.forEach(l -> l.onRemoveIcon(viewIndex));
         }
     }
 
-    /** */
-    @Override
-    public void removeIcon(int index, int tag) {
-        if (getIcon(index, tag) == null) {
-            return;
-        }
-        super.removeIcon(index, tag);
-        int viewIndex = getViewIndex(index, 0);
-        mIconGroups.forEach(l -> l.onRemoveIcon(viewIndex));
-    }
 
-    private void handleSet(int index, StatusBarIconHolder holder) {
-        int viewIndex = getViewIndex(index, holder.getTag());
+
+    private void handleSet(String slotName, StatusBarIconHolder holder) {
+        int viewIndex = mStatusBarIconList.getViewIndex(slotName, holder.getTag());
         mIconGroups.forEach(l -> l.onSetIconHolder(viewIndex, holder));
     }
 
@@ -438,7 +407,7 @@
             }
         }
 
-        super.dump(pw);
+        mStatusBarIconList.dump(pw);
     }
 
     /** */
@@ -482,7 +451,6 @@
     /** */
     @Override
     public void onDensityOrFontScaleChanged() {
-        loadDimens();
         refreshIconGroups();
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconList.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconList.java
index c876c32..8800b05 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconList.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconList.java
@@ -25,60 +25,72 @@
 
 import java.io.PrintWriter;
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.List;
 
+/** A class holding the list of all the system icons that could be shown in the status bar. */
 public class StatusBarIconList {
-    private ArrayList<Slot> mSlots = new ArrayList<>();
+    private final ArrayList<Slot> mSlots = new ArrayList<>();
+    private final List<Slot> mViewOnlySlots = Collections.unmodifiableList(mSlots);
 
     public StatusBarIconList(String[] slots) {
         final int N = slots.length;
-        for (int i=0; i < N; i++) {
+        for (int i = 0; i < N; i++) {
             mSlots.add(new Slot(slots[i], null));
         }
     }
 
-    public int getSlotIndex(String slot) {
-        final int N = mSlots.size();
-        for (int i=0; i<N; i++) {
-            Slot item = mSlots.get(i);
-            if (item.getName().equals(slot)) {
-                return i;
-            }
-        }
-        // Auto insert new items at the beginning.
-        mSlots.add(0, new Slot(slot, null));
-        return 0;
+    /** Returns the list of current slots. */
+    public List<Slot> getSlots() {
+        return mViewOnlySlots;
     }
 
-    protected ArrayList<Slot> getSlots() {
-        return new ArrayList<>(mSlots);
+    /**
+     * Gets the slot with the given {@code name}, or creates a new slot if we don't already have a
+     * slot by that name.
+     *
+     * If a new slot is created, that slot will be inserted at the front of the list.
+     *
+     * TODO(b/237533036): Rename this to getOrCreateSlot to make it more clear that it could create
+     *   a new slot. Other methods in this class will also create a new slot if we don't have one,
+     *   should those be re-named too?
+     */
+    public Slot getSlot(String name) {
+        return mSlots.get(findOrInsertSlot(name));
     }
 
-    protected Slot getSlot(String name) {
-        return mSlots.get(getSlotIndex(name));
+    /**
+     * Sets the icon in {@code holder} to be associated with the slot with the given
+     * {@code slotName}.
+     */
+    public void setIcon(String slotName, @NonNull StatusBarIconHolder holder) {
+        mSlots.get(findOrInsertSlot(slotName)).addHolder(holder);
     }
 
-    public int size() {
-        return mSlots.size();
+    /**
+     * Removes the icon holder that we had associated with {@code slotName}'s slot at the given
+     * {@code tag}.
+     */
+    public void removeIcon(String slotName, int tag) {
+        mSlots.get(findOrInsertSlot(slotName)).removeForTag(tag);
     }
 
-    public void setIcon(int index, @NonNull StatusBarIconHolder holder) {
-        mSlots.get(index).addHolder(holder);
+    /**
+     * Returns the icon holder currently associated with {@code slotName}'s slot at the given
+     * {@code tag}, or null if we don't have one.
+     */
+    @Nullable
+    public StatusBarIconHolder getIconHolder(String slotName, int tag) {
+        return mSlots.get(findOrInsertSlot(slotName)).getHolderForTag(tag);
     }
 
-    public void removeIcon(int index, int tag) {
-        mSlots.get(index).removeForTag(tag);
-    }
-
-    public String getSlotName(int index) {
-        return mSlots.get(index).getName();
-    }
-
-    public StatusBarIconHolder getIcon(int index, int tag) {
-        return mSlots.get(index).getHolderForTag(tag);
-    }
-
-    public int getViewIndex(int slotIndex, int tag) {
+    /**
+     * Returns the index of the icon in {@code slotName}'s slot at the given {@code tag}.
+     *
+     * Note that a single slot can have multiple icons, and this function takes that into account.
+     */
+    public int getViewIndex(String slotName, int tag) {
+        int slotIndex = findOrInsertSlot(slotName);
         int count = 0;
         for (int i = 0; i < slotIndex; i++) {
             Slot item = mSlots.get(i);
@@ -100,6 +112,25 @@
         }
     }
 
+    private int findOrInsertSlot(String slot) {
+        final int N = mSlots.size();
+        for (int i = 0; i < N; i++) {
+            Slot item = mSlots.get(i);
+            if (item.getName().equals(slot)) {
+                return i;
+            }
+        }
+        // Auto insert new items at the beginning.
+        mSlots.add(0, new Slot(slot, null));
+        return 0;
+    }
+
+
+    /**
+     * A class representing one slot in the status bar system icons view.
+     *
+     * Note that one slot can have multiple icons associated with it.
+     */
     public static class Slot {
         private final String mName;
         private StatusBarIconHolder mHolder;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java
index 0e7da81..39bf92a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java
@@ -102,7 +102,7 @@
     private final KeyguardStateController mKeyguardStateController;
     private final NotificationPanelViewController mNotificationPanelViewController;
     private final NetworkController mNetworkController;
-    private LinearLayout mSystemIconArea;
+    private LinearLayout mEndSideContent;
     private View mClockView;
     private View mOngoingCallChip;
     private View mNotificationIconAreaInner;
@@ -232,16 +232,16 @@
         mDarkIconManager.setShouldLog(true);
         updateBlockedIcons();
         mStatusBarIconController.addIconGroup(mDarkIconManager);
-        mSystemIconArea = mStatusBar.findViewById(R.id.system_icon_area);
+        mEndSideContent = mStatusBar.findViewById(R.id.status_bar_end_side_content);
         mClockView = mStatusBar.findViewById(R.id.clock);
         mOngoingCallChip = mStatusBar.findViewById(R.id.ongoing_call_chip);
-        showSystemIconArea(false);
+        showEndSideContent(false);
         showClock(false);
         initEmergencyCryptkeeperText();
         initOperatorName();
         initNotificationIconArea();
         mSystemEventAnimator =
-                new StatusBarSystemEventAnimator(mSystemIconArea, getResources());
+                new StatusBarSystemEventAnimator(mEndSideContent, getResources());
         mCarrierConfigTracker.addCallback(mCarrierConfigCallback);
         mCarrierConfigTracker.addDefaultDataSubscriptionChangedListener(mDefaultDataListener);
     }
@@ -370,10 +370,10 @@
         mDisabled2 = state2;
         if ((diff1 & DISABLE_SYSTEM_INFO) != 0 || ((diff2 & DISABLE2_SYSTEM_ICONS) != 0)) {
             if ((state1 & DISABLE_SYSTEM_INFO) != 0 || ((state2 & DISABLE2_SYSTEM_ICONS) != 0)) {
-                hideSystemIconArea(animate);
+                hideEndSideContent(animate);
                 hideOperatorName(animate);
             } else {
-                showSystemIconArea(animate);
+                showEndSideContent(animate);
                 showOperatorName(animate);
             }
         }
@@ -474,15 +474,15 @@
         return mStatusBarHideIconsForBouncerManager.getShouldHideStatusBarIconsForBouncer();
     }
 
-    private void hideSystemIconArea(boolean animate) {
-        animateHide(mSystemIconArea, animate);
+    private void hideEndSideContent(boolean animate) {
+        animateHide(mEndSideContent, animate);
     }
 
-    private void showSystemIconArea(boolean animate) {
+    private void showEndSideContent(boolean animate) {
         // Only show the system icon area if we are not currently animating
         int state = mAnimationScheduler.getAnimationState();
         if (state == IDLE || state == SHOWING_PERSISTENT_DOT) {
-            animateShow(mSystemIconArea, animate);
+            animateShow(mEndSideContent, animate);
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java b/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java
index e08a9d0..708a8ab 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java
@@ -34,7 +34,6 @@
 import android.app.NotificationManager;
 import android.content.Context;
 import android.content.pm.UserInfo;
-import android.content.res.Configuration;
 import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.os.UserHandle;
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
index fa2af56..83b0022 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
@@ -55,9 +55,6 @@
 import com.android.systemui.tracing.ProtoTracer;
 import com.android.systemui.tracing.nano.SystemUiTraceProto;
 import com.android.wm.shell.ShellCommandHandler;
-import com.android.wm.shell.compatui.CompatUI;
-import com.android.wm.shell.draganddrop.DragAndDrop;
-import com.android.wm.shell.hidedisplaycutout.HideDisplayCutout;
 import com.android.wm.shell.nano.WmShellTraceProto;
 import com.android.wm.shell.onehanded.OneHanded;
 import com.android.wm.shell.onehanded.OneHandedEventCallback;
@@ -110,10 +107,7 @@
     private final Optional<Pip> mPipOptional;
     private final Optional<SplitScreen> mSplitScreenOptional;
     private final Optional<OneHanded> mOneHandedOptional;
-    private final Optional<HideDisplayCutout> mHideDisplayCutoutOptional;
     private final Optional<ShellCommandHandler> mShellCommandHandler;
-    private final Optional<CompatUI> mCompatUIOptional;
-    private final Optional<DragAndDrop> mDragAndDropOptional;
 
     private final CommandQueue mCommandQueue;
     private final ConfigurationController mConfigurationController;
@@ -127,10 +121,7 @@
     private final Executor mSysUiMainExecutor;
 
     private boolean mIsSysUiStateValid;
-    private KeyguardUpdateMonitorCallback mSplitScreenKeyguardCallback;
-    private KeyguardUpdateMonitorCallback mPipKeyguardCallback;
     private KeyguardUpdateMonitorCallback mOneHandedKeyguardCallback;
-    private KeyguardStateController.Callback mCompatUIKeyguardCallback;
     private WakefulnessLifecycle.Observer mWakefulnessObserver;
 
     @Inject
@@ -139,10 +130,7 @@
             Optional<Pip> pipOptional,
             Optional<SplitScreen> splitScreenOptional,
             Optional<OneHanded> oneHandedOptional,
-            Optional<HideDisplayCutout> hideDisplayCutoutOptional,
             Optional<ShellCommandHandler> shellCommandHandler,
-            Optional<CompatUI> sizeCompatUIOptional,
-            Optional<DragAndDrop> dragAndDropOptional,
             CommandQueue commandQueue,
             ConfigurationController configurationController,
             KeyguardStateController keyguardStateController,
@@ -164,12 +152,9 @@
         mPipOptional = pipOptional;
         mSplitScreenOptional = splitScreenOptional;
         mOneHandedOptional = oneHandedOptional;
-        mHideDisplayCutoutOptional = hideDisplayCutoutOptional;
         mWakefulnessLifecycle = wakefulnessLifecycle;
         mProtoTracer = protoTracer;
         mShellCommandHandler = shellCommandHandler;
-        mCompatUIOptional = sizeCompatUIOptional;
-        mDragAndDropOptional = dragAndDropOptional;
         mUserInfoController = userInfoController;
         mSysUiMainExecutor = sysUiMainExecutor;
     }
@@ -185,6 +170,22 @@
             }
         });
 
+        // Subscribe to keyguard changes
+        mKeyguardStateController.addCallback(new KeyguardStateController.Callback() {
+            @Override
+            public void onKeyguardShowingChanged() {
+                mShell.onKeyguardVisibilityChanged(mKeyguardStateController.isShowing(),
+                        mKeyguardStateController.isOccluded(),
+                        mKeyguardStateController.isAnimatingBetweenKeyguardAndSurfaceBehind());
+            }
+        });
+        mKeyguardUpdateMonitor.registerCallback(new KeyguardUpdateMonitorCallback() {
+            @Override
+            public void onKeyguardDismissAnimationFinished() {
+                mShell.onKeyguardDismissAnimationFinished();
+            }
+        });
+
         // TODO: Consider piping config change and other common calls to a shell component to
         //  delegate internally
         mProtoTracer.add(this);
@@ -192,7 +193,6 @@
         mPipOptional.ifPresent(this::initPip);
         mSplitScreenOptional.ifPresent(this::initSplitScreen);
         mOneHandedOptional.ifPresent(this::initOneHanded);
-        mCompatUIOptional.ifPresent(this::initCompatUi);
     }
 
     @VisibleForTesting
@@ -204,20 +204,6 @@
             }
         });
 
-        mPipKeyguardCallback = new KeyguardUpdateMonitorCallback() {
-            @Override
-            public void onKeyguardVisibilityChanged(boolean showing) {
-                pip.onKeyguardVisibilityChanged(showing,
-                        mKeyguardStateController.isAnimatingBetweenKeyguardAndSurfaceBehind());
-            }
-
-            @Override
-            public void onKeyguardDismissAnimationFinished() {
-                pip.onKeyguardDismissAnimationFinished();
-            }
-        };
-        mKeyguardUpdateMonitor.registerCallback(mPipKeyguardCallback);
-
         mSysUiState.addCallback(sysUiStateFlag -> {
             mIsSysUiStateValid = (sysUiStateFlag & INVALID_SYSUI_STATE_MASK) == 0;
             pip.onSystemUiStateChanged(mIsSysUiStateValid, sysUiStateFlag);
@@ -230,14 +216,6 @@
 
     @VisibleForTesting
     void initSplitScreen(SplitScreen splitScreen) {
-        mSplitScreenKeyguardCallback = new KeyguardUpdateMonitorCallback() {
-            @Override
-            public void onKeyguardVisibilityChanged(boolean showing) {
-                splitScreen.onKeyguardVisibilityChanged(showing);
-            }
-        };
-        mKeyguardUpdateMonitor.registerCallback(mSplitScreenKeyguardCallback);
-
         mWakefulnessLifecycle.addObserver(new WakefulnessLifecycle.Observer() {
             @Override
             public void onFinishedWakingUp() {
@@ -283,14 +261,9 @@
             }
         });
 
+        // TODO: Either move into ShellInterface or register a receiver on the Shell side directly
         mOneHandedKeyguardCallback = new KeyguardUpdateMonitorCallback() {
             @Override
-            public void onKeyguardVisibilityChanged(boolean showing) {
-                oneHanded.onKeyguardVisibilityChanged(showing);
-                oneHanded.stopOneHanded();
-            }
-
-            @Override
             public void onUserSwitchComplete(int userId) {
                 oneHanded.onUserSwitch(userId);
             }
@@ -340,17 +313,6 @@
         });
     }
 
-    @VisibleForTesting
-    void initCompatUi(CompatUI sizeCompatUI) {
-        mCompatUIKeyguardCallback = new KeyguardStateController.Callback() {
-            @Override
-            public void onKeyguardShowingChanged() {
-                sizeCompatUI.onKeyguardShowingChanged(mKeyguardStateController.isShowing());
-            }
-        };
-        mKeyguardStateController.addCallback(mCompatUIKeyguardCallback);
-    }
-
     @Override
     public void writeToProto(SystemUiTraceProto proto) {
         if (proto.wmShell == null) {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuViewTest.java
index 7e9f84c..bebd871 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuViewTest.java
@@ -191,6 +191,16 @@
     }
 
     @Test
+    public void hideMenuViewWhenStartingAnimation_animatorNotRunning() {
+        mMenuView.show();
+
+        mMenuView.mDragAnimator.start();
+        mMenuView.hide();
+
+        assertThat(mMenuView.mDragAnimator.isRunning()).isFalse();
+    }
+
+    @Test
     public void onTargetsChanged_singleTarget_expectedRadii() {
         final Position alignRightPosition = new Position(1.0f, 0.0f);
         final AccessibilityFloatingMenuView menuView = new AccessibilityFloatingMenuView(mContext,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsControllerImplTest.kt
index 3c7ea4f..c31fd82 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsControllerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsControllerImplTest.kt
@@ -37,9 +37,14 @@
 import com.android.systemui.controls.management.ControlsListingController
 import com.android.systemui.controls.ui.ControlsUiController
 import com.android.systemui.dump.DumpManager
+import com.android.systemui.settings.UserFileManager
 import com.android.systemui.settings.UserTracker
 import com.android.systemui.util.concurrency.FakeExecutor
 import com.android.systemui.util.time.FakeSystemClock
+import com.google.common.truth.Truth.assertThat
+import java.io.File
+import java.util.Optional
+import java.util.function.Consumer
 import org.junit.After
 import org.junit.Assert.assertEquals
 import org.junit.Assert.assertFalse
@@ -50,20 +55,20 @@
 import org.junit.runner.RunWith
 import org.mockito.ArgumentCaptor
 import org.mockito.ArgumentMatchers
+import org.mockito.ArgumentMatchers.anyString
 import org.mockito.Captor
 import org.mockito.Mock
 import org.mockito.Mockito
 import org.mockito.Mockito.anyInt
-import org.mockito.Mockito.`when`
 import org.mockito.Mockito.inOrder
 import org.mockito.Mockito.mock
 import org.mockito.Mockito.never
 import org.mockito.Mockito.reset
+import org.mockito.Mockito.times
 import org.mockito.Mockito.verify
 import org.mockito.Mockito.verifyNoMoreInteractions
+import org.mockito.Mockito.`when`
 import org.mockito.MockitoAnnotations
-import java.util.Optional
-import java.util.function.Consumer
 
 @SmallTest
 @RunWith(AndroidTestingRunner::class)
@@ -85,6 +90,8 @@
     private lateinit var listingController: ControlsListingController
     @Mock(stubOnly = true)
     private lateinit var userTracker: UserTracker
+    @Mock
+    private lateinit var userFileManager: UserFileManager
 
     @Captor
     private lateinit var structureInfoCaptor: ArgumentCaptor<StructureInfo>
@@ -153,6 +160,9 @@
 
         canceller = DidRunRunnable()
         `when`(bindingController.bindAndLoad(any(), any())).thenReturn(canceller)
+        `when`(userFileManager.getFile(anyString(), anyInt())).thenReturn(mock(File::class.java))
+        `when`(userFileManager.getSharedPreferences(anyString(), anyInt(), anyInt()))
+            .thenReturn(context.getSharedPreferences("test", Context.MODE_PRIVATE))
 
         controller = ControlsControllerImpl(
                 wrapper,
@@ -161,6 +171,7 @@
                 bindingController,
                 listingController,
                 broadcastDispatcher,
+                userFileManager,
                 Optional.of(persistenceWrapper),
                 mock(DumpManager::class.java),
                 userTracker
@@ -217,6 +228,7 @@
                 bindingController,
                 listingController,
                 broadcastDispatcher,
+                userFileManager,
                 Optional.of(persistenceWrapper),
                 mock(DumpManager::class.java),
                 userTracker
@@ -911,6 +923,14 @@
 
         assertTrue(controller.getFavoritesForStructure(TEST_COMPONENT_2, TEST_STRUCTURE).isEmpty())
     }
+
+    @Test
+    fun testUserStructure() {
+        val userStructure = UserStructure(context, context.user, userFileManager)
+        verify(userFileManager, times(2))
+            .getFile(ControlsFavoritePersistenceWrapper.FILE_NAME, context.user.identifier)
+        assertThat(userStructure.file).isNotNull()
+    }
 }
 
 private class DidRunRunnable() : Runnable {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/AirQualityColorPickerTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/AirQualityColorPickerTest.java
deleted file mode 100644
index 33be5dc..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/AirQualityColorPickerTest.java
+++ /dev/null
@@ -1,107 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.dreams.complication;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import android.testing.AndroidTestingRunner;
-
-import androidx.test.filters.SmallTest;
-
-import com.android.systemui.SysuiTestCase;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-@SmallTest
-@RunWith(AndroidTestingRunner.class)
-public class AirQualityColorPickerTest extends SysuiTestCase {
-    private static final int DEFAULT_COLOR = 0;
-    private static final int MOCK_COLOR_1 = 1;
-    private static final int MOCK_COLOR_2 = 2;
-    private static final int MOCK_COLOR_3 = 3;
-    private static final int MOCK_COLOR_4 = 4;
-    private static final int MOCK_COLOR_5 = 5;
-
-    private static final int[] MOCK_THRESHOLDS = {-1, 100, 200, 201, 500};
-    private static final int[] MOCK_COLORS =
-            {MOCK_COLOR_1, MOCK_COLOR_2, MOCK_COLOR_3, MOCK_COLOR_4, MOCK_COLOR_5};
-    private static final int[] EMPTY_ARRAY = {};
-
-    @Test
-    public void testEmptyThresholds() {
-        final AirQualityColorPicker colorPicker = new AirQualityColorPicker(
-                EMPTY_ARRAY,
-                MOCK_COLORS,
-                DEFAULT_COLOR);
-        assertThat(colorPicker.getColorForValue("110 AQI")).isEqualTo(DEFAULT_COLOR);
-    }
-
-    @Test
-    public void testEmptyColors() {
-        final AirQualityColorPicker colorPicker = new AirQualityColorPicker(
-                MOCK_THRESHOLDS,
-                EMPTY_ARRAY,
-                DEFAULT_COLOR);
-        assertThat(colorPicker.getColorForValue("110 AQI")).isEqualTo(DEFAULT_COLOR);
-    }
-
-    @Test
-    public void testEmptyAqiString() {
-        final AirQualityColorPicker colorPicker = new AirQualityColorPicker(
-                MOCK_THRESHOLDS,
-                MOCK_COLORS,
-                DEFAULT_COLOR);
-        assertThat(colorPicker.getColorForValue("")).isEqualTo(DEFAULT_COLOR);
-    }
-
-    @Test
-    public void testInvalidAqiString() {
-        final AirQualityColorPicker colorPicker = new AirQualityColorPicker(
-                MOCK_THRESHOLDS,
-                MOCK_COLORS,
-                DEFAULT_COLOR);
-        assertThat(colorPicker.getColorForValue("invalid")).isEqualTo(DEFAULT_COLOR);
-    }
-
-    @Test
-    public void testZeroAirQuality() {
-        final AirQualityColorPicker colorPicker = new AirQualityColorPicker(
-                MOCK_THRESHOLDS,
-                MOCK_COLORS,
-                DEFAULT_COLOR);
-        assertThat(colorPicker.getColorForValue("0 AQI")).isEqualTo(MOCK_COLOR_1);
-    }
-
-    @Test
-    public void testVeryLargeAirQuality() {
-        final AirQualityColorPicker colorPicker = new AirQualityColorPicker(
-                MOCK_THRESHOLDS,
-                MOCK_COLORS,
-                DEFAULT_COLOR);
-        assertThat(colorPicker.getColorForValue("100000 AQI")).isEqualTo(MOCK_COLOR_5);
-    }
-
-    @Test
-    public void testAirQuality200() {
-        final AirQualityColorPicker colorPicker = new AirQualityColorPicker(
-                MOCK_THRESHOLDS,
-                MOCK_COLORS,
-                DEFAULT_COLOR);
-        assertThat(colorPicker.getColorForValue("200 AQI")).isEqualTo(MOCK_COLOR_2);
-    }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/DreamAirQualityComplicationTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/DreamAirQualityComplicationTest.java
deleted file mode 100644
index b8a7059..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/DreamAirQualityComplicationTest.java
+++ /dev/null
@@ -1,91 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.dreams.complication;
-
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.verify;
-
-import android.testing.AndroidTestingRunner;
-import android.widget.TextView;
-
-import androidx.test.filters.SmallTest;
-
-import com.android.systemui.SysuiTestCase;
-import com.android.systemui.dreams.DreamOverlayStateController;
-import com.android.systemui.dreams.complication.DreamAirQualityComplication.DreamAirQualityViewController;
-import com.android.systemui.dreams.smartspace.DreamSmartspaceController;
-import com.android.systemui.plugins.ActivityStarter;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-@SmallTest
-@RunWith(AndroidTestingRunner.class)
-public class DreamAirQualityComplicationTest extends SysuiTestCase {
-    private static final String TRAMPOLINE_COMPONENT = "TestComponent";
-
-    @Mock
-    private DreamSmartspaceController mDreamSmartspaceController;
-
-    @Mock
-    private DreamOverlayStateController mDreamOverlayStateController;
-
-    @Mock
-    private DreamAirQualityComplication mComplication;
-
-    @Mock
-    private AirQualityColorPicker mColorPicker;
-
-    @Before
-    public void setup() {
-        MockitoAnnotations.initMocks(this);
-    }
-
-    /**
-     * Ensures {@link DreamAirQualityComplication} is registered.
-     */
-    @Test
-    public void testComplicationRegistered() {
-        final DreamAirQualityComplication.Registrant registrant =
-                new DreamAirQualityComplication.Registrant(
-                        mContext,
-                        mDreamOverlayStateController,
-                        mComplication);
-        registrant.start();
-        verify(mDreamOverlayStateController).addComplication(eq(mComplication));
-    }
-
-    @Test
-    public void testGetUnfilteredTargets() {
-        final DreamAirQualityViewController controller =
-                new DreamAirQualityViewController(
-                        mock(TextView.class),
-                        mDreamSmartspaceController,
-                        TRAMPOLINE_COMPONENT,
-                        mock(ActivityStarter.class),
-                        mColorPicker);
-        controller.onViewAttached();
-        verify(mDreamSmartspaceController).addUnfilteredListener(any());
-        controller.onViewDetached();
-        verify(mDreamSmartspaceController).removeUnfilteredListener(any());
-    }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/DreamWeatherComplicationTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/DreamWeatherComplicationTest.java
deleted file mode 100644
index a23c4b5..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/DreamWeatherComplicationTest.java
+++ /dev/null
@@ -1,89 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.systemui.dreams.complication;
-
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.verify;
-
-import android.content.Context;
-import android.content.res.Resources;
-import android.testing.AndroidTestingRunner;
-import android.widget.TextView;
-
-import androidx.test.filters.SmallTest;
-
-import com.android.systemui.SysuiTestCase;
-import com.android.systemui.dreams.DreamOverlayStateController;
-import com.android.systemui.dreams.smartspace.DreamSmartspaceController;
-import com.android.systemui.plugins.ActivityStarter;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-@SmallTest
-@RunWith(AndroidTestingRunner.class)
-public class DreamWeatherComplicationTest extends SysuiTestCase {
-    private static final String TRAMPOLINE_COMPONENT = "TestComponent";
-
-    @SuppressWarnings("HidingField")
-    @Mock
-    private Context mContext;
-
-    @Mock
-    private DreamSmartspaceController mDreamSmartspaceController;
-
-    @Mock
-    private DreamOverlayStateController mDreamOverlayStateController;
-
-    @Mock
-    private DreamWeatherComplication mComplication;
-
-    @Before
-    public void setup() {
-        MockitoAnnotations.initMocks(this);
-    }
-
-    /**
-     * Ensures {@link DreamWeatherComplication} is registered.
-     */
-    @Test
-    public void testComplicationRegistered() {
-        final DreamWeatherComplication.Registrant registrant =
-                new DreamWeatherComplication.Registrant(
-                        mContext,
-                        mDreamOverlayStateController,
-                        mComplication);
-        registrant.start();
-        verify(mDreamOverlayStateController).addComplication(eq(mComplication));
-    }
-
-    @Test
-    public void testGetUnfilteredTargets() {
-        final DreamWeatherComplication.DreamWeatherViewController controller =
-                new DreamWeatherComplication.DreamWeatherViewController(mock(
-                        TextView.class), TRAMPOLINE_COMPONENT, mock(ActivityStarter.class),
-                        mDreamSmartspaceController, mock(Resources.class));
-        controller.onViewAttached();
-        verify(mDreamSmartspaceController).addUnfilteredListener(any());
-        controller.onViewDetached();
-        verify(mDreamSmartspaceController).removeUnfilteredListener(any());
-    }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt
index c13c30b..1785022 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt
@@ -561,6 +561,19 @@
     }
 
     @Test
+    fun bindAlbumView_artUsesResource() {
+        val albumArt = Icon.createWithResource(context, R.drawable.ic_android)
+        val state = mediaData.copy(artwork = albumArt)
+
+        player.attachPlayer(viewHolder)
+        player.bindPlayer(state, PACKAGE)
+        bgExecutor.runAllReady()
+        mainExecutor.runAllReady()
+
+        verify(albumView).setImageDrawable(any(Drawable::class.java))
+    }
+
+    @Test
     fun bindAlbumView_setAfterExecutors() {
         val bmp = Bitmap.createBitmap(10, 10, Bitmap.Config.ARGB_8888)
         val canvas = Canvas(bmp)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/people/widget/LaunchConversationActivityTest.java b/packages/SystemUI/tests/src/com/android/systemui/people/widget/LaunchConversationActivityTest.java
index 99f21ad..c8ebd12 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/people/widget/LaunchConversationActivityTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/people/widget/LaunchConversationActivityTest.java
@@ -48,6 +48,8 @@
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection;
 import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider;
+import com.android.systemui.util.concurrency.FakeExecutor;
+import com.android.systemui.util.time.FakeSystemClock;
 import com.android.systemui.wmshell.BubblesManager;
 import com.android.wm.shell.bubbles.Bubble;
 
@@ -106,6 +108,9 @@
 
     private Intent mIntent;
 
+    private FakeSystemClock mFakeSystemClock = new FakeSystemClock();
+    private FakeExecutor mBgExecutor = new FakeExecutor(mFakeSystemClock);
+
     @Before
     public void setUp() throws Exception {
         MockitoAnnotations.initMocks(this);
@@ -114,7 +119,8 @@
                 mNotifCollection,
                 Optional.of(mBubblesManager),
                 mUserManager,
-                mCommandQueue
+                mCommandQueue,
+                mBgExecutor
         );
         verify(mCommandQueue, times(1)).addCallback(mCallbacksCaptor.capture());
 
@@ -193,6 +199,7 @@
         // Ensure callback removed
         verify(mCommandQueue).removeCallback(any());
         // Clear the notification for bubbles.
+        FakeExecutor.exhaustExecutors(mBgExecutor);
         verify(mIStatusBarService, times(1)).onNotificationClear(any(),
                 anyInt(), any(), anyInt(), anyInt(), mNotificationVisibilityCaptor.capture());
         // Do not select the bubble.
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingServiceTest.java b/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingServiceTest.java
index 91cafea..b05d9a3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingServiceTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingServiceTest.java
@@ -16,18 +16,21 @@
 
 package com.android.systemui.screenrecord;
 
+import static org.junit.Assert.assertThrows;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.Mockito.doNothing;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 
 import android.app.Notification;
 import android.app.NotificationManager;
 import android.content.Intent;
+import android.os.Handler;
 import android.os.RemoteException;
 import android.testing.AndroidTestingRunner;
 
@@ -66,6 +69,8 @@
     @Mock
     private Executor mExecutor;
     @Mock
+    private Handler mHandler;
+    @Mock
     private UserContextProvider mUserContextTracker;
     private KeyguardDismissUtil mKeyguardDismissUtil = new KeyguardDismissUtil() {
         public void executeWhenUnlocked(ActivityStarter.OnDismissAction action,
@@ -79,8 +84,8 @@
     @Before
     public void setUp() throws Exception {
         MockitoAnnotations.initMocks(this);
-        mRecordingService = Mockito.spy(new RecordingService(mController, mExecutor, mUiEventLogger,
-                mNotificationManager, mUserContextTracker, mKeyguardDismissUtil));
+        mRecordingService = Mockito.spy(new RecordingService(mController, mExecutor, mHandler,
+                mUiEventLogger, mNotificationManager, mUserContextTracker, mKeyguardDismissUtil));
 
         // Return actual context info
         doReturn(mContext).when(mRecordingService).getApplicationContext();
@@ -143,4 +148,54 @@
         // Then the state is set to not recording
         verify(mController).updateState(false);
     }
+
+    @Test
+    public void testOnSystemRequestedStop_recordingInProgress_endsRecording() throws IOException {
+        doReturn(true).when(mController).isRecording();
+
+        mRecordingService.onStopped();
+
+        verify(mScreenMediaRecorder).end();
+    }
+
+    @Test
+    public void testOnSystemRequestedStop_recordingInProgress_updatesState() {
+        doReturn(true).when(mController).isRecording();
+
+        mRecordingService.onStopped();
+
+        verify(mController).updateState(false);
+    }
+
+    @Test
+    public void testOnSystemRequestedStop_recordingIsNotInProgress_doesNotEndRecording()
+            throws IOException {
+        doReturn(false).when(mController).isRecording();
+
+        mRecordingService.onStopped();
+
+        verify(mScreenMediaRecorder, never()).end();
+    }
+
+    @Test
+    public void testOnSystemRequestedStop_recorderEndThrowsRuntimeException_releasesRecording()
+            throws IOException {
+        doReturn(true).when(mController).isRecording();
+        doThrow(new RuntimeException()).when(mScreenMediaRecorder).end();
+
+        mRecordingService.onStopped();
+
+        verify(mScreenMediaRecorder).release();
+    }
+
+    @Test
+    public void testOnSystemRequestedStop_recorderEndThrowsOOMError_releasesRecording()
+            throws IOException {
+        doReturn(true).when(mController).isRecording();
+        doThrow(new OutOfMemoryError()).when(mScreenMediaRecorder).end();
+
+        assertThrows(Throwable.class, () -> mRecordingService.onStopped());
+
+        verify(mScreenMediaRecorder).release();
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/transition/ShadeTransitionControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/transition/ShadeTransitionControllerTest.kt
index d6faaee..b6f8326 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/transition/ShadeTransitionControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/transition/ShadeTransitionControllerTest.kt
@@ -7,6 +7,8 @@
 import com.android.systemui.dump.DumpManager
 import com.android.systemui.plugins.qs.QS
 import com.android.systemui.shade.NotificationPanelViewController
+import com.android.systemui.statusbar.StatusBarState
+import com.android.systemui.statusbar.SysuiStatusBarStateController
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController
 import com.android.systemui.statusbar.phone.panelstate.PanelExpansionChangeEvent
 import com.android.systemui.statusbar.phone.panelstate.PanelExpansionStateManager
@@ -19,6 +21,7 @@
 import org.mockito.Mockito.reset
 import org.mockito.Mockito.verify
 import org.mockito.Mockito.verifyZeroInteractions
+import org.mockito.Mockito.`when` as whenever
 import org.mockito.MockitoAnnotations
 
 @RunWith(AndroidTestingRunner::class)
@@ -32,6 +35,7 @@
     @Mock private lateinit var splitShadeOverScroller: SplitShadeOverScroller
     @Mock private lateinit var scrimShadeTransitionController: ScrimShadeTransitionController
     @Mock private lateinit var dumpManager: DumpManager
+    @Mock private lateinit var statusBarStateController: SysuiStatusBarStateController
 
     private lateinit var controller: ShadeTransitionController
 
@@ -50,7 +54,9 @@
                 context,
                 splitShadeOverScrollerFactory = { _, _ -> splitShadeOverScroller },
                 noOpOverScroller,
-                scrimShadeTransitionController)
+                scrimShadeTransitionController,
+                statusBarStateController,
+            )
 
         // Resetting as they are notified upon initialization.
         reset(noOpOverScroller, splitShadeOverScroller)
@@ -80,6 +86,45 @@
     }
 
     @Test
+    fun onPanelStateChanged_inSplitShade_onKeyguard_forwardsToNoOpOverScroller() {
+        initLateProperties()
+        enableSplitShade()
+        setOnKeyguard()
+
+        startPanelExpansion()
+
+        verify(noOpOverScroller).onPanelStateChanged(STATE_OPENING)
+        verify(noOpOverScroller).onDragDownAmountChanged(DEFAULT_DRAG_DOWN_AMOUNT)
+        verifyZeroInteractions(splitShadeOverScroller)
+    }
+
+    @Test
+    fun onPanelStateChanged_inSplitShade_onLockedShade_forwardsToNoOpOverScroller() {
+        initLateProperties()
+        enableSplitShade()
+        setOnLockedShade()
+
+        startPanelExpansion()
+
+        verify(noOpOverScroller).onPanelStateChanged(STATE_OPENING)
+        verify(noOpOverScroller).onDragDownAmountChanged(DEFAULT_DRAG_DOWN_AMOUNT)
+        verifyZeroInteractions(splitShadeOverScroller)
+    }
+
+    @Test
+    fun onPanelExpansionChanged_inSplitShade_onUnlockedShade_forwardsToSplitShadeOverScroller() {
+        initLateProperties()
+        enableSplitShade()
+        setOnUnlockedShade()
+
+        startPanelExpansion()
+
+        verify(splitShadeOverScroller).onPanelStateChanged(STATE_OPENING)
+        verify(splitShadeOverScroller).onDragDownAmountChanged(DEFAULT_DRAG_DOWN_AMOUNT)
+        verifyZeroInteractions(noOpOverScroller)
+    }
+
+    @Test
     fun onPanelStateChanged_notInSplitShade_forwardsToNoOpOverScroller() {
         initLateProperties()
         disableSplitShade()
@@ -129,6 +174,23 @@
         )
     }
 
+    private fun setOnKeyguard() {
+        setShadeState(StatusBarState.KEYGUARD)
+    }
+
+    private fun setOnLockedShade() {
+        setShadeState(StatusBarState.SHADE_LOCKED)
+    }
+
+    private fun setOnUnlockedShade() {
+        setShadeState(StatusBarState.SHADE)
+    }
+
+    private fun setShadeState(state: Int) {
+        whenever(statusBarStateController.state).thenReturn(state)
+        whenever(statusBarStateController.currentOrUpcomingState).thenReturn(state)
+    }
+
     companion object {
         private const val DEFAULT_DRAG_DOWN_AMOUNT = 123f
         private val DEFAULT_EXPANSION_EVENT =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt
new file mode 100644
index 0000000..aaa2357
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt
@@ -0,0 +1,209 @@
+/*
+ * 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.shared.clocks
+
+import org.mockito.Mockito.`when` as whenever
+import android.content.Context
+import android.content.ContentResolver
+import android.graphics.drawable.Drawable
+import android.os.Handler
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.plugins.PluginListener
+import com.android.systemui.shared.plugins.PluginManager
+import com.android.systemui.util.mockito.argumentCaptor
+import com.android.systemui.util.mockito.eq
+import junit.framework.Assert.assertEquals
+import junit.framework.Assert.fail
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.junit.MockitoJUnit
+
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+class ClockRegistryTest : SysuiTestCase() {
+
+    @JvmField @Rule val mockito = MockitoJUnit.rule()
+    @Mock private lateinit var mockContext: Context
+    @Mock private lateinit var mockPluginManager: PluginManager
+    @Mock private lateinit var mockClock: Clock
+    @Mock private lateinit var mockThumbnail: Drawable
+    @Mock private lateinit var mockHandler: Handler
+    @Mock private lateinit var mockContentResolver: ContentResolver
+    private lateinit var pluginListener: PluginListener<ClockProviderPlugin>
+    private lateinit var registry: ClockRegistry
+
+    private var settingValue: String = ""
+
+    companion object {
+        private fun failFactory(): Clock {
+            fail("Unexpected call to createClock")
+            return null!!
+        }
+
+        private fun failThumbnail(): Drawable? {
+            fail("Unexpected call to getThumbnail")
+            return null
+        }
+    }
+
+    private class FakeClockPlugin : ClockProviderPlugin {
+        private val metadata = mutableListOf<ClockMetadata>()
+        private val createCallbacks = mutableMapOf<ClockId, () -> Clock>()
+        private val thumbnailCallbacks = mutableMapOf<ClockId, () -> Drawable?>()
+
+        override fun getClocks() = metadata
+        override fun createClock(id: ClockId): Clock = createCallbacks[id]!!()
+        override fun getClockThumbnail(id: ClockId): Drawable? = thumbnailCallbacks[id]!!()
+
+        fun addClock(
+            id: ClockId,
+            name: String,
+            create: () -> Clock = ::failFactory,
+            getThumbnail: () -> Drawable? = ::failThumbnail
+        ) {
+            metadata.add(ClockMetadata(id, name))
+            createCallbacks[id] = create
+            thumbnailCallbacks[id] = getThumbnail
+        }
+    }
+
+    @Before
+    fun setUp() {
+        whenever(mockContext.contentResolver).thenReturn(mockContentResolver)
+
+        val captor = argumentCaptor<PluginListener<ClockProviderPlugin>>()
+        registry = object : ClockRegistry(mockContext, mockPluginManager, mockHandler) {
+            override var currentClockId: ClockId
+                get() = settingValue
+                set(value) { settingValue = value }
+        }
+        verify(mockPluginManager).addPluginListener(captor.capture(),
+            eq(ClockProviderPlugin::class.java))
+        pluginListener = captor.value
+    }
+
+    @Test
+    fun pluginRegistration_CorrectState() {
+        val plugin1 = FakeClockPlugin()
+        plugin1.addClock("clock_1", "clock 1")
+        plugin1.addClock("clock_2", "clock 2")
+
+        val plugin2 = FakeClockPlugin()
+        plugin2.addClock("clock_3", "clock 3")
+        plugin2.addClock("clock_4", "clock 4")
+
+        pluginListener.onPluginConnected(plugin1, mockContext)
+        pluginListener.onPluginConnected(plugin2, mockContext)
+        val list = registry.getClocks()
+        assertEquals(list, listOf(
+            ClockMetadata("clock_1", "clock 1"),
+            ClockMetadata("clock_2", "clock 2"),
+            ClockMetadata("clock_3", "clock 3"),
+            ClockMetadata("clock_4", "clock 4")
+        ))
+    }
+
+    @Test
+    fun clockIdConflict_ErrorWithoutCrash() {
+        val plugin1 = FakeClockPlugin()
+        plugin1.addClock("clock_1", "clock 1", { mockClock }, { mockThumbnail })
+        plugin1.addClock("clock_2", "clock 2", { mockClock }, { mockThumbnail })
+
+        val plugin2 = FakeClockPlugin()
+        plugin2.addClock("clock_1", "clock 1")
+        plugin2.addClock("clock_2", "clock 2")
+
+        pluginListener.onPluginConnected(plugin1, mockContext)
+        pluginListener.onPluginConnected(plugin2, mockContext)
+        val list = registry.getClocks()
+        assertEquals(list, listOf(
+            ClockMetadata("clock_1", "clock 1"),
+            ClockMetadata("clock_2", "clock 2")
+        ))
+
+        assertEquals(registry.createExampleClock("clock_1"), mockClock)
+        assertEquals(registry.createExampleClock("clock_2"), mockClock)
+        assertEquals(registry.getClockThumbnail("clock_1"), mockThumbnail)
+        assertEquals(registry.getClockThumbnail("clock_2"), mockThumbnail)
+    }
+
+    @Test
+    fun createCurrentClock_pluginConnected() {
+        val plugin1 = FakeClockPlugin()
+        plugin1.addClock("clock_1", "clock 1")
+        plugin1.addClock("clock_2", "clock 2")
+
+        settingValue = "clock_3"
+        val plugin2 = FakeClockPlugin()
+        plugin2.addClock("clock_3", "clock 3", { mockClock })
+        plugin2.addClock("clock_4", "clock 4")
+
+        pluginListener.onPluginConnected(plugin1, mockContext)
+        pluginListener.onPluginConnected(plugin2, mockContext)
+
+        val clock = registry.createCurrentClock()
+        assertEquals(clock, mockClock)
+    }
+
+    @Test
+    fun createDefaultClock_pluginDisconnected() {
+        val plugin1 = FakeClockPlugin()
+        plugin1.addClock(DEFAULT_CLOCK_ID, "default", { mockClock })
+        plugin1.addClock("clock_2", "clock 2")
+
+        settingValue = "clock_3"
+        val plugin2 = FakeClockPlugin()
+        plugin2.addClock("clock_3", "clock 3")
+        plugin2.addClock("clock_4", "clock 4")
+
+        pluginListener.onPluginConnected(plugin1, mockContext)
+        pluginListener.onPluginConnected(plugin2, mockContext)
+        pluginListener.onPluginDisconnected(plugin2)
+
+        val clock = registry.createCurrentClock()
+        assertEquals(clock, mockClock)
+    }
+
+    @Test
+    fun pluginRemoved_clockChanged() {
+        val plugin1 = FakeClockPlugin()
+        plugin1.addClock("clock_1", "clock 1")
+        plugin1.addClock("clock_2", "clock 2")
+
+        settingValue = "clock_3"
+        val plugin2 = FakeClockPlugin()
+        plugin2.addClock("clock_3", "clock 3", { mockClock })
+        plugin2.addClock("clock_4", "clock 4")
+
+        pluginListener.onPluginConnected(plugin1, mockContext)
+        pluginListener.onPluginConnected(plugin2, mockContext)
+
+        var changeCallCount = 0
+        registry.registerClockChangeListener({ changeCallCount++ })
+
+        pluginListener.onPluginDisconnected(plugin1)
+        assertEquals(0, changeCallCount)
+
+        pluginListener.onPluginDisconnected(plugin2)
+        assertEquals(1, changeCallCount)
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarIconListTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarIconListTest.java
deleted file mode 100644
index 4c20b61..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarIconListTest.java
+++ /dev/null
@@ -1,175 +0,0 @@
-package com.android.systemui.statusbar;
-
-import static com.android.systemui.statusbar.phone.StatusBarIconController.TAG_PRIMARY;
-
-import static junit.framework.Assert.assertEquals;
-import static junit.framework.Assert.assertNull;
-import static junit.framework.Assert.assertTrue;
-
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
-
-import android.test.suitebuilder.annotation.SmallTest;
-
-import androidx.test.runner.AndroidJUnit4;
-
-import com.android.systemui.SysuiTestCase;
-import com.android.systemui.statusbar.phone.StatusBarIconHolder;
-import com.android.systemui.statusbar.phone.StatusBarIconList;
-import com.android.systemui.statusbar.phone.StatusBarIconList.Slot;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.util.ArrayList;
-import java.util.List;
-
-@SmallTest
-@RunWith(AndroidJUnit4.class)
-public class StatusBarIconListTest extends SysuiTestCase {
-
-    private final static String[] STATUS_BAR_SLOTS = {"aaa", "bbb", "ccc"};
-
-    @Test
-    public void testGetExistingSlot() {
-        StatusBarIconList statusBarIconList = new StatusBarIconList(STATUS_BAR_SLOTS);
-        assertEquals(1, statusBarIconList.getSlotIndex("bbb"));
-        assertEquals(2, statusBarIconList.getSlotIndex("ccc"));
-    }
-
-    @Test
-    public void testGetNonexistingSlot() {
-        StatusBarIconList statusBarIconList = new StatusBarIconList(STATUS_BAR_SLOTS);
-        assertEquals(0, statusBarIconList.getSlotIndex("aaa"));
-        assertEquals(3, statusBarIconList.size());
-        assertEquals(0, statusBarIconList.getSlotIndex("zzz")); // new content added in front
-        assertEquals(1, statusBarIconList.getSlotIndex("aaa")); // slid back
-        assertEquals(4, statusBarIconList.size());
-    }
-
-    @Test
-    public void testAddSlotSlidesIcons() {
-        StatusBarIconList statusBarIconList = new StatusBarIconList(STATUS_BAR_SLOTS);
-        StatusBarIconHolder sbHolder = mock(StatusBarIconHolder.class);
-        statusBarIconList.setIcon(0, sbHolder);
-        statusBarIconList.getSlotIndex("zzz"); // new content added in front
-        assertNull(statusBarIconList.getIcon(0, TAG_PRIMARY));
-        assertEquals(sbHolder, statusBarIconList.getIcon(1, TAG_PRIMARY));
-    }
-
-    @Test
-    public void testGetAndSetIcon() {
-        StatusBarIconList statusBarIconList = new StatusBarIconList(STATUS_BAR_SLOTS);
-        StatusBarIconHolder sbHolderA = mock(StatusBarIconHolder.class);
-        StatusBarIconHolder sbHolderB = mock(StatusBarIconHolder.class);
-        statusBarIconList.setIcon(0, sbHolderA);
-        statusBarIconList.setIcon(1, sbHolderB);
-        assertEquals(sbHolderA, statusBarIconList.getIcon(0, TAG_PRIMARY));
-        assertEquals(sbHolderB, statusBarIconList.getIcon(1, TAG_PRIMARY));
-        assertNull(statusBarIconList.getIcon(2, TAG_PRIMARY)); // icon not set
-    }
-
-    @Test
-    public void testRemoveIcon() {
-        StatusBarIconList statusBarIconList = new StatusBarIconList(STATUS_BAR_SLOTS);
-        StatusBarIconHolder sbHolderA = mock(StatusBarIconHolder.class);
-        StatusBarIconHolder sbHolderB = mock(StatusBarIconHolder.class);
-        statusBarIconList.setIcon(0, sbHolderA);
-        statusBarIconList.setIcon(1, sbHolderB);
-        statusBarIconList.removeIcon(0, TAG_PRIMARY);
-        assertNull(statusBarIconList.getIcon(0, TAG_PRIMARY)); // icon not set
-    }
-
-    @Test
-    public void testGetViewIndex_NoMultiples() {
-        StatusBarIconList statusBarIconList = new StatusBarIconList(STATUS_BAR_SLOTS);
-        StatusBarIconHolder sbHolder = mock(StatusBarIconHolder.class);
-        statusBarIconList.setIcon(2, sbHolder);
-        // Icon for item 2 is 0th child view.
-        assertEquals(0, statusBarIconList.getViewIndex(2, TAG_PRIMARY));
-        statusBarIconList.setIcon(0, sbHolder);
-        // Icon for item 0 is 0th child view,
-        assertEquals(0, statusBarIconList.getViewIndex(0, TAG_PRIMARY));
-        // and item 2 is now 1st child view.
-        assertEquals(1, statusBarIconList.getViewIndex(2, TAG_PRIMARY));
-    }
-
-    @Test
-    public void testGetViewIndex_MultipleIconsPerSlot() {
-        StatusBarIconList statusBarIconList = new StatusBarIconList(STATUS_BAR_SLOTS);
-        StatusBarIconHolder sbHolder = mock(StatusBarIconHolder.class);
-
-        statusBarIconList.setIcon(2, sbHolder); // item 2, one icon 0th child
-
-        // All of these can be added to the same slot
-        // no tag bc it defaults to 0
-        StatusBarIconHolder sbHolder2 = mock(StatusBarIconHolder.class);
-        StatusBarIconHolder sbHolder3 = mock(StatusBarIconHolder.class);
-        int sb3Tag = 1;
-        when(sbHolder3.getTag()).thenReturn(sb3Tag);
-        StatusBarIconHolder sbHolder4 = mock(StatusBarIconHolder.class);
-        int sb4Tag = 2;
-        when(sbHolder4.getTag()).thenReturn(sb4Tag);
-
-        // Put a holder at slot 1, verify that it is first
-        statusBarIconList.setIcon(1, sbHolder2);
-        assertEquals(0, statusBarIconList.getViewIndex(1, TAG_PRIMARY));
-
-        // Put another holder at slot 1, verify it's index 0 and the rest come after
-        statusBarIconList.setIcon(1, sbHolder3);
-        assertEquals(0, statusBarIconList.getViewIndex(1, sb3Tag));
-        assertEquals(1, statusBarIconList.getViewIndex(1, TAG_PRIMARY));
-        // First icon should be at the end
-        assertEquals(2, statusBarIconList.getViewIndex(2, TAG_PRIMARY));
-
-        // Put another one in there just for good measure
-        statusBarIconList.setIcon(1, sbHolder4);
-        assertEquals(0, statusBarIconList.getViewIndex(1, sb4Tag));
-        assertEquals(1, statusBarIconList.getViewIndex(1, sb3Tag));
-        assertEquals(2, statusBarIconList.getViewIndex(1, TAG_PRIMARY));
-        assertEquals(3, statusBarIconList.getViewIndex(2, TAG_PRIMARY));
-    }
-
-    /**
-     * StatusBarIconList.Slot tests
-     */
-
-    @Test
-    public void testSlot_ViewOrder() {
-        Slot testSlot = new Slot("test_name", null);
-
-        // no tag bc it defaults to 0
-        StatusBarIconHolder sbHolder1 = mock(StatusBarIconHolder.class);
-        StatusBarIconHolder sbHolder2 = mock(StatusBarIconHolder.class);
-        int sb2Tag = 1;
-        when(sbHolder2.getTag()).thenReturn(sb2Tag);
-        StatusBarIconHolder sbHolder3 = mock(StatusBarIconHolder.class);
-        int sb3Tag = 2;
-        when(sbHolder3.getTag()).thenReturn(sb3Tag);
-
-        // Add 3 icons in the same slot, and verify that the list we get is equal to what we gave
-        testSlot.addHolder(sbHolder1);
-        testSlot.addHolder(sbHolder2);
-        testSlot.addHolder(sbHolder3);
-
-        // View order is reverse of the order added
-        ArrayList<StatusBarIconHolder> expected = new ArrayList<>();
-        expected.add(sbHolder3);
-        expected.add(sbHolder2);
-        expected.add(sbHolder1);
-
-        assertTrue(listsEqual(expected, testSlot.getHolderListInViewOrder()));
-    }
-
-    private boolean listsEqual(List<StatusBarIconHolder> list1, List<StatusBarIconHolder> list2) {
-        if (list1.size() != list2.size())  return false;
-
-        for (int i = 0; i < list1.size(); i++) {
-            if (!list1.get(i).equals(list2.get(i))) {
-                return false;
-            }
-        }
-
-        return true;
-    }
-}
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java
index 16b0376..aeef6b0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java
@@ -19,6 +19,8 @@
 import static android.app.NotificationManager.IMPORTANCE_DEFAULT;
 import static android.service.notification.NotificationListenerService.NOTIFICATION_CHANNEL_OR_GROUP_UPDATED;
 import static android.service.notification.NotificationListenerService.REASON_CANCEL;
+import static android.service.notification.NotificationStats.DISMISSAL_SHADE;
+import static android.service.notification.NotificationStats.DISMISS_SENTIMENT_NEUTRAL;
 
 import static com.android.systemui.statusbar.notification.NotificationEntryManager.UNDEFINED_DISMISS_REASON;
 
@@ -41,6 +43,7 @@
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
 import static org.mockito.Mockito.when;
 
 import android.app.ActivityManager;
@@ -50,6 +53,7 @@
 import android.content.Intent;
 import android.graphics.drawable.Icon;
 import android.os.Handler;
+import android.os.RemoteException;
 import android.os.UserHandle;
 import android.service.notification.NotificationListenerService.Ranking;
 import android.service.notification.NotificationListenerService.RankingMap;
@@ -91,7 +95,9 @@
 import com.android.systemui.statusbar.notification.row.RowInflaterTask;
 import com.android.systemui.statusbar.policy.DeviceProvisionedController;
 import com.android.systemui.statusbar.policy.HeadsUpManager;
+import com.android.systemui.util.concurrency.FakeExecutor;
 import com.android.systemui.util.leak.LeakDetector;
+import com.android.systemui.util.time.FakeSystemClock;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -138,9 +144,14 @@
     @Mock private NotificationMediaManager mNotificationMediaManager;
     @Mock private NotificationRowBinder mNotificationRowBinder;
     @Mock private NotificationListener mNotificationListener;
+    @Mock private IStatusBarService mStatusBarService;
+
+    private FakeSystemClock mFakeSystemClock = new FakeSystemClock();
+    private FakeExecutor mBgExecutor = new FakeExecutor(mFakeSystemClock);
 
     private int mId;
     private NotificationEntry mEntry;
+    private DismissedByUserStats mStats;
     private StatusBarNotification mSbn;
     private NotificationEntryManager mEntryManager;
 
@@ -191,6 +202,7 @@
                 Handler.createAsync(TestableLooper.get(this).getLooper()));
 
         mEntry = createNotification();
+        mStats = defaultStats(mEntry);
         mSbn = mEntry.getSbn();
 
         when(mNotifPipelineFlags.isNewPipelineEnabled()).thenReturn(false);
@@ -201,9 +213,10 @@
                 () -> mNotificationRowBinder,
                 () -> mRemoteInputManager,
                 mLeakDetector,
-                mock(IStatusBarService.class),
+                mStatusBarService,
                 NotifLiveDataStoreMocksKt.createNotifLiveDataStoreImplMock(),
-                mock(DumpManager.class)
+                mock(DumpManager.class),
+                mBgExecutor
         );
         mEntryManager.initialize(
                 mNotificationListener,
@@ -316,6 +329,31 @@
     }
 
     @Test
+    public void testPerformRemoveNotification_sendRemovalToServer() throws RemoteException {
+        // GIVEN an entry manager with a notification
+        mEntryManager.addActiveNotificationForTest(mEntry);
+
+        // GIVEN interceptor that doesn't intercept
+        when(mRemoveInterceptor.onNotificationRemoveRequested(
+                eq(mEntry.getKey()), argThat(matchEntryOnKey()), anyInt()))
+                .thenReturn(false);
+
+        // WHEN the notification entry is removed
+        mEntryManager.performRemoveNotification(mSbn, mStats, REASON_CANCEL);
+
+        // THEN notification removal is sent to the server
+        FakeExecutor.exhaustExecutors(mBgExecutor);
+        verify(mStatusBarService).onNotificationClear(
+                mSbn.getPackageName(),
+                mSbn.getUser().getIdentifier(),
+                mSbn.getKey(),
+                mStats.dismissalSurface,
+                mStats.dismissalSentiment,
+                mStats.notificationVisibility);
+        verifyNoMoreInteractions(mStatusBarService);
+    }
+
+    @Test
     public void testRemoveNotification_onEntryRemoveNotFiredIfEntryDoesntExist() {
 
         mEntryManager.removeNotification("not_a_real_key", mRankingMap, UNDEFINED_DISMISS_REASON);
@@ -573,23 +611,6 @@
                 any(NotificationVisibility.class), anyBoolean(), eq(UNDEFINED_DISMISS_REASON));
     }
 
-    private NotificationEntry createNotification() {
-        Notification.Builder n = new Notification.Builder(mContext, "id")
-                .setSmallIcon(R.drawable.ic_person)
-                .setContentTitle("Title")
-                .setContentText("Text");
-
-        return new NotificationEntryBuilder()
-                .setPkg(TEST_PACKAGE_NAME)
-                .setOpPkg(TEST_PACKAGE_NAME)
-                .setUid(TEST_UID)
-                .setId(mId++)
-                .setNotification(n.build())
-                .setChannel(new NotificationChannel("id", "", IMPORTANCE_DEFAULT))
-                .setUser(new UserHandle(ActivityManager.getCurrentUser()))
-                .build();
-    }
-
     /* Tests annexed from NotificationDataTest go here */
 
     @Test
@@ -713,4 +734,28 @@
             return mManagedNotifs.contains(notificationKey);
         }
     }
+
+    private NotificationEntry createNotification() {
+        Notification.Builder n = new Notification.Builder(mContext, "id")
+                .setSmallIcon(R.drawable.ic_person)
+                .setContentTitle("Title")
+                .setContentText("Text");
+
+        return new NotificationEntryBuilder()
+                .setPkg(TEST_PACKAGE_NAME)
+                .setOpPkg(TEST_PACKAGE_NAME)
+                .setUid(TEST_UID)
+                .setId(mId++)
+                .setNotification(n.build())
+                .setChannel(new NotificationChannel("id", "", IMPORTANCE_DEFAULT))
+                .setUser(new UserHandle(ActivityManager.getCurrentUser()))
+                .build();
+    }
+
+    private static DismissedByUserStats defaultStats(NotificationEntry entry) {
+        return new DismissedByUserStats(
+                DISMISSAL_SHADE,
+                DISMISS_SENTIMENT_NEUTRAL,
+                NotificationVisibility.obtain(entry.getKey(), 7, 2, true));
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java
index ef763d9..4df99be 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java
@@ -92,6 +92,7 @@
 import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionLogger;
 import com.android.systemui.statusbar.notification.collection.notifcollection.NotifDismissInterceptor;
 import com.android.systemui.statusbar.notification.collection.notifcollection.NotifLifetimeExtender;
+import com.android.systemui.util.concurrency.FakeExecutor;
 import com.android.systemui.util.time.FakeSystemClock;
 
 import org.junit.Before;
@@ -146,6 +147,7 @@
 
     private NoManSimulator mNoMan;
     private FakeSystemClock mClock = new FakeSystemClock();
+    private FakeExecutor mBgExecutor = new FakeExecutor(mClock);
 
     @Before
     public void setUp() {
@@ -162,6 +164,7 @@
                 mNotifPipelineFlags,
                 mLogger,
                 mMainHandler,
+                mBgExecutor,
                 mEulogizer,
                 mock(DumpManager.class));
         mCollection.attach(mGroupCoalescer);
@@ -461,6 +464,8 @@
         DismissedByUserStats stats = defaultStats(entry2);
         mCollection.dismissNotification(entry2, defaultStats(entry2));
 
+        FakeExecutor.exhaustExecutors(mBgExecutor);
+
         // THEN we send the dismissal to system server
         verify(mStatusBarService).onNotificationClear(
                 notif2.sbn.getPackageName(),
@@ -674,6 +679,8 @@
         mInterceptor1.onEndInterceptionCallback.onEndDismissInterception(mInterceptor1, entry,
                 stats);
 
+        FakeExecutor.exhaustExecutors(mBgExecutor);
+
         // THEN we send the dismissal to system server
         verify(mStatusBarService).onNotificationClear(
                 eq(notif.sbn.getPackageName()),
@@ -1211,6 +1218,7 @@
                         new Pair<>(entry2, defaultStats(entry2))));
 
         // THEN we send the dismissals to system server
+        FakeExecutor.exhaustExecutors(mBgExecutor);
         verify(mStatusBarService).onNotificationClear(
                 notif1.sbn.getPackageName(),
                 notif1.sbn.getUser().getIdentifier(),
@@ -1577,6 +1585,7 @@
 
         // WHEN finally dismissing
         onDismiss.run();
+        FakeExecutor.exhaustExecutors(mBgExecutor);
         verify(mStatusBarService).onNotificationClear(any(), anyInt(), eq(notifEvent.key),
                 anyInt(), anyInt(), any());
         verifyNoMoreInteractions(mStatusBarService);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImplTest.java
index 54cbe24..72d3c2e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImplTest.java
@@ -25,6 +25,7 @@
 import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_PEEK;
 
 import static com.android.systemui.statusbar.NotificationEntryHelper.modifyRanking;
+import static com.android.systemui.statusbar.StatusBarState.KEYGUARD;
 import static com.android.systemui.statusbar.StatusBarState.SHADE;
 
 import static com.google.common.truth.Truth.assertThat;
@@ -32,6 +33,7 @@
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
@@ -96,6 +98,8 @@
     NotifPipelineFlags mFlags;
     @Mock
     KeyguardNotificationVisibilityProvider mKeyguardNotificationVisibilityProvider;
+    @Mock
+    PendingIntent mPendingIntent;
 
     private NotificationInterruptStateProviderImpl mNotifInterruptionStateProvider;
 
@@ -197,7 +201,7 @@
         ensureStateForHeadsUpWhenAwake();
 
         // WHEN this entry should be filtered out
-        NotificationEntry entry  = createNotification(IMPORTANCE_DEFAULT);
+        NotificationEntry entry = createNotification(IMPORTANCE_DEFAULT);
         when(mNotificationFilter.shouldFilterOut(entry)).thenReturn(true);
 
         // THEN we shouldn't heads up this entry
@@ -207,7 +211,7 @@
     @Test
     public void testDoNotRunFilterOnNewPipeline() {
         // WHEN this entry should be filtered out
-        NotificationEntry entry  = createNotification(IMPORTANCE_DEFAULT);
+        NotificationEntry entry = createNotification(IMPORTANCE_DEFAULT);
         mNotifInterruptionStateProvider.shouldHeadsUp(entry);
         verify(mNotificationFilter, times(0)).shouldFilterOut(eq(entry));
     }
@@ -422,6 +426,122 @@
         assertThat(mNotifInterruptionStateProvider.shouldHeadsUp(entry)).isFalse();
     }
 
+    @Test
+    public void testShouldNotFullScreen_notPendingIntent() throws RemoteException {
+        NotificationEntry entry = createNotification(IMPORTANCE_HIGH);
+        when(mPowerManager.isInteractive()).thenReturn(true);
+        when(mDreamManager.isDreaming()).thenReturn(false);
+        when(mStatusBarStateController.getState()).thenReturn(SHADE);
+
+        assertThat(mNotifInterruptionStateProvider.shouldLaunchFullScreenIntentWhenAdded(entry))
+                .isFalse();
+        verify(mLogger, never()).logNoFullscreen(any(), any());
+        verify(mLogger, never()).logNoFullscreenWarning(any(), any());
+        verify(mLogger, never()).logFullscreen(any(), any());
+    }
+
+    @Test
+    public void testShouldNotFullScreen_notHighImportance() throws RemoteException {
+        NotificationEntry entry = createFsiNotification(IMPORTANCE_DEFAULT, /* silenced */ false);
+        when(mPowerManager.isInteractive()).thenReturn(true);
+        when(mDreamManager.isDreaming()).thenReturn(false);
+        when(mStatusBarStateController.getState()).thenReturn(SHADE);
+
+        assertThat(mNotifInterruptionStateProvider.shouldLaunchFullScreenIntentWhenAdded(entry))
+                .isFalse();
+        verify(mLogger).logNoFullscreen(entry, "Not important enough");
+        verify(mLogger, never()).logNoFullscreenWarning(any(), any());
+        verify(mLogger, never()).logFullscreen(any(), any());
+    }
+
+    @Test
+    public void testShouldNotFullScreen_isGroupAlertSilenced() throws RemoteException {
+        NotificationEntry entry = createFsiNotification(IMPORTANCE_HIGH, /* silenced */ true);
+        when(mPowerManager.isInteractive()).thenReturn(false);
+        when(mDreamManager.isDreaming()).thenReturn(true);
+        when(mStatusBarStateController.getState()).thenReturn(KEYGUARD);
+
+        assertThat(mNotifInterruptionStateProvider.shouldLaunchFullScreenIntentWhenAdded(entry))
+                .isFalse();
+        verify(mLogger, never()).logNoFullscreen(any(), any());
+        verify(mLogger).logNoFullscreenWarning(entry, "GroupAlertBehavior will prevent HUN");
+        verify(mLogger, never()).logFullscreen(any(), any());
+    }
+
+    @Test
+    public void testShouldFullScreen_notInteractive() throws RemoteException {
+        NotificationEntry entry = createFsiNotification(IMPORTANCE_HIGH, /* silenced */ false);
+        when(mPowerManager.isInteractive()).thenReturn(false);
+        when(mDreamManager.isDreaming()).thenReturn(false);
+        when(mStatusBarStateController.getState()).thenReturn(SHADE);
+
+        assertThat(mNotifInterruptionStateProvider.shouldLaunchFullScreenIntentWhenAdded(entry))
+                .isTrue();
+        verify(mLogger, never()).logNoFullscreen(any(), any());
+        verify(mLogger, never()).logNoFullscreenWarning(any(), any());
+        verify(mLogger).logFullscreen(entry, "Device is not interactive");
+    }
+
+    @Test
+    public void testShouldFullScreen_isDreaming() throws RemoteException {
+        NotificationEntry entry = createFsiNotification(IMPORTANCE_HIGH, /* silenced */ false);
+        when(mPowerManager.isInteractive()).thenReturn(true);
+        when(mDreamManager.isDreaming()).thenReturn(true);
+        when(mStatusBarStateController.getState()).thenReturn(SHADE);
+
+        assertThat(mNotifInterruptionStateProvider.shouldLaunchFullScreenIntentWhenAdded(entry))
+                .isTrue();
+        verify(mLogger, never()).logNoFullscreen(any(), any());
+        verify(mLogger, never()).logNoFullscreenWarning(any(), any());
+        verify(mLogger).logFullscreen(entry, "Device is dreaming");
+    }
+
+    @Test
+    public void testShouldFullScreen_onKeyguard() throws RemoteException {
+        NotificationEntry entry = createFsiNotification(IMPORTANCE_HIGH, /* silenced */ false);
+        when(mPowerManager.isInteractive()).thenReturn(true);
+        when(mDreamManager.isDreaming()).thenReturn(false);
+        when(mStatusBarStateController.getState()).thenReturn(KEYGUARD);
+
+        assertThat(mNotifInterruptionStateProvider.shouldLaunchFullScreenIntentWhenAdded(entry))
+                .isTrue();
+        verify(mLogger, never()).logNoFullscreen(any(), any());
+        verify(mLogger, never()).logNoFullscreenWarning(any(), any());
+        verify(mLogger).logFullscreen(entry, "Keyguard is showing");
+    }
+
+    @Test
+    public void testShouldNotFullScreen_willHun() throws RemoteException {
+        NotificationEntry entry = createFsiNotification(IMPORTANCE_HIGH, /* silenced */ false);
+        when(mPowerManager.isInteractive()).thenReturn(true);
+        when(mPowerManager.isScreenOn()).thenReturn(true);
+        when(mDreamManager.isDreaming()).thenReturn(false);
+        when(mStatusBarStateController.getState()).thenReturn(SHADE);
+
+        assertThat(mNotifInterruptionStateProvider.shouldLaunchFullScreenIntentWhenAdded(entry))
+                .isFalse();
+        verify(mLogger).logNoFullscreen(entry, "Expected to HUN");
+        verify(mLogger, never()).logNoFullscreenWarning(any(), any());
+        verify(mLogger, never()).logFullscreen(any(), any());
+    }
+
+    @Test
+    public void testShouldFullScreen_packageSnoozed() throws RemoteException {
+        NotificationEntry entry = createFsiNotification(IMPORTANCE_HIGH, /* silenced */ false);
+        when(mPowerManager.isInteractive()).thenReturn(true);
+        when(mPowerManager.isScreenOn()).thenReturn(true);
+        when(mDreamManager.isDreaming()).thenReturn(false);
+        when(mStatusBarStateController.getState()).thenReturn(SHADE);
+        when(mHeadsUpManager.isSnoozed("a")).thenReturn(true);
+
+        assertThat(mNotifInterruptionStateProvider.shouldLaunchFullScreenIntentWhenAdded(entry))
+                .isTrue();
+        verify(mLogger).logNoHeadsUpPackageSnoozed(entry);
+        verify(mLogger, never()).logNoFullscreen(any(), any());
+        verify(mLogger, never()).logNoFullscreenWarning(any(), any());
+        verify(mLogger).logFullscreen(entry, "Expected not to HUN");
+    }
+
     /**
      * Bubbles can happen.
      */
@@ -516,8 +636,8 @@
     private NotificationEntry createBubble(String groupKey, Integer groupAlert) {
         Notification.BubbleMetadata data = new Notification.BubbleMetadata.Builder(
                 PendingIntent.getActivity(mContext, 0, new Intent(),
-                    PendingIntent.FLAG_MUTABLE),
-                        Icon.createWithResource(mContext.getResources(), R.drawable.android))
+                        PendingIntent.FLAG_MUTABLE),
+                Icon.createWithResource(mContext.getResources(), R.drawable.android))
                 .build();
         Notification.Builder nb = new Notification.Builder(getContext(), "a")
                 .setContentTitle("title")
@@ -549,6 +669,10 @@
                 .setContentText("content text")
                 .build();
 
+        return createNotification(importance, n);
+    }
+
+    private NotificationEntry createNotification(int importance, Notification n) {
         return new NotificationEntryBuilder()
                 .setPkg("a")
                 .setOpPkg("a")
@@ -559,45 +683,57 @@
                 .build();
     }
 
+    private NotificationEntry createFsiNotification(int importance, boolean silent) {
+        Notification n = new Notification.Builder(getContext(), "a")
+                .setContentTitle("title")
+                .setContentText("content text")
+                .setFullScreenIntent(mPendingIntent, true)
+                .setGroup("fsi")
+                .setGroupAlertBehavior(silent ? GROUP_ALERT_SUMMARY : Notification.GROUP_ALERT_ALL)
+                .build();
+
+        return createNotification(importance, n);
+    }
+
     private final NotificationInterruptSuppressor
             mSuppressAwakeHeadsUp =
             new NotificationInterruptSuppressor() {
-        @Override
-        public String getName() {
-            return "suppressAwakeHeadsUp";
-        }
+                @Override
+                public String getName() {
+                    return "suppressAwakeHeadsUp";
+                }
 
-        @Override
-        public boolean suppressAwakeHeadsUp(NotificationEntry entry) {
-            return true;
-        }
-    };
+                @Override
+                public boolean suppressAwakeHeadsUp(NotificationEntry entry) {
+                    return true;
+                }
+            };
 
     private final NotificationInterruptSuppressor
             mSuppressAwakeInterruptions =
             new NotificationInterruptSuppressor() {
-        @Override
-        public String getName() {
-            return "suppressAwakeInterruptions";
-        }
+                @Override
+                public String getName() {
+                    return "suppressAwakeInterruptions";
+                }
 
-        @Override
-        public boolean suppressAwakeInterruptions(NotificationEntry entry) {
-            return true;
-        }
-    };
+                @Override
+                public boolean suppressAwakeInterruptions(NotificationEntry entry) {
+                    return true;
+                }
+            };
 
     private final NotificationInterruptSuppressor
             mSuppressInterruptions =
             new NotificationInterruptSuppressor() {
-        @Override
-        public String getName() {
-            return "suppressInterruptions";
-        }
+                @Override
+                public String getName() {
+                    return "suppressInterruptions";
+                }
 
-        @Override
-        public boolean suppressInterruptions(NotificationEntry entry) {
-            return true;
-        }
-    };
+                @Override
+                public boolean suppressInterruptions(NotificationEntry entry) {
+                    return true;
+                }
+            };
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationEntryManagerInflationTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationEntryManagerInflationTest.java
index 251ac7d..bf7549a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationEntryManagerInflationTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationEntryManagerInflationTest.java
@@ -194,7 +194,8 @@
                 mLeakDetector,
                 mock(IStatusBarService.class),
                 NotifLiveDataStoreMocksKt.createNotifLiveDataStoreImplMock(),
-                mock(DumpManager.class)
+                mock(DumpManager.class),
+                mBgExecutor
         );
         mEntryManager.initialize(
                 mNotificationListener,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarIconListTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarIconListTest.java
new file mode 100644
index 0000000..f0a4f3f
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarIconListTest.java
@@ -0,0 +1,208 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.phone;
+
+import static com.android.systemui.statusbar.phone.StatusBarIconController.TAG_PRIMARY;
+
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertNull;
+import static junit.framework.Assert.assertTrue;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.statusbar.phone.StatusBarIconList.Slot;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class StatusBarIconListTest extends SysuiTestCase {
+
+    private final static String[] STATUS_BAR_SLOTS = {"aaa", "bbb", "ccc"};
+
+    @Test
+    public void testGetExistingSlot() {
+        StatusBarIconList statusBarIconList = new StatusBarIconList(STATUS_BAR_SLOTS);
+
+        List<Slot> slots = statusBarIconList.getSlots();
+        assertEquals(3, slots.size());
+        assertEquals("aaa", slots.get(0).getName());
+        assertEquals("bbb", slots.get(1).getName());
+        assertEquals("ccc", slots.get(2).getName());
+    }
+
+    @Test
+    public void testGetNonexistingSlot() {
+        StatusBarIconList statusBarIconList = new StatusBarIconList(STATUS_BAR_SLOTS);
+
+        statusBarIconList.getSlot("zzz");
+
+        List<Slot> slots = statusBarIconList.getSlots();
+        assertEquals(4, slots.size());
+        // new content added in front, so zzz should be first and aaa should slide back to second
+        assertEquals("zzz", slots.get(0).getName());
+        assertEquals("aaa", slots.get(1).getName());
+    }
+
+    @Test
+    public void testAddSlotSlidesIcons() {
+        StatusBarIconList statusBarIconList = new StatusBarIconList(STATUS_BAR_SLOTS);
+        StatusBarIconHolder sbHolder = mock(StatusBarIconHolder.class);
+        statusBarIconList.setIcon("aaa", sbHolder);
+
+        statusBarIconList.getSlot("zzz");
+
+        List<Slot> slots = statusBarIconList.getSlots();
+        // new content added in front, so the holder we set on "aaa" should show up at index 1
+        assertNull(slots.get(0).getHolderForTag(TAG_PRIMARY));
+        assertEquals(sbHolder, slots.get(1).getHolderForTag(TAG_PRIMARY));
+    }
+
+    @Test
+    public void testGetAndSetIcon() {
+        StatusBarIconList statusBarIconList = new StatusBarIconList(STATUS_BAR_SLOTS);
+        StatusBarIconHolder sbHolderA = mock(StatusBarIconHolder.class);
+        StatusBarIconHolder sbHolderB = mock(StatusBarIconHolder.class);
+
+        statusBarIconList.setIcon("aaa", sbHolderA);
+        statusBarIconList.setIcon("bbb", sbHolderB);
+
+        assertEquals(sbHolderA, statusBarIconList.getIconHolder("aaa", TAG_PRIMARY));
+        assertEquals(sbHolderB, statusBarIconList.getIconHolder("bbb", TAG_PRIMARY));
+        assertNull(statusBarIconList.getIconHolder("ccc", TAG_PRIMARY)); // icon not set
+    }
+
+    @Test
+    public void testRemoveIcon() {
+        StatusBarIconList statusBarIconList = new StatusBarIconList(STATUS_BAR_SLOTS);
+        StatusBarIconHolder sbHolderA = mock(StatusBarIconHolder.class);
+        StatusBarIconHolder sbHolderB = mock(StatusBarIconHolder.class);
+
+        statusBarIconList.setIcon("aaa", sbHolderA);
+        statusBarIconList.setIcon("bbb", sbHolderB);
+
+        statusBarIconList.removeIcon("aaa", TAG_PRIMARY);
+
+        assertNull(statusBarIconList.getIconHolder("aaa", TAG_PRIMARY)); // icon not set
+    }
+
+    @Test
+    public void testGetViewIndex_NoMultiples() {
+        StatusBarIconList statusBarIconList = new StatusBarIconList(STATUS_BAR_SLOTS);
+        StatusBarIconHolder sbHolder = mock(StatusBarIconHolder.class);
+
+        statusBarIconList.setIcon("ccc", sbHolder);
+
+        // Since only "ccc" has a holder set, it should be first
+        assertEquals(0, statusBarIconList.getViewIndex("ccc", TAG_PRIMARY));
+
+        // Now, also set a holder for "aaa"
+        statusBarIconList.setIcon("aaa", sbHolder);
+
+        // Then "aaa" gets the first view index and "ccc" gets the second
+        assertEquals(0, statusBarIconList.getViewIndex("aaa", TAG_PRIMARY));
+        assertEquals(1, statusBarIconList.getViewIndex("ccc", TAG_PRIMARY));
+    }
+
+    @Test
+    public void testGetViewIndex_MultipleIconsPerSlot() {
+        StatusBarIconList statusBarIconList = new StatusBarIconList(STATUS_BAR_SLOTS);
+        StatusBarIconHolder sbHolder = mock(StatusBarIconHolder.class);
+
+        statusBarIconList.setIcon("ccc", sbHolder);
+
+        // All of these can be added to the same slot
+        // no tag bc it defaults to 0
+        StatusBarIconHolder sbHolder2 = mock(StatusBarIconHolder.class);
+        StatusBarIconHolder sbHolder3 = mock(StatusBarIconHolder.class);
+        int sb3Tag = 1;
+        when(sbHolder3.getTag()).thenReturn(sb3Tag);
+        StatusBarIconHolder sbHolder4 = mock(StatusBarIconHolder.class);
+        int sb4Tag = 2;
+        when(sbHolder4.getTag()).thenReturn(sb4Tag);
+
+        // Put a holder for "bbb", verify that it is first
+        statusBarIconList.setIcon("bbb", sbHolder2);
+        assertEquals(0, statusBarIconList.getViewIndex("bbb", TAG_PRIMARY));
+
+        // Put another holder for "bbb" at slot 1, verify its index 0 and the rest come after
+        statusBarIconList.setIcon("bbb", sbHolder3);
+        assertEquals(0, statusBarIconList.getViewIndex("bbb", sb3Tag));
+        assertEquals(1, statusBarIconList.getViewIndex("bbb", TAG_PRIMARY));
+        // "ccc" should appear at the end
+        assertEquals(2, statusBarIconList.getViewIndex("ccc", TAG_PRIMARY));
+
+        // Put another one in "bbb" just for good measure
+        statusBarIconList.setIcon("bbb", sbHolder4);
+        assertEquals(0, statusBarIconList.getViewIndex("bbb", sb4Tag));
+        assertEquals(1, statusBarIconList.getViewIndex("bbb", sb3Tag));
+        assertEquals(2, statusBarIconList.getViewIndex("bbb", TAG_PRIMARY));
+        assertEquals(3, statusBarIconList.getViewIndex("ccc", TAG_PRIMARY));
+    }
+
+    /**
+     * StatusBarIconList.Slot tests
+     */
+
+    @Test
+    public void testSlot_ViewOrder() {
+        Slot testSlot = new Slot("test_name", null);
+
+        // no tag bc it defaults to 0
+        StatusBarIconHolder sbHolder1 = mock(StatusBarIconHolder.class);
+        StatusBarIconHolder sbHolder2 = mock(StatusBarIconHolder.class);
+        int sb2Tag = 1;
+        when(sbHolder2.getTag()).thenReturn(sb2Tag);
+        StatusBarIconHolder sbHolder3 = mock(StatusBarIconHolder.class);
+        int sb3Tag = 2;
+        when(sbHolder3.getTag()).thenReturn(sb3Tag);
+
+        // Add 3 icons in the same slot, and verify that the list we get is equal to what we gave
+        testSlot.addHolder(sbHolder1);
+        testSlot.addHolder(sbHolder2);
+        testSlot.addHolder(sbHolder3);
+
+        // View order is reverse of the order added
+        ArrayList<StatusBarIconHolder> expected = new ArrayList<>();
+        expected.add(sbHolder3);
+        expected.add(sbHolder2);
+        expected.add(sbHolder1);
+
+        assertTrue(listsEqual(expected, testSlot.getHolderListInViewOrder()));
+    }
+
+    private boolean listsEqual(List<StatusBarIconHolder> list1, List<StatusBarIconHolder> list2) {
+        if (list1.size() != list2.size())  return false;
+
+        for (int i = 0; i < list1.size(); i++) {
+            if (!list1.get(i).equals(list2.get(i))) {
+                return false;
+            }
+        }
+
+        return true;
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java
index 3f14494..681e998 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java
@@ -124,7 +124,7 @@
 
         fragment.disable(DEFAULT_DISPLAY, 0, 0, false);
 
-        assertEquals(View.VISIBLE, getSystemIconAreaView().getVisibility());
+        assertEquals(View.VISIBLE, getEndSideContentView().getVisibility());
         assertEquals(View.VISIBLE, getClockView().getVisibility());
     }
 
@@ -134,11 +134,11 @@
 
         fragment.disable(DEFAULT_DISPLAY, StatusBarManager.DISABLE_SYSTEM_INFO, 0, false);
 
-        assertEquals(View.INVISIBLE, getSystemIconAreaView().getVisibility());
+        assertEquals(View.INVISIBLE, getEndSideContentView().getVisibility());
 
         fragment.disable(DEFAULT_DISPLAY, 0, 0, false);
 
-        assertEquals(View.VISIBLE, getSystemIconAreaView().getVisibility());
+        assertEquals(View.VISIBLE, getEndSideContentView().getVisibility());
     }
 
     @Test
@@ -234,7 +234,7 @@
 
         fragment.disable(DEFAULT_DISPLAY, 0, 0, false);
 
-        assertEquals(View.VISIBLE, getSystemIconAreaView().getVisibility());
+        assertEquals(View.VISIBLE, getEndSideContentView().getVisibility());
         assertEquals(View.VISIBLE, getClockView().getVisibility());
     }
 
@@ -246,7 +246,7 @@
 
         fragment.disable(DEFAULT_DISPLAY, 0, 0, false);
 
-        assertEquals(View.VISIBLE, getSystemIconAreaView().getVisibility());
+        assertEquals(View.VISIBLE, getEndSideContentView().getVisibility());
         assertEquals(View.VISIBLE, getClockView().getVisibility());
     }
 
@@ -257,12 +257,12 @@
         when(mNotificationPanelViewController.hasCustomClock()).thenReturn(true);
 
         // Make sure they start out as visible
-        assertEquals(View.VISIBLE, getSystemIconAreaView().getVisibility());
+        assertEquals(View.VISIBLE, getEndSideContentView().getVisibility());
         assertEquals(View.VISIBLE, getClockView().getVisibility());
 
         fragment.disable(DEFAULT_DISPLAY, 0, 0, false);
 
-        assertEquals(View.INVISIBLE, getSystemIconAreaView().getVisibility());
+        assertEquals(View.INVISIBLE, getEndSideContentView().getVisibility());
         assertEquals(View.GONE, getClockView().getVisibility());
     }
 
@@ -273,14 +273,14 @@
         when(mNotificationPanelViewController.hasCustomClock()).thenReturn(true);
 
         // Make sure they start out as visible
-        assertEquals(View.VISIBLE, getSystemIconAreaView().getVisibility());
+        assertEquals(View.VISIBLE, getEndSideContentView().getVisibility());
         assertEquals(View.VISIBLE, getClockView().getVisibility());
 
         fragment.onDozingChanged(true);
 
         // When this callback is triggered, we want to make sure the clock and system info
         // visibilities are recalculated. Since dozing=true, they shouldn't be visible.
-        assertEquals(View.INVISIBLE, getSystemIconAreaView().getVisibility());
+        assertEquals(View.INVISIBLE, getEndSideContentView().getVisibility());
         assertEquals(View.GONE, getClockView().getVisibility());
     }
 
@@ -419,7 +419,7 @@
         return mFragment.getView().findViewById(R.id.clock);
     }
 
-    private View getSystemIconAreaView() {
-        return mFragment.getView().findViewById(R.id.system_icon_area);
+    private View getEndSideContentView() {
+        return mFragment.getView().findViewById(R.id.status_bar_end_side_content);
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/WMShellTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/WMShellTest.java
index 2b37b9e..72ade26 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/WMShellTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/WMShellTest.java
@@ -24,12 +24,10 @@
 import androidx.test.runner.AndroidJUnit4;
 
 import com.android.keyguard.KeyguardUpdateMonitor;
-import com.android.keyguard.KeyguardUpdateMonitorCallback;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.keyguard.ScreenLifecycle;
 import com.android.systemui.keyguard.WakefulnessLifecycle;
 import com.android.systemui.model.SysUiState;
-import com.android.systemui.navigationbar.NavigationModeController;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
@@ -37,9 +35,6 @@
 import com.android.systemui.tracing.ProtoTracer;
 import com.android.wm.shell.ShellCommandHandler;
 import com.android.wm.shell.common.ShellExecutor;
-import com.android.wm.shell.compatui.CompatUI;
-import com.android.wm.shell.draganddrop.DragAndDrop;
-import com.android.wm.shell.hidedisplaycutout.HideDisplayCutout;
 import com.android.wm.shell.onehanded.OneHanded;
 import com.android.wm.shell.onehanded.OneHandedEventCallback;
 import com.android.wm.shell.onehanded.OneHandedTransitionCallback;
@@ -71,31 +66,25 @@
     @Mock ConfigurationController mConfigurationController;
     @Mock KeyguardStateController mKeyguardStateController;
     @Mock KeyguardUpdateMonitor mKeyguardUpdateMonitor;
-    @Mock NavigationModeController mNavigationModeController;
     @Mock ScreenLifecycle mScreenLifecycle;
     @Mock SysUiState mSysUiState;
     @Mock Pip mPip;
     @Mock SplitScreen mSplitScreen;
     @Mock OneHanded mOneHanded;
-    @Mock HideDisplayCutout mHideDisplayCutout;
     @Mock WakefulnessLifecycle mWakefulnessLifecycle;
     @Mock ProtoTracer mProtoTracer;
     @Mock ShellCommandHandler mShellCommandHandler;
-    @Mock CompatUI mCompatUI;
     @Mock UserInfoController mUserInfoController;
     @Mock ShellExecutor mSysUiMainExecutor;
-    @Mock DragAndDrop mDragAndDrop;
 
     @Before
     public void setUp() {
         MockitoAnnotations.initMocks(this);
 
         mWMShell = new WMShell(mContext, mShellInterface, Optional.of(mPip),
-                Optional.of(mSplitScreen), Optional.of(mOneHanded), Optional.of(mHideDisplayCutout),
-                Optional.of(mShellCommandHandler), Optional.of(mCompatUI),
-                Optional.of(mDragAndDrop),
-                mCommandQueue, mConfigurationController, mKeyguardStateController,
-                mKeyguardUpdateMonitor, mScreenLifecycle, mSysUiState,
+                Optional.of(mSplitScreen), Optional.of(mOneHanded),
+                Optional.of(mShellCommandHandler), mCommandQueue, mConfigurationController,
+                mKeyguardStateController, mKeyguardUpdateMonitor, mScreenLifecycle, mSysUiState,
                 mProtoTracer, mWakefulnessLifecycle, mUserInfoController, mSysUiMainExecutor);
     }
 
@@ -107,27 +96,12 @@
     }
 
     @Test
-    public void initSplitScreen_registersCallbacks() {
-        mWMShell.initSplitScreen(mSplitScreen);
-
-        verify(mKeyguardUpdateMonitor).registerCallback(any(KeyguardUpdateMonitorCallback.class));
-    }
-
-    @Test
     public void initOneHanded_registersCallbacks() {
         mWMShell.initOneHanded(mOneHanded);
 
-        verify(mKeyguardUpdateMonitor).registerCallback(any(KeyguardUpdateMonitorCallback.class));
         verify(mCommandQueue).addCallback(any(CommandQueue.Callbacks.class));
         verify(mScreenLifecycle).addObserver(any(ScreenLifecycle.Observer.class));
         verify(mOneHanded).registerTransitionCallback(any(OneHandedTransitionCallback.class));
         verify(mOneHanded).registerEventCallback(any(OneHandedEventCallback.class));
     }
-
-    @Test
-    public void initCompatUI_registersCallbacks() {
-        mWMShell.initCompatUi(mCompatUI);
-
-        verify(mKeyguardStateController).addCallback(any(KeyguardStateController.Callback.class));
-    }
 }
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 00d728b..07a5fb5 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -14389,6 +14389,19 @@
             final int callingPid = Binder.getCallingPid();
             final int callingUid = Binder.getCallingUid();
 
+            // Non-system callers can't declare that a broadcast is alarm-related.
+            // The PendingIntent invocation case is handled in PendingIntentRecord.
+            if (bOptions != null && callingUid != SYSTEM_UID) {
+                if (bOptions.containsKey(BroadcastOptions.KEY_ALARM_BROADCAST)) {
+                    if (DEBUG_BROADCAST) {
+                        Slog.w(TAG, "Non-system caller " + callingUid
+                                + " may not flag broadcast as alarm-related");
+                    }
+                    throw new SecurityException(
+                            "Non-system callers may not flag broadcasts as alarm-related");
+                }
+            }
+
             final long origId = Binder.clearCallingIdentity();
             try {
                 return broadcastIntentLocked(callerApp,
@@ -14402,6 +14415,7 @@
         }
     }
 
+    // Not the binder call surface
     int broadcastIntentInPackage(String packageName, @Nullable String featureId, int uid,
             int realCallingUid, int realCallingPid, Intent intent, String resolvedType,
             IIntentReceiver resultTo, int resultCode, String resultData, Bundle resultExtras,
diff --git a/services/core/java/com/android/server/am/BroadcastRecord.java b/services/core/java/com/android/server/am/BroadcastRecord.java
index 19ffc173..ae91d75 100644
--- a/services/core/java/com/android/server/am/BroadcastRecord.java
+++ b/services/core/java/com/android/server/am/BroadcastRecord.java
@@ -70,6 +70,7 @@
     final boolean callerInstantApp; // caller is an Instant App?
     final boolean ordered;  // serialize the send to receivers?
     final boolean sticky;   // originated from existing sticky data?
+    final boolean alarm;    // originated from an alarm triggering?
     final boolean initialSticky; // initial broadcast from register to sticky?
     final int userId;       // user id this broadcast was for
     final String resolvedType; // the resolved data type
@@ -305,6 +306,7 @@
         this.allowBackgroundActivityStarts = allowBackgroundActivityStarts;
         mBackgroundActivityStartsToken = backgroundActivityStartsToken;
         this.timeoutExempt = timeoutExempt;
+        alarm = options != null && options.isAlarmBroadcast();
     }
 
     /**
@@ -357,6 +359,7 @@
         allowBackgroundActivityStarts = from.allowBackgroundActivityStarts;
         mBackgroundActivityStartsToken = from.mBackgroundActivityStartsToken;
         timeoutExempt = from.timeoutExempt;
+        alarm = from.alarm;
     }
 
     /**
diff --git a/services/core/java/com/android/server/am/PendingIntentRecord.java b/services/core/java/com/android/server/am/PendingIntentRecord.java
index 4044cce..bda60ff 100644
--- a/services/core/java/com/android/server/am/PendingIntentRecord.java
+++ b/services/core/java/com/android/server/am/PendingIntentRecord.java
@@ -18,6 +18,7 @@
 
 import static android.app.ActivityManager.START_SUCCESS;
 
+import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_BROADCAST_LIGHT;
 import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM;
 import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME;
 
@@ -34,6 +35,7 @@
 import android.os.IBinder;
 import android.os.PowerWhitelistManager;
 import android.os.PowerWhitelistManager.ReasonCode;
+import android.os.Process;
 import android.os.RemoteCallbackList;
 import android.os.RemoteException;
 import android.os.TransactionTooLargeException;
@@ -416,6 +418,22 @@
 
         final int callingUid = Binder.getCallingUid();
         final int callingPid = Binder.getCallingPid();
+
+        // Only system senders can declare a broadcast to be alarm-originated.  We check
+        // this here rather than in the general case handling below to fail before the other
+        // invocation side effects such as allowlisting.
+        if (options != null && callingUid != Process.SYSTEM_UID
+                && key.type == ActivityManager.INTENT_SENDER_BROADCAST) {
+            if (options.containsKey(BroadcastOptions.KEY_ALARM_BROADCAST)) {
+                if (DEBUG_BROADCAST_LIGHT) {
+                    Slog.w(TAG, "Non-system caller " + callingUid
+                            + " may not flag broadcast as alarm-related");
+                }
+                throw new SecurityException(
+                        "Non-system callers may not flag broadcasts as alarm-related");
+            }
+        }
+
         final long origId = Binder.clearCallingIdentity();
 
         int res = START_SUCCESS;
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 0040ea9..90a47a4 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -10373,6 +10373,11 @@
 
         @Override
         public void setAccessibilityServiceUids(IntArray uids) {
+            // TODO(b/233287010): Fix voice interaction and a11y concurrency in audio policy service
+            if (isPlatformAutomotive()) {
+                return;
+            }
+
             synchronized (mAccessibilityServiceUidsLock) {
                 if (uids.size() == 0) {
                     mAccessibilityServiceUids = null;
diff --git a/services/core/java/com/android/server/biometrics/log/BiometricLogger.java b/services/core/java/com/android/server/biometrics/log/BiometricLogger.java
index ad24cf0..262be08 100644
--- a/services/core/java/com/android/server/biometrics/log/BiometricLogger.java
+++ b/services/core/java/com/android/server/biometrics/log/BiometricLogger.java
@@ -28,8 +28,10 @@
 import android.hardware.biometrics.common.OperationContext;
 import android.hardware.face.FaceManager;
 import android.hardware.fingerprint.FingerprintManager;
+import android.util.Log;
 import android.util.Slog;
 
+import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.FrameworkStatsLog;
 import com.android.server.biometrics.Utils;
@@ -41,6 +43,10 @@
 
     public static final String TAG = "BiometricLogger";
     public static final boolean DEBUG = false;
+    private static final Object sLock = new Object();
+
+    @GuardedBy("sLock")
+    private static int sAlsCounter;
 
     private final int mStatsModality;
     private final int mStatsAction;
@@ -345,13 +351,33 @@
             if (!mLightSensorEnabled) {
                 mLightSensorEnabled = true;
                 mLastAmbientLux = 0;
-                mSensorManager.registerListener(mLightSensorListener, lightSensor,
-                        SensorManager.SENSOR_DELAY_NORMAL);
+                int localAlsCounter;
+                synchronized (sLock) {
+                    localAlsCounter = sAlsCounter++;
+                }
+
+                if (localAlsCounter == 0) {
+                    mSensorManager.registerListener(mLightSensorListener, lightSensor,
+                            SensorManager.SENSOR_DELAY_NORMAL);
+                } else {
+                    Slog.e(TAG, "Ignoring request to subscribe to ALSProbe due to non-zero ALS"
+                            + " counter: " + localAlsCounter);
+                    Slog.e(TAG, Log.getStackTraceString(new Throwable()));
+                }
             }
         } else {
             mLightSensorEnabled = false;
             mLastAmbientLux = 0;
             mSensorManager.unregisterListener(mLightSensorListener);
+            int localAlsCounter;
+            synchronized (sLock) {
+                localAlsCounter = --sAlsCounter;
+            }
+            if (localAlsCounter != 0) {
+                Slog.e(TAG, "Non-zero ALS counter after unsubscribing from ALSProbe: "
+                        + localAlsCounter);
+                Slog.e(TAG, Log.getStackTraceString(new Throwable()));
+            }
         }
     }
 }
diff --git a/services/core/java/com/android/server/display/LogicalDisplay.java b/services/core/java/com/android/server/display/LogicalDisplay.java
index 90b9967e..d14902e 100644
--- a/services/core/java/com/android/server/display/LogicalDisplay.java
+++ b/services/core/java/com/android/server/display/LogicalDisplay.java
@@ -522,10 +522,12 @@
         // Set the layer stack.
         device.setLayerStackLocked(t, isBlanked ? BLANK_LAYER_STACK : mLayerStack);
         // Also inform whether the device is the same one sent to inputflinger for its layerstack.
+        // Prevent displays that are disabled from receiving input.
         // TODO(b/188914255): Remove once input can dispatch against device vs layerstack.
         device.setDisplayFlagsLocked(t,
-                device.getDisplayDeviceInfoLocked().touch != TOUCH_NONE
-                        ? SurfaceControl.DISPLAY_RECEIVES_INPUT : 0);
+                (isEnabled() && device.getDisplayDeviceInfoLocked().touch != TOUCH_NONE)
+                        ? SurfaceControl.DISPLAY_RECEIVES_INPUT
+                        : 0);
 
         // Set the color mode and allowed display mode.
         if (device == mPrimaryDisplayDevice) {
diff --git a/services/core/java/com/android/server/dreams/DreamManagerService.java b/services/core/java/com/android/server/dreams/DreamManagerService.java
index 63c5456..7b60345 100644
--- a/services/core/java/com/android/server/dreams/DreamManagerService.java
+++ b/services/core/java/com/android/server/dreams/DreamManagerService.java
@@ -23,6 +23,7 @@
 
 import static com.android.server.wm.ActivityInterceptorCallback.DREAM_MANAGER_ORDERED_ID;
 
+import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.ActivityManager;
 import android.app.TaskInfo;
@@ -45,6 +46,9 @@
 import android.os.Looper;
 import android.os.PowerManager;
 import android.os.PowerManagerInternal;
+import android.os.RemoteException;
+import android.os.ResultReceiver;
+import android.os.ShellCallback;
 import android.os.SystemClock;
 import android.os.SystemProperties;
 import android.os.UserHandle;
@@ -221,6 +225,10 @@
         }
     }
 
+    protected void requestStartDreamFromShell() {
+        requestDreamInternal();
+    }
+
     private void requestDreamInternal() {
         // Ask the power manager to nap.  It will eventually call back into
         // startDream() if/when it is appropriate to start dreaming.
@@ -275,6 +283,10 @@
         }
     }
 
+    protected void requestStopDreamFromShell() {
+        stopDreamInternal(true, "stopping dream from shell");
+    }
+
     private void stopDreamInternal(boolean immediate, String reason) {
         synchronized (mLock) {
             stopDreamLocked(immediate, reason);
@@ -593,6 +605,14 @@
             }
         }
 
+        public void onShellCommand(@Nullable FileDescriptor in, @Nullable FileDescriptor out,
+                @Nullable FileDescriptor err,
+                @NonNull String[] args, @Nullable ShellCallback callback,
+                @NonNull ResultReceiver resultReceiver) throws RemoteException {
+            new DreamShellCommand(DreamManagerService.this, mPowerManager)
+                    .exec(this, in, out, err, args, callback, resultReceiver);
+        }
+
         @Override // Binder call
         public ComponentName[] getDreamComponents() {
             return getDreamComponentsForUser(UserHandle.getCallingUserId());
diff --git a/services/core/java/com/android/server/dreams/DreamShellCommand.java b/services/core/java/com/android/server/dreams/DreamShellCommand.java
new file mode 100644
index 0000000..eae7e80
--- /dev/null
+++ b/services/core/java/com/android/server/dreams/DreamShellCommand.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.dreams;
+
+import android.annotation.NonNull;
+import android.os.Binder;
+import android.os.PowerManager;
+import android.os.Process;
+import android.os.ShellCommand;
+import android.os.SystemClock;
+import android.text.TextUtils;
+import android.util.Slog;
+
+import java.io.PrintWriter;
+
+/**
+ * {@link DreamShellCommand} allows accessing dream functionality, including toggling dream state.
+ */
+public class DreamShellCommand extends ShellCommand {
+    private static final boolean DEBUG = true;
+    private static final String TAG = "DreamShellCommand";
+    private final @NonNull DreamManagerService mService;
+    private final @NonNull PowerManager mPowerManager;
+
+    DreamShellCommand(@NonNull DreamManagerService service, @NonNull PowerManager powerManager) {
+        mService = service;
+        mPowerManager = powerManager;
+    }
+
+    @Override
+    public int onCommand(String cmd) {
+        final int callingUid = Binder.getCallingUid();
+        if (callingUid != Process.ROOT_UID) {
+            Slog.e(TAG, "Must be root before calling Dream shell commands");
+            return -1;
+        }
+
+        if (TextUtils.isEmpty(cmd)) {
+            return super.handleDefaultCommands(cmd);
+        }
+        if (DEBUG) {
+            Slog.d(TAG, "onCommand:" + cmd);
+        }
+
+        switch (cmd) {
+            case "start-dreaming":
+                return startDreaming();
+            case "stop-dreaming":
+                return stopDreaming();
+            default:
+                return super.handleDefaultCommands(cmd);
+        }
+    }
+
+    private int startDreaming() {
+        mPowerManager.wakeUp(SystemClock.uptimeMillis(),
+                PowerManager.WAKE_REASON_PLUGGED_IN, "shell:cmd:android.service.dreams:DREAM");
+        mService.requestStartDreamFromShell();
+        return 0;
+    }
+
+    private int stopDreaming() {
+        mService.requestStopDreamFromShell();
+        return 0;
+    }
+
+    @Override
+    public void onHelp() {
+        PrintWriter pw = getOutPrintWriter();
+        pw.println("Dream manager (dreams) commands:");
+        pw.println("  help");
+        pw.println("      Print this help text.");
+        pw.println("  start-dreaming");
+        pw.println("      Start the currently configured dream.");
+        pw.println("  stop-dreaming");
+        pw.println("      Stops any active dream");
+    }
+}
diff --git a/services/core/java/com/android/server/input/GestureMonitorSpyWindow.java b/services/core/java/com/android/server/input/GestureMonitorSpyWindow.java
index e222c64..d238dae 100644
--- a/services/core/java/com/android/server/input/GestureMonitorSpyWindow.java
+++ b/services/core/java/com/android/server/input/GestureMonitorSpyWindow.java
@@ -27,8 +27,6 @@
 import android.view.SurfaceControl;
 import android.view.WindowManager;
 
-import com.android.server.policy.WindowManagerPolicy;
-
 /**
  * An internal implementation of an {@link InputMonitor} that uses a spy window.
  *
@@ -69,9 +67,7 @@
 
         final SurfaceControl.Transaction t = new SurfaceControl.Transaction();
         t.setInputWindowInfo(mInputSurface, mWindowHandle);
-        // Gesture monitor should be above handwriting event surface, hence setting it to
-        // WindowManagerPolicy.INPUT_DISPLAY_OVERLAY_LAYER + 1
-        t.setLayer(mInputSurface, WindowManagerPolicy.INPUT_DISPLAY_OVERLAY_LAYER + 1);
+        t.setLayer(mInputSurface, Integer.MAX_VALUE);
         t.setPosition(mInputSurface, 0, 0);
         t.setCrop(mInputSurface, null /* crop to parent surface */);
         t.show(mInputSurface);
diff --git a/services/core/java/com/android/server/inputmethod/HandwritingEventReceiverSurface.java b/services/core/java/com/android/server/inputmethod/HandwritingEventReceiverSurface.java
index 5438faa..8180e66 100644
--- a/services/core/java/com/android/server/inputmethod/HandwritingEventReceiverSurface.java
+++ b/services/core/java/com/android/server/inputmethod/HandwritingEventReceiverSurface.java
@@ -27,8 +27,6 @@
 import android.view.SurfaceControl;
 import android.view.WindowManager;
 
-import com.android.server.policy.WindowManagerPolicy;
-
 final class HandwritingEventReceiverSurface {
 
     public static final String TAG = HandwritingEventReceiverSurface.class.getSimpleName();
@@ -38,8 +36,7 @@
     // is above gesture monitors, then edge-back and swipe-up gestures won't work when this surface
     // is intercepting.
     // TODO(b/217538817): Specify the ordering in WM by usage.
-    private static final int HANDWRITING_SURFACE_LAYER =
-            WindowManagerPolicy.INPUT_DISPLAY_OVERLAY_LAYER;
+    private static final int HANDWRITING_SURFACE_LAYER = Integer.MAX_VALUE - 1;
 
     private final InputWindowHandle mWindowHandle;
     private final InputChannel mClientChannel;
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 9486a45..e589080 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -2459,11 +2459,11 @@
         SnoozeHelper snoozeHelper = new SnoozeHelper(getContext(), (userId, r, muteOnReturn) -> {
             try {
                 if (DBG) {
-                    Slog.d(TAG, "Reposting " + r.getKey());
+                    Slog.d(TAG, "Reposting " + r.getKey() + " " + muteOnReturn);
                 }
                 enqueueNotificationInternal(r.getSbn().getPackageName(), r.getSbn().getOpPkg(),
                         r.getSbn().getUid(), r.getSbn().getInitialPid(), r.getSbn().getTag(),
-                        r.getSbn().getId(),  r.getSbn().getNotification(), userId, true);
+                        r.getSbn().getId(),  r.getSbn().getNotification(), userId, muteOnReturn);
             } catch (Exception e) {
                 Slog.e(TAG, "Cannot un-snooze notification", e);
             }
diff --git a/services/core/java/com/android/server/policy/WindowManagerPolicy.java b/services/core/java/com/android/server/policy/WindowManagerPolicy.java
index 9bc0553..e8a3dcd 100644
--- a/services/core/java/com/android/server/policy/WindowManagerPolicy.java
+++ b/services/core/java/com/android/server/policy/WindowManagerPolicy.java
@@ -155,10 +155,6 @@
     int FINISH_LAYOUT_REDO_ANIM = 0x0008;
     /** Layer for the screen off animation */
     int COLOR_FADE_LAYER = 0x40000001;
-    /** Layer for Input overlays for capturing inputs for gesture detection, etc. */
-    int INPUT_DISPLAY_OVERLAY_LAYER = 0x7f000000;
-    /** Layer for Screen Decoration: The top most visible layer just below input overlay layers */
-    int SCREEN_DECOR_DISPLAY_OVERLAY_LAYER = INPUT_DISPLAY_OVERLAY_LAYER - 1;
 
     /**
      * Register shortcuts for window manager to dispatch.
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index fe66f7a..d9c509c 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -161,6 +161,7 @@
 import android.annotation.Nullable;
 import android.app.ActivityManager;
 import android.app.ActivityManagerInternal;
+import android.content.ComponentName;
 import android.content.Context;
 import android.content.pm.ActivityInfo;
 import android.content.pm.ActivityInfo.ScreenOrientation;
@@ -6520,12 +6521,13 @@
         /**
          * Notifies the remote insets controller that the top focused window has changed.
          *
-         * @param packageName The name of the package that is open in the top focused window.
+         * @param component The application component that is open in the top focussed window.
          * @param requestedVisibilities The insets visibilities requested by the focussed window.
          */
-        void topFocusedWindowChanged(String packageName, InsetsVisibilities requestedVisibilities) {
+        void topFocusedWindowChanged(ComponentName component,
+                InsetsVisibilities requestedVisibilities) {
             try {
-                mRemoteInsetsController.topFocusedWindowChanged(packageName, requestedVisibilities);
+                mRemoteInsetsController.topFocusedWindowChanged(component, requestedVisibilities);
             } catch (RemoteException e) {
                 Slog.w(TAG, "Failed to deliver package in top focused window change", e);
             }
diff --git a/services/core/java/com/android/server/wm/InputManagerCallback.java b/services/core/java/com/android/server/wm/InputManagerCallback.java
index b7ddbd0..33cdd2e 100644
--- a/services/core/java/com/android/server/wm/InputManagerCallback.java
+++ b/services/core/java/com/android/server/wm/InputManagerCallback.java
@@ -265,7 +265,7 @@
                     .setContainerLayer()
                     .setName(name)
                     .setCallsite("createSurfaceForGestureMonitor")
-                    .setParent(dc.getOverlayLayer())
+                    .setParent(dc.getSurfaceControl())
                     .build();
         }
     }
diff --git a/services/core/java/com/android/server/wm/InsetsPolicy.java b/services/core/java/com/android/server/wm/InsetsPolicy.java
index 620a56d..3e2d7c9 100644
--- a/services/core/java/com/android/server/wm/InsetsPolicy.java
+++ b/services/core/java/com/android/server/wm/InsetsPolicy.java
@@ -46,6 +46,7 @@
 import android.app.ActivityTaskManager;
 import android.app.StatusBarManager;
 import android.app.WindowConfiguration;
+import android.content.ComponentName;
 import android.content.res.Resources;
 import android.graphics.Rect;
 import android.util.ArrayMap;
@@ -548,8 +549,10 @@
             return focusedWin;
         }
         if (remoteInsetsControllerControlsSystemBars(focusedWin)) {
+            ComponentName component = focusedWin.mActivityRecord != null
+                    ? focusedWin.mActivityRecord.mActivityComponent : null;
             mDisplayContent.mRemoteInsetsControlTarget.topFocusedWindowChanged(
-                    focusedWin.mAttrs.packageName, focusedWin.getRequestedVisibilities());
+                    component, focusedWin.getRequestedVisibilities());
             return mDisplayContent.mRemoteInsetsControlTarget;
         }
         if (mPolicy.areSystemBarsForcedShownLw()) {
@@ -606,8 +609,10 @@
             return null;
         }
         if (remoteInsetsControllerControlsSystemBars(focusedWin)) {
+            ComponentName component = focusedWin.mActivityRecord != null
+                    ? focusedWin.mActivityRecord.mActivityComponent : null;
             mDisplayContent.mRemoteInsetsControlTarget.topFocusedWindowChanged(
-                    focusedWin.mAttrs.packageName, focusedWin.getRequestedVisibilities());
+                    component, focusedWin.getRequestedVisibilities());
             return mDisplayContent.mRemoteInsetsControlTarget;
         }
         if (mPolicy.areSystemBarsForcedShownLw()) {
diff --git a/services/core/java/com/android/server/wm/RecentsAnimationController.java b/services/core/java/com/android/server/wm/RecentsAnimationController.java
index 08bf7bc..53f1fe6 100644
--- a/services/core/java/com/android/server/wm/RecentsAnimationController.java
+++ b/services/core/java/com/android/server/wm/RecentsAnimationController.java
@@ -352,10 +352,6 @@
             }
         }
 
-        // TODO(b/166736352): Remove this method without the need to expose to launcher.
-        @Override
-        public void hideCurrentInputMethod() { }
-
         @Override
         public void setDeferCancelUntilNextTransition(boolean defer, boolean screenshot) {
             synchronized (mService.mGlobalLock) {
diff --git a/services/core/java/com/android/server/wm/ScreenRotationAnimation.java b/services/core/java/com/android/server/wm/ScreenRotationAnimation.java
index f80e732..8cad165 100644
--- a/services/core/java/com/android/server/wm/ScreenRotationAnimation.java
+++ b/services/core/java/com/android/server/wm/ScreenRotationAnimation.java
@@ -173,11 +173,6 @@
 
             if (isSizeChanged) {
                 mRoundedCornerOverlay = displayContent.findRoundedCornerOverlays();
-            } else {
-                // Exclude rounded corner overlay from screenshot buffer. Rounded
-                // corner overlay windows are un-rotated during rotation animation
-                // for a seamless transition.
-                builder.setExcludeLayers(displayContent.findRoundedCornerOverlays());
             }
 
             SurfaceControl.ScreenshotHardwareBuffer screenshotBuffer =
@@ -600,7 +595,7 @@
         }
 
         private SurfaceAnimator startDisplayRotation() {
-            return startAnimation(initializeBuilder()
+            SurfaceAnimator animator = startAnimation(initializeBuilder()
                             .setAnimationLeashParent(mDisplayContent.getSurfaceControl())
                             .setSurfaceControl(mDisplayContent.getWindowingLayer())
                             .setParentSurfaceControl(mDisplayContent.getSurfaceControl())
@@ -609,6 +604,13 @@
                             .build(),
                     createWindowAnimationSpec(mRotateEnterAnimation),
                     this::onAnimationEnd);
+
+            // Crop the animation leash to avoid extended wallpaper from showing over
+            // mBackColorSurface
+            Rect displayBounds = mDisplayContent.getBounds();
+            mDisplayContent.getPendingTransaction()
+                    .setWindowCrop(animator.mLeash, displayBounds.width(), displayBounds.height());
+            return animator;
         }
 
         private SurfaceAnimator startScreenshotAlphaAnimation() {
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index d31dfee..9d6e250 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -8262,7 +8262,7 @@
                         .setContainerLayer()
                         .setName("IME Handwriting Surface")
                         .setCallsite("getHandwritingSurfaceForDisplay")
-                        .setParent(dc.getOverlayLayer())
+                        .setParent(dc.getSurfaceControl())
                         .build();
             }
         }
diff --git a/services/core/java/com/android/server/wm/WindowToken.java b/services/core/java/com/android/server/wm/WindowToken.java
index 437c934..bbb21f8 100644
--- a/services/core/java/com/android/server/wm/WindowToken.java
+++ b/services/core/java/com/android/server/wm/WindowToken.java
@@ -362,7 +362,7 @@
     @Override
     void assignLayer(SurfaceControl.Transaction t, int layer) {
         if (mRoundedCornerOverlay) {
-            super.assignLayer(t, WindowManagerPolicy.SCREEN_DECOR_DISPLAY_OVERLAY_LAYER);
+            super.assignLayer(t, WindowManagerPolicy.COLOR_FADE_LAYER + 1);
         } else {
             super.assignLayer(t, layer);
         }
@@ -372,7 +372,7 @@
     SurfaceControl.Builder makeSurface() {
         final SurfaceControl.Builder builder = super.makeSurface();
         if (mRoundedCornerOverlay) {
-            builder.setParent(getDisplayContent().getOverlayLayer());
+            builder.setParent(null);
         }
         return builder;
     }
diff --git a/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java
index 4942464..67ef7f5 100644
--- a/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java
@@ -722,6 +722,25 @@
     }
 
     @Test
+    public void testAlarmBroadcastOption() throws Exception {
+        final long triggerTime = mNowElapsedTest + 5000;
+        final PendingIntent alarmPi = getNewMockPendingIntent();
+        setTestAlarm(ELAPSED_REALTIME_WAKEUP, triggerTime, alarmPi);
+
+        mNowElapsedTest = mTestTimer.getElapsed();
+        mTestTimer.expire();
+
+        final ArgumentCaptor<PendingIntent.OnFinished> onFinishedCaptor =
+                ArgumentCaptor.forClass(PendingIntent.OnFinished.class);
+        final ArgumentCaptor<Bundle> optionsCaptor = ArgumentCaptor.forClass(Bundle.class);
+        verify(alarmPi).send(eq(mMockContext), eq(0), any(Intent.class),
+                onFinishedCaptor.capture(), any(Handler.class), isNull(),
+                optionsCaptor.capture());
+        assertTrue(optionsCaptor.getValue()
+                .getBoolean(BroadcastOptions.KEY_ALARM_BROADCAST, false));
+    }
+
+    @Test
     public void testUpdateConstants() {
         setDeviceConfigLong(KEY_MIN_FUTURITY, 5);
         setDeviceConfigLong(KEY_MIN_INTERVAL, 10);
diff --git a/services/tests/servicestests/src/com/android/server/display/LogicalDisplayTest.java b/services/tests/servicestests/src/com/android/server/display/LogicalDisplayTest.java
index ece0a627..b0738fd 100644
--- a/services/tests/servicestests/src/com/android/server/display/LogicalDisplayTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/LogicalDisplayTest.java
@@ -17,7 +17,11 @@
 package com.android.server.display;
 
 import static org.junit.Assert.assertEquals;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.eq;
 import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
 import android.app.PropertyInvalidatedCache;
@@ -45,18 +49,21 @@
 
     private LogicalDisplay mLogicalDisplay;
     private DisplayDevice mDisplayDevice;
+    private final DisplayDeviceInfo mDisplayDeviceInfo = new DisplayDeviceInfo();
 
     @Before
     public void setUp() {
         // Share classloader to allow package private access.
         System.setProperty("dexmaker.share_classloader", "true");
         mDisplayDevice = mock(DisplayDevice.class);
-        DisplayDeviceInfo displayDeviceInfo = new DisplayDeviceInfo();
-        displayDeviceInfo.width = DISPLAY_WIDTH;
-        displayDeviceInfo.height = DISPLAY_HEIGHT;
-        displayDeviceInfo.flags = DisplayDeviceInfo.FLAG_ROTATES_WITH_CONTENT;
         mLogicalDisplay = new LogicalDisplay(DISPLAY_ID, LAYER_STACK, mDisplayDevice);
-        when(mDisplayDevice.getDisplayDeviceInfoLocked()).thenReturn(displayDeviceInfo);
+
+        mDisplayDeviceInfo.copyFrom(new DisplayDeviceInfo());
+        mDisplayDeviceInfo.width = DISPLAY_WIDTH;
+        mDisplayDeviceInfo.height = DISPLAY_HEIGHT;
+        mDisplayDeviceInfo.flags = DisplayDeviceInfo.FLAG_ROTATES_WITH_CONTENT;
+        mDisplayDeviceInfo.touch = DisplayDeviceInfo.TOUCH_INTERNAL;
+        when(mDisplayDevice.getDisplayDeviceInfoLocked()).thenReturn(mDisplayDeviceInfo);
 
         // Disable binder caches in this process.
         PropertyInvalidatedCache.disableForTestMode();
@@ -103,4 +110,33 @@
         mLogicalDisplay.configureDisplayLocked(t, mDisplayDevice, false);
         assertEquals(expectedPosition, mLogicalDisplay.getDisplayPosition());
     }
+
+    @Test
+    public void testDisplayInputFlags() {
+        SurfaceControl.Transaction t = mock(SurfaceControl.Transaction.class);
+        mLogicalDisplay.configureDisplayLocked(t, mDisplayDevice, false);
+        verify(t).setDisplayFlags(any(), eq(SurfaceControl.DISPLAY_RECEIVES_INPUT));
+        reset(t);
+
+        mDisplayDeviceInfo.touch = DisplayDeviceInfo.TOUCH_NONE;
+        mLogicalDisplay.configureDisplayLocked(t, mDisplayDevice, false);
+        verify(t).setDisplayFlags(any(), eq(0));
+        reset(t);
+
+        mDisplayDeviceInfo.touch = DisplayDeviceInfo.TOUCH_VIRTUAL;
+        mLogicalDisplay.configureDisplayLocked(t, mDisplayDevice, false);
+        verify(t).setDisplayFlags(any(), eq(SurfaceControl.DISPLAY_RECEIVES_INPUT));
+        reset(t);
+
+        mLogicalDisplay.setPhase(LogicalDisplay.DISPLAY_PHASE_DISABLED);
+        mLogicalDisplay.configureDisplayLocked(t, mDisplayDevice, false);
+        verify(t).setDisplayFlags(any(), eq(0));
+        reset(t);
+
+        mLogicalDisplay.setPhase(LogicalDisplay.DISPLAY_PHASE_ENABLED);
+        mDisplayDeviceInfo.touch = DisplayDeviceInfo.TOUCH_EXTERNAL;
+        mLogicalDisplay.configureDisplayLocked(t, mDisplayDevice, false);
+        verify(t).setDisplayFlags(any(), eq(SurfaceControl.DISPLAY_RECEIVES_INPUT));
+        reset(t);
+    }
 }
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
index 8a539cd..0cbf1b2 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
@@ -811,7 +811,7 @@
             }
 
             @Override
-            public void topFocusedWindowChanged(String packageName,
+            public void topFocusedWindowChanged(ComponentName component,
                     InsetsVisibilities requestedVisibilities) {
             }
         };
diff --git a/telephony/common/com/android/internal/telephony/SmsApplication.java b/telephony/common/com/android/internal/telephony/SmsApplication.java
index 4924a82..4230225 100644
--- a/telephony/common/com/android/internal/telephony/SmsApplication.java
+++ b/telephony/common/com/android/internal/telephony/SmsApplication.java
@@ -1146,4 +1146,35 @@
         }
         return null;
     }
+
+    /**
+     * Check if a package is default mms app (or equivalent, like bluetooth)
+     *
+     * @param context context from the calling app
+     * @param packageName the name of the package to be checked
+     * @return true if the package is default mms app or bluetooth
+     */
+    @UnsupportedAppUsage
+    public static boolean isDefaultMmsApplication(Context context, String packageName) {
+        if (packageName == null) {
+            return false;
+        }
+        String defaultMmsPackage = getDefaultMmsApplicationPackageName(context);
+        String bluetoothPackageName = context.getResources()
+                .getString(com.android.internal.R.string.config_systemBluetoothStack);
+
+        if ((defaultMmsPackage != null && defaultMmsPackage.equals(packageName))
+                || bluetoothPackageName.equals(packageName)) {
+            return true;
+        }
+        return false;
+    }
+
+    private static String getDefaultMmsApplicationPackageName(Context context) {
+        ComponentName component = getDefaultMmsApplication(context, false);
+        if (component != null) {
+            return component.getPackageName();
+        }
+        return null;
+    }
 }
diff --git a/telephony/java/android/telephony/UiccSlotInfo.java b/telephony/java/android/telephony/UiccSlotInfo.java
index 06c5b5c..5e02532 100644
--- a/telephony/java/android/telephony/UiccSlotInfo.java
+++ b/telephony/java/android/telephony/UiccSlotInfo.java
@@ -129,7 +129,7 @@
         this.mLogicalSlotIdx = logicalSlotIdx;
         this.mIsExtendedApduSupported = isExtendedApduSupported;
         this.mIsRemovable = false;
-        this.mPortList = null;
+        this.mPortList = new ArrayList<UiccPortInfo>();
     }
 
     /**