Merge "Add Sv2 to aapt2 codenames" into sc-v2-dev
diff --git a/core/java/android/app/ActivityOptions.java b/core/java/android/app/ActivityOptions.java
index 8e1f263..76f8731 100644
--- a/core/java/android/app/ActivityOptions.java
+++ b/core/java/android/app/ActivityOptions.java
@@ -215,6 +215,14 @@
             "android.activity.launchRootTaskToken";
 
     /**
+     * The {@link com.android.server.wm.TaskFragment} token the activity should be launched into.
+     * @see #setLaunchTaskFragmentToken(IBinder)
+     * @hide
+     */
+    public static final String KEY_LAUNCH_TASK_FRAGMENT_TOKEN =
+            "android.activity.launchTaskFragmentToken";
+
+    /**
      * The windowing mode the activity should be launched into.
      * @hide
      */
@@ -396,6 +404,7 @@
     private int mCallerDisplayId = INVALID_DISPLAY;
     private WindowContainerToken mLaunchTaskDisplayArea;
     private WindowContainerToken mLaunchRootTask;
+    private IBinder mLaunchTaskFragmentToken;
     @WindowConfiguration.WindowingMode
     private int mLaunchWindowingMode = WINDOWING_MODE_UNDEFINED;
     @WindowConfiguration.ActivityType
@@ -1138,6 +1147,7 @@
         mCallerDisplayId = opts.getInt(KEY_CALLER_DISPLAY_ID, INVALID_DISPLAY);
         mLaunchTaskDisplayArea = opts.getParcelable(KEY_LAUNCH_TASK_DISPLAY_AREA_TOKEN);
         mLaunchRootTask = opts.getParcelable(KEY_LAUNCH_ROOT_TASK_TOKEN);
+        mLaunchTaskFragmentToken = opts.getBinder(KEY_LAUNCH_TASK_FRAGMENT_TOKEN);
         mLaunchWindowingMode = opts.getInt(KEY_LAUNCH_WINDOWING_MODE, WINDOWING_MODE_UNDEFINED);
         mLaunchActivityType = opts.getInt(KEY_LAUNCH_ACTIVITY_TYPE, ACTIVITY_TYPE_UNDEFINED);
         mLaunchTaskId = opts.getInt(KEY_LAUNCH_TASK_ID, -1);
@@ -1473,6 +1483,17 @@
     }
 
     /** @hide */
+    public IBinder getLaunchTaskFragmentToken() {
+        return mLaunchTaskFragmentToken;
+    }
+
+    /** @hide */
+    public ActivityOptions setLaunchTaskFragmentToken(IBinder taskFragmentToken) {
+        mLaunchTaskFragmentToken = taskFragmentToken;
+        return this;
+    }
+
+    /** @hide */
     public int getLaunchWindowingMode() {
         return mLaunchWindowingMode;
     }
@@ -1882,6 +1903,9 @@
         if (mLaunchRootTask != null) {
             b.putParcelable(KEY_LAUNCH_ROOT_TASK_TOKEN, mLaunchRootTask);
         }
+        if (mLaunchTaskFragmentToken != null) {
+            b.putBinder(KEY_LAUNCH_TASK_FRAGMENT_TOKEN, mLaunchTaskFragmentToken);
+        }
         if (mLaunchWindowingMode != WINDOWING_MODE_UNDEFINED) {
             b.putInt(KEY_LAUNCH_WINDOWING_MODE, mLaunchWindowingMode);
         }
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 7d70441..f212b6d 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -5666,8 +5666,8 @@
      */
     private void scheduleRelaunchActivityIfPossible(@NonNull ActivityClientRecord r,
             boolean preserveWindow) {
-        if (r.activity.mFinished || r.token instanceof Binder) {
-            // Do not schedule relaunch if the activity is finishing or not a local object (e.g.
+        if ((r.activity != null && r.activity.mFinished) || r.token instanceof Binder) {
+            // Do not schedule relaunch if the activity is finishing or is a local object (e.g.
             // created by ActivtiyGroup that server side doesn't recognize it).
             return;
         }
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index bb7cdfa..d932a29 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -7205,6 +7205,34 @@
      * @hide
      */
     public interface OnOpStartedListener {
+
+        /**
+         * Represents a start operation that was unsuccessful
+         * @hide
+         */
+        public int START_TYPE_FAILED = 0;
+
+        /**
+         * Represents a successful start operation
+         * @hide
+         */
+        public int START_TYPE_STARTED = 1;
+
+        /**
+         * Represents an operation where a restricted operation became unrestricted, and resumed.
+         * @hide
+         */
+        public int START_TYPE_RESUMED = 2;
+
+        /** @hide */
+        @Retention(RetentionPolicy.SOURCE)
+        @IntDef(flag = true, prefix = { "TYPE_" }, value = {
+            START_TYPE_FAILED,
+            START_TYPE_STARTED,
+            START_TYPE_RESUMED
+        })
+        public @interface StartedType {}
+
         /**
          * Called when an op was started.
          *
@@ -7213,11 +7241,35 @@
          * @param uid The UID performing the operation.
          * @param packageName The package performing the operation.
          * @param attributionTag The attribution tag performing the operation.
-         * @param flags The flags of this op
+         * @param flags The flags of this op.
          * @param result The result of the start.
          */
         void onOpStarted(int op, int uid, String packageName, String attributionTag,
                 @OpFlags int flags, @Mode int result);
+
+        /**
+         * Called when an op was started.
+         *
+         * Note: This is only for op starts. It is not called when an op is noted or stopped.
+         * By default, unless this method is overridden, no code will be executed for resume
+         * events.
+         * @param op The op code.
+         * @param uid The UID performing the operation.
+         * @param packageName The package performing the operation.
+         * @param attributionTag The attribution tag performing the operation.
+         * @param flags The flags of this op.
+         * @param result The result of the start.
+         * @param startType The start type of this start event. Either failed, resumed, or started.
+         * @param attributionFlags The location of this started op in an attribution chain.
+         * @param attributionChainId The ID of the attribution chain of this op, if it is in one.
+         */
+        default void onOpStarted(int op, int uid, String packageName, String attributionTag,
+                @OpFlags int flags, @Mode int result, @StartedType int startType,
+                @AttributionFlags int attributionFlags, int attributionChainId) {
+            if (startType != START_TYPE_RESUMED) {
+                onOpStarted(op, uid, packageName, attributionTag, flags, result);
+            }
+        }
     }
 
     AppOpsManager(Context context, IAppOpsService service) {
@@ -7858,8 +7910,10 @@
              cb = new IAppOpsStartedCallback.Stub() {
                  @Override
                  public void opStarted(int op, int uid, String packageName, String attributionTag,
-                         int flags, int mode) {
-                     callback.onOpStarted(op, uid, packageName, attributionTag, flags, mode);
+                         int flags, int mode, int startType, int attributionFlags,
+                         int attributionChainId) {
+                     callback.onOpStarted(op, uid, packageName, attributionTag, flags, mode,
+                             startType, attributionFlags, attributionChainId);
                  }
              };
              mStartedWatchers.put(callback, cb);
diff --git a/core/java/android/app/Instrumentation.java b/core/java/android/app/Instrumentation.java
index b2184fe..45db0f6 100644
--- a/core/java/android/app/Instrumentation.java
+++ b/core/java/android/app/Instrumentation.java
@@ -743,6 +743,17 @@
         }
 
         /**
+         * This overload is used for notifying the {@link android.window.TaskFragmentOrganizer}
+         * implementation internally about started activities.
+         *
+         * @see #onStartActivity(Intent)
+         * @hide
+         */
+        public ActivityResult onStartActivity(Context who, Intent intent, Bundle options) {
+            return onStartActivity(intent);
+        }
+
+        /**
          * Used for intercepting any started activity.
          *
          * <p> A non-null return value here will be considered a hit for this monitor.
@@ -1722,7 +1733,10 @@
                     final ActivityMonitor am = mActivityMonitors.get(i);
                     ActivityResult result = null;
                     if (am.ignoreMatchingSpecificIntents()) {
-                        result = am.onStartActivity(intent);
+                        if (options == null) {
+                            options = ActivityOptions.makeBasic().toBundle();
+                        }
+                        result = am.onStartActivity(who, intent, options);
                     }
                     if (result != null) {
                         am.mHits++;
@@ -1790,7 +1804,10 @@
                     final ActivityMonitor am = mActivityMonitors.get(i);
                     ActivityResult result = null;
                     if (am.ignoreMatchingSpecificIntents()) {
-                        result = am.onStartActivity(intents[0]);
+                        if (options == null) {
+                            options = ActivityOptions.makeBasic().toBundle();
+                        }
+                        result = am.onStartActivity(who, intents[0], options);
                     }
                     if (result != null) {
                         am.mHits++;
@@ -1861,7 +1878,10 @@
                     final ActivityMonitor am = mActivityMonitors.get(i);
                     ActivityResult result = null;
                     if (am.ignoreMatchingSpecificIntents()) {
-                        result = am.onStartActivity(intent);
+                        if (options == null) {
+                            options = ActivityOptions.makeBasic().toBundle();
+                        }
+                        result = am.onStartActivity(who, intent, options);
                     }
                     if (result != null) {
                         am.mHits++;
@@ -1928,7 +1948,10 @@
                     final ActivityMonitor am = mActivityMonitors.get(i);
                     ActivityResult result = null;
                     if (am.ignoreMatchingSpecificIntents()) {
-                        result = am.onStartActivity(intent);
+                        if (options == null) {
+                            options = ActivityOptions.makeBasic().toBundle();
+                        }
+                        result = am.onStartActivity(who, intent, options);
                     }
                     if (result != null) {
                         am.mHits++;
@@ -1974,7 +1997,10 @@
                     final ActivityMonitor am = mActivityMonitors.get(i);
                     ActivityResult result = null;
                     if (am.ignoreMatchingSpecificIntents()) {
-                        result = am.onStartActivity(intent);
+                        if (options == null) {
+                            options = ActivityOptions.makeBasic().toBundle();
+                        }
+                        result = am.onStartActivity(who, intent, options);
                     }
                     if (result != null) {
                         am.mHits++;
@@ -2021,7 +2047,10 @@
                     final ActivityMonitor am = mActivityMonitors.get(i);
                     ActivityResult result = null;
                     if (am.ignoreMatchingSpecificIntents()) {
-                        result = am.onStartActivity(intent);
+                        if (options == null) {
+                            options = ActivityOptions.makeBasic().toBundle();
+                        }
+                        result = am.onStartActivity(who, intent, options);
                     }
                     if (result != null) {
                         am.mHits++;
diff --git a/core/java/android/hardware/camera2/impl/CameraExtensionUtils.java b/core/java/android/hardware/camera2/impl/CameraExtensionUtils.java
index 9acf9bf..afefcbe 100644
--- a/core/java/android/hardware/camera2/impl/CameraExtensionUtils.java
+++ b/core/java/android/hardware/camera2/impl/CameraExtensionUtils.java
@@ -75,7 +75,7 @@
         ImageWriter writer = null;
         Image img = null;
         SurfaceInfo surfaceInfo = new SurfaceInfo();
-        int nativeFormat = SurfaceUtils.getSurfaceFormat(s);
+        int nativeFormat = SurfaceUtils.detectSurfaceFormat(s);
         int dataspace = SurfaceUtils.getSurfaceDataspace(s);
         Size surfaceSize = SurfaceUtils.getSurfaceSize(s);
         surfaceInfo.mFormat = nativeFormat;
diff --git a/core/java/android/hardware/camera2/utils/SurfaceUtils.java b/core/java/android/hardware/camera2/utils/SurfaceUtils.java
index 57d8ded..fd1a331 100644
--- a/core/java/android/hardware/camera2/utils/SurfaceUtils.java
+++ b/core/java/android/hardware/camera2/utils/SurfaceUtils.java
@@ -160,6 +160,23 @@
     }
 
     /**
+     * Detect and retrieve the Surface format without any
+     * additional overrides.
+     *
+     * @param surface The surface to be queried for format.
+     * @return format of the surface.
+     *
+     * @throws IllegalArgumentException if the surface is already abandoned.
+     */
+    public static int detectSurfaceFormat(Surface surface) {
+        checkNotNull(surface);
+        int surfaceType = nativeDetectSurfaceType(surface);
+        if (surfaceType == BAD_VALUE) throw new IllegalArgumentException("Surface was abandoned");
+
+        return surfaceType;
+    }
+
+    /**
      * Get the Surface dataspace.
      *
      * @param surface The surface to be queried for dataspace.
diff --git a/core/java/android/permission/PermissionUsageHelper.java b/core/java/android/permission/PermissionUsageHelper.java
index 03f94c5..19f204b 100644
--- a/core/java/android/permission/PermissionUsageHelper.java
+++ b/core/java/android/permission/PermissionUsageHelper.java
@@ -31,7 +31,9 @@
 import static android.app.AppOpsManager.OPSTR_PHONE_CALL_CAMERA;
 import static android.app.AppOpsManager.OPSTR_PHONE_CALL_MICROPHONE;
 import static android.app.AppOpsManager.OPSTR_RECORD_AUDIO;
+import static android.app.AppOpsManager.OP_CAMERA;
 import static android.app.AppOpsManager.OP_FLAGS_ALL_TRUSTED;
+import static android.app.AppOpsManager.OP_RECORD_AUDIO;
 import static android.media.AudioSystem.MODE_IN_COMMUNICATION;
 import static android.telephony.TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS;
 
@@ -63,7 +65,8 @@
  *
  * @hide
  */
-public class PermissionUsageHelper implements AppOpsManager.OnOpActiveChangedListener {
+public class PermissionUsageHelper implements AppOpsManager.OnOpActiveChangedListener,
+        AppOpsManager.OnOpStartedListener {
 
     /** Whether to show the mic and camera icons.  */
     private static final String PROPERTY_CAMERA_MIC_ICONS_ENABLED = "camera_mic_icons_enabled";
@@ -160,9 +163,10 @@
         mUserContexts = new ArrayMap<>();
         mUserContexts.put(Process.myUserHandle(), mContext);
         // TODO ntmyren: make this listen for flag enable/disable changes
-        String[] ops = { OPSTR_CAMERA, OPSTR_RECORD_AUDIO };
-        mContext.getSystemService(AppOpsManager.class).startWatchingActive(ops,
-                context.getMainExecutor(), this);
+        String[] opStrs = { OPSTR_CAMERA, OPSTR_RECORD_AUDIO };
+        mAppOpsManager.startWatchingActive(opStrs, context.getMainExecutor(), this);
+        int[] ops = { OP_CAMERA, OP_RECORD_AUDIO };
+        mAppOpsManager.startWatchingStarted(ops, this);
     }
 
     private Context getUserContext(UserHandle user) {
@@ -182,25 +186,65 @@
     public void onOpActiveChanged(@NonNull String op, int uid, @NonNull String packageName,
             @Nullable String attributionTag, boolean active, @AttributionFlags int attributionFlags,
             int attributionChainId) {
-        if (attributionChainId == ATTRIBUTION_CHAIN_ID_NONE
-                || attributionFlags == ATTRIBUTION_FLAGS_NONE
-                || (attributionFlags & ATTRIBUTION_FLAG_TRUSTED) == 0) {
-            // If this is not a chain, or it is untrusted, return
+        if (active) {
+            // Started callback handles these
             return;
         }
 
-        if (!active) {
-            // if any link in the chain is finished, remove the chain.
-            // TODO ntmyren: be smarter about this
-            mAttributionChains.remove(attributionChainId);
+        // if any link in the chain is finished, remove the chain. Then, find any other chains that
+        // contain this op/package/uid/tag combination, and remove them, as well.
+        // TODO ntmyren: be smarter about this
+        mAttributionChains.remove(attributionChainId);
+        int numChains = mAttributionChains.size();
+        ArrayList<Integer> toRemove = new ArrayList<>();
+        for (int i = 0; i < numChains; i++) {
+            int chainId = mAttributionChains.keyAt(i);
+            ArrayList<AccessChainLink> chain = mAttributionChains.valueAt(i);
+            int chainSize = chain.size();
+            for (int j = 0; j < chainSize; j++) {
+                AccessChainLink link = chain.get(j);
+                if (link.packageAndOpEquals(op, packageName, attributionTag, uid)) {
+                    toRemove.add(chainId);
+                    break;
+                }
+            }
+        }
+        mAttributionChains.removeAll(toRemove);
+    }
+
+    @Override
+    public void onOpStarted(int op, int uid, String packageName, String attributionTag,
+                @AppOpsManager.OpFlags int flags, @AppOpsManager.Mode int result) {
+       // not part of an attribution chain. Do nothing
+    }
+
+    @Override
+    public void onOpStarted(int op, int uid, String packageName, String attributionTag,
+            @AppOpsManager.OpFlags int flags, @AppOpsManager.Mode int result,
+            @StartedType int startedType, @AttributionFlags int attributionFlags,
+            int attributionChainId) {
+        if (startedType == START_TYPE_FAILED || attributionChainId == ATTRIBUTION_CHAIN_ID_NONE
+                || attributionFlags == ATTRIBUTION_FLAGS_NONE
+                || (attributionFlags & ATTRIBUTION_FLAG_TRUSTED) == 0) {
+            // If this is not a successful start, or it is not a chain, or it is untrusted, return
             return;
         }
+        addLinkToChainIfNotPresent(AppOpsManager.opToPublicName(op), packageName, uid,
+                attributionTag, attributionFlags, attributionChainId);
+    }
+
+    private void addLinkToChainIfNotPresent(String op, String packageName, int uid,
+            String attributionTag, int attributionFlags, int attributionChainId) {
 
         ArrayList<AccessChainLink> currentChain = mAttributionChains.computeIfAbsent(
                 attributionChainId, k -> new ArrayList<>());
         AccessChainLink link = new AccessChainLink(op, packageName, attributionTag, uid,
                 attributionFlags);
 
+        if (currentChain.contains(link)) {
+            return;
+        }
+
         int currSize = currentChain.size();
         if (currSize == 0 || link.isEnd() || !currentChain.get(currSize - 1).isEnd()) {
             // if the list is empty, this link is the end, or the last link in the current chain
@@ -613,5 +657,21 @@
         public boolean isStart() {
             return (flags & ATTRIBUTION_FLAG_RECEIVER) != 0;
         }
+
+        @Override
+        public boolean equals(Object obj) {
+            if (!(obj instanceof AccessChainLink)) {
+                return false;
+            }
+            AccessChainLink other = (AccessChainLink) obj;
+            return other.flags == flags && packageAndOpEquals(other.usage.op,
+                    other.usage.packageName, other.usage.attributionTag, other.usage.uid);
+        }
+
+        public boolean packageAndOpEquals(String op, String packageName, String attributionTag,
+                int uid) {
+            return Objects.equals(op, usage.op) && Objects.equals(packageName, usage.packageName)
+                    && Objects.equals(attributionTag, usage.attributionTag) && uid == usage.uid;
+        }
     }
 }
diff --git a/core/java/com/android/internal/app/IAppOpsStartedCallback.aidl b/core/java/com/android/internal/app/IAppOpsStartedCallback.aidl
index 3a108e7..06640cb 100644
--- a/core/java/com/android/internal/app/IAppOpsStartedCallback.aidl
+++ b/core/java/com/android/internal/app/IAppOpsStartedCallback.aidl
@@ -18,5 +18,6 @@
 
 // Iterface to observe op starts
 oneway interface IAppOpsStartedCallback {
-    void opStarted(int op, int uid, String packageName, String attributionTag, int flags, int mode);
+    void opStarted(int op, int uid, String packageName, String attributionTag, int flags, int mode,
+    int startedType, int attributionFlags, int attributionChainId);
 }
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index ac39d30..1704452 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -550,6 +550,8 @@
     <protected-broadcast android:name="com.android.server.telecom.intent.action.CALLS_ADD_ENTRY" />
     <protected-broadcast android:name="com.android.settings.location.MODE_CHANGING" />
     <protected-broadcast android:name="com.android.settings.bluetooth.ACTION_DISMISS_PAIRING" />
+    <protected-broadcast android:name="com.android.settings.network.DELETE_SUBSCRIPTION" />
+    <protected-broadcast android:name="com.android.settings.network.SWITCH_TO_SUBSCRIPTION" />
     <protected-broadcast android:name="com.android.settings.wifi.action.NETWORK_REQUEST" />
 
     <protected-broadcast android:name="NotificationManagerService.TIMEOUT" />
diff --git a/core/res/res/layout/splash_screen_view.xml b/core/res/res/layout/splash_screen_view.xml
index aa050f3..2b9f952 100644
--- a/core/res/res/layout/splash_screen_view.xml
+++ b/core/res/res/layout/splash_screen_view.xml
@@ -26,6 +26,7 @@
           android:layout_width="wrap_content"
           android:layout_gravity="center"
           android:padding="0dp"
+          android:background="@null"
           android:contentDescription="@string/splash_screen_view_icon_description"/>
 
     <View android:id="@+id/splashscreen_branding_view"
diff --git a/core/res/res/values-de/strings.xml b/core/res/res/values-de/strings.xml
index 199e82f..865f026 100644
--- a/core/res/res/values-de/strings.xml
+++ b/core/res/res/values-de/strings.xml
@@ -1937,7 +1937,7 @@
     <string name="notification_phishing_alert_content_description" msgid="494227305355958790">"Phishing-Warnung"</string>
     <string name="notification_work_profile_content_description" msgid="5296477955677725799">"Arbeitsprofil"</string>
     <string name="notification_alerted_content_description" msgid="6139691253611265992">"Gewarnt"</string>
-    <string name="notification_verified_content_description" msgid="6401483602782359391">"Bestätigt"</string>
+    <string name="notification_verified_content_description" msgid="6401483602782359391">"Verifiziert"</string>
     <string name="expand_button_content_description_collapsed" msgid="3873368935659010279">"Maximieren"</string>
     <string name="expand_button_content_description_expanded" msgid="7484217944948667489">"Minimieren"</string>
     <string name="expand_action_accessibility" msgid="1947657036871746627">"Maximierung ein-/auschalten"</string>
diff --git a/graphics/java/android/graphics/drawable/RippleAnimationSession.java b/graphics/java/android/graphics/drawable/RippleAnimationSession.java
index 4925209..872331c 100644
--- a/graphics/java/android/graphics/drawable/RippleAnimationSession.java
+++ b/graphics/java/android/graphics/drawable/RippleAnimationSession.java
@@ -67,7 +67,7 @@
 
     @NonNull RippleAnimationSession enter(Canvas canvas) {
         mStartTime = AnimationUtils.currentAnimationTimeMillis();
-        if (isHwAccelerated(canvas)) {
+        if (useRTAnimations(canvas)) {
             enterHardware((RecordingCanvas) canvas);
         } else {
             enterSoftware();
@@ -82,7 +82,7 @@
     }
 
     @NonNull RippleAnimationSession exit(Canvas canvas) {
-        if (isHwAccelerated(canvas)) exitHardware((RecordingCanvas) canvas);
+        if (useRTAnimations(canvas)) exitHardware((RecordingCanvas) canvas);
         else exitSoftware();
         return this;
     }
@@ -102,8 +102,12 @@
         return this;
     }
 
-    private boolean isHwAccelerated(Canvas canvas) {
-        return canvas.isHardwareAccelerated() && !mForceSoftware;
+    private boolean useRTAnimations(Canvas canvas) {
+        if (mForceSoftware) return false;
+        if (!canvas.isHardwareAccelerated()) return false;
+        RecordingCanvas hwCanvas = (RecordingCanvas) canvas;
+        if (hwCanvas.mNode == null || !hwCanvas.mNode.isAttached()) return false;
+        return true;
     }
 
     private void exitSoftware() {
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/organizer/JetpackTaskFragmentOrganizer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/organizer/JetpackTaskFragmentOrganizer.java
index 0f2f23e..531df30 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/organizer/JetpackTaskFragmentOrganizer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/organizer/JetpackTaskFragmentOrganizer.java
@@ -159,13 +159,22 @@
      * @param ownerToken The token of the activity that creates this task fragment. It does not
      *                   have to be a child of this task fragment, but must belong to the same task.
      */
+    void createTaskFragment(WindowContainerTransaction wct, IBinder fragmentToken,
+            IBinder ownerToken, @NonNull Rect bounds, @WindowingMode int windowingMode) {
+        final TaskFragmentCreationParams fragmentOptions =
+                createFragmentOptions(fragmentToken, ownerToken, bounds, windowingMode);
+        wct.createTaskFragment(fragmentOptions);
+    }
+
+    /**
+     * @param ownerToken The token of the activity that creates this task fragment. It does not
+     *                   have to be a child of this task fragment, but must belong to the same task.
+     */
     private void createTaskFragmentAndReparentActivity(
             WindowContainerTransaction wct, IBinder fragmentToken, IBinder ownerToken,
             @NonNull Rect bounds, @WindowingMode int windowingMode, Activity activity) {
-        final TaskFragmentCreationParams fragmentOptions =
-                createFragmentOptions(fragmentToken, ownerToken, bounds, windowingMode);
-        wct.createTaskFragment(fragmentOptions)
-                .reparentActivityToTaskFragment(fragmentToken, activity.getActivityToken());
+        createTaskFragment(wct, fragmentToken, ownerToken, bounds, windowingMode);
+        wct.reparentActivityToTaskFragment(fragmentToken, activity.getActivityToken());
     }
 
     /**
@@ -176,11 +185,8 @@
             WindowContainerTransaction wct, IBinder fragmentToken, IBinder ownerToken,
             @NonNull Rect bounds, @WindowingMode int windowingMode, Intent activityIntent,
             @Nullable Bundle activityOptions) {
-        final TaskFragmentCreationParams fragmentOptions =
-                createFragmentOptions(fragmentToken, ownerToken, bounds, windowingMode);
-        wct.createTaskFragment(fragmentOptions)
-                .startActivityInTaskFragment(fragmentToken, ownerToken, activityIntent,
-                        activityOptions);
+        createTaskFragment(wct, fragmentToken, ownerToken, bounds, windowingMode);
+        wct.startActivityInTaskFragment(fragmentToken, ownerToken, activityIntent, activityOptions);
     }
 
     TaskFragmentCreationParams createFragmentOptions(IBinder fragmentToken, IBinder ownerToken,
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/organizer/SplitController.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/organizer/SplitController.java
index e43c5bf..a1ebb59 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/organizer/SplitController.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/organizer/SplitController.java
@@ -20,9 +20,12 @@
 import android.annotation.Nullable;
 import android.app.Activity;
 import android.app.ActivityClient;
+import android.app.ActivityOptions;
 import android.app.ActivityThread;
 import android.app.Application.ActivityLifecycleCallbacks;
+import android.app.Instrumentation;
 import android.content.ComponentName;
+import android.content.Context;
 import android.content.Intent;
 import android.content.res.Configuration;
 import android.os.Bundle;
@@ -61,9 +64,13 @@
 
     public SplitController() {
         mPresenter = new SplitPresenter(new MainThreadExecutor(), this);
+        ActivityThread activityThread = ActivityThread.currentActivityThread();
         // Register a callback to be notified about activities being created.
-        ActivityThread.currentActivityThread().getApplication().registerActivityLifecycleCallbacks(
+        activityThread.getApplication().registerActivityLifecycleCallbacks(
                 new LifecycleCallbacks());
+        // Intercept activity starts to route activities to new containers if necessary.
+        Instrumentation instrumentation = activityThread.getInstrumentation();
+        instrumentation.addMonitor(new ActivityStartMonitor());
     }
 
     public void setSplitRules(@NonNull List<ExtensionSplitRule> splitRules) {
@@ -118,7 +125,9 @@
         }
 
         container.setInfo(taskFragmentInfo);
-        if (taskFragmentInfo.isEmpty()) {
+        // Check if there are no running activities - consider the container empty if there are no
+        // non-finishing activities left.
+        if (!taskFragmentInfo.hasRunningActivity()) {
             cleanupContainer(container, true /* shouldFinishDependent */);
             updateCallbackIfNecessary();
         }
@@ -664,4 +673,40 @@
             handler.post(r);
         }
     }
+
+    /**
+     * 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 {
+
+        @Override
+        public Instrumentation.ActivityResult onStartActivity(Context who, Intent intent,
+                Bundle options) {
+            // TODO(b/190433398): Check if the activity is configured to always be expanded.
+
+            // Check if activity should be put in a split with the activity that launched it.
+            if (!(who instanceof Activity)) {
+                return super.onStartActivity(who, intent, options);
+            }
+            final Activity launchingActivity = (Activity) who;
+
+            final ExtensionSplitPairRule splitPairRule = getSplitRule(
+                    launchingActivity.getComponentName(), intent.getComponent(), getSplitRules());
+            if (splitPairRule == null) {
+                return super.onStartActivity(who, intent, options);
+            }
+
+            // Create a new split with an empty side container
+            final TaskFragmentContainer secondaryContainer = mPresenter
+                    .createNewSplitWithEmptySideContainer(launchingActivity, splitPairRule);
+
+            // Amend the request to let the WM know that the activity should be placed in the
+            // dedicated container.
+            options.putBinder(ActivityOptions.KEY_LAUNCH_TASK_FRAGMENT_TOKEN,
+                    secondaryContainer.getTaskFragmentToken());
+
+            return super.onStartActivity(who, intent, options);
+        }
+    }
 }
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/organizer/SplitPresenter.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/organizer/SplitPresenter.java
index 7ad83aa..d8b1d2a 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/organizer/SplitPresenter.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/organizer/SplitPresenter.java
@@ -83,6 +83,37 @@
     }
 
     /**
+     * Creates a new split with the primary activity and an empty secondary container.
+     * @return The newly created secondary container.
+     */
+    TaskFragmentContainer createNewSplitWithEmptySideContainer(@NonNull Activity primaryActivity,
+            @NonNull ExtensionSplitPairRule rule) {
+        final WindowContainerTransaction wct = new WindowContainerTransaction();
+
+        final Rect parentBounds = getParentContainerBounds(primaryActivity);
+        final Rect primaryRectBounds = getBoundsForPosition(POSITION_LEFT, parentBounds, rule);
+        final TaskFragmentContainer primaryContainer = prepareContainerForActivity(wct,
+                primaryActivity, primaryRectBounds, null);
+
+        // Create new empty task fragment
+        TaskFragmentContainer secondaryContainer = mController.newContainer(null);
+        final Rect secondaryRectBounds = getBoundsForPosition(POSITION_RIGHT, parentBounds, rule);
+        createTaskFragment(wct, secondaryContainer.getTaskFragmentToken(),
+                primaryActivity.getActivityToken(), secondaryRectBounds,
+                WINDOWING_MODE_MULTI_WINDOW);
+        secondaryContainer.setLastRequestedBounds(secondaryRectBounds);
+
+        // Set adjacent to each other so that the containers below will be invisible.
+        wct.setAdjacentTaskFragments(
+                primaryContainer.getTaskFragmentToken(), secondaryContainer.getTaskFragmentToken());
+        applyTransaction(wct);
+
+        mController.registerSplit(primaryContainer, primaryActivity, secondaryContainer, rule);
+
+        return secondaryContainer;
+    }
+
+    /**
      * Creates a new split container with the two provided activities.
      * @param primaryActivity An activity that should be in the primary container. If it is not
      *                        currently in an existing container, a new one will be created and the
@@ -99,48 +130,12 @@
 
         final Rect parentBounds = getParentContainerBounds(primaryActivity);
         final Rect primaryRectBounds = getBoundsForPosition(POSITION_LEFT, parentBounds, rule);
-        TaskFragmentContainer primaryContainer = mController.getContainerWithActivity(
-                primaryActivity.getActivityToken());
-        if (primaryContainer == null) {
-            primaryContainer = mController.newContainer(primaryActivity);
-
-            final TaskFragmentCreationParams fragmentOptions =
-                    createFragmentOptions(
-                            primaryContainer.getTaskFragmentToken(),
-                            primaryActivity.getActivityToken(),
-                            primaryRectBounds,
-                            WINDOWING_MODE_MULTI_WINDOW);
-            wct.createTaskFragment(fragmentOptions);
-
-            wct.reparentActivityToTaskFragment(primaryContainer.getTaskFragmentToken(),
-                    primaryActivity.getActivityToken());
-
-            primaryContainer.setLastRequestedBounds(primaryRectBounds);
-        } else {
-            resizeTaskFragmentIfRegistered(wct, primaryContainer, primaryRectBounds);
-        }
+        final TaskFragmentContainer primaryContainer = prepareContainerForActivity(wct,
+                primaryActivity, primaryRectBounds, null);
 
         final Rect secondaryRectBounds = getBoundsForPosition(POSITION_RIGHT, parentBounds, rule);
-        TaskFragmentContainer secondaryContainer = mController.getContainerWithActivity(
-                secondaryActivity.getActivityToken());
-        if (secondaryContainer == null || secondaryContainer == primaryContainer) {
-            secondaryContainer = mController.newContainer(secondaryActivity);
-
-            final TaskFragmentCreationParams fragmentOptions =
-                    createFragmentOptions(
-                            secondaryContainer.getTaskFragmentToken(),
-                            secondaryActivity.getActivityToken(),
-                            secondaryRectBounds,
-                            WINDOWING_MODE_MULTI_WINDOW);
-            wct.createTaskFragment(fragmentOptions);
-
-            wct.reparentActivityToTaskFragment(secondaryContainer.getTaskFragmentToken(),
-                    secondaryActivity.getActivityToken());
-
-            secondaryContainer.setLastRequestedBounds(secondaryRectBounds);
-        } else {
-            resizeTaskFragmentIfRegistered(wct, secondaryContainer, secondaryRectBounds);
-        }
+        final TaskFragmentContainer secondaryContainer = prepareContainerForActivity(wct,
+                secondaryActivity, secondaryRectBounds, primaryContainer);
 
         // Set adjacent to each other so that the containers below will be invisible.
         wct.setAdjacentTaskFragments(
@@ -151,6 +146,38 @@
     }
 
     /**
+     * Creates a new container or resizes an existing container for activity to the provided bounds.
+     * @param activity The activity to be re-parented to the container if necessary.
+     * @param containerToAvoid Re-parent from this container if an activity is already in it.
+     */
+    private TaskFragmentContainer prepareContainerForActivity(
+            @NonNull WindowContainerTransaction wct, @NonNull Activity activity,
+            @NonNull Rect bounds, @Nullable TaskFragmentContainer containerToAvoid) {
+        TaskFragmentContainer container = mController.getContainerWithActivity(
+                activity.getActivityToken());
+        if (container == null || container == containerToAvoid) {
+            container = mController.newContainer(activity);
+
+            final TaskFragmentCreationParams fragmentOptions =
+                    createFragmentOptions(
+                            container.getTaskFragmentToken(),
+                            activity.getActivityToken(),
+                            bounds,
+                            WINDOWING_MODE_MULTI_WINDOW);
+            wct.createTaskFragment(fragmentOptions);
+
+            wct.reparentActivityToTaskFragment(container.getTaskFragmentToken(),
+                    activity.getActivityToken());
+
+            container.setLastRequestedBounds(bounds);
+        } else {
+            resizeTaskFragmentIfRegistered(wct, container, bounds);
+        }
+
+        return container;
+    }
+
+    /**
      * Starts a new activity to the side, creating a new split container. A new container will be
      * created for the activity that will be started.
      * @param launchingActivity An activity that should be in the primary container. If it is not
diff --git a/libs/WindowManager/Shell/res/values-te/strings.xml b/libs/WindowManager/Shell/res/values-te/strings.xml
index 624b8b3..c2b6ffb 100644
--- a/libs/WindowManager/Shell/res/values-te/strings.xml
+++ b/libs/WindowManager/Shell/res/values-te/strings.xml
@@ -31,7 +31,7 @@
     <string name="accessibility_action_pip_stash" msgid="4060775037619702641">"స్టాచ్"</string>
     <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"ఆన్‌స్టాచ్"</string>
     <string name="dock_forced_resizable" msgid="1749750436092293116">"స్క్రీన్ విభజనతో యాప్‌ పని చేయకపోవచ్చు."</string>
-    <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"అనువర్తనంలో స్క్రీన్ విభజనకు మద్దతు లేదు."</string>
+    <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"యాప్‌లో స్క్రీన్ విభజనకు మద్దతు లేదు."</string>
     <string name="forced_resizable_secondary_display" msgid="1768046938673582671">"ప్రత్యామ్నాయ డిస్‌ప్లేలో యాప్ పని చేయకపోవచ్చు."</string>
     <string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"ప్రత్యామ్నాయ డిస్‌ప్లేల్లో ప్రారంభానికి యాప్ మద్దతు లేదు."</string>
     <string name="accessibility_divider" msgid="703810061635792791">"విభజన స్క్రీన్ విభాగిని"</string>
diff --git a/mms/OWNERS b/mms/OWNERS
index befc320..7f05a2a 100644
--- a/mms/OWNERS
+++ b/mms/OWNERS
@@ -2,7 +2,6 @@
 
 tgunn@google.com
 breadley@google.com
-hallliu@google.com
 rgreenwalt@google.com
 amitmahajan@google.com
 fionaxu@google.com
@@ -10,7 +9,10 @@
 jminjie@google.com
 satk@google.com
 shuoq@google.com
-refuhoo@google.com
 nazaninb@google.com
 sarahchin@google.com
-dbright@google.com
\ No newline at end of file
+xiaotonj@google.com
+huiwang@google.com
+jayachandranc@google.com
+chinmayd@google.com
+amruthr@google.com
diff --git a/packages/CarrierDefaultApp/OWNERS b/packages/CarrierDefaultApp/OWNERS
index 5668840..0d23f05 100644
--- a/packages/CarrierDefaultApp/OWNERS
+++ b/packages/CarrierDefaultApp/OWNERS
@@ -1,7 +1,6 @@
 set noparent
 tgunn@google.com
 breadley@google.com
-hallliu@google.com
 rgreenwalt@google.com
 amitmahajan@google.com
 fionaxu@google.com
@@ -9,9 +8,11 @@
 jminjie@google.com
 satk@google.com
 shuoq@google.com
-refuhoo@google.com
 nazaninb@google.com
 sarahchin@google.com
-dbright@google.com
 xiaotonj@google.com
+huiwang@google.com
+jayachandranc@google.com
+chinmayd@google.com
+amruthr@google.com
 
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/flags/Flag.kt b/packages/SystemUI/plugin/src/com/android/systemui/flags/Flag.kt
new file mode 100644
index 0000000..68834bc
--- /dev/null
+++ b/packages/SystemUI/plugin/src/com/android/systemui/flags/Flag.kt
@@ -0,0 +1,52 @@
+/*
+ * 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.systemui.flags
+
+interface Flag<T> {
+    val id: Int
+    val default: T
+}
+
+data class BooleanFlag @JvmOverloads constructor(
+    override val id: Int,
+    override val default: Boolean = false
+) : Flag<Boolean>
+
+data class StringFlag @JvmOverloads constructor(
+    override val id: Int,
+    override val default: String = ""
+) : Flag<String>
+
+data class IntFlag @JvmOverloads constructor(
+    override val id: Int,
+    override val default: Int = 0
+) : Flag<Int>
+
+data class LongFlag @JvmOverloads constructor(
+    override val id: Int,
+    override val default: Long = 0
+) : Flag<Long>
+
+data class FloatFlag @JvmOverloads constructor(
+    override val id: Int,
+    override val default: Float = 0f
+) : Flag<Float>
+
+data class DoubleFlag @JvmOverloads constructor(
+    override val id: Int,
+    override val default: Double = 0.0
+) : Flag<Double>
\ No newline at end of file
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/flags/Flags.java b/packages/SystemUI/plugin/src/com/android/systemui/flags/Flags.java
new file mode 100644
index 0000000..d5b9243
--- /dev/null
+++ b/packages/SystemUI/plugin/src/com/android/systemui/flags/Flags.java
@@ -0,0 +1,23 @@
+/*
+ * 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.systemui.flags;
+
+/**
+ * List of {@link Flag} objects for use in SystemUI.
+ */
+public class Flags {
+}
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/FlagReaderPlugin.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/FlagReaderPlugin.java
new file mode 100644
index 0000000..d153bd8
--- /dev/null
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/FlagReaderPlugin.java
@@ -0,0 +1,59 @@
+/*
+ * 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.systemui.plugins;
+
+import com.android.systemui.plugins.annotations.ProvidesInterface;
+
+
+/**
+ * Plugin for loading flag values from an alternate source of truth.
+ */
+@ProvidesInterface(action = FlagReaderPlugin.ACTION, version = FlagReaderPlugin.VERSION)
+public interface FlagReaderPlugin extends Plugin {
+    int VERSION = 1;
+    String ACTION = "com.android.systemui.flags.FLAG_READER_PLUGIN";
+
+    /** Returns a boolean value for the given flag. */
+    default boolean isEnabled(int id, boolean def) {
+        return def;
+    }
+
+    /** Returns a string value for the given flag id. */
+    default String getValue(int id, String def) {
+        return def;
+    }
+
+    /** Returns a int value for the given flag. */
+    default int getValue(int id, int def) {
+        return def;
+    }
+
+    /** Returns a long value for the given flag. */
+    default long getValue(int id, long def) {
+        return def;
+    }
+
+    /** Returns a float value for the given flag. */
+    default float getValue(int id, float def) {
+        return def;
+    }
+
+    /** Returns a double value for the given flag. */
+    default double getValue(int id, double def) {
+        return def;
+    }
+}
diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_clock_switch.xml b/packages/SystemUI/res-keyguard/layout/keyguard_clock_switch.xml
index 28c6166..6016aaf 100644
--- a/packages/SystemUI/res-keyguard/layout/keyguard_clock_switch.xml
+++ b/packages/SystemUI/res-keyguard/layout/keyguard_clock_switch.xml
@@ -68,6 +68,16 @@
             lockScreenWeight="400"
         />
     </FrameLayout>
+    <FrameLayout
+        android:id="@+id/keyguard_smartspace_container"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:paddingStart="@dimen/below_clock_padding_start"
+        android:paddingEnd="@dimen/below_clock_padding_end"
+        android:layout_alignParentStart="true"
+        android:layout_below="@id/lockscreen_clock_view"
+        />
+    <!-- either keyguard_status_area or keyguard_smartspace_container is visible -->
     <include layout="@layout/keyguard_status_area"
         android:id="@+id/keyguard_status_area"
         android:layout_width="match_parent"
diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_pattern_view.xml b/packages/SystemUI/res-keyguard/layout/keyguard_pattern_view.xml
index ce63082..f613a19 100644
--- a/packages/SystemUI/res-keyguard/layout/keyguard_pattern_view.xml
+++ b/packages/SystemUI/res-keyguard/layout/keyguard_pattern_view.xml
@@ -27,49 +27,44 @@
     android:orientation="vertical"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
-    android:clipChildren="false"
-    android:clipToPadding="false"
     androidprv:layout_maxWidth="@dimen/keyguard_security_width"
-    androidprv:layout_maxHeight="@dimen/keyguard_security_height"
-    android:gravity="center_horizontal">
+    android:clipChildren="false"
+    android:clipToPadding="false">
 
-    <FrameLayout
+    <androidx.constraintlayout.widget.ConstraintLayout
+        android:id="@+id/pattern_container"
         android:layout_width="match_parent"
-        android:layout_height="match_parent"
-        android:clipChildren="false"
-        android:clipToPadding="false">
-
-        <LinearLayout
-            android:id="@+id/pattern_container"
-            android:layout_height="wrap_content"
+        android:layout_height="0dp"
+        android:layout_marginBottom="8dp"
+        android:layout_weight="1"
+        android:layoutDirection="ltr">
+        <androidx.constraintlayout.widget.Guideline
+            android:id="@+id/pattern_top_guideline"
             android:layout_width="wrap_content"
-            android:orientation="vertical"
-            android:layout_gravity="center_horizontal|bottom"
-            android:clipChildren="false"
-            android:clipToPadding="false">
+            android:layout_height="wrap_content"
+            androidprv:layout_constraintGuide_percent="0"
+            android:orientation="horizontal" />
 
-            <com.android.internal.widget.LockPatternView
-                android:id="@+id/lockPatternView"
-                android:layout_width="match_parent"
-                android:layout_height="0dp"
-                android:layout_weight="1"
-                android:layout_marginEnd="8dip"
-                android:layout_marginBottom="4dip"
-                android:layout_marginStart="8dip"
-                android:layout_gravity="center_horizontal"
-                android:gravity="center"
-                android:clipChildren="false"
-                android:clipToPadding="false" />
+        <com.android.internal.widget.LockPatternView
+            android:id="@+id/lockPatternView"
+            android:layout_width="0dp"
+            android:layout_height="0dp"
+            androidprv:layout_constraintTop_toBottomOf="@id/pattern_top_guideline"
+            androidprv:layout_constraintBottom_toBottomOf="parent"
+            androidprv:layout_constraintLeft_toLeftOf="parent"
+            androidprv:layout_constraintRight_toRightOf="parent"
+            androidprv:layout_constraintDimensionRatio="1.0"
+            androidprv:layout_constraintVertical_bias="1.0"
+            />
+    </androidx.constraintlayout.widget.ConstraintLayout>
 
-          <include layout="@layout/keyguard_eca"
-              android:id="@+id/keyguard_selector_fade_container"
-              android:layout_width="match_parent"
-              android:layout_height="wrap_content"
-              android:orientation="vertical"
-              android:layout_gravity="bottom|center_horizontal"
-              android:layout_marginTop="@dimen/keyguard_eca_top_margin"
-              android:gravity="center_horizontal" />
-        </LinearLayout>
-    </FrameLayout>
+    <include layout="@layout/keyguard_eca"
+        android:id="@+id/keyguard_selector_fade_container"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:orientation="vertical"
+        android:layout_gravity="bottom|center_horizontal"
+        android:layout_marginTop="@dimen/keyguard_eca_top_margin"
+        android:gravity="center_horizontal" />
 
 </com.android.keyguard.KeyguardPatternView>
diff --git a/packages/SystemUI/res/layout/status_bar_expanded.xml b/packages/SystemUI/res/layout/status_bar_expanded.xml
index 6f7358c..b6b3cc5 100644
--- a/packages/SystemUI/res/layout/status_bar_expanded.xml
+++ b/packages/SystemUI/res/layout/status_bar_expanded.xml
@@ -87,6 +87,18 @@
             layout="@layout/keyguard_status_view"
             android:visibility="gone"/>
 
+        <FrameLayout
+            android:id="@+id/split_shade_smartspace_container"
+            android:layout_width="0dp"
+            android:layout_height="wrap_content"
+            android:paddingStart="@dimen/notification_side_paddings"
+            android:paddingEnd="@dimen/notification_side_paddings"
+            systemui:layout_constraintStart_toStartOf="@id/qs_edge_guideline"
+            systemui:layout_constraintEnd_toEndOf="parent"
+            systemui:layout_constraintTop_toTopOf="parent"
+            android:visibility="gone">
+        </FrameLayout>
+
         <include layout="@layout/dock_info_overlay" />
 
         <FrameLayout
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
index 9e456cf..27b6182 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
@@ -17,13 +17,13 @@
 package com.android.keyguard;
 
 import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
-import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
 
 import static com.android.keyguard.KeyguardClockSwitch.LARGE;
 
 import android.app.WallpaperManager;
 import android.text.TextUtils;
 import android.view.View;
+import android.view.ViewGroup;
 import android.widget.FrameLayout;
 import android.widget.RelativeLayout;
 
@@ -93,8 +93,7 @@
 
     private final ClockManager.ClockChangedListener mClockChangedListener = this::setClockPlugin;
 
-    // If set, will replace keyguard_status_area
-    private View mSmartspaceView;
+    private ViewGroup mSmartspaceContainer;
 
     private final KeyguardUnlockAnimationController mKeyguardUnlockAnimationController;
     private SmartspaceTransitionController mSmartspaceTransitionController;
@@ -139,6 +138,8 @@
 
         mClockFrame = mView.findViewById(R.id.lockscreen_clock_view);
         mLargeClockFrame = mView.findViewById(R.id.lockscreen_clock_view_large);
+        mSmartspaceContainer = mView.findViewById(R.id.keyguard_smartspace_container);
+        mSmartspaceController.setKeyguardStatusContainer(mSmartspaceContainer);
 
         mClockViewController =
                 new AnimatableClockController(
@@ -170,35 +171,25 @@
         mView.updateColors(getGradientColors());
         updateAodIcons();
 
-        if (mSmartspaceController.isEnabled()) {
-            mSmartspaceView = mSmartspaceController.buildAndConnectView(mView);
+        if (mSmartspaceController.isSmartspaceEnabled()) {
+            // "Enabled" doesn't mean smartspace is displayed here - inside mSmartspaceContainer -
+            // it might be a part of another view when in split shade. But it means that it CAN be
+            // displayed here, so we want to hide keyguard_status_area and set views relations
+            // accordingly.
 
             View ksa = mView.findViewById(R.id.keyguard_status_area);
-            int ksaIndex = mView.indexOfChild(ksa);
+            // we show either keyguard_status_area or smartspace, so when smartspace can be visible,
+            // keyguard_status_area should be hidden
             ksa.setVisibility(View.GONE);
 
-            // Place smartspace view below normal clock...
-            RelativeLayout.LayoutParams lp = new RelativeLayout.LayoutParams(
-                    MATCH_PARENT, WRAP_CONTENT);
-            lp.addRule(RelativeLayout.BELOW, R.id.lockscreen_clock_view);
-
-            mView.addView(mSmartspaceView, ksaIndex, lp);
-            int startPadding = getContext().getResources()
-                    .getDimensionPixelSize(R.dimen.below_clock_padding_start);
-            int endPadding = getContext().getResources()
-                    .getDimensionPixelSize(R.dimen.below_clock_padding_end);
-            mSmartspaceView.setPaddingRelative(startPadding, 0, endPadding, 0);
-
             updateClockLayout();
 
-            View nic = mView.findViewById(
-                    R.id.left_aligned_notification_icon_container);
-            lp = (RelativeLayout.LayoutParams) nic.getLayoutParams();
-            lp.addRule(RelativeLayout.BELOW, mSmartspaceView.getId());
+            View nic = mView.findViewById(R.id.left_aligned_notification_icon_container);
+            RelativeLayout.LayoutParams lp = (RelativeLayout.LayoutParams) nic.getLayoutParams();
+            lp.addRule(RelativeLayout.BELOW, mSmartspaceContainer.getId());
             nic.setLayoutParams(lp);
-
-            mView.setSmartspaceView(mSmartspaceView);
-            mSmartspaceTransitionController.setLockscreenSmartspace(mSmartspaceView);
+            mView.setSmartspaceView(mSmartspaceContainer);
+            mSmartspaceTransitionController.setLockscreenSmartspace(mSmartspaceContainer);
         }
     }
 
@@ -221,10 +212,7 @@
         // instance of this class. In order to fix this, we need to modify the plugin so that
         // (a) we get a new view each time and (b) we can properly clean up an old view by making
         // it unregister itself as a plugin listener.
-        if (mSmartspaceView != null) {
-            mView.removeView(mSmartspaceView);
-            mSmartspaceView = null;
-        }
+        mSmartspaceContainer.removeAllViews();
     }
 
     /**
@@ -237,7 +225,7 @@
     }
 
     private void updateClockLayout() {
-        if (mSmartspaceController.isEnabled()) {
+        if (mSmartspaceController.isSmartspaceEnabled()) {
             RelativeLayout.LayoutParams lp = new RelativeLayout.LayoutParams(MATCH_PARENT,
                     MATCH_PARENT);
             lp.topMargin = getContext().getResources().getDimensionPixelSize(
@@ -302,8 +290,8 @@
         PropertyAnimator.setProperty(mLargeClockFrame, AnimatableProperty.SCALE_Y,
                 scale, props, animate);
 
-        if (mSmartspaceView != null) {
-            PropertyAnimator.setProperty(mSmartspaceView, AnimatableProperty.TRANSLATION_X,
+        if (mSmartspaceContainer != null) {
+            PropertyAnimator.setProperty(mSmartspaceContainer, AnimatableProperty.TRANSLATION_X,
                     x, props, animate);
 
             // If we're unlocking with the SmartSpace shared element transition, let the controller
@@ -321,8 +309,8 @@
     public void setChildrenAlphaExcludingSmartspace(float alpha) {
         final Set<View> excludedViews = new HashSet<>();
 
-        if (mSmartspaceView != null) {
-            excludedViews.add(mSmartspaceView);
+        if (mSmartspaceContainer != null) {
+            excludedViews.add(mSmartspaceContainer);
         }
 
         setChildrenAlphaExcluding(alpha, excludedViews);
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java
index b473f6d..75425e1 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java
@@ -204,7 +204,8 @@
                 return new KeyguardPatternViewController((KeyguardPatternView) keyguardInputView,
                         mKeyguardUpdateMonitor, securityMode, mLockPatternUtils,
                         keyguardSecurityCallback, mLatencyTracker, mFalsingCollector,
-                        emergencyButtonController, mMessageAreaControllerFactory);
+                        emergencyButtonController, mMessageAreaControllerFactory,
+                        mDevicePostureController);
             } else if (keyguardInputView instanceof KeyguardPasswordView) {
                 return new KeyguardPasswordViewController((KeyguardPasswordView) keyguardInputView,
                         mKeyguardUpdateMonitor, securityMode, mLockPatternUtils,
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPatternView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPatternView.java
index 98e7fb4..a35aedf 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPatternView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPatternView.java
@@ -15,6 +15,8 @@
  */
 package com.android.keyguard;
 
+import static com.android.systemui.statusbar.policy.DevicePostureController.DEVICE_POSTURE_HALF_OPENED;
+
 import android.content.Context;
 import android.graphics.Rect;
 import android.os.SystemClock;
@@ -22,16 +24,19 @@
 import android.util.AttributeSet;
 import android.view.MotionEvent;
 import android.view.View;
-import android.view.ViewGroup;
 import android.view.animation.AnimationUtils;
 import android.view.animation.Interpolator;
 
+import androidx.constraintlayout.widget.ConstraintLayout;
+import androidx.constraintlayout.widget.ConstraintSet;
+
 import com.android.internal.jank.InteractionJankMonitor;
 import com.android.internal.widget.LockPatternView;
 import com.android.settingslib.animation.AppearAnimationCreator;
 import com.android.settingslib.animation.AppearAnimationUtils;
 import com.android.settingslib.animation.DisappearAnimationUtils;
 import com.android.systemui.R;
+import com.android.systemui.statusbar.policy.DevicePostureController.DevicePostureInt;
 
 public class KeyguardPatternView extends KeyguardInputView
         implements AppearAnimationCreator<LockPatternView.CellState> {
@@ -68,7 +73,7 @@
 
     KeyguardMessageArea mSecurityMessageDisplay;
     private View mEcaView;
-    private ViewGroup mContainer;
+    private ConstraintLayout mContainer;
 
     public KeyguardPatternView(Context context) {
         this(context, null);
@@ -90,6 +95,18 @@
                 mContext, android.R.interpolator.fast_out_linear_in));
     }
 
+    void onDevicePostureChanged(@DevicePostureInt int posture) {
+        // Update the guideline based on the device posture...
+        float halfOpenPercentage =
+                mContext.getResources().getFloat(R.dimen.half_opened_bouncer_height_ratio);
+
+        ConstraintSet cs = new ConstraintSet();
+        cs.clone(mContainer);
+        cs.setGuidelinePercent(R.id.pin_pad_top_guideline, posture == DEVICE_POSTURE_HALF_OPENED
+                ? halfOpenPercentage : 0.0f);
+        cs.applyTo(mContainer);
+    }
+
     @Override
     protected void onFinishInflate() {
         super.onFinishInflate();
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java
index d5be7ba..94e07b7 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java
@@ -38,6 +38,7 @@
 import com.android.systemui.R;
 import com.android.systemui.classifier.FalsingClassifier;
 import com.android.systemui.classifier.FalsingCollector;
+import com.android.systemui.statusbar.policy.DevicePostureController;
 
 import java.util.List;
 
@@ -56,6 +57,9 @@
     private final FalsingCollector mFalsingCollector;
     private final EmergencyButtonController mEmergencyButtonController;
     private final KeyguardMessageAreaController.Factory mMessageAreaControllerFactory;
+    private final DevicePostureController mPostureController;
+    private final DevicePostureController.Callback mPostureCallback =
+            posture -> mView.onDevicePostureChanged(posture);
 
     private KeyguardMessageAreaController mMessageAreaController;
     private LockPatternView mLockPatternView;
@@ -192,7 +196,8 @@
             LatencyTracker latencyTracker,
             FalsingCollector falsingCollector,
             EmergencyButtonController emergencyButtonController,
-            KeyguardMessageAreaController.Factory messageAreaControllerFactory) {
+            KeyguardMessageAreaController.Factory messageAreaControllerFactory,
+            DevicePostureController postureController) {
         super(view, securityMode, keyguardSecurityCallback, emergencyButtonController);
         mKeyguardUpdateMonitor = keyguardUpdateMonitor;
         mLockPatternUtils = lockPatternUtils;
@@ -203,6 +208,7 @@
         KeyguardMessageArea kma = KeyguardMessageArea.findSecurityMessageDisplay(mView);
         mMessageAreaController = mMessageAreaControllerFactory.create(kma);
         mLockPatternView = mView.findViewById(R.id.lockPatternView);
+        mPostureController = postureController;
     }
 
     @Override
@@ -235,6 +241,7 @@
                 getKeyguardSecurityCallback().onCancelClicked();
             });
         }
+        mPostureController.addCallback(mPostureCallback);
     }
 
     @Override
@@ -247,6 +254,7 @@
         if (cancelBtn != null) {
             cancelBtn.setOnClickListener(null);
         }
+        mPostureController.removeCallback(mPostureCallback);
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java b/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java
index 23c4413..b2db86f 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java
@@ -115,6 +115,7 @@
         mSecureSettings = secureSettings;
         mCallback = callback;
         mProximitySensor = proximitySensor;
+        mProximitySensor.setTag(TAG);
         mSelectivelyRegisterProxSensors = dozeParameters.getSelectivelyRegisterSensorsUsingProx();
         mListeningProxSensors = !mSelectivelyRegisterProxSensors;
         mScreenOffUdfpsEnabled =
diff --git a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagReader.java b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagReader.java
index 1fec7a6..08247a8 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagReader.java
+++ b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagReader.java
@@ -16,6 +16,7 @@
 
 package com.android.systemui.flags;
 
+import android.content.Context;
 import android.content.res.Resources;
 import android.util.SparseArray;
 
@@ -25,6 +26,9 @@
 import com.android.systemui.R;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.plugins.FlagReaderPlugin;
+import com.android.systemui.plugins.PluginListener;
+import com.android.systemui.shared.plugins.PluginManager;
 import com.android.systemui.util.wrapper.BuildInfo;
 
 import javax.inject.Inject;
@@ -54,18 +58,60 @@
 public class FeatureFlagReader {
     private final Resources mResources;
     private final boolean mAreFlagsOverrideable;
+    private final PluginManager mPluginManager;
     private final SystemPropertiesHelper mSystemPropertiesHelper;
     private final SparseArray<CachedFlag> mCachedFlags = new SparseArray<>();
 
+    private FlagReaderPlugin mPlugin = new FlagReaderPlugin(){};
+
     @Inject
     public FeatureFlagReader(
             @Main Resources resources,
             BuildInfo build,
+            PluginManager pluginManager,
             SystemPropertiesHelper systemPropertiesHelper) {
         mResources = resources;
+        mPluginManager = pluginManager;
         mSystemPropertiesHelper = systemPropertiesHelper;
         mAreFlagsOverrideable =
                 build.isDebuggable() && mResources.getBoolean(R.bool.are_flags_overrideable);
+
+        mPluginManager.addPluginListener(mPluginListener, FlagReaderPlugin.class);
+    }
+
+    private final PluginListener<FlagReaderPlugin> mPluginListener =
+            new PluginListener<FlagReaderPlugin>() {
+                public void onPluginConnected(FlagReaderPlugin plugin, Context context) {
+                    mPlugin = plugin;
+                }
+
+                public void onPluginDisconnected(FlagReaderPlugin plugin) {
+                    mPlugin = new FlagReaderPlugin() {};
+                }
+            };
+
+    boolean isEnabled(BooleanFlag flag) {
+        return mPlugin.isEnabled(flag.getId(), flag.getDefault());
+    }
+
+    String getValue(StringFlag flag) {
+        return mPlugin.getValue(flag.getId(), flag.getDefault());
+    }
+
+    int getValue(IntFlag flag) {
+        return mPlugin.getValue(flag.getId(), flag.getDefault());
+    }
+
+    long getValue(LongFlag flag) {
+        return mPlugin.getValue(flag.getId(), flag.getDefault());
+    }
+
+    float getValue(FloatFlag flag) {
+        return mPlugin.getValue(flag.getId(), flag.getDefault());
+    }
+
+    double getValue(DoubleFlag flag) {
+        return mPlugin.getValue(flag.getId(), flag.getDefault());
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlags.java b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlags.java
index 5882179..0c9e6de 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlags.java
+++ b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlags.java
@@ -22,6 +22,10 @@
 import com.android.systemui.R;
 import com.android.systemui.dagger.SysUISingleton;
 
+import java.lang.reflect.Field;
+import java.util.HashMap;
+import java.util.Map;
+
 import javax.inject.Inject;
 
 /**
@@ -40,6 +44,54 @@
         mContext = context;
     }
 
+    /**
+     * @param flag The {@link BooleanFlag} of interest.
+     * @return The value of the flag.
+     */
+    public boolean isEnabled(BooleanFlag flag) {
+        return mFlagReader.isEnabled(flag);
+    }
+
+    /**
+     * @param flag The {@link StringFlag} of interest.
+     * @return The value of the flag.
+     */
+    public String getValue(StringFlag flag) {
+        return mFlagReader.getValue(flag);
+    }
+
+    /**
+     * @param flag The {@link IntFlag} of interest.
+     * @return The value of the flag.
+     */
+    public int getValue(IntFlag flag) {
+        return mFlagReader.getValue(flag);
+    }
+
+    /**
+     * @param flag The {@link LongFlag} of interest.
+     * @return The value of the flag.
+     */
+    public long getValue(LongFlag flag) {
+        return mFlagReader.getValue(flag);
+    }
+
+    /**
+     * @param flag The {@link FloatFlag} of interest.
+     * @return The value of the flag.
+     */
+    public float getValue(FloatFlag flag) {
+        return mFlagReader.getValue(flag);
+    }
+
+    /**
+     * @param flag The {@link DoubleFlag} of interest.
+     * @return The value of the flag.
+     */
+    public double getValue(DoubleFlag flag) {
+        return mFlagReader.getValue(flag);
+    }
+
     public boolean isNewNotifPipelineEnabled() {
         return mFlagReader.isEnabled(R.bool.flag_notification_pipeline2);
     }
@@ -107,4 +159,31 @@
     public static boolean isProviderModelSettingEnabled(Context context) {
         return FeatureFlagUtils.isEnabled(context, FeatureFlagUtils.SETTINGS_PROVIDER_MODEL);
     }
+
+    private Map<Integer, Flag<?>> collectFlags() {
+        Map<Integer, Flag<?>> flags = new HashMap<>();
+
+        Field[] fields = this.getClass().getFields();
+
+        for (Field field : fields) {
+            Class<?> t = field.getType();
+            if (Flag.class.isAssignableFrom(t)) {
+                try {
+                    //flags.add((Flag<?>) field.get(null));
+                    Flag flag = (Flag) field.get(null);
+                    flags.put(flag.getId(), flag);
+                } catch (IllegalAccessException e) {
+                    // no-op
+                }
+            }
+        }
+
+        return flags;
+    }
+
+    /** Simple interface for beinga alerted when a specific flag changes value. */
+    public interface Listener {
+        /** */
+        void onFlagChanged(Flag<?> flag);
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
index aaa3bf0..2bf4bf4 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
@@ -106,7 +106,7 @@
     static final String DEBUG_MISSING_GESTURE_TAG = "NoBackGesture";
 
     private static final boolean ENABLE_PER_WINDOW_INPUT_ROTATION =
-            SystemProperties.getBoolean("persist.debug.per_window_input_rotation", false);
+            SystemProperties.getBoolean("persist.debug.per_window_input_rotation", true);
 
     private ISystemGestureExclusionListener mGestureExclusionListener =
             new ISystemGestureExclusionListener.Stub() {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
index f754494..4e09bc6 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
@@ -52,6 +52,7 @@
 import com.android.systemui.statusbar.notification.stack.StackStateAnimator;
 import com.android.systemui.statusbar.phone.KeyguardBypassController;
 import com.android.systemui.statusbar.phone.NotificationsQuickSettingsContainer;
+import com.android.systemui.statusbar.policy.BrightnessMirrorController;
 import com.android.systemui.statusbar.policy.RemoteInputQuickSettingsDisabler;
 import com.android.systemui.util.InjectionInflationController;
 import com.android.systemui.util.LifecycleFragment;
@@ -414,6 +415,12 @@
         return mQSPanelController;
     }
 
+    public void setBrightnessMirrorController(
+            BrightnessMirrorController brightnessMirrorController) {
+        mQSPanelController.setBrightnessMirror(brightnessMirrorController);
+        mQuickQSPanelController.setBrightnessMirror(brightnessMirrorController);
+    }
+
     @Override
     public boolean isShowingDetail() {
         return mQSCustomizerController.isCustomizing() || mQSDetail.isShowingDetail();
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java
index f3d071e..6e09f22 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java
@@ -40,6 +40,7 @@
 import com.android.systemui.qs.dagger.QSScope;
 import com.android.systemui.qs.logging.QSLogger;
 import com.android.systemui.settings.brightness.BrightnessController;
+import com.android.systemui.settings.brightness.BrightnessMirrorHandler;
 import com.android.systemui.settings.brightness.BrightnessSlider;
 import com.android.systemui.statusbar.policy.BrightnessMirrorController;
 import com.android.systemui.tuner.TunerService;
@@ -60,10 +61,9 @@
     private final QSTileRevealController.Factory mQsTileRevealControllerFactory;
     private final FalsingManager mFalsingManager;
     private final BrightnessController mBrightnessController;
-    private final BrightnessSlider.Factory mBrightnessSliderFactory;
     private final BrightnessSlider mBrightnessSlider;
+    private final BrightnessMirrorHandler mBrightnessMirrorHandler;
 
-    private BrightnessMirrorController mBrightnessMirrorController;
     private boolean mGridContentVisible = true;
 
     private final QSPanel.OnConfigurationChangedListener mOnConfigurationChangedListener =
@@ -75,14 +75,10 @@
             if (mView.isListening()) {
                 refreshAllTiles();
             }
-            updateBrightnessMirror();
             mView.switchSecurityFooter(mShouldUseSplitNotificationShade);
         }
     };
 
-    private final BrightnessMirrorController.BrightnessMirrorListener mBrightnessMirrorListener =
-            mirror -> updateBrightnessMirror();
-
     private View.OnTouchListener mTileLayoutTouchListener = new View.OnTouchListener() {
         @Override
         public boolean onTouch(View v, MotionEvent event) {
@@ -110,12 +106,12 @@
         mQsTileRevealControllerFactory = qsTileRevealControllerFactory;
         mFalsingManager = falsingManager;
         mQsSecurityFooter.setHostEnvironment(qstileHost);
-        mBrightnessSliderFactory = brightnessSliderFactory;
 
-        mBrightnessSlider = mBrightnessSliderFactory.create(getContext(), mView);
+        mBrightnessSlider = brightnessSliderFactory.create(getContext(), mView);
         mView.setBrightnessView(mBrightnessSlider.getRootView());
 
         mBrightnessController = brightnessControllerFactory.create(mBrightnessSlider);
+        mBrightnessMirrorHandler = new BrightnessMirrorHandler(mBrightnessController);
     }
 
     @Override
@@ -142,9 +138,7 @@
         mView.addOnConfigurationChangedListener(mOnConfigurationChangedListener);
         mView.setSecurityFooter(mQsSecurityFooter.getView(), mShouldUseSplitNotificationShade);
         switchTileLayout(true);
-        if (mBrightnessMirrorController != null) {
-            mBrightnessMirrorController.addCallback(mBrightnessMirrorListener);
-        }
+        mBrightnessMirrorHandler.onQsPanelAttached();
 
         ((PagedTileLayout) mView.getOrCreateTileLayout())
                 .setOnTouchListener(mTileLayoutTouchListener);
@@ -160,9 +154,7 @@
     protected void onViewDetached() {
         mTunerService.removeTunable(mView);
         mView.removeOnConfigurationChangedListener(mOnConfigurationChangedListener);
-        if (mBrightnessMirrorController != null) {
-            mBrightnessMirrorController.removeCallback(mBrightnessMirrorListener);
-        }
+        mBrightnessMirrorHandler.onQsPanelDettached();
         super.onViewDetached();
     }
 
@@ -196,23 +188,8 @@
         }
     }
 
-    /** */
     public void setBrightnessMirror(BrightnessMirrorController brightnessMirrorController) {
-        mBrightnessMirrorController = brightnessMirrorController;
-        if (mBrightnessMirrorController != null) {
-            mBrightnessMirrorController.removeCallback(mBrightnessMirrorListener);
-        }
-        mBrightnessMirrorController = brightnessMirrorController;
-        if (mBrightnessMirrorController != null) {
-            mBrightnessMirrorController.addCallback(mBrightnessMirrorListener);
-        }
-        updateBrightnessMirror();
-    }
-
-    private void updateBrightnessMirror() {
-        if (mBrightnessMirrorController != null) {
-            mBrightnessSlider.setMirrorControllerAndMirror(mBrightnessMirrorController);
-        }
+        mBrightnessMirrorHandler.setController(brightnessMirrorController);
     }
 
     /** Get the QSTileHost this panel uses. */
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickQSBrightnessController.kt b/packages/SystemUI/src/com/android/systemui/qs/QuickQSBrightnessController.kt
index 7c81abc..14374ff 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QuickQSBrightnessController.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/QuickQSBrightnessController.kt
@@ -19,6 +19,8 @@
 import androidx.annotation.VisibleForTesting
 import com.android.systemui.settings.brightness.BrightnessController
 import com.android.systemui.settings.brightness.BrightnessSlider
+import com.android.systemui.settings.brightness.MirroredBrightnessController
+import com.android.systemui.statusbar.policy.BrightnessMirrorController
 import javax.inject.Inject
 
 /**
@@ -27,7 +29,7 @@
  */
 class QuickQSBrightnessController @VisibleForTesting constructor(
     private val brightnessControllerFactory: () -> BrightnessController
-) {
+) : MirroredBrightnessController {
 
     @Inject constructor(
         brightnessControllerFactory: BrightnessController.Factory,
@@ -42,6 +44,7 @@
 
     private var isListening = false
     private var brightnessController: BrightnessController? = null
+    private var mirrorController: BrightnessMirrorController? = null
 
     fun init(shouldUseSplitNotificationShade: Boolean) {
         refreshVisibility(shouldUseSplitNotificationShade)
@@ -77,6 +80,11 @@
         }
     }
 
+    override fun setMirror(controller: BrightnessMirrorController) {
+        mirrorController = controller
+        mirrorController?.let { brightnessController?.setMirror(it) }
+    }
+
     private fun hideBrightnessSlider() {
         brightnessController?.hideSlider()
     }
@@ -84,11 +92,10 @@
     private fun showBrightnessSlider() {
         if (brightnessController == null) {
             brightnessController = brightnessControllerFactory()
-        }
-        brightnessController?.showSlider()
-        if (!isListening) {
+            mirrorController?.also { brightnessController?.setMirror(it) }
             brightnessController?.registerCallbacks()
             isListening = true
         }
+        brightnessController?.showSlider()
     }
-}
\ No newline at end of file
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanelController.java b/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanelController.java
index 74cd50c..8c7a2cd 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanelController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanelController.java
@@ -29,6 +29,8 @@
 import com.android.systemui.qs.customize.QSCustomizerController;
 import com.android.systemui.qs.dagger.QSScope;
 import com.android.systemui.qs.logging.QSLogger;
+import com.android.systemui.settings.brightness.BrightnessMirrorHandler;
+import com.android.systemui.statusbar.policy.BrightnessMirrorController;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -50,6 +52,7 @@
 
     // brightness is visible only in split shade
     private final QuickQSBrightnessController mBrightnessController;
+    private final BrightnessMirrorHandler mBrightnessMirrorHandler;
 
     @Inject
     QuickQSPanelController(QuickQSPanel view, QSTileHost qsTileHost,
@@ -63,6 +66,7 @@
         super(view, qsTileHost, qsCustomizerController, usingMediaPlayer, mediaHost, metricsLogger,
                 uiEventLogger, qsLogger, dumpManager);
         mBrightnessController = quickQSBrightnessController;
+        mBrightnessMirrorHandler = new BrightnessMirrorHandler(mBrightnessController);
     }
 
     @Override
@@ -78,12 +82,14 @@
     protected void onViewAttached() {
         super.onViewAttached();
         mView.addOnConfigurationChangedListener(mOnConfigurationChangedListener);
+        mBrightnessMirrorHandler.onQsPanelAttached();
     }
 
     @Override
     protected void onViewDetached() {
         super.onViewDetached();
         mView.removeOnConfigurationChangedListener(mOnConfigurationChangedListener);
+        mBrightnessMirrorHandler.onQsPanelDettached();
     }
 
     @Override
@@ -132,4 +138,8 @@
     public int getNumQuickTiles() {
         return mView.getNumQuickTiles();
     }
+
+    public void setBrightnessMirror(BrightnessMirrorController brightnessMirrorController) {
+        mBrightnessMirrorHandler.setController(brightnessMirrorController);
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessController.java b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessController.java
index a9ebcad..185b8ef 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessController.java
+++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessController.java
@@ -50,12 +50,13 @@
 import com.android.systemui.Dependency;
 import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.settings.CurrentUserTracker;
+import com.android.systemui.statusbar.policy.BrightnessMirrorController;
 
 import java.util.ArrayList;
 
 import javax.inject.Inject;
 
-public class BrightnessController implements ToggleSlider.Listener {
+public class BrightnessController implements ToggleSlider.Listener, MirroredBrightnessController {
     private static final String TAG = "StatusBar.BrightnessController";
     private static final int SLIDER_ANIMATION_DURATION = 3000;
 
@@ -109,6 +110,11 @@
 
     private ValueAnimator mSliderAnimator;
 
+    @Override
+    public void setMirror(BrightnessMirrorController controller) {
+        mControl.setMirrorControllerAndMirror(controller);
+    }
+
     public interface BrightnessStateChangeCallback {
         /** Indicates that some of the brightness settings have changed */
         void onBrightnessLevelChanged();
diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessMirrorHandler.kt b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessMirrorHandler.kt
new file mode 100644
index 0000000..51aa339
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessMirrorHandler.kt
@@ -0,0 +1,46 @@
+/*
+ * 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.systemui.settings.brightness
+
+import com.android.systemui.statusbar.policy.BrightnessMirrorController
+import com.android.systemui.statusbar.policy.BrightnessMirrorController.BrightnessMirrorListener
+
+class BrightnessMirrorHandler(private val brightnessController: MirroredBrightnessController) {
+
+    private var mirrorController: BrightnessMirrorController? = null
+
+    private val brightnessMirrorListener = BrightnessMirrorListener { updateBrightnessMirror() }
+
+    fun onQsPanelAttached() {
+        mirrorController?.addCallback(brightnessMirrorListener)
+    }
+
+    fun onQsPanelDettached() {
+        mirrorController?.removeCallback(brightnessMirrorListener)
+    }
+
+    fun setController(controller: BrightnessMirrorController) {
+        mirrorController?.removeCallback(brightnessMirrorListener)
+        mirrorController = controller
+        mirrorController?.addCallback(brightnessMirrorListener)
+        updateBrightnessMirror()
+    }
+
+    private fun updateBrightnessMirror() {
+        mirrorController?.let { brightnessController.setMirror(it) }
+    }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSlider.java b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSlider.java
index 896106a..b0e320a 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSlider.java
+++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSlider.java
@@ -140,13 +140,7 @@
     @Override
     public void setMirrorControllerAndMirror(BrightnessMirrorController c) {
         mMirrorController = c;
-        if (c != null) {
-            setMirror(c.getToggleSlider());
-        } else {
-            // If there's no mirror, we may be the ones dispatching, events but we should not mirror
-            // them
-            mView.setOnDispatchTouchEventListener(null);
-        }
+        setMirror(c.getToggleSlider());
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/MirroredBrightnessController.kt b/packages/SystemUI/src/com/android/systemui/settings/brightness/MirroredBrightnessController.kt
new file mode 100644
index 0000000..8d857de
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/MirroredBrightnessController.kt
@@ -0,0 +1,26 @@
+/*
+ * 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.systemui.settings.brightness
+
+import com.android.systemui.statusbar.policy.BrightnessMirrorController
+
+/**
+ * Indicates controller that has brightness slider and uses [BrightnessMirrorController]
+ */
+interface MirroredBrightnessController {
+    fun setMirror(controller: BrightnessMirrorController)
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt
index f2060b7..fdbe728 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt
@@ -31,6 +31,8 @@
 import android.os.UserHandle
 import android.provider.Settings
 import android.view.View
+import android.view.View.GONE
+import android.view.View.VISIBLE
 import android.view.ViewGroup
 import com.android.settingslib.Utils
 import com.android.systemui.R
@@ -44,14 +46,21 @@
 import com.android.systemui.plugins.statusbar.StatusBarStateController
 import com.android.systemui.settings.UserTracker
 import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.statusbar.StatusBarState
+import com.android.systemui.statusbar.notification.AnimatableProperty
+import com.android.systemui.statusbar.notification.PropertyAnimator
+import com.android.systemui.statusbar.notification.stack.AnimationProperties
+import com.android.systemui.statusbar.notification.stack.StackStateAnimator
 import com.android.systemui.statusbar.policy.ConfigurationController
 import com.android.systemui.util.concurrency.Execution
 import com.android.systemui.util.settings.SecureSettings
-import java.lang.RuntimeException
 import java.util.Optional
 import java.util.concurrent.Executor
 import javax.inject.Inject
 
+private val ANIMATION_PROPERTIES = AnimationProperties()
+        .setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD.toLong())
+
 /**
  * Controller for managing the smartspace view on the lockscreen
  */
@@ -72,10 +81,15 @@
     @Main private val handler: Handler,
     optionalPlugin: Optional<BcSmartspaceDataPlugin>
 ) {
+
+    var splitShadeContainer: ViewGroup? = null
+    private var singlePaneContainer: ViewGroup? = null
+
     private var session: SmartspaceSession? = null
     private val plugin: BcSmartspaceDataPlugin? = optionalPlugin.orElse(null)
     private lateinit var smartspaceView: SmartspaceView
 
+    // smartspace casted to View
     lateinit var view: View
         private set
 
@@ -83,12 +97,60 @@
     private var showSensitiveContentForManagedUser = false
     private var managedUserHandle: UserHandle? = null
 
-    fun isEnabled(): Boolean {
+    private var isAod = false
+    private var isSplitShade = false
+
+    fun isSmartspaceEnabled(): Boolean {
         execution.assertIsMainThread()
 
         return featureFlags.isSmartspaceEnabled && plugin != null
     }
 
+    fun setKeyguardStatusContainer(container: ViewGroup) {
+        singlePaneContainer = container
+        // reattach smartspace if necessary as this might be a new container
+        updateSmartSpaceContainer()
+    }
+
+    fun onSplitShadeChanged(splitShade: Boolean) {
+        isSplitShade = splitShade
+        updateSmartSpaceContainer()
+    }
+
+    private fun updateSmartSpaceContainer() {
+        if (!isSmartspaceEnabled()) return
+        // in AOD we always want to show smartspace on the left i.e. in singlePaneContainer
+        if (isSplitShade && !isAod) {
+            switchContainerVisibility(
+                    newParent = splitShadeContainer,
+                    oldParent = singlePaneContainer)
+        } else {
+            switchContainerVisibility(
+                    newParent = singlePaneContainer,
+                    oldParent = splitShadeContainer)
+        }
+        requestSmartspaceUpdate()
+    }
+
+    private fun switchContainerVisibility(newParent: ViewGroup?, oldParent: ViewGroup?) {
+        // it might be the case that smartspace was already attached and we just needed to update
+        // visibility, e.g. going from lockscreen -> unlocked -> lockscreen
+        if (newParent?.childCount == 0) {
+            oldParent?.removeAllViews()
+            newParent.addView(buildAndConnectView(newParent))
+        }
+        oldParent?.visibility = GONE
+        newParent?.visibility = VISIBLE
+    }
+
+    fun setSplitShadeSmartspaceAlpha(alpha: Float) {
+        // the other container's alpha is modified as a part of keyguard status view, so we don't
+        // have to do that here
+        if (splitShadeContainer?.visibility == VISIBLE) {
+            splitShadeContainer?.alpha = alpha
+        }
+    }
+
     /**
      * Constructs the smartspace view and connects it to the smartspace service. Subsequent calls
      * are idempotent until [disconnect] is called.
@@ -96,7 +158,7 @@
     fun buildAndConnectView(parent: ViewGroup): View {
         execution.assertIsMainThread()
 
-        if (!isEnabled()) {
+        if (!isSmartspaceEnabled()) {
             throw RuntimeException("Cannot build view when not enabled")
         }
 
@@ -182,7 +244,6 @@
         userTracker.removeCallback(userTrackerCallback)
         contentResolver.unregisterContentObserver(settingsObserver)
         configurationController.removeCallback(configChangeListener)
-        statusBarStateController.removeCallback(statusBarStateListener)
         session = null
 
         plugin?.onTargetsAvailable(emptyList())
@@ -198,6 +259,13 @@
         plugin?.unregisterListener(listener)
     }
 
+    fun shiftSplitShadeSmartspace(y: Int, animate: Boolean) {
+        if (splitShadeContainer?.visibility == VISIBLE) {
+            PropertyAnimator.setProperty(splitShadeContainer, AnimatableProperty.Y, y.toFloat(),
+                    ANIMATION_PROPERTIES, animate)
+        }
+    }
+
     private val sessionListener = SmartspaceSession.OnTargetsAvailableListener { targets ->
         execution.assertIsMainThread()
         val filteredTargets = targets.filter(::filterSmartspaceTarget)
@@ -233,6 +301,23 @@
             execution.assertIsMainThread()
             smartspaceView.setDozeAmount(eased)
         }
+
+        override fun onDozingChanged(isDozing: Boolean) {
+            isAod = isDozing
+            updateSmartSpaceContainer()
+        }
+
+        override fun onStateChanged(newState: Int) {
+            if (newState == StatusBarState.KEYGUARD) {
+                if (isSmartspaceEnabled()) {
+                    updateSmartSpaceContainer()
+                }
+            } else {
+                splitShadeContainer?.visibility = GONE
+                singlePaneContainer?.visibility = GONE
+                disconnect()
+            }
+        }
     }
 
     private fun filterSmartspaceTarget(t: SmartspaceTarget): Boolean {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
index 34929f2..880e558 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
@@ -407,6 +407,7 @@
     private boolean mBackwardScrollable;
     private NotificationShelf mShelf;
     private int mMaxDisplayedNotifications = -1;
+    private float mKeyguardBottomPadding = -1;
     private int mStatusBarHeight;
     private int mMinInteractionHeight;
     private final Rect mClipRect = new Rect();
@@ -742,6 +743,16 @@
             mDebugPaint.setColor(Color.YELLOW);
             canvas.drawLine(0, y, getWidth(), y, mDebugPaint);
 
+            y = (int) mMaxLayoutHeight;
+            mDebugPaint.setColor(Color.MAGENTA);
+            canvas.drawLine(0, y, getWidth(), y, mDebugPaint);
+
+            if (mKeyguardBottomPadding >= 0) {
+                y = getHeight() - (int) mKeyguardBottomPadding;
+                mDebugPaint.setColor(Color.GRAY);
+                canvas.drawLine(0, y, getWidth(), y, mDebugPaint);
+            }
+
             y = getHeight() - getEmptyBottomMargin();
             mDebugPaint.setColor(Color.GREEN);
             canvas.drawLine(0, y, getWidth(), y, mDebugPaint);
@@ -4784,6 +4795,16 @@
         }
     }
 
+    /**
+     * This is used for debugging only; it will be used to draw the otherwise invisible line which
+     * NotificationPanelViewController treats as the bottom when calculating how many notifications
+     * appear on the keyguard.
+     * Setting a negative number will disable rendering this line.
+     */
+    public void setKeyguardBottomPadding(float keyguardBottomPadding) {
+        mKeyguardBottomPadding = keyguardBottomPadding;
+    }
+
     @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
     public void setShouldShowShelfOnly(boolean shouldShowShelfOnly) {
         mShouldShowShelfOnly = shouldShowShelfOnly;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
index 417418a..a865c3a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
@@ -1218,6 +1218,16 @@
         mNotificationListContainer.setMaxDisplayedNotifications(maxNotifications);
     }
 
+    /**
+     * This is used for debugging only; it will be used to draw the otherwise invisible line which
+     * NotificationPanelViewController treats as the bottom when calculating how many notifications
+     * appear on the keyguard.
+     * Setting a negative number will disable rendering this line.
+     */
+    public void setKeyguardBottomPadding(float keyguardBottomPadding) {
+        mView.setKeyguardBottomPadding(keyguardBottomPadding);
+    }
+
     public RemoteInputController.Delegate createDelegate() {
         return new RemoteInputController.Delegate() {
             public void setRemoteInputActive(NotificationEntry entry,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java
index a0af389..7908d84 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java
@@ -231,7 +231,9 @@
      * possible if AOD isn't even enabled or if the flag is disabled.
      */
     public boolean canControlUnlockedScreenOff() {
-        return getAlwaysOn() && mFeatureFlags.useNewLockscreenAnimations();
+        return getAlwaysOn()
+                && mFeatureFlags.useNewLockscreenAnimations()
+                && !getDisplayNeedsBlanking();
     }
 
     private boolean getBoolean(String propName, int resId) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java
index f77c052..19c2585 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java
@@ -33,23 +33,11 @@
  */
 public class KeyguardClockPositionAlgorithm {
     /**
-     * How much the clock height influences the shade position.
-     * 0 means nothing, 1 means move the shade up by the height of the clock
-     * 0.5f means move the shade up by half of the size of the clock.
-     */
-    private static float CLOCK_HEIGHT_WEIGHT = 0.7f;
-
-    /**
      * Margin between the bottom of the status view and the notification shade.
      */
     private int mStatusViewBottomMargin;
 
     /**
-     * Height of the parent view - display size in px.
-     */
-    private int mHeight;
-
-    /**
      * Height of {@link KeyguardStatusView}.
      */
     private int mKeyguardStatusHeight;
@@ -68,21 +56,6 @@
     private int mUserSwitchPreferredY;
 
     /**
-     * Whether or not there is a custom clock face on keyguard.
-     */
-    private boolean mHasCustomClock;
-
-    /**
-     * Whether or not the NSSL contains any visible notifications.
-     */
-    private boolean mHasVisibleNotifs;
-
-    /**
-     * Height of notification stack: Sum of height of each notification.
-     */
-    private int mNotificationStackHeight;
-
-    /**
      * Minimum top margin to avoid overlap with status bar, lock icon, or multi-user switcher
      * avatar.
      */
@@ -148,6 +121,7 @@
     private int mUnlockedStackScrollerPadding;
 
     private boolean mIsSplitShade;
+    private int mSplitShadeSmartspaceHeight;
 
     /**
      * Refreshes the dimension values.
@@ -170,28 +144,25 @@
      * Sets up algorithm values.
      */
     public void setup(int keyguardStatusBarHeaderHeight, int maxShadeBottom,
-            int notificationStackHeight, float panelExpansion, int parentHeight,
-            int keyguardStatusHeight, int userSwitchHeight, int userSwitchPreferredY,
-            boolean hasCustomClock, boolean hasVisibleNotifs, float dark,
+            float panelExpansion,
+            int keyguardStatusHeight, int userSwitchHeight, int userSwitchPreferredY, float dark,
             float overStrechAmount, boolean bypassEnabled, int unlockedStackScrollerPadding,
-            float qsExpansion, int cutoutTopInset, boolean isSplitShade) {
+            float qsExpansion, int cutoutTopInset, int splitShadeSmartspaceHeight,
+            boolean isSplitShade) {
         mMinTopMargin = keyguardStatusBarHeaderHeight + Math.max(mContainerTopPadding,
                 userSwitchHeight);
         mMaxShadeBottom = maxShadeBottom;
-        mNotificationStackHeight = notificationStackHeight;
         mPanelExpansion = panelExpansion;
-        mHeight = parentHeight;
         mKeyguardStatusHeight = keyguardStatusHeight + mStatusViewBottomMargin;
         mUserSwitchHeight = userSwitchHeight;
         mUserSwitchPreferredY = userSwitchPreferredY;
-        mHasCustomClock = hasCustomClock;
-        mHasVisibleNotifs = hasVisibleNotifs;
         mDarkAmount = dark;
         mOverStretchAmount = overStrechAmount;
         mBypassEnabled = bypassEnabled;
         mUnlockedStackScrollerPadding = unlockedStackScrollerPadding;
         mQsExpansion = qsExpansion;
         mCutoutTopInset = cutoutTopInset;
+        mSplitShadeSmartspaceHeight = splitShadeSmartspaceHeight;
         mIsSplitShade = isSplitShade;
     }
 
@@ -213,7 +184,7 @@
         if (mBypassEnabled) {
             return (int) (mUnlockedStackScrollerPadding + mOverStretchAmount);
         } else if (mIsSplitShade) {
-            return clockYPosition;
+            return clockYPosition + mSplitShadeSmartspaceHeight;
         } else {
             return clockYPosition + mKeyguardStatusHeight;
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
index 3b65ce0..66d5960 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
@@ -138,6 +138,7 @@
 import com.android.systemui.statusbar.SysuiStatusBarStateController;
 import com.android.systemui.statusbar.VibratorHelper;
 import com.android.systemui.statusbar.events.PrivacyDotViewController;
+import com.android.systemui.statusbar.lockscreen.LockscreenSmartspaceController;
 import com.android.systemui.statusbar.notification.AnimatableProperty;
 import com.android.systemui.statusbar.notification.ConversationNotificationManager;
 import com.android.systemui.statusbar.notification.DynamicPrivacyController;
@@ -229,6 +230,7 @@
     private final HeightListener mHeightListener = new HeightListener();
     private final ConfigurationListener mConfigurationListener = new ConfigurationListener();
     private final SettingsChangeObserver mSettingsChangeObserver;
+    private final LockscreenSmartspaceController mLockscreenSmartspaceController;
 
     @VisibleForTesting final StatusBarStateListener mStatusBarStateListener =
             new StatusBarStateListener();
@@ -338,6 +340,8 @@
     private final SplitShadeHeaderController mSplitShadeHeaderController;
     private final RecordingController mRecordingController;
     private boolean mShouldUseSplitNotificationShade;
+    // The bottom padding reserved for elements of the keyguard measuring notifications
+    private float mKeyguardNotificationBottomPadding;
     // Current max allowed keyguard notifications determined by measuring the panel
     private int mMaxAllowedKeyguardNotifications;
 
@@ -353,6 +357,8 @@
     private KeyguardStatusViewController mKeyguardStatusViewController;
     private LockIconViewController mLockIconViewController;
     private NotificationsQuickSettingsContainer mNotificationContainerParent;
+    private FrameLayout mSplitShadeSmartspaceContainer;
+
     private boolean mAnimateNextPositionUpdate;
     private float mQuickQsOffsetHeight;
     private UnlockedScreenOffAnimationController mUnlockedScreenOffAnimationController;
@@ -734,6 +740,7 @@
             @Main Executor uiExecutor,
             SecureSettings secureSettings,
             SplitShadeHeaderController splitShadeHeaderController,
+            LockscreenSmartspaceController lockscreenSmartspaceController,
             UnlockedScreenOffAnimationController unlockedScreenOffAnimationController,
             NotificationRemoteInputManager remoteInputManager,
             ControlsComponent controlsComponent) {
@@ -765,6 +772,7 @@
         mQSDetailDisplayer = qsDetailDisplayer;
         mFragmentService = fragmentService;
         mSettingsChangeObserver = new SettingsChangeObserver(handler);
+        mLockscreenSmartspaceController = lockscreenSmartspaceController;
         mShouldUseSplitNotificationShade =
                 Utils.shouldUseSplitNotificationShade(mResources);
         mView.setWillNotDraw(!DEBUG);
@@ -859,6 +867,9 @@
         loadDimens();
         mKeyguardStatusBar = mView.findViewById(R.id.keyguard_header);
         mBigClockContainer = mView.findViewById(R.id.big_clock_container);
+        mSplitShadeSmartspaceContainer = mView.findViewById(R.id.split_shade_smartspace_container);
+        mLockscreenSmartspaceController.setSplitShadeContainer(mSplitShadeSmartspaceContainer);
+        mLockscreenSmartspaceController.onSplitShadeChanged(mShouldUseSplitNotificationShade);
 
         UserAvatarView userAvatarView = null;
         KeyguardUserSwitcherView keyguardUserSwitcherView = null;
@@ -1071,7 +1082,7 @@
         mNotificationContainerParent.setSplitShadeEnabled(mShouldUseSplitNotificationShade);
 
         updateKeyguardStatusViewAlignment(false /* animate */);
-
+        mLockscreenSmartspaceController.onSplitShadeChanged(mShouldUseSplitNotificationShade);
         mKeyguardMediaController.refreshMediaPosition();
     }
 
@@ -1206,9 +1217,12 @@
         if (mKeyguardShowing && !mKeyguardBypassController.getBypassEnabled()) {
             mNotificationStackScrollLayoutController.setMaxDisplayedNotifications(
                     mMaxAllowedKeyguardNotifications);
+            mNotificationStackScrollLayoutController.setKeyguardBottomPadding(
+                    mKeyguardNotificationBottomPadding);
         } else {
             // no max when not on the keyguard
             mNotificationStackScrollLayoutController.setMaxDisplayedNotifications(-1);
+            mNotificationStackScrollLayoutController.setKeyguardBottomPadding(-1f);
         }
     }
 
@@ -1331,16 +1345,15 @@
                         ? 1.0f : mInterpolatedDarkAmount;
         mClockPositionAlgorithm.setup(mStatusBarHeaderHeightKeyguard,
                 totalHeight - bottomPadding,
-                mNotificationStackScrollLayoutController.getIntrinsicContentHeight(),
                 expandedFraction,
-                totalHeight,
                 mKeyguardStatusViewController.getLockscreenHeight(),
                 userIconHeight,
-                userSwitcherPreferredY, hasCustomClock(),
-                hasVisibleNotifications, darkamount, mOverStretchAmount,
+                userSwitcherPreferredY,
+                darkamount, mOverStretchAmount,
                 bypassEnabled, getUnlockedStackScrollerPadding(),
                 computeQsExpansionFraction(),
                 mDisplayTopInset,
+                mSplitShadeSmartspaceContainer.getHeight(),
                 mShouldUseSplitNotificationShade);
         mClockPositionAlgorithm.run(mClockPositionResult);
         boolean animate = mNotificationStackScrollLayoutController.isAddOrRemoveAnimationPending();
@@ -1360,6 +1373,9 @@
                     mClockPositionResult.userSwitchY,
                     animateClock);
         }
+        // no need to translate in X axis - horizontal position is determined by constraints
+        mLockscreenSmartspaceController
+                .shiftSplitShadeSmartspace(mClockPositionResult.clockY, animateClock);
         updateNotificationTranslucency();
         updateClock();
     }
@@ -1411,6 +1427,7 @@
 
         float bottomPadding = Math.max(mIndicationBottomPadding, mAmbientIndicationBottomPadding);
         bottomPadding = Math.max(lockIconPadding, bottomPadding);
+        mKeyguardNotificationBottomPadding = bottomPadding;
 
         float availableSpace =
                 mNotificationStackScrollLayoutController.getHeight()
@@ -1526,6 +1543,7 @@
         if (mKeyguardUserSwitcherController != null) {
             mKeyguardUserSwitcherController.setAlpha(alpha);
         }
+        mLockscreenSmartspaceController.setSplitShadeSmartspaceAlpha(alpha);
     }
 
     public void animateToFullShade(long delay) {
@@ -3707,6 +3725,7 @@
     public void dozeTimeTick() {
         mKeyguardBottomArea.dozeTimeTick();
         mKeyguardStatusViewController.dozeTimeTick();
+        mLockscreenSmartspaceController.requestSmartspaceUpdate();
         if (mInterpolatedDarkAmount > 0) {
             positionClockAndNotifications();
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java
index c300b11..70a46b2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java
@@ -27,6 +27,7 @@
 import android.graphics.Rect;
 import android.util.AttributeSet;
 import android.util.EventLog;
+import android.util.Log;
 import android.util.Pair;
 import android.view.DisplayCutout;
 import android.view.Gravity;
@@ -42,7 +43,6 @@
 import com.android.systemui.R;
 import com.android.systemui.plugins.DarkIconDispatcher;
 import com.android.systemui.plugins.DarkIconDispatcher.DarkReceiver;
-import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.util.leak.RotationUtils;
 
 import java.util.List;
@@ -52,7 +52,6 @@
     private static final String TAG = "PhoneStatusBarView";
     private static final boolean DEBUG = StatusBar.DEBUG;
     private static final boolean DEBUG_GESTURES = false;
-    private final CommandQueue mCommandQueue;
     private final StatusBarContentInsetsProvider mContentInsetsProvider;
 
     StatusBar mBar;
@@ -81,6 +80,8 @@
     @Nullable
     private List<StatusBar.ExpansionChangedListener> mExpansionChangedListeners;
 
+    private PanelEnabledProvider mPanelEnabledProvider;
+
     /**
      * Draw this many pixels into the left/right side of the cutout to optimally use the space
      */
@@ -89,7 +90,6 @@
 
     public PhoneStatusBarView(Context context, AttributeSet attrs) {
         super(context, attrs);
-        mCommandQueue = Dependency.get(CommandQueue.class);
         mContentInsetsProvider = Dependency.get(StatusBarContentInsetsProvider.class);
     }
 
@@ -177,7 +177,11 @@
 
     @Override
     public boolean panelEnabled() {
-        return mCommandQueue.panelsEnabled();
+        if (mPanelEnabledProvider == null) {
+            Log.e(TAG, "panelEnabledProvider is null; defaulting to super class.");
+            return super.panelEnabled();
+        }
+        return mPanelEnabledProvider.panelEnabled();
     }
 
     @Override
@@ -294,6 +298,11 @@
         }
     }
 
+    /** Set the {@link PanelEnabledProvider} to use. */
+    public void setPanelEnabledProvider(PanelEnabledProvider panelEnabledProvider) {
+        mPanelEnabledProvider = panelEnabledProvider;
+    }
+
     private void updateScrimFraction() {
         float scrimFraction = mPanelFraction;
         if (mMinFraction < 1.0f) {
@@ -391,4 +400,10 @@
     protected boolean shouldPanelBeVisible() {
         return mHeadsUpVisible || super.shouldPanelBeVisible();
     }
+
+    /** An interface that will provide whether panel is enabled. */
+    interface PanelEnabledProvider {
+        /** Returns true if the panel is enabled and false otherwise. */
+        boolean panelEnabled();
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.java
new file mode 100644
index 0000000..b36b67d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.java
@@ -0,0 +1,39 @@
+/*
+ * 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.systemui.statusbar.phone;
+
+import com.android.systemui.statusbar.CommandQueue;
+import com.android.systemui.util.ViewController;
+
+/** Controller for {@link PhoneStatusBarView}. */
+public class PhoneStatusBarViewController extends ViewController<PhoneStatusBarView> {
+
+    protected PhoneStatusBarViewController(
+            PhoneStatusBarView view,
+            CommandQueue commandQueue) {
+        super(view);
+        mView.setPanelEnabledProvider(commandQueue::panelsEnabled);
+    }
+
+    @Override
+    protected void onViewAttached() {
+    }
+
+    @Override
+    protected void onViewDetached() {
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
index 6cafa0d..4016b1a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
@@ -462,6 +462,7 @@
     protected NotificationShadeWindowView mNotificationShadeWindowView;
     protected StatusBarWindowView mPhoneStatusBarWindow;
     protected PhoneStatusBarView mStatusBarView;
+    private PhoneStatusBarViewController mPhoneStatusBarViewController;
     private AuthRippleController mAuthRippleController;
     private int mStatusBarWindowState = WINDOW_STATE_SHOWING;
     protected NotificationShadeWindowController mNotificationShadeWindowController;
@@ -1206,6 +1207,9 @@
                     mStatusBarView.setPanel(mNotificationPanelViewController);
                     mStatusBarView.setScrimController(mScrimController);
                     mStatusBarView.setExpansionChangedListeners(mExpansionChangedListeners);
+                    mPhoneStatusBarViewController =
+                            new PhoneStatusBarViewController(mStatusBarView, mCommandQueue);
+                    mPhoneStatusBarViewController.init();
 
                     mBatteryMeterViewController = new BatteryMeterViewController(
                             mStatusBarView.findViewById(R.id.battery)
@@ -1365,7 +1369,7 @@
                 QS qs = (QS) f;
                 if (qs instanceof QSFragment) {
                     mQSPanelController = ((QSFragment) qs).getQSPanelController();
-                    mQSPanelController.setBrightnessMirror(mBrightnessMirrorController);
+                    ((QSFragment) qs).setBrightnessMirrorController(mBrightnessMirrorController);
                 }
             });
         }
diff --git a/packages/SystemUI/src/com/android/systemui/util/sensors/ProximitySensor.java b/packages/SystemUI/src/com/android/systemui/util/sensors/ProximitySensor.java
index b38fc77..90e022a5 100644
--- a/packages/SystemUI/src/com/android/systemui/util/sensors/ProximitySensor.java
+++ b/packages/SystemUI/src/com/android/systemui/util/sensors/ProximitySensor.java
@@ -290,15 +290,18 @@
             return;
         }
 
-        if (!mSecondaryThresholdSensor.isLoaded()) {
+
+        if (!mSecondaryThresholdSensor.isLoaded()) {  // No secondary
             logDebug("Primary sensor event: " + event.getBelow() + ". No secondary.");
             onSensorEvent(event);
-        } else if (event.getBelow()) {
+        } else if (event.getBelow()) {  // Covered? Check secondary.
             logDebug("Primary sensor event: " + event.getBelow() + ". Checking secondary.");
             if (mCancelSecondaryRunnable != null) {
                 mCancelSecondaryRunnable.run();
             }
             mSecondaryThresholdSensor.resume();
+        } else {  // Uncovered. Report immediately.
+            onSensorEvent(event);
         }
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java
index 06b0bb2..ce65733 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java
@@ -19,7 +19,6 @@
 import static org.junit.Assert.assertEquals;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.times;
@@ -29,6 +28,7 @@
 import android.content.res.Resources;
 import android.testing.AndroidTestingRunner;
 import android.view.View;
+import android.view.ViewGroup;
 import android.widget.FrameLayout;
 import android.widget.RelativeLayout;
 
@@ -104,6 +104,8 @@
     private AnimatableClockView mLargeClockView;
     @Mock
     private FrameLayout mLargeClockFrame;
+    @Mock
+    private ViewGroup mSmartspaceContainer;
 
     private final View mFakeSmartspaceView = new View(mContext);
 
@@ -123,6 +125,8 @@
         when(mView.findViewById(R.id.animatable_clock_view)).thenReturn(mClockView);
         when(mView.findViewById(R.id.animatable_clock_view_large)).thenReturn(mLargeClockView);
         when(mView.findViewById(R.id.lockscreen_clock_view_large)).thenReturn(mLargeClockFrame);
+        when(mView.findViewById(R.id.keyguard_smartspace_container))
+                .thenReturn(mSmartspaceContainer);
         when(mClockView.getContext()).thenReturn(getContext());
         when(mLargeClockView.getContext()).thenReturn(getContext());
 
@@ -210,7 +214,7 @@
 
     @Test
     public void testSmartspaceEnabledRemovesKeyguardStatusArea() {
-        when(mSmartspaceController.isEnabled()).thenReturn(true);
+        when(mSmartspaceController.isSmartspaceEnabled()).thenReturn(true);
         when(mSmartspaceController.buildAndConnectView(any())).thenReturn(mFakeSmartspaceView);
         mController.init();
 
@@ -219,7 +223,7 @@
 
     @Test
     public void testSmartspaceDisabledShowsKeyguardStatusArea() {
-        when(mSmartspaceController.isEnabled()).thenReturn(false);
+        when(mSmartspaceController.isSmartspaceEnabled()).thenReturn(false);
         mController.init();
 
         assertEquals(View.VISIBLE, mStatusArea.getVisibility());
@@ -227,17 +231,16 @@
 
     @Test
     public void testDetachRemovesSmartspaceView() {
-        when(mSmartspaceController.isEnabled()).thenReturn(true);
+        when(mSmartspaceController.isSmartspaceEnabled()).thenReturn(true);
         when(mSmartspaceController.buildAndConnectView(any())).thenReturn(mFakeSmartspaceView);
         mController.init();
-        verify(mView).addView(eq(mFakeSmartspaceView), anyInt(), any());
 
         ArgumentCaptor<View.OnAttachStateChangeListener> listenerArgumentCaptor =
                 ArgumentCaptor.forClass(View.OnAttachStateChangeListener.class);
         verify(mView).addOnAttachStateChangeListener(listenerArgumentCaptor.capture());
 
         listenerArgumentCaptor.getValue().onViewDetachedFromWindow(mView);
-        verify(mView).removeView(mFakeSmartspaceView);
+        verify(mSmartspaceContainer).removeAllViews();
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt
index bb71bed8..8e1e42a 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt
@@ -26,6 +26,7 @@
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.classifier.FalsingCollector
 import com.android.systemui.classifier.FalsingCollectorFake
+import com.android.systemui.statusbar.policy.DevicePostureController
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -62,6 +63,8 @@
     private lateinit var mKeyguardMessageAreaController: KeyguardMessageAreaController
     @Mock
     private lateinit var mLockPatternView: LockPatternView
+    @Mock
+    private lateinit var mPostureController: DevicePostureController
 
     private lateinit var mKeyguardPatternViewController: KeyguardPatternViewController
 
@@ -78,7 +81,7 @@
         mKeyguardPatternViewController = KeyguardPatternViewController(mKeyguardPatternView,
         mKeyguardUpdateMonitor, mSecurityMode, mLockPatternUtils, mKeyguardSecurityCallback,
                 mLatencyTracker, mFalsingCollector, mEmergencyButtonController,
-                mKeyguardMessageAreaControllerFactory)
+                mKeyguardMessageAreaControllerFactory, mPostureController)
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagReaderTest.java b/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagReaderTest.java
index 223714c..7bc5f86 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagReaderTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagReaderTest.java
@@ -32,6 +32,7 @@
 
 import com.android.systemui.R;
 import com.android.systemui.SysuiTestCase;
+import com.android.systemui.shared.plugins.PluginManager;
 import com.android.systemui.util.wrapper.BuildInfo;
 
 import org.junit.Before;
@@ -43,6 +44,7 @@
 public class FeatureFlagReaderTest extends SysuiTestCase {
     @Mock private Resources mResources;
     @Mock private BuildInfo mBuildInfo;
+    @Mock private PluginManager mPluginManager;
     @Mock private SystemPropertiesHelper mSystemPropertiesHelper;
 
     private FeatureFlagReader mReader;
@@ -63,7 +65,8 @@
     private void initialize(boolean isDebuggable, boolean isOverrideable) {
         when(mBuildInfo.isDebuggable()).thenReturn(isDebuggable);
         when(mResources.getBoolean(R.bool.are_flags_overrideable)).thenReturn(isOverrideable);
-        mReader = new FeatureFlagReader(mResources, mBuildInfo, mSystemPropertiesHelper);
+        mReader = new FeatureFlagReader(
+                mResources, mBuildInfo, mPluginManager, mSystemPropertiesHelper);
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/FlagsTest.java b/packages/SystemUI/tests/src/com/android/systemui/flags/FlagsTest.java
new file mode 100644
index 0000000..25c3028
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/flags/FlagsTest.java
@@ -0,0 +1,119 @@
+/*
+ * 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.systemui.flags;
+
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import android.util.Pair;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.systemui.SysuiTestCase;
+
+import org.junit.Test;
+
+import java.lang.reflect.Field;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+@SmallTest
+public class FlagsTest extends SysuiTestCase {
+
+    @Test
+    public void testDuplicateFlagIdCheckWorks() {
+        List<Pair<String, Flag<?>>> flags = collectFlags(DuplicateFlagContainer.class);
+        Map<Integer, List<String>> duplicates = groupDuplicateFlags(flags);
+
+        assertWithMessage(generateAssertionMessage(duplicates))
+                .that(duplicates.size()).isEqualTo(2);
+    }
+
+    @Test
+    public void testNoDuplicateFlagIds() {
+        List<Pair<String, Flag<?>>> flags = collectFlags(Flags.class);
+        Map<Integer, List<String>> duplicates = groupDuplicateFlags(flags);
+
+        assertWithMessage(generateAssertionMessage(duplicates))
+                .that(duplicates.size()).isEqualTo(0);
+    }
+
+    private String generateAssertionMessage(Map<Integer, List<String>> duplicates) {
+        StringBuilder stringBuilder = new StringBuilder();
+        stringBuilder.append("Duplicate flag keys found: {");
+        for (int id : duplicates.keySet()) {
+            stringBuilder
+                    .append(" ")
+                    .append(id)
+                    .append(": [")
+                    .append(String.join(", ", duplicates.get(id)))
+                    .append("]");
+        }
+        stringBuilder.append(" }");
+
+        return stringBuilder.toString();
+    }
+
+    private List<Pair<String, Flag<?>>> collectFlags(Class<?> clz) {
+        List<Pair<String, Flag<?>>> flags = new ArrayList<>();
+
+        Field[] fields = clz.getFields();
+
+        for (Field field : fields) {
+            Class<?> t = field.getType();
+            if (Flag.class.isAssignableFrom(t)) {
+                try {
+                    flags.add(Pair.create(field.getName(), (Flag<?>) field.get(null)));
+                } catch (IllegalAccessException e) {
+                    // no-op
+                }
+            }
+        }
+
+        return flags;
+    }
+
+    private Map<Integer, List<String>> groupDuplicateFlags(List<Pair<String, Flag<?>>> flags) {
+        Map<Integer, List<String>> grouping = new HashMap<>();
+
+        for (Pair<String, Flag<?>> flag : flags) {
+            grouping.putIfAbsent(flag.second.getId(), new ArrayList<>());
+            grouping.get(flag.second.getId()).add(flag.first);
+        }
+
+        Map<Integer, List<String>> result = new HashMap<>();
+        for (Integer id : grouping.keySet()) {
+            if (grouping.get(id).size() > 1) {
+                result.put(id, grouping.get(id));
+            }
+        }
+
+        return result;
+    }
+
+    private static class DuplicateFlagContainer {
+        public static final BooleanFlag A_FLAG = new BooleanFlag(0);
+        public static final BooleanFlag B_FLAG = new BooleanFlag(0);
+        public static final StringFlag C_FLAG = new StringFlag(0);
+
+        public static final BooleanFlag D_FLAG = new BooleanFlag(1);
+
+        public static final DoubleFlag E_FLAG = new DoubleFlag(3);
+        public static final DoubleFlag F_FLAG = new DoubleFlag(3);
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QuickQSBrightnessControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/QuickQSBrightnessControllerTest.kt
index f8373ff..de1d86b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QuickQSBrightnessControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QuickQSBrightnessControllerTest.kt
@@ -19,13 +19,15 @@
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.settings.brightness.BrightnessController
+import com.android.systemui.statusbar.policy.BrightnessMirrorController
 import org.junit.Before
 import org.junit.Rule
 import org.junit.Test
 import org.mockito.Mock
-import org.mockito.Mockito.verify
 import org.mockito.Mockito.times
+import org.mockito.Mockito.verify
 import org.mockito.Mockito.never
+import org.mockito.Mockito.mock
 import org.mockito.junit.MockitoJUnit
 
 @SmallTest
@@ -104,4 +106,13 @@
 
         verify(brightnessController, never()).registerCallbacks()
     }
+
+    @Test
+    fun testMirrorIsSetWhenSliderIsShown() {
+        val mirrorController = mock(BrightnessMirrorController::class.java)
+        quickQSBrightnessController.setMirror(mirrorController)
+        quickQSBrightnessController.refreshVisibility(shouldUseSplitNotificationShade = true)
+
+        verify(brightnessController).setMirror(mirrorController)
+    }
 }
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/settings/brightness/BrightnessSliderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/settings/brightness/BrightnessSliderTest.kt
index e0187bd..bceb928 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/settings/brightness/BrightnessSliderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/settings/brightness/BrightnessSliderTest.kt
@@ -108,15 +108,6 @@
     }
 
     @Test
-    fun testNullMirrorControllerNotTrackingTouch() {
-        mController.setMirrorControllerAndMirror(null)
-
-        verify(brightnessSliderView, never()).max
-        verify(brightnessSliderView, never()).value
-        verify(brightnessSliderView).setOnDispatchTouchEventListener(isNull())
-    }
-
-    @Test
     fun testNullMirrorNotTrackingTouch() {
         whenever(mirrorController.toggleSlider).thenReturn(null)
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceControllerTest.kt
index a2bb0af..756e984 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceControllerTest.kt
@@ -389,7 +389,6 @@
         verify(userTracker).removeCallback(userListener)
         verify(contentResolver).unregisterContentObserver(settingsObserver)
         verify(configurationController).removeCallback(configChangeListener)
-        verify(statusBarStateController).removeCallback(statusBarStateListener)
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithmTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithmTest.java
index 690b841..2e76bd7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithmTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithmTest.java
@@ -38,35 +38,27 @@
     private static final float ZERO_DRAG = 0.f;
     private static final float OPAQUE = 1.f;
     private static final float TRANSPARENT = 0.f;
-    private static final boolean HAS_CUSTOM_CLOCK = false;
-    private static final boolean HAS_VISIBLE_NOTIFS = false;
 
     private KeyguardClockPositionAlgorithm mClockPositionAlgorithm;
     private KeyguardClockPositionAlgorithm.Result mClockPosition;
-    private int mNotificationStackHeight;
     private float mPanelExpansion;
     private int mKeyguardStatusHeight;
     private float mDark;
-    private boolean mHasCustomClock;
-    private boolean mHasVisibleNotifs;
     private float mQsExpansion;
-    private int mCutoutTopInset = 0; // in pixels
+    private int mCutoutTopInsetPx = 0;
+    private int mSplitShadeSmartspaceHeightPx = 0;
     private boolean mIsSplitShade = false;
 
     @Before
     public void setUp() {
         mClockPositionAlgorithm = new KeyguardClockPositionAlgorithm();
         mClockPosition = new KeyguardClockPositionAlgorithm.Result();
-
-        mHasCustomClock = HAS_CUSTOM_CLOCK;
-        mHasVisibleNotifs = HAS_VISIBLE_NOTIFS;
     }
 
     @Test
     public void clockPositionTopOfScreenOnAOD() {
-        // GIVEN on AOD and both stack scroll and clock have 0 height
+        // GIVEN on AOD and clock has 0 height
         givenAOD();
-        mNotificationStackHeight = EMPTY_HEIGHT;
         mKeyguardStatusHeight = EMPTY_HEIGHT;
         // WHEN the clock position algorithm is run
         positionClock();
@@ -79,11 +71,10 @@
 
     @Test
     public void clockPositionBelowCutout() {
-        // GIVEN on AOD and both stack scroll and clock have 0 height
+        // GIVEN on AOD and clock has 0 height
         givenAOD();
-        mNotificationStackHeight = EMPTY_HEIGHT;
         mKeyguardStatusHeight = EMPTY_HEIGHT;
-        mCutoutTopInset = 300;
+        mCutoutTopInsetPx = 300;
         // WHEN the clock position algorithm is run
         positionClock();
         // THEN the clock Y position is below the cutout
@@ -97,7 +88,6 @@
     public void clockPositionAdjustsForKeyguardStatusOnAOD() {
         // GIVEN on AOD with a clock of height 100
         givenAOD();
-        mNotificationStackHeight = EMPTY_HEIGHT;
         mKeyguardStatusHeight = 100;
         // WHEN the clock position algorithm is run
         positionClock();
@@ -112,7 +102,6 @@
     public void clockPositionLargeClockOnAOD() {
         // GIVEN on AOD with a full screen clock
         givenAOD();
-        mNotificationStackHeight = EMPTY_HEIGHT;
         mKeyguardStatusHeight = SCREEN_HEIGHT;
         // WHEN the clock position algorithm is run
         positionClock();
@@ -125,9 +114,8 @@
 
     @Test
     public void clockPositionTopOfScreenOnLockScreen() {
-        // GIVEN on lock screen with stack scroll and clock of 0 height
+        // GIVEN on lock screen with clock of 0 height
         givenLockScreen();
-        mNotificationStackHeight = EMPTY_HEIGHT;
         mKeyguardStatusHeight = EMPTY_HEIGHT;
         // WHEN the clock position algorithm is run
         positionClock();
@@ -138,24 +126,9 @@
     }
 
     @Test
-    public void clockPositionWithStackScrollExpandOnLockScreen() {
-        // GIVEN on lock screen with stack scroll of height 500
-        givenLockScreen();
-        mNotificationStackHeight = 500;
-        mKeyguardStatusHeight = EMPTY_HEIGHT;
-        // WHEN the clock position algorithm is run
-        positionClock();
-        // THEN the clock Y position stays to the top
-        assertThat(mClockPosition.clockY).isEqualTo(0);
-        // AND the clock is positioned on the left.
-        assertThat(mClockPosition.clockX).isEqualTo(0);
-    }
-
-    @Test
     public void clockPositionWithPartialDragOnLockScreen() {
         // GIVEN dragging up on lock screen
         givenLockScreen();
-        mNotificationStackHeight = EMPTY_HEIGHT;
         mKeyguardStatusHeight = EMPTY_HEIGHT;
         mPanelExpansion = 0.5f;
         // WHEN the clock position algorithm is run
@@ -171,7 +144,6 @@
     public void clockPositionWithFullDragOnLockScreen() {
         // GIVEN the lock screen is dragged up
         givenLockScreen();
-        mNotificationStackHeight = EMPTY_HEIGHT;
         mKeyguardStatusHeight = EMPTY_HEIGHT;
         mPanelExpansion = 0.f;
         // WHEN the clock position algorithm is run
@@ -184,7 +156,6 @@
     public void largeClockOnLockScreenIsTransparent() {
         // GIVEN on lock screen with a full screen clock
         givenLockScreen();
-        mNotificationStackHeight = EMPTY_HEIGHT;
         mKeyguardStatusHeight = SCREEN_HEIGHT;
         // WHEN the clock position algorithm is run
         positionClock();
@@ -194,9 +165,8 @@
 
     @Test
     public void notifPositionTopOfScreenOnAOD() {
-        // GIVEN on AOD and both stack scroll and clock have 0 height
+        // GIVEN on AOD and clock has 0 height
         givenAOD();
-        mNotificationStackHeight = EMPTY_HEIGHT;
         mKeyguardStatusHeight = EMPTY_HEIGHT;
         // WHEN the position algorithm is run
         positionClock();
@@ -208,7 +178,6 @@
     public void notifPositionIndependentOfKeyguardStatusHeightOnAOD() {
         // GIVEN on AOD and clock has a nonzero height
         givenAOD();
-        mNotificationStackHeight = EMPTY_HEIGHT;
         mKeyguardStatusHeight = 100;
         // WHEN the position algorithm is run
         positionClock();
@@ -220,7 +189,6 @@
     public void notifPositionWithLargeClockOnAOD() {
         // GIVEN on AOD and clock has a nonzero height
         givenAOD();
-        mNotificationStackHeight = EMPTY_HEIGHT;
         mKeyguardStatusHeight = SCREEN_HEIGHT;
         // WHEN the position algorithm is run
         positionClock();
@@ -230,9 +198,8 @@
 
     @Test
     public void notifPositionMiddleOfScreenOnLockScreen() {
-        // GIVEN on lock screen and both stack scroll and clock have 0 height
+        // GIVEN on lock screen and clock has 0 height
         givenLockScreen();
-        mNotificationStackHeight = EMPTY_HEIGHT;
         mKeyguardStatusHeight = EMPTY_HEIGHT;
         // WHEN the position algorithm is run
         positionClock();
@@ -241,47 +208,21 @@
     }
 
     @Test
-    public void notifPositionAdjustsForStackHeightOnLockScreen() {
-        // GIVEN on lock screen and stack scroller has a nonzero height
-        givenLockScreen();
-        mNotificationStackHeight = 500;
-        mKeyguardStatusHeight = EMPTY_HEIGHT;
-        // WHEN the position algorithm is run
-        positionClock();
-        // THEN the notif padding adjusts for keyguard status height
-        assertThat(mClockPosition.stackScrollerPadding).isEqualTo(0);
-    }
-
-    @Test
     public void notifPositionAdjustsForClockHeightOnLockScreen() {
         // GIVEN on lock screen and stack scroller has a nonzero height
         givenLockScreen();
-        mNotificationStackHeight = EMPTY_HEIGHT;
         mKeyguardStatusHeight = 200;
         // WHEN the position algorithm is run
         positionClock();
-        // THEN the notif padding adjusts for both clock and notif stack.
-        assertThat(mClockPosition.stackScrollerPadding).isEqualTo(200);
-    }
 
-    @Test
-    public void notifPositionAdjustsForStackHeightAndClockHeightOnLockScreen() {
-        // GIVEN on lock screen and stack scroller has a nonzero height
-        givenLockScreen();
-        mNotificationStackHeight = 500;
-        mKeyguardStatusHeight = 200;
-        // WHEN the position algorithm is run
-        positionClock();
-        // THEN the notifs are placed below the statusview
         assertThat(mClockPosition.stackScrollerPadding).isEqualTo(200);
     }
 
     @Test
     public void notifPositionAlignedWithClockInSplitShadeMode() {
-        // GIVEN on lock screen and split shade mode
         givenLockScreen();
         mIsSplitShade = true;
-        mHasCustomClock = true;
+        mKeyguardStatusHeight = 200;
         // WHEN the position algorithm is run
         positionClock();
         // THEN the notif padding DOESN'T adjust for keyguard status height.
@@ -289,10 +230,20 @@
     }
 
     @Test
+    public void notifPositionAdjustedBySmartspaceHeightInSplitShadeMode() {
+        givenLockScreen();
+        mSplitShadeSmartspaceHeightPx = 200;
+        mIsSplitShade = true;
+        // WHEN the position algorithm is run
+        positionClock();
+
+        assertThat(mClockPosition.stackScrollerPadding).isEqualTo(200);
+    }
+
+    @Test
     public void notifPositionWithLargeClockOnLockScreen() {
         // GIVEN on lock screen and clock has a nonzero height
         givenLockScreen();
-        mNotificationStackHeight = EMPTY_HEIGHT;
         mKeyguardStatusHeight = SCREEN_HEIGHT;
         // WHEN the position algorithm is run
         positionClock();
@@ -304,7 +255,6 @@
     public void notifPositionWithFullDragOnLockScreen() {
         // GIVEN the lock screen is dragged up
         givenLockScreen();
-        mNotificationStackHeight = EMPTY_HEIGHT;
         mKeyguardStatusHeight = EMPTY_HEIGHT;
         mPanelExpansion = 0.f;
         // WHEN the clock position algorithm is run
@@ -317,19 +267,18 @@
     public void notifPositionWithLargeClockFullDragOnLockScreen() {
         // GIVEN the lock screen is dragged up and a full screen clock
         givenLockScreen();
-        mNotificationStackHeight = EMPTY_HEIGHT;
         mKeyguardStatusHeight = SCREEN_HEIGHT;
         mPanelExpansion = 0.f;
         // WHEN the clock position algorithm is run
         positionClock();
-        // THEN the notif padding is zero.
+
         assertThat(mClockPosition.stackScrollerPadding).isEqualTo(
                 (int) (mKeyguardStatusHeight * .667f));
     }
 
     @Test
     public void clockHiddenWhenQsIsExpanded() {
-        // GIVEN on the lock screen with a custom clock and visible notifications
+        // GIVEN on the lock screen with visible notifications
         givenLockScreen();
         mQsExpansion = 1;
         // WHEN the clock position algorithm is run
@@ -349,12 +298,12 @@
     }
 
     private void positionClock() {
-        mClockPositionAlgorithm.setup(EMPTY_MARGIN, SCREEN_HEIGHT, mNotificationStackHeight,
-                mPanelExpansion, SCREEN_HEIGHT, mKeyguardStatusHeight,
+        mClockPositionAlgorithm.setup(EMPTY_MARGIN, SCREEN_HEIGHT,
+                mPanelExpansion, mKeyguardStatusHeight,
                 0 /* userSwitchHeight */, 0 /* userSwitchPreferredY */,
-                mHasCustomClock, mHasVisibleNotifs, mDark, ZERO_DRAG, false /* bypassEnabled */,
+                mDark, ZERO_DRAG, false /* bypassEnabled */,
                 0 /* unlockedStackScrollerPadding */, mQsExpansion,
-                mCutoutTopInset, mIsSplitShade);
+                mCutoutTopInsetPx, mSplitShadeSmartspaceHeightPx, mIsSplitShade);
         mClockPositionAlgorithm.run(mClockPosition);
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewTest.java
index cbaca3a..1387b8e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewTest.java
@@ -63,6 +63,7 @@
 import android.view.ViewStub;
 import android.view.accessibility.AccessibilityManager;
 import android.view.accessibility.AccessibilityNodeInfo;
+import android.widget.FrameLayout;
 
 import androidx.constraintlayout.widget.ConstraintLayout;
 import androidx.constraintlayout.widget.ConstraintSet;
@@ -112,6 +113,7 @@
 import com.android.systemui.statusbar.SysuiStatusBarStateController;
 import com.android.systemui.statusbar.VibratorHelper;
 import com.android.systemui.statusbar.events.PrivacyDotViewController;
+import com.android.systemui.statusbar.lockscreen.LockscreenSmartspaceController;
 import com.android.systemui.statusbar.notification.ConversationNotificationManager;
 import com.android.systemui.statusbar.notification.DynamicPrivacyController;
 import com.android.systemui.statusbar.notification.NotificationEntryManager;
@@ -300,6 +302,10 @@
     private RecordingController mRecordingController;
     @Mock
     private ControlsComponent mControlsComponent;
+    @Mock
+    private LockscreenSmartspaceController mLockscreenSmartspaceController;
+    @Mock
+    private FrameLayout mSplitShadeSmartspaceContainer;
 
     private SysuiStatusBarStateController mStatusBarStateController;
     private NotificationPanelViewController mNotificationPanelViewController;
@@ -349,6 +355,8 @@
         when(mView.findViewById(R.id.qs_frame)).thenReturn(mQsFrame);
         when(mView.findViewById(R.id.keyguard_status_view))
                 .thenReturn(mock(KeyguardStatusView.class));
+        when(mView.findViewById(R.id.split_shade_smartspace_container))
+                .thenReturn(mSplitShadeSmartspaceContainer);
         mNotificationContainerParent = new NotificationsQuickSettingsContainer(getContext(), null);
         mNotificationContainerParent.addView(newViewWithId(R.id.qs_frame));
         mNotificationContainerParent.addView(newViewWithId(R.id.notification_stack_scroller));
@@ -442,6 +450,7 @@
                 new FakeExecutor(new FakeSystemClock()),
                 mSecureSettings,
                 mSplitShadeHeaderController,
+                mLockscreenSmartspaceController,
                 mUnlockedScreenOffAnimationController,
                 mNotificationRemoteInputManager,
                 mControlsComponent);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt
new file mode 100644
index 0000000..50cea07
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt
@@ -0,0 +1,60 @@
+/*
+ * 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.systemui.statusbar.phone
+
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.statusbar.CommandQueue
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.mockito.Mock
+import org.mockito.Mockito.`when`
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+class PhoneStatusBarViewControllerTest : SysuiTestCase() {
+
+    @Mock
+    private lateinit var commandQueue: CommandQueue
+
+    private lateinit var view: PhoneStatusBarView
+    private lateinit var controller: PhoneStatusBarViewController
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+
+        view = PhoneStatusBarView(mContext, null)
+        controller = PhoneStatusBarViewController(view, commandQueue)
+    }
+
+    @Test
+    fun constructor_setsPanelEnabledProviderOnView() {
+        var providerUsed = false
+        `when`(commandQueue.panelsEnabled()).then {
+            providerUsed = true
+            true
+        }
+
+        // If the constructor correctly set a [PanelEnabledProvider], then it should be used
+        // when [PhoneStatusBarView.panelEnabled] is called.
+        view.panelEnabled()
+
+        assertThat(providerUsed).isTrue()
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewTest.kt
new file mode 100644
index 0000000..49ab6eb
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewTest.kt
@@ -0,0 +1,54 @@
+/*
+ * 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.systemui.statusbar.phone
+
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+
+@SmallTest
+class PhoneStatusBarViewTest : SysuiTestCase() {
+
+    private lateinit var view: PhoneStatusBarView
+
+    @Before
+    fun setUp() {
+        view = PhoneStatusBarView(mContext, null)
+    }
+
+    @Test
+    fun panelEnabled_providerReturnsTrue_returnsTrue() {
+        view.setPanelEnabledProvider { true }
+
+        assertThat(view.panelEnabled()).isTrue()
+    }
+
+    @Test
+    fun panelEnabled_providerReturnsFalse_returnsFalse() {
+        view.setPanelEnabledProvider { false }
+
+        assertThat(view.panelEnabled()).isFalse()
+    }
+
+    @Test
+    fun panelEnabled_noProvider_noCrash() {
+        view.panelEnabled()
+        // No assert needed, just testing no crash
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/sensors/ProximitySensorDualTest.java b/packages/SystemUI/tests/src/com/android/systemui/util/sensors/ProximitySensorDualTest.java
index cc2afe2..a34c598 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/util/sensors/ProximitySensorDualTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/sensors/ProximitySensorDualTest.java
@@ -18,6 +18,7 @@
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 
@@ -60,6 +61,68 @@
     }
 
     @Test
+    public void testInitiallyAbovePrimary() {
+
+        TestableListener listener = new TestableListener();
+
+        mProximitySensor.register(listener);
+        assertTrue(mProximitySensor.isRegistered());
+        assertFalse(mThresholdSensorPrimary.isPaused());
+        assertTrue(mThresholdSensorSecondary.isPaused());
+        assertNull(listener.mLastEvent);
+        assertEquals(0, listener.mCallCount);
+
+        mThresholdSensorPrimary.triggerEvent(false, 0);
+        assertNotNull(listener.mLastEvent);
+        assertFalse(listener.mLastEvent.getBelow());
+        assertEquals(1, listener.mCallCount);
+    }
+
+    @Test
+    public void testInitiallyBelowPrimaryAboveSecondary() {
+
+        TestableListener listener = new TestableListener();
+
+        mProximitySensor.register(listener);
+        assertTrue(mProximitySensor.isRegistered());
+        assertFalse(mThresholdSensorPrimary.isPaused());
+        assertTrue(mThresholdSensorSecondary.isPaused());
+        assertNull(listener.mLastEvent);
+        assertEquals(0, listener.mCallCount);
+
+        mThresholdSensorPrimary.triggerEvent(true, 0);
+        assertNull(listener.mLastEvent);
+        assertEquals(0, listener.mCallCount);
+
+        mThresholdSensorSecondary.triggerEvent(false, 1);
+        assertNotNull(listener.mLastEvent);
+        assertFalse(listener.mLastEvent.getBelow());
+        assertEquals(1, listener.mCallCount);
+    }
+
+    @Test
+    public void testInitiallyBelowPrimaryAndSecondary() {
+
+        TestableListener listener = new TestableListener();
+
+        mProximitySensor.register(listener);
+        assertTrue(mProximitySensor.isRegistered());
+        assertFalse(mThresholdSensorPrimary.isPaused());
+        assertTrue(mThresholdSensorSecondary.isPaused());
+        assertNull(listener.mLastEvent);
+        assertEquals(0, listener.mCallCount);
+
+        mThresholdSensorPrimary.triggerEvent(true, 0);
+        assertNull(listener.mLastEvent);
+        assertEquals(0, listener.mCallCount);
+
+        mThresholdSensorSecondary.triggerEvent(true, 1);
+        assertNotNull(listener.mLastEvent);
+        assertTrue(listener.mLastEvent.getBelow());
+        assertEquals(1, listener.mCallCount);
+    }
+
+    @Test
     public void testPrimaryBelowDoesNotInvokeSecondary() {
         TestableListener listener = new TestableListener();
 
@@ -74,8 +137,6 @@
         mThresholdSensorPrimary.triggerEvent(false, 0);
         assertFalse(mThresholdSensorPrimary.isPaused());
         assertTrue(mThresholdSensorSecondary.isPaused());
-        assertNull(listener.mLastEvent);
-        assertEquals(0, listener.mCallCount);
     }
 
     @Test
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index 07847f1..45e6f1ec 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -102,6 +102,7 @@
 import android.appwidget.AppWidgetManagerInternal;
 import android.compat.annotation.ChangeId;
 import android.compat.annotation.EnabledSince;
+import android.compat.annotation.Overridable;
 import android.content.ComponentName;
 import android.content.ComponentName.WithComponentName;
 import android.content.Context;
@@ -307,6 +308,7 @@
      */
     @ChangeId
     @EnabledSince(targetSdkVersion = android.os.Build.VERSION_CODES.S)
+    @Overridable
     static final long FGS_BG_START_RESTRICTION_CHANGE_ID = 170668199L;
 
     /**
diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java
index 6d7966f..64b9bd9 100644
--- a/services/core/java/com/android/server/appop/AppOpsService.java
+++ b/services/core/java/com/android/server/appop/AppOpsService.java
@@ -45,6 +45,9 @@
 import static android.app.AppOpsManager.OP_PLAY_AUDIO;
 import static android.app.AppOpsManager.OP_RECORD_AUDIO;
 import static android.app.AppOpsManager.OP_RECORD_AUDIO_HOTWORD;
+import static android.app.AppOpsManager.OnOpStartedListener.START_TYPE_FAILED;
+import static android.app.AppOpsManager.OnOpStartedListener.START_TYPE_RESUMED;
+import static android.app.AppOpsManager.OnOpStartedListener.START_TYPE_STARTED;
 import static android.app.AppOpsManager.OpEventProxyInfo;
 import static android.app.AppOpsManager.RestrictionBypass;
 import static android.app.AppOpsManager.SAMPLING_STRATEGY_BOOT_TIME_SAMPLING;
@@ -1238,6 +1241,11 @@
                     scheduleOpActiveChangedIfNeededLocked(parent.op, parent.uid, parent.packageName,
                             tag, true, event.getAttributionFlags(), event.getAttributionChainId());
                 }
+                // Note: this always sends MODE_ALLOWED, even if the mode is FOREGROUND
+                // TODO ntmyren: figure out how to get the real mode.
+                scheduleOpStartedIfNeededLocked(parent.op, parent.uid, parent.packageName,
+                        tag, event.getFlags(), MODE_ALLOWED, START_TYPE_RESUMED,
+                        event.getAttributionFlags(), event.getAttributionChainId());
             }
             mPausedInProgressEvents = null;
         }
@@ -3945,13 +3953,15 @@
         }
 
         boolean isRestricted = false;
+        int startType = START_TYPE_FAILED;
         synchronized (this) {
             final Ops ops = getOpsLocked(uid, packageName, attributionTag,
                     pvr.isAttributionTagValid, pvr.bypass, /* edit */ true);
             if (ops == null) {
                 if (!dryRun) {
                     scheduleOpStartedIfNeededLocked(code, uid, packageName, attributionTag,
-                            flags, AppOpsManager.MODE_IGNORED);
+                            flags, AppOpsManager.MODE_IGNORED, startType, attributionFlags,
+                            attributionChainId);
                 }
                 if (DEBUG) Slog.d(TAG, "startOperation: no op for code " + code + " uid " + uid
                         + " package " + packageName + " flags: "
@@ -3977,7 +3987,7 @@
                     if (!dryRun) {
                         attributedOp.rejected(uidState.state, flags);
                         scheduleOpStartedIfNeededLocked(code, uid, packageName, attributionTag,
-                                flags, uidMode);
+                                flags, uidMode, startType, attributionFlags, attributionChainId);
                     }
                     return new SyncNotedAppOp(uidMode, code, attributionTag, packageName);
                 }
@@ -3993,7 +4003,7 @@
                     if (!dryRun) {
                         attributedOp.rejected(uidState.state, flags);
                         scheduleOpStartedIfNeededLocked(code, uid, packageName, attributionTag,
-                                flags, mode);
+                                flags, mode, startType, attributionFlags, attributionChainId);
                     }
                     return new SyncNotedAppOp(mode, code, attributionTag, packageName);
                 }
@@ -4011,12 +4021,14 @@
                         attributedOp.started(clientId, proxyUid, proxyPackageName,
                                 proxyAttributionTag, uidState.state, flags, attributionFlags,
                                 attributionChainId);
+                        startType = START_TYPE_STARTED;
                     }
                 } catch (RemoteException e) {
                     throw new RuntimeException(e);
                 }
                 scheduleOpStartedIfNeededLocked(code, uid, packageName, attributionTag, flags,
-                        isRestricted ? MODE_IGNORED : MODE_ALLOWED);
+                        isRestricted ? MODE_IGNORED : MODE_ALLOWED, startType, attributionFlags,
+                        attributionChainId);
             }
         }
 
@@ -4187,7 +4199,9 @@
     }
 
     private void scheduleOpStartedIfNeededLocked(int code, int uid, String pkgName,
-            String attributionTag, @OpFlags int flags, @Mode int result) {
+            String attributionTag, @OpFlags int flags, @Mode int result,
+            @AppOpsManager.OnOpStartedListener.StartedType int startedType,
+            @AttributionFlags int attributionFlags, int attributionChainId) {
         ArraySet<StartedCallback> dispatchedCallbacks = null;
         final int callbackListCount = mStartedWatchers.size();
         for (int i = 0; i < callbackListCount; i++) {
@@ -4213,12 +4227,13 @@
         mHandler.sendMessage(PooledLambda.obtainMessage(
                 AppOpsService::notifyOpStarted,
                 this, dispatchedCallbacks, code, uid, pkgName, attributionTag, flags,
-                result));
+                result, startedType, attributionFlags, attributionChainId));
     }
 
     private void notifyOpStarted(ArraySet<StartedCallback> callbacks,
             int code, int uid, String packageName, String attributionTag, @OpFlags int flags,
-            @Mode int result) {
+            @Mode int result, @AppOpsManager.OnOpStartedListener.StartedType int startedType,
+            @AttributionFlags int attributionFlags, int attributionChainId) {
         final long identity = Binder.clearCallingIdentity();
         try {
             final int callbackCount = callbacks.size();
@@ -4226,7 +4241,7 @@
                 final StartedCallback callback = callbacks.valueAt(i);
                 try {
                     callback.mCallback.opStarted(code, uid, packageName, attributionTag, flags,
-                            result);
+                            result, startedType, attributionFlags, attributionChainId);
                 } catch (RemoteException e) {
                     /* do nothing */
                 }
diff --git a/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java
index f4327e8..6f38ed0 100644
--- a/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java
@@ -82,7 +82,7 @@
 
     private long mStartTimeMs;
 
-    protected boolean mAuthAttempted;
+    private boolean mAuthAttempted;
 
     // TODO: This is currently hard to maintain, as each AuthenticationClient subclass must update
     //  the state. We should think of a way to improve this in the future.
@@ -98,6 +98,12 @@
      */
     protected abstract void handleLifecycleAfterAuth(boolean authenticated);
 
+    /**
+     * @return true if a user was detected (i.e. face was found, fingerprint sensor was touched.
+     *         etc)
+     */
+    public abstract boolean wasUserDetected();
+
     public AuthenticationClient(@NonNull Context context, @NonNull LazyDaemon<T> lazyDaemon,
             @NonNull IBinder token, @NonNull ClientMonitorCallbackConverter listener,
             int targetUserId, long operationId, boolean restricted, @NonNull String owner,
@@ -180,7 +186,8 @@
                 + ", isBP: " + isBiometricPrompt()
                 + ", listener: " + listener
                 + ", requireConfirmation: " + mRequireConfirmation
-                + ", user: " + getTargetUserId());
+                + ", user: " + getTargetUserId()
+                + ", clientMonitor: " + toString());
 
         final PerformanceTracker pm = PerformanceTracker.getInstanceForSensorId(getSensorId());
         if (isCryptoOperation()) {
@@ -304,6 +311,11 @@
                 public void handleLifecycleAfterAuth() {
                     AuthenticationClient.this.handleLifecycleAfterAuth(true /* authenticated */);
                 }
+
+                @Override
+                public void sendAuthenticationCanceled() {
+                    sendCancelOnly(listener);
+                }
             });
         } else {
             // Allow system-defined limit of number of attempts before giving up
@@ -338,10 +350,30 @@
                 public void handleLifecycleAfterAuth() {
                     AuthenticationClient.this.handleLifecycleAfterAuth(false /* authenticated */);
                 }
+
+                @Override
+                public void sendAuthenticationCanceled() {
+                    sendCancelOnly(listener);
+                }
             });
         }
     }
 
+    private void sendCancelOnly(@Nullable ClientMonitorCallbackConverter listener) {
+        if (listener == null) {
+            Slog.e(TAG, "Unable to sendAuthenticationCanceled, listener null");
+            return;
+        }
+        try {
+            listener.onError(getSensorId(),
+                    getCookie(),
+                    BiometricConstants.BIOMETRIC_ERROR_CANCELED,
+                    0 /* vendorCode */);
+        } catch (RemoteException e) {
+            Slog.e(TAG, "Remote exception", e);
+        }
+    }
+
     @Override
     public void onAcquired(int acquiredInfo, int vendorCode) {
         super.onAcquired(acquiredInfo, vendorCode);
@@ -355,9 +387,11 @@
     }
 
     @Override
-    public void onError(int errorCode, int vendorCode) {
+    public void onError(@BiometricConstants.Errors int errorCode, int vendorCode) {
         super.onError(errorCode, vendorCode);
         mState = STATE_STOPPED;
+
+        CoexCoordinator.getInstance().onAuthenticationError(this, errorCode, this::vibrateError);
     }
 
     /**
@@ -419,4 +453,8 @@
     public boolean interruptsPrecedingClients() {
         return true;
     }
+
+    public boolean wasAuthAttempted() {
+        return mAuthAttempted;
+    }
 }
diff --git a/services/core/java/com/android/server/biometrics/sensors/CoexCoordinator.java b/services/core/java/com/android/server/biometrics/sensors/CoexCoordinator.java
index a15ecad..25d4a38 100644
--- a/services/core/java/com/android/server/biometrics/sensors/CoexCoordinator.java
+++ b/services/core/java/com/android/server/biometrics/sensors/CoexCoordinator.java
@@ -22,6 +22,7 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.hardware.biometrics.BiometricConstants;
 import android.os.Handler;
 import android.os.Looper;
 import android.util.Slog;
@@ -54,7 +55,7 @@
 
     /**
      * Callback interface notifying the owner of "results" from the CoexCoordinator's business
-     * logic.
+     * logic for accept and reject.
      */
     interface Callback {
         /**
@@ -73,6 +74,22 @@
          * from scheduler if auth was successful).
          */
         void handleLifecycleAfterAuth();
+
+        /**
+         * Requests the owner to notify the caller that authentication was canceled.
+         */
+        void sendAuthenticationCanceled();
+    }
+
+    /**
+     * Callback interface notifying the owner of "results" from the CoexCoordinator's business
+     * logic for errors.
+     */
+    interface ErrorCallback {
+        /**
+         * Requests the owner to initiate a vibration for this event.
+         */
+        void sendHapticFeedback();
     }
 
     private static CoexCoordinator sInstance;
@@ -198,6 +215,9 @@
         mClientMap.remove(sensorType);
     }
 
+    /**
+     * Notify the coordinator that authentication succeeded (accepted)
+     */
     public void onAuthenticationSucceeded(long currentTimeMillis,
             @NonNull AuthenticationClient<?> client,
             @NonNull Callback callback) {
@@ -268,6 +288,9 @@
         }
     }
 
+    /**
+     * Notify the coordinator that a rejection has occurred.
+     */
     public void onAuthenticationRejected(long currentTimeMillis,
             @NonNull AuthenticationClient<?> client,
             @LockoutTracker.LockoutMode int lockoutMode,
@@ -352,11 +375,63 @@
         }
     }
 
+    /**
+     * Notify the coordinator that an error has occurred.
+     */
+    public void onAuthenticationError(@NonNull AuthenticationClient<?> client,
+            @BiometricConstants.Errors int error, @NonNull ErrorCallback callback) {
+        // Figure out non-coex state
+        final boolean shouldUsuallyVibrate;
+        if (isCurrentFaceAuth(client)) {
+            final boolean notDetectedOnKeyguard = client.isKeyguard() && !client.wasUserDetected();
+            final boolean authAttempted = client.wasAuthAttempted();
+
+            switch (error) {
+                case BiometricConstants.BIOMETRIC_ERROR_TIMEOUT:
+                case BiometricConstants.BIOMETRIC_ERROR_LOCKOUT:
+                case BiometricConstants.BIOMETRIC_ERROR_LOCKOUT_PERMANENT:
+                    shouldUsuallyVibrate = authAttempted && !notDetectedOnKeyguard;
+                    break;
+                default:
+                    shouldUsuallyVibrate = false;
+                    break;
+            }
+        } else {
+            shouldUsuallyVibrate = false;
+        }
+
+        // Figure out coex state
+        final boolean keyguardAdvancedLogic = mAdvancedLogicEnabled && client.isKeyguard();
+        final boolean hapticSuppressedByCoex;
+
+        if (keyguardAdvancedLogic) {
+            if (isSingleAuthOnly(client)) {
+                hapticSuppressedByCoex = false;
+            } else {
+                hapticSuppressedByCoex = isCurrentFaceAuth(client)
+                        && !client.isKeyguardBypassEnabled();
+            }
+        } else {
+            hapticSuppressedByCoex = false;
+        }
+
+        // Combine and send feedback if appropriate
+        Slog.d(TAG, "shouldUsuallyVibrate: " + shouldUsuallyVibrate
+                + ", hapticSuppressedByCoex: " + hapticSuppressedByCoex);
+        if (shouldUsuallyVibrate && !hapticSuppressedByCoex) {
+            callback.sendHapticFeedback();
+        }
+    }
+
     @Nullable
     private SuccessfulAuth popSuccessfulFaceAuthIfExists(long currentTimeMillis) {
         for (SuccessfulAuth auth : mSuccessfulAuths) {
             if (currentTimeMillis - auth.mAuthTimestamp >= SUCCESSFUL_AUTH_VALID_DURATION_MS) {
-                Slog.d(TAG, "Removing stale auth: " + auth);
+                // TODO(b/193089985): This removes the auth but does not notify the client with
+                //  an appropriate lifecycle event (such as ERROR_CANCELED), and violates the
+                //  API contract. However, this might be OK for now since the validity duration
+                //  is way longer than the time it takes to auth with fingerprint.
+                Slog.e(TAG, "Removing stale auth: " + auth);
                 mSuccessfulAuths.remove(auth);
             } else if (auth.mSensorType == SENSOR_TYPE_FACE) {
                 mSuccessfulAuths.remove(auth);
@@ -367,9 +442,13 @@
     }
 
     private void removeAndFinishAllFaceFromQueue() {
+        // Note that these auth are all successful, but have never notified the client (e.g.
+        // keyguard). To comply with the authentication lifecycle, we must notify the client that
+        // auth is "done". The safest thing to do is to send ERROR_CANCELED.
         for (SuccessfulAuth auth : mSuccessfulAuths) {
             if (auth.mSensorType == SENSOR_TYPE_FACE) {
-                Slog.d(TAG, "Removing from queue and finishing: " + auth);
+                Slog.d(TAG, "Removing from queue, canceling, and finishing: " + auth);
+                auth.mCallback.sendAuthenticationCanceled();
                 auth.mCallback.handleLifecycleAfterAuth();
                 mSuccessfulAuths.remove(auth);
             }
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java
index f7fd8d0..d66a279 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java
@@ -127,7 +127,8 @@
         }
     }
 
-    private boolean wasUserDetected() {
+    @Override
+    public boolean wasUserDetected() {
         // Do not provide haptic feedback if the user was not detected, and an error (usually
         // ERROR_TIMEOUT) is received.
         return mLastAcquire != FaceManager.FACE_ACQUIRED_NOT_DETECTED
@@ -160,7 +161,7 @@
     }
 
     @Override
-    public void onError(int error, int vendorCode) {
+    public void onError(@BiometricConstants.Errors int error, int vendorCode) {
         mUsageStats.addEvent(new UsageStats.AuthenticationEvent(
                 getStartTimeMs(),
                 System.currentTimeMillis() - getStartTimeMs() /* latency */,
@@ -169,25 +170,8 @@
                 vendorCode,
                 getTargetUserId()));
 
-        switch (error) {
-            case BiometricConstants.BIOMETRIC_ERROR_TIMEOUT:
-                if (!wasUserDetected() && !isBiometricPrompt()) {
-                    // No vibration if user was not detected on keyguard
-                    break;
-                }
-            case BiometricConstants.BIOMETRIC_ERROR_LOCKOUT:
-            case BiometricConstants.BIOMETRIC_ERROR_LOCKOUT_PERMANENT:
-                if (mAuthAttempted) {
-                    // Only vibrate if auth was attempted. If the user was already locked out prior
-                    // to starting authentication, do not vibrate.
-                    vibrateError();
-                }
-                break;
-            case BiometricConstants.BIOMETRIC_ERROR_RE_ENROLL:
-                BiometricNotificationUtils.showReEnrollmentNotification(getContext());
-                break;
-            default:
-                break;
+        if (error == BiometricConstants.BIOMETRIC_ERROR_RE_ENROLL) {
+            BiometricNotificationUtils.showReEnrollmentNotification(getContext());
         }
 
         super.onError(error, vendorCode);
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceAuthenticationClient.java
index c33b957..33950af 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceAuthenticationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceAuthenticationClient.java
@@ -115,7 +115,8 @@
         }
     }
 
-    private boolean wasUserDetected() {
+    @Override
+    public boolean wasUserDetected() {
         // Do not provide haptic feedback if the user was not detected, and an error (usually
         // ERROR_TIMEOUT) is received.
         return mLastAcquire != FaceManager.FACE_ACQUIRED_NOT_DETECTED
@@ -147,7 +148,7 @@
     }
 
     @Override
-    public void onError(int error, int vendorCode) {
+    public void onError(@BiometricConstants.Errors int error, int vendorCode) {
         mUsageStats.addEvent(new UsageStats.AuthenticationEvent(
                 getStartTimeMs(),
                 System.currentTimeMillis() - getStartTimeMs() /* latency */,
@@ -156,24 +157,6 @@
                 vendorCode,
                 getTargetUserId()));
 
-        switch (error) {
-            case BiometricConstants.BIOMETRIC_ERROR_TIMEOUT:
-                if (!wasUserDetected() && !isBiometricPrompt()) {
-                    // No vibration if user was not detected on keyguard
-                    break;
-                }
-            case BiometricConstants.BIOMETRIC_ERROR_LOCKOUT:
-            case BiometricConstants.BIOMETRIC_ERROR_LOCKOUT_PERMANENT:
-                if (mAuthAttempted) {
-                    // Only vibrate if auth was attempted. If the user was already locked out prior
-                    // to starting authentication, do not vibrate.
-                    vibrateError();
-                }
-                break;
-            default:
-                break;
-        }
-
         super.onError(error, vendorCode);
     }
 
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java
index 8835c1e0..37ee76a 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java
@@ -106,6 +106,12 @@
     }
 
     @Override
+    public boolean wasUserDetected() {
+        // TODO: Update if it needs to be used for fingerprint, i.e. success/reject, error_timeout
+        return false;
+    }
+
+    @Override
     public void onAuthenticated(BiometricAuthenticator.Identifier identifier,
             boolean authenticated, ArrayList<Byte> token) {
         super.onAuthenticated(identifier, authenticated, token);
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintAuthenticationClient.java
index 83f1480..5060744 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintAuthenticationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintAuthenticationClient.java
@@ -153,6 +153,12 @@
     }
 
     @Override
+    public boolean wasUserDetected() {
+        // TODO: Update if it needs to be used for fingerprint, i.e. success/reject, error_timeout
+        return false;
+    }
+
+    @Override
     public @LockoutTracker.LockoutMode int handleFailedAttempt(int userId) {
         mLockoutFrameworkImpl.addFailedAttemptForUser(userId);
         return super.handleFailedAttempt(userId);
diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java
index 6fb9e58..a23d6cf 100644
--- a/services/core/java/com/android/server/input/InputManagerService.java
+++ b/services/core/java/com/android/server/input/InputManagerService.java
@@ -180,7 +180,7 @@
     private static final boolean UNTRUSTED_TOUCHES_TOAST = false;
 
     public static final boolean ENABLE_PER_WINDOW_INPUT_ROTATION =
-            SystemProperties.getBoolean("persist.debug.per_window_input_rotation", false);
+            SystemProperties.getBoolean("persist.debug.per_window_input_rotation", true);
 
     // Pointer to native input manager service object.
     private final long mPtr;
@@ -189,7 +189,7 @@
     private final InputManagerHandler mHandler;
 
     // Context cache used for loading pointer resources.
-    private Context mDisplayContext;
+    private Context mPointerIconDisplayContext;
 
     private final File mDoubleTouchGestureEnableFile;
 
@@ -839,21 +839,31 @@
             throw new IllegalArgumentException("mode is invalid");
         }
         if (ENABLE_PER_WINDOW_INPUT_ROTATION) {
-            if (event instanceof MotionEvent) {
-                final Context dispCtx = getContextForDisplay(event.getDisplayId());
-                final Display display = dispCtx.getDisplay();
+            // Motion events that are pointer events or relative mouse events will need to have the
+            // inverse display rotation applied to them.
+            if (event instanceof MotionEvent
+                    && (event.isFromSource(InputDevice.SOURCE_CLASS_POINTER)
+                    || event.isFromSource(InputDevice.SOURCE_MOUSE_RELATIVE))) {
+                Context displayContext = getContextForDisplay(event.getDisplayId());
+                if (displayContext == null) {
+                    displayContext = Objects.requireNonNull(
+                            getContextForDisplay(Display.DEFAULT_DISPLAY));
+                }
+                final Display display = displayContext.getDisplay();
                 final int rotation = display.getRotation();
                 if (rotation != ROTATION_0) {
                     final MotionEvent motion = (MotionEvent) event;
                     // Injections are currently expected to be in the space of the injector (ie.
-                    // usually assumed to be post-rotated). Thus we need to unrotate into raw
+                    // usually assumed to be post-rotated). Thus we need to un-rotate into raw
                     // input coordinates for dispatch.
                     final Point sz = new Point();
-                    display.getRealSize(sz);
-                    if ((rotation % 2) != 0) {
-                        final int tmpX = sz.x;
-                        sz.x = sz.y;
-                        sz.y = tmpX;
+                    if (event.isFromSource(InputDevice.SOURCE_CLASS_POINTER)) {
+                        display.getRealSize(sz);
+                        if ((rotation % 2) != 0) {
+                            final int tmpX = sz.x;
+                            sz.x = sz.y;
+                            sz.y = tmpX;
+                        }
                     }
                     motion.applyTransform(MotionEvent.createRotateMatrix(
                             (4 - rotation), sz.x, sz.y));
@@ -1742,6 +1752,11 @@
 
     /** Clean up input window handles of the given display. */
     public void onDisplayRemoved(int displayId) {
+        if (mPointerIconDisplayContext != null
+                && mPointerIconDisplayContext.getDisplay().getDisplayId() == displayId) {
+            mPointerIconDisplayContext = null;
+        }
+
         nativeDisplayRemoved(mPtr, displayId);
     }
 
@@ -2971,24 +2986,43 @@
 
     // Native callback.
     private PointerIcon getPointerIcon(int displayId) {
-        return PointerIcon.getDefaultIcon(getContextForDisplay(displayId));
+        return PointerIcon.getDefaultIcon(getContextForPointerIcon(displayId));
     }
 
-    private Context getContextForDisplay(int displayId) {
-        if (mDisplayContext != null && mDisplayContext.getDisplay().getDisplayId() == displayId) {
-            return mDisplayContext;
-        }
-
-        if (mContext.getDisplay().getDisplayId() == displayId) {
-            mDisplayContext = mContext;
-            return mDisplayContext;
+    @NonNull
+    private Context getContextForPointerIcon(int displayId) {
+        if (mPointerIconDisplayContext != null
+                && mPointerIconDisplayContext.getDisplay().getDisplayId() == displayId) {
+            return mPointerIconDisplayContext;
         }
 
         // Create and cache context for non-default display.
-        final DisplayManager displayManager = mContext.getSystemService(DisplayManager.class);
+        mPointerIconDisplayContext = getContextForDisplay(displayId);
+
+        // Fall back to default display if the requested displayId does not exist.
+        if (mPointerIconDisplayContext == null) {
+            mPointerIconDisplayContext = getContextForDisplay(Display.DEFAULT_DISPLAY);
+        }
+        return mPointerIconDisplayContext;
+    }
+
+    @Nullable
+    private Context getContextForDisplay(int displayId) {
+        if (displayId == Display.INVALID_DISPLAY) {
+            return null;
+        }
+        if (mContext.getDisplay().getDisplayId() == displayId) {
+            return mContext;
+        }
+
+        final DisplayManager displayManager = Objects.requireNonNull(
+                mContext.getSystemService(DisplayManager.class));
         final Display display = displayManager.getDisplay(displayId);
-        mDisplayContext = mContext.createDisplayContext(display);
-        return mDisplayContext;
+        if (display == null) {
+            return null;
+        }
+
+        return mContext.createDisplayContext(display);
     }
 
     // Native callback.
diff --git a/services/core/java/com/android/server/pm/PackageSettingBase.java b/services/core/java/com/android/server/pm/PackageSettingBase.java
index bf82bd8..88dd033 100644
--- a/services/core/java/com/android/server/pm/PackageSettingBase.java
+++ b/services/core/java/com/android/server/pm/PackageSettingBase.java
@@ -176,6 +176,10 @@
         name = orig.name;
         realName = orig.realName;
         doCopy(orig);
+        // Clone the user states.
+        for (int i = 0; i < mUserState.size(); i++) {
+            mUserState.put(mUserState.keyAt(i), new PackageUserState(mUserState.valueAt(i)));
+        }
     }
 
     public void setInstallerPackageName(String packageName) {
@@ -314,6 +318,7 @@
 
     void setInstalled(boolean inst, int userId) {
         modifyUserState(userId).installed = inst;
+        onChanged();
     }
 
     boolean getInstalled(int userId) {
@@ -326,6 +331,7 @@
 
     void setInstallReason(int installReason, int userId) {
         modifyUserState(userId).installReason = installReason;
+        onChanged();
     }
 
     int getUninstallReason(int userId) {
@@ -334,10 +340,13 @@
 
     void setUninstallReason(@UninstallReason int uninstallReason, int userId) {
         modifyUserState(userId).uninstallReason = uninstallReason;
+        onChanged();
     }
 
     boolean setOverlayPaths(OverlayPaths overlayPaths, int userId) {
-        return modifyUserState(userId).setOverlayPaths(overlayPaths);
+        boolean returnValue = modifyUserState(userId).setOverlayPaths(overlayPaths);
+        onChanged();
+        return returnValue;
     }
 
     OverlayPaths getOverlayPaths(int userId) {
@@ -346,7 +355,10 @@
 
     boolean setOverlayPathsForLibrary(String libName, OverlayPaths overlayPaths,
             int userId) {
-        return modifyUserState(userId).setSharedLibraryOverlayPaths(libName, overlayPaths);
+        boolean returnValue =  modifyUserState(userId)
+                .setSharedLibraryOverlayPaths(libName, overlayPaths);
+        onChanged();
+        return returnValue;
     }
 
     Map<String, OverlayPaths> getOverlayPathsForLibrary(int userId) {
@@ -395,6 +407,7 @@
 
     void setCeDataInode(long ceDataInode, int userId) {
         modifyUserState(userId).ceDataInode = ceDataInode;
+        onChanged();
     }
 
     boolean getStopped(int userId) {
@@ -403,6 +416,7 @@
 
     void setStopped(boolean stop, int userId) {
         modifyUserState(userId).stopped = stop;
+        onChanged();
     }
 
     boolean getNotLaunched(int userId) {
@@ -411,6 +425,7 @@
 
     void setNotLaunched(boolean stop, int userId) {
         modifyUserState(userId).notLaunched = stop;
+        onChanged();
     }
 
     boolean getHidden(int userId) {
@@ -419,6 +434,7 @@
 
     void setHidden(boolean hidden, int userId) {
         modifyUserState(userId).hidden = hidden;
+        onChanged();
     }
 
     int getDistractionFlags(int userId) {
@@ -427,6 +443,7 @@
 
     void setDistractionFlags(int distractionFlags, int userId) {
         modifyUserState(userId).distractionFlags = distractionFlags;
+        onChanged();
     }
 
     boolean getSuspended(int userId) {
@@ -487,6 +504,7 @@
 
     void setInstantApp(boolean instantApp, int userId) {
         modifyUserState(userId).instantApp = instantApp;
+        onChanged();
     }
 
     boolean getVirtulalPreload(int userId) {
@@ -495,6 +513,7 @@
 
     void setVirtualPreload(boolean virtualPreload, int userId) {
         modifyUserState(userId).virtualPreload = virtualPreload;
+        onChanged();
     }
 
     void setUserState(int userId, long ceDataInode, int enabled, boolean installed, boolean stopped,
@@ -547,20 +566,24 @@
 
     void setEnabledComponents(ArraySet<String> components, int userId) {
         modifyUserState(userId).enabledComponents = components;
+        onChanged();
     }
 
     void setDisabledComponents(ArraySet<String> components, int userId) {
         modifyUserState(userId).disabledComponents = components;
+        onChanged();
     }
 
     void setEnabledComponentsCopy(ArraySet<String> components, int userId) {
         modifyUserState(userId).enabledComponents = components != null
                 ? new ArraySet<String>(components) : null;
+        onChanged();
     }
 
     void setDisabledComponentsCopy(ArraySet<String> components, int userId) {
         modifyUserState(userId).disabledComponents = components != null
                 ? new ArraySet<String>(components) : null;
+        onChanged();
     }
 
     PackageUserState modifyUserStateComponents(int userId, boolean disabled, boolean enabled) {
@@ -582,10 +605,12 @@
 
     void addDisabledComponent(String componentClassName, int userId) {
         modifyUserStateComponents(userId, true, false).disabledComponents.add(componentClassName);
+        onChanged();
     }
 
     void addEnabledComponent(String componentClassName, int userId) {
         modifyUserStateComponents(userId, false, true).enabledComponents.add(componentClassName);
+        onChanged();
     }
 
     boolean enableComponentLPw(String componentClassName, int userId) {
@@ -593,6 +618,9 @@
         boolean changed = state.disabledComponents != null
                 ? state.disabledComponents.remove(componentClassName) : false;
         changed |= state.enabledComponents.add(componentClassName);
+        if (changed) {
+            onChanged();
+        }
         return changed;
     }
 
@@ -601,6 +629,9 @@
         boolean changed = state.enabledComponents != null
                 ? state.enabledComponents.remove(componentClassName) : false;
         changed |= state.disabledComponents.add(componentClassName);
+        if (changed) {
+            onChanged();
+        }
         return changed;
     }
 
@@ -610,6 +641,9 @@
                 ? state.disabledComponents.remove(componentClassName) : false;
         changed |= state.enabledComponents != null
                 ? state.enabledComponents.remove(componentClassName) : false;
+        if (changed) {
+            onChanged();
+        }
         return changed;
     }
 
@@ -701,6 +735,7 @@
     PackageSettingBase setPath(@NonNull File path) {
         this.mPath = path;
         this.mPathString = path.toString();
+        onChanged();
         return this;
     }
 
@@ -722,7 +757,9 @@
     @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
     public boolean overrideNonLocalizedLabelAndIcon(@NonNull ComponentName component,
             @Nullable String label, @Nullable Integer icon, @UserIdInt int userId) {
-        return modifyUserState(userId).overrideLabelAndIcon(component, label, icon);
+        boolean returnValue = modifyUserState(userId).overrideLabelAndIcon(component, label, icon);
+        onChanged();
+        return returnValue;
     }
 
     /**
@@ -732,6 +769,7 @@
      */
     public void resetOverrideComponentLabelIcon(@UserIdInt int userId) {
         modifyUserState(userId).resetOverrideComponentLabelIcon();
+        onChanged();
     }
 
     /**
@@ -741,6 +779,7 @@
      */
     public void setSplashScreenTheme(@UserIdInt int userId, @Nullable String themeName) {
         modifyUserState(userId).splashScreenTheme = themeName;
+        onChanged();
     }
 
     /**
@@ -776,6 +815,7 @@
      */
     public void setStatesOnCommit() {
         incrementalStates.onCommit(IncrementalManager.isIncrementalPath(getPathString()));
+        onChanged();
     }
 
     /**
@@ -783,6 +823,7 @@
      */
     public void setIncrementalStatesCallback(IncrementalStates.Callback callback) {
         incrementalStates.setCallback(callback);
+        onChanged();
     }
 
     /**
@@ -791,6 +832,7 @@
      */
     public void setLoadingProgress(float progress) {
         incrementalStates.setProgress(progress);
+        onChanged();
     }
 
     public long getFirstInstallTime() {
diff --git a/services/core/java/com/android/server/wm/ActivityMetricsLogger.java b/services/core/java/com/android/server/wm/ActivityMetricsLogger.java
index 005a62a..20958aa 100644
--- a/services/core/java/com/android/server/wm/ActivityMetricsLogger.java
+++ b/services/core/java/com/android/server/wm/ActivityMetricsLogger.java
@@ -633,6 +633,7 @@
             if (crossPackage) {
                 startLaunchTrace(info);
             }
+            scheduleCheckActivityToBeDrawnIfSleeping(launchedActivity);
             return;
         }
 
@@ -654,13 +655,7 @@
             // As abort for no process switch.
             launchObserverNotifyIntentFailed();
         }
-        if (launchedActivity.mDisplayContent.isSleeping()) {
-            // It is unknown whether the activity can be drawn or not, e.g. it depends on the
-            // keyguard states and the attributes or flags set by the activity. If the activity
-            // keeps invisible in the grace period, the tracker will be cancelled so it won't get
-            // a very long launch time that takes unlocking as the end of launch.
-            scheduleCheckActivityToBeDrawn(launchedActivity, UNKNOWN_VISIBILITY_CHECK_DELAY_MS);
-        }
+        scheduleCheckActivityToBeDrawnIfSleeping(launchedActivity);
 
         // If the previous transitions are no longer visible, abort them to avoid counting the
         // launch time when resuming from back stack. E.g. launch 2 independent tasks in a short
@@ -675,6 +670,16 @@
         }
     }
 
+    private void scheduleCheckActivityToBeDrawnIfSleeping(@NonNull ActivityRecord r) {
+        if (r.mDisplayContent.isSleeping()) {
+            // It is unknown whether the activity can be drawn or not, e.g. it depends on the
+            // keyguard states and the attributes or flags set by the activity. If the activity
+            // keeps invisible in the grace period, the tracker will be cancelled so it won't get
+            // a very long launch time that takes unlocking as the end of launch.
+            scheduleCheckActivityToBeDrawn(r, UNKNOWN_VISIBILITY_CHECK_DELAY_MS);
+        }
+    }
+
     /**
      * Notifies the tracker that all windows of the app have been drawn.
      *
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 10c3f78..bc5ace8 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -702,8 +702,6 @@
     private boolean mInSizeCompatModeForBounds = false;
 
     // Whether the aspect ratio restrictions applied to the activity bounds in applyAspectRatio().
-    // TODO(b/182268157): Aspect ratio can also be applie in resolveFixedOrientationConfiguration
-    // but that isn't reflected in this boolean.
     private boolean mIsAspectRatioApplied = false;
 
     // Bounds populated in resolveFixedOrientationConfiguration when this activity is letterboxed
@@ -7504,11 +7502,14 @@
      * requested orientation. If not, it may be necessary to letterbox the window.
      * @param parentBounds are the new parent bounds passed down to the activity and should be used
      *                     to compute the stable bounds.
-     * @param outBounds will store the stable bounds, which are the bounds with insets applied.
-     *                  These should be used to compute letterboxed bounds if orientation is not
-     *                  respected when insets are applied.
+     * @param outStableBounds will store the stable bounds, which are the bounds with insets
+     *                        applied, if orientation is not respected when insets are applied.
+     *                        Otherwise outStableBounds will be empty. Stable bounds should be used
+     *                        to compute letterboxed bounds if orientation is not respected when
+     *                        insets are applied.
      */
-    private boolean orientationRespectedWithInsets(Rect parentBounds, Rect outBounds) {
+    private boolean orientationRespectedWithInsets(Rect parentBounds, Rect outStableBounds) {
+        outStableBounds.setEmpty();
         if (mDisplayContent == null) {
             return true;
         }
@@ -7523,17 +7524,21 @@
         // Compute orientation from stable parent bounds (= parent bounds with insets applied)
         final Task task = getTask();
         task.calculateInsetFrames(mTmpOutNonDecorBounds /* outNonDecorBounds */,
-                outBounds /* outStableBounds */, parentBounds /* bounds */,
+                outStableBounds /* outStableBounds */, parentBounds /* bounds */,
                 mDisplayContent.getDisplayInfo());
-        final int orientationWithInsets = outBounds.height() >= outBounds.width()
+        final int orientationWithInsets = outStableBounds.height() >= outStableBounds.width()
                 ? ORIENTATION_PORTRAIT : ORIENTATION_LANDSCAPE;
         // If orientation does not match the orientation with insets applied, then a
         // display rotation will not be enough to respect orientation. However, even if they do
         // not match but the orientation with insets applied matches the requested orientation, then
         // there is no need to modify the bounds because when insets are applied, the activity will
         // have the desired orientation.
-        return orientation == orientationWithInsets
+        final boolean orientationRespectedWithInsets = orientation == orientationWithInsets
                 || orientationWithInsets == requestedOrientation;
+        if (orientationRespectedWithInsets) {
+            outStableBounds.setEmpty();
+        }
+        return orientationRespectedWithInsets;
     }
 
     /**
@@ -7551,9 +7556,10 @@
             int windowingMode) {
         mLetterboxBoundsForFixedOrientationAndAspectRatio = null;
         final Rect parentBounds = newParentConfig.windowConfiguration.getBounds();
-        final Rect containerBounds = new Rect(parentBounds);
+        final Rect stableBounds = new Rect();
+        // If orientation is respected when insets are applied, then stableBounds will be empty.
         boolean orientationRespectedWithInsets =
-                orientationRespectedWithInsets(parentBounds, containerBounds);
+                orientationRespectedWithInsets(parentBounds, stableBounds);
         if (handlesOrientationChangeFromDescendant() && orientationRespectedWithInsets) {
             // No need to letterbox because of fixed orientation. Display will handle
             // fixed-orientation requests and a display rotation is enough to respect requested
@@ -7590,71 +7596,68 @@
             return;
         }
 
-        // TODO(b/182268157) merge aspect ratio logic here and in
-        // {@link ActivityRecord#applyAspectRatio}
-        // if no aspect ratio constraints are provided, parent aspect ratio is used
-        float aspectRatio = computeAspectRatio(parentBounds);
+        // TODO(b/182268157): Explore using only one type of parentBoundsWithInsets, either app
+        // bounds or stable bounds to unify aspect ratio logic.
+        final Rect parentBoundsWithInsets = orientationRespectedWithInsets
+                ? newParentConfig.windowConfiguration.getAppBounds() : stableBounds;
+        final Rect containingBounds = new Rect();
+        final Rect containingBoundsWithInsets = new Rect();
+        // Need to shrink the containing bounds into a square because the parent orientation
+        // does not match the activity requested orientation.
+        if (forcedOrientation == ORIENTATION_LANDSCAPE) {
+            // Landscape is defined as width > height. Make the container respect landscape
+            // orientation by shrinking height to one less than width. Landscape activity will be
+            // vertically centered within parent bounds with insets, so position vertical bounds
+            // within parent bounds with insets to prevent insets from unnecessarily trimming
+            // vertical bounds.
+            final int bottom = Math.min(parentBoundsWithInsets.top + parentBounds.width() - 1,
+                    parentBoundsWithInsets.bottom);
+            containingBounds.set(parentBounds.left, parentBoundsWithInsets.top, parentBounds.right,
+                    bottom);
+            containingBoundsWithInsets.set(parentBoundsWithInsets.left, parentBoundsWithInsets.top,
+                    parentBoundsWithInsets.right, bottom);
+        } else {
+            // Portrait is defined as width <= height. Make the container respect portrait
+            // orientation by shrinking width to match height. Portrait activity will be
+            // horizontally centered within parent bounds with insets, so position horizontal bounds
+            // within parent bounds with insets to prevent insets from unnecessarily trimming
+            // horizontal bounds.
+            final int right = Math.min(parentBoundsWithInsets.left + parentBounds.height(),
+                    parentBoundsWithInsets.right);
+            containingBounds.set(parentBoundsWithInsets.left, parentBounds.top, right,
+                    parentBounds.bottom);
+            containingBoundsWithInsets.set(parentBoundsWithInsets.left, parentBoundsWithInsets.top,
+                    right, parentBoundsWithInsets.bottom);
+        }
+
+        // Store the current bounds to be able to revert to size compat mode values below if needed.
+        final Rect prevResolvedBounds = new Rect(resolvedBounds);
+        resolvedBounds.set(containingBounds);
 
         // Override from config_fixedOrientationLetterboxAspectRatio or via ADB with
         // set-fixed-orientation-letterbox-aspect-ratio.
         final float letterboxAspectRatioOverride =
                 mWmService.mLetterboxConfiguration.getFixedOrientationLetterboxAspectRatio();
-        aspectRatio = letterboxAspectRatioOverride > MIN_FIXED_ORIENTATION_LETTERBOX_ASPECT_RATIO
-                ? letterboxAspectRatioOverride : aspectRatio;
+        final float desiredAspectRatio =
+                letterboxAspectRatioOverride > MIN_FIXED_ORIENTATION_LETTERBOX_ASPECT_RATIO
+                        ? letterboxAspectRatioOverride : computeAspectRatio(parentBounds);
+        // Apply aspect ratio to resolved bounds
+        mIsAspectRatioApplied = applyAspectRatio(resolvedBounds, containingBoundsWithInsets,
+                containingBounds, desiredAspectRatio, true);
 
-        // Adjust the fixed orientation letterbox bounds to fit the app request aspect ratio in
-        // order to use the extra available space.
-        final float maxAspectRatio = info.getMaxAspectRatio();
-        final float minAspectRatio = info.getMinAspectRatio();
-        if (aspectRatio > maxAspectRatio && maxAspectRatio != 0) {
-            aspectRatio = maxAspectRatio;
-        } else if (aspectRatio < minAspectRatio) {
-            aspectRatio = minAspectRatio;
-        }
-
-        // Store the current bounds to be able to revert to size compat mode values below if needed.
-        final Rect prevResolvedBounds = new Rect(resolvedBounds);
-
-        // Compute other dimension based on aspect ratio. Use bounds intersected with insets, stored
-        // in containerBounds after calling {@link ActivityRecord#orientationRespectedWithInsets()},
-        // to ensure that aspect ratio is respected after insets are applied.
-        int activityWidth;
-        int activityHeight;
+        // Vertically center if orientation is landscape. Center within parent bounds with insets
+        // to ensure that insets do not trim height. Bounds will later be horizontally centered in
+        // {@link updateResolvedBoundsHorizontalPosition()} regardless of orientation.
         if (forcedOrientation == ORIENTATION_LANDSCAPE) {
-            activityWidth = parentBounds.width();
-            // Compute height from stable bounds width to ensure orientation respected after insets.
-            activityHeight = (int) Math.rint(containerBounds.width() / aspectRatio);
-            // Landscape is defined as width > height. To ensure activity is landscape when aspect
-            // ratio is close to 1, reduce the height by one pixel.
-            if (activityWidth == activityHeight) {
-                activityHeight -= 1;
-            }
-            // Center vertically within stable bounds in landscape to ensure insets do not trim
-            // height.
-            final int top = containerBounds.centerY() - activityHeight / 2;
-            resolvedBounds.set(parentBounds.left, top, parentBounds.right, top + activityHeight);
-        } else {
-            activityHeight = parentBounds.height();
-            // Compute width from stable bounds height to ensure orientation respected after insets.
-            activityWidth = (int) Math.rint(containerBounds.height() / aspectRatio);
-            // Center horizontally in portrait. For now, align to left and allow
-            // {@link ActivityRecord#updateResolvedBoundsHorizontalPosition()} to center
-            // horizontally. Exclude left insets from parent to ensure cutout does not trim width.
-            final Rect parentAppBounds = newParentConfig.windowConfiguration.getAppBounds();
-            resolvedBounds.set(parentAppBounds.left, parentBounds.top,
-                    parentAppBounds.left + activityWidth, parentBounds.bottom);
+            final int offsetY = parentBoundsWithInsets.centerY() - resolvedBounds.centerY();
+            resolvedBounds.offset(0, offsetY);
         }
 
         if (mCompatDisplayInsets != null) {
             mCompatDisplayInsets.getBoundsByRotation(
                     mTmpBounds, newParentConfig.windowConfiguration.getRotation());
-            // Insets may differ between different rotations, for example in the case of a display
-            // cutout. To ensure consistent bounds across rotations, compare the activity dimensions
-            // minus insets from the rotation the compat bounds were computed in.
-            Task.intersectWithInsetsIfFits(mTmpBounds, parentBounds,
-                    mCompatDisplayInsets.mStableInsets[mCompatDisplayInsets.mOriginalRotation]);
-            if (activityWidth != mTmpBounds.width()
-                    || activityHeight != mTmpBounds.height()) {
+            if (resolvedBounds.width() != mTmpBounds.width()
+                    || resolvedBounds.height() != mTmpBounds.height()) {
                 // The app shouldn't be resized, we only do fixed orientation letterboxing if the
                 // compat bounds are also from the same fixed orientation letterbox. Otherwise,
                 // clear the fixed orientation bounds to show app in size compat mode.
@@ -7690,8 +7693,6 @@
         // then they should be aligned later in #updateResolvedBoundsHorizontalPosition().
         if (!mTmpBounds.isEmpty()) {
             resolvedBounds.set(mTmpBounds);
-            // Exclude the horizontal decor area.
-            resolvedBounds.left = parentAppBounds.left;
         }
         if (!resolvedBounds.isEmpty() && !resolvedBounds.equals(parentBounds)) {
             // Compute the configuration based on the resolved bounds. If aspect ratio doesn't
@@ -7752,13 +7753,6 @@
             mIsAspectRatioApplied =
                     applyAspectRatio(resolvedBounds, containingAppBounds, containingBounds);
         }
-        // If the bounds are restricted by fixed aspect ratio, the resolved bounds should be put in
-        // the container app bounds. Otherwise the entire container bounds are available.
-        final boolean fillContainer = resolvedBounds.equals(containingBounds);
-        if (!fillContainer) {
-            // The horizontal position should not cover insets.
-            resolvedBounds.left = containingAppBounds.left;
-        }
 
         // Use resolvedBounds to compute other override configurations such as appBounds. The bounds
         // are calculated in compat container space. The actual position on screen will be applied
@@ -7825,6 +7819,7 @@
         // Align to top of parent (bounds) - this is a UX choice and exclude the horizontal decor
         // if needed. Horizontal position is adjusted in updateResolvedBoundsHorizontalPosition.
         // Above coordinates are in "@" space, now place "*" and "#" to screen space.
+        final boolean fillContainer = resolvedBounds.equals(containingBounds);
         final int screenPosX = fillContainer ? containerBounds.left : containerAppBounds.left;
         final int screenPosY = containerBounds.top;
         if (screenPosX != 0 || screenPosY != 0) {
@@ -8066,6 +8061,12 @@
         return true;
     }
 
+    private boolean applyAspectRatio(Rect outBounds, Rect containingAppBounds,
+            Rect containingBounds) {
+        return applyAspectRatio(outBounds, containingAppBounds, containingBounds,
+                0 /* desiredAspectRatio */, false /* fixedOrientationLetterboxed */);
+    }
+
     /**
      * Applies aspect ratio restrictions to outBounds. If no restrictions, then no change is
      * made to outBounds.
@@ -8074,17 +8075,19 @@
      */
     // TODO(b/36505427): Consider moving this method and similar ones to ConfigurationContainer.
     private boolean applyAspectRatio(Rect outBounds, Rect containingAppBounds,
-            Rect containingBounds) {
+            Rect containingBounds, float desiredAspectRatio, boolean fixedOrientationLetterboxed) {
         final float maxAspectRatio = info.getMaxAspectRatio();
         final Task rootTask = getRootTask();
         final float minAspectRatio = info.getMinAspectRatio();
 
         if (task == null || rootTask == null
-                || (inMultiWindowMode() && !shouldCreateCompatDisplayInsets())
-                || (maxAspectRatio == 0 && minAspectRatio == 0)
+                || (inMultiWindowMode() && !shouldCreateCompatDisplayInsets()
+                && !fixedOrientationLetterboxed)
+                || (maxAspectRatio < 1 && minAspectRatio < 1 && desiredAspectRatio < 1)
                 || isInVrUiMode(getConfiguration())) {
-            // We don't enforce aspect ratio if the activity task is in multiwindow unless it
-            // is in size-compat mode. We also don't set it if we are in VR mode.
+            // We don't enforce aspect ratio if the activity task is in multiwindow unless it is in
+            // size-compat mode or is letterboxed from fixed orientation. We also don't set it if we
+            // are in VR mode.
             return false;
         }
 
@@ -8092,20 +8095,30 @@
         final int containingAppHeight = containingAppBounds.height();
         final float containingRatio = computeAspectRatio(containingAppBounds);
 
+        if (desiredAspectRatio < 1) {
+            desiredAspectRatio = containingRatio;
+        }
+
+        if (maxAspectRatio >= 1 && desiredAspectRatio > maxAspectRatio) {
+            desiredAspectRatio = maxAspectRatio;
+        } else if (minAspectRatio >= 1 && desiredAspectRatio < minAspectRatio) {
+            desiredAspectRatio = minAspectRatio;
+        }
+
         int activityWidth = containingAppWidth;
         int activityHeight = containingAppHeight;
 
-        if (containingRatio > maxAspectRatio && maxAspectRatio != 0) {
+        if (containingRatio > desiredAspectRatio) {
             if (containingAppWidth < containingAppHeight) {
                 // Width is the shorter side, so we use that to figure-out what the max. height
                 // should be given the aspect ratio.
-                activityHeight = (int) ((activityWidth * maxAspectRatio) + 0.5f);
+                activityHeight = (int) ((activityWidth * desiredAspectRatio) + 0.5f);
             } else {
                 // Height is the shorter side, so we use that to figure-out what the max. width
                 // should be given the aspect ratio.
-                activityWidth = (int) ((activityHeight * maxAspectRatio) + 0.5f);
+                activityWidth = (int) ((activityHeight * desiredAspectRatio) + 0.5f);
             }
-        } else if (containingRatio < minAspectRatio) {
+        } else if (containingRatio < desiredAspectRatio) {
             boolean adjustWidth;
             switch (getRequestedConfigurationOrientation()) {
                 case ORIENTATION_LANDSCAPE:
@@ -8133,9 +8146,9 @@
                     break;
             }
             if (adjustWidth) {
-                activityWidth = (int) ((activityHeight / minAspectRatio) + 0.5f);
+                activityWidth = (int) ((activityHeight / desiredAspectRatio) + 0.5f);
             } else {
-                activityHeight = (int) ((activityWidth / minAspectRatio) + 0.5f);
+                activityHeight = (int) ((activityWidth / desiredAspectRatio) + 0.5f);
             }
         }
 
@@ -8159,6 +8172,13 @@
         }
         outBounds.set(containingBounds.left, containingBounds.top, right, bottom);
 
+        // If the bounds are restricted by fixed aspect ratio, then out bounds should be put in the
+        // container app bounds. Otherwise the entire container bounds are available.
+        if (!outBounds.equals(containingBounds)) {
+            // The horizontal position should not cover insets (e.g. display cutout).
+            outBounds.left = containingAppBounds.left;
+        }
+
         return true;
     }
 
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index 395b25d..c9f3356 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -2384,6 +2384,11 @@
             }
             mTransientLaunch = mOptions.getTransientLaunch();
             mTargetRootTask = Task.fromWindowContainerToken(mOptions.getLaunchRootTask());
+
+            if (inTaskFragment == null) {
+                inTaskFragment = TaskFragment.fromTaskFragmentToken(
+                        mOptions.getLaunchTaskFragmentToken(), mService);
+            }
         }
 
         mNotTop = (mLaunchFlags & FLAG_ACTIVITY_PREVIOUS_IS_TOP) != 0 ? sourceRecord : null;
@@ -2779,7 +2784,8 @@
             }
         } else {
             // Use the child TaskFragment (if any) as the new parent if the activity can be embedded
-            final ActivityRecord top = task.topRunningActivity();
+            final ActivityRecord top = task.topRunningActivity(false /* focusableOnly */,
+                    false /* includingEmbeddedTask */);
             newParent = top != null ? top.getTaskFragment() : task;
         }
 
diff --git a/services/core/java/com/android/server/wm/RecentTasks.java b/services/core/java/com/android/server/wm/RecentTasks.java
index ede4c2e..dca0bbd 100644
--- a/services/core/java/com/android/server/wm/RecentTasks.java
+++ b/services/core/java/com/android/server/wm/RecentTasks.java
@@ -1346,7 +1346,8 @@
                     + " activityType=" + task.getActivityType()
                     + " windowingMode=" + task.getWindowingMode()
                     + " isAlwaysOnTopWhenVisible=" + task.isAlwaysOnTopWhenVisible()
-                    + " intentFlags=" + task.getBaseIntent().getFlags());
+                    + " intentFlags=" + task.getBaseIntent().getFlags()
+                    + " isEmbedded=" + task.isEmbedded());
         }
 
         switch (task.getActivityType()) {
@@ -1392,6 +1393,11 @@
             return false;
         }
 
+        // Ignore the task if it is a embedded task
+        if (task.isEmbedded()) {
+            return false;
+        }
+
         return true;
     }
 
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index 3516c75..da3f983 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -365,8 +365,26 @@
                 return false;
             }
 
+            if (matchingCandidate(task)) {
+                return true;
+            }
+
+            // Looking for the embedded tasks (if any)
+            return !task.isLeafTaskFragment() && task.forAllLeafTaskFragments(
+                    this::matchingCandidate);
+        }
+
+        boolean matchingCandidate(TaskFragment taskFragment) {
+            final Task task = taskFragment.asTask();
+            if (task == null) {
+                return false;
+            }
+
             // Overlays should not be considered as the task's logical top activity.
-            final ActivityRecord r = task.getTopNonFinishingActivity(false /* includeOverlays */);
+            // Activities of the tasks that embedded from this one should not be used.
+            final ActivityRecord r = task.getTopNonFinishingActivity(false /* includeOverlays */,
+                    false /* includingEmbeddedTask */);
+
             if (r == null || r.finishing || r.mUserId != userId
                     || r.launchMode == ActivityInfo.LAUNCH_SINGLE_INSTANCE) {
                 ProtoLog.d(WM_DEBUG_TASKS, "Skipping %s: mismatch root %s", task, r);
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 7cb8109..69ad59a 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -1394,14 +1394,6 @@
         return mFindRootHelper.findRoot(ignoreRelinquishIdentity, setToBottomIfNone);
     }
 
-    ActivityRecord getTopNonFinishingActivity() {
-        return getTopNonFinishingActivity(true /* includeOverlays */);
-    }
-
-    ActivityRecord getTopNonFinishingActivity(boolean includeOverlays) {
-        return getTopActivity(false /*includeFinishing*/, includeOverlays);
-    }
-
     ActivityRecord topRunningActivityLocked() {
         if (getParent() == null) {
             return null;
@@ -2976,11 +2968,54 @@
     /** Returns the top-most activity that occludes the given one, or {@code null} if none. */
     @Nullable
     ActivityRecord getOccludingActivityAbove(ActivityRecord activity) {
-        final ActivityRecord top = getActivity(ActivityRecord::occludesParent,
-                true /* traverseTopToBottom */, activity);
+        final ActivityRecord top = getActivity(r -> {
+            if (r == activity) {
+                // Reached the given activity, return the activity to stop searching.
+                return true;
+            }
+
+            if (!r.occludesParent()) {
+                return false;
+            }
+
+            TaskFragment parent = r.getTaskFragment();
+            if (parent == activity.getTaskFragment()) {
+                // Found it. This activity on top of the given activity on the same TaskFragment.
+                return true;
+            }
+            if (isSelfOrNonEmbeddedTask(parent.asTask())) {
+                // Found it. This activity is the direct child of a leaf Task without being
+                // embedded.
+                return true;
+            }
+            // The candidate activity is being embedded. Checking if the bounds of the containing
+            // TaskFragment equals to the outer TaskFragment.
+            TaskFragment grandParent = parent.getParent().asTaskFragment();
+            while (grandParent != null) {
+                if (!parent.getBounds().equals(grandParent.getBounds())) {
+                    // Not occluding the grandparent.
+                    break;
+                }
+                if (isSelfOrNonEmbeddedTask(grandParent.asTask())) {
+                    // Found it. The activity occludes its parent TaskFragment and the parent
+                    // TaskFragment also occludes its parent all the way up.
+                    return true;
+                }
+                parent = grandParent;
+                grandParent = parent.getParent().asTaskFragment();
+            }
+            return false;
+        });
         return top != activity ? top : null;
     }
 
+    private boolean isSelfOrNonEmbeddedTask(Task task) {
+        if (task == this) {
+            return true;
+        }
+        return task != null && !task.isEmbedded();
+    }
+
     @Override
     public SurfaceControl.Builder makeAnimationLeash() {
         return super.makeAnimationLeash().setMetadata(METADATA_TASK_ID, mTaskId);
diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java
index 255e443..5d679cf 100644
--- a/services/core/java/com/android/server/wm/TaskFragment.java
+++ b/services/core/java/com/android/server/wm/TaskFragment.java
@@ -293,6 +293,13 @@
         mRemoteToken = new RemoteToken(this);
     }
 
+    @NonNull
+    static TaskFragment fromTaskFragmentToken(@Nullable IBinder token,
+            @NonNull ActivityTaskManagerService service) {
+        if (token == null) return null;
+        return service.mWindowOrganizerController.getTaskFragment(token);
+    }
+
     void setAdjacentTaskFragment(@Nullable TaskFragment taskFragment) {
         if (mAdjacentTaskFragment == taskFragment) {
             return;
@@ -607,17 +614,68 @@
         return false;
     }
 
+    ActivityRecord getTopNonFinishingActivity() {
+        return getTopNonFinishingActivity(true /* includeOverlays */);
+    }
+
+    ActivityRecord getTopNonFinishingActivity(boolean includeOverlays) {
+        return getTopNonFinishingActivity(includeOverlays, true /* includingEmbeddedTask */);
+    }
+
+    /**
+     * Returns the top-most non-finishing activity, even if the activity is NOT ok to show to
+     * the current user.
+     * @param includeOverlays whether the task overlay activity should be included.
+     * @param includingEmbeddedTask whether the activity in a task that being embedded from this
+     *                              one should be included.
+     * @see #topRunningActivity(boolean, boolean)
+     * @see ActivityRecord#okToShowLocked()
+     */
+    ActivityRecord getTopNonFinishingActivity(boolean includeOverlays,
+            boolean includingEmbeddedTask) {
+        // Split into 4 to avoid object creation due to variable capture.
+        if (includeOverlays) {
+            if (includingEmbeddedTask) {
+                return getActivity((r) -> !r.finishing);
+            }
+            return getActivity((r) -> !r.finishing && r.getTask() == this.getTask());
+        }
+
+        if (includingEmbeddedTask) {
+            return getActivity((r) -> !r.finishing && !r.isTaskOverlay());
+        }
+        return getActivity(
+                (r) -> !r.finishing && !r.isTaskOverlay() && r.getTask() == this.getTask());
+    }
+
     ActivityRecord topRunningActivity() {
         return topRunningActivity(false /* focusableOnly */);
     }
 
     ActivityRecord topRunningActivity(boolean focusableOnly) {
-        // Split into 2 to avoid object creation due to variable capture.
+        return topRunningActivity(focusableOnly, true /* includingEmbeddedTask */);
+    }
+
+    /**
+     * Returns the top-most running activity, which the activity is non-finishing and ok to show
+     * to the current user.
+     *
+     * @see ActivityRecord#canBeTopRunning()
+     */
+    ActivityRecord topRunningActivity(boolean focusableOnly, boolean includingEmbeddedTask) {
+        // Split into 4 to avoid object creation due to variable capture.
         if (focusableOnly) {
-            return getActivity((r) -> r.canBeTopRunning() && r.isFocusable());
-        } else {
+            if (includingEmbeddedTask) {
+                return getActivity((r) -> r.canBeTopRunning() && r.isFocusable());
+            }
+            return getActivity(
+                    (r) -> r.canBeTopRunning() && r.isFocusable() && r.getTask() == this.getTask());
+        }
+
+        if (includingEmbeddedTask) {
             return getActivity(ActivityRecord::canBeTopRunning);
         }
+        return getActivity((r) -> r.canBeTopRunning() && r.getTask() == this.getTask());
     }
 
     boolean isTopActivityFocusable() {
@@ -1340,6 +1398,8 @@
 
             } else {
                 prev.schedulePauseTimeout();
+                // Unset readiness since we now need to wait until this pause is complete.
+                mAtmService.getTransitionController().setReady(this, false /* ready */);
                 return true;
             }
 
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index 6bfa611..a55fc4e 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -657,7 +657,7 @@
         ArrayMap<WindowContainer, Integer> reasons = new ArrayMap<>();
         for (int i = mParticipants.size() - 1; i >= 0; --i) {
             ActivityRecord r = mParticipants.valueAt(i).asActivityRecord();
-            if (r == null) continue;
+            if (r == null || !r.mVisibleRequested) continue;
             // At this point, r is "ready", but if it's not "ALL ready" then it is probably only
             // ready due to starting-window.
             reasons.put(r, (r.mStartingData instanceof SplashScreenStartingData
diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java
index e62a6e4..b5d98a6 100644
--- a/services/core/java/com/android/server/wm/WindowOrganizerController.java
+++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java
@@ -1174,6 +1174,11 @@
         taskFragment.removeImmediately();
     }
 
+    @Nullable
+    TaskFragment getTaskFragment(IBinder tfToken) {
+        return mLaunchTaskFragments.get(tfToken);
+    }
+
     static class CallerInfo {
         final int mPid;
         final int mUid;
diff --git a/services/tests/servicestests/src/com/android/server/appop/AppOpsStartedWatcherTest.java b/services/tests/servicestests/src/com/android/server/appop/AppOpsStartedWatcherTest.java
index c12eb32..e98a4dd 100644
--- a/services/tests/servicestests/src/com/android/server/appop/AppOpsStartedWatcherTest.java
+++ b/services/tests/servicestests/src/com/android/server/appop/AppOpsStartedWatcherTest.java
@@ -64,12 +64,16 @@
                 .times(1)).onOpStarted(eq(AppOpsManager.OP_FINE_LOCATION),
                 eq(Process.myUid()), eq(getContext().getPackageName()),
                 eq(getContext().getAttributionTag()), eq(AppOpsManager.OP_FLAG_SELF),
-                eq(AppOpsManager.MODE_ALLOWED));
+                eq(AppOpsManager.MODE_ALLOWED), eq(OnOpStartedListener.START_TYPE_STARTED),
+                eq(AppOpsManager.ATTRIBUTION_FLAGS_NONE),
+                eq(AppOpsManager.ATTRIBUTION_CHAIN_ID_NONE));
         inOrder.verify(listener, timeout(NOTIFICATION_TIMEOUT_MILLIS)
                 .times(1)).onOpStarted(eq(AppOpsManager.OP_CAMERA),
                 eq(Process.myUid()), eq(getContext().getPackageName()),
                 eq(getContext().getAttributionTag()), eq(AppOpsManager.OP_FLAG_SELF),
-                eq(AppOpsManager.MODE_ALLOWED));
+                eq(AppOpsManager.MODE_ALLOWED), eq(OnOpStartedListener.START_TYPE_STARTED),
+                eq(AppOpsManager.ATTRIBUTION_FLAGS_NONE),
+                eq(AppOpsManager.ATTRIBUTION_CHAIN_ID_NONE));
 
         // Stop watching
         appOpsManager.stopWatchingStarted(listener);
@@ -94,7 +98,9 @@
                 .times(2)).onOpStarted(eq(AppOpsManager.OP_CAMERA),
                 eq(Process.myUid()), eq(getContext().getPackageName()),
                 eq(getContext().getAttributionTag()), eq(AppOpsManager.OP_FLAG_SELF),
-                eq(AppOpsManager.MODE_ALLOWED));
+                eq(AppOpsManager.MODE_ALLOWED), eq(OnOpStartedListener.START_TYPE_STARTED),
+                eq(AppOpsManager.ATTRIBUTION_FLAGS_NONE),
+                eq(AppOpsManager.ATTRIBUTION_CHAIN_ID_NONE));
         verifyNoMoreInteractions(listener);
 
         // Finish up
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerTest.java
index 8592166a..f4d1499 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerTest.java
@@ -378,6 +378,11 @@
         protected void handleLifecycleAfterAuth(boolean authenticated) {
 
         }
+
+        @Override
+        public boolean wasUserDetected() {
+            return false;
+        }
     }
 
     private static class TestAuthenticationClient extends AuthenticationClient<Object> {
@@ -407,6 +412,11 @@
         protected void handleLifecycleAfterAuth(boolean authenticated) {
 
         }
+
+        @Override
+        public boolean wasUserDetected() {
+            return false;
+        }
     }
 
     private static class TestClientMonitor2 extends TestClientMonitor {
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/CoexCoordinatorTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/CoexCoordinatorTest.java
index 7f5f3c2..bfb0be7 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/CoexCoordinatorTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/CoexCoordinatorTest.java
@@ -32,6 +32,7 @@
 import static org.mockito.Mockito.withSettings;
 
 import android.content.Context;
+import android.hardware.biometrics.BiometricConstants;
 import android.os.Handler;
 import android.os.Looper;
 import android.platform.test.annotations.Presubmit;
@@ -61,6 +62,8 @@
     private Context mContext;
     @Mock
     private CoexCoordinator.Callback mCallback;
+    @Mock
+    private CoexCoordinator.ErrorCallback mErrorCallback;
 
     @Before
     public void setUp() {
@@ -255,13 +258,16 @@
         mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_FACE, faceClient);
         mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_UDFPS, udfpsClient);
 
+        // For easier reading
+        final CoexCoordinator.Callback faceCallback = mCallback;
+
         mCoexCoordinator.onAuthenticationSucceeded(0 /* currentTimeMillis */, faceClient,
-                mCallback);
-        verify(mCallback, never()).sendHapticFeedback();
-        verify(mCallback, never()).sendAuthenticationResult(anyBoolean());
+                faceCallback);
+        verify(faceCallback, never()).sendHapticFeedback();
+        verify(faceCallback, never()).sendAuthenticationResult(anyBoolean());
         // CoexCoordinator requests the system to hold onto this AuthenticationClient until
         // UDFPS result is known
-        verify(mCallback, never()).handleLifecycleAfterAuth();
+        verify(faceCallback, never()).handleLifecycleAfterAuth();
 
         // Reset the mock
         CoexCoordinator.Callback udfpsCallback = mock(CoexCoordinator.Callback.class);
@@ -274,6 +280,8 @@
             verify(udfpsCallback).sendAuthenticationResult(true /* addAuthTokenIfStrong */);
             verify(udfpsCallback).handleLifecycleAfterAuth();
 
+            verify(faceCallback).sendAuthenticationCanceled();
+
             assertTrue(mCoexCoordinator.mSuccessfulAuths.isEmpty());
         } else {
             mCoexCoordinator.onAuthenticationRejected(udfpsRejectedAfterMs, udfpsClient,
@@ -281,16 +289,16 @@
             if (udfpsRejectedAfterMs <= CoexCoordinator.SUCCESSFUL_AUTH_VALID_DURATION_MS) {
                 verify(udfpsCallback, never()).sendHapticFeedback();
 
-                verify(mCallback).sendHapticFeedback();
-                verify(mCallback).sendAuthenticationResult(eq(true) /* addAuthTokenIfStrong */);
-                verify(mCallback).handleLifecycleAfterAuth();
+                verify(faceCallback).sendHapticFeedback();
+                verify(faceCallback).sendAuthenticationResult(eq(true) /* addAuthTokenIfStrong */);
+                verify(faceCallback).handleLifecycleAfterAuth();
 
                 assertTrue(mCoexCoordinator.mSuccessfulAuths.isEmpty());
             } else {
                 assertTrue(mCoexCoordinator.mSuccessfulAuths.isEmpty());
 
-                verify(mCallback, never()).sendHapticFeedback();
-                verify(mCallback, never()).sendAuthenticationResult(anyBoolean());
+                verify(faceCallback, never()).sendHapticFeedback();
+                verify(faceCallback, never()).sendAuthenticationResult(anyBoolean());
 
                 verify(udfpsCallback).sendHapticFeedback();
                 verify(udfpsCallback)
@@ -485,4 +493,82 @@
         verify(callback).handleLifecycleAfterAuth();
         verify(successfulAuths).remove(eq(auth));
     }
+
+    @Test
+    public void testBiometricPrompt_FaceError() {
+        mCoexCoordinator.reset();
+
+        AuthenticationClient<?> client = mock(AuthenticationClient.class);
+        when(client.isBiometricPrompt()).thenReturn(true);
+        when(client.wasAuthAttempted()).thenReturn(true);
+
+        mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_FACE, client);
+
+        mCoexCoordinator.onAuthenticationError(client, BiometricConstants.BIOMETRIC_ERROR_TIMEOUT,
+                mErrorCallback);
+        verify(mErrorCallback).sendHapticFeedback();
+    }
+
+    @Test
+    public void testKeyguard_faceAuthOnly_errorWhenBypassEnabled() {
+        testKeyguard_faceAuthOnly(true /* bypassEnabled */);
+    }
+
+    @Test
+    public void testKeyguard_faceAuthOnly_errorWhenBypassDisabled() {
+        testKeyguard_faceAuthOnly(false /* bypassEnabled */);
+    }
+
+    private void testKeyguard_faceAuthOnly(boolean bypassEnabled) {
+        mCoexCoordinator.reset();
+
+        AuthenticationClient<?> client = mock(AuthenticationClient.class);
+        when(client.isKeyguard()).thenReturn(true);
+        when(client.isKeyguardBypassEnabled()).thenReturn(bypassEnabled);
+        when(client.wasAuthAttempted()).thenReturn(true);
+        when(client.wasUserDetected()).thenReturn(true);
+
+        mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_FACE, client);
+
+        mCoexCoordinator.onAuthenticationError(client, BiometricConstants.BIOMETRIC_ERROR_TIMEOUT,
+                mErrorCallback);
+        verify(mErrorCallback).sendHapticFeedback();
+    }
+
+    @Test
+    public void testKeyguard_coex_faceErrorWhenBypassEnabled() {
+        testKeyguard_coex_faceError(true /* bypassEnabled */);
+    }
+
+    @Test
+    public void testKeyguard_coex_faceErrorWhenBypassDisabled() {
+        testKeyguard_coex_faceError(false /* bypassEnabled */);
+    }
+
+    private void testKeyguard_coex_faceError(boolean bypassEnabled) {
+        mCoexCoordinator.reset();
+
+        AuthenticationClient<?> faceClient = mock(AuthenticationClient.class);
+        when(faceClient.isKeyguard()).thenReturn(true);
+        when(faceClient.isKeyguardBypassEnabled()).thenReturn(bypassEnabled);
+        when(faceClient.wasAuthAttempted()).thenReturn(true);
+        when(faceClient.wasUserDetected()).thenReturn(true);
+
+        AuthenticationClient<?> udfpsClient = mock(AuthenticationClient.class,
+                withSettings().extraInterfaces(Udfps.class));
+        when(udfpsClient.isKeyguard()).thenReturn(true);
+        when(((Udfps) udfpsClient).isPointerDown()).thenReturn(false);
+
+        mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_FACE, faceClient);
+        mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_UDFPS, udfpsClient);
+
+        mCoexCoordinator.onAuthenticationError(faceClient,
+                BiometricConstants.BIOMETRIC_ERROR_TIMEOUT, mErrorCallback);
+
+        if (bypassEnabled) {
+            verify(mErrorCallback).sendHapticFeedback();
+        } else {
+            verify(mErrorCallback, never()).sendHapticFeedback();
+        }
+    }
 }
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityMetricsLaunchObserverTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityMetricsLaunchObserverTests.java
index fa1f4ac..b282cd7 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityMetricsLaunchObserverTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityMetricsLaunchObserverTests.java
@@ -27,6 +27,7 @@
 
 import static com.google.common.truth.Truth.assertWithMessage;
 
+import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.anyLong;
 import static org.mockito.ArgumentMatchers.argThat;
@@ -70,6 +71,7 @@
 @Presubmit
 @RunWith(WindowTestRunner.class)
 public class ActivityMetricsLaunchObserverTests extends WindowTestsBase {
+    private static final long TIMEOUT_MS = TimeUnit.SECONDS.toMillis(5);
     private ActivityMetricsLogger mActivityMetricsLogger;
     private ActivityMetricsLogger.LaunchingState mLaunchingState;
     private ActivityMetricsLaunchObserver mLaunchObserver;
@@ -137,7 +139,7 @@
         // messages that are waiting for the lock.
         waitHandlerIdle(mAtm.mH);
         // AMLO callbacks happen on a separate thread than AML calls, so we need to use a timeout.
-        return verify(mock, timeout(TimeUnit.SECONDS.toMillis(5)));
+        return verify(mock, timeout(TIMEOUT_MS));
     }
 
     private void verifyOnActivityLaunchFinished(ActivityRecord activity) {
@@ -258,15 +260,40 @@
 
     @Test
     public void testOnActivityLaunchWhileSleeping() {
-        notifyActivityLaunching(mTopActivity.intent);
-        notifyActivityLaunched(START_SUCCESS, mTopActivity);
-        doReturn(true).when(mTopActivity.mDisplayContent).isSleeping();
-        mTopActivity.setState(ActivityRecord.State.RESUMED, "test");
-        mTopActivity.setVisibility(false);
+        notifyActivityLaunching(mTrampolineActivity.intent);
+        notifyActivityLaunched(START_SUCCESS, mTrampolineActivity);
+        doReturn(true).when(mTrampolineActivity.mDisplayContent).isSleeping();
+        mTrampolineActivity.setState(ActivityRecord.State.RESUMED, "test");
+        mTrampolineActivity.setVisibility(false);
         waitHandlerIdle(mAtm.mH);
         // Not cancel immediately because in one of real cases, the keyguard may be going away or
         // occluded later, then the activity can be drawn.
-        verify(mLaunchObserver, never()).onActivityLaunchCancelled(eqProto(mTopActivity));
+        verify(mLaunchObserver, never()).onActivityLaunchCancelled(eqProto(mTrampolineActivity));
+
+        clearInvocations(mLaunchObserver);
+        mLaunchTopByTrampoline = true;
+        mTopActivity.mVisibleRequested = false;
+        notifyActivityLaunching(mTopActivity.intent);
+        // It should schedule a message with UNKNOWN_VISIBILITY_CHECK_DELAY_MS to check whether
+        // the launch event is still valid.
+        notifyActivityLaunched(START_SUCCESS, mTopActivity);
+
+        // The posted message will acquire wm lock, so the test needs to release the lock to verify.
+        final Throwable error = awaitInWmLock(() -> {
+            try {
+                // Though the aborting target should be eqProto(mTopActivity), use any() to avoid
+                // any changes in proto that may cause failure by different arguments.
+                verify(mLaunchObserver, timeout(TIMEOUT_MS)).onActivityLaunchCancelled(any());
+            } catch (Throwable e) {
+                // Catch any errors including assertion because this runs in another thread.
+                return e;
+            }
+            return null;
+        });
+        // The launch event must be cancelled because the activity keeps invisible.
+        if (error != null) {
+            throw new AssertionError(error);
+        }
     }
 
     @Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java b/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java
index e3c38b0..1b078b7 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java
@@ -788,6 +788,19 @@
     }
 
     @Test
+    public void testVisibleEmbeddedTask_expectNotVisible() {
+        Task task = createTaskBuilder(".Task")
+                .setFlags(FLAG_ACTIVITY_NEW_TASK)
+                .build();
+        doReturn(true).when(task).isEmbedded();
+        mRecentTasks.add(task);
+
+        assertThat(mCallbacksRecorder.mAdded).hasSize(1);
+        assertFalse("embedded task should not be visible recents",
+                mRecentTasks.isVisibleRecentTask(task));
+    }
+
+    @Test
     public void testFreezeTaskListOrder_reorderExistingTask() {
         // Add some tasks
         mRecentTasks.add(mTasks.get(0));
diff --git a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
index d10c006..3bebf6b 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
@@ -1986,6 +1986,61 @@
         assertTrue(mActivity.areBoundsLetterboxed());
     }
 
+    /**
+     * Tests that all three paths in which aspect ratio logic can be applied yield the same
+     * result, which is that aspect ratio is respected on app bounds. The three paths are
+     * fixed orientation, no fixed orientation but fixed aspect ratio, and size compat mode.
+     */
+    @Test
+    public void testAllAspectRatioLogicConsistent() {
+        // Create display that has all stable insets and does not rotate. Make sure that status bar
+        // height is greater than notch height so that stable bounds do not equal app bounds.
+        final int notchHeight = 75;
+        final DisplayContent display = new TestDisplayContent.Builder(mAtm, 1080, 600)
+                .setSystemDecorations(true).setNotch(notchHeight)
+                .setStatusBarHeight(notchHeight + 20).setCanRotate(false).build();
+
+        // Create task on test display.
+        final Task task = new TaskBuilder(mSupervisor).setDisplay(display).build();
+
+        // Target min aspect ratio must be larger than parent aspect ratio to be applied.
+        final float targetMinAspectRatio = 3.0f;
+
+        // Create fixed portait activity with min aspect ratio greater than parent aspect ratio.
+        final ActivityRecord fixedOrientationActivity = new ActivityBuilder(mAtm)
+                .setTask(task).setScreenOrientation(SCREEN_ORIENTATION_PORTRAIT)
+                .setMinAspectRatio(targetMinAspectRatio).build();
+        final Rect fixedOrientationAppBounds = new Rect(fixedOrientationActivity.getConfiguration()
+                .windowConfiguration.getAppBounds());
+
+        // Create activity with no fixed orientation and min aspect ratio greater than parent aspect
+        // ratio.
+        final ActivityRecord minAspectRatioActivity = new ActivityBuilder(mAtm).setTask(task)
+                .setMinAspectRatio(targetMinAspectRatio).build();
+        final Rect minAspectRatioAppBounds = new Rect(minAspectRatioActivity.getConfiguration()
+                .windowConfiguration.getAppBounds());
+
+        // Create unresizeable fixed portait activity with min aspect ratio greater than parent
+        // aspect ratio.
+        final ActivityRecord sizeCompatActivity = new ActivityBuilder(mAtm)
+                .setTask(task).setResizeMode(RESIZE_MODE_UNRESIZEABLE)
+                .setScreenOrientation(SCREEN_ORIENTATION_PORTRAIT)
+                .setMinAspectRatio(targetMinAspectRatio).build();
+        // Resize display running unresizeable activity to make it enter size compat mode.
+        resizeDisplay(display, 1800, 1000);
+        final Rect sizeCompatAppBounds = new Rect(sizeCompatActivity.getConfiguration()
+                .windowConfiguration.getAppBounds());
+
+        // Check that aspect ratio of app bounds is equal to the min aspect ratio.
+        final float delta = 0.01f;
+        assertEquals(targetMinAspectRatio, ActivityRecord
+                .computeAspectRatio(fixedOrientationAppBounds), delta);
+        assertEquals(targetMinAspectRatio, ActivityRecord
+                .computeAspectRatio(minAspectRatioAppBounds), delta);
+        assertEquals(targetMinAspectRatio, ActivityRecord
+                .computeAspectRatio(sizeCompatAppBounds), delta);
+    }
+
     private void assertHorizontalPositionForDifferentDisplayConfigsForLandscapeActivity(
             float letterboxHorizontalPositionMultiplier) {
         // Set up a display in landscape and ignoring orientation request.
diff --git a/services/tests/wmtests/src/com/android/server/wm/TestDisplayContent.java b/services/tests/wmtests/src/com/android/server/wm/TestDisplayContent.java
index ce2d748..0e504d3 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TestDisplayContent.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TestDisplayContent.java
@@ -19,7 +19,9 @@
 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
 import static android.view.DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS;
 
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.any;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.anyBoolean;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.anyInt;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
@@ -77,6 +79,7 @@
         private int mPosition = POSITION_BOTTOM;
         protected final ActivityTaskManagerService mService;
         private boolean mSystemDecorations = false;
+        private int mStatusBarHeight = 0;
 
         Builder(ActivityTaskManagerService service, int width, int height) {
             mService = service;
@@ -125,6 +128,10 @@
                     Insets.of(0, height, 0, 0), null, new Rect(20, 0, 80, height), null, null);
             return this;
         }
+        Builder setStatusBarHeight(int height) {
+            mStatusBarHeight = height;
+            return this;
+        }
         Builder setCanRotate(boolean canRotate) {
             mCanRotate = canRotate;
             return this;
@@ -158,6 +165,14 @@
                 doReturn(false).when(displayPolicy).hasStatusBar();
                 doReturn(false).when(newDisplay).supportsSystemDecorations();
             }
+            if (mStatusBarHeight > 0) {
+                doReturn(true).when(displayPolicy).hasStatusBar();
+                doAnswer(invocation -> {
+                    Rect inOutInsets = (Rect) invocation.getArgument(0);
+                    inOutInsets.top = mStatusBarHeight;
+                    return null;
+                }).when(displayPolicy).convertNonDecorInsetsToStableInsets(any(), anyInt());
+            }
             Configuration c = new Configuration();
             newDisplay.computeScreenConfiguration(c);
             c.windowConfiguration.setWindowingMode(mWindowingMode);
diff --git a/telephony/OWNERS b/telephony/OWNERS
index 628c480..4df8a4b 100644
--- a/telephony/OWNERS
+++ b/telephony/OWNERS
@@ -4,13 +4,14 @@
 breadley@google.com
 fionaxu@google.com
 jackyu@google.com
-hallliu@google.com
 rgreenwalt@google.com
 tgunn@google.com
 jminjie@google.com
 shuoq@google.com
-refuhoo@google.com
 nazaninb@google.com
 sarahchin@google.com
-dbright@google.com
 xiaotonj@google.com
+huiwang@google.com
+jayachandranc@google.com
+chinmayd@google.com
+amruthr@google.com
diff --git a/telephony/java/android/telephony/ims/aidl/SipDelegateAidlWrapper.java b/telephony/java/android/telephony/ims/aidl/SipDelegateAidlWrapper.java
index c18ab33..f004824 100644
--- a/telephony/java/android/telephony/ims/aidl/SipDelegateAidlWrapper.java
+++ b/telephony/java/android/telephony/ims/aidl/SipDelegateAidlWrapper.java
@@ -193,6 +193,10 @@
         return mDelegateBinder;
     }
 
+    public ISipDelegateStateCallback getStateCallbackBinder() {
+        return mStateBinder;
+    }
+
     private void notifyLocalMessageFailedToBeReceived(SipMessage m, int reason) {
         String transactionId = m.getViaBranchParameter();
         SipDelegate d = mDelegate;
diff --git a/telephony/java/android/telephony/ims/stub/SipTransportImplBase.java b/telephony/java/android/telephony/ims/stub/SipTransportImplBase.java
index 1f74c09..13ea9973 100644
--- a/telephony/java/android/telephony/ims/stub/SipTransportImplBase.java
+++ b/telephony/java/android/telephony/ims/stub/SipTransportImplBase.java
@@ -21,6 +21,7 @@
 import android.annotation.SystemApi;
 import android.os.Binder;
 import android.os.IBinder;
+import android.os.RemoteException;
 import android.telephony.ims.DelegateMessageCallback;
 import android.telephony.ims.DelegateRequest;
 import android.telephony.ims.DelegateStateCallback;
@@ -33,6 +34,7 @@
 import android.util.Log;
 
 import java.util.ArrayList;
+import java.util.NoSuchElementException;
 import java.util.Objects;
 import java.util.concurrent.Executor;
 
@@ -49,10 +51,15 @@
 public class SipTransportImplBase {
     private static final String LOG_TAG = "SipTransportIB";
 
-    private IBinder.DeathRecipient mDeathRecipient = new IBinder.DeathRecipient() {
+    private final IBinder.DeathRecipient mDeathRecipient = new IBinder.DeathRecipient() {
         @Override
         public void binderDied() {
-            mBinderExecutor.execute(() -> binderDiedInternal());
+            // Clean up all binders in this case.
+            mBinderExecutor.execute(() -> binderDiedInternal(null));
+        }
+        @Override
+        public void binderDied(IBinder who) {
+            mBinderExecutor.execute(() -> binderDiedInternal(who));
         }
     };
 
@@ -142,6 +149,7 @@
             ISipDelegateStateCallback cb, ISipDelegateMessageCallback mc) {
         SipDelegateAidlWrapper wrapper = new SipDelegateAidlWrapper(mBinderExecutor, cb, mc);
         mDelegates.add(wrapper);
+        linkDeathRecipient(wrapper);
         createSipDelegate(subId, r, wrapper, wrapper);
     }
 
@@ -155,6 +163,7 @@
         }
 
         if (result != null) {
+            unlinkDeathRecipient(result);
             mDelegates.remove(result);
             destroySipDelegate(result.getDelegate(), reason);
         } else {
@@ -163,12 +172,37 @@
         }
     }
 
-    private void binderDiedInternal() {
-        for (SipDelegateAidlWrapper w : mDelegates) {
-            destroySipDelegate(w.getDelegate(),
-                    SipDelegateManager.SIP_DELEGATE_DESTROY_REASON_SERVICE_DEAD);
+    private void linkDeathRecipient(SipDelegateAidlWrapper w) {
+        try {
+            w.getStateCallbackBinder().asBinder().linkToDeath(mDeathRecipient, 0);
+        } catch (RemoteException e) {
+            Log.w(LOG_TAG, "linkDeathRecipient, remote process already died, cleaning up.");
+            mDeathRecipient.binderDied(w.getStateCallbackBinder().asBinder());
         }
-        mDelegates.clear();
+    }
+
+    private void unlinkDeathRecipient(SipDelegateAidlWrapper w) {
+        try {
+            w.getStateCallbackBinder().asBinder().unlinkToDeath(mDeathRecipient, 0);
+        } catch (NoSuchElementException e) {
+            // Ignore this case.
+        }
+    }
+
+    private void binderDiedInternal(IBinder who) {
+        for (SipDelegateAidlWrapper w : mDelegates) {
+            // If the binder itself was not given from the platform, just clean up all binders.
+            if (who == null || w.getStateCallbackBinder().asBinder().equals(who))  {
+                Log.w(LOG_TAG, "Binder death detected for " + w + ", calling destroy and "
+                        + "removing.");
+                mDelegates.remove(w);
+                destroySipDelegate(w.getDelegate(),
+                        SipDelegateManager.SIP_DELEGATE_DESTROY_REASON_SERVICE_DEAD);
+                return;
+            }
+        }
+        Log.w(LOG_TAG, "Binder death detected for IBinder " + who + ", but couldn't find matching "
+                + "SipDelegate");
     }
 
     /**