Merge "Don't show biometric prompt if owner is not in foreground" into udc-qpr-dev
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
index 92fc78e..4f5eb37 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
@@ -1598,7 +1598,7 @@
                     uId, null, jobStatus.getBatteryName(),
                     FrameworkStatsLog.SCHEDULED_JOB_STATE_CHANGED__STATE__SCHEDULED,
                     JobProtoEnums.INTERNAL_STOP_REASON_UNKNOWN, jobStatus.getStandbyBucket(),
-                    jobStatus.getJobId(),
+                    jobStatus.getLoggingJobId(),
                     jobStatus.hasChargingConstraint(),
                     jobStatus.hasBatteryNotLowConstraint(),
                     jobStatus.hasStorageNotLowConstraint(),
@@ -2026,7 +2026,7 @@
                     cancelled.getSourceUid(), null, cancelled.getBatteryName(),
                     FrameworkStatsLog.SCHEDULED_JOB_STATE_CHANGED__STATE__CANCELLED,
                     internalReasonCode, cancelled.getStandbyBucket(),
-                    cancelled.getJobId(),
+                    cancelled.getLoggingJobId(),
                     cancelled.hasChargingConstraint(),
                     cancelled.hasBatteryNotLowConstraint(),
                     cancelled.hasStorageNotLowConstraint(),
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java b/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
index bf2e456..90f1523 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
@@ -465,7 +465,8 @@
                     job.getSourceUid(), null, job.getBatteryName(),
                     FrameworkStatsLog.SCHEDULED_JOB_STATE_CHANGED__STATE__STARTED,
                     JobProtoEnums.INTERNAL_STOP_REASON_UNKNOWN,
-                    job.getStandbyBucket(), job.getJobId(),
+                    job.getStandbyBucket(),
+                    job.getLoggingJobId(),
                     job.hasChargingConstraint(),
                     job.hasBatteryNotLowConstraint(),
                     job.hasStorageNotLowConstraint(),
@@ -1521,7 +1522,8 @@
         FrameworkStatsLog.write_non_chained(FrameworkStatsLog.SCHEDULED_JOB_STATE_CHANGED,
                 completedJob.getSourceUid(), null, completedJob.getBatteryName(),
                 FrameworkStatsLog.SCHEDULED_JOB_STATE_CHANGED__STATE__FINISHED,
-                loggingInternalStopReason, completedJob.getStandbyBucket(), completedJob.getJobId(),
+                loggingInternalStopReason, completedJob.getStandbyBucket(),
+                completedJob.getLoggingJobId(),
                 completedJob.hasChargingConstraint(),
                 completedJob.hasBatteryNotLowConstraint(),
                 completedJob.hasStorageNotLowConstraint(),
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java
index b5d763c..a8b4c69 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java
@@ -231,6 +231,8 @@
     final String sourceTag;
     @Nullable
     private final String mNamespace;
+    /** An ID that can be used to uniquely identify the job when logging statsd metrics. */
+    private final long mLoggingJobId;
 
     final String tag;
 
@@ -568,6 +570,7 @@
         this.callingUid = callingUid;
         this.standbyBucket = standbyBucket;
         mNamespace = namespace;
+        mLoggingJobId = generateLoggingId(namespace, job.getId());
 
         int tempSourceUid = -1;
         if (sourceUserId != -1 && sourcePackageName != null) {
@@ -804,6 +807,13 @@
                 /*innerFlags=*/ 0, /* dynamicConstraints */ 0);
     }
 
+    private long generateLoggingId(@Nullable String namespace, int jobId) {
+        if (namespace == null) {
+            return jobId;
+        }
+        return ((long) namespace.hashCode()) << 31 | jobId;
+    }
+
     public void enqueueWorkLocked(JobWorkItem work) {
         if (pendingWork == null) {
             pendingWork = new ArrayList<>();
@@ -956,6 +966,11 @@
         return job.getId();
     }
 
+    /** Returns an ID that can be used to uniquely identify the job when logging statsd metrics. */
+    public long getLoggingJobId() {
+        return mLoggingJobId;
+    }
+
     public void printUniqueId(PrintWriter pw) {
         if (mNamespace != null) {
             pw.print(mNamespace);
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index 9eb9d66..9bd2970 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -3568,6 +3568,8 @@
     field public static final int ACCESSIBILITY_TITLE_CHANGED = 33554432; // 0x2000000
     field public static final int FLAG_SLIPPERY = 536870912; // 0x20000000
     field public CharSequence accessibilityTitle;
+    field public float preferredMaxDisplayRefreshRate;
+    field public float preferredMinDisplayRefreshRate;
     field public int privateFlags;
   }
 
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index 81258f1..6e0fc4f 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -1084,6 +1084,16 @@
         }
 
         /**
+         * Update the forced status bar appearance.
+         * @hide
+         */
+        @Override
+        public void updateStatusBarAppearance(int appearance) {
+            mTaskDescription.setStatusBarAppearance(appearance);
+            setTaskDescription(mTaskDescription);
+        }
+
+        /**
          * Update the forced navigation bar color.
          * @hide
          */
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index b5ee895..ff0f437 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -81,6 +81,7 @@
 import android.util.DisplayMetrics;
 import android.util.Singleton;
 import android.util.Size;
+import android.view.WindowInsetsController.Appearance;
 import android.window.TaskSnapshot;
 
 import com.android.internal.app.LocalePicker;
@@ -1553,6 +1554,8 @@
         private int mColorBackgroundFloating;
         private int mStatusBarColor;
         private int mNavigationBarColor;
+        @Appearance
+        private int mStatusBarAppearance;
         private boolean mEnsureStatusBarContrastWhenTransparent;
         private boolean mEnsureNavigationBarContrastWhenTransparent;
         private int mResizeMode;
@@ -1653,8 +1656,8 @@
                 final Icon icon = mIconRes == Resources.ID_NULL ? null :
                         Icon.createWithResource(ActivityThread.currentPackageName(), mIconRes);
                 return new TaskDescription(mLabel, icon, mPrimaryColor, mBackgroundColor,
-                        mStatusBarColor, mNavigationBarColor, false, false, RESIZE_MODE_RESIZEABLE,
-                        -1, -1, 0);
+                        mStatusBarColor, mNavigationBarColor, 0, false, false,
+                        RESIZE_MODE_RESIZEABLE, -1, -1, 0);
             }
         }
 
@@ -1672,7 +1675,7 @@
         @Deprecated
         public TaskDescription(String label, @DrawableRes int iconRes, int colorPrimary) {
             this(label, Icon.createWithResource(ActivityThread.currentPackageName(), iconRes),
-                    colorPrimary, 0, 0, 0, false, false, RESIZE_MODE_RESIZEABLE, -1, -1, 0);
+                    colorPrimary, 0, 0, 0, 0, false, false, RESIZE_MODE_RESIZEABLE, -1, -1, 0);
             if ((colorPrimary != 0) && (Color.alpha(colorPrimary) != 255)) {
                 throw new RuntimeException("A TaskDescription's primary color should be opaque");
             }
@@ -1690,7 +1693,7 @@
         @Deprecated
         public TaskDescription(String label, @DrawableRes int iconRes) {
             this(label, Icon.createWithResource(ActivityThread.currentPackageName(), iconRes),
-                    0, 0, 0, 0, false, false, RESIZE_MODE_RESIZEABLE, -1, -1, 0);
+                    0, 0, 0, 0, 0, false, false, RESIZE_MODE_RESIZEABLE, -1, -1, 0);
         }
 
         /**
@@ -1702,7 +1705,7 @@
          */
         @Deprecated
         public TaskDescription(String label) {
-            this(label, null, 0, 0, 0, 0, false, false, RESIZE_MODE_RESIZEABLE, -1, -1, 0);
+            this(label, null, 0, 0, 0, 0, 0, false, false, RESIZE_MODE_RESIZEABLE, -1, -1, 0);
         }
 
         /**
@@ -1712,7 +1715,7 @@
          */
         @Deprecated
         public TaskDescription() {
-            this(null, null, 0, 0, 0, 0, false, false, RESIZE_MODE_RESIZEABLE, -1, -1, 0);
+            this(null, null, 0, 0, 0, 0, 0, false, false, RESIZE_MODE_RESIZEABLE, -1, -1, 0);
         }
 
         /**
@@ -1728,7 +1731,7 @@
         @Deprecated
         public TaskDescription(String label, Bitmap icon, int colorPrimary) {
             this(label, icon != null ? Icon.createWithBitmap(icon) : null, colorPrimary, 0, 0, 0,
-                    false, false, RESIZE_MODE_RESIZEABLE, -1, -1, 0);
+                    0, false, false, RESIZE_MODE_RESIZEABLE, -1, -1, 0);
             if ((colorPrimary != 0) && (Color.alpha(colorPrimary) != 255)) {
                 throw new RuntimeException("A TaskDescription's primary color should be opaque");
             }
@@ -1744,14 +1747,15 @@
          */
         @Deprecated
         public TaskDescription(String label, Bitmap icon) {
-            this(label, icon != null ? Icon.createWithBitmap(icon) : null, 0, 0, 0, 0, false, false,
-                    RESIZE_MODE_RESIZEABLE, -1, -1, 0);
+            this(label, icon != null ? Icon.createWithBitmap(icon) : null, 0, 0, 0, 0, 0, false,
+                    false, RESIZE_MODE_RESIZEABLE, -1, -1, 0);
         }
 
         /** @hide */
         public TaskDescription(@Nullable String label, @Nullable Icon icon,
                 int colorPrimary, int colorBackground,
                 int statusBarColor, int navigationBarColor,
+                @Appearance int statusBarAppearance,
                 boolean ensureStatusBarContrastWhenTransparent,
                 boolean ensureNavigationBarContrastWhenTransparent, int resizeMode, int minWidth,
                 int minHeight, int colorBackgroundFloating) {
@@ -1761,6 +1765,7 @@
             mColorBackground = colorBackground;
             mStatusBarColor = statusBarColor;
             mNavigationBarColor = navigationBarColor;
+            mStatusBarAppearance = statusBarAppearance;
             mEnsureStatusBarContrastWhenTransparent = ensureStatusBarContrastWhenTransparent;
             mEnsureNavigationBarContrastWhenTransparent =
                     ensureNavigationBarContrastWhenTransparent;
@@ -1789,6 +1794,7 @@
             mColorBackground = other.mColorBackground;
             mStatusBarColor = other.mStatusBarColor;
             mNavigationBarColor = other.mNavigationBarColor;
+            mStatusBarAppearance = other.mStatusBarAppearance;
             mEnsureStatusBarContrastWhenTransparent = other.mEnsureStatusBarContrastWhenTransparent;
             mEnsureNavigationBarContrastWhenTransparent =
                     other.mEnsureNavigationBarContrastWhenTransparent;
@@ -1818,6 +1824,9 @@
             if (other.mNavigationBarColor != 0) {
                 mNavigationBarColor = other.mNavigationBarColor;
             }
+            if (other.mStatusBarAppearance != 0) {
+                mStatusBarAppearance = other.mStatusBarAppearance;
+            }
 
             mEnsureStatusBarContrastWhenTransparent = other.mEnsureStatusBarContrastWhenTransparent;
             mEnsureNavigationBarContrastWhenTransparent =
@@ -2089,6 +2098,14 @@
         /**
          * @hide
          */
+        @Appearance
+        public int getStatusBarAppearance() {
+            return mStatusBarAppearance;
+        }
+
+        /**
+         * @hide
+         */
         public void setEnsureStatusBarContrastWhenTransparent(
                 boolean ensureStatusBarContrastWhenTransparent) {
             mEnsureStatusBarContrastWhenTransparent = ensureStatusBarContrastWhenTransparent;
@@ -2097,6 +2114,13 @@
         /**
          * @hide
          */
+        public void setStatusBarAppearance(@Appearance int statusBarAppearance) {
+            mStatusBarAppearance = statusBarAppearance;
+        }
+
+        /**
+         * @hide
+         */
         public boolean getEnsureNavigationBarContrastWhenTransparent() {
             return mEnsureNavigationBarContrastWhenTransparent;
         }
@@ -2218,6 +2242,7 @@
             dest.writeInt(mColorBackground);
             dest.writeInt(mStatusBarColor);
             dest.writeInt(mNavigationBarColor);
+            dest.writeInt(mStatusBarAppearance);
             dest.writeBoolean(mEnsureStatusBarContrastWhenTransparent);
             dest.writeBoolean(mEnsureNavigationBarContrastWhenTransparent);
             dest.writeInt(mResizeMode);
@@ -2241,6 +2266,7 @@
             mColorBackground = source.readInt();
             mStatusBarColor = source.readInt();
             mNavigationBarColor = source.readInt();
+            mStatusBarAppearance = source.readInt();
             mEnsureStatusBarContrastWhenTransparent = source.readBoolean();
             mEnsureNavigationBarContrastWhenTransparent = source.readBoolean();
             mResizeMode = source.readInt();
@@ -2289,6 +2315,7 @@
                     && mColorBackground == other.mColorBackground
                     && mStatusBarColor == other.mStatusBarColor
                     && mNavigationBarColor == other.mNavigationBarColor
+                    && mStatusBarAppearance == other.mStatusBarAppearance
                     && mEnsureStatusBarContrastWhenTransparent
                             == other.mEnsureStatusBarContrastWhenTransparent
                     && mEnsureNavigationBarContrastWhenTransparent
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 783e7d3..b2a9230 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -581,6 +581,7 @@
      *     <li>{@link #LOCK_TASK_FEATURE_HOME}</li>
      *     <li>{@link #LOCK_TASK_FEATURE_GLOBAL_ACTIONS}</li>
      *     <li>{@link #LOCK_TASK_FEATURE_NOTIFICATIONS}</li>
+     *     <li>{@link #LOCK_TASK_FEATURE_BLOCK_ACTIVITY_START_IN_TASK}</li>
      * </ul>
      * <li>{@link #getLockTaskFeatures(ComponentName)}</li>
      * <li>{@link #setLockTaskPackages(ComponentName, String[])}</li>
diff --git a/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
index e23bbc6..d3bde4b 100644
--- a/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
+++ b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
@@ -20,6 +20,7 @@
 
 import android.annotation.NonNull;
 import android.content.Context;
+import android.graphics.ImageFormat;
 import android.hardware.ICameraService;
 import android.hardware.camera2.CameraAccessException;
 import android.hardware.camera2.CameraCaptureSession;
@@ -1478,6 +1479,12 @@
             }
         }
 
+        // Allow RAW formats, even when not advertised.
+        if (inputFormat == ImageFormat.RAW_PRIVATE || inputFormat == ImageFormat.RAW10
+                || inputFormat == ImageFormat.RAW12 || inputFormat == ImageFormat.RAW_SENSOR) {
+            return true;
+        }
+
         if (validFormat == false) {
             return false;
         }
diff --git a/core/java/android/net/VpnManager.java b/core/java/android/net/VpnManager.java
index 2e64a74..ff47f3f 100644
--- a/core/java/android/net/VpnManager.java
+++ b/core/java/android/net/VpnManager.java
@@ -444,7 +444,7 @@
      * Retrieve the VpnProfileState for the profile provisioned by the calling package.
      *
      * @return the VpnProfileState with current information, or null if there was no profile
-     *         provisioned by the calling package.
+     *         provisioned and started by the calling package.
      */
     @Nullable
     public VpnProfileState getProvisionedVpnProfileState() {
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index 7d68b44..84f1880 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -736,7 +736,7 @@
      * The default value is <code>false</code>.
      *
      * <p>Holders of the permission
-     * {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_USERS}
+     * {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_MODIFY_USERS}
      * can set this restriction using the DevicePolicyManager APIs mentioned below.
      *
      * <p>Key for user restrictions.
@@ -941,7 +941,7 @@
      * this restriction will be set as a base restriction which cannot be removed by any admin.
      *
      * <p>Holders of the permission
-     * {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_USERS}
+     * {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_MODIFY_USERS}
      * can set this restriction using the DevicePolicyManager APIs mentioned below.
      *
      * <p>Key for user restrictions.
@@ -1451,7 +1451,7 @@
      * affected. The default value is <code>false</code>.
      *
      * <p>Holders of the permission
-     * {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_USERS}
+     * {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_MODIFY_USERS}
      * can set this restriction using the DevicePolicyManager APIs mentioned below.
      *
      * <p>Key for user restrictions.
@@ -1597,7 +1597,7 @@
      * set.
      *
      * <p>Holders of the permission
-     * {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_USERS}
+     * {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_MODIFY_USERS}
      * can set this restriction using the DevicePolicyManager APIs mentioned below.
      *
      * <p>The default value is <code>false</code>.
diff --git a/core/java/android/service/contentcapture/ContentCaptureService.java b/core/java/android/service/contentcapture/ContentCaptureService.java
index 737c95f..f0140e1 100644
--- a/core/java/android/service/contentcapture/ContentCaptureService.java
+++ b/core/java/android/service/contentcapture/ContentCaptureService.java
@@ -88,6 +88,18 @@
             "android.service.contentcapture.ContentCaptureService";
 
     /**
+     * The {@link Intent} that must be declared as handled by the protection service.
+     *
+     * <p>To be supported, the service must also require the {@link
+     * android.Manifest.permission#BIND_CONTENT_CAPTURE_SERVICE} permission so that other
+     * applications can not abuse it.
+     *
+     * @hide
+     */
+    public static final String PROTECTION_SERVICE_INTERFACE =
+            "android.service.contentcapture.ContentProtectionService";
+
+    /**
      * Name under which a ContentCaptureService component publishes information about itself.
      *
      * <p>This meta-data should reference an XML resource containing a
diff --git a/core/java/android/view/Window.java b/core/java/android/view/Window.java
index 7596459..2f04b0c 100644
--- a/core/java/android/view/Window.java
+++ b/core/java/android/view/Window.java
@@ -658,6 +658,12 @@
         void updateStatusBarColor(int color);
 
         /**
+         * Update the status bar appearance.
+         */
+
+        void updateStatusBarAppearance(int appearance);
+
+        /**
          * Update the navigation bar color to a forced one.
          */
         void updateNavigationBarColor(int color);
@@ -1039,6 +1045,9 @@
         if (mDecorCallback != null) {
             mDecorCallback.onSystemBarAppearanceChanged(appearance);
         }
+        if (mWindowControllerCallback != null) {
+            mWindowControllerCallback.updateStatusBarAppearance(appearance);
+        }
     }
 
     /** @hide */
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java
index d40c032..adf772b 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -3763,6 +3763,7 @@
          * This value is ignored if {@link #preferredDisplayModeId} is set.
          * @hide
          */
+        @TestApi
         public float preferredMinDisplayRefreshRate;
 
         /**
@@ -3771,6 +3772,7 @@
          * This value is ignored if {@link #preferredDisplayModeId} is set.
          * @hide
          */
+        @TestApi
         public float preferredMaxDisplayRefreshRate;
 
         /** Indicates whether this window wants the HDR conversion is disabled. */
diff --git a/core/java/android/view/contentcapture/ContentCaptureManager.java b/core/java/android/view/contentcapture/ContentCaptureManager.java
index c37e311..c9afdc0 100644
--- a/core/java/android/view/contentcapture/ContentCaptureManager.java
+++ b/core/java/android/view/contentcapture/ContentCaptureManager.java
@@ -52,6 +52,7 @@
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.RingBuffer;
 import com.android.internal.util.SyncResultReceiver;
 
 import java.io.PrintWriter;
@@ -446,6 +447,9 @@
     @Nullable // set on-demand by addDumpable()
     private Dumper mDumpable;
 
+    // Created here in order to live across activity and session changes
+    @Nullable private final RingBuffer<ContentCaptureEvent> mContentProtectionEventBuffer;
+
     /** @hide */
     public interface ContentCaptureClient {
         /**
@@ -456,12 +460,15 @@
     }
 
     /** @hide */
-    static class StrippedContext {
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+    public static class StrippedContext {
         @NonNull final String mPackageName;
         @NonNull final String mContext;
         final @UserIdInt int mUserId;
 
-        private StrippedContext(@NonNull Context context) {
+        /** @hide */
+        @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+        public StrippedContext(@NonNull Context context) {
             mPackageName = context.getPackageName();
             mContext = context.toString();
             mUserId = context.getUserId();
@@ -502,6 +509,16 @@
         mHandler = Handler.createAsync(Looper.getMainLooper());
 
         mDataShareAdapterResourceManager = new LocalDataShareAdapterResourceManager();
+
+        if (mOptions.contentProtectionOptions.enableReceiver
+                && mOptions.contentProtectionOptions.bufferSize > 0) {
+            mContentProtectionEventBuffer =
+                    new RingBuffer(
+                            ContentCaptureEvent.class,
+                            mOptions.contentProtectionOptions.bufferSize);
+        } else {
+            mContentProtectionEventBuffer = null;
+        }
     }
 
     /**
@@ -870,6 +887,13 @@
         activity.addDumpable(mDumpable);
     }
 
+    /** @hide */
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+    @Nullable
+    public RingBuffer<ContentCaptureEvent> getContentProtectionEventBuffer() {
+        return mContentProtectionEventBuffer;
+    }
+
     // NOTE: ContentCaptureManager cannot implement it directly as it would be exposed as public API
     private final class Dumper implements Dumpable {
         @Override
diff --git a/core/java/android/view/contentcapture/OWNERS b/core/java/android/view/contentcapture/OWNERS
index d1eda96..a958bbe 100644
--- a/core/java/android/view/contentcapture/OWNERS
+++ b/core/java/android/view/contentcapture/OWNERS
@@ -6,4 +6,5 @@
 lpeter@google.com
 tymtsai@google.com
 hackz@google.com
-volnov@google.com
\ No newline at end of file
+shivanker@google.com
+volnov@google.com
diff --git a/core/java/android/view/contentprotection/ContentProtectionEventProcessor.java b/core/java/android/view/contentprotection/ContentProtectionEventProcessor.java
new file mode 100644
index 0000000..0840b66
--- /dev/null
+++ b/core/java/android/view/contentprotection/ContentProtectionEventProcessor.java
@@ -0,0 +1,228 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view.contentprotection;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.UiThread;
+import android.content.pm.ParceledListSlice;
+import android.os.Handler;
+import android.text.InputType;
+import android.util.Log;
+import android.view.contentcapture.ContentCaptureEvent;
+import android.view.contentcapture.IContentCaptureManager;
+import android.view.contentcapture.ViewNode;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.RingBuffer;
+
+import java.time.Duration;
+import java.time.Instant;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Main entry point for processing {@link ContentCaptureEvent} for the content protection flow.
+ *
+ * @hide
+ */
+public class ContentProtectionEventProcessor {
+
+    private static final String TAG = "ContentProtectionEventProcessor";
+
+    private static final List<Integer> PASSWORD_FIELD_INPUT_TYPES =
+            Collections.unmodifiableList(
+                    Arrays.asList(
+                            InputType.TYPE_NUMBER_VARIATION_PASSWORD,
+                            InputType.TYPE_TEXT_VARIATION_PASSWORD,
+                            InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD,
+                            InputType.TYPE_TEXT_VARIATION_WEB_PASSWORD));
+
+    private static final List<String> PASSWORD_TEXTS =
+            Collections.unmodifiableList(
+                    Arrays.asList("password", "pass word", "code", "pin", "credential"));
+
+    private static final List<String> ADDITIONAL_SUSPICIOUS_TEXTS =
+            Collections.unmodifiableList(
+                    Arrays.asList("user", "mail", "phone", "number", "login", "log in", "sign in"));
+
+    private static final Duration MIN_DURATION_BETWEEN_FLUSHING = Duration.ofSeconds(3);
+
+    private static final String ANDROID_CLASS_NAME_PREFIX = "android.";
+
+    private static final Set<Integer> EVENT_TYPES_TO_STORE =
+            Collections.unmodifiableSet(
+                    new HashSet<>(
+                            Arrays.asList(
+                                    ContentCaptureEvent.TYPE_VIEW_APPEARED,
+                                    ContentCaptureEvent.TYPE_VIEW_DISAPPEARED,
+                                    ContentCaptureEvent.TYPE_VIEW_TEXT_CHANGED)));
+
+    @NonNull private final RingBuffer<ContentCaptureEvent> mEventBuffer;
+
+    @NonNull private final Handler mHandler;
+
+    @NonNull private final IContentCaptureManager mContentCaptureManager;
+
+    @NonNull private final String mPackageName;
+
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+    public boolean mPasswordFieldDetected = false;
+
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+    public boolean mSuspiciousTextDetected = false;
+
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+    @Nullable
+    public Instant mLastFlushTime;
+
+    public ContentProtectionEventProcessor(
+            @NonNull RingBuffer<ContentCaptureEvent> eventBuffer,
+            @NonNull Handler handler,
+            @NonNull IContentCaptureManager contentCaptureManager,
+            @NonNull String packageName) {
+        mEventBuffer = eventBuffer;
+        mHandler = handler;
+        mContentCaptureManager = contentCaptureManager;
+        mPackageName = packageName;
+    }
+
+    /** Main entry point for {@link ContentCaptureEvent} processing. */
+    @UiThread
+    public void processEvent(@NonNull ContentCaptureEvent event) {
+        if (EVENT_TYPES_TO_STORE.contains(event.getType())) {
+            storeEvent(event);
+        }
+        if (event.getType() == ContentCaptureEvent.TYPE_VIEW_APPEARED) {
+            processViewAppearedEvent(event);
+        }
+    }
+
+    @UiThread
+    private void storeEvent(@NonNull ContentCaptureEvent event) {
+        // Ensure receiver gets the package name which might not be set
+        ViewNode viewNode = (event.getViewNode() != null) ? event.getViewNode() : new ViewNode();
+        viewNode.setTextIdEntry(mPackageName);
+        event.setViewNode(viewNode);
+        mEventBuffer.append(event);
+    }
+
+    @UiThread
+    private void processViewAppearedEvent(@NonNull ContentCaptureEvent event) {
+        mPasswordFieldDetected |= isPasswordField(event);
+        mSuspiciousTextDetected |= isSuspiciousText(event);
+        if (mPasswordFieldDetected && mSuspiciousTextDetected) {
+            loginDetected();
+        }
+    }
+
+    @UiThread
+    private void loginDetected() {
+        if (mLastFlushTime == null
+                || Instant.now().isAfter(mLastFlushTime.plus(MIN_DURATION_BETWEEN_FLUSHING))) {
+            flush();
+        }
+        mPasswordFieldDetected = false;
+        mSuspiciousTextDetected = false;
+    }
+
+    @UiThread
+    private void flush() {
+        mLastFlushTime = Instant.now();
+
+        // Note the thread annotations, do not move clearEvents to mHandler
+        ParceledListSlice<ContentCaptureEvent> events = clearEvents();
+        mHandler.post(() -> handlerOnLoginDetected(events));
+    }
+
+    @UiThread
+    @NonNull
+    private ParceledListSlice<ContentCaptureEvent> clearEvents() {
+        List<ContentCaptureEvent> events = Arrays.asList(mEventBuffer.toArray());
+        mEventBuffer.clear();
+        return new ParceledListSlice<>(events);
+    }
+
+    private void handlerOnLoginDetected(@NonNull ParceledListSlice<ContentCaptureEvent> events) {
+        try {
+            // TODO(b/275732576): Call mContentCaptureManager
+        } catch (Exception ex) {
+            Log.e(TAG, "Failed to flush events for: " + mPackageName, ex);
+        }
+    }
+
+    private boolean isPasswordField(@NonNull ContentCaptureEvent event) {
+        return isPasswordField(event.getViewNode());
+    }
+
+    private boolean isPasswordField(@Nullable ViewNode viewNode) {
+        if (viewNode == null) {
+            return false;
+        }
+        return isAndroidPasswordField(viewNode) || isWebViewPasswordField(viewNode);
+    }
+
+    private boolean isAndroidPasswordField(@NonNull ViewNode viewNode) {
+        if (!isAndroidViewNode(viewNode)) {
+            return false;
+        }
+        int inputType = viewNode.getInputType();
+        return PASSWORD_FIELD_INPUT_TYPES.stream()
+                .anyMatch(passwordInputType -> (inputType & passwordInputType) != 0);
+    }
+
+    private boolean isWebViewPasswordField(@NonNull ViewNode viewNode) {
+        if (viewNode.getClassName() != null) {
+            return false;
+        }
+        return isPasswordText(ContentProtectionUtils.getViewNodeText(viewNode));
+    }
+
+    private boolean isAndroidViewNode(@NonNull ViewNode viewNode) {
+        String className = viewNode.getClassName();
+        return className != null && className.startsWith(ANDROID_CLASS_NAME_PREFIX);
+    }
+
+    private boolean isSuspiciousText(@NonNull ContentCaptureEvent event) {
+        return isSuspiciousText(ContentProtectionUtils.getEventText(event))
+                || isSuspiciousText(ContentProtectionUtils.getViewNodeText(event));
+    }
+
+    private boolean isSuspiciousText(@Nullable String text) {
+        if (text == null) {
+            return false;
+        }
+        if (isPasswordText(text)) {
+            return true;
+        }
+        String lowerCaseText = text.toLowerCase();
+        return ADDITIONAL_SUSPICIOUS_TEXTS.stream()
+                .anyMatch(suspiciousText -> lowerCaseText.contains(suspiciousText));
+    }
+
+    private boolean isPasswordText(@Nullable String text) {
+        if (text == null) {
+            return false;
+        }
+        String lowerCaseText = text.toLowerCase();
+        return PASSWORD_TEXTS.stream()
+                .anyMatch(passwordText -> lowerCaseText.contains(passwordText));
+    }
+}
diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java
index 7f96266..3be8c3d 100644
--- a/core/java/android/widget/RemoteViews.java
+++ b/core/java/android/widget/RemoteViews.java
@@ -232,6 +232,7 @@
     private static final int NIGHT_MODE_REFLECTION_ACTION_TAG = 30;
     private static final int SET_REMOTE_COLLECTION_ITEMS_ADAPTER_TAG = 31;
     private static final int ATTRIBUTE_REFLECTION_ACTION_TAG = 32;
+    private static final int SET_REMOTE_ADAPTER_TAG = 33;
 
     /** @hide **/
     @IntDef(prefix = "MARGIN_", value = {
@@ -960,6 +961,11 @@
             return SET_REMOTE_VIEW_ADAPTER_LIST_TAG;
         }
 
+        @Override
+        public String getUniqueKey() {
+            return (SET_REMOTE_ADAPTER_TAG + "_" + viewId);
+        }
+
         int viewTypeCount;
         ArrayList<RemoteViews> list;
     }
@@ -1082,6 +1088,11 @@
         public int getActionTag() {
             return SET_REMOTE_COLLECTION_ITEMS_ADAPTER_TAG;
         }
+
+        @Override
+        public String getUniqueKey() {
+            return (SET_REMOTE_ADAPTER_TAG + "_" + viewId);
+        }
     }
 
     private class SetRemoteViewsAdapterIntent extends Action {
diff --git a/core/java/android/window/TransitionInfo.java b/core/java/android/window/TransitionInfo.java
index 8c05130..a92ca8a 100644
--- a/core/java/android/window/TransitionInfo.java
+++ b/core/java/android/window/TransitionInfo.java
@@ -424,8 +424,8 @@
             case TRANSIT_NONE: return "NONE";
             case TRANSIT_OPEN: return "OPEN";
             case TRANSIT_CLOSE: return "CLOSE";
-            case TRANSIT_TO_FRONT: return "SHOW";
-            case TRANSIT_TO_BACK: return "HIDE";
+            case TRANSIT_TO_FRONT: return "TO_FRONT";
+            case TRANSIT_TO_BACK: return "TO_BACK";
             case TRANSIT_CHANGE: return "CHANGE";
             default: return "<unknown:" + mode + ">";
         }
diff --git a/core/java/com/android/internal/accessibility/common/MagnificationConstants.java b/core/java/com/android/internal/accessibility/common/MagnificationConstants.java
index 94c230b..95c3419 100644
--- a/core/java/com/android/internal/accessibility/common/MagnificationConstants.java
+++ b/core/java/com/android/internal/accessibility/common/MagnificationConstants.java
@@ -27,4 +27,10 @@
      * the min value, there will be no obvious magnification effect.
      */
     public static final float PERSISTED_SCALE_MIN_VALUE = 1.3f;
+
+    /** Minimum supported value for magnification scale. */
+    public static final float SCALE_MIN_VALUE = 1.0f;
+
+    /** Maximum supported value for magnification scale. */
+    public static final float SCALE_MAX_VALUE = 8.0f;
 }
diff --git a/core/java/com/android/internal/os/anr/AnrLatencyTracker.java b/core/java/com/android/internal/os/anr/AnrLatencyTracker.java
index 3ba4ea5..80d753c 100644
--- a/core/java/com/android/internal/os/anr/AnrLatencyTracker.java
+++ b/core/java/com/android/internal/os/anr/AnrLatencyTracker.java
@@ -120,6 +120,10 @@
 
     private long mPreDumpIfLockTooSlowStartUptime;
     private long mPreDumpIfLockTooSlowDuration = 0;
+    private long mNotifyAppUnresponsiveStartUptime;
+    private long mNotifyAppUnresponsiveDuration = 0;
+    private long mNotifyWindowUnresponsiveStartUptime;
+    private long mNotifyWindowUnresponsiveDuration = 0;
 
     private final int mAnrRecordPlacedOnQueueCookie =
             sNextAnrRecordPlacedOnQueueCookieGenerator.incrementAndGet();
@@ -425,11 +429,36 @@
         anrSkipped("dumpStackTraces");
     }
 
+    /** Records the start of AnrController#notifyAppUnresponsive. */
+    public void notifyAppUnresponsiveStarted() {
+        mNotifyAppUnresponsiveStartUptime = getUptimeMillis();
+        Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "notifyAppUnresponsive()");
+    }
+
+    /** Records the end of AnrController#notifyAppUnresponsive. */
+    public void notifyAppUnresponsiveEnded() {
+        mNotifyAppUnresponsiveDuration = getUptimeMillis() - mNotifyAppUnresponsiveStartUptime;
+        Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
+    }
+
+    /** Records the start of AnrController#notifyWindowUnresponsive. */
+    public void notifyWindowUnresponsiveStarted() {
+        mNotifyWindowUnresponsiveStartUptime = getUptimeMillis();
+        Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "notifyWindowUnresponsive()");
+    }
+
+    /** Records the end of AnrController#notifyWindowUnresponsive. */
+    public void notifyWindowUnresponsiveEnded() {
+        mNotifyWindowUnresponsiveDuration = getUptimeMillis()
+                - mNotifyWindowUnresponsiveStartUptime;
+        Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
+    }
+
     /**
      * Returns latency data as a comma separated value string for inclusion in ANR report.
      */
     public String dumpAsCommaSeparatedArrayWithHeader() {
-        return "DurationsV4: " + mAnrTriggerUptime
+        return "DurationsV5: " + mAnrTriggerUptime
                 /* triggering_to_app_not_responding_duration = */
                 + "," + (mAppNotRespondingStartUptime -  mAnrTriggerUptime)
                 /* app_not_responding_duration = */
@@ -480,6 +509,10 @@
                 + "," + (mCopyingFirstPidSucceeded ? 1 : 0)
                 /* preDumpIfLockTooSlow_duration = */
                 + "," + mPreDumpIfLockTooSlowDuration
+                /* notifyAppUnresponsive_duration = */
+                + "," + mNotifyAppUnresponsiveDuration
+                /* notifyWindowUnresponsive_duration = */
+                + "," + mNotifyWindowUnresponsiveDuration
                 + "\n\n";
 
     }
diff --git a/core/proto/android/companion/telecom.proto b/core/proto/android/companion/telecom.proto
index 700baa1..7fe24670 100644
--- a/core/proto/android/companion/telecom.proto
+++ b/core/proto/android/companion/telecom.proto
@@ -45,10 +45,18 @@
       AUDIO_PROCESSING = 5;
       RINGING_SIMULATED = 6;
       DISCONNECTED = 7;
+      DIALING = 8;
     }
     Status status = 3;
 
     repeated Control controls = 4;
+
+    enum Direction {
+      UNKNOWN_DIRECTION = 0;
+      INCOMING = 1;
+      OUTGOING = 2;
+    }
+    Direction direction = 5;
   }
 
   message Request {
diff --git a/core/tests/coretests/src/android/app/activity/ActivityManagerTest.java b/core/tests/coretests/src/android/app/activity/ActivityManagerTest.java
index 81eb213..5ac99db 100644
--- a/core/tests/coretests/src/android/app/activity/ActivityManagerTest.java
+++ b/core/tests/coretests/src/android/app/activity/ActivityManagerTest.java
@@ -128,6 +128,7 @@
                 0x222222,                // colorBackground
                 0x333333,                // statusBarColor
                 0x444444,                // navigationBarColor
+                0,                       // statusBarAppearance
                 true,                    // ensureStatusBarContrastWhenTransparent
                 true,                    // ensureNavigationBarContrastWhenTransparent
                 RESIZE_MODE_RESIZEABLE,  // resizeMode
@@ -152,6 +153,7 @@
                 0x222222,                  // colorBackground
                 0x333333,                  // statusBarColor
                 0x444444,                  // navigationBarColor
+                0,                         // statusBarAppearance
                 false,                     // ensureStatusBarContrastWhenTransparent
                 false,                     // ensureNavigationBarContrastWhenTransparent
                 RESIZE_MODE_UNRESIZEABLE,  // resizeMode
@@ -167,6 +169,7 @@
                 0x2222222,               // colorBackground
                 0x3333332,               // statusBarColor
                 0x4444442,               // navigationBarColor
+                0,                       // statusBarAppearance
                 true,                    // ensureStatusBarContrastWhenTransparent
                 true,                    // ensureNavigationBarContrastWhenTransparent
                 RESIZE_MODE_RESIZEABLE,  // resizeMode
@@ -197,6 +200,7 @@
                 0x222222,                  // colorBackground
                 0x333333,                  // statusBarColor
                 0x444444,                  // navigationBarColor
+                0,                         // statusBarAppearance
                 false,                     // ensureStatusBarContrastWhenTransparent
                 false,                     // ensureNavigationBarContrastWhenTransparent
                 RESIZE_MODE_UNRESIZEABLE,  // resizeMode
@@ -219,6 +223,7 @@
                 0x222222,                  // colorBackground
                 0x333333,                  // statusBarColor
                 0x444444,                  // navigationBarColor
+                0,                         // statusBarAppearance
                 false,                     // ensureStatusBarContrastWhenTransparent
                 false,                     // ensureNavigationBarContrastWhenTransparent
                 RESIZE_MODE_UNRESIZEABLE,  // resizeMode
@@ -250,6 +255,7 @@
             assertEquals(td1.getBackgroundColor(), td2.getBackgroundColor());
             assertEquals(td1.getStatusBarColor(), td2.getStatusBarColor());
             assertEquals(td1.getNavigationBarColor(), td2.getNavigationBarColor());
+            assertEquals(td1.getStatusBarAppearance(), td2.getStatusBarAppearance());
             assertEquals(td1.getResizeMode(), td2.getResizeMode());
             assertEquals(td1.getMinWidth(), td2.getMinWidth());
             assertEquals(td1.getMinHeight(), td2.getMinHeight());
diff --git a/core/tests/coretests/src/android/view/contentcapture/ContentCaptureManagerTest.java b/core/tests/coretests/src/android/view/contentcapture/ContentCaptureManagerTest.java
index 17ed4c4..101f7c2 100644
--- a/core/tests/coretests/src/android/view/contentcapture/ContentCaptureManagerTest.java
+++ b/core/tests/coretests/src/android/view/contentcapture/ContentCaptureManagerTest.java
@@ -15,14 +15,17 @@
  */
 package android.view.contentcapture;
 
+import static android.view.contentcapture.ContentCaptureEvent.TYPE_SESSION_STARTED;
+
 import static com.google.common.truth.Truth.assertThat;
 
-import static org.mockito.Mockito.mock;
 import static org.testng.Assert.assertThrows;
 
 import android.content.ContentCaptureOptions;
 import android.content.Context;
 
+import com.android.internal.util.RingBuffer;
+
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
@@ -37,9 +40,15 @@
 @RunWith(MockitoJUnitRunner.class)
 public class ContentCaptureManagerTest {
 
+    private static final int BUFFER_SIZE = 100;
+
+    private static final ContentCaptureOptions EMPTY_OPTIONS = new ContentCaptureOptions(null);
+
     @Mock
     private Context mMockContext;
 
+    @Mock private IContentCaptureManager mMockContentCaptureManager;
+
     @Test
     public void testConstructor_invalidParametersThrowsException() {
         assertThrows(NullPointerException.class,
@@ -48,11 +57,65 @@
     }
 
     @Test
+    public void testConstructor_contentProtection_default_bufferNotCreated() {
+        ContentCaptureManager manager =
+                new ContentCaptureManager(mMockContext, mMockContentCaptureManager, EMPTY_OPTIONS);
+
+        assertThat(manager.getContentProtectionEventBuffer()).isNull();
+    }
+
+    @Test
+    public void testConstructor_contentProtection_disabled_bufferNotCreated() {
+        ContentCaptureOptions options =
+                createOptions(
+                        new ContentCaptureOptions.ContentProtectionOptions(
+                                /* enableReceiver= */ false, BUFFER_SIZE));
+
+        ContentCaptureManager manager =
+                new ContentCaptureManager(mMockContext, mMockContentCaptureManager, options);
+
+        assertThat(manager.getContentProtectionEventBuffer()).isNull();
+    }
+
+    @Test
+    public void testConstructor_contentProtection_invalidBufferSize_bufferNotCreated() {
+        ContentCaptureOptions options =
+                createOptions(
+                        new ContentCaptureOptions.ContentProtectionOptions(
+                                /* enableReceiver= */ true, /* bufferSize= */ 0));
+
+        ContentCaptureManager manager =
+                new ContentCaptureManager(mMockContext, mMockContentCaptureManager, options);
+
+        assertThat(manager.getContentProtectionEventBuffer()).isNull();
+    }
+
+    @Test
+    public void testConstructor_contentProtection_enabled_bufferCreated() {
+        ContentCaptureOptions options =
+                createOptions(
+                        new ContentCaptureOptions.ContentProtectionOptions(
+                                /* enableReceiver= */ true, BUFFER_SIZE));
+
+        ContentCaptureManager manager =
+                new ContentCaptureManager(mMockContext, mMockContentCaptureManager, options);
+        RingBuffer<ContentCaptureEvent> buffer = manager.getContentProtectionEventBuffer();
+
+        assertThat(buffer).isNotNull();
+        ContentCaptureEvent[] expected = new ContentCaptureEvent[BUFFER_SIZE];
+        int offset = 3;
+        for (int i = 0; i < BUFFER_SIZE + offset; i++) {
+            ContentCaptureEvent event = new ContentCaptureEvent(i, TYPE_SESSION_STARTED);
+            buffer.append(event);
+            expected[(i + BUFFER_SIZE - offset) % BUFFER_SIZE] = event;
+        }
+        assertThat(buffer.toArray()).isEqualTo(expected);
+    }
+
+    @Test
     public void testRemoveData_invalidParametersThrowsException() {
-        final IContentCaptureManager mockService = mock(IContentCaptureManager.class);
-        final ContentCaptureOptions options = new ContentCaptureOptions(null);
         final ContentCaptureManager manager =
-                new ContentCaptureManager(mMockContext, mockService, options);
+                new ContentCaptureManager(mMockContext, mMockContentCaptureManager, EMPTY_OPTIONS);
 
         assertThrows(NullPointerException.class, () -> manager.removeData(null));
     }
@@ -60,10 +123,8 @@
     @Test
     @SuppressWarnings("GuardedBy")
     public void testFlushViewTreeAppearingEventDisabled_setAndGet() {
-        final IContentCaptureManager mockService = mock(IContentCaptureManager.class);
-        final ContentCaptureOptions options = new ContentCaptureOptions(null);
         final ContentCaptureManager manager =
-                new ContentCaptureManager(mMockContext, mockService, options);
+                new ContentCaptureManager(mMockContext, mMockContentCaptureManager, EMPTY_OPTIONS);
 
         assertThat(manager.getFlushViewTreeAppearingEventDisabled()).isFalse();
         manager.setFlushViewTreeAppearingEventDisabled(true);
@@ -71,4 +132,18 @@
         manager.setFlushViewTreeAppearingEventDisabled(false);
         assertThat(manager.getFlushViewTreeAppearingEventDisabled()).isFalse();
     }
+
+    private ContentCaptureOptions createOptions(
+            ContentCaptureOptions.ContentProtectionOptions contentProtectionOptions) {
+        return new ContentCaptureOptions(
+                /* loggingLevel= */ 0,
+                /* maxBufferSize= */ 0,
+                /* idleFlushingFrequencyMs= */ 0,
+                /* textChangeFlushingFrequencyMs= */ 0,
+                /* logHistorySize= */ 0,
+                /* disableFlushForViewTreeAppearing= */ false,
+                /* enableReceiver= */ true,
+                contentProtectionOptions,
+                /* whitelistedComponents= */ null);
+    }
 }
diff --git a/core/tests/coretests/src/android/view/contentprotection/ContentProtectionEventProcessorTest.java b/core/tests/coretests/src/android/view/contentprotection/ContentProtectionEventProcessorTest.java
new file mode 100644
index 0000000..fd5627d
--- /dev/null
+++ b/core/tests/coretests/src/android/view/contentprotection/ContentProtectionEventProcessorTest.java
@@ -0,0 +1,477 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view.contentprotection;
+
+import static android.view.contentcapture.ContentCaptureEvent.TYPE_VIEW_APPEARED;
+import static android.view.contentcapture.ContentCaptureEvent.TYPE_VIEW_DISAPPEARED;
+import static android.view.contentcapture.ContentCaptureEvent.TYPE_VIEW_TEXT_CHANGED;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.when;
+
+import android.annotation.Nullable;
+import android.content.Context;
+import android.os.Handler;
+import android.os.Looper;
+import android.text.InputType;
+import android.view.View;
+import android.view.contentcapture.ContentCaptureEvent;
+import android.view.contentcapture.IContentCaptureManager;
+import android.view.contentcapture.ViewNode;
+import android.view.contentcapture.ViewNode.ViewStructureImpl;
+
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import com.android.internal.util.RingBuffer;
+
+import com.google.common.collect.ImmutableSet;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+import java.time.Instant;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Test for {@link ContentProtectionEventProcessor}.
+ *
+ * <p>Run with: {@code atest
+ * FrameworksCoreTests:android.view.contentprotection.ContentProtectionEventProcessorTest}
+ */
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class ContentProtectionEventProcessorTest {
+
+    private static final String PACKAGE_NAME = "com.test.package.name";
+
+    private static final String ANDROID_CLASS_NAME = "android.test.some.class.name";
+
+    private static final String PASSWORD_TEXT = "ENTER PASSWORD HERE";
+
+    private static final String SUSPICIOUS_TEXT = "PLEASE SIGN IN";
+
+    private static final String SAFE_TEXT = "SAFE TEXT";
+
+    private static final ContentCaptureEvent PROCESS_EVENT = createProcessEvent();
+
+    private static final Set<Integer> EVENT_TYPES_TO_STORE =
+            ImmutableSet.of(TYPE_VIEW_APPEARED, TYPE_VIEW_DISAPPEARED, TYPE_VIEW_TEXT_CHANGED);
+
+    @Rule public final MockitoRule mMockitoRule = MockitoJUnit.rule();
+
+    @Mock private RingBuffer<ContentCaptureEvent> mMockEventBuffer;
+
+    @Mock private IContentCaptureManager mMockContentCaptureManager;
+
+    private final Context mContext = ApplicationProvider.getApplicationContext();
+
+    private ContentProtectionEventProcessor mContentProtectionEventProcessor;
+
+    @Before
+    public void setup() {
+        mContentProtectionEventProcessor =
+                new ContentProtectionEventProcessor(
+                        mMockEventBuffer,
+                        new Handler(Looper.getMainLooper()),
+                        mMockContentCaptureManager,
+                        PACKAGE_NAME);
+    }
+
+    @Test
+    public void processEvent_buffer_storesOnlySubsetOfEventTypes() {
+        List<ContentCaptureEvent> expectedEvents = new ArrayList<>();
+        for (int type = -100; type <= 100; type++) {
+            ContentCaptureEvent event = createEvent(type);
+            if (EVENT_TYPES_TO_STORE.contains(type)) {
+                expectedEvents.add(event);
+            }
+
+            mContentProtectionEventProcessor.processEvent(event);
+        }
+
+        assertThat(expectedEvents).hasSize(EVENT_TYPES_TO_STORE.size());
+        expectedEvents.forEach((expectedEvent) -> verify(mMockEventBuffer).append(expectedEvent));
+        verifyNoMoreInteractions(mMockEventBuffer);
+    }
+
+    @Test
+    public void processEvent_buffer_setsTextIdEntry_withoutExistingViewNode() {
+        ContentCaptureEvent event = createStoreEvent();
+
+        mContentProtectionEventProcessor.processEvent(event);
+
+        assertThat(event.getViewNode()).isNotNull();
+        assertThat(event.getViewNode().getTextIdEntry()).isEqualTo(PACKAGE_NAME);
+        verify(mMockEventBuffer).append(event);
+    }
+
+    @Test
+    public void processEvent_buffer_setsTextIdEntry_withExistingViewNode() {
+        ViewNode viewNode = new ViewNode();
+        viewNode.setTextIdEntry(PACKAGE_NAME + "TO BE OVERWRITTEN");
+        ContentCaptureEvent event = createStoreEvent();
+        event.setViewNode(viewNode);
+
+        mContentProtectionEventProcessor.processEvent(event);
+
+        assertThat(event.getViewNode()).isSameInstanceAs(viewNode);
+        assertThat(viewNode.getTextIdEntry()).isEqualTo(PACKAGE_NAME);
+        verify(mMockEventBuffer).append(event);
+    }
+
+    @Test
+    public void processEvent_loginDetected_inspectsOnlyTypeViewAppeared() {
+        mContentProtectionEventProcessor.mPasswordFieldDetected = true;
+        mContentProtectionEventProcessor.mSuspiciousTextDetected = true;
+
+        for (int type = -100; type <= 100; type++) {
+            if (type == TYPE_VIEW_APPEARED) {
+                continue;
+            }
+
+            mContentProtectionEventProcessor.processEvent(createEvent(type));
+
+            assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isTrue();
+            assertThat(mContentProtectionEventProcessor.mSuspiciousTextDetected).isTrue();
+        }
+
+        verify(mMockEventBuffer, never()).clear();
+        verify(mMockEventBuffer, never()).toArray();
+    }
+
+    @Test
+    public void processEvent_loginDetected() {
+        when(mMockEventBuffer.toArray()).thenReturn(new ContentCaptureEvent[0]);
+        mContentProtectionEventProcessor.mPasswordFieldDetected = true;
+        mContentProtectionEventProcessor.mSuspiciousTextDetected = true;
+
+        mContentProtectionEventProcessor.processEvent(PROCESS_EVENT);
+
+        assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isFalse();
+        assertThat(mContentProtectionEventProcessor.mSuspiciousTextDetected).isFalse();
+        verify(mMockEventBuffer).clear();
+        verify(mMockEventBuffer).toArray();
+    }
+
+    @Test
+    public void processEvent_loginDetected_passwordFieldNotDetected() {
+        mContentProtectionEventProcessor.mSuspiciousTextDetected = true;
+        assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isFalse();
+
+        mContentProtectionEventProcessor.processEvent(PROCESS_EVENT);
+
+        assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isFalse();
+        assertThat(mContentProtectionEventProcessor.mSuspiciousTextDetected).isTrue();
+        verify(mMockEventBuffer, never()).clear();
+        verify(mMockEventBuffer, never()).toArray();
+    }
+
+    @Test
+    public void processEvent_loginDetected_suspiciousTextNotDetected() {
+        mContentProtectionEventProcessor.mPasswordFieldDetected = true;
+        assertThat(mContentProtectionEventProcessor.mSuspiciousTextDetected).isFalse();
+
+        mContentProtectionEventProcessor.processEvent(PROCESS_EVENT);
+
+        assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isTrue();
+        assertThat(mContentProtectionEventProcessor.mSuspiciousTextDetected).isFalse();
+        verify(mMockEventBuffer, never()).clear();
+        verify(mMockEventBuffer, never()).toArray();
+    }
+
+    @Test
+    public void processEvent_loginDetected_withoutViewNode() {
+        assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isFalse();
+        assertThat(mContentProtectionEventProcessor.mSuspiciousTextDetected).isFalse();
+
+        mContentProtectionEventProcessor.processEvent(PROCESS_EVENT);
+
+        assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isFalse();
+        assertThat(mContentProtectionEventProcessor.mSuspiciousTextDetected).isFalse();
+        verify(mMockEventBuffer, never()).clear();
+        verify(mMockEventBuffer, never()).toArray();
+    }
+
+    @Test
+    public void processEvent_multipleLoginsDetected_belowFlushThreshold() {
+        when(mMockEventBuffer.toArray()).thenReturn(new ContentCaptureEvent[0]);
+
+        mContentProtectionEventProcessor.mPasswordFieldDetected = true;
+        mContentProtectionEventProcessor.mSuspiciousTextDetected = true;
+        mContentProtectionEventProcessor.processEvent(PROCESS_EVENT);
+
+        mContentProtectionEventProcessor.mPasswordFieldDetected = true;
+        mContentProtectionEventProcessor.mSuspiciousTextDetected = true;
+        mContentProtectionEventProcessor.processEvent(PROCESS_EVENT);
+
+        assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isFalse();
+        assertThat(mContentProtectionEventProcessor.mSuspiciousTextDetected).isFalse();
+        verify(mMockEventBuffer).clear();
+        verify(mMockEventBuffer).toArray();
+    }
+
+    @Test
+    public void processEvent_multipleLoginsDetected_aboveFlushThreshold() {
+        when(mMockEventBuffer.toArray()).thenReturn(new ContentCaptureEvent[0]);
+
+        mContentProtectionEventProcessor.mPasswordFieldDetected = true;
+        mContentProtectionEventProcessor.mSuspiciousTextDetected = true;
+        mContentProtectionEventProcessor.processEvent(PROCESS_EVENT);
+
+        mContentProtectionEventProcessor.mLastFlushTime = Instant.now().minusSeconds(5);
+
+        mContentProtectionEventProcessor.mPasswordFieldDetected = true;
+        mContentProtectionEventProcessor.mSuspiciousTextDetected = true;
+        mContentProtectionEventProcessor.processEvent(PROCESS_EVENT);
+
+        assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isFalse();
+        assertThat(mContentProtectionEventProcessor.mSuspiciousTextDetected).isFalse();
+        verify(mMockEventBuffer, times(2)).clear();
+        verify(mMockEventBuffer, times(2)).toArray();
+    }
+
+    @Test
+    public void isPasswordField_android() {
+        ContentCaptureEvent event =
+                createAndroidPasswordFieldEvent(
+                        ANDROID_CLASS_NAME, InputType.TYPE_TEXT_VARIATION_PASSWORD);
+
+        mContentProtectionEventProcessor.processEvent(event);
+
+        assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isTrue();
+        verify(mMockEventBuffer, never()).clear();
+        verify(mMockEventBuffer, never()).toArray();
+    }
+
+    @Test
+    public void isPasswordField_android_withoutClassName() {
+        ContentCaptureEvent event =
+                createAndroidPasswordFieldEvent(
+                        /* className= */ null, InputType.TYPE_TEXT_VARIATION_PASSWORD);
+
+        mContentProtectionEventProcessor.processEvent(event);
+
+        assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isFalse();
+        verify(mMockEventBuffer, never()).clear();
+        verify(mMockEventBuffer, never()).toArray();
+    }
+
+    @Test
+    public void isPasswordField_android_wrongClassName() {
+        ContentCaptureEvent event =
+                createAndroidPasswordFieldEvent(
+                        "wrong.prefix" + ANDROID_CLASS_NAME,
+                        InputType.TYPE_TEXT_VARIATION_PASSWORD);
+
+        mContentProtectionEventProcessor.processEvent(event);
+
+        assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isFalse();
+        verify(mMockEventBuffer, never()).clear();
+        verify(mMockEventBuffer, never()).toArray();
+    }
+
+    @Test
+    public void isPasswordField_android_wrongInputType() {
+        ContentCaptureEvent event =
+                createAndroidPasswordFieldEvent(
+                        ANDROID_CLASS_NAME, InputType.TYPE_TEXT_VARIATION_NORMAL);
+
+        mContentProtectionEventProcessor.processEvent(event);
+
+        assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isFalse();
+        verify(mMockEventBuffer, never()).clear();
+        verify(mMockEventBuffer, never()).toArray();
+    }
+
+    @Test
+    public void isPasswordField_webView() {
+        when(mMockEventBuffer.toArray()).thenReturn(new ContentCaptureEvent[0]);
+
+        ContentCaptureEvent event =
+                createWebViewPasswordFieldEvent(
+                        /* className= */ null, /* eventText= */ null, PASSWORD_TEXT);
+
+        mContentProtectionEventProcessor.processEvent(event);
+
+        assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isFalse();
+        verify(mMockEventBuffer).clear();
+        verify(mMockEventBuffer).toArray();
+    }
+
+    @Test
+    public void isPasswordField_webView_withClassName() {
+        ContentCaptureEvent event =
+                createWebViewPasswordFieldEvent(
+                        /* className= */ "any.class.name", /* eventText= */ null, PASSWORD_TEXT);
+
+        mContentProtectionEventProcessor.processEvent(event);
+
+        assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isFalse();
+        verify(mMockEventBuffer, never()).clear();
+        verify(mMockEventBuffer, never()).toArray();
+    }
+
+    @Test
+    public void isPasswordField_webView_withSafeViewNodeText() {
+        ContentCaptureEvent event =
+                createWebViewPasswordFieldEvent(
+                        /* className= */ null, /* eventText= */ null, SAFE_TEXT);
+
+        mContentProtectionEventProcessor.processEvent(event);
+
+        assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isFalse();
+        verify(mMockEventBuffer, never()).clear();
+        verify(mMockEventBuffer, never()).toArray();
+    }
+
+    @Test
+    public void isPasswordField_webView_withEventText() {
+        ContentCaptureEvent event =
+                createWebViewPasswordFieldEvent(/* className= */ null, PASSWORD_TEXT, SAFE_TEXT);
+
+        mContentProtectionEventProcessor.processEvent(event);
+
+        assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isFalse();
+        verify(mMockEventBuffer, never()).clear();
+        verify(mMockEventBuffer, never()).toArray();
+    }
+
+    @Test
+    public void isSuspiciousText_withSafeText() {
+        ContentCaptureEvent event = createSuspiciousTextEvent(SAFE_TEXT, SAFE_TEXT);
+
+        mContentProtectionEventProcessor.processEvent(event);
+
+        assertThat(mContentProtectionEventProcessor.mSuspiciousTextDetected).isFalse();
+        verify(mMockEventBuffer, never()).clear();
+        verify(mMockEventBuffer, never()).toArray();
+    }
+
+    @Test
+    public void isSuspiciousText_eventText_suspiciousText() {
+        ContentCaptureEvent event = createSuspiciousTextEvent(SUSPICIOUS_TEXT, SAFE_TEXT);
+
+        mContentProtectionEventProcessor.processEvent(event);
+
+        assertThat(mContentProtectionEventProcessor.mSuspiciousTextDetected).isTrue();
+        verify(mMockEventBuffer, never()).clear();
+        verify(mMockEventBuffer, never()).toArray();
+    }
+
+    @Test
+    public void isSuspiciousText_viewNodeText_suspiciousText() {
+        ContentCaptureEvent event = createSuspiciousTextEvent(SAFE_TEXT, SUSPICIOUS_TEXT);
+
+        mContentProtectionEventProcessor.processEvent(event);
+
+        assertThat(mContentProtectionEventProcessor.mSuspiciousTextDetected).isTrue();
+        verify(mMockEventBuffer, never()).clear();
+        verify(mMockEventBuffer, never()).toArray();
+    }
+
+    @Test
+    public void isSuspiciousText_eventText_passwordText() {
+        ContentCaptureEvent event = createSuspiciousTextEvent(PASSWORD_TEXT, SAFE_TEXT);
+
+        mContentProtectionEventProcessor.processEvent(event);
+
+        assertThat(mContentProtectionEventProcessor.mSuspiciousTextDetected).isTrue();
+        verify(mMockEventBuffer, never()).clear();
+        verify(mMockEventBuffer, never()).toArray();
+    }
+
+    @Test
+    public void isSuspiciousText_viewNodeText_passwordText() {
+        // Specify the class to differ from {@link isPasswordField_webView} test in this version
+        ContentCaptureEvent event =
+                createProcessEvent(
+                        "test.class.not.a.web.view", /* inputType= */ 0, SAFE_TEXT, PASSWORD_TEXT);
+
+        mContentProtectionEventProcessor.processEvent(event);
+
+        assertThat(mContentProtectionEventProcessor.mSuspiciousTextDetected).isTrue();
+        verify(mMockEventBuffer, never()).clear();
+        verify(mMockEventBuffer, never()).toArray();
+    }
+
+    private static ContentCaptureEvent createEvent(int type) {
+        return new ContentCaptureEvent(/* sessionId= */ 123, type);
+    }
+
+    private static ContentCaptureEvent createStoreEvent() {
+        return createEvent(TYPE_VIEW_TEXT_CHANGED);
+    }
+
+    private static ContentCaptureEvent createProcessEvent() {
+        return createEvent(TYPE_VIEW_APPEARED);
+    }
+
+    private ContentCaptureEvent createProcessEvent(
+            @Nullable String className,
+            int inputType,
+            @Nullable String eventText,
+            @Nullable String viewNodeText) {
+        View view = new View(mContext);
+        ViewStructureImpl viewStructure = new ViewStructureImpl(view);
+        if (className != null) {
+            viewStructure.setClassName(className);
+        }
+        if (viewNodeText != null) {
+            viewStructure.setText(viewNodeText);
+        }
+        viewStructure.setInputType(inputType);
+
+        ContentCaptureEvent event = createProcessEvent();
+        event.setViewNode(viewStructure.getNode());
+        if (eventText != null) {
+            event.setText(eventText);
+        }
+
+        return event;
+    }
+
+    private ContentCaptureEvent createAndroidPasswordFieldEvent(
+            @Nullable String className, int inputType) {
+        return createProcessEvent(
+                className, inputType, /* eventText= */ null, /* viewNodeText= */ null);
+    }
+
+    private ContentCaptureEvent createWebViewPasswordFieldEvent(
+            @Nullable String className, @Nullable String eventText, @Nullable String viewNodeText) {
+        return createProcessEvent(className, /* inputType= */ 0, eventText, viewNodeText);
+    }
+
+    private ContentCaptureEvent createSuspiciousTextEvent(
+            @Nullable String eventText, @Nullable String viewNodeText) {
+        return createProcessEvent(
+                /* className= */ null, /* inputType= */ 0, eventText, viewNodeText);
+    }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/LaunchAdjacentController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/common/LaunchAdjacentController.kt
new file mode 100644
index 0000000..81592c3
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/LaunchAdjacentController.kt
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.wm.shell.common
+
+import android.window.WindowContainerToken
+import android.window.WindowContainerTransaction
+import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_TASK_ORG
+import com.android.wm.shell.util.KtProtoLog
+
+/**
+ * Controller to manage behavior of activities launched with
+ * [android.content.Intent.FLAG_ACTIVITY_LAUNCH_ADJACENT].
+ */
+class LaunchAdjacentController(private val syncQueue: SyncTransactionQueue) {
+
+    /** Allows to temporarily disable launch adjacent handling */
+    var launchAdjacentEnabled: Boolean = true
+        set(value) {
+            if (field != value) {
+                KtProtoLog.d(WM_SHELL_TASK_ORG, "set launch adjacent flag root enabled=%b", value)
+                field = value
+                container?.let { c ->
+                    if (value) {
+                        enableContainer(c)
+                    } else {
+                        disableContainer((c))
+                    }
+                }
+            }
+        }
+    private var container: WindowContainerToken? = null
+
+    /**
+     * Set [container] as the new launch adjacent flag root container.
+     *
+     * If launch adjacent handling is disabled through [setLaunchAdjacentEnabled], won't set the
+     * container until after it is enabled again.
+     *
+     * @see WindowContainerTransaction.setLaunchAdjacentFlagRoot
+     */
+    fun setLaunchAdjacentRoot(container: WindowContainerToken) {
+        KtProtoLog.d(WM_SHELL_TASK_ORG, "set new launch adjacent flag root container")
+        this.container = container
+        if (launchAdjacentEnabled) {
+            enableContainer(container)
+        }
+    }
+
+    /**
+     * Clear a container previously set through [setLaunchAdjacentRoot].
+     *
+     * Always clears the container, regardless of [launchAdjacentEnabled] value.
+     *
+     * @see WindowContainerTransaction.clearLaunchAdjacentFlagRoot
+     */
+    fun clearLaunchAdjacentRoot() {
+        KtProtoLog.d(WM_SHELL_TASK_ORG, "clear launch adjacent flag root container")
+        container?.let {
+            disableContainer(it)
+            container = null
+        }
+    }
+
+    private fun enableContainer(container: WindowContainerToken) {
+        KtProtoLog.v(WM_SHELL_TASK_ORG, "enable launch adjacent flag root container")
+        val wct = WindowContainerTransaction()
+        wct.setLaunchAdjacentFlagRoot(container)
+        syncQueue.queue(wct)
+    }
+
+    private fun disableContainer(container: WindowContainerToken) {
+        KtProtoLog.v(WM_SHELL_TASK_ORG, "disable launch adjacent flag root container")
+        val wct = WindowContainerTransaction()
+        wct.clearLaunchAdjacentFlagRoot(container)
+        syncQueue.queue(wct)
+    }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
index d191fcd..37cbbcd 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
@@ -482,7 +482,7 @@
         val cornerRadius = ta.getDimensionPixelSize(0, 0).toFloat()
         ta.recycle()
         info.changes
-                .filter { it.taskInfo.windowingMode == WINDOWING_MODE_FREEFORM }
+                .filter { it.taskInfo?.windowingMode == WINDOWING_MODE_FREEFORM }
                 .forEach { finishTransaction.setCornerRadius(it.leash, cornerRadius) }
     }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMenuView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMenuView.java
index 167c032..779c539 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMenuView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMenuView.java
@@ -45,6 +45,7 @@
 import android.graphics.Color;
 import android.graphics.Rect;
 import android.graphics.drawable.Drawable;
+import android.graphics.drawable.Icon;
 import android.net.Uri;
 import android.os.Bundle;
 import android.os.Handler;
@@ -513,13 +514,19 @@
                     final boolean isCloseAction = mCloseAction != null && Objects.equals(
                             mCloseAction.getActionIntent(), action.getActionIntent());
 
-                    // TODO: Check if the action drawable has changed before we reload it
-                    action.getIcon().loadDrawableAsync(mContext, d -> {
-                        if (d != null) {
-                            d.setTint(Color.WHITE);
-                            actionView.setImageDrawable(d);
-                        }
-                    }, mMainHandler);
+                    final int iconType = action.getIcon().getType();
+                    if (iconType == Icon.TYPE_URI || iconType == Icon.TYPE_URI_ADAPTIVE_BITMAP) {
+                        // Disallow loading icon from content URI
+                        actionView.setImageDrawable(null);
+                    } else {
+                        // TODO: Check if the action drawable has changed before we reload it
+                        action.getIcon().loadDrawableAsync(mContext, d -> {
+                            if (d != null) {
+                                d.setTint(Color.WHITE);
+                                actionView.setImageDrawable(d);
+                            }
+                        }, mMainHandler);
+                    }
                     actionView.setCustomCloseBackgroundVisibility(
                             isCloseAction ? View.VISIBLE : View.GONE);
                     actionView.setContentDescription(action.getContentDescription());
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java
index c286959..8723f9b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java
@@ -35,6 +35,8 @@
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.util.ArrayMap;
+import android.util.IntArray;
+import android.util.Pair;
 import android.util.Slog;
 import android.view.Display;
 import android.view.IRecentsAnimationController;
@@ -135,8 +137,12 @@
     @Override
     public WindowContainerTransaction handleRequest(IBinder transition,
             TransitionRequestInfo request) {
-        // do not directly handle requests. Only entry point should be via startRecentsTransition
-        // TODO: Only log an error if the transition is a recents transition
+        if (mControllers.isEmpty()) {
+            // Ignore if there is no running recents transition
+            return null;
+        }
+        final RecentsController controller = mControllers.get(mControllers.size() - 1);
+        controller.handleMidTransitionRequest(request);
         return null;
     }
 
@@ -239,6 +245,11 @@
         /** The latest state that the recents animation is operating in. */
         private int mState = STATE_NORMAL;
 
+        // Snapshots taken when a new display change transition is requested, prior to the display
+        // change being applied.  This pending set of snapshots will only be applied when cancel is
+        // next called.
+        private Pair<int[], TaskSnapshot[]> mPendingPauseSnapshotsForCancel;
+
         RecentsController(IRecentsAnimationRunner listener) {
             mInstanceId = System.identityHashCode(this);
             mListener = listener;
@@ -290,6 +301,16 @@
          * "replace-with-screenshot" like behavior.
          */
         private boolean sendCancelWithSnapshots() {
+            Pair<int[], TaskSnapshot[]> snapshots = mPendingPauseSnapshotsForCancel != null
+                    ? mPendingPauseSnapshotsForCancel
+                    : getSnapshotsForPausingTasks();
+            return sendCancel(snapshots.first, snapshots.second);
+        }
+
+        /**
+         * Snapshots the pausing tasks and returns the mapping of the taskId -> snapshot.
+         */
+        private Pair<int[], TaskSnapshot[]> getSnapshotsForPausingTasks() {
             int[] taskIds = null;
             TaskSnapshot[] snapshots = null;
             if (mPausingTasks.size() > 0) {
@@ -298,6 +319,9 @@
                 try {
                     for (int i = 0; i < mPausingTasks.size(); ++i) {
                         TaskState state = mPausingTasks.get(0);
+                        ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
+                                "[%d] RecentsController.sendCancel: Snapshotting task=%d",
+                                mInstanceId, state.mTaskInfo.taskId);
                         snapshots[i] = ActivityTaskManager.getService().takeTaskSnapshot(
                                 state.mTaskInfo.taskId, true /* updateCache */);
                     }
@@ -306,7 +330,7 @@
                     snapshots = null;
                 }
             }
-            return sendCancel(taskIds, snapshots);
+            return new Pair(taskIds, snapshots);
         }
 
         /**
@@ -315,7 +339,7 @@
         private boolean sendCancel(@Nullable int[] taskIds,
                 @Nullable TaskSnapshot[] taskSnapshots) {
             try {
-                final String cancelDetails = taskSnapshots != null ? " with snapshots" : "";
+                final String cancelDetails = taskSnapshots != null ? "with snapshots" : "";
                 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
                         "[%d] RecentsController.cancel: calling onAnimationCanceled %s",
                         mInstanceId, cancelDetails);
@@ -348,6 +372,7 @@
             mOpeningTasks = null;
             mInfo = null;
             mTransition = null;
+            mPendingPauseSnapshotsForCancel = null;
             mControllers.remove(this);
         }
 
@@ -398,26 +423,28 @@
             // About layering: we divide up the "layer space" into 3 regions (each the size of
             // the change count). This lets us categorize things into above/below/between
             // while maintaining their relative ordering.
+            final int belowLayers = info.getChanges().size();
+            final int aboveLayers = info.getChanges().size() * 3;
             for (int i = 0; i < info.getChanges().size(); ++i) {
                 final TransitionInfo.Change change = info.getChanges().get(i);
                 final ActivityManager.RunningTaskInfo taskInfo = change.getTaskInfo();
                 if (TransitionUtil.isWallpaper(change)) {
                     final RemoteAnimationTarget target = TransitionUtil.newTarget(change,
                             // wallpapers go into the "below" layer space
-                            info.getChanges().size() - i, info, t, mLeashMap);
+                            belowLayers - i, info, t, mLeashMap);
                     wallpapers.add(target);
                     // Make all the wallpapers opaque since we want them visible from the start
                     t.setAlpha(target.leash, 1);
                 } else if (leafTaskFilter.test(change)) {
                     // start by putting everything into the "below" layer space.
                     final RemoteAnimationTarget target = TransitionUtil.newTarget(change,
-                            info.getChanges().size() - i, info, t, mLeashMap);
+                            belowLayers - i, info, t, mLeashMap);
                     apps.add(target);
                     if (TransitionUtil.isClosingType(change.getMode())) {
                         ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
-                                "  adding pausing taskId=%d", taskInfo.taskId);
+                                "  adding pausing leaf taskId=%d", taskInfo.taskId);
                         // raise closing (pausing) task to "above" layer so it isn't covered
-                        t.setLayer(target.leash, info.getChanges().size() * 3 - i);
+                        t.setLayer(target.leash, aboveLayers - i);
                         mPausingTasks.add(new TaskState(change, target.leash));
                         if (taskInfo.topActivityType == ACTIVITY_TYPE_HOME) {
                             // This can only happen if we have a separate recents/home (3p launcher)
@@ -430,17 +457,32 @@
                     } else if (taskInfo != null
                             && taskInfo.topActivityType == ACTIVITY_TYPE_RECENTS) {
                         // There's a 3p launcher, so make sure recents goes above that.
-                        t.setLayer(target.leash, info.getChanges().size() * 3 - i);
+                        t.setLayer(target.leash, aboveLayers - i);
                     } else if (taskInfo != null && taskInfo.topActivityType == ACTIVITY_TYPE_HOME) {
                         // do nothing
                     } else if (TransitionUtil.isOpeningType(change.getMode())) {
                         ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
-                                "  adding opening taskId=%d", taskInfo.taskId);
+                                "  adding opening leaf taskId=%d", taskInfo.taskId);
                         mOpeningTasks.add(new TaskState(change, target.leash));
                     }
+                } else if (taskInfo != null && TransitionInfo.isIndependent(change, info)) {
+                    // Root tasks
+                    if (TransitionUtil.isClosingType(change.getMode())) {
+                        ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
+                                "  adding pausing taskId=%d", taskInfo.taskId);
+                        // raise closing (pausing) task to "above" layer so it isn't covered
+                        t.setLayer(change.getLeash(), aboveLayers - i);
+                        mPausingTasks.add(new TaskState(change, null /* leash */));
+                    } else if (TransitionUtil.isOpeningType(change.getMode())) {
+                        ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
+                                "  adding opening taskId=%d", taskInfo.taskId);
+                        // Put into the "below" layer space.
+                        t.setLayer(change.getLeash(), belowLayers - i);
+                        mOpeningTasks.add(new TaskState(change, null /* leash */));
+                    }
                 } else if (TransitionUtil.isDividerBar(change)) {
                     final RemoteAnimationTarget target = TransitionUtil.newTarget(change,
-                            info.getChanges().size() - i, info, t, mLeashMap);
+                            belowLayers - i, info, t, mLeashMap);
                     // Add this as a app and we will separate them on launcher side by window type.
                     apps.add(target);
                 }
@@ -460,6 +502,22 @@
             return true;
         }
 
+        /**
+         * Updates this controller when a new transition is requested mid-recents transition.
+         */
+        void handleMidTransitionRequest(TransitionRequestInfo request) {
+            if (request.getType() == TRANSIT_CHANGE && request.getDisplayChange() != null) {
+                final TransitionRequestInfo.DisplayChange dispChange = request.getDisplayChange();
+                if (dispChange.getStartRotation() != dispChange.getEndRotation()) {
+                    ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
+                            "[%d] RecentsController.prepareForMerge: "
+                                    + "Snapshot due to requested display change",
+                            mInstanceId);
+                    mPendingPauseSnapshotsForCancel = getSnapshotsForPausingTasks();
+                }
+            }
+        }
+
         @SuppressLint("NewApi")
         void merge(TransitionInfo info, SurfaceControl.Transaction t,
                 Transitions.TransitionFinishCallback finishCallback) {
@@ -479,7 +537,9 @@
             }
             ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
                     "[%d] RecentsController.merge", mInstanceId);
+            // Keep all tasks in one list because order matters.
             ArrayList<TransitionInfo.Change> openingTasks = null;
+            IntArray openingTaskIsLeafs = null;
             ArrayList<TransitionInfo.Change> closingTasks = null;
             mOpeningSeparateHome = false;
             TransitionInfo.Change recentsOpening = null;
@@ -498,25 +558,29 @@
                     cancel("task #" + taskInfo.taskId + " is always_on_top");
                     return;
                 }
-                hasTaskChange = hasTaskChange || taskInfo != null;
+                final boolean isRootTask = taskInfo != null
+                        && TransitionInfo.isIndependent(change, info);
+                hasTaskChange = hasTaskChange || isRootTask;
                 final boolean isLeafTask = leafTaskFilter.test(change);
                 if (TransitionUtil.isOpeningType(change.getMode())) {
                     if (mRecentsTask != null && mRecentsTask.equals(change.getContainer())) {
                         recentsOpening = change;
-                    } else if (isLeafTask) {
-                        if (taskInfo.topActivityType == ACTIVITY_TYPE_HOME) {
+                    } else if (isRootTask || isLeafTask) {
+                        if (isLeafTask && taskInfo.topActivityType == ACTIVITY_TYPE_HOME) {
                             // This is usually a 3p launcher
                             mOpeningSeparateHome = true;
                         }
                         if (openingTasks == null) {
                             openingTasks = new ArrayList<>();
+                            openingTaskIsLeafs = new IntArray();
                         }
                         openingTasks.add(change);
+                        openingTaskIsLeafs.add(isLeafTask ? 1 : 0);
                     }
                 } else if (TransitionUtil.isClosingType(change.getMode())) {
                     if (mRecentsTask != null && mRecentsTask.equals(change.getContainer())) {
                         foundRecentsClosing = true;
-                    } else if (isLeafTask) {
+                    } else if (isRootTask || isLeafTask) {
                         if (closingTasks == null) {
                             closingTasks = new ArrayList<>();
                         }
@@ -526,6 +590,8 @@
                     // Finish recents animation if the display is changed, so the default
                     // transition handler can play the animation such as rotation effect.
                     if (change.hasFlags(TransitionInfo.FLAG_IS_DISPLAY)) {
+                        // This call to cancel will use the screenshots taken preemptively in
+                        // handleMidTransitionRequest() prior to the display changing
                         cancel(mWillFinishToHome, true /* withScreenshots */, "display change");
                         return;
                     }
@@ -580,7 +646,8 @@
                     }
                     final TaskState openingTask = mOpeningTasks.remove(openingIdx);
                     ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
-                            "  pausing opening taskId=%d", openingTask.mTaskInfo.taskId);
+                            "  pausing opening %staskId=%d", openingTask.isLeaf() ? "leaf " : "",
+                            openingTask.mTaskInfo.taskId);
                     mPausingTasks.add(openingTask);
                     didMergeThings = true;
                 }
@@ -590,35 +657,55 @@
                 // Switching to some new tasks, add to mOpening and remove from mPausing. Also,
                 // enter NEW_TASK state since this will start the switch-to animation.
                 final int layer = mInfo.getChanges().size() * 3;
-                appearedTargets = new RemoteAnimationTarget[openingTasks.size()];
+                int openingLeafCount = 0;
+                for (int i = 0; i < openingTaskIsLeafs.size(); ++i) {
+                    openingLeafCount += openingTaskIsLeafs.get(i);
+                }
+                if (openingLeafCount > 0) {
+                    appearedTargets = new RemoteAnimationTarget[openingLeafCount];
+                }
+                int nextTargetIdx = 0;
                 for (int i = 0; i < openingTasks.size(); ++i) {
                     final TransitionInfo.Change change = openingTasks.get(i);
+                    final boolean isLeaf = openingTaskIsLeafs.get(i) == 1;
                     int pausingIdx = TaskState.indexOf(mPausingTasks, change);
                     if (pausingIdx >= 0) {
                         // Something is showing/opening a previously-pausing app.
-                        appearedTargets[i] = TransitionUtil.newTarget(
-                                change, layer, mPausingTasks.get(pausingIdx).mLeash);
+                        if (isLeaf) {
+                            appearedTargets[nextTargetIdx++] = TransitionUtil.newTarget(
+                                    change, layer, mPausingTasks.get(pausingIdx).mLeash);
+                        }
                         final TaskState pausingTask = mPausingTasks.remove(pausingIdx);
                         ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
-                                "  opening pausing taskId=%d", pausingTask.mTaskInfo.taskId);
+                                "  opening pausing %staskId=%d", isLeaf ? "leaf " : "",
+                                pausingTask.mTaskInfo.taskId);
                         mOpeningTasks.add(pausingTask);
                         // Setup hides opening tasks initially, so make it visible again (since we
                         // are already showing it).
                         t.show(change.getLeash());
                         t.setAlpha(change.getLeash(), 1.f);
-                    } else {
-                        // We are receiving new opening tasks, so convert to onTasksAppeared.
-                        appearedTargets[i] = TransitionUtil.newTarget(
+                    } else if (isLeaf) {
+                        // We are receiving new opening leaf tasks, so convert to onTasksAppeared.
+                        final RemoteAnimationTarget target = TransitionUtil.newTarget(
                                 change, layer, info, t, mLeashMap);
+                        appearedTargets[nextTargetIdx++] = target;
                         // reparent into the original `mInfo` since that's where we are animating.
                         final int rootIdx = TransitionUtil.rootIndexFor(change, mInfo);
-                        t.reparent(appearedTargets[i].leash, mInfo.getRoot(rootIdx).getLeash());
-                        t.setLayer(appearedTargets[i].leash, layer);
+                        t.reparent(target.leash, mInfo.getRoot(rootIdx).getLeash());
+                        t.setLayer(target.leash, layer);
                         // Hide the animation leash, let listener show it.
-                        t.hide(appearedTargets[i].leash);
+                        t.hide(target.leash);
                         ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
-                                "  opening new taskId=%d", appearedTargets[i].taskId);
-                        mOpeningTasks.add(new TaskState(change, appearedTargets[i].leash));
+                                "  opening new leaf taskId=%d", target.taskId);
+                        mOpeningTasks.add(new TaskState(change, target.leash));
+                    } else {
+                        ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
+                                "  opening new taskId=%d", change.getTaskInfo().taskId);
+                        t.setLayer(change.getLeash(), layer);
+                        // Setup hides opening tasks initially, so make it visible since recents
+                        // is only animating the leafs.
+                        t.show(change.getLeash());
+                        mOpeningTasks.add(new TaskState(change, null));
                     }
                 }
                 didMergeThings = true;
@@ -803,7 +890,7 @@
                     t.show(mOpeningTasks.get(i).mTaskSurface);
                 }
                 for (int i = 0; i < mPausingTasks.size(); ++i) {
-                    if (!sendUserLeaveHint) {
+                    if (!sendUserLeaveHint && mPausingTasks.get(i).isLeaf()) {
                         // This means recents is not *actually* finishing, so of course we gotta
                         // do special stuff in WMCore to accommodate.
                         wct.setDoNotPip(mPausingTasks.get(i).mToken);
@@ -882,7 +969,8 @@
         /** The surface/leash of the task provided by Core. */
         SurfaceControl mTaskSurface;
 
-        /** The (local) animation-leash created for this task. */
+        /** The (local) animation-leash created for this task. Only non-null for leafs. */
+        @Nullable
         SurfaceControl mLeash;
 
         TaskState(TransitionInfo.Change change, SurfaceControl leash) {
@@ -901,6 +989,10 @@
             return -1;
         }
 
+        boolean isLeaf() {
+            return mLeash != null;
+        }
+
         public String toString() {
             return "" + mToken + " : " + mLeash;
         }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
index b2526ee..366f4f1 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
@@ -24,6 +24,7 @@
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;
 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
 import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
+import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
 import static android.content.res.Configuration.SMALLEST_SCREEN_WIDTH_DP_UNDEFINED;
 import static android.view.Display.DEFAULT_DISPLAY;
 import static android.view.RemoteAnimationTarget.MODE_OPENING;
@@ -2406,6 +2407,7 @@
                 if (change.getMode() == TRANSIT_CHANGE
                         && (change.getFlags() & FLAG_IS_DISPLAY) != 0) {
                     mSplitLayout.update(startTransaction);
+                    record.mContainDisplayChange = true;
                 }
 
                 final ActivityManager.RunningTaskInfo taskInfo = change.getTaskInfo();
@@ -2435,7 +2437,13 @@
                     continue;
                 }
                 final StageTaskListener stage = getStageOfTask(taskInfo);
-                if (stage == null) continue;
+                if (stage == null) {
+                    if (change.getParent() == null && !isClosingType(change.getMode())
+                            && taskInfo.getWindowingMode() == WINDOWING_MODE_PINNED) {
+                        record.mContainShowPipChange = true;
+                    }
+                    continue;
+                }
                 if (isOpeningType(change.getMode())) {
                     if (!stage.containsTask(taskInfo.taskId)) {
                         Log.w(TAG, "Expected onTaskAppeared on " + stage + " to have been called"
@@ -2450,13 +2458,22 @@
                     }
                 }
             }
-            // If the size of dismissStages == 1, one of the task is closed without prepare pending
-            // transition, which could happen if all activities were finished after finish top
-            // activity in a task, so the trigger task is null when handleRequest.
-            // Note if the size of dismissStages == 2, it's starting a new task, so don't handle it.
             final ArraySet<StageTaskListener> dismissStages = record.getShouldDismissedStage();
-            if (mMainStage.getChildCount() == 0 || mSideStage.getChildCount() == 0
+            if (!record.mContainDisplayChange && record.mContainShowPipChange) {
+                // This occurred when split enter pip by open another fullscreen app so let
+                // pip tranistion handler to handle this but also start our dismiss transition.
+                // TODO(b/282894249): Should improve this case animation on pip.
+                final WindowContainerTransaction wct = new WindowContainerTransaction();
+                prepareExitSplitScreen(STAGE_TYPE_UNDEFINED, wct);
+                mSplitTransitions.startDismissTransition(wct, this, STAGE_TYPE_UNDEFINED,
+                        EXIT_REASON_CHILD_TASK_ENTER_PIP);
+            } else if (mMainStage.getChildCount() == 0 || mSideStage.getChildCount() == 0
                     || dismissStages.size() == 1) {
+                // If the size of dismissStages == 1, one of the task is closed without prepare
+                // pending transition, which could happen if all activities were finished after
+                // finish top activity in a task, so the trigger task is null when handleRequest.
+                // Note if the size of dismissStages == 2, it's starting a new task,
+                // so don't handle it.
                 Log.e(TAG, "Somehow removed the last task in a stage outside of a proper "
                         + "transition.");
                 final WindowContainerTransaction wct = new WindowContainerTransaction();
@@ -2473,7 +2490,6 @@
                 // TODO(b/184679596): Find a way to either include task-org information in
                 //                    the transition, or synchronize task-org callbacks.
             }
-
             // Use normal animations.
             return false;
         } else if (mMixedHandler != null && TransitionUtil.hasDisplayChange(info)) {
@@ -2490,6 +2506,8 @@
     }
 
     static class StageChangeRecord {
+        boolean mContainDisplayChange = false;
+        boolean mContainShowPipChange = false;
         static class StageChange {
             final StageTaskListener mStageTaskListener;
             final IntArray mAddedTaskId = new IntArray();
@@ -2622,6 +2640,7 @@
                 Log.w(TAG, "Launched a task in split, but didn't receive any task in transition.");
                 mSplitTransitions.mPendingEnter.cancel((cancelWct, cancelT)
                         -> prepareExitSplitScreen(STAGE_TYPE_UNDEFINED, cancelWct));
+                mSplitUnsupportedToast.show();
                 return true;
             }
         } else {
@@ -2632,6 +2651,7 @@
                         (sideChild != null ? STAGE_TYPE_SIDE : STAGE_TYPE_UNDEFINED);
                 mSplitTransitions.mPendingEnter.cancel(
                         (cancelWct, cancelT) -> prepareExitSplitScreen(dismissTop, cancelWct));
+                mSplitUnsupportedToast.show();
                 return true;
             }
         }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
index ce8d792..de20c2d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
@@ -29,6 +29,7 @@
 import static android.view.WindowManager.fixScale;
 import static android.window.TransitionInfo.FLAG_IS_OCCLUDED;
 import static android.window.TransitionInfo.FLAG_IS_WALLPAPER;
+import static android.window.TransitionInfo.FLAG_MOVED_TO_TOP;
 import static android.window.TransitionInfo.FLAG_NO_ANIMATION;
 import static android.window.TransitionInfo.FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT;
 
@@ -554,7 +555,10 @@
                     layer = -zSplitLine - i;
                 }
             } else if (mode == TRANSIT_OPEN || mode == TRANSIT_TO_FRONT) {
-                if (isOpening) {
+                if (isOpening
+                        // This is for when an activity launches while a different transition is
+                        // collecting.
+                        || change.hasFlags(FLAG_MOVED_TO_TOP)) {
                     // put on top
                     layer = zSplitLine + numChanges - i;
                 } else {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldTransitionHandler.java
index bb0eba6..c504f57 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldTransitionHandler.java
@@ -173,6 +173,17 @@
         mTransition = null;
     }
 
+    @Override
+    public void mergeAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
+            @NonNull SurfaceControl.Transaction t, @NonNull IBinder mergeTarget,
+            @NonNull TransitionFinishCallback finishCallback) {
+        if (info.getType() == TRANSIT_CHANGE) {
+            // Apply changes happening during the unfold animation immediately
+            t.apply();
+            finishCallback.onTransitionFinished(null, null);
+        }
+    }
+
     @Nullable
     @Override
     public WindowContainerTransaction handleRequest(@NonNull IBinder transition,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/util/TransitionUtil.java b/libs/WindowManager/Shell/src/com/android/wm/shell/util/TransitionUtil.java
index 143b42a..c33a633 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/util/TransitionUtil.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/util/TransitionUtil.java
@@ -121,15 +121,14 @@
         @Override
         public boolean test(TransitionInfo.Change change) {
             final ActivityManager.RunningTaskInfo taskInfo = change.getTaskInfo();
+            if (taskInfo == null) return false;
             // Children always come before parent since changes are in top-to-bottom z-order.
-            if ((taskInfo == null) || mChildTaskTargets.get(taskInfo.taskId)) {
-                // has children, so not a leaf. Skip.
-                return false;
-            }
+            boolean hasChildren = mChildTaskTargets.get(taskInfo.taskId);
             if (taskInfo.hasParentTask()) {
                 mChildTaskTargets.put(taskInfo.parentTaskId, true);
             }
-            return true;
+            // If it has children, it's not a leaf.
+            return !hasChildren;
         }
     }
 
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/MockToken.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/MockToken.java
similarity index 89%
rename from libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/MockToken.java
rename to libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/MockToken.java
index 09d474d..a97c19f 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/MockToken.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/MockToken.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.wm.shell.desktopmode;
+package com.android.wm.shell;
 
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
@@ -25,16 +25,16 @@
 /**
  * {@link WindowContainerToken} wrapper that supports a mock binder
  */
-class MockToken {
+public class MockToken {
     private final WindowContainerToken mToken;
 
-    MockToken() {
+    public MockToken() {
         mToken = mock(WindowContainerToken.class);
         IBinder binder = mock(IBinder.class);
         when(mToken.asBinder()).thenReturn(binder);
     }
 
-    WindowContainerToken token() {
+    public WindowContainerToken token() {
         return mToken;
     }
 }
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/LaunchAdjacentControllerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/LaunchAdjacentControllerTest.kt
new file mode 100644
index 0000000..9dc816b
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/LaunchAdjacentControllerTest.kt
@@ -0,0 +1,172 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.wm.shell.common
+
+import android.os.IBinder
+import android.testing.AndroidTestingRunner
+import android.window.WindowContainerTransaction
+import android.window.WindowContainerTransaction.HierarchyOp
+import androidx.test.filters.SmallTest
+import com.android.wm.shell.MockToken
+import com.android.wm.shell.ShellTestCase
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.Mock
+import org.mockito.Mockito.any
+import org.mockito.Mockito.atLeastOnce
+import org.mockito.Mockito.clearInvocations
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class LaunchAdjacentControllerTest : ShellTestCase() {
+
+    private lateinit var controller: LaunchAdjacentController
+
+    @Mock private lateinit var syncQueue: SyncTransactionQueue
+
+    @Before
+    fun setUp() {
+        controller = LaunchAdjacentController(syncQueue)
+    }
+
+    @Test
+    fun newInstance_enabledByDefault() {
+        assertThat(controller.launchAdjacentEnabled).isTrue()
+    }
+
+    @Test
+    fun setLaunchAdjacentRoot_launchAdjacentEnabled_setsFlagRoot() {
+        val token = MockToken().token()
+        controller.setLaunchAdjacentRoot(token)
+        val wct = getLatestTransactionOrFail()
+        assertThat(wct.getSetLaunchAdjacentFlagRootContainer()).isEqualTo(token.asBinder())
+    }
+
+    @Test
+    fun setLaunchAdjacentRoot_launchAdjacentDisabled_doesNotUpdateFlagRoot() {
+        val token = MockToken().token()
+        controller.launchAdjacentEnabled = false
+        controller.setLaunchAdjacentRoot(token)
+        verify(syncQueue, never()).queue(any())
+    }
+
+    @Test
+    fun clearLaunchAdjacentRoot_launchAdjacentEnabled_clearsFlagRoot() {
+        val token = MockToken().token()
+        controller.setLaunchAdjacentRoot(token)
+        controller.clearLaunchAdjacentRoot()
+        val wct = getLatestTransactionOrFail()
+        assertThat(wct.getClearLaunchAdjacentFlagRootContainer()).isEqualTo(token.asBinder())
+    }
+
+    @Test
+    fun clearLaunchAdjacentRoot_launchAdjacentDisabled_clearsFlagRoot() {
+        val token = MockToken().token()
+        controller.setLaunchAdjacentRoot(token)
+        controller.launchAdjacentEnabled = false
+        clearInvocations(syncQueue)
+
+        controller.clearLaunchAdjacentRoot()
+        val wct = getLatestTransactionOrFail()
+        assertThat(wct.getClearLaunchAdjacentFlagRootContainer()).isEqualTo(token.asBinder())
+    }
+
+    @Test
+    fun setLaunchAdjacentEnabled_wasDisabledWithContainerSet_setsFlagRoot() {
+        val token = MockToken().token()
+        controller.setLaunchAdjacentRoot(token)
+        controller.launchAdjacentEnabled = false
+        clearInvocations(syncQueue)
+
+        controller.launchAdjacentEnabled = true
+        val wct = getLatestTransactionOrFail()
+        assertThat(wct.getSetLaunchAdjacentFlagRootContainer()).isEqualTo(token.asBinder())
+    }
+
+    @Test
+    fun setLaunchAdjacentEnabled_containerNotSet_doesNotUpdateFlagRoot() {
+        controller.launchAdjacentEnabled = false
+        controller.launchAdjacentEnabled = true
+        verify(syncQueue, never()).queue(any())
+    }
+
+    @Test
+    fun setLaunchAdjacentEnabled_multipleTimes_setsFlagRootOnce() {
+        val token = MockToken().token()
+        controller.setLaunchAdjacentRoot(token)
+        controller.launchAdjacentEnabled = true
+        controller.launchAdjacentEnabled = true
+        // Only execute once
+        verify(syncQueue).queue(any())
+    }
+
+    @Test
+    fun setLaunchAdjacentDisabled_containerSet_clearsFlagRoot() {
+        val token = MockToken().token()
+        controller.setLaunchAdjacentRoot(token)
+        controller.launchAdjacentEnabled = false
+        val wct = getLatestTransactionOrFail()
+        assertThat(wct.getClearLaunchAdjacentFlagRootContainer()).isEqualTo(token.asBinder())
+    }
+
+    @Test
+    fun setLaunchAdjacentDisabled_containerNotSet_doesNotUpdateFlagRoot() {
+        controller.launchAdjacentEnabled = false
+        verify(syncQueue, never()).queue(any())
+    }
+
+    @Test
+    fun setLaunchAdjacentDisabled_multipleTimes_setsFlagRootOnce() {
+        val token = MockToken().token()
+        controller.setLaunchAdjacentRoot(token)
+        clearInvocations(syncQueue)
+        controller.launchAdjacentEnabled = false
+        controller.launchAdjacentEnabled = false
+        // Only execute once
+        verify(syncQueue).queue(any())
+    }
+
+    private fun getLatestTransactionOrFail(): WindowContainerTransaction {
+        val arg = ArgumentCaptor.forClass(WindowContainerTransaction::class.java)
+        verify(syncQueue, atLeastOnce()).queue(arg.capture())
+        return arg.allValues.last().also { assertThat(it).isNotNull() }
+    }
+}
+
+private fun WindowContainerTransaction.getSetLaunchAdjacentFlagRootContainer(): IBinder {
+    return hierarchyOps
+        // Find the operation with the correct type
+        .filter { op -> op.type == HierarchyOp.HIERARCHY_OP_TYPE_SET_LAUNCH_ADJACENT_FLAG_ROOT }
+        // For set flag root operation, toTop is false
+        .filter { op -> !op.toTop }
+        .map { it.container }
+        .first()
+}
+
+private fun WindowContainerTransaction.getClearLaunchAdjacentFlagRootContainer(): IBinder {
+    return hierarchyOps
+        // Find the operation with the correct type
+        .filter { op -> op.type == HierarchyOp.HIERARCHY_OP_TYPE_SET_LAUNCH_ADJACENT_FLAG_ROOT }
+        // For clear flag root operation, toTop is true
+        .filter { op -> op.toTop }
+        .map { it.container }
+        .first()
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeControllerTest.java
index d6387ee..605a762 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeControllerTest.java
@@ -59,6 +59,7 @@
 import androidx.test.filters.SmallTest;
 
 import com.android.dx.mockito.inline.extended.StaticMockitoSession;
+import com.android.wm.shell.MockToken;
 import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
 import com.android.wm.shell.ShellTaskOrganizer;
 import com.android.wm.shell.ShellTestCase;
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
index e3dd449..666ef55 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
@@ -39,6 +39,7 @@
 import com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession
 import com.android.dx.mockito.inline.extended.ExtendedMockito.never
 import com.android.dx.mockito.inline.extended.StaticMockitoSession
+import com.android.wm.shell.MockToken
 import com.android.wm.shell.RootTaskDisplayAreaOrganizer
 import com.android.wm.shell.ShellTaskOrganizer
 import com.android.wm.shell.ShellTestCase
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTestHelpers.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTestHelpers.kt
index cf1ff32..29a757c 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTestHelpers.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTestHelpers.kt
@@ -22,6 +22,7 @@
 import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM
 import android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN
 import android.view.Display.DEFAULT_DISPLAY
+import com.android.wm.shell.MockToken
 import com.android.wm.shell.TestRunningTaskInfoBuilder
 
 class DesktopTestHelpers {
diff --git a/libs/androidfw/CursorWindow.cpp b/libs/androidfw/CursorWindow.cpp
index 3527eee..2a6dc7b 100644
--- a/libs/androidfw/CursorWindow.cpp
+++ b/libs/androidfw/CursorWindow.cpp
@@ -108,7 +108,7 @@
 
     {
         // Migrate existing contents into new ashmem region
-        uint32_t slotsSize = mSize - mSlotsOffset;
+        uint32_t slotsSize = sizeOfSlots();
         uint32_t newSlotsOffset = mInflatedSize - slotsSize;
         memcpy(static_cast<uint8_t*>(newData),
                 static_cast<uint8_t*>(mData), mAllocOffset);
@@ -216,11 +216,9 @@
         if (parcel->writeDupFileDescriptor(mAshmemFd)) goto fail;
     } else {
         // Since we know we're going to be read-only on the remote side,
-        // we can compact ourselves on the wire, with just enough padding
-        // to ensure our slots stay aligned
-        size_t slotsSize = mSize - mSlotsOffset;
-        size_t compactedSize = mAllocOffset + slotsSize;
-        compactedSize = (compactedSize + 3) & ~3;
+        // we can compact ourselves on the wire.
+        size_t slotsSize = sizeOfSlots();
+        size_t compactedSize = sizeInUse();
         if (parcel->writeUint32(compactedSize)) goto fail;
         if (parcel->writeBool(false)) goto fail;
         void* dest = parcel->writeInplace(compactedSize);
diff --git a/libs/androidfw/include/androidfw/CursorWindow.h b/libs/androidfw/include/androidfw/CursorWindow.h
index 6e55a9a..9ec026a 100644
--- a/libs/androidfw/include/androidfw/CursorWindow.h
+++ b/libs/androidfw/include/androidfw/CursorWindow.h
@@ -90,6 +90,9 @@
     inline uint32_t getNumRows() { return mNumRows; }
     inline uint32_t getNumColumns() { return mNumColumns; }
 
+    inline size_t sizeOfSlots() const { return mSize - mSlotsOffset; }
+    inline size_t sizeInUse() const { return mAllocOffset + sizeOfSlots(); }
+
     status_t clear();
     status_t setNumColumns(uint32_t numColumns);
 
diff --git a/libs/androidfw/tests/CursorWindow_test.cpp b/libs/androidfw/tests/CursorWindow_test.cpp
index d1cfd03..3cdb31e 100644
--- a/libs/androidfw/tests/CursorWindow_test.cpp
+++ b/libs/androidfw/tests/CursorWindow_test.cpp
@@ -21,9 +21,16 @@
 
 #include "TestHelpers.h"
 
+// Verify that the memory in use is a multiple of 4 bytes
+#define ASSERT_ALIGNED(w) \
+    ASSERT_EQ(((w)->sizeInUse() & 3), 0); \
+    ASSERT_EQ(((w)->freeSpace() & 3), 0); \
+    ASSERT_EQ(((w)->sizeOfSlots() & 3), 0)
+
 #define CREATE_WINDOW_1K \
     CursorWindow* w; \
-    CursorWindow::create(String8("test"), 1 << 10, &w);
+    CursorWindow::create(String8("test"), 1 << 10, &w); \
+    ASSERT_ALIGNED(w);
 
 #define CREATE_WINDOW_1K_3X3 \
     CursorWindow* w; \
@@ -31,11 +38,13 @@
     ASSERT_EQ(w->setNumColumns(3), OK); \
     ASSERT_EQ(w->allocRow(), OK); \
     ASSERT_EQ(w->allocRow(), OK); \
-    ASSERT_EQ(w->allocRow(), OK);
+    ASSERT_EQ(w->allocRow(), OK); \
+    ASSERT_ALIGNED(w);
 
 #define CREATE_WINDOW_2M \
     CursorWindow* w; \
-    CursorWindow::create(String8("test"), 1 << 21, &w);
+    CursorWindow::create(String8("test"), 1 << 21, &w); \
+    ASSERT_ALIGNED(w);
 
 static constexpr const size_t kHalfInlineSize = 8192;
 static constexpr const size_t kGiantSize = 1048576;
@@ -49,6 +58,7 @@
     ASSERT_EQ(w->getNumColumns(), 0);
     ASSERT_EQ(w->size(), 1 << 10);
     ASSERT_EQ(w->freeSpace(), 1 << 10);
+    ASSERT_ALIGNED(w);
 }
 
 TEST(CursorWindowTest, SetNumColumns) {
@@ -60,6 +70,7 @@
     ASSERT_NE(w->setNumColumns(5), OK);
     ASSERT_NE(w->setNumColumns(3), OK);
     ASSERT_EQ(w->getNumColumns(), 4);
+    ASSERT_ALIGNED(w);
 }
 
 TEST(CursorWindowTest, SetNumColumnsAfterRow) {
@@ -70,6 +81,7 @@
     ASSERT_EQ(w->allocRow(), OK);
     ASSERT_NE(w->setNumColumns(4), OK);
     ASSERT_EQ(w->getNumColumns(), 0);
+    ASSERT_ALIGNED(w);
 }
 
 TEST(CursorWindowTest, AllocRow) {
@@ -83,14 +95,17 @@
     ASSERT_EQ(w->allocRow(), OK);
     ASSERT_LT(w->freeSpace(), before);
     ASSERT_EQ(w->getNumRows(), 1);
+    ASSERT_ALIGNED(w);
 
     // Verify we can unwind
     ASSERT_EQ(w->freeLastRow(), OK);
     ASSERT_EQ(w->freeSpace(), before);
     ASSERT_EQ(w->getNumRows(), 0);
+    ASSERT_ALIGNED(w);
 
     // Can't unwind when no rows left
     ASSERT_NE(w->freeLastRow(), OK);
+    ASSERT_ALIGNED(w);
 }
 
 TEST(CursorWindowTest, AllocRowBounds) {
@@ -100,6 +115,7 @@
     ASSERT_EQ(w->setNumColumns(60), OK);
     ASSERT_EQ(w->allocRow(), OK);
     ASSERT_NE(w->allocRow(), OK);
+    ASSERT_ALIGNED(w);
 }
 
 TEST(CursorWindowTest, StoreNull) {
@@ -116,6 +132,7 @@
         auto field = w->getFieldSlot(0, 0);
         ASSERT_EQ(w->getFieldSlotType(field), CursorWindow::FIELD_TYPE_NULL);
     }
+    ASSERT_ALIGNED(w);
 }
 
 TEST(CursorWindowTest, StoreLong) {
@@ -134,6 +151,7 @@
         ASSERT_EQ(w->getFieldSlotType(field), CursorWindow::FIELD_TYPE_INTEGER);
         ASSERT_EQ(w->getFieldSlotValueLong(field), 0xcafe);
     }
+    ASSERT_ALIGNED(w);
 }
 
 TEST(CursorWindowTest, StoreString) {
@@ -155,6 +173,7 @@
         auto actual = w->getFieldSlotValueString(field, &size);
         ASSERT_EQ(std::string(actual), "cafe");
     }
+    ASSERT_ALIGNED(w);
 }
 
 TEST(CursorWindowTest, StoreBounds) {
@@ -175,6 +194,7 @@
     ASSERT_EQ(w->getFieldSlot(-1, 0), nullptr);
     ASSERT_EQ(w->getFieldSlot(0, -1), nullptr);
     ASSERT_EQ(w->getFieldSlot(-1, -1), nullptr);
+    ASSERT_ALIGNED(w);
 }
 
 TEST(CursorWindowTest, Inflate) {
@@ -234,6 +254,7 @@
         ASSERT_NE(actual, buf);
         ASSERT_EQ(memcmp(buf, actual, kHalfInlineSize), 0);
     }
+    ASSERT_ALIGNED(w);
 }
 
 TEST(CursorWindowTest, ParcelEmpty) {
@@ -249,10 +270,12 @@
     ASSERT_EQ(w->getNumColumns(), 0);
     ASSERT_EQ(w->size(), 0);
     ASSERT_EQ(w->freeSpace(), 0);
+    ASSERT_ALIGNED(w);
 
     // We can't mutate the window after parceling
     ASSERT_NE(w->setNumColumns(4), OK);
     ASSERT_NE(w->allocRow(), OK);
+    ASSERT_ALIGNED(w);
 }
 
 TEST(CursorWindowTest, ParcelSmall) {
@@ -311,6 +334,7 @@
         ASSERT_EQ(actualSize, 0);
         ASSERT_NE(actual, nullptr);
     }
+    ASSERT_ALIGNED(w);
 }
 
 TEST(CursorWindowTest, ParcelLarge) {
@@ -364,6 +388,7 @@
         ASSERT_EQ(actualSize, 0);
         ASSERT_NE(actual, nullptr);
     }
+    ASSERT_ALIGNED(w);
 }
 
 } // android
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/DeleteStagedFileOnResult.java b/packages/PackageInstaller/src/com/android/packageinstaller/DeleteStagedFileOnResult.java
index 7b17cbd..19d74b3 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/DeleteStagedFileOnResult.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/DeleteStagedFileOnResult.java
@@ -16,6 +16,8 @@
 
 package com.android.packageinstaller;
 
+import static android.content.Intent.CATEGORY_LAUNCHER;
+
 import static com.android.packageinstaller.PackageInstallerActivity.EXTRA_STAGED_SESSION_ID;
 
 import android.app.Activity;
@@ -45,6 +47,9 @@
     protected void onActivityResult(int requestCode, int resultCode, Intent data) {
         setResult(resultCode, data);
         finish();
+        if (data != null && data.hasCategory(CATEGORY_LAUNCHER)) {
+            startActivity(data);
+        }
     }
 
     @Override
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/InstallSuccess.java b/packages/PackageInstaller/src/com/android/packageinstaller/InstallSuccess.java
index 73c03a5..ff991d2 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/InstallSuccess.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/InstallSuccess.java
@@ -17,7 +17,6 @@
 package com.android.packageinstaller;
 
 import android.app.Activity;
-import android.content.ActivityNotFoundException;
 import android.content.DialogInterface;
 import android.content.Intent;
 import android.content.pm.ApplicationInfo;
@@ -123,11 +122,7 @@
         Button launchButton = mAlert.getButton(DialogInterface.BUTTON_POSITIVE);
         if (enabled) {
             launchButton.setOnClickListener(view -> {
-                try {
-                    startActivity(mLaunchIntent);
-                } catch (ActivityNotFoundException | SecurityException e) {
-                    Log.e(LOG_TAG, "Could not start activity", e);
-                }
+                setResult(Activity.RESULT_OK, mLaunchIntent);
                 finish();
             });
         } else {
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java b/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java
index d154156..e071c11 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java
@@ -16,8 +16,9 @@
 */
 package com.android.packageinstaller;
 
+import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TOP;
 import static android.content.Intent.FLAG_ACTIVITY_NO_HISTORY;
-import static android.content.Intent.FLAG_ACTIVITY_REORDER_TO_FRONT;
+import static android.content.Intent.FLAG_ACTIVITY_SINGLE_TOP;
 import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS;
 
 import android.Manifest;
@@ -789,7 +790,8 @@
             }
             new Handler(Looper.getMainLooper()).postDelayed(() -> {
                 if (!isDestroyed()) {
-                    startActivity(getIntent().addFlags(FLAG_ACTIVITY_REORDER_TO_FRONT));
+                    startActivity(getIntent().addFlags(
+                            FLAG_ACTIVITY_CLEAR_TOP | FLAG_ACTIVITY_SINGLE_TOP));
                 }
             }, 500);
 
diff --git a/packages/SettingsLib/Android.bp b/packages/SettingsLib/Android.bp
index 750c156..c244ca0 100644
--- a/packages/SettingsLib/Android.bp
+++ b/packages/SettingsLib/Android.bp
@@ -69,9 +69,6 @@
         "src/**/*.java",
         "src/**/*.kt",
     ],
-
-    min_sdk_version: "30",
-
 }
 
 // NOTE: Keep this module in sync with ./common.mk
diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml
index dac7f8d..9f884b2 100644
--- a/packages/SettingsLib/res/values/strings.xml
+++ b/packages/SettingsLib/res/values/strings.xml
@@ -241,12 +241,12 @@
     <!-- Bluetooth settings. Similar to bluetooth_profile_a2dp_high_quality, but used when the device supports high quality audio but we don't know which codec that will be used. -->
     <string name="bluetooth_profile_a2dp_high_quality_unknown_codec">HD audio</string>
 
-    <!-- Bluetooth settings.  The user-visible string that is used whenever referring to the Hearing Aid profile. -->
-    <string name="bluetooth_profile_hearing_aid">Hearing Aids</string>
+    <!-- Bluetooth settings.  The user-visible string that is used whenever referring to the Hearing aid profile. -->
+    <string name="bluetooth_profile_hearing_aid">Hearing aids</string>
     <!-- Bluetooth settings.  The user-visible string that is used whenever referring to the LE audio profile. -->
     <string name="bluetooth_profile_le_audio">LE Audio</string>
-    <!-- Bluetooth settings.  Connection options screen.  The summary for the Hearing Aid checkbox preference when Hearing Aid is connected. -->
-    <string name="bluetooth_hearing_aid_profile_summary_connected">Connected to Hearing Aids</string>
+    <!-- Bluetooth settings.  Connection options screen.  The summary for the Hearing aid checkbox preference when hearing aid is connected. -->
+    <string name="bluetooth_hearing_aid_profile_summary_connected">Connected to hearing aids</string>
     <!-- Bluetooth settings.  Connection options screen.  The summary for the LE audio checkbox preference when LE audio is connected. -->
     <string name="bluetooth_le_audio_profile_summary_connected">Connected to LE audio</string>
 
@@ -287,8 +287,8 @@
          for the HID checkbox preference that describes how checking it
          will set the HID profile as preferred. -->
     <string name="bluetooth_hid_profile_summary_use_for">Use for input</string>
-    <!-- Bluetooth settings.  Connection options screen.  The summary for the Hearing Aid checkbox preference that describes how checking it will set the Hearing Aid profile as preferred. -->
-    <string name="bluetooth_hearing_aid_profile_summary_use_for">Use for Hearing Aids</string>
+    <!-- Bluetooth settings.  Connection options screen.  The summary for the Hearing aid checkbox preference that describes how checking it will set the hearing aid related profile as preferred. -->
+    <string name="bluetooth_hearing_aid_profile_summary_use_for">Use for hearing aids</string>
     <!-- Bluetooth settings.  Connection options screen.  The summary for the LE_AUDIO checkbox preference that describes how checking it will set the LE_AUDIO profile as preferred. -->
     <string name="bluetooth_le_audio_profile_summary_use_for">Use for LE_AUDIO</string>
 
@@ -827,6 +827,11 @@
     <!-- UI debug setting: show touches location summary [CHAR LIMIT=50] -->
     <string name="show_touches_summary">Show visual feedback for taps</string>
 
+    <!-- UI debug setting: show key presses? [CHAR LIMIT=25] -->
+    <string name="show_key_presses">Show key presses</string>
+    <!-- UI debug setting: show physical key presses summary [CHAR LIMIT=50] -->
+    <string name="show_key_presses_summary">Show visual feedback for physical key presses</string>
+
     <!-- UI debug setting: show where surface updates happen? [CHAR LIMIT=25] -->
     <string name="show_screen_updates">Show surface updates</string>
     <!-- UI debug setting: show surface updates summary [CHAR LIMIT=50] -->
diff --git a/packages/SettingsLib/search/Android.bp b/packages/SettingsLib/search/Android.bp
index cfff519..918d696 100644
--- a/packages/SettingsLib/search/Android.bp
+++ b/packages/SettingsLib/search/Android.bp
@@ -7,8 +7,18 @@
     default_applicable_licenses: ["frameworks_base_license"],
 }
 
+java_library {
+    name: "SettingsLib-search-interface",
+    visibility: ["//visibility:private"],
+    srcs: ["interface-src/**/*.java"],
+    host_supported: true,
+}
+
 android_library {
     name: "SettingsLib-search",
+    static_libs: [
+        "SettingsLib-search-interface",
+    ],
     srcs: ["src/**/*.java"],
 
     sdk_version: "system_current",
@@ -19,12 +29,10 @@
     name: "SettingsLib-annotation-processor",
     processor_class: "com.android.settingslib.search.IndexableProcessor",
     static_libs: [
+        "SettingsLib-search-interface",
         "javapoet",
     ],
-    srcs: [
-        "processor-src/**/*.java",
-        "src/com/android/settingslib/search/SearchIndexable.java",
-    ],
+    srcs: ["processor-src/**/*.java"],
     java_resource_dirs: ["resources"],
 }
 
diff --git a/packages/SettingsLib/search/common.mk b/packages/SettingsLib/search/common.mk
deleted file mode 100644
index 05226db..0000000
--- a/packages/SettingsLib/search/common.mk
+++ /dev/null
@@ -1,10 +0,0 @@
-# Include this file to generate SearchIndexableResourcesImpl
-
-LOCAL_ANNOTATION_PROCESSORS += \
-    SettingsLib-annotation-processor
-
-LOCAL_ANNOTATION_PROCESSOR_CLASSES += \
-    com.android.settingslib.search.IndexableProcessor
-
-LOCAL_STATIC_JAVA_LIBRARIES += \
-    SettingsLib-search
diff --git a/packages/SettingsLib/search/src/com/android/settingslib/search/SearchIndexable.java b/packages/SettingsLib/search/interface-src/com/android/settingslib/search/SearchIndexable.java
similarity index 92%
rename from packages/SettingsLib/search/src/com/android/settingslib/search/SearchIndexable.java
rename to packages/SettingsLib/search/interface-src/com/android/settingslib/search/SearchIndexable.java
index 638fa3e9..174f337 100644
--- a/packages/SettingsLib/search/src/com/android/settingslib/search/SearchIndexable.java
+++ b/packages/SettingsLib/search/interface-src/com/android/settingslib/search/SearchIndexable.java
@@ -27,7 +27,7 @@
     /**
      * Bitfield for the form factors this class should be considered indexable for.
      * Default is {@link #ALL}.
-     *
+     * <p>
      * TODO: actually use this value somehow
      */
     int forTarget() default ALL;
@@ -35,27 +35,27 @@
     /**
      * Indicates that the class should be considered indexable for Mobile.
      */
-    int MOBILE = 1<<0;
+    int MOBILE = 1 << 0;
 
     /**
      * Indicates that the class should be considered indexable for TV.
      */
-    int TV = 1<<1;
+    int TV = 1 << 1;
 
     /**
      * Indicates that the class should be considered indexable for Wear.
      */
-    int WEAR = 1<<2;
+    int WEAR = 1 << 2;
 
     /**
      * Indicates that the class should be considered indexable for Auto.
      */
-    int AUTO = 1<<3;
+    int AUTO = 1 << 3;
 
     /**
      * Indicates that the class should be considered indexable for ARC++.
      */
-    int ARC = 1<<4;
+    int ARC = 1 << 4;
 
     /**
      * Indicates that the class should be considered indexable for all targets.
diff --git a/packages/SettingsLib/search/processor-src/com/android/settingslib/search/IndexableProcessor.java b/packages/SettingsLib/search/processor-src/com/android/settingslib/search/IndexableProcessor.java
index e92157e..fa43915 100644
--- a/packages/SettingsLib/search/processor-src/com/android/settingslib/search/IndexableProcessor.java
+++ b/packages/SettingsLib/search/processor-src/com/android/settingslib/search/IndexableProcessor.java
@@ -34,6 +34,7 @@
 import javax.annotation.processing.ProcessingEnvironment;
 import javax.annotation.processing.RoundEnvironment;
 import javax.annotation.processing.SupportedAnnotationTypes;
+import javax.annotation.processing.SupportedOptions;
 import javax.annotation.processing.SupportedSourceVersion;
 import javax.lang.model.SourceVersion;
 import javax.lang.model.element.Element;
@@ -48,10 +49,11 @@
  * subclasses.
  */
 @SupportedSourceVersion(SourceVersion.RELEASE_17)
+@SupportedOptions(IndexableProcessor.PACKAGE_KEY)
 @SupportedAnnotationTypes({"com.android.settingslib.search.SearchIndexable"})
 public class IndexableProcessor extends AbstractProcessor {
 
-    private static final String PACKAGE = "com.android.settingslib.search";
+    private static final String SETTINGSLIB_SEARCH_PACKAGE = "com.android.settingslib.search";
     private static final String CLASS_BASE = "SearchIndexableResourcesBase";
     private static final String CLASS_MOBILE = "SearchIndexableResourcesMobile";
     private static final String CLASS_TV = "SearchIndexableResourcesTv";
@@ -59,6 +61,9 @@
     private static final String CLASS_AUTO = "SearchIndexableResourcesAuto";
     private static final String CLASS_ARC = "SearchIndexableResourcesArc";
 
+    static final String PACKAGE_KEY = "com.android.settingslib.search.processor.package";
+
+    private String mPackage;
     private Filer mFiler;
     private Messager mMessager;
     private boolean mRanOnce;
@@ -72,7 +77,8 @@
         }
         mRanOnce = true;
 
-        final ClassName searchIndexableData = ClassName.get(PACKAGE, "SearchIndexableData");
+        final ClassName searchIndexableData =
+                ClassName.get(SETTINGSLIB_SEARCH_PACKAGE, "SearchIndexableData");
 
         final FieldSpec providers = FieldSpec.builder(
                 ParameterizedTypeName.get(
@@ -130,7 +136,7 @@
                         builder = arcConstructorBuilder;
                     }
                     builder.addCode(
-                            "$N(new SearchIndexableData($L.class, $L"
+                            "$N(new com.android.settingslib.search.SearchIndexableData($L.class, $L"
                                     + ".SEARCH_INDEX_DATA_PROVIDER));\n",
                             addIndex, className, className);
                 } else {
@@ -150,50 +156,51 @@
 
         final TypeSpec baseClass = TypeSpec.classBuilder(CLASS_BASE)
                 .addModifiers(Modifier.PUBLIC)
-                .addSuperinterface(ClassName.get(PACKAGE, "SearchIndexableResources"))
+                .addSuperinterface(
+                        ClassName.get(SETTINGSLIB_SEARCH_PACKAGE, "SearchIndexableResources"))
                 .addField(providers)
                 .addMethod(baseConstructorBuilder.build())
                 .addMethod(addIndex)
                 .addMethod(getProviderValues)
                 .build();
-        final JavaFile searchIndexableResourcesBase = JavaFile.builder(PACKAGE, baseClass).build();
+        final JavaFile searchIndexableResourcesBase = JavaFile.builder(mPackage, baseClass).build();
 
-        final JavaFile searchIndexableResourcesMobile = JavaFile.builder(PACKAGE,
+        final JavaFile searchIndexableResourcesMobile = JavaFile.builder(mPackage,
                 TypeSpec.classBuilder(CLASS_MOBILE)
                         .addModifiers(Modifier.PUBLIC)
-                        .superclass(ClassName.get(PACKAGE, baseClass.name))
+                        .superclass(ClassName.get(mPackage, baseClass.name))
                         .addMethod(mobileConstructorBuilder.build())
                         .build())
                 .build();
 
-        final JavaFile searchIndexableResourcesTv = JavaFile.builder(PACKAGE,
+        final JavaFile searchIndexableResourcesTv = JavaFile.builder(mPackage,
                 TypeSpec.classBuilder(CLASS_TV)
                         .addModifiers(Modifier.PUBLIC)
-                        .superclass(ClassName.get(PACKAGE, baseClass.name))
+                        .superclass(ClassName.get(mPackage, baseClass.name))
                         .addMethod(tvConstructorBuilder.build())
                         .build())
                 .build();
 
-        final JavaFile searchIndexableResourcesWear = JavaFile.builder(PACKAGE,
+        final JavaFile searchIndexableResourcesWear = JavaFile.builder(mPackage,
                 TypeSpec.classBuilder(CLASS_WEAR)
                         .addModifiers(Modifier.PUBLIC)
-                        .superclass(ClassName.get(PACKAGE, baseClass.name))
+                        .superclass(ClassName.get(mPackage, baseClass.name))
                         .addMethod(wearConstructorBuilder.build())
                         .build())
                 .build();
 
-        final JavaFile searchIndexableResourcesAuto = JavaFile.builder(PACKAGE,
+        final JavaFile searchIndexableResourcesAuto = JavaFile.builder(mPackage,
                 TypeSpec.classBuilder(CLASS_AUTO)
                         .addModifiers(Modifier.PUBLIC)
-                        .superclass(ClassName.get(PACKAGE, baseClass.name))
+                        .superclass(ClassName.get(mPackage, baseClass.name))
                         .addMethod(autoConstructorBuilder.build())
                         .build())
                 .build();
 
-        final JavaFile searchIndexableResourcesArc = JavaFile.builder(PACKAGE,
+        final JavaFile searchIndexableResourcesArc = JavaFile.builder(mPackage,
                 TypeSpec.classBuilder(CLASS_ARC)
                         .addModifiers(Modifier.PUBLIC)
-                        .superclass(ClassName.get(PACKAGE, baseClass.name))
+                        .superclass(ClassName.get(mPackage, baseClass.name))
                         .addMethod(arcConstructorBuilder.build())
                         .build())
                 .build();
@@ -214,6 +221,8 @@
     @Override
     public synchronized void init(ProcessingEnvironment processingEnvironment) {
         super.init(processingEnvironment);
+        mPackage = processingEnvironment.getOptions()
+                .getOrDefault(PACKAGE_KEY, SETTINGSLIB_SEARCH_PACKAGE);
         mFiler = processingEnvironment.getFiler();
         mMessager = processingEnvironment.getMessager();
     }
diff --git a/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java b/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java
index fe89883..6eb2f38 100644
--- a/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java
+++ b/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java
@@ -484,8 +484,9 @@
         if (DEBUG_LOCKING) Log.v(TAG, "getEntry about to acquire lock...");
         synchronized (mEntriesMap) {
             AppEntry entry = null;
-            if (mEntriesMap.contains(userId)) {
-                entry = mEntriesMap.get(userId).get(packageName);
+            HashMap<String, AppEntry> userEntriesMap = mEntriesMap.get(userId);
+            if (userEntriesMap != null) {
+                entry = userEntriesMap.get(packageName);
             }
             if (entry == null) {
                 ApplicationInfo info = getAppInfoLocked(packageName, userId);
@@ -735,8 +736,9 @@
     private AppEntry getEntryLocked(ApplicationInfo info) {
         int userId = UserHandle.getUserId(info.uid);
         AppEntry entry = null;
-        if (mEntriesMap.contains(userId)) {
-            entry = mEntriesMap.get(userId).get(info.packageName);
+        HashMap<String, AppEntry> userEntriesMap = mEntriesMap.get(userId);
+        if (userEntriesMap != null) {
+            entry = userEntriesMap.get(info.packageName);
         }
         if (DEBUG) {
             Log.i(TAG, "Looking up entry of pkg " + info.packageName + ": " + entry);
@@ -752,8 +754,11 @@
                 Log.i(TAG, "Creating AppEntry for " + info.packageName);
             }
             entry = new AppEntry(mContext, info, mCurId++);
-            mEntriesMap.get(userId).put(info.packageName, entry);
-            mAppEntries.add(entry);
+            userEntriesMap = mEntriesMap.get(userId);
+            if (userEntriesMap != null) {
+                userEntriesMap.put(info.packageName, entry);
+                mAppEntries.add(entry);
+            }
         } else if (entry.info != info) {
             entry.info = info;
         }
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/ApplicationsStateRoboTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/ApplicationsStateRoboTest.java
index 1d081d7..34d8148 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/ApplicationsStateRoboTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/ApplicationsStateRoboTest.java
@@ -804,7 +804,7 @@
     }
 
     @Test
-    public void getEntry_validUserId_shouldReturnEntry() {
+    public void getEntry_hasCache_shouldReturnCacheEntry() {
         mApplicationsState.mEntriesMap.put(/* userId= */ 0, new HashMap<>());
         addApp(PKG_1, /* id= */ 1);
 
@@ -813,10 +813,13 @@
     }
 
     @Test
-    public void getEntry_invalidUserId_shouldReturnNull() {
-        mApplicationsState.mEntriesMap.put(/* userId= */ 0, new HashMap<>());
-        addApp(PKG_1, /* id= */ 1);
+    public void getEntry_hasNoCache_shouldReturnEntry() {
+        mApplicationsState.mEntriesMap.clear();
+        ApplicationInfo appInfo = createApplicationInfo(PKG_1, /* uid= */ 0);
+        mApplicationsState.mApplications.add(appInfo);
+        mApplicationsState.mSystemModules.put(PKG_1, /* value= */ false);
 
-        assertThat(mApplicationsState.getEntry(PKG_1, /* userId= */ -1)).isNull();
+        assertThat(mApplicationsState.getEntry(PKG_1, /* userId= */ 0).info.packageName)
+                .isEqualTo(PKG_1);
     }
 }
diff --git a/packages/SystemUI/TEST_MAPPING b/packages/SystemUI/TEST_MAPPING
index aee829d..01e6bf0 100644
--- a/packages/SystemUI/TEST_MAPPING
+++ b/packages/SystemUI/TEST_MAPPING
@@ -1,6 +1,22 @@
 {
-  // Looking for unit test presubmit configuration?
-  // This currently lives in ATP config apct/system_ui/unit_test
+  // Curious where your @Scenario tests will run?
+  //
+  // @Ignore: Will not run in any configuration
+  //
+  // @FlakyTest: Tests that don't block pre/postsubmit but are staged to run known failures
+  //
+  // @Postsubmit: Runs in platinum suite and blocks droidfood in postsubmit
+  //
+  // @PlatinumTest: As of May, 2023, running in postsubmit. Set to run in presubmit as part of
+  // v2/android-platinum/suite-test-mapping-platinum-sysui
+  // Please DO NOT annotate new or old tests with @PlatinumTest annotation without discussing
+  // with mdb:android-platinum
+  //
+  // As of May, 2023, If you don't use @Postsubmit, your new test will immediately
+  // block presubmit, which is probably NOT what you want. This will change effectively once
+  // we move to @PlatinumTest annotation.
+
+  // v2/sysui/test-mapping-presubmit-sysui_cloud-tf
   "presubmit-sysui": [
     {
       "name": "PlatformScenarioTests",
@@ -23,6 +39,7 @@
       ]
     }
   ],
+  // v2/android-virtual-infra/test_mapping/presubmit-avd
   "presubmit": [
     {
       "name": "SystemUIGoogleTests",
@@ -75,21 +92,6 @@
       ]
     }
   ],
-
-  // Curious where your @Scenario tests will run?
-  //
-  // @Ignore: nowhere
-  // @FlakyTest: in staged-postsubmit, but not blocking postsubmit or
-  // presubmit
-  // @Postsubmit: in postsubmit and staged-postsubmit, but not presubmit
-  // none of the above: in presubmit, postsubmit, and staged-postsubmit
-  //
-  // Ideally, please annotate new tests with @FlakyTest, then with @Postsubmit
-  // once they're ready for postsubmit as they will immediately block go/android-platinum,
-  // then with neither once they're ready for presubmit.
-  //
-  // If you don't use @Postsubmit, your new test will immediately
-  // block presubmit, which is probably not what you want!
   "auto-end-to-end-postsubmit": [
     {
       "name": "AndroidAutomotiveHomeTests",
@@ -107,5 +109,27 @@
         }
       ]
     }
+  ],
+  "silver-sysui": [
+   {
+      "name": "PlatformScenarioTests",
+      "options": [
+        {
+            "include-filter": "android.platform.test.scenario.sysui"
+        },
+        {
+            "include-annotation": "android.platform.test.scenario.annotation.Scenario"
+        },
+        {
+            "exclude-annotation": "org.junit.Ignore"
+        },
+        {
+            "exclude-annotation": "android.platform.test.annotations.PlatinumTest"
+        },
+        {
+            "exclude-annotation": "android.platform.test.annotations.FlakyTest"
+        }
+      ]
+    }
   ]
 }
diff --git a/packages/SystemUI/res/layout/window_magnification_settings_view.xml b/packages/SystemUI/res/layout/window_magnification_settings_view.xml
index a8febe7..efdb0a3 100644
--- a/packages/SystemUI/res/layout/window_magnification_settings_view.xml
+++ b/packages/SystemUI/res/layout/window_magnification_settings_view.xml
@@ -147,10 +147,12 @@
         android:id="@+id/magnifier_zoom_slider"
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
-        app:max="6"
         app:progress="0"
         app:iconStartContentDescription="@string/accessibility_control_zoom_out"
-        app:iconEndContentDescription="@string/accessibility_control_zoom_in"/>
+        app:iconEndContentDescription="@string/accessibility_control_zoom_in"
+        app:tickMark="@android:color/transparent"
+        app:seekBarChangeMagnitude="10"
+    />
 
     <Button
         android:id="@+id/magnifier_done_button"
diff --git a/packages/SystemUI/res/raw/fingerprint_dialogue_unlocked_to_checkmark_success_lottie.json b/packages/SystemUI/res/raw/fingerprint_dialogue_unlocked_to_checkmark_success_lottie.json
new file mode 100644
index 0000000..b1d6a27
--- /dev/null
+++ b/packages/SystemUI/res/raw/fingerprint_dialogue_unlocked_to_checkmark_success_lottie.json
@@ -0,0 +1 @@
+{"v":"5.7.13","fr":60,"ip":0,"op":55,"w":80,"h":80,"nm":"unlocked_to_checkmark_success","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":".colorAccentPrimary","cl":"colorAccentPrimary","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[47.143,32,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[2.761,0],[0,-2.7],[0,0]],"o":[[0,0],[0,-2.7],[-2.761,0],[0,0],[0,0]],"v":[[5,5],[5,-0.111],[0,-5],[-5,-0.111],[-5.01,4]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.827450990677,0.890196084976,0.992156863213,1],"ix":3},"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[100]},{"t":10,"s":[0]}],"ix":4},"w":{"a":0,"k":2.5,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":85,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":".colorAccentPrimary","cl":"colorAccentPrimary","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[38,45,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[18,16],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":2,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"st","c":{"a":0,"k":[0.827450990677,0.890196084976,0.992156863213,1],"ix":3},"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[100]},{"t":10,"s":[0]}],"ix":4},"w":{"a":0,"k":2.5,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":85,"st":0,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":".colorAccentPrimary","cl":"colorAccentPrimary","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[37.999,44.999,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-1.42,0],[0,1.42],[1.42,0],[0,-1.42]],"o":[[1.42,0],[0,-1.42],[-1.42,0],[0,1.42]],"v":[[0,2.571],[2.571,0],[0,-2.571],[-2.571,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.827450990677,0.890196084976,0.992156863213,1],"ix":4},"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[100]},{"t":10,"s":[0]}],"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":85,"st":0,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":".green200","cl":"green200","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[40.5,40.75,0],"ix":2,"l":2},"a":{"a":0,"k":[12.5,-6.25,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":10,"s":[60,60,100]},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":19,"s":[112,112,100]},{"t":30,"s":[100,100,100]}],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-10.556,-9.889],[7.444,6.555],[34.597,-20.486]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.658823529412,0.854901960784,0.709803921569,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":4,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Shape 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":0,"k":0,"ix":1},"e":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":10,"s":[0]},{"t":20,"s":[100]}],"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":2,"nm":"Trim Paths 1","mn":"ADBE Vector Filter - Trim","hd":false}],"ip":10,"op":910,"st":10,"bm":0},{"ddd":0,"ind":5,"ty":4,"nm":".green200","cl":"green200","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":10,"s":[0]},{"t":15,"s":[100]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[40,40,0],"ix":2,"l":2},"a":{"a":0,"k":[40.25,40.25,0],"ix":1,"l":2},"s":{"a":0,"k":[93.5,93.5,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[22.12,0],[0,-22.08],[-22.08,0],[0,22.08]],"o":[[-22.08,0],[0,22.08],[22.12,0],[0,-22.08]],"v":[[-0.04,-40],[-40,0],[-0.04,40],[40,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tr","p":{"a":0,"k":[40.25,40.25],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 2","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.658823529412,0.854901960784,0.709803921569,1],"ix":3},"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":10,"s":[0]},{"t":15,"s":[100]}],"ix":4},"w":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":10,"s":[0]},{"t":20,"s":[4]}],"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false}],"ip":10,"op":77,"st":10,"bm":0},{"ddd":0,"ind":6,"ty":4,"nm":".grey700","cl":"grey700","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[40,40,0],"ix":2,"l":2},"a":{"a":0,"k":[40.25,40.25,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[22.12,0],[0,-22.08],[-22.08,0],[0,22.08]],"o":[[-22.08,0],[0,22.08],[22.12,0],[0,-22.08]],"v":[[-0.04,-40],[-40,0],[-0.04,40],[40,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.278431385756,0.278431385756,0.278431385756,1],"ix":4},"o":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":0,"s":[100]},{"t":20,"s":[0]}],"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[40.25,40.25],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 2","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":600,"st":0,"bm":0}],"markers":[]}
\ No newline at end of file
diff --git a/packages/SystemUI/res/values-night/colors.xml b/packages/SystemUI/res/values-night/colors.xml
index 5efcef3..99311e3 100644
--- a/packages/SystemUI/res/values-night/colors.xml
+++ b/packages/SystemUI/res/values-night/colors.xml
@@ -100,4 +100,8 @@
     <color name="accessibility_floating_menu_message_text">@*android:color/primary_text_default_material_dark</color>
 
     <color name="people_tile_background">@color/material_dynamic_secondary20</color>
+
+    <!-- Internet Dialog -->
+    <color name="connected_network_primary_color">@color/material_dynamic_primary80</color>
+    <color name="connected_network_secondary_color">@color/material_dynamic_secondary80</color>
 </resources>
diff --git a/packages/SystemUI/res/values-night/styles.xml b/packages/SystemUI/res/values-night/styles.xml
index 99bc794..b6971d3 100644
--- a/packages/SystemUI/res/values-night/styles.xml
+++ b/packages/SystemUI/res/values-night/styles.xml
@@ -58,17 +58,13 @@
     <style name="TextAppearance.InternetDialog.Active">
         <item name="android:fontFamily">@*android:string/config_headlineFontFamily</item>
         <item name="android:textSize">16sp</item>
-        <item name="android:textColor">?android:attr/textColorPrimaryInverse</item>
+        <item name="android:textColor">@color/material_dynamic_primary80</item>
         <item name="android:textDirection">locale</item>
     </style>
 
     <style name="TextAppearance.InternetDialog.Secondary.Active">
         <item name="android:textSize">14sp</item>
-        <item name="android:textColor">?android:attr/textColorSecondaryInverse</item>
-    </style>
-
-    <style name="InternetDialog.Divider.Active">
-        <item name="android:background">?android:attr/textColorSecondaryInverse</item>
+        <item name="android:textColor">@color/material_dynamic_secondary80</item>
     </style>
 
 </resources>
diff --git a/packages/SystemUI/res/values/attrs.xml b/packages/SystemUI/res/values/attrs.xml
index bd86e51..3a1d1a8 100644
--- a/packages/SystemUI/res/values/attrs.xml
+++ b/packages/SystemUI/res/values/attrs.xml
@@ -216,6 +216,8 @@
         <attr name="progress" format="integer" />
         <attr name="iconStartContentDescription" format="reference" />
         <attr name="iconEndContentDescription" format="reference" />
+        <attr name="tickMark" format="reference" />
+        <attr name="seekBarChangeMagnitude" format="integer" />
     </declare-styleable>
 </resources>
 
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index c619d46..ab789d9 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -613,8 +613,8 @@
     <string name="quick_settings_bluetooth_secondary_label_headset">Headset</string>
     <!-- QuickSettings: Bluetooth secondary label for an input/IO device being connected [CHAR LIMIT=20]-->
     <string name="quick_settings_bluetooth_secondary_label_input">Input</string>
-    <!-- QuickSettings: Bluetooth secondary label for a Hearing Aids device being connected [CHAR LIMIT=20]-->
-    <string name="quick_settings_bluetooth_secondary_label_hearing_aids">Hearing Aids</string>
+    <!-- QuickSettings: Bluetooth secondary label for a Hearing aids device being connected [CHAR LIMIT=20]-->
+    <string name="quick_settings_bluetooth_secondary_label_hearing_aids">Hearing aids</string>
     <!-- QuickSettings: Bluetooth secondary label shown when bluetooth is being enabled [CHAR LIMIT=NONE] -->
     <string name="quick_settings_bluetooth_secondary_label_transient">Turning on&#8230;</string>
     <!-- QuickSettings: Brightness [CHAR LIMIT=NONE] -->
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index cee2135..cb5342a 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -1183,12 +1183,6 @@
 
     <style name="TextAppearance.InternetDialog.Secondary.Active"/>
 
-    <style name="InternetDialog.Divider">
-        <item name="android:background">?android:attr/textColorSecondary</item>
-    </style>
-
-    <style name="InternetDialog.Divider.Active"/>
-
     <style name="FgsManagerDialogTitle">
         <item name="android:fontFamily">@*android:string/config_bodyFontFamily</item>
         <item name="android:textStyle">bold</item>
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
index 4b02171..4211f55 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
@@ -1226,15 +1226,16 @@
                 constraintSet.constrainHeight(mViewFlipper.getId(), MATCH_CONSTRAINT);
                 constraintSet.applyTo(mView);
             } else {
-                int leftElement = leftAlign ? mViewFlipper.getId() : mUserSwitcherViewGroup.getId();
-                int rightElement =
+                int startElement =
+                        leftAlign ? mViewFlipper.getId() : mUserSwitcherViewGroup.getId();
+                int endElement =
                         leftAlign ? mUserSwitcherViewGroup.getId() : mViewFlipper.getId();
 
                 ConstraintSet constraintSet = new ConstraintSet();
-                constraintSet.connect(leftElement, LEFT, PARENT_ID, LEFT);
-                constraintSet.connect(leftElement, RIGHT, rightElement, LEFT);
-                constraintSet.connect(rightElement, LEFT, leftElement, RIGHT);
-                constraintSet.connect(rightElement, RIGHT, PARENT_ID, RIGHT);
+                constraintSet.connect(startElement, START, PARENT_ID, START);
+                constraintSet.connect(startElement, END, endElement, START);
+                constraintSet.connect(endElement, START, startElement, END);
+                constraintSet.connect(endElement, END, PARENT_ID, END);
                 constraintSet.connect(mUserSwitcherViewGroup.getId(), TOP, PARENT_ID, TOP);
                 constraintSet.connect(mUserSwitcherViewGroup.getId(), BOTTOM, PARENT_ID, BOTTOM);
                 constraintSet.connect(mViewFlipper.getId(), TOP, PARENT_ID, TOP);
diff --git a/packages/SystemUI/src/com/android/systemui/SwipeHelper.java b/packages/SystemUI/src/com/android/systemui/SwipeHelper.java
index 99d4662..c30a214 100644
--- a/packages/SystemUI/src/com/android/systemui/SwipeHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/SwipeHelper.java
@@ -20,6 +20,7 @@
 import static androidx.dynamicanimation.animation.FloatPropertyCompat.createFloatPropertyCompat;
 
 import static com.android.systemui.classifier.Classifier.NOTIFICATION_DISMISS;
+import static com.android.systemui.statusbar.notification.NotificationUtils.logKey;
 
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
@@ -54,9 +55,10 @@
 import com.android.wm.shell.animation.PhysicsAnimator;
 import com.android.wm.shell.animation.PhysicsAnimator.SpringConfig;
 
+import java.io.PrintWriter;
 import java.util.function.Consumer;
 
-public class SwipeHelper implements Gefingerpoken {
+public class SwipeHelper implements Gefingerpoken, Dumpable {
     static final String TAG = "com.android.systemui.SwipeHelper";
     private static final boolean DEBUG_INVALIDATE = false;
     private static final boolean CONSTRAIN_SWIPE = true;
@@ -844,6 +846,31 @@
         return false;
     }
 
+    @Override
+    public void dump(@NonNull PrintWriter pw, @NonNull String[] args) {
+        pw.append("mTouchedView=").print(mTouchedView);
+        if (mTouchedView instanceof ExpandableNotificationRow) {
+            pw.append(" key=").println(logKey((ExpandableNotificationRow) mTouchedView));
+        } else {
+            pw.println();
+        }
+        pw.append("mIsSwiping=").println(mIsSwiping);
+        pw.append("mSnappingChild=").println(mSnappingChild);
+        pw.append("mLongPressSent=").println(mLongPressSent);
+        pw.append("mInitialTouchPos=").println(mInitialTouchPos);
+        pw.append("mTranslation=").println(mTranslation);
+        pw.append("mCanCurrViewBeDimissed=").println(mCanCurrViewBeDimissed);
+        pw.append("mMenuRowIntercepting=").println(mMenuRowIntercepting);
+        pw.append("mDisableHwLayers=").println(mDisableHwLayers);
+        pw.append("mDismissPendingMap: ").println(mDismissPendingMap.size());
+        if (!mDismissPendingMap.isEmpty()) {
+            mDismissPendingMap.forEach((view, animator) -> {
+                pw.append("  ").print(view);
+                pw.append(": ").println(animator);
+            });
+        }
+    }
+
     public interface Callback {
         View getChildAtPosition(MotionEvent ev);
 
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java
index 7c76588..e7eab7e 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java
@@ -76,6 +76,7 @@
 
 import androidx.core.math.MathUtils;
 
+import com.android.internal.accessibility.common.MagnificationConstants;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.graphics.SfVsyncFrameCallbackProvider;
 import com.android.systemui.R;
@@ -101,7 +102,9 @@
     // Delay to avoid updating state description too frequently.
     private static final int UPDATE_STATE_DESCRIPTION_DELAY_MS = 100;
     // It should be consistent with the value defined in WindowMagnificationGestureHandler.
-    private static final Range<Float> A11Y_ACTION_SCALE_RANGE = new Range<>(1.0f, 8.0f);
+    private static final Range<Float> A11Y_ACTION_SCALE_RANGE = new Range<>(
+            MagnificationConstants.SCALE_MIN_VALUE,
+            MagnificationConstants.SCALE_MAX_VALUE);
     private static final float A11Y_CHANGE_SCALE_DIFFERENCE = 1.0f;
     private static final float ANIMATION_BOUNCE_EFFECT_SCALE = 1.05f;
     private final SparseArray<Float> mMagnificationSizeScaleOptions = new SparseArray<>();
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationSettings.java b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationSettings.java
index d27087d..b086912 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationSettings.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationSettings.java
@@ -22,6 +22,9 @@
 import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW;
 import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
 
+import static com.android.internal.accessibility.common.MagnificationConstants.SCALE_MAX_VALUE;
+import static com.android.internal.accessibility.common.MagnificationConstants.SCALE_MIN_VALUE;
+
 import android.annotation.IntDef;
 import android.content.BroadcastReceiver;
 import android.content.Context;
@@ -87,7 +90,8 @@
     private final MagnificationGestureDetector mGestureDetector;
     private boolean mSingleTapDetected = false;
 
-    private SeekBarWithIconButtonsView mZoomSeekbar;
+    @VisibleForTesting
+    SeekBarWithIconButtonsView mZoomSeekbar;
     private LinearLayout mAllowDiagonalScrollingView;
     private TextView mAllowDiagonalScrollingTitle;
     private Switch mAllowDiagonalScrollingSwitch;
@@ -102,11 +106,15 @@
     private ImageButton mFullScreenButton;
     private int mLastSelectedButtonIndex = MagnificationSize.NONE;
     private boolean mAllowDiagonalScrolling = false;
-    private static final float A11Y_CHANGE_SCALE_DIFFERENCE = 1.0f;
-    private static final float A11Y_SCALE_MIN_VALUE = 1.0f;
+    /**
+     * Amount by which magnification scale changes compared to seekbar in settings.
+     * magnitude = 10 means, for every 1 scale increase, 10 progress increase in seekbar.
+     */
+    private int mSeekBarMagnitude;
     private WindowMagnificationSettingsCallback mCallback;
 
     private ContentObserver mMagnificationCapabilityObserver;
+    private ContentObserver mMagnificationScaleObserver;
 
     @Retention(RetentionPolicy.SOURCE)
     @IntDef({
@@ -156,18 +164,26 @@
                 });
             }
         };
+        mMagnificationScaleObserver = new ContentObserver(
+                mContext.getMainThreadHandler()) {
+            @Override
+            public void onChange(boolean selfChange) {
+                setScaleSeekbar(getMagnificationScale());
+            }
+        };
     }
 
     private class ZoomSeekbarChangeListener implements SeekBar.OnSeekBarChangeListener {
         @Override
         public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
-            float scale = progress * A11Y_CHANGE_SCALE_DIFFERENCE + A11Y_SCALE_MIN_VALUE;
+            float scale = (progress / (float) mSeekBarMagnitude) + SCALE_MIN_VALUE;
             // Update persisted scale only when scale >= PERSISTED_SCALE_MIN_VALUE const.
             // We assume if the scale is lower than the PERSISTED_SCALE_MIN_VALUE, there will be
             // no obvious magnification effect.
             if (scale >= MagnificationConstants.PERSISTED_SCALE_MIN_VALUE) {
-                Settings.Secure.putFloatForUser(mContext.getContentResolver(),
-                        Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_SCALE, scale,
+                mSecureSettings.putFloatForUser(
+                        Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_SCALE,
+                        scale,
                         UserHandle.USER_CURRENT);
             }
             mCallback.onMagnifierScale(scale);
@@ -301,6 +317,7 @@
 
         // Unregister observer before removing view
         mSecureSettings.unregisterContentObserver(mMagnificationCapabilityObserver);
+        mSecureSettings.unregisterContentObserver(mMagnificationScaleObserver);
         mWindowManager.removeView(mSettingView);
         mIsVisible = false;
         if (resetPosition) {
@@ -329,7 +346,13 @@
     }
 
     public void setScaleSeekbar(float scale) {
-        setSeekbarProgress(scale);
+        int index = (int) ((scale - SCALE_MIN_VALUE) * mSeekBarMagnitude);
+        if (index < 0) {
+            index = 0;
+        } else if (index > mZoomSeekbar.getMax()) {
+            index = mZoomSeekbar.getMax();
+        }
+        mZoomSeekbar.setProgress(index);
     }
 
     private void transitToMagnificationMode(int mode) {
@@ -346,6 +369,7 @@
     private void showSettingPanel(boolean resetPosition) {
         if (!mIsVisible) {
             updateUIControlsIfNeeded();
+            setScaleSeekbar(getMagnificationScale());
             if (resetPosition) {
                 mDraggableWindowBounds.set(getDraggableWindowBounds());
                 mParams.x = mDraggableWindowBounds.right;
@@ -358,6 +382,10 @@
                     Settings.Secure.ACCESSIBILITY_MAGNIFICATION_CAPABILITY,
                     mMagnificationCapabilityObserver,
                     UserHandle.USER_CURRENT);
+            mSecureSettings.registerContentObserverForUser(
+                    Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_SCALE,
+                    mMagnificationScaleObserver,
+                    UserHandle.USER_CURRENT);
 
             // Exclude magnification switch button from system gesture area.
             setSystemGestureExclusion();
@@ -390,9 +418,15 @@
                 UserHandle.USER_CURRENT);
     }
 
+    private float getMagnificationScale() {
+        return mSecureSettings.getFloatForUser(
+                Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_SCALE,
+                SCALE_MIN_VALUE,
+                UserHandle.USER_CURRENT);
+    }
+
     private void updateUIControlsIfNeeded() {
         int capability = getMagnificationCapability();
-
         int selectedButtonIndex = mLastSelectedButtonIndex;
         switch (capability) {
             case ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW:
@@ -453,14 +487,6 @@
         }
     };
 
-    private void setSeekbarProgress(float scale) {
-        int index = (int) ((scale - A11Y_SCALE_MIN_VALUE) / A11Y_CHANGE_SCALE_DIFFERENCE);
-        if (index < 0) {
-            index = 0;
-        }
-        mZoomSeekbar.setProgress(index);
-    }
-
     void inflateView() {
         mSettingView = (LinearLayout) View.inflate(mContext,
                 R.layout.window_magnification_settings_view, null);
@@ -482,10 +508,13 @@
                 mSettingView.findViewById(R.id.magnifier_horizontal_lock_title);
 
         mZoomSeekbar = mSettingView.findViewById(R.id.magnifier_zoom_slider);
+        mZoomSeekbar.setMax((int) (mZoomSeekbar.getChangeMagnitude()
+                * (SCALE_MAX_VALUE - SCALE_MIN_VALUE)));
+        mSeekBarMagnitude = mZoomSeekbar.getChangeMagnitude();
         float scale = mSecureSettings.getFloatForUser(
                 Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_SCALE, 0,
                 UserHandle.USER_CURRENT);
-        setSeekbarProgress(scale);
+        setScaleSeekbar(scale);
         mZoomSeekbar.setOnSeekBarChangeListener(new ZoomSeekbarChangeListener());
 
         mAllowDiagonalScrollingView =
@@ -601,7 +630,7 @@
 
     @VisibleForTesting
     void setDiagonalScrolling(boolean enabled) {
-        Settings.Secure.putIntForUser(mContext.getContentResolver(),
+        mSecureSettings.putIntForUser(
                 Settings.Secure.ACCESSIBILITY_ALLOW_DIAGONAL_SCROLLING, enabled ? 1 : 0,
                 UserHandle.USER_CURRENT);
 
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintAndFaceIconController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintAndFaceIconController.kt
index 682888f..95610ae 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintAndFaceIconController.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintAndFaceIconController.kt
@@ -21,6 +21,7 @@
 import com.airbnb.lottie.LottieAnimationView
 import com.android.systemui.R
 import com.android.systemui.biometrics.AuthBiometricView.BiometricState
+import com.android.systemui.biometrics.AuthBiometricView.STATE_AUTHENTICATED
 import com.android.systemui.biometrics.AuthBiometricView.STATE_ERROR
 import com.android.systemui.biometrics.AuthBiometricView.STATE_HELP
 import com.android.systemui.biometrics.AuthBiometricView.STATE_PENDING_CONFIRMATION
@@ -47,13 +48,16 @@
         @BiometricState oldState: Int,
         @BiometricState newState: Int
     ): Int? = when (newState) {
+        STATE_AUTHENTICATED -> {
+           if (oldState == STATE_PENDING_CONFIRMATION) {
+               R.raw.fingerprint_dialogue_unlocked_to_checkmark_success_lottie
+           } else {
+               super.getAnimationForTransition(oldState, newState)
+           }
+        }
         STATE_PENDING_CONFIRMATION -> {
             if (oldState == STATE_ERROR || oldState == STATE_HELP) {
                 R.raw.fingerprint_dialogue_error_to_unlock_lottie
-            } else if (oldState == STATE_PENDING_CONFIRMATION) {
-                // TODO(jbolinger): missing asset for this transition
-                // (unlocked icon to success checkmark)
-                R.raw.fingerprint_dialogue_fingerprint_to_unlock_lottie
             } else {
                 R.raw.fingerprint_dialogue_fingerprint_to_unlock_lottie
             }
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsController.kt
index 630cfff..baaa96e 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsController.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsController.kt
@@ -15,8 +15,6 @@
  */
 package com.android.systemui.biometrics
 
-import android.animation.Animator
-import android.animation.AnimatorListenerAdapter
 import android.app.ActivityTaskManager
 import android.content.Context
 import android.content.res.Configuration
@@ -43,7 +41,6 @@
 import android.view.View
 import android.view.View.AccessibilityDelegate
 import android.view.ViewPropertyAnimator
-import android.view.WindowInsets
 import android.view.WindowManager
 import android.view.WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION
 import android.view.WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY
@@ -61,7 +58,6 @@
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.dump.DumpManager
 import com.android.systemui.keyguard.domain.interactor.AlternateBouncerInteractor
-import com.android.systemui.recents.OverviewProxyService
 import com.android.systemui.util.concurrency.DelayableExecutor
 import com.android.systemui.util.traceSection
 import java.io.PrintWriter
@@ -83,7 +79,6 @@
     fingerprintManager: FingerprintManager?,
     private val windowManager: WindowManager,
     private val activityTaskManager: ActivityTaskManager,
-    overviewProxyService: OverviewProxyService,
     displayManager: DisplayManager,
     private val displayStateInteractor: DisplayStateInteractor,
     @Main private val mainExecutor: DelayableExecutor,
@@ -112,19 +107,6 @@
 
     @VisibleForTesting val orientationListener = orientationReasonListener.orientationListener
 
-    @VisibleForTesting
-    val overviewProxyListener =
-        object : OverviewProxyService.OverviewProxyListener {
-            override fun onTaskbarStatusUpdated(visible: Boolean, stashed: Boolean) {
-                overlayView?.let { view ->
-                    handler.postDelayed({ updateOverlayVisibility(view) }, 500)
-                }
-            }
-        }
-
-    private val animationDuration =
-        context.resources.getInteger(android.R.integer.config_mediumAnimTime).toLong()
-
     private val isReverseDefaultRotation =
         context.resources.getBoolean(com.android.internal.R.bool.config_reverseDefaultRotation)
 
@@ -142,7 +124,6 @@
             field = value
             field?.let { newView ->
                 windowManager.addView(newView, overlayViewParams)
-                updateOverlayVisibility(newView)
                 orientationListener.enable()
             }
         }
@@ -181,7 +162,6 @@
                 override fun hide(sensorId: Int) = hide(SideFpsUiRequestSource.AUTO_SHOW)
             }
         )
-        overviewProxyService.addCallback(overviewProxyListener)
         listenForAlternateBouncerVisibility()
 
         dumpManager.registerDumpable(this)
@@ -340,45 +320,6 @@
         windowManager.updateViewLayout(overlayView, overlayViewParams)
     }
 
-    private fun updateOverlayVisibility(view: View) {
-        if (view != overlayView) {
-            return
-        }
-        // hide after a few seconds if the sensor is oriented down and there are
-        // large overlapping system bars
-        var rotation = context.display?.rotation
-
-        if (rotation != null) {
-            rotation = getRotationFromDefault(rotation)
-        }
-
-        if (
-            windowManager.currentWindowMetrics.windowInsets.hasBigNavigationBar() &&
-                ((rotation == Surface.ROTATION_270 && overlayOffsets.isYAligned()) ||
-                    (rotation == Surface.ROTATION_180 && !overlayOffsets.isYAligned()))
-        ) {
-            overlayHideAnimator =
-                view
-                    .animate()
-                    .alpha(0f)
-                    .setStartDelay(3_000)
-                    .setDuration(animationDuration)
-                    .setListener(
-                        object : AnimatorListenerAdapter() {
-                            override fun onAnimationEnd(animation: Animator) {
-                                view.visibility = View.GONE
-                                overlayHideAnimator = null
-                            }
-                        }
-                    )
-        } else {
-            overlayHideAnimator?.cancel()
-            overlayHideAnimator = null
-            view.alpha = 1f
-            view.visibility = View.VISIBLE
-        }
-    }
-
     private fun getRotationFromDefault(rotation: Int): Int =
         if (isReverseDefaultRotation) (rotation + 1) % 4 else rotation
 }
@@ -426,9 +367,6 @@
 private fun Display.isNaturalOrientation(): Boolean =
     rotation == Surface.ROTATION_0 || rotation == Surface.ROTATION_180
 
-private fun WindowInsets.hasBigNavigationBar(): Boolean =
-    getInsets(WindowInsets.Type.navigationBars()).bottom >= 70
-
 private fun LottieAnimationView.addOverlayDynamicColor(
     context: Context,
     @BiometricOverlayConstants.ShowReason reason: Int
diff --git a/packages/SystemUI/src/com/android/systemui/common/ui/view/SeekBarWithIconButtonsView.java b/packages/SystemUI/src/com/android/systemui/common/ui/view/SeekBarWithIconButtonsView.java
index de3a990..4538a6c 100644
--- a/packages/SystemUI/src/com/android/systemui/common/ui/view/SeekBarWithIconButtonsView.java
+++ b/packages/SystemUI/src/com/android/systemui/common/ui/view/SeekBarWithIconButtonsView.java
@@ -27,6 +27,7 @@
 import android.widget.LinearLayout;
 import android.widget.SeekBar;
 
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.systemui.R;
 
 /**
@@ -37,12 +38,14 @@
 
     private static final int DEFAULT_SEEKBAR_MAX = 6;
     private static final int DEFAULT_SEEKBAR_PROGRESS = 0;
+    private static final int DEFAULT_SEEKBAR_TICK_MARK = 0;
 
     private ViewGroup mIconStartFrame;
     private ViewGroup mIconEndFrame;
     private ImageView mIconStart;
     private ImageView mIconEnd;
     private SeekBar mSeekbar;
+    private int mSeekBarChangeMagnitude = 1;
 
     private SeekBarChangeListener mSeekBarListener = new SeekBarChangeListener();
     private String[] mStateLabels = null;
@@ -102,6 +105,15 @@
                         context.getString(iconEndFrameContentDescriptionId);
                 mIconEndFrame.setContentDescription(contentDescription);
             }
+            int tickMarkId = typedArray.getResourceId(
+                    R.styleable.SeekBarWithIconButtonsView_Layout_tickMark,
+                    DEFAULT_SEEKBAR_TICK_MARK);
+            if (tickMarkId != DEFAULT_SEEKBAR_TICK_MARK) {
+                mSeekbar.setTickMark(getResources().getDrawable(tickMarkId));
+            }
+            mSeekBarChangeMagnitude = typedArray.getInt(
+                    R.styleable.SeekBarWithIconButtonsView_Layout_seekBarChangeMagnitude,
+                    /* defValue= */ 1);
         } else {
             mSeekbar.setMax(DEFAULT_SEEKBAR_MAX);
             setProgress(DEFAULT_SEEKBAR_PROGRESS);
@@ -112,7 +124,7 @@
         mIconStartFrame.setOnClickListener((view) -> {
             final int progress = mSeekbar.getProgress();
             if (progress > 0) {
-                mSeekbar.setProgress(progress - 1);
+                mSeekbar.setProgress(progress - mSeekBarChangeMagnitude);
                 setIconViewAndFrameEnabled(mIconStart, mSeekbar.getProgress() > 0);
             }
         });
@@ -120,7 +132,7 @@
         mIconEndFrame.setOnClickListener((view) -> {
             final int progress = mSeekbar.getProgress();
             if (progress < mSeekbar.getMax()) {
-                mSeekbar.setProgress(progress + 1);
+                mSeekbar.setProgress(progress + mSeekBarChangeMagnitude);
                 setIconViewAndFrameEnabled(mIconEnd, mSeekbar.getProgress() < mSeekbar.getMax());
             }
         });
@@ -183,6 +195,20 @@
     }
 
     /**
+     * Gets max to the seekbar in the layout.
+     */
+    public int getMax() {
+        return mSeekbar.getMax();
+    }
+
+    /**
+     * @return the magnitude by which seekbar progress changes when start and end icons are clicked.
+     */
+    public int getChangeMagnitude() {
+        return mSeekBarChangeMagnitude;
+    }
+
+    /**
      * Sets progress to the seekbar in the layout.
      * If the progress is smaller than or equals to 0, the IconStart will be disabled. If the
      * progress is larger than or equals to Max, the IconEnd will be disabled. The seekbar progress
@@ -193,6 +219,11 @@
         updateIconViewIfNeeded(progress);
     }
 
+    @VisibleForTesting
+    public int getProgress() {
+        return mSeekbar.getProgress();
+    }
+
     private class SeekBarChangeListener implements SeekBar.OnSeekBarChangeListener {
         private SeekBar.OnSeekBarChangeListener mOnSeekBarChangeListener = null;
 
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStateController.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStateController.java
index b141db1..c2421dc 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStateController.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStateController.java
@@ -19,7 +19,6 @@
 import static com.android.systemui.dreams.dagger.DreamModule.DREAM_OVERLAY_ENABLED;
 
 import android.service.dreams.DreamService;
-import android.util.Log;
 
 import androidx.annotation.NonNull;
 
@@ -52,7 +51,6 @@
 public class DreamOverlayStateController implements
         CallbackController<DreamOverlayStateController.Callback> {
     private static final String TAG = "DreamOverlayStateCtlr";
-    private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
 
     public static final int STATE_DREAM_OVERLAY_ACTIVE = 1 << 0;
     public static final int STATE_LOW_LIGHT_ACTIVE = 1 << 1;
@@ -110,13 +108,17 @@
 
     private final int mSupportedTypes;
 
+    private final DreamLogger mLogger;
+
     @VisibleForTesting
     @Inject
     public DreamOverlayStateController(@Main Executor executor,
             @Named(DREAM_OVERLAY_ENABLED) boolean overlayEnabled,
-            FeatureFlags featureFlags) {
+            FeatureFlags featureFlags,
+            DreamLogger dreamLogger) {
         mExecutor = executor;
         mOverlayEnabled = overlayEnabled;
+        mLogger = dreamLogger;
         mFeatureFlags = featureFlags;
         if (mFeatureFlags.isEnabled(Flags.ALWAYS_SHOW_HOME_CONTROLS_ON_DREAMS)) {
             mSupportedTypes = Complication.COMPLICATION_TYPE_NONE
@@ -124,9 +126,7 @@
         } else {
             mSupportedTypes = Complication.COMPLICATION_TYPE_NONE;
         }
-        if (DEBUG) {
-            Log.d(TAG, "Dream overlay enabled:" + mOverlayEnabled);
-        }
+        mLogger.d(TAG, "Dream overlay enabled: " + mOverlayEnabled);
     }
 
     /**
@@ -134,18 +134,14 @@
      */
     public void addComplication(Complication complication) {
         if (!mOverlayEnabled) {
-            if (DEBUG) {
-                Log.d(TAG,
-                        "Ignoring adding complication due to overlay disabled:" + complication);
-            }
+            mLogger.d(TAG,
+                    "Ignoring adding complication due to overlay disabled: " + complication);
             return;
         }
 
         mExecutor.execute(() -> {
             if (mComplications.add(complication)) {
-                if (DEBUG) {
-                    Log.d(TAG, "addComplication: added " + complication);
-                }
+                mLogger.d(TAG, "Added dream complication: " + complication);
                 mCallbacks.stream().forEach(callback -> callback.onComplicationsChanged());
             }
         });
@@ -156,18 +152,14 @@
      */
     public void removeComplication(Complication complication) {
         if (!mOverlayEnabled) {
-            if (DEBUG) {
-                Log.d(TAG,
-                        "Ignoring removing complication due to overlay disabled:" + complication);
-            }
+            mLogger.d(TAG,
+                    "Ignoring removing complication due to overlay disabled: " + complication);
             return;
         }
 
         mExecutor.execute(() -> {
             if (mComplications.remove(complication)) {
-                if (DEBUG) {
-                    Log.d(TAG, "removeComplication: removed " + complication);
-                }
+                mLogger.d(TAG, "Removed dream complication: " + complication);
                 mCallbacks.stream().forEach(callback -> callback.onComplicationsChanged());
             }
         });
@@ -313,6 +305,7 @@
      * @param active {@code true} if overlay is active, {@code false} otherwise.
      */
     public void setOverlayActive(boolean active) {
+        mLogger.d(TAG, "Dream overlay active: " + active);
         modifyState(active ? OP_SET_STATE : OP_CLEAR_STATE, STATE_DREAM_OVERLAY_ACTIVE);
     }
 
@@ -321,6 +314,8 @@
      * @param active {@code true} if low light mode is active, {@code false} otherwise.
      */
     public void setLowLightActive(boolean active) {
+        mLogger.d(TAG, "Low light mode active: " + active);
+
         if (isLowLightActive() && !active) {
             // Notify that we're exiting low light only on the transition from active to not active.
             mCallbacks.forEach(Callback::onExitLowLight);
@@ -351,6 +346,7 @@
      * @param hasAttention {@code true} if has the user's attention, {@code false} otherwise.
      */
     public void setHasAssistantAttention(boolean hasAttention) {
+        mLogger.d(TAG, "Dream overlay has Assistant attention: " + hasAttention);
         modifyState(hasAttention ? OP_SET_STATE : OP_CLEAR_STATE, STATE_HAS_ASSISTANT_ATTENTION);
     }
 
@@ -359,6 +355,7 @@
      * @param visible {@code true} if the status bar is visible, {@code false} otherwise.
      */
     public void setDreamOverlayStatusBarVisible(boolean visible) {
+        mLogger.d(TAG, "Dream overlay status bar visible: " + visible);
         modifyState(
                 visible ? OP_SET_STATE : OP_CLEAR_STATE, STATE_DREAM_OVERLAY_STATUS_BAR_VISIBLE);
     }
@@ -376,6 +373,7 @@
      */
     public void setAvailableComplicationTypes(@Complication.ComplicationType int types) {
         mExecutor.execute(() -> {
+            mLogger.d(TAG, "Available complication types: " + types);
             mAvailableComplicationTypes = types;
             mCallbacks.forEach(Callback::onAvailableComplicationTypesChanged);
         });
@@ -393,6 +391,7 @@
      */
     public void setShouldShowComplications(boolean shouldShowComplications) {
         mExecutor.execute(() -> {
+            mLogger.d(TAG, "Should show complications: " + shouldShowComplications);
             mShouldShowComplications = shouldShowComplications;
             mCallbacks.forEach(Callback::onAvailableComplicationTypesChanged);
         });
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarView.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarView.java
index 7c1bfed..1865c38 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarView.java
@@ -141,6 +141,20 @@
         mExtraSystemStatusViewGroup = findViewById(R.id.dream_overlay_extra_items);
     }
 
+    protected static String getLoggableStatusIconType(@StatusIconType int type) {
+        return switch (type) {
+            case STATUS_ICON_NOTIFICATIONS -> "notifications";
+            case STATUS_ICON_WIFI_UNAVAILABLE -> "wifi_unavailable";
+            case STATUS_ICON_ALARM_SET -> "alarm_set";
+            case STATUS_ICON_CAMERA_DISABLED -> "camera_disabled";
+            case STATUS_ICON_MIC_DISABLED -> "mic_disabled";
+            case STATUS_ICON_MIC_CAMERA_DISABLED -> "mic_camera_disabled";
+            case STATUS_ICON_PRIORITY_MODE_ON -> "priority_mode_on";
+            case STATUS_ICON_ASSISTANT_ATTENTION_ACTIVE -> "assistant_attention_active";
+            default -> type + "(unknown)";
+        };
+    }
+
     void showIcon(@StatusIconType int iconType, boolean show, @Nullable String contentDescription) {
         View icon = mStatusIcons.get(iconType);
         if (icon == null) {
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarViewController.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarViewController.java
index c954f98..3a28408 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarViewController.java
@@ -61,6 +61,8 @@
  */
 @DreamOverlayComponent.DreamOverlayScope
 public class DreamOverlayStatusBarViewController extends ViewController<DreamOverlayStatusBarView> {
+    private static final String TAG = "DreamStatusBarCtrl";
+
     private final ConnectivityManager mConnectivityManager;
     private final TouchInsetManager.TouchInsetSession mTouchInsetSession;
     private final NextAlarmController mNextAlarmController;
@@ -78,6 +80,7 @@
     private final Executor mMainExecutor;
     private final List<DreamOverlayStatusBarItemsProvider.StatusBarItem> mExtraStatusBarItems =
             new ArrayList<>();
+    private final DreamLogger mLogger;
 
     private boolean mIsAttached;
 
@@ -157,7 +160,8 @@
             StatusBarWindowStateController statusBarWindowStateController,
             DreamOverlayStatusBarItemsProvider statusBarItemsProvider,
             DreamOverlayStateController dreamOverlayStateController,
-            UserTracker userTracker) {
+            UserTracker userTracker,
+            DreamLogger dreamLogger) {
         super(view);
         mResources = resources;
         mMainExecutor = mainExecutor;
@@ -173,6 +177,7 @@
         mZenModeController = zenModeController;
         mDreamOverlayStateController = dreamOverlayStateController;
         mUserTracker = userTracker;
+        mLogger = dreamLogger;
 
         // Register to receive show/hide updates for the system status bar. Our custom status bar
         // needs to hide when the system status bar is showing to ovoid overlapping status bars.
@@ -341,6 +346,8 @@
             @Nullable String contentDescription) {
         mMainExecutor.execute(() -> {
             if (mIsAttached) {
+                mLogger.d(TAG, (show ? "Showing" : "Hiding") + " dream status bar item: "
+                        + DreamOverlayStatusBarView.getLoggableStatusIconType(iconType));
                 mView.showIcon(iconType, show, contentDescription);
             }
         });
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt
index 29a7fe7..c706363 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt
@@ -33,10 +33,10 @@
 import android.view.View
 import androidx.annotation.VisibleForTesting
 import androidx.core.math.MathUtils
+import com.android.app.animation.Interpolators
 import com.android.internal.R
 import com.android.keyguard.KeyguardClockSwitchController
 import com.android.keyguard.KeyguardViewController
-import com.android.app.animation.Interpolators
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.flags.FeatureFlags
 import com.android.systemui.flags.Flags
@@ -433,7 +433,14 @@
         // animate state.
         if (!keyguardStateController.isKeyguardGoingAway &&
                 willUnlockWithInWindowLauncherAnimations) {
-            launcherUnlockController?.setUnlockAmount(1f, true /* forceIfAnimating */)
+            try {
+                launcherUnlockController?.setUnlockAmount(1f, true /* forceIfAnimating */)
+            } catch (e: DeadObjectException) {
+                Log.e(TAG, "launcherUnlockAnimationController was dead, but non-null in " +
+                        "onKeyguardGoingAwayChanged(). Catching exception as this should mean " +
+                        "Launcher is in the process of being destroyed, but the IPC to System UI " +
+                        "telling us hasn't arrived yet.")
+            }
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
index 742e535..81f62b6 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
@@ -25,6 +25,7 @@
 import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
 import com.android.systemui.common.shared.model.Position
 import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.doze.DozeMachine
 import com.android.systemui.doze.DozeTransitionCallback
@@ -44,13 +45,16 @@
 import com.android.systemui.statusbar.policy.KeyguardStateController
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.channels.awaitClose
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.asStateFlow
 import kotlinx.coroutines.flow.distinctUntilChanged
 import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.flow.stateIn
 
 /** Defines interface for classes that encapsulate application state for the keyguard. */
 interface KeyguardRepository {
@@ -138,7 +142,7 @@
     val statusBarState: Flow<StatusBarState>
 
     /** Observable for device wake/sleep state */
-    val wakefulness: Flow<WakefulnessModel>
+    val wakefulness: StateFlow<WakefulnessModel>
 
     /** Observable for biometric unlock modes */
     val biometricUnlockState: Flow<BiometricUnlockModel>
@@ -202,7 +206,8 @@
     private val dozeParameters: DozeParameters,
     private val authController: AuthController,
     private val dreamOverlayCallbackController: DreamOverlayCallbackController,
-    @Main private val mainDispatcher: CoroutineDispatcher
+    @Main private val mainDispatcher: CoroutineDispatcher,
+    @Application private val scope: CoroutineScope,
 ) : KeyguardRepository {
     private val _animateBottomAreaDozingTransitions = MutableStateFlow(false)
     override val animateBottomAreaDozingTransitions =
@@ -486,47 +491,48 @@
         awaitClose { biometricUnlockController.removeListener(callback) }
     }
 
-    override val wakefulness: Flow<WakefulnessModel> = conflatedCallbackFlow {
-        val observer =
-            object : WakefulnessLifecycle.Observer {
-                override fun onStartedWakingUp() {
-                    dispatchNewState()
-                }
+    override val wakefulness: StateFlow<WakefulnessModel> =
+        conflatedCallbackFlow {
+                val observer =
+                    object : WakefulnessLifecycle.Observer {
+                        override fun onStartedWakingUp() {
+                            dispatchNewState()
+                        }
 
-                override fun onFinishedWakingUp() {
-                    dispatchNewState()
-                }
+                        override fun onFinishedWakingUp() {
+                            dispatchNewState()
+                        }
 
-                override fun onPostFinishedWakingUp() {
-                    dispatchNewState()
-                }
+                        override fun onPostFinishedWakingUp() {
+                            dispatchNewState()
+                        }
 
-                override fun onStartedGoingToSleep() {
-                    dispatchNewState()
-                }
+                        override fun onStartedGoingToSleep() {
+                            dispatchNewState()
+                        }
 
-                override fun onFinishedGoingToSleep() {
-                    dispatchNewState()
-                }
+                        override fun onFinishedGoingToSleep() {
+                            dispatchNewState()
+                        }
 
-                private fun dispatchNewState() {
-                    trySendWithFailureLogging(
-                        WakefulnessModel.fromWakefulnessLifecycle(wakefulnessLifecycle),
-                        TAG,
-                        "updated wakefulness state"
-                    )
-                }
+                        private fun dispatchNewState() {
+                            trySendWithFailureLogging(
+                                WakefulnessModel.fromWakefulnessLifecycle(wakefulnessLifecycle),
+                                TAG,
+                                "updated wakefulness state",
+                            )
+                        }
+                    }
+
+                wakefulnessLifecycle.addObserver(observer)
+                awaitClose { wakefulnessLifecycle.removeObserver(observer) }
             }
-
-        wakefulnessLifecycle.addObserver(observer)
-        trySendWithFailureLogging(
-            WakefulnessModel.fromWakefulnessLifecycle(wakefulnessLifecycle),
-            TAG,
-            "initial wakefulness state"
-        )
-
-        awaitClose { wakefulnessLifecycle.removeObserver(observer) }
-    }
+            .stateIn(
+                scope,
+                // Use Eagerly so that we're always listening and never miss an event.
+                SharingStarted.Eagerly,
+                initialValue = WakefulnessModel.fromWakefulnessLifecycle(wakefulnessLifecycle),
+            )
 
     override val fingerprintSensorLocation: Flow<Point?> = conflatedCallbackFlow {
         fun sendFpLocation() {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
index f99b8a2..d13d5ad 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
@@ -40,6 +40,7 @@
 import kotlinx.coroutines.channels.awaitClose
 import kotlinx.coroutines.delay
 import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.distinctUntilChanged
 import kotlinx.coroutines.flow.filter
@@ -99,7 +100,7 @@
     }
 
     /** The device wake/sleep state */
-    val wakefulnessModel: Flow<WakefulnessModel> = repository.wakefulness
+    val wakefulnessModel: StateFlow<WakefulnessModel> = repository.wakefulness
 
     /**
      * Dozing and dreaming have overlapping events. If the doze state remains in FINISH, it means
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/WakeSleepReason.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/WakeSleepReason.kt
index 51ce7ff..fb685da 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/WakeSleepReason.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/WakeSleepReason.kt
@@ -23,9 +23,12 @@
     /** The physical power button was pressed to wake up or sleep the device. */
     POWER_BUTTON,
 
-    /** The user has taped or double tapped to wake the screen */
+    /** The user has tapped or double tapped to wake the screen. */
     TAP,
 
+    /** The user performed some sort of gesture to wake the screen. */
+    GESTURE,
+
     /** Something else happened to wake up or sleep the device. */
     OTHER;
 
@@ -34,6 +37,7 @@
             return when (reason) {
                 PowerManager.WAKE_REASON_POWER_BUTTON -> POWER_BUTTON
                 PowerManager.WAKE_REASON_TAP -> TAP
+                PowerManager.WAKE_REASON_GESTURE -> GESTURE
                 else -> OTHER
             }
         }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/WakefulnessModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/WakefulnessModel.kt
index 7ca90ba..dd57713 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/WakefulnessModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/WakefulnessModel.kt
@@ -27,7 +27,11 @@
 
     fun isStartingToSleep() = state == WakefulnessState.STARTING_TO_SLEEP
 
-    fun isStartingToSleepOrAsleep() = isStartingToSleep() || state == WakefulnessState.ASLEEP
+    private fun isAsleep() = state == WakefulnessState.ASLEEP
+
+    fun isStartingToSleepOrAsleep() = isStartingToSleep() || isAsleep()
+
+    fun isDeviceInteractive() = !isAsleep()
 
     fun isStartingToSleepFromPowerButton() =
         isStartingToSleep() && lastWakeReason == WakeSleepReason.POWER_BUTTON
@@ -41,6 +45,11 @@
     fun isAwakeFromTap() =
         state == WakefulnessState.STARTING_TO_WAKE && lastWakeReason == WakeSleepReason.TAP
 
+    fun isDeviceInteractiveFromTapOrGesture(): Boolean {
+        return isDeviceInteractive() &&
+            (lastWakeReason == WakeSleepReason.TAP || lastWakeReason == WakeSleepReason.GESTURE)
+    }
+
     companion object {
         fun fromWakefulnessLifecycle(wakefulnessLifecycle: WakefulnessLifecycle): WakefulnessModel {
             return WakefulnessModel(
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/models/player/SeekBarObserver.kt b/packages/SystemUI/src/com/android/systemui/media/controls/models/player/SeekBarObserver.kt
index e38abc2..6eaff3f 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/models/player/SeekBarObserver.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/models/player/SeekBarObserver.kt
@@ -56,6 +56,7 @@
             R.dimen.qs_media_session_disabled_seekbar_vertical_padding
         )
     var seekBarResetAnimator: Animator? = null
+    var animationEnabled: Boolean = true
 
     init {
         val seekBarProgressWavelength =
@@ -104,7 +105,7 @@
 
         holder.seekBar.thumb.alpha = if (data.seekAvailable) 255 else 0
         holder.seekBar.isEnabled = data.seekAvailable
-        progressDrawable?.animate = data.playing && !data.scrubbing
+        progressDrawable?.animate = data.playing && !data.scrubbing && animationEnabled
         progressDrawable?.transitionEnabled = !data.seekAvailable
 
         if (holder.seekBar.maxHeight != seekBarEnabledMaxHeight) {
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java
index 32a7935..516fbf5 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java
@@ -34,6 +34,7 @@
 import android.content.res.ColorStateList;
 import android.content.res.Configuration;
 import android.content.res.Resources;
+import android.database.ContentObserver;
 import android.graphics.Bitmap;
 import android.graphics.BlendMode;
 import android.graphics.Color;
@@ -54,6 +55,7 @@
 import android.media.session.PlaybackState;
 import android.os.Process;
 import android.os.Trace;
+import android.provider.Settings;
 import android.text.TextUtils;
 import android.util.Log;
 import android.util.Pair;
@@ -121,6 +123,7 @@
 import com.android.systemui.util.ColorUtilKt;
 import com.android.systemui.util.animation.TransitionLayout;
 import com.android.systemui.util.concurrency.DelayableExecutor;
+import com.android.systemui.util.settings.GlobalSettings;
 import com.android.systemui.util.time.SystemClock;
 
 import dagger.Lazy;
@@ -244,11 +247,19 @@
     private MultiRippleController mMultiRippleController;
     private TurbulenceNoiseController mTurbulenceNoiseController;
     private final FeatureFlags mFeatureFlags;
+    private final GlobalSettings mGlobalSettings;
 
     // TODO(b/281032715): Consider making this as a final variable. For now having a null check
     //  due to unit test failure. (Perhaps missing some setup)
     private TurbulenceNoiseAnimationConfig mTurbulenceNoiseAnimationConfig;
 
+    private ContentObserver mAnimationScaleObserver = new ContentObserver(null) {
+        @Override
+        public void onChange(boolean selfChange) {
+            updateAnimatorDurationScale();
+        }
+    };
+
     /**
      * Initialize a new control panel
      *
@@ -276,7 +287,8 @@
             ActivityIntentHelper activityIntentHelper,
             NotificationLockscreenUserManager lockscreenUserManager,
             BroadcastDialogController broadcastDialogController,
-            FeatureFlags featureFlags
+            FeatureFlags featureFlags,
+            GlobalSettings globalSettings
     ) {
         mContext = context;
         mBackgroundExecutor = backgroundExecutor;
@@ -305,6 +317,13 @@
         });
 
         mFeatureFlags = featureFlags;
+
+        mGlobalSettings = globalSettings;
+        mGlobalSettings.registerContentObserver(
+                Settings.Global.getUriFor(Settings.Global.ANIMATOR_DURATION_SCALE),
+                mAnimationScaleObserver
+        );
+        updateAnimatorDurationScale();
     }
 
     /**
@@ -387,6 +406,13 @@
         updateSeekBarVisibility();
     }
 
+    private void updateAnimatorDurationScale() {
+        if (mSeekBarObserver != null) {
+            mSeekBarObserver.setAnimationEnabled(
+                    mGlobalSettings.getFloat(Settings.Global.ANIMATOR_DURATION_SCALE, 1f) > 0f);
+        }
+    }
+
     /**
      * Get the context
      *
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 cfb581b..cb8d87a 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
@@ -20,9 +20,8 @@
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_EXCLUDE_FROM_SCREEN_MAGNIFICATION;
 
 import static com.android.systemui.classifier.Classifier.BACK_GESTURE;
-import static com.android.systemui.navigationbar.gestural.Utilities.isTrackpadFourFingerSwipe;
-import static com.android.systemui.navigationbar.gestural.Utilities.isTrackpadMultiFingerSwipe;
 import static com.android.systemui.navigationbar.gestural.Utilities.isTrackpadScroll;
+import static com.android.systemui.navigationbar.gestural.Utilities.isTrackpadThreeFingerSwipe;
 
 import android.annotation.NonNull;
 import android.app.ActivityManager;
@@ -989,7 +988,7 @@
 
     private void onMotionEvent(MotionEvent ev) {
         int action = ev.getActionMasked();
-        boolean isTrackpadMultiFingerSwipe = isTrackpadMultiFingerSwipe(
+        boolean isTrackpadThreeFingerSwipe = isTrackpadThreeFingerSwipe(
                 mIsTrackpadGestureFeaturesEnabled, ev);
         if (action == MotionEvent.ACTION_DOWN) {
             if (DEBUG_MISSING_GESTURE) {
@@ -1003,7 +1002,7 @@
             // Verify if this is in within the touch region and we aren't in immersive mode, and
             // either the bouncer is showing or the notification panel is hidden
             mInputEventReceiver.setBatchingEnabled(false);
-            if (isTrackpadMultiFingerSwipe) {
+            if (isTrackpadThreeFingerSwipe) {
                 // Since trackpad gestures don't have zones, this will be determined later by the
                 // direction of the gesture. {@code mIsOnLeftEdge} is set to false to begin with.
                 mDeferSetIsOnLeftEdge = true;
@@ -1019,11 +1018,11 @@
                     && !mGestureBlockingActivityRunning
                     && !QuickStepContract.isBackGestureDisabled(mSysUiFlags)
                     && !isTrackpadScroll(mIsTrackpadGestureFeaturesEnabled, ev);
-            if (isTrackpadMultiFingerSwipe) {
+            if (isTrackpadThreeFingerSwipe) {
                 // Trackpad back gestures don't have zones, so we don't need to check if the down
                 // event is within insets.
                 mAllowGesture = isBackAllowedCommon && isValidTrackpadBackGesture(
-                        isTrackpadMultiFingerSwipe);
+                        isTrackpadThreeFingerSwipe);
             } else {
                 mAllowGesture = isBackAllowedCommon && !mUsingThreeButtonNav && isWithinInsets
                     && isWithinTouchRegion((int) ev.getX(), (int) ev.getY())
@@ -1034,7 +1033,7 @@
                 mEdgeBackPlugin.onMotionEvent(ev);
                 dispatchToBackAnimation(ev);
             }
-            if (mLogGesture || isTrackpadMultiFingerSwipe) {
+            if (mLogGesture || isTrackpadThreeFingerSwipe) {
                 mDownPoint.set(ev.getX(), ev.getY());
                 mEndPoint.set(-1, -1);
                 mThresholdCrossed = false;
@@ -1045,10 +1044,10 @@
             mTmpLogDate.setTime(curTime);
             String curTimeStr = mLogDateFormat.format(mTmpLogDate);
             (isWithinInsets ? mGestureLogInsideInsets : mGestureLogOutsideInsets).log(String.format(
-                    "Gesture [%d [%s],alw=%B, mltf=%B, left=%B, defLeft=%B, backAlw=%B, disbld=%B,"
+                    "Gesture [%d [%s],alw=%B, t3fs=%B, left=%B, defLeft=%B, backAlw=%B, disbld=%B,"
                             + " qsDisbld=%b, blkdAct=%B, pip=%B,"
                             + " disp=%s, wl=%d, il=%d, wr=%d, ir=%d, excl=%s]",
-                    curTime, curTimeStr, mAllowGesture, isTrackpadMultiFingerSwipe,
+                    curTime, curTimeStr, mAllowGesture, isTrackpadThreeFingerSwipe,
                     mIsOnLeftEdge, mDeferSetIsOnLeftEdge, mIsBackGestureAllowed,
                     QuickStepContract.isBackGestureDisabled(mSysUiFlags), mDisabledForQuickstep,
                     mGestureBlockingActivityRunning, mIsInPip, mDisplaySize,
@@ -1057,8 +1056,7 @@
             if (!mThresholdCrossed) {
                 mEndPoint.x = (int) ev.getX();
                 mEndPoint.y = (int) ev.getY();
-                if (action == MotionEvent.ACTION_POINTER_DOWN && (!isTrackpadMultiFingerSwipe
-                        || isTrackpadFourFingerSwipe(mIsTrackpadGestureFeaturesEnabled, ev))) {
+                if (action == MotionEvent.ACTION_POINTER_DOWN && !isTrackpadThreeFingerSwipe) {
                     if (mAllowGesture) {
                         logGesture(SysUiStatsLog.BACK_GESTURE__TYPE__INCOMPLETE_MULTI_TOUCH);
                         if (DEBUG_MISSING_GESTURE) {
@@ -1070,11 +1068,7 @@
                     mLogGesture = false;
                     return;
                 } else if (action == MotionEvent.ACTION_MOVE) {
-                    if (isTrackpadFourFingerSwipe(isTrackpadMultiFingerSwipe, ev)) {
-                        cancelGesture(ev);
-                        return;
-                    }
-                    if (isTrackpadMultiFingerSwipe && mDeferSetIsOnLeftEdge) {
+                    if (isTrackpadThreeFingerSwipe && mDeferSetIsOnLeftEdge) {
                         // mIsOnLeftEdge is determined by the relative position between the down
                         // and the current motion event for trackpad gestures instead of zoning.
                         mIsOnLeftEdge = mEndPoint.x > mDownPoint.x;
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/Utilities.java b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/Utilities.java
index bf5e442..10a88c8 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/Utilities.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/Utilities.java
@@ -16,6 +16,7 @@
 
 package com.android.systemui.navigationbar.gestural;
 
+import static android.view.MotionEvent.AXIS_GESTURE_SWIPE_FINGER_COUNT;
 import static android.view.MotionEvent.CLASSIFICATION_MULTI_FINGER_SWIPE;
 import static android.view.MotionEvent.CLASSIFICATION_TWO_FINGER_SWIPE;
 
@@ -29,21 +30,10 @@
                 && event.getClassification() == CLASSIFICATION_TWO_FINGER_SWIPE;
     }
 
-    public static boolean isTrackpadMultiFingerSwipe(boolean isTrackpadGestureFeaturesEnabled,
-            MotionEvent event) {
-        return isTrackpadGestureFeaturesEnabled
-                && event.getClassification() == CLASSIFICATION_MULTI_FINGER_SWIPE;
-    }
-
     public static boolean isTrackpadThreeFingerSwipe(boolean isTrackpadGestureFeaturesEnabled,
             MotionEvent event) {
-        return isTrackpadMultiFingerSwipe(isTrackpadGestureFeaturesEnabled, event)
-                && event.getPointerCount() == 3;
-    }
-
-    public static boolean isTrackpadFourFingerSwipe(boolean isTrackpadGestureFeaturesEnabled,
-            MotionEvent event) {
-        return isTrackpadMultiFingerSwipe(isTrackpadGestureFeaturesEnabled, event)
-                && event.getPointerCount() == 4;
+        return isTrackpadGestureFeaturesEnabled
+                && event.getClassification() == CLASSIFICATION_MULTI_FINGER_SWIPE
+                && event.getAxisValue(AXIS_GESTURE_SWIPE_FINGER_COUNT) == 3;
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialog.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialog.java
index 212a2b7..6e1ef91 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialog.java
@@ -20,7 +20,6 @@
 
 import android.app.AlertDialog;
 import android.content.Context;
-import android.content.res.TypedArray;
 import android.graphics.drawable.Drawable;
 import android.net.Network;
 import android.net.NetworkCapabilities;
@@ -58,7 +57,6 @@
 
 import com.android.internal.logging.UiEvent;
 import com.android.internal.logging.UiEventLogger;
-import com.android.settingslib.Utils;
 import com.android.settingslib.wifi.WifiEnterpriseRestrictionUtils;
 import com.android.systemui.Prefs;
 import com.android.systemui.R;
@@ -416,14 +414,6 @@
                 });
             });
 
-            TypedArray array = mContext.obtainStyledAttributes(
-                    R.style.InternetDialog_Divider_Active, new int[]{android.R.attr.background});
-            int dividerColor = Utils.getColorAttrDefaultColor(mContext,
-                    android.R.attr.textColorSecondary);
-            mMobileToggleDivider.setBackgroundColor(isNetworkConnected
-                    ? array.getColor(0, dividerColor) : dividerColor);
-            array.recycle();
-
             mMobileDataToggle.setVisibility(mCanConfigMobileData ? View.VISIBLE : View.INVISIBLE);
             mMobileToggleDivider.setVisibility(
                     mCanConfigMobileData ? View.VISIBLE : View.INVISIBLE);
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index 1174d3d..127c415 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -17,8 +17,6 @@
 package com.android.systemui.shade;
 
 import static android.app.StatusBarManager.WINDOW_STATE_SHOWING;
-import static android.view.MotionEvent.CLASSIFICATION_MULTI_FINGER_SWIPE;
-import static android.view.MotionEvent.CLASSIFICATION_TWO_FINGER_SWIPE;
 import static android.view.View.INVISIBLE;
 import static android.view.View.VISIBLE;
 
@@ -30,6 +28,8 @@
 import static com.android.systemui.classifier.Classifier.GENERIC;
 import static com.android.systemui.classifier.Classifier.QUICK_SETTINGS;
 import static com.android.systemui.classifier.Classifier.UNLOCK;
+import static com.android.systemui.navigationbar.gestural.Utilities.isTrackpadScroll;
+import static com.android.systemui.navigationbar.gestural.Utilities.isTrackpadThreeFingerSwipe;
 import static com.android.systemui.shade.ShadeExpansionStateManagerKt.STATE_CLOSED;
 import static com.android.systemui.shade.ShadeExpansionStateManagerKt.STATE_OPEN;
 import static com.android.systemui.shade.ShadeExpansionStateManagerKt.STATE_OPENING;
@@ -134,6 +134,7 @@
 import com.android.systemui.keyguard.shared.constants.KeyguardBouncerConstants;
 import com.android.systemui.keyguard.shared.model.TransitionState;
 import com.android.systemui.keyguard.shared.model.TransitionStep;
+import com.android.systemui.keyguard.shared.model.WakefulnessModel;
 import com.android.systemui.keyguard.ui.binder.KeyguardLongPressViewBinder;
 import com.android.systemui.keyguard.ui.viewmodel.DreamingToLockscreenTransitionViewModel;
 import com.android.systemui.keyguard.ui.viewmodel.GoneToDreamingTransitionViewModel;
@@ -2150,10 +2151,14 @@
     }
 
     int getFalsingThreshold() {
-        float factor = mCentralSurfaces.isWakeUpComingFromTouch() ? 1.5f : 1.0f;
+        float factor = ShadeViewController.getFalsingThresholdFactor(getWakefulness());
         return (int) (mQsController.getFalsingThreshold() * factor);
     }
 
+    private WakefulnessModel getWakefulness() {
+        return mKeyguardInteractor.getWakefulnessModel().getValue();
+    }
+
     private void maybeAnimateBottomAreaAlpha() {
         mBottomAreaShadeAlphaAnimator.cancel();
         if (mBarState == StatusBarState.SHADE_LOCKED) {
@@ -3587,8 +3592,10 @@
                 expand = flingExpands(vel, vectorVel, x, y);
             }
 
-            mDozeLog.traceFling(expand, mTouchAboveFalsingThreshold,
-                    mCentralSurfaces.isWakeUpComingFromTouch());
+            mDozeLog.traceFling(
+                    expand,
+                    mTouchAboveFalsingThreshold,
+                    /* screenOnFromTouch=*/ getWakefulness().isDeviceInteractiveFromTapOrGesture());
             // Log collapse gesture if on lock screen.
             if (!expand && onKeyguard) {
                 float displayDensity = mCentralSurfaces.getDisplayDensity();
@@ -4655,6 +4662,9 @@
             final float x = event.getX(pointerIndex);
             final float y = event.getY(pointerIndex);
             boolean canCollapsePanel = canCollapsePanelOnTouch();
+            final boolean isTrackpadTwoOrThreeFingerSwipe = isTrackpadScroll(
+                    mTrackpadGestureFeaturesEnabled, event) || isTrackpadThreeFingerSwipe(
+                    mTrackpadGestureFeaturesEnabled, event);
 
             switch (event.getActionMasked()) {
                 case MotionEvent.ACTION_DOWN:
@@ -4687,7 +4697,7 @@
                     addMovement(event);
                     break;
                 case MotionEvent.ACTION_POINTER_UP:
-                    if (isTrackpadMotionEvent(event)) {
+                    if (isTrackpadTwoOrThreeFingerSwipe) {
                         break;
                     }
                     final int upPointer = event.getPointerId(event.getActionIndex());
@@ -4703,7 +4713,7 @@
                     mShadeLog.logMotionEventStatusBarState(event,
                             mStatusBarStateController.getState(),
                             "onInterceptTouchEvent: pointer down action");
-                    if (!isTrackpadMotionEvent(event)
+                    if (!isTrackpadTwoOrThreeFingerSwipe
                             && mStatusBarStateController.getState() == StatusBarState.KEYGUARD) {
                         mMotionAborted = true;
                         mVelocityTracker.clear();
@@ -4852,9 +4862,13 @@
                 return false;
             }
 
+            final boolean isTrackpadTwoOrThreeFingerSwipe = isTrackpadScroll(
+                    mTrackpadGestureFeaturesEnabled, event) || isTrackpadThreeFingerSwipe(
+                    mTrackpadGestureFeaturesEnabled, event);
+
             // On expanding, single mouse click expands the panel instead of dragging.
             if (isFullyCollapsed() && (event.isFromSource(InputDevice.SOURCE_MOUSE)
-                    && !isTrackpadMotionEvent(event))) {
+                    && !isTrackpadTwoOrThreeFingerSwipe)) {
                 if (event.getAction() == MotionEvent.ACTION_UP) {
                     expand(true /* animate */);
                 }
@@ -4914,7 +4928,7 @@
                     break;
 
                 case MotionEvent.ACTION_POINTER_UP:
-                    if (isTrackpadMotionEvent(event)) {
+                    if (isTrackpadTwoOrThreeFingerSwipe) {
                         break;
                     }
                     final int upPointer = event.getPointerId(event.getActionIndex());
@@ -4933,7 +4947,7 @@
                     mShadeLog.logMotionEventStatusBarState(event,
                             mStatusBarStateController.getState(),
                             "handleTouch: pointer down action");
-                    if (!isTrackpadMotionEvent(event)
+                    if (!isTrackpadTwoOrThreeFingerSwipe
                             && mStatusBarStateController.getState() == StatusBarState.KEYGUARD) {
                         mMotionAborted = true;
                         endMotionEvent(event, x, y, true /* forceCancel */);
@@ -5008,12 +5022,6 @@
             mShadeLog.logHandleTouchLastReturn(event, !mGestureWaitForTouchSlop, mTracking);
             return !mGestureWaitForTouchSlop || mTracking;
         }
-
-        private boolean isTrackpadMotionEvent(MotionEvent ev) {
-            return mTrackpadGestureFeaturesEnabled && (
-                    ev.getClassification() == CLASSIFICATION_MULTI_FINGER_SWIPE
-                            || ev.getClassification() == CLASSIFICATION_TWO_FINGER_SWIPE);
-        }
     }
 
     private final class HeadsUpNotificationViewControllerImpl implements
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeViewController.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewController.kt
index 203355e..0548180 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewController.kt
@@ -17,6 +17,7 @@
 
 import android.view.MotionEvent
 import android.view.ViewGroup
+import com.android.systemui.keyguard.shared.model.WakefulnessModel
 import com.android.systemui.statusbar.RemoteInputController
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController
@@ -218,6 +219,16 @@
     val shadeNotificationPresenter: ShadeNotificationPresenter
 
     companion object {
+        /**
+         * Returns a multiplicative factor to use when determining the falsing threshold for touches
+         * on the shade. The factor will be larger when the device is waking up due to a touch or
+         * gesture.
+         */
+        @JvmStatic
+        fun getFalsingThresholdFactor(wakefulness: WakefulnessModel): Float {
+            return if (wakefulness.isDeviceInteractiveFromTapOrGesture()) 1.5f else 1.0f
+        }
+
         const val WAKEUP_ANIMATION_DELAY_MS = 250
         const val FLING_MAX_LENGTH_SECONDS = 0.6f
         const val FLING_SPEED_UP_FACTOR = 0.6f
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java
index 0c4b092..7dbca42 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java
@@ -552,6 +552,9 @@
 
                 Runnable r = () -> mMainHandler.post(
                         () -> openGutsInternal(view, x, y, menuItem));
+                // If the bouncer shows, it will block the TOUCH_UP event from reaching the notif,
+                // so explicitly mark it as unpressed here to reset the touch animation.
+                view.setPressed(false);
                 mActivityStarter.executeRunnableDismissingKeyguard(
                         r,
                         null /* cancelAction */,
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 cdc7cee..c452c0f 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
@@ -104,13 +104,13 @@
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.collection.render.GroupExpansionManager;
 import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager;
+import com.android.systemui.statusbar.notification.init.NotificationsController;
 import com.android.systemui.statusbar.notification.logging.NotificationLogger;
 import com.android.systemui.statusbar.notification.row.ActivatableNotificationView;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 import com.android.systemui.statusbar.notification.row.ExpandableView;
 import com.android.systemui.statusbar.notification.row.FooterView;
 import com.android.systemui.statusbar.notification.row.StackScrollerDecorView;
-import com.android.systemui.statusbar.phone.CentralSurfaces;
 import com.android.systemui.statusbar.phone.HeadsUpAppearanceController;
 import com.android.systemui.statusbar.phone.HeadsUpTouchHelper;
 import com.android.systemui.statusbar.phone.ScreenOffAnimationController;
@@ -315,7 +315,7 @@
         }
     };
     private NotificationStackScrollLogger mLogger;
-    private CentralSurfaces mCentralSurfaces;
+    private NotificationsController mNotificationsController;
     private ActivityStarter mActivityStarter;
     private final int[] mTempInt2 = new int[2];
     private boolean mGenerateChildOrderChangedEvent;
@@ -3989,7 +3989,7 @@
         mAmbientState.setExpansionChanging(false);
         if (!mIsExpanded) {
             resetScrollPosition();
-            mCentralSurfaces.resetUserExpandedStates();
+            mNotificationsController.resetUserExpandedStates();
             clearTemporaryViews();
             clearUserLockedViews();
             cancelActiveSwipe();
@@ -4570,8 +4570,8 @@
         return max + getStackTranslation();
     }
 
-    public void setCentralSurfaces(CentralSurfaces centralSurfaces) {
-        this.mCentralSurfaces = centralSurfaces;
+    public void setNotificationsController(NotificationsController notificationsController) {
+        this.mNotificationsController = notificationsController;
     }
 
     public void setActivityStarter(ActivityStarter activityStarter) {
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 7b046d6..12b2ef8 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
@@ -61,6 +61,7 @@
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor;
 import com.android.systemui.media.controls.ui.KeyguardMediaController;
 import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.plugins.FalsingManager;
@@ -69,6 +70,7 @@
 import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.shade.ShadeController;
+import com.android.systemui.shade.ShadeViewController;
 import com.android.systemui.statusbar.LockscreenShadeTransitionController;
 import com.android.systemui.statusbar.NotificationLockscreenUserManager;
 import com.android.systemui.statusbar.NotificationLockscreenUserManager.UserChangedListener;
@@ -98,6 +100,7 @@
 import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider;
 import com.android.systemui.statusbar.notification.collection.render.SectionHeaderController;
 import com.android.systemui.statusbar.notification.dagger.SilentHeader;
+import com.android.systemui.statusbar.notification.init.NotificationsController;
 import com.android.systemui.statusbar.notification.logging.NotificationLogger;
 import com.android.systemui.statusbar.notification.row.ActivatableNotificationView;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
@@ -144,6 +147,7 @@
 
     private final boolean mAllowLongPress;
     private final NotificationGutsManager mNotificationGutsManager;
+    private final NotificationsController mNotificationsController;
     private final NotificationVisibilityProvider mVisibilityProvider;
     private final HeadsUpManagerPhone mHeadsUpManager;
     private final NotificationRoundnessManager mNotificationRoundnessManager;
@@ -170,6 +174,7 @@
     private final KeyguardMediaController mKeyguardMediaController;
     private final SysuiStatusBarStateController mStatusBarStateController;
     private final KeyguardBypassController mKeyguardBypassController;
+    private final KeyguardInteractor mKeyguardInteractor;
     private final NotificationLockscreenUserManager mLockscreenUserManager;
     // TODO: CentralSurfaces should be encapsulated behind a Controller
     private final CentralSurfaces mCentralSurfaces;
@@ -428,7 +433,7 @@
                 @Override
                 public void onSnooze(StatusBarNotification sbn,
                                      NotificationSwipeActionHelper.SnoozeOption snoozeOption) {
-                    mCentralSurfaces.setNotificationSnoozed(sbn, snoozeOption);
+                    mNotificationsController.setNotificationSnoozed(sbn, snoozeOption);
                 }
 
                 @Override
@@ -558,7 +563,8 @@
 
                 @Override
                 public float getFalsingThresholdFactor() {
-                    return mCentralSurfaces.isWakeUpComingFromTouch() ? 1.5f : 1.0f;
+                    return ShadeViewController.getFalsingThresholdFactor(
+                            mKeyguardInteractor.getWakefulnessModel().getValue());
                 }
 
                 @Override
@@ -612,6 +618,7 @@
             NotificationStackScrollLayout view,
             @Named(ALLOW_NOTIFICATION_LONG_PRESS_NAME) boolean allowLongPress,
             NotificationGutsManager notificationGutsManager,
+            NotificationsController notificationsController,
             NotificationVisibilityProvider visibilityProvider,
             HeadsUpManagerPhone headsUpManager,
             NotificationRoundnessManager notificationRoundnessManager,
@@ -622,6 +629,7 @@
             SysuiStatusBarStateController statusBarStateController,
             KeyguardMediaController keyguardMediaController,
             KeyguardBypassController keyguardBypassController,
+            KeyguardInteractor keyguardInteractor,
             ZenModeController zenModeController,
             NotificationLockscreenUserManager lockscreenUserManager,
             Optional<NotificationListViewModel> nsslViewModel,
@@ -659,6 +667,7 @@
         mLogger = logger;
         mAllowLongPress = allowLongPress;
         mNotificationGutsManager = notificationGutsManager;
+        mNotificationsController = notificationsController;
         mVisibilityProvider = visibilityProvider;
         mHeadsUpManager = headsUpManager;
         mNotificationRoundnessManager = notificationRoundnessManager;
@@ -669,6 +678,7 @@
         mStatusBarStateController = statusBarStateController;
         mKeyguardMediaController = keyguardMediaController;
         mKeyguardBypassController = keyguardBypassController;
+        mKeyguardInteractor = keyguardInteractor;
         mZenModeController = zenModeController;
         mLockscreenUserManager = lockscreenUserManager;
         mViewModel = nsslViewModel;
@@ -708,7 +718,7 @@
         mView.setController(this);
         mView.setLogger(mLogger);
         mView.setTouchHandler(new TouchHandler());
-        mView.setCentralSurfaces(mCentralSurfaces);
+        mView.setNotificationsController(mNotificationsController);
         mView.setActivityStarter(mActivityStarter);
         mView.setClearAllAnimationListener(this::onAnimationEnd);
         mView.setClearAllListener((selection) -> mUiEventLogger.log(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelper.java
index b956207..7e327e6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelper.java
@@ -34,6 +34,7 @@
 import com.android.internal.jank.InteractionJankMonitor;
 import com.android.systemui.SwipeHelper;
 import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.dump.DumpManager;
 import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.plugins.FalsingManager;
 import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
@@ -545,14 +546,17 @@
         private final FeatureFlags mFeatureFlags;
         private NotificationCallback mNotificationCallback;
         private NotificationMenuRowPlugin.OnMenuEventListener mOnMenuEventListener;
+        private DumpManager mDumpManager;
         private NotificationRoundnessManager mNotificationRoundnessManager;
 
         @Inject
         Builder(@Main Resources resources, ViewConfiguration viewConfiguration,
+                DumpManager dumpManager,
                 FalsingManager falsingManager, FeatureFlags featureFlags,
                 NotificationRoundnessManager notificationRoundnessManager) {
             mResources = resources;
             mViewConfiguration = viewConfiguration;
+            mDumpManager = dumpManager;
             mFalsingManager = falsingManager;
             mFeatureFlags = featureFlags;
             mNotificationRoundnessManager = notificationRoundnessManager;
@@ -570,9 +574,12 @@
         }
 
         NotificationSwipeHelper build() {
-            return new NotificationSwipeHelper(mResources, mViewConfiguration, mFalsingManager,
+            NotificationSwipeHelper swipeHelper = new NotificationSwipeHelper(
+                    mResources, mViewConfiguration, mFalsingManager,
                     mFeatureFlags, mNotificationCallback, mOnMenuEventListener,
                     mNotificationRoundnessManager);
+            mDumpManager.registerDumpable(swipeHelper);
+            return swipeHelper;
         }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java
index 0b02f33..8bd9158 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java
@@ -26,7 +26,6 @@
 import android.os.Bundle;
 import android.os.PowerManager;
 import android.os.UserHandle;
-import android.service.notification.StatusBarNotification;
 import android.view.KeyEvent;
 import android.view.MotionEvent;
 import android.view.RemoteAnimationAdapter;
@@ -44,7 +43,6 @@
 import com.android.systemui.animation.ActivityLaunchAnimator;
 import com.android.systemui.navigationbar.NavigationBarView;
 import com.android.systemui.plugins.ActivityStarter.OnDismissAction;
-import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper;
 import com.android.systemui.qs.QSPanelController;
 import com.android.systemui.shade.ShadeViewController;
 import com.android.systemui.shared.system.RemoteAnimationRunnerCompat;
@@ -224,8 +222,6 @@
 
     boolean isLaunchingActivityOverLockscreen();
 
-    boolean isWakeUpComingFromTouch();
-
     void onKeyguardViewManagerStatesUpdated();
 
     boolean isPulsing();
@@ -295,8 +291,6 @@
 
     void readyForKeyguardDone();
 
-    void resetUserExpandedStates();
-
     void setLockscreenUser(int newUserId);
 
     void showKeyguard();
@@ -385,9 +379,6 @@
 
     boolean isDeviceInteractive();
 
-    void setNotificationSnoozed(StatusBarNotification sbn,
-            NotificationSwipeActionHelper.SnoozeOption snoozeOption);
-
     void awakenDreams();
 
     void clearNotificationEffects();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
index fde08bd..45b65bd 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
@@ -173,7 +173,6 @@
 import com.android.systemui.plugins.PluginListener;
 import com.android.systemui.plugins.PluginManager;
 import com.android.systemui.plugins.qs.QS;
-import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper.SnoozeOption;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.qs.QSFragment;
 import com.android.systemui.qs.QSPanelController;
@@ -466,8 +465,6 @@
     private final LightRevealScrim mLightRevealScrim;
     private PowerButtonReveal mPowerButtonReveal;
 
-    private boolean mWakeUpComingFromTouch;
-
     /**
      * Whether we should delay the wakeup animation (which shows the notifications and moves the
      * clock view). This is typically done when waking up from a 'press to unlock' gesture on a
@@ -1625,7 +1622,6 @@
         if (mDozing && mScreenOffAnimationController.allowWakeUpIfDozing()) {
             mPowerManager.wakeUp(
                     time, wakeReason, "com.android.systemui:" + why);
-            mWakeUpComingFromTouch = true;
             mFalsingCollector.onScreenOnFromTouch();
         }
     }
@@ -1802,11 +1798,6 @@
         return mIsLaunchingActivityOverLockscreen;
     }
 
-    @Override
-    public boolean isWakeUpComingFromTouch() {
-        return mWakeUpComingFromTouch;
-    }
-
     /**
      * To be called when there's a state change in StatusBarKeyguardViewManager.
      */
@@ -1935,7 +1926,6 @@
                     SystemClock.uptimeMillis(),
                     PowerManager.WAKE_REASON_APPLICATION,
                     "com.android.systemui:full_screen_intent");
-            mWakeUpComingFromTouch = false;
         }
     }
 
@@ -2323,7 +2313,7 @@
                     mNotificationShadeWindowController.setNotTouchable(false);
                 }
                 finishBarAnimations();
-                resetUserExpandedStates();
+                mNotificationsController.resetUserExpandedStates();
             }
             Trace.endSection();
         }
@@ -2342,11 +2332,6 @@
         }
     };
 
-    @Override
-    public void resetUserExpandedStates() {
-        mNotificationsController.resetUserExpandedStates();
-    }
-
     /**
      * Notify the shade controller that the current user changed
      *
@@ -3116,7 +3101,6 @@
             releaseGestureWakeLock();
             mLaunchCameraWhenFinishedWaking = false;
             mDeviceInteractive = false;
-            mWakeUpComingFromTouch = false;
             updateVisibleToUser();
 
             updateNotificationPanelTouchState();
@@ -3587,12 +3571,6 @@
     };
 
     @Override
-    public void setNotificationSnoozed(StatusBarNotification sbn, SnoozeOption snoozeOption) {
-        mNotificationsController.setNotificationSnoozed(sbn, snoozeOption);
-    }
-
-
-    @Override
     public void awakenDreams() {
         mUiBgExecutor.execute(() -> {
             try {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt
index b36ba384..d42e30c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt
@@ -110,6 +110,9 @@
 
     /** See [MobileIconsInteractor.isForceHidden]. */
     val isForceHidden: Flow<Boolean>
+
+    /** True when in carrier network change mode */
+    val carrierNetworkChangeActive: StateFlow<Boolean>
 }
 
 /** Interactor for a single mobile connection. This connection _should_ have one subscription ID */
@@ -135,6 +138,9 @@
 
     override val isDataEnabled: StateFlow<Boolean> = connectionRepository.dataEnabled
 
+    override val carrierNetworkChangeActive: StateFlow<Boolean> =
+        connectionRepository.carrierNetworkChangeActive
+
     // True if there exists _any_ icon override for this carrierId. Note that overrides can include
     // any or none of the icon groups defined in MobileMappings, so we still need to check on a
     // per-network-type basis whether or not the given icon group is overridden
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/binder/MobileIconBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/binder/MobileIconBinder.kt
index 5b7d45b..a2a247a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/binder/MobileIconBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/binder/MobileIconBinder.kt
@@ -109,12 +109,7 @@
                             viewModel.subscriptionId,
                             icon,
                         )
-                        mobileDrawable.level =
-                            SignalDrawable.getState(
-                                icon.level,
-                                icon.numberOfLevels,
-                                icon.showExclamationMark,
-                            )
+                        mobileDrawable.level = icon.toSignalDrawableState()
                     }
                 }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/model/SignalIconModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/model/SignalIconModel.kt
index be2e41a..6de3f85 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/model/SignalIconModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/model/SignalIconModel.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.statusbar.pipeline.mobile.ui.model
 
+import com.android.settingslib.graph.SignalDrawable
 import com.android.systemui.log.table.Diffable
 import com.android.systemui.log.table.TableRowLogger
 
@@ -24,6 +25,7 @@
     val level: Int,
     val numberOfLevels: Int,
     val showExclamationMark: Boolean,
+    val carrierNetworkChange: Boolean,
 ) : Diffable<SignalIconModel> {
     // TODO(b/267767715): Can we implement [logDiffs] and [logFull] generically for data classes?
     override fun logDiffs(prevVal: SignalIconModel, row: TableRowLogger) {
@@ -36,17 +38,30 @@
         if (prevVal.showExclamationMark != showExclamationMark) {
             row.logChange(COL_SHOW_EXCLAMATION, showExclamationMark)
         }
+        if (prevVal.carrierNetworkChange != carrierNetworkChange) {
+            row.logChange(COL_CARRIER_NETWORK_CHANGE, carrierNetworkChange)
+        }
     }
 
     override fun logFull(row: TableRowLogger) {
         row.logChange(COL_LEVEL, level)
         row.logChange(COL_NUM_LEVELS, numberOfLevels)
         row.logChange(COL_SHOW_EXCLAMATION, showExclamationMark)
+        row.logChange(COL_CARRIER_NETWORK_CHANGE, carrierNetworkChange)
     }
 
+    /** Convert this model to an [Int] consumable by [SignalDrawable]. */
+    fun toSignalDrawableState(): Int =
+        if (carrierNetworkChange) {
+            SignalDrawable.getCarrierChangeState(numberOfLevels)
+        } else {
+            SignalDrawable.getState(level, numberOfLevels, showExclamationMark)
+        }
+
     companion object {
         private const val COL_LEVEL = "level"
         private const val COL_NUM_LEVELS = "numLevels"
         private const val COL_SHOW_EXCLAMATION = "showExclamation"
+        private const val COL_CARRIER_NETWORK_CHANGE = "carrierNetworkChange"
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModel.kt
index 54730ed..35f4f9a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModel.kt
@@ -122,13 +122,20 @@
                 level = shownLevel.value,
                 numberOfLevels = iconInteractor.numberOfLevels.value,
                 showExclamationMark = showExclamationMark.value,
+                carrierNetworkChange = iconInteractor.carrierNetworkChangeActive.value,
             )
         combine(
                 shownLevel,
                 iconInteractor.numberOfLevels,
                 showExclamationMark,
-            ) { shownLevel, numberOfLevels, showExclamationMark ->
-                SignalIconModel(shownLevel, numberOfLevels, showExclamationMark)
+                iconInteractor.carrierNetworkChangeActive,
+            ) { shownLevel, numberOfLevels, showExclamationMark, carrierNetworkChange ->
+                SignalIconModel(
+                    shownLevel,
+                    numberOfLevels,
+                    showExclamationMark,
+                    carrierNetworkChange,
+                )
             }
             .distinctUntilChanged()
             .logDiffsForTable(
@@ -152,8 +159,10 @@
                 iconInteractor.isDataEnabled,
                 iconInteractor.alwaysShowDataRatIcon,
                 iconInteractor.mobileIsDefault,
-            ) { dataConnected, dataEnabled, alwaysShow, mobileIsDefault ->
-                alwaysShow || (dataEnabled && dataConnected && mobileIsDefault)
+                iconInteractor.carrierNetworkChangeActive,
+            ) { dataConnected, dataEnabled, alwaysShow, mobileIsDefault, carrierNetworkChange ->
+                alwaysShow ||
+                    (!carrierNetworkChange && (dataEnabled && dataConnected && mobileIsDefault))
             }
             .distinctUntilChanged()
             .logDiffsForTable(
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerTest.java
index 565fc57..f6450a4 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerTest.java
@@ -253,11 +253,11 @@
                 getViewConstraint(mSecurityViewFlipper.getId());
         ConstraintSet.Constraint userSwitcherConstraint =
                 getViewConstraint(R.id.keyguard_bouncer_user_switcher);
-        assertThat(viewFlipperConstraint.layout.rightToRight).isEqualTo(PARENT_ID);
-        assertThat(viewFlipperConstraint.layout.leftToRight).isEqualTo(
+        assertThat(viewFlipperConstraint.layout.endToEnd).isEqualTo(PARENT_ID);
+        assertThat(viewFlipperConstraint.layout.startToEnd).isEqualTo(
                 R.id.keyguard_bouncer_user_switcher);
-        assertThat(userSwitcherConstraint.layout.leftToLeft).isEqualTo(PARENT_ID);
-        assertThat(userSwitcherConstraint.layout.rightToLeft).isEqualTo(
+        assertThat(userSwitcherConstraint.layout.startToStart).isEqualTo(PARENT_ID);
+        assertThat(userSwitcherConstraint.layout.endToStart).isEqualTo(
                 mSecurityViewFlipper.getId());
         assertThat(viewFlipperConstraint.layout.topToTop).isEqualTo(PARENT_ID);
         assertThat(viewFlipperConstraint.layout.bottomToBottom).isEqualTo(PARENT_ID);
@@ -377,7 +377,7 @@
 
         ConstraintSet.Constraint viewFlipperConstraint = getViewConstraint(
                 mSecurityViewFlipper.getId());
-        assertThat(viewFlipperConstraint.layout.leftToLeft).isEqualTo(PARENT_ID);
+        assertThat(viewFlipperConstraint.layout.startToStart).isEqualTo(PARENT_ID);
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationSettingsTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationSettingsTest.java
index df08dec..b3f9958 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationSettingsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationSettingsTest.java
@@ -16,6 +16,7 @@
 
 package com.android.systemui.accessibility;
 
+import static android.provider.Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_SCALE;
 import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_CAPABILITY;
 import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_ALL;
 import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN;
@@ -30,6 +31,7 @@
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
@@ -53,6 +55,7 @@
 import androidx.test.InstrumentationRegistry;
 import androidx.test.filters.SmallTest;
 
+import com.android.internal.accessibility.common.MagnificationConstants;
 import com.android.internal.graphics.SfVsyncFrameCallbackProvider;
 import com.android.systemui.R;
 import com.android.systemui.SysuiTestCase;
@@ -62,6 +65,7 @@
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
@@ -87,6 +91,11 @@
     private WindowMagnificationSettings mWindowMagnificationSettings;
     private MotionEventHelper mMotionEventHelper = new MotionEventHelper();
 
+    private ArgumentCaptor<Float> mSecureSettingsScaleCaptor;
+    private ArgumentCaptor<String> mSecureSettingsNameCaptor;
+    private ArgumentCaptor<Integer> mSecureSettingsUserHandleCaptor;
+    private ArgumentCaptor<Float> mCallbackMagnifierScaleCaptor;
+
     @Before
     public void setUp() throws Exception {
         MockitoAnnotations.initMocks(this);
@@ -102,6 +111,10 @@
                 mSecureSettings);
 
         mSettingView = mWindowMagnificationSettings.getSettingView();
+        mSecureSettingsScaleCaptor = ArgumentCaptor.forClass(Float.class);
+        mSecureSettingsNameCaptor = ArgumentCaptor.forClass(String.class);
+        mSecureSettingsUserHandleCaptor = ArgumentCaptor.forClass(Integer.class);
+        mCallbackMagnifierScaleCaptor = ArgumentCaptor.forClass(Float.class);
     }
 
     @After
@@ -324,6 +337,20 @@
     }
 
     @Test
+    public void showSettingsPanel_observerForMagnificationScaleRegistered() {
+        setupMagnificationCapabilityAndMode(
+                /* capability= */ ACCESSIBILITY_MAGNIFICATION_MODE_ALL,
+                /* mode= */ ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW);
+
+        mWindowMagnificationSettings.showSettingPanel();
+
+        verify(mSecureSettings).registerContentObserverForUser(
+                eq(ACCESSIBILITY_DISPLAY_MAGNIFICATION_SCALE),
+                any(ContentObserver.class),
+                eq(UserHandle.USER_CURRENT));
+    }
+
+    @Test
     public void hideSettingsPanel_observerUnregistered() {
         setupMagnificationCapabilityAndMode(
                 /* capability= */ ACCESSIBILITY_MAGNIFICATION_MODE_ALL,
@@ -332,7 +359,162 @@
         mWindowMagnificationSettings.showSettingPanel();
         mWindowMagnificationSettings.hideSettingPanel();
 
-        verify(mSecureSettings).unregisterContentObserver(any(ContentObserver.class));
+        verify(mSecureSettings, times(2)).unregisterContentObserver(any(ContentObserver.class));
+    }
+
+    @Test
+    public void seekbarProgress_justInflated_maxValueAndProgressSetCorrectly() {
+        setupScaleInSecureSettings(0f);
+        assertThat(mWindowMagnificationSettings.mZoomSeekbar.getProgress()).isEqualTo(0);
+        assertThat(mWindowMagnificationSettings.mZoomSeekbar.getMax()).isEqualTo(70);
+    }
+
+    @Test
+    public void seekbarProgress_minMagnification_seekbarProgressIsCorrect() {
+        setupScaleInSecureSettings(0f);
+        setupMagnificationCapabilityAndMode(
+                /* capability= */ ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW,
+                /* mode= */ ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW);
+
+        mWindowMagnificationSettings.showSettingPanel();
+
+        // Seekbar index from 0 to 70. 1.0f scale (A11Y_SCALE_MIN_VALUE) would correspond to 0.
+        assertThat(mWindowMagnificationSettings.mZoomSeekbar.getProgress()).isEqualTo(0);
+    }
+
+    @Test
+    public void seekbarProgress_belowMinMagnification_seekbarProgressIsZero() {
+        setupScaleInSecureSettings(0f);
+        setupMagnificationCapabilityAndMode(
+                /* capability= */ ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW,
+                /* mode= */ ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW);
+
+        mWindowMagnificationSettings.showSettingPanel();
+
+        assertThat(mWindowMagnificationSettings.mZoomSeekbar.getProgress()).isEqualTo(0);
+    }
+
+    @Test
+    public void seekbarProgress_magnificationBefore_seekbarProgressIsHalf() {
+        setupScaleInSecureSettings(4f);
+        setupMagnificationCapabilityAndMode(
+                /* capability= */ ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW,
+                /* mode= */ ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW);
+
+        mWindowMagnificationSettings.showSettingPanel();
+
+        // float scale : from 1.0f to 8.0f, seekbar index from 0 to 70.
+        // 4.0f would correspond to 30.
+        assertThat(mWindowMagnificationSettings.mZoomSeekbar.getProgress()).isEqualTo(30);
+    }
+
+    @Test
+    public void seekbarProgress_maxMagnificationBefore_seekbarProgressIsMax() {
+        setupScaleInSecureSettings(8f);
+        setupMagnificationCapabilityAndMode(
+                /* capability= */ ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW,
+                /* mode= */ ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW);
+
+        mWindowMagnificationSettings.showSettingPanel();
+
+        // 8.0f is max magnification {@link MagnificationScaleProvider#MAX_SCALE}.
+        // Max zoom seek bar is 70.
+        assertThat(mWindowMagnificationSettings.mZoomSeekbar.getProgress()).isEqualTo(70);
+    }
+
+    @Test
+    public void seekbarProgress_aboveMaxMagnificationBefore_seekbarProgressIsMax() {
+        setupScaleInSecureSettings(9f);
+        setupMagnificationCapabilityAndMode(
+                /* capability= */ ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW,
+                /* mode= */ ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW);
+
+        mWindowMagnificationSettings.showSettingPanel();
+
+        // Max zoom seek bar is 70.
+        assertThat(mWindowMagnificationSettings.mZoomSeekbar.getProgress()).isEqualTo(70);
+    }
+
+    @Test
+    public void seekbarProgress_progressChangedRoughlyHalf_scaleAndCallbackUpdated() {
+        setupMagnificationCapabilityAndMode(
+                /* capability= */ ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW,
+                /* mode= */ ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW);
+        mWindowMagnificationSettings.showSettingPanel();
+
+        mWindowMagnificationSettings.mZoomSeekbar.setProgress(30);
+
+        verifyScaleUpdatedInSecureSettings(4f);
+        verifyCallbackOnMagnifierScale(4f);
+    }
+
+    @Test
+    public void seekbarProgress_minProgress_callbackUpdated() {
+        setupMagnificationCapabilityAndMode(
+                /* capability= */ ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW,
+                /* mode= */ ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW);
+        mWindowMagnificationSettings.showSettingPanel();
+        // Set progress to non-zero first so onProgressChanged can be triggered upon setting to 0.
+        mWindowMagnificationSettings.mZoomSeekbar.setProgress(30);
+
+        mWindowMagnificationSettings.mZoomSeekbar.setProgress(0);
+
+        // For now, secure settings will not be updated for values < 1.3f. Follow up on this later.
+        verify(mWindowMagnificationSettingsCallback, times(2))
+                .onMagnifierScale(mCallbackMagnifierScaleCaptor.capture());
+        var capturedArgs = mCallbackMagnifierScaleCaptor.getAllValues();
+        assertThat(capturedArgs).hasSize(2);
+        assertThat(capturedArgs.get(1)).isWithin(0.01f).of(1f);
+    }
+
+    @Test
+    public void seekbarProgress_maxProgress_scaleAndCallbackUpdated() {
+        setupMagnificationCapabilityAndMode(
+                /* capability= */ ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW,
+                /* mode= */ ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW);
+        mWindowMagnificationSettings.showSettingPanel();
+
+        mWindowMagnificationSettings.mZoomSeekbar.setProgress(70);
+
+        verifyScaleUpdatedInSecureSettings(8f);
+        verifyCallbackOnMagnifierScale(8f);
+    }
+
+    @Test
+    public void seekbarProgress_scaleUpdatedAfterSettingPanelOpened_progressAlsoUpdated() {
+        setupMagnificationCapabilityAndMode(
+                /* capability= */ ACCESSIBILITY_MAGNIFICATION_MODE_ALL,
+                /* mode= */ ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW);
+        var contentObserverCaptor = ArgumentCaptor.forClass(ContentObserver.class);
+        mWindowMagnificationSettings.showSettingPanel();
+        verify(mSecureSettings).registerContentObserverForUser(
+                eq(ACCESSIBILITY_DISPLAY_MAGNIFICATION_SCALE),
+                contentObserverCaptor.capture(),
+                eq(UserHandle.USER_CURRENT));
+
+        // Simulate outside changes.
+        setupScaleInSecureSettings(4f);
+        // Simulate callback due to outside change.
+        contentObserverCaptor.getValue().onChange(/* selfChange= */ false);
+
+        assertThat(mWindowMagnificationSettings.mZoomSeekbar.getProgress()).isEqualTo(30);
+    }
+
+    private void verifyScaleUpdatedInSecureSettings(float scale) {
+        verify(mSecureSettings).putFloatForUser(
+                mSecureSettingsNameCaptor.capture(),
+                mSecureSettingsScaleCaptor.capture(),
+                mSecureSettingsUserHandleCaptor.capture());
+        assertThat(mSecureSettingsScaleCaptor.getValue()).isWithin(0.01f).of(scale);
+        assertThat(mSecureSettingsNameCaptor.getValue())
+                .isEqualTo(Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_SCALE);
+        assertThat(mSecureSettingsUserHandleCaptor.getValue()).isEqualTo(UserHandle.USER_CURRENT);
+    }
+
+    private void verifyCallbackOnMagnifierScale(float scale) {
+        verify(mWindowMagnificationSettingsCallback)
+                .onMagnifierScale(mCallbackMagnifierScaleCaptor.capture());
+        assertThat(mCallbackMagnifierScaleCaptor.getValue()).isWithin(0.01f).of(scale);
     }
 
     private <T extends View> T getInternalView(@IdRes int idRes) {
@@ -351,4 +533,11 @@
                 ACCESSIBILITY_MAGNIFICATION_MODE_NONE,
                 UserHandle.USER_CURRENT)).thenReturn(mode);
     }
+
+    private void setupScaleInSecureSettings(float scale) {
+        when(mSecureSettings.getFloatForUser(
+                ACCESSIBILITY_DISPLAY_MAGNIFICATION_SCALE,
+                MagnificationConstants.SCALE_MIN_VALUE,
+                UserHandle.USER_CURRENT)).thenReturn(scale);
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/SideFpsControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/SideFpsControllerTest.kt
index 40d9009..2908e75 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/SideFpsControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/SideFpsControllerTest.kt
@@ -64,7 +64,6 @@
 import com.android.systemui.keyguard.data.repository.FakeKeyguardBouncerRepository
 import com.android.systemui.keyguard.domain.interactor.AlternateBouncerInteractor
 import com.android.systemui.plugins.statusbar.StatusBarStateController
-import com.android.systemui.recents.OverviewProxyService
 import com.android.systemui.statusbar.policy.KeyguardStateController
 import com.android.systemui.util.concurrency.FakeExecutor
 import com.android.systemui.util.time.FakeSystemClock
@@ -111,7 +110,6 @@
     @Mock lateinit var activityTaskManager: ActivityTaskManager
     @Mock lateinit var sideFpsView: View
     @Mock lateinit var displayManager: DisplayManager
-    @Mock lateinit var overviewProxyService: OverviewProxyService
     @Mock lateinit var handler: Handler
     @Mock lateinit var dumpManager: DumpManager
     @Captor lateinit var overlayCaptor: ArgumentCaptor<View>
@@ -262,7 +260,6 @@
                 fingerprintManager,
                 windowManager,
                 activityTaskManager,
-                overviewProxyService,
                 displayManager,
                 displayStateInteractor,
                 executor,
@@ -686,18 +683,6 @@
         verify(windowManager).removeView(any())
     }
 
-    private fun hidesWithTaskbar(visible: Boolean) {
-        overlayController.show(SENSOR_ID, REASON_UNKNOWN)
-        executor.runAllReady()
-
-        sideFpsController.overviewProxyListener.onTaskbarStatusUpdated(true, false)
-        executor.runAllReady()
-
-        verify(windowManager).addView(any(), any())
-        verify(windowManager, never()).removeView(any())
-        verify(sideFpsView).visibility = if (visible) View.VISIBLE else View.GONE
-    }
-
     /**
      * {@link SideFpsController#updateOverlayParams} calculates indicator placement for ROTATION_0,
      * and uses RotateUtils.rotateBounds to map to the correct indicator location given the device
diff --git a/packages/SystemUI/tests/src/com/android/systemui/complication/ComplicationCollectionLiveDataTest.java b/packages/SystemUI/tests/src/com/android/systemui/complication/ComplicationCollectionLiveDataTest.java
index ca6282c..461ec65 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/complication/ComplicationCollectionLiveDataTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/complication/ComplicationCollectionLiveDataTest.java
@@ -28,6 +28,7 @@
 import androidx.test.filters.SmallTest;
 
 import com.android.systemui.SysuiTestCase;
+import com.android.systemui.dreams.DreamLogger;
 import com.android.systemui.dreams.DreamOverlayStateController;
 import com.android.systemui.flags.FakeFeatureFlags;
 import com.android.systemui.flags.Flags;
@@ -56,6 +57,8 @@
     private FakeFeatureFlags mFeatureFlags;
     @Mock
     private Observer mObserver;
+    @Mock
+    private DreamLogger mLogger;
 
     @Before
     public void setUp() {
@@ -66,7 +69,8 @@
         mStateController = new DreamOverlayStateController(
                 mExecutor,
                 /* overlayEnabled= */ true,
-                mFeatureFlags);
+                mFeatureFlags,
+                mLogger);
         mLiveData = new ComplicationCollectionLiveData(mStateController);
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStateControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStateControllerTest.java
index 7b41605..2c1ebe4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStateControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStateControllerTest.java
@@ -58,6 +58,9 @@
     @Mock
     private FeatureFlags mFeatureFlags;
 
+    @Mock
+    private DreamLogger mLogger;
+
     final FakeExecutor mExecutor = new FakeExecutor(new FakeSystemClock());
 
     @Before
@@ -405,6 +408,6 @@
     }
 
     private DreamOverlayStateController getDreamOverlayStateController(boolean overlayEnabled) {
-        return new DreamOverlayStateController(mExecutor, overlayEnabled, mFeatureFlags);
+        return new DreamOverlayStateController(mExecutor, overlayEnabled, mFeatureFlags, mLogger);
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStatusBarViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStatusBarViewControllerTest.java
index d16b757..5dc0e55 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStatusBarViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStatusBarViewControllerTest.java
@@ -113,6 +113,8 @@
     DreamOverlayStateController mDreamOverlayStateController;
     @Mock
     UserTracker mUserTracker;
+    @Mock
+    DreamLogger mLogger;
 
     @Captor
     private ArgumentCaptor<DreamOverlayStateController.Callback> mCallbackCaptor;
@@ -146,7 +148,8 @@
                 mStatusBarWindowStateController,
                 mDreamOverlayStatusBarItemsProvider,
                 mDreamOverlayStateController,
-                mUserTracker);
+                mUserTracker,
+                mLogger);
     }
 
     @Test
@@ -289,7 +292,8 @@
                 mStatusBarWindowStateController,
                 mDreamOverlayStatusBarItemsProvider,
                 mDreamOverlayStateController,
-                mUserTracker);
+                mUserTracker,
+                mLogger);
         controller.onViewAttached();
         verify(mView, never()).showIcon(
                 eq(DreamOverlayStatusBarView.STATUS_ICON_NOTIFICATIONS), eq(true), any());
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt
index 4b797cb..953d618 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt
@@ -97,7 +97,8 @@
                 dozeParameters,
                 authController,
                 dreamOverlayCallbackController,
-                mainDispatcher
+                mainDispatcher,
+                testScope.backgroundScope,
             )
     }
 
@@ -343,8 +344,6 @@
                 )
 
             job.cancel()
-            runCurrent()
-            verify(wakefulnessLifecycle).removeObserver(captor.value)
         }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/models/player/SeekBarObserverTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/models/player/SeekBarObserverTest.kt
index 97b18e2..d6ceea1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/models/player/SeekBarObserverTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/models/player/SeekBarObserverTest.kt
@@ -234,4 +234,17 @@
         assertThat(seekBarView.progress).isEqualTo(4000)
         verify(mockSeekbarAnimator).start()
     }
+
+    @Test
+    fun seekbarActive_animationsDisabled() {
+        // WHEN playing, but animations have been disabled
+        observer.animationEnabled = false
+        val isPlaying = true
+        val isScrubbing = false
+        val data = SeekBarViewModel.Progress(true, true, isPlaying, isScrubbing, 3000, 120000)
+        observer.onChanged(data)
+
+        // THEN progress drawable does not animate
+        verify(mockSquigglyProgress).animate = false
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaControlPanelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaControlPanelTest.kt
index f030a03..7b673bc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaControlPanelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaControlPanelTest.kt
@@ -25,6 +25,7 @@
 import android.content.pm.ApplicationInfo
 import android.content.pm.PackageManager
 import android.content.res.Configuration
+import android.database.ContentObserver
 import android.graphics.Bitmap
 import android.graphics.Canvas
 import android.graphics.Color
@@ -40,6 +41,7 @@
 import android.media.session.MediaSession
 import android.media.session.PlaybackState
 import android.os.Bundle
+import android.provider.Settings
 import android.provider.Settings.ACTION_MEDIA_CONTROLS_SETTINGS
 import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper
@@ -73,6 +75,7 @@
 import com.android.systemui.media.controls.models.player.MediaData
 import com.android.systemui.media.controls.models.player.MediaDeviceData
 import com.android.systemui.media.controls.models.player.MediaViewHolder
+import com.android.systemui.media.controls.models.player.SeekBarObserver
 import com.android.systemui.media.controls.models.player.SeekBarViewModel
 import com.android.systemui.media.controls.models.recommendation.KEY_SMARTSPACE_APP_NAME
 import com.android.systemui.media.controls.models.recommendation.RecommendationViewHolder
@@ -97,6 +100,7 @@
 import com.android.systemui.util.mockito.argumentCaptor
 import com.android.systemui.util.mockito.eq
 import com.android.systemui.util.mockito.withArgCaptor
+import com.android.systemui.util.settings.GlobalSettings
 import com.android.systemui.util.time.FakeSystemClock
 import com.google.common.truth.Truth.assertThat
 import dagger.Lazy
@@ -109,6 +113,7 @@
 import org.mockito.ArgumentCaptor
 import org.mockito.ArgumentMatchers.anyInt
 import org.mockito.ArgumentMatchers.anyLong
+import org.mockito.Captor
 import org.mockito.Mock
 import org.mockito.Mockito.anyString
 import org.mockito.Mockito.mock
@@ -233,6 +238,8 @@
             this.set(Flags.MEDIA_EXPLICIT_INDICATOR, true)
             this.set(Flags.MEDIA_RECOMMENDATION_CARD_UPDATE, false)
         }
+    @Mock private lateinit var globalSettings: GlobalSettings
+    @Captor private lateinit var settingsObserverCaptor: ArgumentCaptor<ContentObserver>
 
     @JvmField @Rule val mockito = MockitoJUnit.rule()
 
@@ -273,7 +280,8 @@
                     activityIntentHelper,
                     lockscreenUserManager,
                     broadcastDialogController,
-                    fakeFeatureFlag
+                    fakeFeatureFlag,
+                    globalSettings,
                 ) {
                 override fun loadAnimator(
                     animId: Int,
@@ -284,6 +292,12 @@
                 }
             }
 
+        verify(globalSettings)
+            .registerContentObserver(
+                eq(Settings.Global.getUriFor(Settings.Global.ANIMATOR_DURATION_SCALE)),
+                settingsObserverCaptor.capture()
+            )
+
         initGutsViewHolderMocks()
         initMediaViewHolderMocks()
 
@@ -955,6 +969,30 @@
     }
 
     @Test
+    fun animationSettingChange_updateSeekbar() {
+        // When animations are enabled
+        globalSettings.putFloat(Settings.Global.ANIMATOR_DURATION_SCALE, 1f)
+        val progress = 0.5
+        val state = mediaData.copy(resumption = true, resumeProgress = progress)
+        player.attachPlayer(viewHolder)
+        player.bindPlayer(state, PACKAGE)
+
+        val captor = argumentCaptor<SeekBarObserver>()
+        verify(seekBarData).observeForever(captor.capture())
+        val seekBarObserver = captor.value!!
+
+        // Then the seekbar is set to animate
+        assertThat(seekBarObserver.animationEnabled).isTrue()
+
+        // When the setting changes,
+        globalSettings.putFloat(Settings.Global.ANIMATOR_DURATION_SCALE, 0f)
+        settingsObserverCaptor.value!!.onChange(false)
+
+        // Then the seekbar is set to not animate
+        assertThat(seekBarObserver.animationEnabled).isFalse()
+    }
+
+    @Test
     fun bindNotificationActions() {
         val icon = context.getDrawable(android.R.drawable.ic_media_play)
         val bg = context.getDrawable(R.drawable.qs_media_round_button_background)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
index 41351e5..ee382d3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
@@ -315,6 +315,7 @@
 
     protected final int mMaxUdfpsBurnInOffsetY = 5;
     protected KeyguardBottomAreaInteractor mKeyguardBottomAreaInteractor;
+    protected FakeKeyguardRepository mFakeKeyguardRepository;
     protected KeyguardInteractor mKeyguardInteractor;
     protected NotificationPanelViewController.TouchHandler mTouchHandler;
     protected ConfigurationController mConfigurationController;
@@ -342,10 +343,12 @@
     public void setup() {
         MockitoAnnotations.initMocks(this);
         mMainDispatcher = getMainDispatcher();
-        mKeyguardBottomAreaInteractor = new KeyguardBottomAreaInteractor(
-                new FakeKeyguardRepository());
+        KeyguardInteractorFactory.WithDependencies keyguardInteractorDeps =
+                KeyguardInteractorFactory.create();
+        mFakeKeyguardRepository = keyguardInteractorDeps.getRepository();
+        mKeyguardBottomAreaInteractor = new KeyguardBottomAreaInteractor(mFakeKeyguardRepository);
+        mKeyguardInteractor = keyguardInteractorDeps.getKeyguardInteractor();
 
-        mKeyguardInteractor = KeyguardInteractorFactory.create().getKeyguardInteractor();
         SystemClock systemClock = new FakeSystemClock();
         mStatusBarStateController = new StatusBarStateControllerImpl(mUiEventLogger, mDumpManager,
                 mInteractionJankMonitor, mShadeExpansionStateManager);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
index a5a9de5..b1f8475 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
@@ -58,6 +58,9 @@
 import com.android.keyguard.FaceAuthApiRequestReason;
 import com.android.systemui.DejankUtils;
 import com.android.systemui.R;
+import com.android.systemui.keyguard.shared.model.WakeSleepReason;
+import com.android.systemui.keyguard.shared.model.WakefulnessModel;
+import com.android.systemui.keyguard.shared.model.WakefulnessState;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.statusbar.notification.row.ExpandableView;
 import com.android.systemui.statusbar.notification.row.ExpandableView.OnHeightChangedListener;
@@ -1162,4 +1165,43 @@
         when(mUnlockedScreenOffAnimationController.isAnimationPlaying()).thenReturn(true);
         assertThat(mNotificationPanelViewController.isExpanded()).isTrue();
     }
+
+    @Test
+    public void getFalsingThreshold_deviceNotInteractive_isQsThreshold() {
+        mFakeKeyguardRepository.setWakefulnessModel(
+                new WakefulnessModel(
+                        WakefulnessState.ASLEEP,
+                        /* lastWakeReason= */ WakeSleepReason.TAP,
+                        /* lastSleepReason= */ WakeSleepReason.POWER_BUTTON)
+        );
+        when(mQsController.getFalsingThreshold()).thenReturn(14);
+
+        assertThat(mNotificationPanelViewController.getFalsingThreshold()).isEqualTo(14);
+    }
+
+    @Test
+    public void getFalsingThreshold_lastWakeNotDueToTouch_isQsThreshold() {
+        mFakeKeyguardRepository.setWakefulnessModel(
+                new WakefulnessModel(
+                        WakefulnessState.AWAKE,
+                        /* lastWakeReason= */ WakeSleepReason.POWER_BUTTON,
+                        /* lastSleepReason= */ WakeSleepReason.POWER_BUTTON)
+        );
+        when(mQsController.getFalsingThreshold()).thenReturn(14);
+
+        assertThat(mNotificationPanelViewController.getFalsingThreshold()).isEqualTo(14);
+    }
+
+    @Test
+    public void getFalsingThreshold_lastWakeDueToTouch_greaterThanQsThreshold() {
+        mFakeKeyguardRepository.setWakefulnessModel(
+                new WakefulnessModel(
+                        WakefulnessState.AWAKE,
+                        /* lastWakeReason= */ WakeSleepReason.TAP,
+                        /* lastSleepReason= */ WakeSleepReason.POWER_BUTTON)
+        );
+        when(mQsController.getFalsingThreshold()).thenReturn(14);
+
+        assertThat(mNotificationPanelViewController.getFalsingThreshold()).isGreaterThan(14);
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java
index 6a0e3c6..10aac4e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java
@@ -47,6 +47,7 @@
 import com.android.systemui.classifier.FalsingManagerFake;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor;
 import com.android.systemui.media.controls.ui.KeyguardMediaController;
 import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
@@ -70,6 +71,7 @@
 import com.android.systemui.statusbar.notification.collection.render.NotifStats;
 import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider;
 import com.android.systemui.statusbar.notification.collection.render.SectionHeaderController;
+import com.android.systemui.statusbar.notification.init.NotificationsController;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController.NotificationPanelEvent;
@@ -105,6 +107,7 @@
 public class NotificationStackScrollLayoutControllerTest extends SysuiTestCase {
 
     @Mock private NotificationGutsManager mNotificationGutsManager;
+    @Mock private NotificationsController mNotificationsController;
     @Mock private NotificationVisibilityProvider mVisibilityProvider;
     @Mock private HeadsUpManagerPhone mHeadsUpManager;
     @Mock private NotificationRoundnessManager mNotificationRoundnessManager;
@@ -117,6 +120,7 @@
     @Mock private KeyguardMediaController mKeyguardMediaController;
     @Mock private SysuiStatusBarStateController mSysuiStatusBarStateController;
     @Mock private KeyguardBypassController mKeyguardBypassController;
+    @Mock private KeyguardInteractor mKeyguardInteractor;
     @Mock private NotificationLockscreenUserManager mNotificationLockscreenUserManager;
     @Mock private MetricsLogger mMetricsLogger;
     @Mock private DumpManager mDumpManager;
@@ -453,6 +457,7 @@
                 mNotificationStackScrollLayout,
                 true,
                 mNotificationGutsManager,
+                mNotificationsController,
                 mVisibilityProvider,
                 mHeadsUpManager,
                 mNotificationRoundnessManager,
@@ -463,6 +468,7 @@
                 mSysuiStatusBarStateController,
                 mKeyguardMediaController,
                 mKeyguardBypassController,
+                mKeyguardInteractor,
                 mZenModeController,
                 mNotificationLockscreenUserManager,
                 Optional.<NotificationListViewModel>empty(),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
index a4ee349..c0d98b2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
@@ -79,9 +79,9 @@
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.collection.render.GroupExpansionManager;
 import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager;
+import com.android.systemui.statusbar.notification.init.NotificationsController;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 import com.android.systemui.statusbar.notification.row.FooterView;
-import com.android.systemui.statusbar.phone.CentralSurfaces;
 import com.android.systemui.statusbar.phone.KeyguardBypassController;
 import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
 import com.android.systemui.statusbar.phone.UnlockedScreenOffAnimationController;
@@ -114,7 +114,7 @@
     private TestableResources mTestableResources;
 
     @Rule public MockitoRule mockito = MockitoJUnit.rule();
-    @Mock private CentralSurfaces mCentralSurfaces;
+    @Mock private NotificationsController mNotificationsController;
     @Mock private SysuiStatusBarStateController mBarState;
     @Mock private GroupMembershipManager mGroupMembershipManger;
     @Mock private GroupExpansionManager mGroupExpansionManager;
@@ -181,7 +181,7 @@
                 mNotificationStackSizeCalculator);
         mStackScroller = spy(mStackScrollerInternal);
         mStackScroller.setShelfController(notificationShelfController);
-        mStackScroller.setCentralSurfaces(mCentralSurfaces);
+        mStackScroller.setNotificationsController(mNotificationsController);
         mStackScroller.setEmptyShadeView(mEmptyShadeView);
         when(mStackScrollLayoutController.isHistoryEnabled()).thenReturn(true);
         when(mStackScrollLayoutController.getNotificationRoundnessManager())
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconInteractor.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconInteractor.kt
index f054422e..c4e4193 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconInteractor.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconInteractor.kt
@@ -40,6 +40,8 @@
             )
         )
 
+    override val carrierNetworkChangeActive = MutableStateFlow(false)
+
     override val mobileIsDefault = MutableStateFlow(true)
 
     override val networkTypeIconGroup =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/model/SignalIconModelParameterizedTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/model/SignalIconModelParameterizedTest.kt
new file mode 100644
index 0000000..01c388a
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/model/SignalIconModelParameterizedTest.kt
@@ -0,0 +1,117 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.pipeline.mobile.ui.model
+
+import androidx.test.filters.SmallTest
+import com.android.settingslib.graph.SignalDrawable
+import com.android.systemui.SysuiTestCase
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+import org.junit.runners.Parameterized.Parameters
+
+@SmallTest
+@RunWith(Parameterized::class)
+internal class SignalIconModelParameterizedTest(private val testCase: TestCase) : SysuiTestCase() {
+    @Test
+    fun drawableFromModel_level0_numLevels4_noExclamation_notCarrierNetworkChange() {
+        val model =
+            SignalIconModel(
+                level = 0,
+                numberOfLevels = 4,
+                showExclamationMark = false,
+                carrierNetworkChange = false
+            )
+
+        val expected =
+            SignalDrawable.getState(/* level = */ 0, /* numLevels = */ 4, /* cutOut = */ false)
+
+        assertThat(model.toSignalDrawableState()).isEqualTo(expected)
+    }
+
+    @Test
+    fun runTest() {
+        val model = testCase.toSignalIconModel()
+        assertThat(model.toSignalDrawableState()).isEqualTo(testCase.expected)
+    }
+
+    internal data class TestCase(
+        val level: Int,
+        val numberOfLevels: Int,
+        val showExclamation: Boolean,
+        val carrierNetworkChange: Boolean,
+        val expected: Int,
+    ) {
+        fun toSignalIconModel() =
+            SignalIconModel(
+                level = level,
+                numberOfLevels = numberOfLevels,
+                showExclamationMark = showExclamation,
+                carrierNetworkChange = carrierNetworkChange,
+            )
+
+        override fun toString(): String =
+            "INPUT(level=$level," +
+                "numberOfLevels=$numberOfLevels," +
+                "showExclamation=$showExclamation," +
+                "carrierNetworkChange=$carrierNetworkChange)"
+    }
+
+    companion object {
+        @Parameters(name = "{0}") @JvmStatic fun data() = testData()
+
+        private fun testData(): Collection<TestCase> =
+            listOf(
+                TestCase(
+                    level = 0,
+                    numberOfLevels = 4,
+                    showExclamation = false,
+                    carrierNetworkChange = false,
+                    expected = SignalDrawable.getState(0, 4, false)
+                ),
+                TestCase(
+                    level = 0,
+                    numberOfLevels = 4,
+                    showExclamation = false,
+                    carrierNetworkChange = true,
+                    expected = SignalDrawable.getCarrierChangeState(4)
+                ),
+                TestCase(
+                    level = 2,
+                    numberOfLevels = 5,
+                    showExclamation = false,
+                    carrierNetworkChange = false,
+                    expected = SignalDrawable.getState(2, 5, false)
+                ),
+                TestCase(
+                    level = 2,
+                    numberOfLevels = 5,
+                    showExclamation = true,
+                    carrierNetworkChange = false,
+                    expected = SignalDrawable.getState(2, 5, true)
+                ),
+                TestCase(
+                    level = 2,
+                    numberOfLevels = 5,
+                    showExclamation = true,
+                    carrierNetworkChange = true,
+                    expected = SignalDrawable.getCarrierChangeState(5)
+                ),
+            )
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModelTest.kt
index 2b7bc78..b5ab29d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModelTest.kt
@@ -273,6 +273,27 @@
         }
 
     @Test
+    fun icon_usesCarrierNetworkState_whenInCarrierNetworkChangeMode() =
+        testScope.runTest {
+            var latest: SignalIconModel? = null
+            val job = underTest.icon.onEach { latest = it }.launchIn(this)
+
+            interactor.carrierNetworkChangeActive.value = true
+            interactor.level.value = 1
+
+            assertThat(latest!!.level).isEqualTo(1)
+            assertThat(latest!!.carrierNetworkChange).isTrue()
+
+            // SignalIconModel respects the current level
+            interactor.level.value = 2
+
+            assertThat(latest!!.level).isEqualTo(2)
+            assertThat(latest!!.carrierNetworkChange).isTrue()
+
+            job.cancel()
+        }
+
+    @Test
     fun contentDescription_notInService_usesNoPhone() =
         testScope.runTest {
             var latest: ContentDescription? = null
@@ -338,6 +359,20 @@
         }
 
     @Test
+    fun networkType_null_whenCarrierNetworkChangeActive() =
+        testScope.runTest {
+            interactor.networkTypeIconGroup.value = NetworkTypeIconModel.DefaultIcon(THREE_G)
+            interactor.carrierNetworkChangeActive.value = true
+            interactor.mobileIsDefault.value = true
+            var latest: Icon? = null
+            val job = underTest.networkTypeIcon.onEach { latest = it }.launchIn(this)
+
+            assertThat(latest).isNull()
+
+            job.cancel()
+        }
+
+    @Test
     fun networkTypeIcon_notNull_whenEnabled() =
         testScope.runTest {
             val expected =
@@ -617,13 +652,14 @@
         }
 
     private fun createAndSetViewModel() {
-        underTest = MobileIconViewModel(
-            SUB_1_ID,
-            interactor,
-            airplaneModeInteractor,
-            constants,
-            testScope.backgroundScope,
-        )
+        underTest =
+            MobileIconViewModel(
+                SUB_1_ID,
+                interactor,
+                airplaneModeInteractor,
+                constants,
+                testScope.backgroundScope,
+            )
     }
 
     companion object {
@@ -632,10 +668,20 @@
 
         /** Convenience constructor for these tests */
         fun defaultSignal(level: Int = 1): SignalIconModel {
-            return SignalIconModel(level, NUM_LEVELS, showExclamationMark = false)
+            return SignalIconModel(
+                level,
+                NUM_LEVELS,
+                showExclamationMark = false,
+                carrierNetworkChange = false,
+            )
         }
 
         fun emptySignal(): SignalIconModel =
-            SignalIconModel(level = 0, numberOfLevels = NUM_LEVELS, showExclamationMark = true)
+            SignalIconModel(
+                level = 0,
+                numberOfLevels = NUM_LEVELS,
+                showExclamationMark = true,
+                carrierNetworkChange = false,
+            )
     }
 }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt
index b52a768..f6cbb07 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt
@@ -81,7 +81,7 @@
         MutableStateFlow(
             WakefulnessModel(WakefulnessState.ASLEEP, WakeSleepReason.OTHER, WakeSleepReason.OTHER)
         )
-    override val wakefulness: Flow<WakefulnessModel> = _wakefulnessModel
+    override val wakefulness = _wakefulnessModel
 
     private val _isUdfpsSupported = MutableStateFlow(false)
 
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index ad038d1..fdb28ba 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -4006,17 +4006,14 @@
     @Override
     public boolean registerProxyForDisplay(IAccessibilityServiceClient client, int displayId)
             throws RemoteException {
-        mSecurityPolicy.enforceCallingOrSelfPermission(Manifest.permission.MANAGE_ACCESSIBILITY);
         mSecurityPolicy.enforceCallingOrSelfPermission(Manifest.permission.CREATE_VIRTUAL_DEVICE);
+        mSecurityPolicy.checkForAccessibilityPermissionOrRole();
         if (client == null) {
             return false;
         }
         if (displayId < 0) {
             throw new IllegalArgumentException("The display id " + displayId + " is invalid.");
         }
-        if (displayId == Display.DEFAULT_DISPLAY) {
-            throw new IllegalArgumentException("The default display cannot be proxy-ed.");
-        }
         if (!isTrackedDisplay(displayId)) {
             throw new IllegalArgumentException("The display " + displayId + " does not exist or is"
                     + " not tracked by accessibility.");
@@ -4025,6 +4022,10 @@
             throw new IllegalArgumentException("The display " + displayId + " is already being"
                     + " proxy-ed");
         }
+        if (!mProxyManager.displayBelongsToCaller(Binder.getCallingUid(), displayId)) {
+            throw new SecurityException("The display " + displayId + " does not belong to"
+                    + " the caller.");
+        }
 
         final long identity = Binder.clearCallingIdentity();
         try {
@@ -4042,8 +4043,8 @@
 
     @Override
     public boolean unregisterProxyForDisplay(int displayId) {
-        mSecurityPolicy.enforceCallingOrSelfPermission(Manifest.permission.MANAGE_ACCESSIBILITY);
         mSecurityPolicy.enforceCallingOrSelfPermission(Manifest.permission.CREATE_VIRTUAL_DEVICE);
+        mSecurityPolicy.checkForAccessibilityPermissionOrRole();
         final long identity = Binder.clearCallingIdentity();
         try {
             return mProxyManager.unregisterProxy(displayId);
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilitySecurityPolicy.java b/services/accessibility/java/com/android/server/accessibility/AccessibilitySecurityPolicy.java
index 9335626..f45fa92 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilitySecurityPolicy.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilitySecurityPolicy.java
@@ -18,6 +18,7 @@
 
 import static android.accessibilityservice.AccessibilityService.SoftKeyboardController.ENABLE_IME_FAIL_BY_ADMIN;
 import static android.accessibilityservice.AccessibilityService.SoftKeyboardController.ENABLE_IME_SUCCESS;
+import static android.companion.AssociationRequest.DEVICE_PROFILE_APP_STREAMING;
 
 import android.Manifest;
 import android.accessibilityservice.AccessibilityService;
@@ -25,6 +26,7 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.AppOpsManager;
+import android.app.role.RoleManager;
 import android.appwidget.AppWidgetManagerInternal;
 import android.content.ComponentName;
 import android.content.Context;
@@ -675,6 +677,42 @@
     }
 
     /**
+     * Throws a SecurityException if the caller has neither the MANAGE_ACCESSIBILITY permission nor
+     * the COMPANION_DEVICE_APP_STREAMING role.
+     */
+    public void checkForAccessibilityPermissionOrRole() {
+        final boolean canManageAccessibility =
+                mContext.checkCallingOrSelfPermission(Manifest.permission.MANAGE_ACCESSIBILITY)
+                        == PackageManager.PERMISSION_GRANTED;
+        if (canManageAccessibility) {
+            return;
+        }
+        final int callingUid = Binder.getCallingUid();
+        final long identity = Binder.clearCallingIdentity();
+        try {
+            final RoleManager roleManager = mContext.getSystemService(RoleManager.class);
+            if (roleManager != null) {
+                final List<String> holders = roleManager.getRoleHoldersAsUser(
+                        DEVICE_PROFILE_APP_STREAMING, UserHandle.getUserHandleForUid(callingUid));
+                final String[] packageNames = mPackageManager.getPackagesForUid(callingUid);
+                if (packageNames != null) {
+                    for (String packageName : packageNames) {
+                        if (holders.contains(packageName)) {
+                            return;
+                        }
+                    }
+                }
+            }
+            throw new SecurityException(
+                    "Cannot register a proxy for a device without the "
+                            + "android.app.role.COMPANION_DEVICE_APP_STREAMING role or the"
+                            + " MANAGE_ACCESSIBILITY permission.");
+        } finally {
+            Binder.restoreCallingIdentity(identity);
+        }
+    }
+
+    /**
      * Called after a service was bound or unbound. Checks the current bound accessibility
      * services and updates alarms.
      *
diff --git a/services/accessibility/java/com/android/server/accessibility/ProxyManager.java b/services/accessibility/java/com/android/server/accessibility/ProxyManager.java
index 6dc8fb3..70882c6 100644
--- a/services/accessibility/java/com/android/server/accessibility/ProxyManager.java
+++ b/services/accessibility/java/com/android/server/accessibility/ProxyManager.java
@@ -24,6 +24,7 @@
 import android.accessibilityservice.AccessibilityTrace;
 import android.accessibilityservice.IAccessibilityServiceClient;
 import android.annotation.NonNull;
+import android.companion.virtual.VirtualDevice;
 import android.companion.virtual.VirtualDeviceManager;
 import android.content.ComponentName;
 import android.content.Context;
@@ -319,6 +320,25 @@
         return isTrackingDeviceId;
     }
 
+    /** Returns true if the display belongs to one of the caller's virtual devices. */
+    public boolean displayBelongsToCaller(int callingUid, int proxyDisplayId) {
+        final VirtualDeviceManager vdm = mContext.getSystemService(VirtualDeviceManager.class);
+        final VirtualDeviceManagerInternal localVdm = getLocalVdm();
+        if (vdm == null || localVdm == null) {
+            return false;
+        }
+        final List<VirtualDevice> virtualDevices = vdm.getVirtualDevices();
+        for (VirtualDevice device : virtualDevices) {
+            if (localVdm.getDisplayIdsForDevice(device.getDeviceId()).contains(proxyDisplayId)) {
+                final int ownerUid = localVdm.getDeviceOwnerUid(device.getDeviceId());
+                if (callingUid == ownerUid) {
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+
     /**
      * Sends AccessibilityEvents to a proxy given the event's displayId.
      */
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationScaleProvider.java b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationScaleProvider.java
index 8e1aa38..41c3dcb 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationScaleProvider.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationScaleProvider.java
@@ -16,6 +16,9 @@
 
 package com.android.server.accessibility.magnification;
 
+import static com.android.internal.accessibility.common.MagnificationConstants.SCALE_MAX_VALUE;
+import static com.android.internal.accessibility.common.MagnificationConstants.SCALE_MIN_VALUE;
+
 import android.content.Context;
 import android.os.UserHandle;
 import android.provider.Settings;
@@ -37,8 +40,9 @@
 
     @VisibleForTesting
     protected static final float DEFAULT_MAGNIFICATION_SCALE = 2.0f;
-    public static final float MIN_SCALE = 1.0f;
-    public static final float MAX_SCALE = 8.0f;
+
+    public static final float MIN_SCALE = SCALE_MIN_VALUE;
+    public static final float MAX_SCALE = SCALE_MAX_VALUE;
 
     private final Context mContext;
     // Stores the scale for non-default displays.
@@ -134,6 +138,6 @@
     }
 
     static float constrainScale(float scale) {
-        return MathUtils.constrain(scale, MIN_SCALE, MAX_SCALE);
+        return MathUtils.constrain(scale, SCALE_MIN_VALUE, SCALE_MAX_VALUE);
     }
 }
diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java
index 4576abb..563e431 100644
--- a/services/autofill/java/com/android/server/autofill/Session.java
+++ b/services/autofill/java/com/android/server/autofill/Session.java
@@ -4857,16 +4857,16 @@
             return null;
         }
 
-        final boolean isWhitelisted = mService
+        final boolean isAllowlisted = mService
                 .isWhitelistedForAugmentedAutofillLocked(mComponentName);
 
-        if (!isWhitelisted) {
+        if (!isAllowlisted) {
             if (sVerbose) {
                 Slog.v(TAG, "triggerAugmentedAutofillLocked(): "
                         + ComponentName.flattenToShortString(mComponentName) + " not whitelisted ");
             }
             logAugmentedAutofillRequestLocked(mode, remoteService.getComponentName(),
-                    mCurrentViewId, isWhitelisted, /* isInline= */ null);
+                    mCurrentViewId, isAllowlisted, /* isInline= */ null);
             return null;
         }
 
@@ -4899,32 +4899,9 @@
 
         final AutofillId focusedId = mCurrentViewId;
 
-        final Function<InlineFillUi, Boolean> inlineSuggestionsResponseCallback =
-                response -> {
-                    synchronized (mLock) {
-                        return mInlineSessionController.setInlineFillUiLocked(response);
-                    }
-                };
         final Consumer<InlineSuggestionsRequest> requestAugmentedAutofill =
-                (inlineSuggestionsRequest) -> {
-                    synchronized (mLock) {
-                        logAugmentedAutofillRequestLocked(mode, remoteService.getComponentName(),
-                                focusedId, isWhitelisted, inlineSuggestionsRequest != null);
-                        remoteService.onRequestAutofillLocked(id, mClient,
-                                taskId, mComponentName, mActivityToken,
-                                AutofillId.withoutSession(focusedId), currentValue,
-                                inlineSuggestionsRequest, inlineSuggestionsResponseCallback,
-                                /*onErrorCallback=*/ () -> {
-                                    synchronized (mLock) {
-                                        cancelAugmentedAutofillLocked();
-
-                                        // Also cancel augmented in IME
-                                        mInlineSessionController.setInlineFillUiLocked(
-                                                InlineFillUi.emptyUi(mCurrentViewId));
-                                    }
-                                }, mService.getRemoteInlineSuggestionRenderServiceLocked(), userId);
-                    }
-                };
+                new AugmentedAutofillInlineSuggestionRequestConsumer(
+                        this, focusedId, isAllowlisted, mode, currentValue);
 
         // When the inline suggestion render service is available and the view is focused, there
         // are 3 cases when augmented autofill should ask IME for inline suggestion request,
@@ -4942,14 +4919,11 @@
                         || mSessionFlags.mExpiredResponse)
                 && (isViewFocusedLocked(flags) || isRequestSupportFillDialog(flags))) {
             if (sDebug) Slog.d(TAG, "Create inline request for augmented autofill");
-            remoteRenderService.getInlineSuggestionsRendererInfo(new RemoteCallback(
-                    (extras) -> {
-                        synchronized (mLock) {
-                            mInlineSessionController.onCreateInlineSuggestionsRequestLocked(
-                                    focusedId, /*requestConsumer=*/ requestAugmentedAutofill,
-                                    extras);
-                        }
-                    }, mHandler));
+            remoteRenderService.getInlineSuggestionsRendererInfo(
+                    new RemoteCallback(
+                            new AugmentedAutofillInlineSuggestionRendererOnResultListener(
+                                    this, focusedId, requestAugmentedAutofill),
+                            mHandler));
         } else {
             requestAugmentedAutofill.accept(
                     mInlineSessionController.getInlineSuggestionsRequestLocked().orElse(null));
@@ -4960,6 +4934,169 @@
         return mAugmentedAutofillDestroyer;
     }
 
+    private static class AugmentedAutofillInlineSuggestionRendererOnResultListener
+            implements RemoteCallback.OnResultListener {
+
+        WeakReference<Session> mSessionWeakRef;
+        final AutofillId mFocusedId;
+        Consumer<InlineSuggestionsRequest> mRequestAugmentedAutofill;
+
+        AugmentedAutofillInlineSuggestionRendererOnResultListener(
+                Session session,
+                AutofillId focussedId,
+                Consumer<InlineSuggestionsRequest> requestAugmentedAutofill) {
+            mSessionWeakRef = new WeakReference<>(session);
+            mFocusedId = focussedId;
+            mRequestAugmentedAutofill = requestAugmentedAutofill;
+        }
+
+        @Override
+        public void onResult(@Nullable Bundle result) {
+            Session session = mSessionWeakRef.get();
+
+            if (logIfSessionNull(
+                    session, "AugmentedAutofillInlineSuggestionRendererOnResultListener:")) {
+                return;
+            }
+            synchronized (session.mLock) {
+                session.mInlineSessionController.onCreateInlineSuggestionsRequestLocked(
+                        mFocusedId, /*requestConsumer=*/ mRequestAugmentedAutofill,
+                        result);
+            }
+        }
+    }
+
+    private static class AugmentedAutofillInlineSuggestionRequestConsumer
+            implements Consumer<InlineSuggestionsRequest> {
+
+        WeakReference<Session> mSessionWeakRef;
+        final AutofillId mFocusedId;
+        final boolean mIsAllowlisted;
+        final int mMode;
+        final AutofillValue mCurrentValue;
+
+        AugmentedAutofillInlineSuggestionRequestConsumer(
+                Session session,
+                AutofillId focussedId,
+                boolean isAllowlisted,
+                int mode,
+                AutofillValue currentValue) {
+            mSessionWeakRef = new WeakReference<>(session);
+            mFocusedId = focussedId;
+            mIsAllowlisted = isAllowlisted;
+            mMode = mode;
+            mCurrentValue = currentValue;
+
+        }
+        @Override
+        public void accept(InlineSuggestionsRequest inlineSuggestionsRequest) {
+            Session session = mSessionWeakRef.get();
+
+            if (logIfSessionNull(
+                    session, "AugmentedAutofillInlineSuggestionRequestConsumer:")) {
+                return;
+            }
+            session.onAugmentedAutofillInlineSuggestionAccept(
+                    inlineSuggestionsRequest, mFocusedId, mIsAllowlisted, mMode, mCurrentValue);
+
+        }
+    }
+
+    private static class AugmentedAutofillInlineSuggestionsResponseCallback
+            implements Function<InlineFillUi, Boolean> {
+
+        WeakReference<Session> mSessionWeakRef;
+
+        AugmentedAutofillInlineSuggestionsResponseCallback(Session session) {
+            this.mSessionWeakRef = new WeakReference<>(session);
+        }
+
+        @Override
+        public Boolean apply(InlineFillUi inlineFillUi) {
+            Session session = mSessionWeakRef.get();
+
+            if (logIfSessionNull(
+                    session, "AugmentedAutofillInlineSuggestionsResponseCallback:")) {
+                return false;
+            }
+
+            synchronized (session.mLock) {
+                return session.mInlineSessionController.setInlineFillUiLocked(inlineFillUi);
+            }
+        }
+    }
+
+    private static class AugmentedAutofillErrorCallback implements Runnable {
+
+        WeakReference<Session> mSessionWeakRef;
+
+        AugmentedAutofillErrorCallback(Session session) {
+            this.mSessionWeakRef = new WeakReference<>(session);
+        }
+
+        @Override
+        public void run() {
+            Session session = mSessionWeakRef.get();
+
+            if (logIfSessionNull(session, "AugmentedAutofillErrorCallback:")) {
+                return;
+            }
+            session.onAugmentedAutofillErrorCallback();
+        }
+    }
+
+    /**
+     * If the session is null or has been destroyed, log the error msg, and return true.
+     * This is a helper function intended to be called when de-referencing from a weak reference.
+     * @param session
+     * @param logPrefix
+     * @return true if the session is null, false otherwise.
+     */
+    private static boolean logIfSessionNull(Session session, String logPrefix) {
+        if (session == null) {
+            Slog.wtf(TAG, logPrefix + " Session null");
+            return true;
+        }
+        if (session.mDestroyed) {
+            // TODO: Update this to return in this block. We aren't doing this to preserve the
+            //  behavior, but can be modified once we have more time to soak the changes.
+            Slog.w(TAG, logPrefix + " Session destroyed, but following through");
+            // Follow-through
+        }
+        return false;
+    }
+
+    private void onAugmentedAutofillInlineSuggestionAccept(
+            InlineSuggestionsRequest inlineSuggestionsRequest,
+            AutofillId focussedId,
+            boolean isAllowlisted,
+            int mode,
+            AutofillValue currentValue) {
+        synchronized (mLock) {
+            final RemoteAugmentedAutofillService remoteService =
+                    mService.getRemoteAugmentedAutofillServiceLocked();
+            logAugmentedAutofillRequestLocked(mode, remoteService.getComponentName(),
+                    focussedId, isAllowlisted, inlineSuggestionsRequest != null);
+            remoteService.onRequestAutofillLocked(id, mClient,
+                    taskId, mComponentName, mActivityToken,
+                    AutofillId.withoutSession(focussedId), currentValue,
+                    inlineSuggestionsRequest,
+                    new AugmentedAutofillInlineSuggestionsResponseCallback(this),
+                    new AugmentedAutofillErrorCallback(this),
+                    mService.getRemoteInlineSuggestionRenderServiceLocked(), userId);
+        }
+    }
+
+    private void onAugmentedAutofillErrorCallback() {
+        synchronized (mLock) {
+            cancelAugmentedAutofillLocked();
+
+            // Also cancel augmented in IME
+            mInlineSessionController.setInlineFillUiLocked(
+                    InlineFillUi.emptyUi(mCurrentViewId));
+        }
+    }
+
     @GuardedBy("mLock")
     private void cancelAugmentedAutofillLocked() {
         final RemoteAugmentedAutofillService remoteService = mService
diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
index 6b99494..a1ccade 100644
--- a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
+++ b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
@@ -1415,6 +1415,30 @@
                 mCrossDeviceSyncController.syncMessageToDevice(associationId, message);
             }
         }
+
+        @Override
+        public void sendCrossDeviceSyncMessageToAllDevices(int userId, byte[] message) {
+            if (CompanionDeviceConfig.isEnabled(
+                    CompanionDeviceConfig.ENABLE_CONTEXT_SYNC_TELECOM)) {
+                mCrossDeviceSyncController.syncMessageToAllDevicesForUserId(userId, message);
+            }
+        }
+
+        @Override
+        public void addSelfOwnedCallId(String callId) {
+            if (CompanionDeviceConfig.isEnabled(
+                    CompanionDeviceConfig.ENABLE_CONTEXT_SYNC_TELECOM)) {
+                mCrossDeviceSyncController.addSelfOwnedCallId(callId);
+            }
+        }
+
+        @Override
+        public void removeSelfOwnedCallId(String callId) {
+            if (CompanionDeviceConfig.isEnabled(
+                    CompanionDeviceConfig.ENABLE_CONTEXT_SYNC_TELECOM)) {
+                mCrossDeviceSyncController.removeSelfOwnedCallId(callId);
+            }
+        }
     }
 
     /**
diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceManagerServiceInternal.java b/services/companion/java/com/android/server/companion/CompanionDeviceManagerServiceInternal.java
index c5ef4e4..cdf832f 100644
--- a/services/companion/java/com/android/server/companion/CompanionDeviceManagerServiceInternal.java
+++ b/services/companion/java/com/android/server/companion/CompanionDeviceManagerServiceInternal.java
@@ -45,6 +45,15 @@
      */
     void sendCrossDeviceSyncMessage(int associationId, byte[] message);
 
+    /** Sends the provided message to all active associations for the specified user. */
+    void sendCrossDeviceSyncMessageToAllDevices(int userId, byte[] message);
+
+    /** Mark a call id as "self owned" (i.e. this device owns the canonical call). */
+    void addSelfOwnedCallId(String callId);
+
+    /** Unmark a call id as "self owned" (i.e. this device no longer owns the canonical call). */
+    void removeSelfOwnedCallId(String callId);
+
     /**
      * Requests a sync from an InCallService to CDM, for the given user and call metadata.
      */
diff --git a/services/companion/java/com/android/server/companion/datatransfer/contextsync/CallMetadataSyncConnectionService.java b/services/companion/java/com/android/server/companion/datatransfer/contextsync/CallMetadataSyncConnectionService.java
index 7371824..fac1c89 100644
--- a/services/companion/java/com/android/server/companion/datatransfer/contextsync/CallMetadataSyncConnectionService.java
+++ b/services/companion/java/com/android/server/companion/datatransfer/contextsync/CallMetadataSyncConnectionService.java
@@ -36,7 +36,7 @@
 import java.util.HashMap;
 import java.util.Map;
 import java.util.Objects;
-import java.util.UUID;
+import java.util.Set;
 
 /** Service for Telecom to bind to when call metadata is synced between devices. */
 public class CallMetadataSyncConnectionService extends ConnectionService {
@@ -65,11 +65,32 @@
                                         associationId, call.getId()));
                         if (existingConnection != null) {
                             existingConnection.update(call);
+                        } else {
+                            // Check if this is an in-progress id being finalized.
+                            CallMetadataSyncConnectionIdentifier key = null;
+                            for (Map.Entry<CallMetadataSyncConnectionIdentifier,
+                                    CallMetadataSyncConnection> e : mActiveConnections.entrySet()) {
+                                if (e.getValue().getAssociationId() == associationId
+                                        && !e.getValue().isIdFinalized()
+                                        && call.getId().endsWith(e.getValue().getCallId())) {
+                                    key = e.getKey();
+                                    break;
+                                }
+                            }
+                            if (key != null) {
+                                final CallMetadataSyncConnection connection =
+                                        mActiveConnections.remove(key);
+                                connection.update(call);
+                                mActiveConnections.put(
+                                        new CallMetadataSyncConnectionIdentifier(associationId,
+                                                call.getId()), connection);
+                            }
                         }
                     }
                     // Remove obsolete calls.
                     mActiveConnections.values().removeIf(connection -> {
-                        if (associationId == connection.getAssociationId()
+                        if (connection.isIdFinalized()
+                                && associationId == connection.getAssociationId()
                                 && !callMetadataSyncData.hasCall(connection.getCallId())) {
                             connection.setDisconnected(new DisconnectCause(DisconnectCause.REMOTE));
                             return true;
@@ -77,6 +98,17 @@
                         return false;
                     });
                 }
+
+                @Override
+                void cleanUpCallIds(Set<String> callIds) {
+                    mActiveConnections.values().removeIf(connection -> {
+                        if (callIds.contains(connection.getCallId())) {
+                            connection.setDisconnected(new DisconnectCause(DisconnectCause.REMOTE));
+                            return true;
+                        }
+                        return false;
+                    });
+                }
             };
 
     @Override
@@ -95,10 +127,9 @@
             ConnectionRequest connectionRequest) {
         final int associationId = connectionRequest.getExtras().getInt(
                 CrossDeviceSyncController.EXTRA_ASSOCIATION_ID);
-        final CallMetadataSyncData.Call call = connectionRequest.getExtras().getParcelable(
-                CrossDeviceSyncController.EXTRA_CALL, CallMetadataSyncData.Call.class);
-        // InCallServices outside of framework (like Dialer's) might try to read this, and crash
-        // when they can't. Remove it once we're done with it, as well as the other internal ones.
+        final CallMetadataSyncData.Call call = CallMetadataSyncData.Call.fromBundle(
+                connectionRequest.getExtras().getBundle(CrossDeviceSyncController.EXTRA_CALL));
+        call.setDirection(android.companion.Telecom.Call.INCOMING);
         connectionRequest.getExtras().remove(CrossDeviceSyncController.EXTRA_CALL);
         connectionRequest.getExtras().remove(CrossDeviceSyncController.EXTRA_CALL_FACILITATOR_ID);
         connectionRequest.getExtras().remove(CrossDeviceSyncController.EXTRA_ASSOCIATION_ID);
@@ -130,18 +161,26 @@
     @Override
     public Connection onCreateOutgoingConnection(PhoneAccountHandle phoneAccountHandle,
             ConnectionRequest connectionRequest) {
-        final PhoneAccount phoneAccount = mTelecomManager.getPhoneAccount(phoneAccountHandle);
+        final PhoneAccountHandle handle = phoneAccountHandle != null ? phoneAccountHandle
+                : connectionRequest.getAccountHandle();
+        final PhoneAccount phoneAccount = mTelecomManager.getPhoneAccount(handle);
 
         final CallMetadataSyncData.Call call = new CallMetadataSyncData.Call();
-        call.setId(UUID.randomUUID().toString());
+        call.setId(
+                connectionRequest.getExtras().getString(CrossDeviceSyncController.EXTRA_CALL_ID));
         call.setStatus(android.companion.Telecom.Call.UNKNOWN_STATUS);
         final CallMetadataSyncData.CallFacilitator callFacilitator =
-                new CallMetadataSyncData.CallFacilitator(phoneAccount.getLabel().toString(),
-                        phoneAccount.getExtras().getString(
-                                CrossDeviceSyncController.EXTRA_CALL_FACILITATOR_ID));
+                new CallMetadataSyncData.CallFacilitator(phoneAccount != null
+                        ? phoneAccount.getLabel().toString()
+                        : handle.getComponentName().getShortClassName(),
+                        phoneAccount != null ? phoneAccount.getExtras().getString(
+                                CrossDeviceSyncController.EXTRA_CALL_FACILITATOR_ID)
+                                : handle.getComponentName().getPackageName());
         call.setFacilitator(callFacilitator);
+        call.setDirection(android.companion.Telecom.Call.OUTGOING);
+        call.setCallerId(connectionRequest.getAddress().getSchemeSpecificPart());
 
-        final int associationId = connectionRequest.getExtras().getInt(
+        final int associationId = phoneAccount.getExtras().getInt(
                 CrossDeviceSyncController.EXTRA_ASSOCIATION_ID);
 
         connectionRequest.getExtras().remove(CrossDeviceSyncController.EXTRA_CALL);
@@ -160,13 +199,15 @@
                                 CrossDeviceSyncController.createCallControlMessage(callId, action));
                     }
                 });
-        connection.setConnectionProperties(Connection.PROPERTY_IS_EXTERNAL_CALL);
+        connection.setCallerDisplayName(call.getCallerId(), TelecomManager.PRESENTATION_ALLOWED);
 
+        mCdmsi.addSelfOwnedCallId(call.getId());
         mCdmsi.sendCrossDeviceSyncMessage(associationId,
                 CrossDeviceSyncController.createCallCreateMessage(call.getId(),
                         connectionRequest.getAddress().toString(),
                         call.getFacilitator().getIdentifier()));
 
+        connection.setInitializing();
         return connection;
     }
 
@@ -240,6 +281,7 @@
         private final int mAssociationId;
         private final CallMetadataSyncData.Call mCall;
         private final CallMetadataSyncConnectionCallback mCallback;
+        private boolean mIsIdFinalized;
 
         CallMetadataSyncConnection(TelecomManager telecomManager, AudioManager audioManager,
                 int associationId, CallMetadataSyncData.Call call,
@@ -259,6 +301,10 @@
             return mAssociationId;
         }
 
+        public boolean isIdFinalized() {
+            return mIsIdFinalized;
+        }
+
         private void initialize() {
             final int status = mCall.getStatus();
             if (status == android.companion.Telecom.Call.RINGING_SILENCED) {
@@ -273,6 +319,8 @@
                 setOnHold();
             } else if (state == Call.STATE_DISCONNECTED) {
                 setDisconnected(new DisconnectCause(DisconnectCause.REMOTE));
+            } else if (state == Call.STATE_DIALING) {
+                setDialing();
             } else {
                 setInitialized();
             }
@@ -307,6 +355,10 @@
         }
 
         private void update(CallMetadataSyncData.Call call) {
+            if (!mIsIdFinalized) {
+                mCall.setId(call.getId());
+                mIsIdFinalized = true;
+            }
             final int status = call.getStatus();
             if (status == android.companion.Telecom.Call.RINGING_SILENCED
                     && mCall.getStatus() != android.companion.Telecom.Call.RINGING_SILENCED) {
@@ -323,6 +375,8 @@
                     setOnHold();
                 } else if (state == Call.STATE_DISCONNECTED) {
                     setDisconnected(new DisconnectCause(DisconnectCause.REMOTE));
+                } else if (state == Call.STATE_DIALING) {
+                    setDialing();
                 } else {
                     Slog.e(TAG, "Could not update call to unknown state");
                 }
diff --git a/services/companion/java/com/android/server/companion/datatransfer/contextsync/CallMetadataSyncData.java b/services/companion/java/com/android/server/companion/datatransfer/contextsync/CallMetadataSyncData.java
index d8621cb..74641a4 100644
--- a/services/companion/java/com/android/server/companion/datatransfer/contextsync/CallMetadataSyncData.java
+++ b/services/companion/java/com/android/server/companion/datatransfer/contextsync/CallMetadataSyncData.java
@@ -16,10 +16,8 @@
 
 package com.android.server.companion.datatransfer.contextsync;
 
-import android.annotation.NonNull;
 import android.companion.ContextSyncMessage;
-import android.os.Parcel;
-import android.os.Parcelable;
+import android.os.Bundle;
 
 import java.util.ArrayList;
 import java.util.Collection;
@@ -74,9 +72,10 @@
         return mCallFacilitators;
     }
 
-    public static class CallFacilitator implements Parcelable {
+    public static class CallFacilitator {
         private String mName;
         private String mIdentifier;
+        private boolean mIsTel;
 
         CallFacilitator() {}
 
@@ -85,16 +84,6 @@
             mIdentifier = identifier;
         }
 
-        CallFacilitator(Parcel parcel) {
-            this(parcel.readString(), parcel.readString());
-        }
-
-        @Override
-        public void writeToParcel(Parcel parcel, int parcelableFlags) {
-            parcel.writeString(mName);
-            parcel.writeString(mIdentifier);
-        }
-
         public String getName() {
             return mName;
         }
@@ -103,6 +92,10 @@
             return mIdentifier;
         }
 
+        public boolean isTel() {
+            return mIsTel;
+        }
+
         public void setName(String name) {
             mName = name;
         }
@@ -111,25 +104,9 @@
             mIdentifier = identifier;
         }
 
-        @Override
-        public int describeContents() {
-            return 0;
+        public void setIsTel(boolean isTel) {
+            mIsTel = isTel;
         }
-
-        @NonNull
-        public static final Parcelable.Creator<CallFacilitator> CREATOR =
-                new Parcelable.Creator<>() {
-
-                    @Override
-                    public CallFacilitator createFromParcel(Parcel source) {
-                        return new CallFacilitator(source);
-                    }
-
-                    @Override
-                    public CallFacilitator[] newArray(int size) {
-                        return new CallFacilitator[size];
-                    }
-                };
     }
 
     public static class CallControlRequest {
@@ -183,40 +160,57 @@
         }
     }
 
-    public static class Call implements Parcelable {
+    public static class Call {
+
+        private static final String EXTRA_CALLER_ID =
+                "com.android.server.companion.datatransfer.contextsync.extra.CALLER_ID";
+        private static final String EXTRA_APP_ICON =
+                "com.android.server.companion.datatransfer.contextsync.extra.APP_ICON";
+        private static final String EXTRA_FACILITATOR_NAME =
+                "com.android.server.companion.datatransfer.contextsync.extra.FACILITATOR_NAME";
+        private static final String EXTRA_FACILITATOR_ID =
+                "com.android.server.companion.datatransfer.contextsync.extra.FACILITATOR_ID";
+        private static final String EXTRA_STATUS =
+                "com.android.server.companion.datatransfer.contextsync.extra.STATUS";
+        private static final String EXTRA_DIRECTION =
+                "com.android.server.companion.datatransfer.contextsync.extra.DIRECTION";
+        private static final String EXTRA_CONTROLS =
+                "com.android.server.companion.datatransfer.contextsync.extra.CONTROLS";
         private String mId;
         private String mCallerId;
         private byte[] mAppIcon;
         private CallFacilitator mFacilitator;
         private int mStatus;
+        private int mDirection;
         private final Set<Integer> mControls = new HashSet<>();
 
-        public static Call fromParcel(Parcel parcel) {
+        public static Call fromBundle(Bundle bundle) {
             final Call call = new Call();
-            call.setId(parcel.readString());
-            call.setCallerId(parcel.readString());
-            call.setAppIcon(parcel.readBlob());
-            call.setFacilitator(parcel.readParcelable(CallFacilitator.class.getClassLoader(),
-                    CallFacilitator.class));
-            call.setStatus(parcel.readInt());
-            final int numberOfControls = parcel.readInt();
-            for (int i = 0; i < numberOfControls; i++) {
-                call.addControl(parcel.readInt());
+            if (bundle != null) {
+                call.setId(bundle.getString(CrossDeviceSyncController.EXTRA_CALL_ID));
+                call.setCallerId(bundle.getString(EXTRA_CALLER_ID));
+                call.setAppIcon(bundle.getByteArray(EXTRA_APP_ICON));
+                final String facilitatorName = bundle.getString(EXTRA_FACILITATOR_NAME);
+                final String facilitatorIdentifier = bundle.getString(EXTRA_FACILITATOR_ID);
+                call.setFacilitator(new CallFacilitator(facilitatorName, facilitatorIdentifier));
+                call.setStatus(bundle.getInt(EXTRA_STATUS));
+                call.setDirection(bundle.getInt(EXTRA_DIRECTION));
+                call.setControls(new HashSet<>(bundle.getIntegerArrayList(EXTRA_CONTROLS)));
             }
             return call;
         }
 
-        @Override
-        public void writeToParcel(Parcel parcel, int parcelableFlags) {
-            parcel.writeString(mId);
-            parcel.writeString(mCallerId);
-            parcel.writeBlob(mAppIcon);
-            parcel.writeParcelable(mFacilitator, parcelableFlags);
-            parcel.writeInt(mStatus);
-            parcel.writeInt(mControls.size());
-            for (int control : mControls) {
-                parcel.writeInt(control);
-            }
+        public Bundle writeToBundle() {
+            final Bundle bundle = new Bundle();
+            bundle.putString(CrossDeviceSyncController.EXTRA_CALL_ID, mId);
+            bundle.putString(EXTRA_CALLER_ID, mCallerId);
+            bundle.putByteArray(EXTRA_APP_ICON, mAppIcon);
+            bundle.putString(EXTRA_FACILITATOR_NAME, mFacilitator.getName());
+            bundle.putString(EXTRA_FACILITATOR_ID, mFacilitator.getIdentifier());
+            bundle.putInt(EXTRA_STATUS, mStatus);
+            bundle.putInt(EXTRA_DIRECTION, mDirection);
+            bundle.putIntegerArrayList(EXTRA_CONTROLS, new ArrayList<>(mControls));
+            return bundle;
         }
 
         void setId(String id) {
@@ -239,6 +233,10 @@
             mStatus = status;
         }
 
+        void setDirection(int direction) {
+            mDirection = direction;
+        }
+
         void addControl(int control) {
             mControls.add(control);
         }
@@ -268,6 +266,10 @@
             return mStatus;
         }
 
+        int getDirection() {
+            return mDirection;
+        }
+
         Set<Integer> getControls() {
             return mControls;
         }
@@ -288,23 +290,5 @@
         public int hashCode() {
             return Objects.hashCode(mId);
         }
-
-        @Override
-        public int describeContents() {
-            return 0;
-        }
-
-        @NonNull public static final Parcelable.Creator<Call> CREATOR = new Parcelable.Creator<>() {
-
-            @Override
-            public Call createFromParcel(Parcel source) {
-                return Call.fromParcel(source);
-            }
-
-            @Override
-            public Call[] newArray(int size) {
-                return new Call[size];
-            }
-        };
     }
 }
diff --git a/services/companion/java/com/android/server/companion/datatransfer/contextsync/CallMetadataSyncInCallService.java b/services/companion/java/com/android/server/companion/datatransfer/contextsync/CallMetadataSyncInCallService.java
index b46d5d3..e6d36a5 100644
--- a/services/companion/java/com/android/server/companion/datatransfer/contextsync/CallMetadataSyncInCallService.java
+++ b/services/companion/java/com/android/server/companion/datatransfer/contextsync/CallMetadataSyncInCallService.java
@@ -79,15 +79,20 @@
                         int callControlAction) {
                     final CrossDeviceCall crossDeviceCall = getCallForId(crossDeviceCallId,
                             mCurrentCalls.values());
-                    if (crossDeviceCall == null) {
-                        return;
-                    }
                     switch (callControlAction) {
                         case android.companion.Telecom.ACCEPT:
-                            crossDeviceCall.doAccept();
+                            if (crossDeviceCall != null) {
+                                crossDeviceCall.doAccept();
+                            } else {
+                                Slog.w(TAG, "Failed to process accept action; no matching call");
+                            }
                             break;
                         case android.companion.Telecom.REJECT:
-                            crossDeviceCall.doReject();
+                            if (crossDeviceCall != null) {
+                                crossDeviceCall.doReject();
+                            } else {
+                                Slog.w(TAG, "Failed to process reject action; no matching call");
+                            }
                             break;
                         case android.companion.Telecom.SILENCE:
                             doSilence();
@@ -99,13 +104,25 @@
                             doUnmute();
                             break;
                         case android.companion.Telecom.END:
-                            crossDeviceCall.doEnd();
+                            if (crossDeviceCall != null) {
+                                crossDeviceCall.doEnd();
+                            } else {
+                                Slog.w(TAG, "Failed to process end action; no matching call");
+                            }
                             break;
                         case android.companion.Telecom.PUT_ON_HOLD:
-                            crossDeviceCall.doPutOnHold();
+                            if (crossDeviceCall != null) {
+                                crossDeviceCall.doPutOnHold();
+                            } else {
+                                Slog.w(TAG, "Failed to process hold action; no matching call");
+                            }
                             break;
                         case android.companion.Telecom.TAKE_OFF_HOLD:
-                            crossDeviceCall.doTakeOffHold();
+                            if (crossDeviceCall != null) {
+                                crossDeviceCall.doTakeOffHold();
+                            } else {
+                                Slog.w(TAG, "Failed to process unhold action; no matching call");
+                            }
                             break;
                         default:
                     }
@@ -188,6 +205,8 @@
                 && mNumberOfActiveSyncAssociations > 0) {
             mCurrentCalls.remove(call);
             call.unregisterCallback(mTelecomCallback);
+            mCdmsi.removeSelfOwnedCallId(call.getDetails().getExtras().getString(
+                    CrossDeviceSyncController.EXTRA_CALL_ID));
             sync(getUserId());
         }
     }
@@ -196,8 +215,9 @@
     public void onMuteStateChanged(boolean isMuted) {
         if (CompanionDeviceConfig.isEnabled(CompanionDeviceConfig.ENABLE_CONTEXT_SYNC_TELECOM)
                 && mNumberOfActiveSyncAssociations > 0) {
-            mCurrentCalls.values().forEach(call -> call.updateMuted(isMuted));
-            sync(getUserId());
+            mCdmsi.sendCrossDeviceSyncMessageToAllDevices(getUserId(),
+                    CrossDeviceSyncController.createCallControlMessage(null, isMuted
+                            ? android.companion.Telecom.MUTE : android.companion.Telecom.UNMUTE));
         }
     }
 
@@ -205,8 +225,9 @@
     public void onSilenceRinger() {
         if (CompanionDeviceConfig.isEnabled(CompanionDeviceConfig.ENABLE_CONTEXT_SYNC_TELECOM)
                 && mNumberOfActiveSyncAssociations > 0) {
-            mCurrentCalls.values().forEach(call -> call.updateSilencedIfRinging());
-            sync(getUserId());
+            mCdmsi.sendCrossDeviceSyncMessageToAllDevices(getUserId(),
+                    CrossDeviceSyncController.createCallControlMessage(null,
+                            android.companion.Telecom.SILENCE));
         }
     }
 
diff --git a/services/companion/java/com/android/server/companion/datatransfer/contextsync/CrossDeviceCall.java b/services/companion/java/com/android/server/companion/datatransfer/contextsync/CrossDeviceCall.java
index fec6923..e8392d2 100644
--- a/services/companion/java/com/android/server/companion/datatransfer/contextsync/CrossDeviceCall.java
+++ b/services/companion/java/com/android/server/companion/datatransfer/contextsync/CrossDeviceCall.java
@@ -25,8 +25,10 @@
 import android.net.Uri;
 import android.telecom.Call;
 import android.telecom.CallAudioState;
+import android.telecom.PhoneAccountHandle;
 import android.telecom.TelecomManager;
 import android.telecom.VideoProfile;
+import android.text.TextUtils;
 import android.util.Slog;
 
 import com.android.internal.annotations.VisibleForTesting;
@@ -39,9 +41,11 @@
 public class CrossDeviceCall {
 
     private static final String TAG = "CrossDeviceCall";
+    private static final String SEPARATOR = "::";
 
     private final String mId;
     private final Call mCall;
+    private final int mUserId;
     @VisibleForTesting boolean mIsEnterprise;
     private final String mCallingAppPackageName;
     private String mCallingAppName;
@@ -52,6 +56,7 @@
     private String mContactDisplayName;
     private Uri mHandle;
     private int mHandlePresentation;
+    private int mDirection;
     private boolean mIsMuted;
     private final Set<Integer> mControls = new HashSet<>();
     private final boolean mIsCallPlacedByContextSync;
@@ -73,22 +78,24 @@
                 ? callDetails.getIntentExtras().getString(CrossDeviceSyncController.EXTRA_CALL_ID)
                 : null;
         final String generatedId = UUID.randomUUID().toString();
-        mId = predefinedId != null ? (generatedId + predefinedId) : generatedId;
+        mId = predefinedId != null ? (generatedId + SEPARATOR + predefinedId) : generatedId;
         if (call != null) {
             call.putExtra(CrossDeviceSyncController.EXTRA_CALL_ID, mId);
         }
-        mIsCallPlacedByContextSync =
-                new ComponentName(context, CallMetadataSyncConnectionService.class)
-                        .equals(callDetails.getAccountHandle().getComponentName());
-        mCallingAppPackageName =
-                callDetails.getAccountHandle().getComponentName().getPackageName();
+        final PhoneAccountHandle handle = callDetails.getAccountHandle();
+        mUserId = handle != null ? handle.getUserHandle().getIdentifier() : -1;
+        mIsCallPlacedByContextSync = handle != null
+                && new ComponentName(context, CallMetadataSyncConnectionService.class)
+                .equals(handle.getComponentName());
+        mCallingAppPackageName = handle != null
+                ? callDetails.getAccountHandle().getComponentName().getPackageName() : "";
         mIsEnterprise = (callDetails.getCallProperties() & Call.Details.PROPERTY_ENTERPRISE_CALL)
                 == Call.Details.PROPERTY_ENTERPRISE_CALL;
         final PackageManager packageManager = context.getPackageManager();
         try {
             final ApplicationInfo applicationInfo = packageManager
-                    .getApplicationInfo(mCallingAppPackageName,
-                            PackageManager.ApplicationInfoFlags.of(0));
+                    .getApplicationInfoAsUser(mCallingAppPackageName,
+                            PackageManager.ApplicationInfoFlags.of(0), mUserId);
             mCallingAppName = packageManager.getApplicationLabel(applicationInfo).toString();
             mCallingAppIcon = BitmapUtils.renderDrawableToByteArray(
                     packageManager.getApplicationIcon(applicationInfo));
@@ -128,8 +135,19 @@
         mContactDisplayName = callDetails.getContactDisplayName();
         mHandle = callDetails.getHandle();
         mHandlePresentation = callDetails.getHandlePresentation();
+        final int direction = callDetails.getCallDirection();
+        if (direction == Call.Details.DIRECTION_INCOMING) {
+            mDirection = android.companion.Telecom.Call.INCOMING;
+        } else if (direction == Call.Details.DIRECTION_OUTGOING) {
+            mDirection = android.companion.Telecom.Call.OUTGOING;
+        } else {
+            mDirection = android.companion.Telecom.Call.UNKNOWN_DIRECTION;
+        }
         mStatus = convertStateToStatus(callDetails.getState());
         mControls.clear();
+        if (mStatus == android.companion.Telecom.Call.DIALING) {
+            mControls.add(android.companion.Telecom.END);
+        }
         if (mStatus == android.companion.Telecom.Call.RINGING
                 || mStatus == android.companion.Telecom.Call.RINGING_SILENCED) {
             mControls.add(android.companion.Telecom.ACCEPT);
@@ -170,6 +188,8 @@
                 return android.companion.Telecom.Call.RINGING_SIMULATED;
             case Call.STATE_DISCONNECTED:
                 return android.companion.Telecom.Call.DISCONNECTED;
+            case Call.STATE_DIALING:
+                return android.companion.Telecom.Call.DIALING;
             default:
                 Slog.e(TAG, "Couldn't resolve state to status: " + callState);
                 return android.companion.Telecom.Call.UNKNOWN_STATUS;
@@ -195,6 +215,8 @@
                 return Call.STATE_SIMULATED_RINGING;
             case android.companion.Telecom.Call.DISCONNECTED:
                 return Call.STATE_DISCONNECTED;
+            case android.companion.Telecom.Call.DIALING:
+                return Call.STATE_DIALING;
             case android.companion.Telecom.Call.UNKNOWN_STATUS:
             default:
                 return Call.STATE_NEW;
@@ -209,6 +231,10 @@
         return mCall;
     }
 
+    public int getUserId() {
+        return mUserId;
+    }
+
     public String getCallingAppName() {
         return mCallingAppName;
     }
@@ -231,11 +257,11 @@
             // Cannot use any contact information.
             return getNonContactString();
         }
-        return mContactDisplayName != null ? mContactDisplayName : getNonContactString();
+        return TextUtils.isEmpty(mContactDisplayName) ? getNonContactString() : mContactDisplayName;
     }
 
     private String getNonContactString() {
-        if (mCallerDisplayName != null
+        if (!TextUtils.isEmpty(mCallerDisplayName)
                 && mCallerDisplayNamePresentation == TelecomManager.PRESENTATION_ALLOWED) {
             return mCallerDisplayName;
         }
@@ -250,6 +276,10 @@
         return mStatus;
     }
 
+    public int getDirection() {
+        return mDirection;
+    }
+
     public Set<Integer> getControls() {
         return mControls;
     }
diff --git a/services/companion/java/com/android/server/companion/datatransfer/contextsync/CrossDeviceSyncController.java b/services/companion/java/com/android/server/companion/datatransfer/contextsync/CrossDeviceSyncController.java
index bf82f3f..3169459 100644
--- a/services/companion/java/com/android/server/companion/datatransfer/contextsync/CrossDeviceSyncController.java
+++ b/services/companion/java/com/android/server/companion/datatransfer/contextsync/CrossDeviceSyncController.java
@@ -154,6 +154,7 @@
                                 Slog.w(TAG, "No callback to report removed transport");
                             }
                         }
+                        clearInProgressCalls(associationInfo.getId());
                     } else {
                         // Stable association!
                         final boolean systemBlocked = isAssociationBlocked(associationInfo);
@@ -187,6 +188,7 @@
                                 // will get stale)
                                 syncMessageToDevice(associationInfo.getId(),
                                         createEmptyMessage());
+                                clearInProgressCalls(associationInfo.getId());
                             }
                         }
                     }
@@ -201,9 +203,14 @@
                             return;
                         }
                         final CallMetadataSyncData processedData = processTelecomDataFromSync(data);
-                        mPhoneAccountManager.updateFacilitators(associationId, processedData);
-                        mCallManager.updateCalls(associationId, processedData);
-                        processCallCreateRequests(processedData);
+                        final boolean isRequest = processedData.getCallControlRequests().size() != 0
+                                || processedData.getCallCreateRequests().size() != 0;
+                        if (!isRequest) {
+                            mPhoneAccountManager.updateFacilitators(associationId, processedData);
+                            mCallManager.updateCalls(associationId, processedData);
+                        } else {
+                            processCallCreateRequests(processedData);
+                        }
                         if (mInCallServiceCallbackRef == null
                                 && mConnectionServiceCallbackRef == null) {
                             Slog.w(TAG, "No callback to process context sync message");
@@ -213,8 +220,10 @@
                                 mInCallServiceCallbackRef != null ? mInCallServiceCallbackRef.get()
                                         : null;
                         if (inCallServiceCallback != null) {
-                            inCallServiceCallback.processContextSyncMessage(associationId,
-                                    processedData);
+                            if (isRequest) {
+                                inCallServiceCallback.processContextSyncMessage(associationId,
+                                        processedData);
+                            }
                         } else {
                             // This is dead; get rid of it lazily
                             mInCallServiceCallbackRef = null;
@@ -224,8 +233,10 @@
                                 mConnectionServiceCallbackRef != null
                                         ? mConnectionServiceCallbackRef.get() : null;
                         if (connectionServiceCallback != null) {
-                            connectionServiceCallback.processContextSyncMessage(associationId,
-                                    processedData);
+                            if (!isRequest) {
+                                connectionServiceCallback.processContextSyncMessage(associationId,
+                                        processedData);
+                            }
                         } else {
                             // This is dead; get rid of it lazily
                             mConnectionServiceCallbackRef = null;
@@ -236,6 +247,15 @@
         mCallManager = new CallManager(mContext, mPhoneAccountManager);
     }
 
+    private void clearInProgressCalls(int associationId) {
+        final Set<String> removedIds = mCallManager.clearCallIdsForAssociationId(associationId);
+        final CrossDeviceSyncControllerCallback connectionServiceCallback =
+                mConnectionServiceCallbackRef != null ? mConnectionServiceCallbackRef.get() : null;
+        if (connectionServiceCallback != null) {
+            connectionServiceCallback.cleanUpCallIds(removedIds);
+        }
+    }
+
     private static boolean isAssociationBlocked(AssociationInfo info) {
         return (info.getSystemDataSyncFlags() & CompanionDeviceManager.FLAG_CALL_METADATA)
                 != CompanionDeviceManager.FLAG_CALL_METADATA;
@@ -274,6 +294,7 @@
             if (FACILITATOR_ID_SYSTEM.equals(request.getFacilitator().getIdentifier())) {
                 if (request.getAddress() != null && request.getAddress().startsWith(
                         PhoneAccount.SCHEME_TEL)) {
+                    mCallManager.addSelfOwnedCallId(request.getId());
                     // Remove all the non-numbers (dashes, parens, scheme)
                     final Uri uri = Uri.fromParts(PhoneAccount.SCHEME_TEL,
                             request.getAddress().replaceAll("\\D+", ""), /* fragment= */ null);
@@ -382,6 +403,38 @@
                 new int[]{associationId});
     }
 
+    /** Sync message to all associated devices. */
+    public void syncMessageToAllDevicesForUserId(int userId, byte[] message) {
+        final Set<Integer> associationIds = new HashSet<>();
+        for (AssociationInfo associationInfo : mConnectedAssociations) {
+            if (associationInfo.getUserId() == userId && !isAssociationBlocked(associationInfo)) {
+                associationIds.add(associationInfo.getId());
+            }
+        }
+        if (associationIds.isEmpty()) {
+            Slog.w(TAG, "No eligible devices to sync to");
+            return;
+        }
+
+        mCompanionTransportManager.sendMessage(MESSAGE_REQUEST_CONTEXT_SYNC, message,
+                associationIds.stream().mapToInt(Integer::intValue).toArray());
+    }
+
+    /**
+     * Mark a call id as owned (i.e. this device owns the canonical call). Note that both sides will
+     * own outgoing calls that were placed on behalf of another device.
+     */
+    public void addSelfOwnedCallId(String callId) {
+        mCallManager.addSelfOwnedCallId(callId);
+    }
+
+    /** Unmark a call id as owned (i.e. this device no longer owns the canonical call). */
+    public void removeSelfOwnedCallId(String callId) {
+        if (callId != null) {
+            mCallManager.removeSelfOwnedCallId(callId);
+        }
+    }
+
     @VisibleForTesting
     CallMetadataSyncData processTelecomDataFromSync(byte[] data) {
         final CallMetadataSyncData callMetadataSyncData = new CallMetadataSyncData();
@@ -430,8 +483,10 @@
                                     pis.end(requestsToken);
                                 } else if (pis.getFieldNumber() == (int) Telecom.FACILITATORS) {
                                     final long facilitatorsToken = pis.start(Telecom.FACILITATORS);
-                                    callMetadataSyncData.addFacilitator(
-                                            processFacilitatorDataFromSync(pis));
+                                    final CallMetadataSyncData.CallFacilitator facilitator =
+                                            processFacilitatorDataFromSync(pis);
+                                    facilitator.setIsTel(true);
+                                    callMetadataSyncData.addFacilitator(facilitator);
                                     pis.end(facilitatorsToken);
                                 } else {
                                     Slog.e(TAG, "Unhandled field in Telecom:"
@@ -561,6 +616,9 @@
                 case (int) Telecom.Call.STATUS:
                     call.setStatus(pis.readInt(Telecom.Call.STATUS));
                     break;
+                case (int) Telecom.Call.DIRECTION:
+                    call.setDirection(pis.readInt(Telecom.Call.DIRECTION));
+                    break;
                 case (int) Telecom.Call.CONTROLS:
                     call.addControl(pis.readInt(Telecom.Call.CONTROLS));
                     break;
@@ -578,15 +636,15 @@
         pos.write(ContextSyncMessage.VERSION, CURRENT_VERSION);
         final long telecomToken = pos.start(ContextSyncMessage.TELECOM);
         for (CrossDeviceCall call : calls) {
-            if (call.isCallPlacedByContextSync()) {
-                // Do not sync any calls which our "ours" as that would be duplicative.
+            if (call.isCallPlacedByContextSync() || mCallManager.isExternallyOwned(call.getId())) {
+                // Do not sync any of "our" calls, nor external calls, as that would be duplicative.
                 continue;
             }
             final long callsToken = pos.start(Telecom.CALLS);
             pos.write(Telecom.Call.ID, call.getId());
             final long originToken = pos.start(Telecom.Call.ORIGIN);
             pos.write(Telecom.Call.Origin.CALLER_ID,
-                    call.getReadableCallerId(isAdminBlocked(userId)));
+                    call.getReadableCallerId(isAdminBlocked(call.getUserId())));
             pos.write(Telecom.Call.Origin.APP_ICON, call.getCallingAppIcon());
             final long facilitatorToken = pos.start(Telecom.Call.Origin.FACILITATOR);
             pos.write(Telecom.CallFacilitator.NAME, call.getCallingAppName());
@@ -594,6 +652,7 @@
             pos.end(facilitatorToken);
             pos.end(originToken);
             pos.write(Telecom.Call.STATUS, call.getStatus());
+            pos.write(Telecom.Call.DIRECTION, call.getDirection());
             for (int control : call.getControls()) {
                 pos.write(Telecom.Call.CONTROLS, control);
             }
@@ -658,6 +717,9 @@
     @VisibleForTesting
     static class CallManager {
 
+        @VisibleForTesting final Set<String> mSelfOwnedCalls = new HashSet<>();
+        @VisibleForTesting final Set<String> mExternallyOwnedCalls = new HashSet<>();
+
         @VisibleForTesting final Map<Integer, Set<String>> mCallIds = new HashMap<>();
         private final TelecomManager mTelecomManager;
         private final PhoneAccountManager mPhoneAccountManager;
@@ -678,20 +740,63 @@
 
             for (CallMetadataSyncData.Call currentCall : data.getCalls()) {
                 if (!oldCallIds.contains(currentCall.getId())
-                        && currentCall.getFacilitator() != null) {
+                        && currentCall.getFacilitator() != null
+                        && !isSelfOwned(currentCall.getId())) {
+                    mExternallyOwnedCalls.add(currentCall.getId());
                     final Bundle extras = new Bundle();
                     extras.putInt(EXTRA_ASSOCIATION_ID, associationId);
                     extras.putBoolean(EXTRA_IS_REMOTE_ORIGIN, true);
-                    extras.putParcelable(EXTRA_CALL, currentCall);
+                    extras.putBundle(EXTRA_CALL, currentCall.writeToBundle());
                     extras.putString(EXTRA_CALL_ID, currentCall.getId());
                     extras.putByteArray(EXTRA_FACILITATOR_ICON, currentCall.getAppIcon());
-                    final PhoneAccountHandle handle = mPhoneAccountManager.getPhoneAccountHandle(
-                            associationId, currentCall.getFacilitator().getIdentifier());
-                    mTelecomManager.addNewIncomingCall(handle, extras);
+                    final PhoneAccountHandle handle =
+                            mPhoneAccountManager.getPhoneAccountHandle(
+                                    associationId,
+                                    currentCall.getFacilitator().getIdentifier());
+                    if (currentCall.getDirection() == android.companion.Telecom.Call.INCOMING) {
+                        mTelecomManager.addNewIncomingCall(handle, extras);
+                    } else if (currentCall.getDirection()
+                            == android.companion.Telecom.Call.OUTGOING) {
+                        final Bundle wrappedExtras = new Bundle();
+                        wrappedExtras.putParcelable(TelecomManager.EXTRA_OUTGOING_CALL_EXTRAS,
+                                extras);
+                        wrappedExtras.putParcelable(TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE,
+                                handle);
+                        final String address = currentCall.getCallerId();
+                        if (address != null) {
+                            mTelecomManager.placeCall(Uri.fromParts(PhoneAccount.SCHEME_SIP,
+                                    address, /* fragment= */ null), wrappedExtras);
+                        }
+                    }
                 }
             }
             mCallIds.put(associationId, newCallIds);
         }
+
+        Set<String> clearCallIdsForAssociationId(int associationId) {
+            return mCallIds.remove(associationId);
+        }
+
+        void addSelfOwnedCallId(String callId) {
+            mSelfOwnedCalls.add(callId);
+        }
+
+        void removeSelfOwnedCallId(String callId) {
+            mSelfOwnedCalls.remove(callId);
+        }
+
+        boolean isExternallyOwned(String callId) {
+            return mExternallyOwnedCalls.contains(callId);
+        }
+
+        private boolean isSelfOwned(String currentCallId) {
+            for (String selfOwnedCallId : mSelfOwnedCalls) {
+                if (currentCallId.endsWith(selfOwnedCallId)) {
+                    return true;
+                }
+            }
+            return false;
+        }
     }
 
     static class PhoneAccountManager {
@@ -745,7 +850,8 @@
                         new PhoneAccountHandleIdentifier(associationId,
                                 facilitator.getIdentifier());
                 if (!mPhoneAccountHandles.containsKey(phoneAccountHandleIdentifier)) {
-                    registerPhoneAccount(phoneAccountHandleIdentifier, facilitator.getName());
+                    registerPhoneAccount(phoneAccountHandleIdentifier, facilitator.getName(),
+                            facilitator.isTel());
                 }
             }
         }
@@ -755,7 +861,7 @@
          * synced device, and records it in the local {@link #mPhoneAccountHandles} map.
          */
         private void registerPhoneAccount(PhoneAccountHandleIdentifier handleIdentifier,
-                String humanReadableAppName) {
+                String humanReadableAppName, boolean isTel) {
             if (mPhoneAccountHandles.containsKey(handleIdentifier)) {
                 // Already exists!
                 return;
@@ -765,7 +871,8 @@
                     UUID.randomUUID().toString());
             mPhoneAccountHandles.put(handleIdentifier, handle);
             final PhoneAccount phoneAccount = createPhoneAccount(handle, humanReadableAppName,
-                    handleIdentifier.getAppIdentifier());
+                    handleIdentifier.getAppIdentifier(), handleIdentifier.getAssociationId(),
+                    isTel);
             mTelecomManager.registerPhoneAccount(phoneAccount);
             mTelecomManager.enablePhoneAccount(mPhoneAccountHandles.get(handleIdentifier), true);
         }
@@ -781,11 +888,16 @@
         @VisibleForTesting
         static PhoneAccount createPhoneAccount(PhoneAccountHandle handle,
                 String humanReadableAppName,
-                String appIdentifier) {
+                String appIdentifier,
+                int associationId,
+                boolean isTel) {
             final Bundle extras = new Bundle();
             extras.putString(EXTRA_CALL_FACILITATOR_ID, appIdentifier);
+            extras.putInt(EXTRA_ASSOCIATION_ID, associationId);
             return new PhoneAccount.Builder(handle, humanReadableAppName)
                     .setExtras(extras)
+                    .setSupportedUriSchemes(List.of(isTel ? PhoneAccount.SCHEME_TEL
+                            : PhoneAccount.SCHEME_SIP))
                     .setCapabilities(PhoneAccount.CAPABILITY_CALL_PROVIDER
                             | PhoneAccount.CAPABILITY_CONNECTION_MANAGER).build();
         }
diff --git a/services/companion/java/com/android/server/companion/datatransfer/contextsync/CrossDeviceSyncControllerCallback.java b/services/companion/java/com/android/server/companion/datatransfer/contextsync/CrossDeviceSyncControllerCallback.java
index 8a0ba27..6764830 100644
--- a/services/companion/java/com/android/server/companion/datatransfer/contextsync/CrossDeviceSyncControllerCallback.java
+++ b/services/companion/java/com/android/server/companion/datatransfer/contextsync/CrossDeviceSyncControllerCallback.java
@@ -21,6 +21,7 @@
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
+import java.util.Set;
 
 /** Callback for call metadata syncing. */
 public abstract class CrossDeviceSyncControllerCallback {
@@ -40,4 +41,7 @@
     void requestCrossDeviceSync(AssociationInfo associationInfo) {}
 
     void updateNumberOfActiveSyncAssociations(int userId, boolean added) {}
+
+    /** Clean up any remaining state for the given calls. */
+    void cleanUpCallIds(Set<String> callIds) {}
 }
diff --git a/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java b/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java
index 51359ad..9ff0746 100644
--- a/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java
+++ b/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java
@@ -88,11 +88,14 @@
 import android.view.contentcapture.IDataShareWriteAdapter;
 
 import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.infra.AbstractRemoteService;
 import com.android.internal.infra.GlobalWhitelistState;
 import com.android.internal.os.IResultReceiver;
 import com.android.internal.util.DumpUtils;
 import com.android.server.LocalServices;
+import com.android.server.contentprotection.ContentProtectionBlocklistManager;
+import com.android.server.contentprotection.ContentProtectionPackageManager;
 import com.android.server.infra.AbstractMasterSystemService;
 import com.android.server.infra.FrameworkResourcesServiceNameResolver;
 
@@ -117,7 +120,7 @@
  * with other sources to provide contextual data in other areas of the system
  * such as Autofill.
  */
-public final class ContentCaptureManagerService extends
+public class ContentCaptureManagerService extends
         AbstractMasterSystemService<ContentCaptureManagerService, ContentCapturePerUserService> {
 
     private static final String TAG = ContentCaptureManagerService.class.getSimpleName();
@@ -205,6 +208,8 @@
     final GlobalContentCaptureOptions mGlobalContentCaptureOptions =
             new GlobalContentCaptureOptions();
 
+    @Nullable private final ContentProtectionBlocklistManager mContentProtectionBlocklistManager;
+
     public ContentCaptureManagerService(@NonNull Context context) {
         super(context, new FrameworkResourcesServiceNameResolver(context,
                 com.android.internal.R.string.config_defaultContentCaptureService),
@@ -242,6 +247,14 @@
                     mServiceNameResolver.getServiceName(userId),
                     mServiceNameResolver.isTemporary(userId));
         }
+
+        if (getEnableContentProtectionReceiverLocked()) {
+            mContentProtectionBlocklistManager = createContentProtectionBlocklistManager(context);
+            mContentProtectionBlocklistManager.updateBlocklist(
+                    mDevCfgContentProtectionAppsBlocklistSize);
+        } else {
+            mContentProtectionBlocklistManager = null;
+        }
     }
 
     @Override // from AbstractMasterSystemService
@@ -397,7 +410,9 @@
         }
     }
 
-    private void setFineTuneParamsFromDeviceConfig() {
+    /** @hide */
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+    protected void setFineTuneParamsFromDeviceConfig() {
         synchronized (mLock) {
             mDevCfgMaxBufferSize =
                     DeviceConfig.getInt(
@@ -443,6 +458,8 @@
                             ContentCaptureManager
                                     .DEVICE_CONFIG_PROPERTY_CONTENT_PROTECTION_APPS_BLOCKLIST_SIZE,
                             ContentCaptureManager.DEFAULT_CONTENT_PROTECTION_APPS_BLOCKLIST_SIZE);
+            // mContentProtectionBlocklistManager.updateBlocklist not called on purpose here to keep
+            // it immutable at this point
             mDevCfgContentProtectionBufferSize =
                     DeviceConfig.getInt(
                             DeviceConfig.NAMESPACE_CONTENT_CAPTURE,
@@ -754,6 +771,25 @@
         mGlobalContentCaptureOptions.dump(prefix2, pw);
     }
 
+    /**
+     * Used by the constructor in order to be able to override the value in the tests.
+     *
+     * @hide
+     */
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+    @GuardedBy("mLock")
+    protected boolean getEnableContentProtectionReceiverLocked() {
+        return mDevCfgEnableContentProtectionReceiver;
+    }
+
+    /** @hide */
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+    @NonNull
+    protected ContentProtectionBlocklistManager createContentProtectionBlocklistManager(
+            @NonNull Context context) {
+        return new ContentProtectionBlocklistManager(new ContentProtectionPackageManager(context));
+    }
+
     final class ContentCaptureManagerServiceStub extends IContentCaptureManager.Stub {
 
         @Override
@@ -1075,14 +1111,21 @@
         @GuardedBy("mGlobalWhitelistStateLock")
         public ContentCaptureOptions getOptions(@UserIdInt int userId,
                 @NonNull String packageName) {
-            boolean packageWhitelisted;
+            boolean isContentCaptureReceiverEnabled;
+            boolean isContentProtectionReceiverEnabled;
             ArraySet<ComponentName> whitelistedComponents = null;
+
             synchronized (mGlobalWhitelistStateLock) {
-                packageWhitelisted = isWhitelisted(userId, packageName);
-                if (!packageWhitelisted) {
-                    // Full package is not allowlisted: check individual components first
+                isContentCaptureReceiverEnabled =
+                        isContentCaptureReceiverEnabled(userId, packageName);
+                isContentProtectionReceiverEnabled =
+                        isContentProtectionReceiverEnabled(packageName);
+
+                if (!isContentCaptureReceiverEnabled) {
+                    // Full package is not allowlisted: check individual components next
                     whitelistedComponents = getWhitelistedComponents(userId, packageName);
-                    if (whitelistedComponents == null
+                    if (!isContentProtectionReceiverEnabled
+                            && whitelistedComponents == null
                             && packageName.equals(mServicePackages.get(userId))) {
                         // No components allowlisted either, but let it go because it's the
                         // service's own package
@@ -1101,7 +1144,9 @@
                 }
             }
 
-            if (!packageWhitelisted && whitelistedComponents == null) {
+            if (!isContentCaptureReceiverEnabled
+                    && !isContentProtectionReceiverEnabled
+                    && whitelistedComponents == null) {
                 // No can do!
                 if (verbose) {
                     Slog.v(TAG, "getOptionsForPackage(" + packageName + "): not whitelisted");
@@ -1118,9 +1163,9 @@
                                 mDevCfgTextChangeFlushingFrequencyMs,
                                 mDevCfgLogHistorySize,
                                 mDevCfgDisableFlushForViewTreeAppearing,
-                                /* enableReceiver= */ true,
+                                isContentCaptureReceiverEnabled || whitelistedComponents != null,
                                 new ContentCaptureOptions.ContentProtectionOptions(
-                                        mDevCfgEnableContentProtectionReceiver,
+                                        isContentProtectionReceiverEnabled,
                                         mDevCfgContentProtectionBufferSize),
                                 whitelistedComponents);
                 if (verbose) Slog.v(TAG, "getOptionsForPackage(" + packageName + "): " + options);
@@ -1141,6 +1186,35 @@
                 }
             }
         }
+
+        @Override // from GlobalWhitelistState
+        public boolean isWhitelisted(@UserIdInt int userId, @NonNull String packageName) {
+            return isContentCaptureReceiverEnabled(userId, packageName)
+                    || isContentProtectionReceiverEnabled(packageName);
+        }
+
+        @Override // from GlobalWhitelistState
+        public boolean isWhitelisted(@UserIdInt int userId, @NonNull ComponentName componentName) {
+            return super.isWhitelisted(userId, componentName)
+                    || isContentProtectionReceiverEnabled(componentName.getPackageName());
+        }
+
+        private boolean isContentCaptureReceiverEnabled(
+                @UserIdInt int userId, @NonNull String packageName) {
+            return super.isWhitelisted(userId, packageName);
+        }
+
+        private boolean isContentProtectionReceiverEnabled(@NonNull String packageName) {
+            if (mContentProtectionBlocklistManager == null) {
+                return false;
+            }
+            synchronized (mLock) {
+                if (!mDevCfgEnableContentProtectionReceiver) {
+                    return false;
+                }
+            }
+            return mContentProtectionBlocklistManager.isAllowed(packageName);
+        }
     }
 
     private static class DataShareCallbackDelegate extends IDataShareCallback.Stub {
diff --git a/services/contentcapture/java/com/android/server/contentprotection/ContentProtectionBlocklistManager.java b/services/contentcapture/java/com/android/server/contentprotection/ContentProtectionBlocklistManager.java
index 715cf9a..a0fd28b 100644
--- a/services/contentcapture/java/com/android/server/contentprotection/ContentProtectionBlocklistManager.java
+++ b/services/contentcapture/java/com/android/server/contentprotection/ContentProtectionBlocklistManager.java
@@ -35,7 +35,7 @@
  *
  * @hide
  */
-class ContentProtectionBlocklistManager {
+public class ContentProtectionBlocklistManager {
 
     private static final String TAG = "ContentProtectionBlocklistManager";
 
@@ -46,7 +46,7 @@
 
     @Nullable private Set<String> mPackageNameBlocklist;
 
-    protected ContentProtectionBlocklistManager(
+    public ContentProtectionBlocklistManager(
             @NonNull ContentProtectionPackageManager contentProtectionPackageManager) {
         mContentProtectionPackageManager = contentProtectionPackageManager;
     }
diff --git a/services/contentcapture/java/com/android/server/contentprotection/ContentProtectionPackageManager.java b/services/contentcapture/java/com/android/server/contentprotection/ContentProtectionPackageManager.java
index 1847e5d..4ebac07 100644
--- a/services/contentcapture/java/com/android/server/contentprotection/ContentProtectionPackageManager.java
+++ b/services/contentcapture/java/com/android/server/contentprotection/ContentProtectionPackageManager.java
@@ -43,7 +43,7 @@
 
     @NonNull private final PackageManager mPackageManager;
 
-    ContentProtectionPackageManager(@NonNull Context context) {
+    public ContentProtectionPackageManager(@NonNull Context context) {
         mPackageManager = context.getPackageManager();
     }
 
diff --git a/services/contentcapture/java/com/android/server/contentprotection/RemoteContentProtectionService.java b/services/contentcapture/java/com/android/server/contentprotection/RemoteContentProtectionService.java
new file mode 100644
index 0000000..7b34e73
--- /dev/null
+++ b/services/contentcapture/java/com/android/server/contentprotection/RemoteContentProtectionService.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.contentprotection;
+
+import static android.view.contentcapture.ContentCaptureSession.FLUSH_REASON_LOGIN_DETECTED;
+
+import android.annotation.NonNull;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ParceledListSlice;
+import android.service.contentcapture.ContentCaptureService;
+import android.util.Slog;
+import android.view.contentcapture.ContentCaptureEvent;
+import android.view.contentcapture.IContentCaptureDirectManager;
+
+import com.android.internal.infra.ServiceConnector;
+
+import java.time.Duration;
+
+/**
+ * Connector for the remote content protection service.
+ *
+ * @hide
+ */
+class RemoteContentProtectionService extends ServiceConnector.Impl<IContentCaptureDirectManager> {
+
+    private static final String TAG = RemoteContentProtectionService.class.getSimpleName();
+
+    private static final Duration AUTO_DISCONNECT_TIMEOUT = Duration.ofSeconds(3);
+
+    @NonNull private final ComponentName mComponentName;
+
+    protected RemoteContentProtectionService(
+            @NonNull Context context,
+            @NonNull ComponentName componentName,
+            int userId,
+            boolean bindAllowInstant) {
+        super(
+                context,
+                new Intent(ContentCaptureService.PROTECTION_SERVICE_INTERFACE)
+                        .setComponent(componentName),
+                bindAllowInstant ? Context.BIND_ALLOW_INSTANT : 0,
+                userId,
+                IContentCaptureDirectManager.Stub::asInterface);
+        mComponentName = componentName;
+    }
+
+    @Override // from ServiceConnector.Impl
+    protected long getAutoDisconnectTimeoutMs() {
+        return AUTO_DISCONNECT_TIMEOUT.toMillis();
+    }
+
+    @Override // from ServiceConnector.Impl
+    protected void onServiceConnectionStatusChanged(
+            @NonNull IContentCaptureDirectManager service, boolean isConnected) {
+        Slog.i(
+                TAG,
+                "Connection status for: "
+                        + mComponentName
+                        + " changed to: "
+                        + (isConnected ? "connected" : "disconnected"));
+    }
+
+    public void onLoginDetected(@NonNull ParceledListSlice<ContentCaptureEvent> events) {
+        run(
+                service ->
+                        service.sendEvents(
+                                events, FLUSH_REASON_LOGIN_DETECTED, /* options= */ null));
+    }
+}
diff --git a/services/core/java/com/android/server/VpnManagerService.java b/services/core/java/com/android/server/VpnManagerService.java
index 2b43ef4..9b4f968 100644
--- a/services/core/java/com/android/server/VpnManagerService.java
+++ b/services/core/java/com/android/server/VpnManagerService.java
@@ -406,7 +406,7 @@
      * Retrieve the VpnProfileState for the profile provisioned by the given package.
      *
      * @return the VpnProfileState with current information, or null if there was no profile
-     *         provisioned by the given package.
+     *         provisioned and started by the given package.
      * @hide
      */
     @Override
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 1bd0675..5c1dad9 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -1489,12 +1489,10 @@
     static final class ProcessChangeItem {
         static final int CHANGE_ACTIVITIES = 1<<0;
         static final int CHANGE_FOREGROUND_SERVICES = 1<<1;
-        static final int CHANGE_CAPABILITY = 1<<2;
         int changes;
         int uid;
         int pid;
         int processState;
-        int capability;
         boolean foregroundActivities;
         int foregroundServiceTypes;
     }
diff --git a/services/core/java/com/android/server/am/BroadcastQueueImpl.java b/services/core/java/com/android/server/am/BroadcastQueueImpl.java
index 4eedfe2..f6004d7 100644
--- a/services/core/java/com/android/server/am/BroadcastQueueImpl.java
+++ b/services/core/java/com/android/server/am/BroadcastQueueImpl.java
@@ -264,7 +264,7 @@
                 if (oldRecord.resultTo != null) {
                     try {
                         oldRecord.mIsReceiverAppRunning = true;
-                        performReceiveLocked(oldRecord.resultToApp, oldRecord.resultTo,
+                        performReceiveLocked(oldRecord, oldRecord.resultToApp, oldRecord.resultTo,
                                 oldRecord.intent,
                                 Activity.RESULT_CANCELED, null, null,
                                 false, false, oldRecord.shareIdentity, oldRecord.userId,
@@ -615,7 +615,9 @@
                     finishTime - r.receiverTime,
                     packageState,
                     r.curApp.info.packageName,
-                    r.callerPackage);
+                    r.callerPackage,
+                    r.calculateTypeForLogging(),
+                    r.getDeliveryGroupPolicy());
         }
         if (state == BroadcastRecord.IDLE) {
             Slog.w(TAG_BROADCAST, "finishReceiver [" + mQueueName + "] called but state is IDLE");
@@ -742,7 +744,7 @@
         }
     }
 
-    public void performReceiveLocked(ProcessRecord app, IIntentReceiver receiver,
+    public void performReceiveLocked(BroadcastRecord r, ProcessRecord app, IIntentReceiver receiver,
             Intent intent, int resultCode, String data, Bundle extras,
             boolean ordered, boolean sticky, boolean shareIdentity, int sendingUser,
             int receiverUid, int callingUid, String callingPackage,
@@ -795,7 +797,8 @@
                     BROADCAST_DELIVERY_EVENT_REPORTED__PROC_START_TYPE__PROCESS_START_TYPE_WARM,
                     dispatchDelay, receiveDelay, 0 /* finish_delay */,
                     SERVICE_REQUEST_EVENT_REPORTED__PACKAGE_STOPPED_STATE__PACKAGE_STATE_NORMAL,
-                    app != null ? app.info.packageName : null, callingPackage);
+                    app != null ? app.info.packageName : null, callingPackage,
+                    r.calculateTypeForLogging(), r.getDeliveryGroupPolicy());
         }
     }
 
@@ -871,7 +874,7 @@
                 maybeAddBackgroundStartPrivileges(filter.receiverList.app, r);
                 maybeScheduleTempAllowlistLocked(filter.owningUid, r, r.options);
                 maybeReportBroadcastDispatchedEventLocked(r, filter.owningUid);
-                performReceiveLocked(filter.receiverList.app, filter.receiverList.receiver,
+                performReceiveLocked(r, filter.receiverList.app, filter.receiverList.receiver,
                         prepareReceiverIntent(r.intent, filteredExtras), r.resultCode, r.resultData,
                         r.resultExtras, r.ordered, r.initialSticky, r.shareIdentity, r.userId,
                         filter.receiverList.uid, r.callingUid, r.callerPackage,
@@ -1162,7 +1165,7 @@
                                 r.dispatchTime = now;
                             }
                             r.mIsReceiverAppRunning = true;
-                            performReceiveLocked(r.resultToApp, r.resultTo,
+                            performReceiveLocked(r, r.resultToApp, r.resultTo,
                                     new Intent(r.intent), r.resultCode,
                                     r.resultData, r.resultExtras, false, false, r.shareIdentity,
                                     r.userId, r.callingUid, r.callingUid, r.callerPackage,
diff --git a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
index bb5fcbe..cbc7540 100644
--- a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
+++ b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
@@ -731,8 +731,7 @@
         if (mService.shouldIgnoreDeliveryGroupPolicy(r.intent.getAction())) {
             return;
         }
-        final int policy = (r.options != null)
-                ? r.options.getDeliveryGroupPolicy() : BroadcastOptions.DELIVERY_GROUP_POLICY_ALL;
+        final int policy = r.getDeliveryGroupPolicy();
         final BroadcastConsumer broadcastConsumer;
         switch (policy) {
             case BroadcastOptions.DELIVERY_GROUP_POLICY_ALL:
@@ -1932,7 +1931,8 @@
                     : SERVICE_REQUEST_EVENT_REPORTED__PACKAGE_STOPPED_STATE__PACKAGE_STATE_NORMAL;
             FrameworkStatsLog.write(BROADCAST_DELIVERY_EVENT_REPORTED, uid, senderUid, actionName,
                     receiverType, type, dispatchDelay, receiveDelay, finishDelay, packageState,
-                    app != null ? app.info.packageName : null, r.callerPackage);
+                    app != null ? app.info.packageName : null, r.callerPackage,
+                    r.calculateTypeForLogging(), r.getDeliveryGroupPolicy());
         }
     }
 
diff --git a/services/core/java/com/android/server/am/BroadcastRecord.java b/services/core/java/com/android/server/am/BroadcastRecord.java
index 863dd63..cfdb133 100644
--- a/services/core/java/com/android/server/am/BroadcastRecord.java
+++ b/services/core/java/com/android/server/am/BroadcastRecord.java
@@ -17,6 +17,19 @@
 package com.android.server.am;
 
 import static android.app.ActivityManager.RESTRICTION_LEVEL_BACKGROUND_RESTRICTED;
+import static android.app.AppProtoEnums.BROADCAST_TYPE_ALARM;
+import static android.app.AppProtoEnums.BROADCAST_TYPE_BACKGROUND;
+import static android.app.AppProtoEnums.BROADCAST_TYPE_DEFERRABLE_UNTIL_ACTIVE;
+import static android.app.AppProtoEnums.BROADCAST_TYPE_FOREGROUND;
+import static android.app.AppProtoEnums.BROADCAST_TYPE_INITIAL_STICKY;
+import static android.app.AppProtoEnums.BROADCAST_TYPE_INTERACTIVE;
+import static android.app.AppProtoEnums.BROADCAST_TYPE_NONE;
+import static android.app.AppProtoEnums.BROADCAST_TYPE_ORDERED;
+import static android.app.AppProtoEnums.BROADCAST_TYPE_PRIORITIZED;
+import static android.app.AppProtoEnums.BROADCAST_TYPE_PUSH_MESSAGE;
+import static android.app.AppProtoEnums.BROADCAST_TYPE_PUSH_MESSAGE_OVER_QUOTA;
+import static android.app.AppProtoEnums.BROADCAST_TYPE_RESULT_TO;
+import static android.app.AppProtoEnums.BROADCAST_TYPE_STICKY;
 
 import static com.android.server.am.BroadcastConstants.DEFER_BOOT_COMPLETED_BROADCAST_ALL;
 import static com.android.server.am.BroadcastConstants.DEFER_BOOT_COMPLETED_BROADCAST_BACKGROUND_RESTRICTED_ONLY;
@@ -35,6 +48,7 @@
 import android.app.AppOpsManager;
 import android.app.BackgroundStartPrivileges;
 import android.app.BroadcastOptions;
+import android.app.BroadcastOptions.DeliveryGroupPolicy;
 import android.app.compat.CompatChanges;
 import android.content.ComponentName;
 import android.content.IIntentReceiver;
@@ -1018,6 +1032,46 @@
         }
     }
 
+    int calculateTypeForLogging() {
+        int type = BROADCAST_TYPE_NONE;
+        if (isForeground()) {
+            type |= BROADCAST_TYPE_FOREGROUND;
+        } else {
+            type |= BROADCAST_TYPE_BACKGROUND;
+        }
+        if (alarm) {
+            type |= BROADCAST_TYPE_ALARM;
+        }
+        if (interactive) {
+            type |= BROADCAST_TYPE_INTERACTIVE;
+        }
+        if (ordered) {
+            type |= BROADCAST_TYPE_ORDERED;
+        }
+        if (prioritized) {
+            type |= BROADCAST_TYPE_PRIORITIZED;
+        }
+        if (resultTo != null) {
+            type |= BROADCAST_TYPE_RESULT_TO;
+        }
+        if (deferUntilActive) {
+            type |= BROADCAST_TYPE_DEFERRABLE_UNTIL_ACTIVE;
+        }
+        if (pushMessage) {
+            type |= BROADCAST_TYPE_PUSH_MESSAGE;
+        }
+        if (pushMessageOverQuota) {
+            type |= BROADCAST_TYPE_PUSH_MESSAGE_OVER_QUOTA;
+        }
+        if (sticky) {
+            type |= BROADCAST_TYPE_STICKY;
+        }
+        if (initialSticky) {
+            type |= BROADCAST_TYPE_INITIAL_STICKY;
+        }
+        return type;
+    }
+
     public BroadcastRecord maybeStripForHistory() {
         if (!intent.canStripForHistory()) {
             return this;
@@ -1113,6 +1167,12 @@
         return true;
     }
 
+    @DeliveryGroupPolicy
+    int getDeliveryGroupPolicy() {
+        return (options != null) ? options.getDeliveryGroupPolicy()
+                : BroadcastOptions.DELIVERY_GROUP_POLICY_ALL;
+    }
+
     boolean matchesDeliveryGroup(@NonNull BroadcastRecord other) {
         return matchesDeliveryGroup(this, other);
     }
diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java
index 6c3f01e..a86c2e3 100644
--- a/services/core/java/com/android/server/am/OomAdjuster.java
+++ b/services/core/java/com/android/server/am/OomAdjuster.java
@@ -3188,7 +3188,6 @@
         }
 
         if (state.getCurCapability() != state.getSetCapability()) {
-            changes |= ActivityManagerService.ProcessChangeItem.CHANGE_CAPABILITY;
             state.setSetCapability(state.getCurCapability());
         }
 
@@ -3212,13 +3211,12 @@
                     mProcessList.enqueueProcessChangeItemLocked(app.getPid(), app.info.uid);
             item.changes |= changes;
             item.foregroundActivities = state.hasRepForegroundActivities();
-            item.capability = state.getSetCapability();
             if (DEBUG_PROCESS_OBSERVERS) Slog.i(TAG_PROCESS_OBSERVERS,
                     "Item " + Integer.toHexString(System.identityHashCode(item))
                             + " " + app.toShortString() + ": changes=" + item.changes
                             + " foreground=" + item.foregroundActivities
                             + " type=" + state.getAdjType() + " source=" + state.getAdjSource()
-                            + " target=" + state.getAdjTarget() + " capability=" + item.capability);
+                            + " target=" + state.getAdjTarget());
         }
 
         if (state.isCached() && !state.shouldNotKillOnBgRestrictedAndIdle()) {
diff --git a/services/core/java/com/android/server/audio/AudioDeviceBroker.java b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
index d7a5ee9..b86da88 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceBroker.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
@@ -2205,19 +2205,19 @@
         if (preferredCommunicationDevice == null) {
             AudioDeviceAttributes defaultDevice = getDefaultCommunicationDevice();
             if (defaultDevice != null) {
-                mDeviceInventory.setPreferredDevicesForStrategy(
+                mDeviceInventory.setPreferredDevicesForStrategyInt(
                         mCommunicationStrategyId, Arrays.asList(defaultDevice));
-                mDeviceInventory.setPreferredDevicesForStrategy(
+                mDeviceInventory.setPreferredDevicesForStrategyInt(
                         mAccessibilityStrategyId, Arrays.asList(defaultDevice));
             } else {
-                mDeviceInventory.removePreferredDevicesForStrategy(mCommunicationStrategyId);
-                mDeviceInventory.removePreferredDevicesForStrategy(mAccessibilityStrategyId);
+                mDeviceInventory.removePreferredDevicesForStrategInt(mCommunicationStrategyId);
+                mDeviceInventory.removePreferredDevicesForStrategInt(mAccessibilityStrategyId);
             }
             mDeviceInventory.applyConnectedDevicesRoles();
         } else {
-            mDeviceInventory.setPreferredDevicesForStrategy(
+            mDeviceInventory.setPreferredDevicesForStrategyInt(
                     mCommunicationStrategyId, Arrays.asList(preferredCommunicationDevice));
-            mDeviceInventory.setPreferredDevicesForStrategy(
+            mDeviceInventory.setPreferredDevicesForStrategyInt(
                     mAccessibilityStrategyId, Arrays.asList(preferredCommunicationDevice));
         }
         onUpdatePhoneStrategyDevice(preferredCommunicationDevice);
diff --git a/services/core/java/com/android/server/audio/AudioDeviceInventory.java b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
index a561612..7b2e732 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceInventory.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
@@ -67,6 +67,7 @@
 import java.util.Objects;
 import java.util.Set;
 import java.util.UUID;
+import java.util.stream.Stream;
 
 /**
  * Class to manage the inventory of all connected devices.
@@ -324,14 +325,22 @@
         mPreferredDevicesForCapturePreset.forEach((capturePreset, devices) -> {
             pw.println("  " + prefix + "capturePreset:" + capturePreset
                     + " devices:" + devices); });
-        pw.println("\n" + prefix + "Applied devices roles for strategies:");
+        pw.println("\n" + prefix + "Applied devices roles for strategies (from API):");
         mAppliedStrategyRoles.forEach((key, devices) -> {
             pw.println("  " + prefix + "strategy: " + key.first
                     +  " role:" + key.second + " devices:" + devices); });
-        pw.println("\n" + prefix + "Applied devices roles for presets:");
+        pw.println("\n" + prefix + "Applied devices roles for strategies (internal):");
+        mAppliedStrategyRolesInt.forEach((key, devices) -> {
+            pw.println("  " + prefix + "strategy: " + key.first
+                    +  " role:" + key.second + " devices:" + devices); });
+        pw.println("\n" + prefix + "Applied devices roles for presets (from API):");
         mAppliedPresetRoles.forEach((key, devices) -> {
             pw.println("  " + prefix + "preset: " + key.first
                     +  " role:" + key.second + " devices:" + devices); });
+        pw.println("\n" + prefix + "Applied devices roles for presets (internal:");
+        mAppliedPresetRolesInt.forEach((key, devices) -> {
+            pw.println("  " + prefix + "preset: " + key.first
+                    +  " role:" + key.second + " devices:" + devices); });
     }
 
     //------------------------------------------------------------
@@ -353,6 +362,9 @@
                         di.mDeviceCodecFormat);
             }
             mAppliedStrategyRoles.clear();
+            mAppliedStrategyRolesInt.clear();
+            mAppliedPresetRoles.clear();
+            mAppliedPresetRolesInt.clear();
             applyConnectedDevicesRoles_l();
         }
         synchronized (mPreferredDevices) {
@@ -361,8 +373,8 @@
         }
         synchronized (mNonDefaultDevices) {
             mNonDefaultDevices.forEach((strategy, devices) -> {
-                addDevicesRoleForStrategy(
-                        strategy, AudioSystem.DEVICE_ROLE_DISABLED, devices); });
+                addDevicesRoleForStrategy(strategy, AudioSystem.DEVICE_ROLE_DISABLED,
+                        devices, false /* internal */); });
         }
         synchronized (mPreferredDevicesForCapturePreset) {
             // TODO: call audiosystem to restore
@@ -768,7 +780,7 @@
         }
         return status;
     }
-
+    // Only used for external requests coming from an API
     /*package*/ int setPreferredDevicesForStrategy(int strategy,
             @NonNull List<AudioDeviceAttributes> devices) {
         int status = AudioSystem.ERROR;
@@ -777,10 +789,17 @@
                     "setPreferredDevicesForStrategy, strategy: " + strategy
                             + " devices: " + devices)).printLog(TAG));
             status = setDevicesRoleForStrategy(
-                    strategy, AudioSystem.DEVICE_ROLE_PREFERRED, devices);
+                    strategy, AudioSystem.DEVICE_ROLE_PREFERRED, devices, false /* internal */);
         }
         return status;
     }
+    // Only used for internal requests
+    /*package*/ int setPreferredDevicesForStrategyInt(int strategy,
+                                                  @NonNull List<AudioDeviceAttributes> devices) {
+
+        return setDevicesRoleForStrategy(
+                    strategy, AudioSystem.DEVICE_ROLE_PREFERRED, devices, true /* internal */);
+    }
 
     /*package*/ int removePreferredDevicesForStrategyAndSave(int strategy) {
         final int status = removePreferredDevicesForStrategy(strategy);
@@ -789,7 +808,7 @@
         }
         return status;
     }
-
+    // Only used for external requests coming from an API
     /*package*/ int removePreferredDevicesForStrategy(int strategy) {
         int status = AudioSystem.ERROR;
 
@@ -799,10 +818,15 @@
                             + strategy)).printLog(TAG));
 
             status = clearDevicesRoleForStrategy(
-                    strategy, AudioSystem.DEVICE_ROLE_PREFERRED);
+                    strategy, AudioSystem.DEVICE_ROLE_PREFERRED, false /*internal */);
         }
         return status;
     }
+    // Only used for internal requests
+    /*package*/ int removePreferredDevicesForStrategInt(int strategy) {
+        return clearDevicesRoleForStrategy(
+                    strategy, AudioSystem.DEVICE_ROLE_PREFERRED, true /*internal */);
+    }
 
     /*package*/ int setDeviceAsNonDefaultForStrategyAndSave(int strategy,
             @NonNull AudioDeviceAttributes device) {
@@ -816,7 +840,7 @@
                             "setDeviceAsNonDefaultForStrategyAndSave, strategy: " + strategy
                             + " device: " + device)).printLog(TAG));
             status = addDevicesRoleForStrategy(
-                    strategy, AudioSystem.DEVICE_ROLE_DISABLED, devices);
+                    strategy, AudioSystem.DEVICE_ROLE_DISABLED, devices, false /* internal */);
         }
 
         if (status == AudioSystem.SUCCESS) {
@@ -838,7 +862,7 @@
                             + strategy + " devices: " + device)).printLog(TAG));
 
             status = removeDevicesRoleForStrategy(
-                    strategy, AudioSystem.DEVICE_ROLE_DISABLED, devices);
+                    strategy, AudioSystem.DEVICE_ROLE_DISABLED, devices, false /* internal */);
         }
 
         if (status == AudioSystem.SUCCESS) {
@@ -877,6 +901,7 @@
         return status;
     }
 
+    // Only used for external requests coming from an API
     private int setPreferredDevicesForCapturePreset(
             int capturePreset, @NonNull List<AudioDeviceAttributes> devices) {
         int status = AudioSystem.ERROR;
@@ -895,6 +920,7 @@
         return status;
     }
 
+    // Only used for external requests coming from an API
     private int clearPreferredDevicesForCapturePreset(int capturePreset) {
         int status  = AudioSystem.ERROR;
 
@@ -905,20 +931,23 @@
         return status;
     }
 
-    private int addDevicesRoleForCapturePreset(int capturePreset, int role,
+    // Only used for internal requests
+    private int addDevicesRoleForCapturePresetInt(int capturePreset, int role,
                                                @NonNull List<AudioDeviceAttributes> devices) {
-        return addDevicesRole(mAppliedPresetRoles, (p, r, d) -> {
+        return addDevicesRole(mAppliedPresetRolesInt, (p, r, d) -> {
             return mAudioSystem.addDevicesRoleForCapturePreset(p, r, d);
         }, capturePreset, role, devices);
     }
 
-    private int removeDevicesRoleForCapturePreset(int capturePreset, int role,
+    // Only used for internal requests
+    private int removeDevicesRoleForCapturePresetInt(int capturePreset, int role,
                                                   @NonNull List<AudioDeviceAttributes> devices) {
-        return removeDevicesRole(mAppliedPresetRoles, (p, r, d) -> {
+        return removeDevicesRole(mAppliedPresetRolesInt, (p, r, d) -> {
             return mAudioSystem.removeDevicesRoleForCapturePreset(p, r, d);
         }, capturePreset, role, devices);
     }
 
+    // Only used for external requests coming from an API
     private int setDevicesRoleForCapturePreset(int capturePreset, int role,
                                                @NonNull List<AudioDeviceAttributes> devices) {
         return setDevicesRole(mAppliedPresetRoles, (p, r, d) -> {
@@ -928,6 +957,7 @@
             }, capturePreset, role, devices);
     }
 
+    // Only used for external requests coming from an API
     private int clearDevicesRoleForCapturePreset(int capturePreset, int role) {
         return clearDevicesRole(mAppliedPresetRoles, (p, r, d) -> {
             return mAudioSystem.clearDevicesRoleForCapturePreset(p, r);
@@ -945,32 +975,39 @@
     }
 
     private int addDevicesRoleForStrategy(int strategy, int role,
-                                          @NonNull List<AudioDeviceAttributes> devices) {
-        return addDevicesRole(mAppliedStrategyRoles, (s, r, d) -> {
-            return mAudioSystem.setDevicesRoleForStrategy(s, r, d);
-        }, strategy, role, devices);
+                                          @NonNull List<AudioDeviceAttributes> devices,
+                                          boolean internal) {
+        return addDevicesRole(internal ? mAppliedStrategyRolesInt : mAppliedStrategyRoles,
+                (s, r, d) -> {
+                    return mAudioSystem.setDevicesRoleForStrategy(s, r, d);
+                }, strategy, role, devices);
     }
 
     private int removeDevicesRoleForStrategy(int strategy, int role,
-                                      @NonNull List<AudioDeviceAttributes> devices) {
-        return removeDevicesRole(mAppliedStrategyRoles, (s, r, d) -> {
-            return mAudioSystem.removeDevicesRoleForStrategy(s, r, d);
-        }, strategy, role, devices);
+                                      @NonNull List<AudioDeviceAttributes> devices,
+                                             boolean internal) {
+        return removeDevicesRole(internal ? mAppliedStrategyRolesInt : mAppliedStrategyRoles,
+                (s, r, d) -> {
+                    return mAudioSystem.removeDevicesRoleForStrategy(s, r, d);
+                }, strategy, role, devices);
     }
 
     private int setDevicesRoleForStrategy(int strategy, int role,
-                                          @NonNull List<AudioDeviceAttributes> devices) {
-        return setDevicesRole(mAppliedStrategyRoles, (s, r, d) -> {
-            return mAudioSystem.setDevicesRoleForStrategy(s, r, d);
-        }, (s, r, d) -> {
-                return mAudioSystem.clearDevicesRoleForStrategy(s, r);
-            }, strategy, role, devices);
+                                          @NonNull List<AudioDeviceAttributes> devices,
+                                          boolean internal) {
+        return setDevicesRole(internal ? mAppliedStrategyRolesInt : mAppliedStrategyRoles,
+                (s, r, d) -> {
+                    return mAudioSystem.setDevicesRoleForStrategy(s, r, d);
+                }, (s, r, d) -> {
+                    return mAudioSystem.clearDevicesRoleForStrategy(s, r);
+                }, strategy, role, devices);
     }
 
-    private int clearDevicesRoleForStrategy(int strategy, int role) {
-        return clearDevicesRole(mAppliedStrategyRoles, (s, r, d) -> {
-            return mAudioSystem.clearDevicesRoleForStrategy(s, r);
-        }, strategy, role);
+    private int clearDevicesRoleForStrategy(int strategy, int role, boolean internal) {
+        return clearDevicesRole(internal ? mAppliedStrategyRolesInt : mAppliedStrategyRoles,
+                (s, r, d) -> {
+                    return mAudioSystem.clearDevicesRoleForStrategy(s, r);
+                }, strategy, role);
     }
 
     //------------------------------------------------------------
@@ -978,16 +1015,25 @@
     // same list of devices for a given role and strategy and the corresponding systematic
     // redundant work in audio policy manager and audio flinger.
     // The key is the pair <Strategy , Role> and the value is the current list of devices.
-
+    // mAppliedStrategyRoles is for requests coming from an API.
+    // mAppliedStrategyRolesInt is for internal requests. Entries are removed when the requested
+    // device is disconnected.
     private final ArrayMap<Pair<Integer, Integer>, List<AudioDeviceAttributes>>
             mAppliedStrategyRoles = new ArrayMap<>();
+    private final ArrayMap<Pair<Integer, Integer>, List<AudioDeviceAttributes>>
+            mAppliedStrategyRolesInt = new ArrayMap<>();
 
     // Cache for applied roles for capture presets and devices. The cache avoids reapplying the
     // same list of devices for a given role and capture preset and the corresponding systematic
     // redundant work in audio policy manager and audio flinger.
     // The key is the pair <Preset , Role> and the value is the current list of devices.
+    // mAppliedPresetRoles is for requests coming from an API.
+    // mAppliedPresetRolesInt is for internal requests. Entries are removed when the requested
+    // device is disconnected.
     private final ArrayMap<Pair<Integer, Integer>, List<AudioDeviceAttributes>>
             mAppliedPresetRoles = new ArrayMap<>();
+    private final ArrayMap<Pair<Integer, Integer>, List<AudioDeviceAttributes>>
+            mAppliedPresetRolesInt = new ArrayMap<>();
 
     interface AudioSystemInterface {
         int deviceRoleAction(int usecase, int role, @Nullable List<AudioDeviceAttributes> devices);
@@ -1113,9 +1159,9 @@
 
     @GuardedBy("mDevicesLock")
     private void purgeDevicesRoles_l() {
-        purgeRoles(mAppliedStrategyRoles, (s, r, d) -> {
+        purgeRoles(mAppliedStrategyRolesInt, (s, r, d) -> {
             return mAudioSystem.removeDevicesRoleForStrategy(s, r, d); });
-        purgeRoles(mAppliedPresetRoles, (p, r, d) -> {
+        purgeRoles(mAppliedPresetRolesInt, (p, r, d) -> {
             return mAudioSystem.removeDevicesRoleForCapturePreset(p, r, d); });
     }
 
@@ -1124,8 +1170,12 @@
             ArrayMap<Pair<Integer, Integer>, List<AudioDeviceAttributes>> rolesMap,
             AudioSystemInterface asi) {
         synchronized (rolesMap) {
+            AudioDeviceInfo[] connectedDevices = AudioManager.getDevicesStatic(
+                    AudioManager.GET_DEVICES_ALL);
+
             Iterator<Map.Entry<Pair<Integer, Integer>, List<AudioDeviceAttributes>>> itRole =
                     rolesMap.entrySet().iterator();
+
             while (itRole.hasNext()) {
                 Map.Entry<Pair<Integer, Integer>, List<AudioDeviceAttributes>> entry =
                         itRole.next();
@@ -1133,9 +1183,15 @@
                 Iterator<AudioDeviceAttributes> itDev = rolesMap.get(keyRole).iterator();
                 while (itDev.hasNext()) {
                     AudioDeviceAttributes ada = itDev.next();
-                    final String devKey = DeviceInfo.makeDeviceListKey(ada.getInternalType(),
-                            ada.getAddress());
-                    if (mConnectedDevices.get(devKey) == null) {
+
+                    AudioDeviceInfo device = Stream.of(connectedDevices)
+                            .filter(d -> d.getInternalType() == ada.getInternalType())
+                            .filter(d -> (!AudioSystem.isBluetoothDevice(d.getInternalType())
+                                            || (d.getAddress() == ada.getAddress())))
+                            .findFirst()
+                            .orElse(null);
+
+                    if (device == null) {
                         asi.deviceRoleAction(keyRole.first, keyRole.second, Arrays.asList(ada));
                         itDev.remove();
                     }
@@ -1582,10 +1638,12 @@
                     }
                     if (disable) {
                         addDevicesRoleForStrategy(strategy.getId(),
-                                AudioSystem.DEVICE_ROLE_DISABLED, Arrays.asList(ada));
+                                AudioSystem.DEVICE_ROLE_DISABLED,
+                                Arrays.asList(ada), true /* internal */);
                     } else {
                         removeDevicesRoleForStrategy(strategy.getId(),
-                                AudioSystem.DEVICE_ROLE_DISABLED, Arrays.asList(ada));
+                                AudioSystem.DEVICE_ROLE_DISABLED,
+                                Arrays.asList(ada), true /* internal */);
                     }
                 }
             }
@@ -1602,10 +1660,10 @@
                                 + ", disable: " + disable);
                     }
                     if (disable) {
-                        addDevicesRoleForCapturePreset(capturePreset,
+                        addDevicesRoleForCapturePresetInt(capturePreset,
                                 AudioSystem.DEVICE_ROLE_DISABLED, Arrays.asList(ada));
                     } else {
-                        removeDevicesRoleForCapturePreset(capturePreset,
+                        removeDevicesRoleForCapturePresetInt(capturePreset,
                                 AudioSystem.DEVICE_ROLE_DISABLED, Arrays.asList(ada));
                     }
                 }
diff --git a/services/core/java/com/android/server/connectivity/Vpn.java b/services/core/java/com/android/server/connectivity/Vpn.java
index d4bb445..e85eee81 100644
--- a/services/core/java/com/android/server/connectivity/Vpn.java
+++ b/services/core/java/com/android/server/connectivity/Vpn.java
@@ -309,6 +309,8 @@
      */
     @VisibleForTesting
     static final int DEFAULT_LONG_LIVED_TCP_CONNS_EXPENSIVE_TIMEOUT_SEC = 60;
+
+    private static final int PREFERRED_IKE_PROTOCOL_UNKNOWN = -1;
     /**
      *  Prefer using {@link IkeSessionParams.ESP_IP_VERSION_AUTO} and
      *  {@link IkeSessionParams.ESP_ENCAP_TYPE_AUTO} for ESP packets.
@@ -3643,11 +3645,11 @@
 
             final int natKeepalive =
                     carrierConfig.getInt(KEY_MIN_UDP_PORT_4500_NAT_TIMEOUT_SEC_INT);
-            final int preferredIpPortocol =
-                    carrierConfig.getInt(KEY_PREFERRED_IKE_PROTOCOL_INT);
+            final int preferredIpProtocol = carrierConfig.getInt(
+                    KEY_PREFERRED_IKE_PROTOCOL_INT, PREFERRED_IKE_PROTOCOL_UNKNOWN);
             final String mccMnc = perSubTm.getSimOperator(subId);
             final CarrierConfigInfo info =
-                    buildCarrierConfigInfo(mccMnc, natKeepalive, preferredIpPortocol);
+                    buildCarrierConfigInfo(mccMnc, natKeepalive, preferredIpProtocol);
             synchronized (Vpn.this) {
                 mCachedCarrierConfigInfoPerSubId.put(subId, info);
             }
@@ -5010,7 +5012,7 @@
      * Retrieve the VpnProfileState for the profile provisioned by the given package.
      *
      * @return the VpnProfileState with current information, or null if there was no profile
-     *         provisioned by the given package.
+     *         provisioned and started by the given package.
      */
     @Nullable
     public synchronized VpnProfileState getProvisionedVpnProfileState(
diff --git a/services/core/java/com/android/server/display/LocalDisplayAdapter.java b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
index c7c0fab..7701bc6 100644
--- a/services/core/java/com/android/server/display/LocalDisplayAdapter.java
+++ b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
@@ -701,11 +701,15 @@
                         maxDisplayMode == null ? mInfo.width : maxDisplayMode.getPhysicalWidth();
                 final int maxHeight =
                         maxDisplayMode == null ? mInfo.height : maxDisplayMode.getPhysicalHeight();
-                mInfo.displayCutout = DisplayCutout.fromResourcesRectApproximation(res,
-                        mInfo.uniqueId, maxWidth, maxHeight, mInfo.width, mInfo.height);
 
-                mInfo.roundedCorners = RoundedCorners.fromResources(
-                        res, mInfo.uniqueId, maxWidth, maxHeight, mInfo.width, mInfo.height);
+                // We cannot determine cutouts and rounded corners of external displays.
+                if (mStaticDisplayInfo.isInternal) {
+                    mInfo.displayCutout = DisplayCutout.fromResourcesRectApproximation(res,
+                            mInfo.uniqueId, maxWidth, maxHeight, mInfo.width, mInfo.height);
+                    mInfo.roundedCorners = RoundedCorners.fromResources(
+                            res, mInfo.uniqueId, maxWidth, maxHeight, mInfo.width, mInfo.height);
+                }
+
                 mInfo.installOrientation = mStaticDisplayInfo.installOrientation;
 
                 mInfo.displayShape = DisplayShape.fromResources(
diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java
index 2c54e1c..3eab4b0 100644
--- a/services/core/java/com/android/server/hdmi/HdmiControlService.java
+++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java
@@ -1579,6 +1579,10 @@
             // If the device is not TV, we can't convert path to port-id, so stop here.
             return true;
         }
+        // Invalidate the physical address if parameters length is too short.
+        if (params.length < offset + 2) {
+            return false;
+        }
         int path = HdmiUtils.twoBytesToInt(params, offset);
         if (path != Constants.INVALID_PHYSICAL_ADDRESS && path == getPhysicalAddress()) {
             return true;
diff --git a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
index 1059c19..309a99e 100644
--- a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
+++ b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
@@ -1644,6 +1644,26 @@
         }
 
         /**
+         * Notifies the corresponding router that it was successfully registered.
+         *
+         * <p>The message sent to the router includes a snapshot of the initial state, including
+         * known routes and the system {@link RoutingSessionInfo}.
+         *
+         * @param currentRoutes All currently known routes, which are filtered according to package
+         *     visibility before being sent to the router.
+         * @param currentSystemSessionInfo The current system {@link RoutingSessionInfo}.
+         */
+        public void notifyRegistered(
+                List<MediaRoute2Info> currentRoutes, RoutingSessionInfo currentSystemSessionInfo) {
+            try {
+                mRouter.notifyRouterRegistered(
+                        getVisibleRoutes(currentRoutes), currentSystemSessionInfo);
+            } catch (RemoteException ex) {
+                Slog.w(TAG, "Failed to notify router registered. Router probably died.", ex);
+            }
+        }
+
+        /**
          * Sends the corresponding router an {@link
          * android.media.MediaRouter2.RouteCallback#onRoutesUpdated update} for the given {@code
          * routes}.
@@ -2544,12 +2564,7 @@
                 return;
             }
 
-            try {
-                routerRecord.mRouter.notifyRouterRegistered(
-                        currentRoutes, currentSystemSessionInfo);
-            } catch (RemoteException ex) {
-                Slog.w(TAG, "Failed to notify router registered. Router probably died.", ex);
-            }
+            routerRecord.notifyRegistered(currentRoutes, currentSystemSessionInfo);
         }
 
         private static void notifyRoutesUpdatedToRouterRecords(
diff --git a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
index 0171c20..3c97672 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
@@ -2367,6 +2367,10 @@
         final ActivityRecord prevTopActivity = mTopResumedActivity;
         final Task topRootTask = mRootWindowContainer.getTopDisplayFocusedRootTask();
         if (topRootTask == null || topRootTask.getTopResumedActivity() == prevTopActivity) {
+            if (topRootTask == null) {
+                // There's no focused task and there won't have any resumed activity either.
+                scheduleTopResumedActivityStateLossIfNeeded();
+            }
             if (mService.isSleepingLocked()) {
                 // There won't be a next resumed activity. The top process should still be updated
                 // according to the current top focused activity.
@@ -2376,16 +2380,7 @@
         }
 
         // Ask previous activity to release the top state.
-        final boolean prevActivityReceivedTopState =
-                prevTopActivity != null && !mTopResumedActivityWaitingForPrev;
-        // mTopResumedActivityWaitingForPrev == true at this point would mean that an activity
-        // before the prevTopActivity one hasn't reported back yet. So server never sent the top
-        // resumed state change message to prevTopActivity.
-        if (prevActivityReceivedTopState
-                && prevTopActivity.scheduleTopResumedActivityChanged(false /* onTop */)) {
-            scheduleTopResumedStateLossTimeout(prevTopActivity);
-            mTopResumedActivityWaitingForPrev = true;
-        }
+        scheduleTopResumedActivityStateLossIfNeeded();
 
         // Update the current top activity.
         mTopResumedActivity = topRootTask.getTopResumedActivity();
@@ -2410,6 +2405,23 @@
         mService.updateTopApp(mTopResumedActivity);
     }
 
+    /** Schedule current top resumed activity state loss */
+    private void scheduleTopResumedActivityStateLossIfNeeded() {
+        if (mTopResumedActivity == null) {
+            return;
+        }
+
+        // mTopResumedActivityWaitingForPrev == true at this point would mean that an activity
+        // before the prevTopActivity one hasn't reported back yet. So server never sent the top
+        // resumed state change message to prevTopActivity.
+        if (!mTopResumedActivityWaitingForPrev
+                && mTopResumedActivity.scheduleTopResumedActivityChanged(false /* onTop */)) {
+            scheduleTopResumedStateLossTimeout(mTopResumedActivity);
+            mTopResumedActivityWaitingForPrev = true;
+            mTopResumedActivity = null;
+        }
+    }
+
     /** Schedule top resumed state change if previous top activity already reported back. */
     private void scheduleTopResumedActivityStateIfNeeded() {
         if (mTopResumedActivity != null && !mTopResumedActivityWaitingForPrev) {
diff --git a/services/core/java/com/android/server/wm/AnrController.java b/services/core/java/com/android/server/wm/AnrController.java
index 2df601f..b9f6e17 100644
--- a/services/core/java/com/android/server/wm/AnrController.java
+++ b/services/core/java/com/android/server/wm/AnrController.java
@@ -68,7 +68,7 @@
     void notifyAppUnresponsive(InputApplicationHandle applicationHandle,
             TimeoutRecord timeoutRecord) {
         try {
-            Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "notifyAppUnresponsive()");
+            timeoutRecord.mLatencyTracker.notifyAppUnresponsiveStarted();
             timeoutRecord.mLatencyTracker.preDumpIfLockTooSlowStarted();
             preDumpIfLockTooSlow();
             timeoutRecord.mLatencyTracker.preDumpIfLockTooSlowEnded();
@@ -111,7 +111,6 @@
                 if (!blamePendingFocusRequest) {
                     Slog.i(TAG_WM, "ANR in " + activity.getName() + ".  Reason: "
                             + timeoutRecord.mReason);
-                    dumpAnrStateAsync(activity, null /* windowState */, timeoutRecord.mReason);
                     mUnresponsiveAppByDisplay.put(activity.getDisplayId(), activity);
                 }
             }
@@ -123,8 +122,13 @@
             } else {
                 activity.inputDispatchingTimedOut(timeoutRecord, INVALID_PID);
             }
+
+            if (!blamePendingFocusRequest) {
+                dumpAnrStateAsync(activity, null /* windowState */, timeoutRecord.mReason);
+            }
+
         } finally {
-            Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
+            timeoutRecord.mLatencyTracker.notifyAppUnresponsiveEnded();
         }
     }
 
@@ -139,7 +143,7 @@
     void notifyWindowUnresponsive(@NonNull IBinder token, @NonNull OptionalInt pid,
             @NonNull TimeoutRecord timeoutRecord) {
         try {
-            Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "notifyWindowUnresponsive()");
+            timeoutRecord.mLatencyTracker.notifyWindowUnresponsiveStarted();
             if (notifyWindowUnresponsive(token, timeoutRecord)) {
                 return;
             }
@@ -150,7 +154,7 @@
             }
             notifyWindowUnresponsive(pid.getAsInt(), timeoutRecord);
         } finally {
-            Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
+            timeoutRecord.mLatencyTracker.notifyWindowUnresponsiveEnded();
         }
     }
 
@@ -168,6 +172,7 @@
         final int pid;
         final boolean aboveSystem;
         final ActivityRecord activity;
+        final WindowState windowState;
         timeoutRecord.mLatencyTracker.waitingOnGlobalLockStarted();
         synchronized (mService.mGlobalLock) {
             timeoutRecord.mLatencyTracker.waitingOnGlobalLockEnded();
@@ -175,7 +180,7 @@
             if (target == null) {
                 return false;
             }
-            WindowState windowState = target.getWindowState();
+            windowState = target.getWindowState();
             pid = target.getPid();
             // Blame the activity if the input token belongs to the window. If the target is
             // embedded, then we will blame the pid instead.
@@ -183,13 +188,13 @@
                     ? windowState.mActivityRecord : null;
             Slog.i(TAG_WM, "ANR in " + target + ". Reason:" + timeoutRecord.mReason);
             aboveSystem = isWindowAboveSystem(windowState);
-            dumpAnrStateAsync(activity, windowState, timeoutRecord.mReason);
         }
         if (activity != null) {
             activity.inputDispatchingTimedOut(timeoutRecord, pid);
         } else {
             mService.mAmInternal.inputDispatchingTimedOut(pid, aboveSystem, timeoutRecord);
         }
+        dumpAnrStateAsync(activity, windowState, timeoutRecord.mReason);
         return true;
     }
 
@@ -199,15 +204,10 @@
     private void notifyWindowUnresponsive(int pid, TimeoutRecord timeoutRecord) {
         Slog.i(TAG_WM,
                 "ANR in input window owned by pid=" + pid + ". Reason: " + timeoutRecord.mReason);
-        timeoutRecord.mLatencyTracker.waitingOnGlobalLockStarted();
-        synchronized (mService.mGlobalLock) {
-            timeoutRecord.mLatencyTracker.waitingOnGlobalLockEnded();
-            dumpAnrStateAsync(null /* activity */, null /* windowState */, timeoutRecord.mReason);
-        }
-
         // We cannot determine the z-order of the window, so place the anr dialog as high
         // as possible.
         mService.mAmInternal.inputDispatchingTimedOut(pid, true /*aboveSystem*/, timeoutRecord);
+        dumpAnrStateAsync(null /* activity */, null /* windowState */, timeoutRecord.mReason);
     }
 
     /**
diff --git a/services/core/java/com/android/server/wm/InsetsSourceProvider.java b/services/core/java/com/android/server/wm/InsetsSourceProvider.java
index 7f845e6..21004ab 100644
--- a/services/core/java/com/android/server/wm/InsetsSourceProvider.java
+++ b/services/core/java/com/android/server/wm/InsetsSourceProvider.java
@@ -272,15 +272,17 @@
 
     /** @return A new source computed by the specified window frame in the given display frames. */
     InsetsSource createSimulatedSource(DisplayFrames displayFrames, Rect frame) {
-        // Don't copy visible frame because it might not be calculated in the provided display
-        // frames and it is not significant for this usage.
-        final InsetsSource source = new InsetsSource(mSource.getId(), mSource.getType());
-        source.setVisible(mSource.isVisible());
+        final InsetsSource source = new InsetsSource(mSource);
         mTmpRect.set(frame);
         if (mFrameProvider != null) {
             mFrameProvider.apply(displayFrames, mWindowContainer, mTmpRect);
         }
         source.setFrame(mTmpRect);
+
+        // Don't copy visible frame because it might not be calculated in the provided display
+        // frames and it is not significant for this usage.
+        source.setVisibleFrame(null);
+
         return source;
     }
 
diff --git a/services/core/java/com/android/server/wm/SnapshotPersistQueue.java b/services/core/java/com/android/server/wm/SnapshotPersistQueue.java
index afef85e..58e1c54 100644
--- a/services/core/java/com/android/server/wm/SnapshotPersistQueue.java
+++ b/services/core/java/com/android/server/wm/SnapshotPersistQueue.java
@@ -314,6 +314,11 @@
             }
 
             final Bitmap swBitmap = bitmap.copy(Bitmap.Config.ARGB_8888, false /* isMutable */);
+            if (swBitmap == null) {
+                Slog.e(TAG, "Bitmap conversion from (config=" + bitmap.getConfig() + ", isMutable="
+                        + bitmap.isMutable() + ") to (config=ARGB_8888, isMutable=false) failed.");
+                return false;
+            }
 
             final File file = mPersistInfoProvider.getHighResolutionBitmapFile(mId, mUserId);
             try {
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index ce4f445..1ae1e03 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -1847,6 +1847,9 @@
                 td.setEnsureStatusBarContrastWhenTransparent(
                         atd.getEnsureStatusBarContrastWhenTransparent());
             }
+            if (td.getStatusBarAppearance() == 0) {
+                td.setStatusBarAppearance(atd.getStatusBarAppearance());
+            }
             if (td.getNavigationBarColor() == 0) {
                 td.setNavigationBarColor(atd.getNavigationBarColor());
                 td.setEnsureNavigationBarContrastWhenTransparent(
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index cb6a57b..7460d12 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -8270,6 +8270,13 @@
         @Override
         public void addTrustedTaskOverlay(int taskId,
                 SurfaceControlViewHost.SurfacePackage overlay) {
+            if (overlay == null) {
+                throw new IllegalArgumentException("Invalid overlay passed in for task=" + taskId);
+            } else if (overlay.getSurfaceControl() == null
+                    || !overlay.getSurfaceControl().isValid()) {
+                throw new IllegalArgumentException(
+                        "Invalid overlay surfacecontrol passed in for task=" + taskId);
+            }
             synchronized (mGlobalLock) {
                 final Task task = mRoot.getRootTask(taskId);
                 if (task == null) {
@@ -8282,6 +8289,13 @@
         @Override
         public void removeTrustedTaskOverlay(int taskId,
                 SurfaceControlViewHost.SurfacePackage overlay) {
+            if (overlay == null) {
+                throw new IllegalArgumentException("Invalid overlay passed in for task=" + taskId);
+            } else if (overlay.getSurfaceControl() == null
+                    || !overlay.getSurfaceControl().isValid()) {
+                throw new IllegalArgumentException(
+                        "Invalid overlay surfacecontrol passed in for task=" + taskId);
+            }
             synchronized (mGlobalLock) {
                 final Task task = mRoot.getRootTask(taskId);
                 if (task == null) {
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 7cd7f69..8f49384 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -3883,13 +3883,15 @@
     }
 
     /**
-     * @return true if activity bounds are letterboxed or letterboxed for diplay cutout.
+     * @return {@code true} if activity bounds are letterboxed or letterboxed for display cutout.
+     * Note that it's always {@code false} if the activity is in pip mode.
      *
      * <p>Note that letterbox UI may not be shown even when this returns {@code true}. See {@link
      * LetterboxUiController#shouldShowLetterboxUi} for more context.
      */
     boolean areAppWindowBoundsLetterboxed() {
         return mActivityRecord != null
+                && !mActivityRecord.inPinnedWindowingMode()
                 && (mActivityRecord.areBoundsLetterboxed() || isLetterboxedForDisplayCutout());
     }
 
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index edb8e0c..b43209f 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -49,6 +49,7 @@
 import static android.Manifest.permission.MANAGE_DEVICE_POLICY_LOCK_TASK;
 import static android.Manifest.permission.MANAGE_DEVICE_POLICY_MICROPHONE;
 import static android.Manifest.permission.MANAGE_DEVICE_POLICY_MOBILE_NETWORK;
+import static android.Manifest.permission.MANAGE_DEVICE_POLICY_MODIFY_USERS;
 import static android.Manifest.permission.MANAGE_DEVICE_POLICY_MTE;
 import static android.Manifest.permission.MANAGE_DEVICE_POLICY_NEARBY_COMMUNICATION;
 import static android.Manifest.permission.MANAGE_DEVICE_POLICY_ORGANIZATION_IDENTITY;
@@ -73,7 +74,6 @@
 import static android.Manifest.permission.MANAGE_DEVICE_POLICY_TIME;
 import static android.Manifest.permission.MANAGE_DEVICE_POLICY_USB_DATA_SIGNALLING;
 import static android.Manifest.permission.MANAGE_DEVICE_POLICY_USB_FILE_TRANSFER;
-import static android.Manifest.permission.MANAGE_DEVICE_POLICY_USERS;
 import static android.Manifest.permission.MANAGE_DEVICE_POLICY_VPN;
 import static android.Manifest.permission.MANAGE_DEVICE_POLICY_WALLPAPER;
 import static android.Manifest.permission.MANAGE_DEVICE_POLICY_WIFI;
@@ -139,6 +139,7 @@
 import static android.app.admin.DevicePolicyManager.ID_TYPE_MEID;
 import static android.app.admin.DevicePolicyManager.ID_TYPE_SERIAL;
 import static android.app.admin.DevicePolicyManager.LEAVE_ALL_SYSTEM_APPS_ENABLED;
+import static android.app.admin.DevicePolicyManager.LOCK_TASK_FEATURE_BLOCK_ACTIVITY_START_IN_TASK;
 import static android.app.admin.DevicePolicyManager.LOCK_TASK_FEATURE_GLOBAL_ACTIONS;
 import static android.app.admin.DevicePolicyManager.LOCK_TASK_FEATURE_HOME;
 import static android.app.admin.DevicePolicyManager.LOCK_TASK_FEATURE_KEYGUARD;
@@ -13720,7 +13721,7 @@
         USER_RESTRICTION_PERMISSIONS.put(
                 UserManager.DISALLOW_ADD_CLONE_PROFILE, new String[]{MANAGE_DEVICE_POLICY_PROFILES});
         USER_RESTRICTION_PERMISSIONS.put(
-                UserManager.DISALLOW_ADD_USER, new String[]{MANAGE_DEVICE_POLICY_USERS});
+                UserManager.DISALLOW_ADD_USER, new String[]{MANAGE_DEVICE_POLICY_MODIFY_USERS});
         USER_RESTRICTION_PERMISSIONS.put(
                 UserManager.DISALLOW_ADD_WIFI_CONFIG, new String[]{MANAGE_DEVICE_POLICY_WIFI});
         USER_RESTRICTION_PERMISSIONS.put(
@@ -13810,13 +13811,13 @@
         USER_RESTRICTION_PERMISSIONS.put(
                 UserManager.DISALLOW_PRINTING, new String[]{MANAGE_DEVICE_POLICY_PRINTING});
         USER_RESTRICTION_PERMISSIONS.put(
-                UserManager.DISALLOW_REMOVE_USER, new String[]{MANAGE_DEVICE_POLICY_USERS});
+                UserManager.DISALLOW_REMOVE_USER, new String[]{MANAGE_DEVICE_POLICY_MODIFY_USERS});
         USER_RESTRICTION_PERMISSIONS.put(
                 UserManager.DISALLOW_RUN_IN_BACKGROUND, new String[]{MANAGE_DEVICE_POLICY_RUN_IN_BACKGROUND});
         USER_RESTRICTION_PERMISSIONS.put(
                 UserManager.DISALLOW_SAFE_BOOT, new String[]{MANAGE_DEVICE_POLICY_SAFE_BOOT});
         USER_RESTRICTION_PERMISSIONS.put(
-                UserManager.DISALLOW_SET_USER_ICON, new String[]{MANAGE_DEVICE_POLICY_USERS});
+                UserManager.DISALLOW_SET_USER_ICON, new String[]{MANAGE_DEVICE_POLICY_MODIFY_USERS});
         USER_RESTRICTION_PERMISSIONS.put(
                 UserManager.DISALLOW_SET_WALLPAPER, new String[]{MANAGE_DEVICE_POLICY_WALLPAPER});
         USER_RESTRICTION_PERMISSIONS.put(
@@ -13842,7 +13843,7 @@
         USER_RESTRICTION_PERMISSIONS.put(
                 UserManager.DISALLOW_USB_FILE_TRANSFER, new String[]{MANAGE_DEVICE_POLICY_USB_FILE_TRANSFER});
         USER_RESTRICTION_PERMISSIONS.put(
-                UserManager.DISALLOW_USER_SWITCH, new String[]{MANAGE_DEVICE_POLICY_USERS});
+                UserManager.DISALLOW_USER_SWITCH, new String[]{MANAGE_DEVICE_POLICY_MODIFY_USERS});
         USER_RESTRICTION_PERMISSIONS.put(
                 UserManager.DISALLOW_WIFI_DIRECT, new String[]{MANAGE_DEVICE_POLICY_WIFI});
         USER_RESTRICTION_PERMISSIONS.put(
@@ -14836,17 +14837,23 @@
                 "Caller is not managed profile owner or device owner;"
                         + " only managed profile owner or device owner may control the preferential"
                         + " network service");
-        synchronized (getLockObject()) {
-            final ActiveAdmin requiredAdmin = getDeviceOrProfileOwnerAdminLocked(
-                    caller.getUserId());
-            if (!requiredAdmin.mPreferentialNetworkServiceConfigs.equals(
-                    preferentialNetworkServiceConfigs)) {
-                requiredAdmin.mPreferentialNetworkServiceConfigs =
-                        new ArrayList<>(preferentialNetworkServiceConfigs);
-                saveSettingsLocked(caller.getUserId());
+
+        try {
+            updateNetworkPreferenceForUser(caller.getUserId(), preferentialNetworkServiceConfigs);
+            synchronized (getLockObject()) {
+                final ActiveAdmin requiredAdmin = getDeviceOrProfileOwnerAdminLocked(
+                        caller.getUserId());
+                if (!requiredAdmin.mPreferentialNetworkServiceConfigs.equals(
+                        preferentialNetworkServiceConfigs)) {
+                    requiredAdmin.mPreferentialNetworkServiceConfigs =
+                            new ArrayList<>(preferentialNetworkServiceConfigs);
+                    saveSettingsLocked(caller.getUserId());
+                }
             }
+        } catch (Exception e) {
+            Slogf.e(LOG_TAG, "Failed to set preferential network service configs");
+            throw e;
         }
-        updateNetworkPreferenceForUser(caller.getUserId(), preferentialNetworkServiceConfigs);
         DevicePolicyEventLogger
                 .createEvent(DevicePolicyEnums.SET_PREFERENTIAL_NETWORK_SERVICE_ENABLED)
                 .setBoolean(preferentialNetworkServiceConfigs
@@ -15139,7 +15146,7 @@
     private void enforceCanSetLockTaskFeaturesOnFinancedDevice(CallerIdentity caller, int flags) {
         int allowedFlags = LOCK_TASK_FEATURE_SYSTEM_INFO | LOCK_TASK_FEATURE_KEYGUARD
                 | LOCK_TASK_FEATURE_HOME | LOCK_TASK_FEATURE_GLOBAL_ACTIONS
-                | LOCK_TASK_FEATURE_NOTIFICATIONS;
+                | LOCK_TASK_FEATURE_NOTIFICATIONS | LOCK_TASK_FEATURE_BLOCK_ACTIVITY_START_IN_TASK;
 
         if (!isFinancedDeviceOwner(caller)) {
             return;
@@ -15150,7 +15157,8 @@
                     "Permitted lock task features when managing a financed device: "
                             + "LOCK_TASK_FEATURE_SYSTEM_INFO, LOCK_TASK_FEATURE_KEYGUARD, "
                             + "LOCK_TASK_FEATURE_HOME, LOCK_TASK_FEATURE_GLOBAL_ACTIONS, "
-                            + "or LOCK_TASK_FEATURE_NOTIFICATIONS");
+                            + "LOCK_TASK_FEATURE_NOTIFICATIONS"
+                            + " or LOCK_TASK_FEATURE_BLOCK_ACTIVITY_START_IN_TASK");
         }
     }
 
@@ -23019,6 +23027,7 @@
             MANAGE_DEVICE_POLICY_LOCK_TASK,
             MANAGE_DEVICE_POLICY_MICROPHONE,
             MANAGE_DEVICE_POLICY_MOBILE_NETWORK,
+            MANAGE_DEVICE_POLICY_MODIFY_USERS,
             MANAGE_DEVICE_POLICY_MTE,
             MANAGE_DEVICE_POLICY_NEARBY_COMMUNICATION,
             MANAGE_DEVICE_POLICY_ORGANIZATION_IDENTITY,
@@ -23042,7 +23051,6 @@
             MANAGE_DEVICE_POLICY_TIME,
             MANAGE_DEVICE_POLICY_USB_DATA_SIGNALLING,
             MANAGE_DEVICE_POLICY_USB_FILE_TRANSFER,
-            MANAGE_DEVICE_POLICY_USERS,
             MANAGE_DEVICE_POLICY_VPN,
             MANAGE_DEVICE_POLICY_WALLPAPER,
             MANAGE_DEVICE_POLICY_WIFI,
@@ -23063,12 +23071,12 @@
             MANAGE_DEVICE_POLICY_KEYGUARD,
             MANAGE_DEVICE_POLICY_LOCK_CREDENTIALS,
             MANAGE_DEVICE_POLICY_LOCK_TASK,
+            MANAGE_DEVICE_POLICY_MODIFY_USERS,
             MANAGE_DEVICE_POLICY_ORGANIZATION_IDENTITY,
             MANAGE_DEVICE_POLICY_RUNTIME_PERMISSIONS,
             MANAGE_DEVICE_POLICY_SAFE_BOOT,
             MANAGE_DEVICE_POLICY_SUPPORT_MESSAGE,
             MANAGE_DEVICE_POLICY_TIME,
-            MANAGE_DEVICE_POLICY_USERS,
             MANAGE_DEVICE_POLICY_WIPE_DATA
     );
 
@@ -23154,6 +23162,7 @@
                     MANAGE_DEVICE_POLICY_FUN,
                     MANAGE_DEVICE_POLICY_LOCK_TASK,
                     MANAGE_DEVICE_POLICY_MOBILE_NETWORK,
+                    MANAGE_DEVICE_POLICY_MODIFY_USERS,
                     MANAGE_DEVICE_POLICY_PHYSICAL_MEDIA,
                     MANAGE_DEVICE_POLICY_PRINTING,
                     MANAGE_DEVICE_POLICY_PROFILES,
@@ -23162,7 +23171,6 @@
                     MANAGE_DEVICE_POLICY_SMS,
                     MANAGE_DEVICE_POLICY_SYSTEM_DIALOGS,
                     MANAGE_DEVICE_POLICY_USB_FILE_TRANSFER,
-                    MANAGE_DEVICE_POLICY_USERS,
                     MANAGE_DEVICE_POLICY_WINDOWS,
                     SET_TIME,
                     SET_TIME_ZONE
@@ -23352,6 +23360,8 @@
                 MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL);
         CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_LOCK_TASK,
                 MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL);
+        CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_MODIFY_USERS,
+                MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL);
         CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_MICROPHONE_TOGGLE,
                 MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL);
         CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_ORGANIZATION_IDENTITY,
@@ -23372,8 +23382,6 @@
                 MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL);
         CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_SYSTEM_DIALOGS,
                 MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL);
-        CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_USERS,
-                MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL);
         CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_VPN,
                 MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL);
         CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_WALLPAPER,
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java
index 6bce71e..948687a 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java
@@ -1406,7 +1406,8 @@
                 eq(getUidForPackage(PACKAGE_GREEN)), anyInt(), eq(Intent.ACTION_TIME_TICK),
                 eq(BROADCAST_DELIVERY_EVENT_REPORTED__RECEIVER_TYPE__MANIFEST),
                 eq(BROADCAST_DELIVERY_EVENT_REPORTED__PROC_START_TYPE__PROCESS_START_TYPE_COLD),
-                anyLong(), anyLong(), anyLong(), anyInt(), nullable(String.class), anyString()),
+                anyLong(), anyLong(), anyLong(), anyInt(), nullable(String.class),
+                anyString(), anyInt(), anyInt()),
                 times(1));
     }
 
diff --git a/services/tests/mockingservicestests/src/com/android/server/display/LocalDisplayAdapterTest.java b/services/tests/mockingservicestests/src/com/android/server/display/LocalDisplayAdapterTest.java
index b7dbaf9..f89f73c9 100644
--- a/services/tests/mockingservicestests/src/com/android/server/display/LocalDisplayAdapterTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/display/LocalDisplayAdapterTest.java
@@ -37,6 +37,7 @@
 import android.content.Context;
 import android.content.res.Resources;
 import android.content.res.TypedArray;
+import android.graphics.Rect;
 import android.os.Binder;
 import android.os.Handler;
 import android.os.IBinder;
@@ -1009,6 +1010,72 @@
                 0.001f);
     }
 
+    @Test
+    public void test_getDisplayDeviceInfoLocked_internalDisplay_usesCutoutAndCorners()
+            throws Exception {
+        setupCutoutAndRoundedCorners();
+        FakeDisplay display = new FakeDisplay(PORT_A);
+        display.info.isInternal = true;
+        setUpDisplay(display);
+        updateAvailableDisplays();
+        mAdapter.registerLocked();
+        waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
+        assertThat(mListener.addedDisplays.size()).isEqualTo(1);
+        DisplayDevice displayDevice = mListener.addedDisplays.get(0);
+
+        // Turn on / initialize
+        Runnable changeStateRunnable = displayDevice.requestDisplayStateLocked(Display.STATE_ON, 0,
+                0);
+        changeStateRunnable.run();
+        waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
+        mListener.changedDisplays.clear();
+
+        DisplayDeviceInfo info = displayDevice.getDisplayDeviceInfoLocked();
+
+        assertThat(info.displayCutout).isNotNull();
+        assertThat(info.displayCutout.getBoundingRectTop()).isEqualTo(new Rect(507, 33, 573, 99));
+        assertThat(info.roundedCorners).isNotNull();
+        assertThat(info.roundedCorners.getRoundedCorner(0).getRadius()).isEqualTo(5);
+    }
+
+    @Test public void test_getDisplayDeviceInfoLocked_externalDisplay_doesNotUseCutoutOrCorners()
+            throws Exception {
+        setupCutoutAndRoundedCorners();
+        FakeDisplay display = new FakeDisplay(PORT_A);
+        display.info.isInternal = false;
+        setUpDisplay(display);
+        updateAvailableDisplays();
+        mAdapter.registerLocked();
+        waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
+        assertThat(mListener.addedDisplays.size()).isEqualTo(1);
+        DisplayDevice displayDevice = mListener.addedDisplays.get(0);
+
+        // Turn on / initialize
+        Runnable changeStateRunnable = displayDevice.requestDisplayStateLocked(Display.STATE_ON, 0,
+                0);
+        changeStateRunnable.run();
+        waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
+        mListener.changedDisplays.clear();
+
+        DisplayDeviceInfo info = displayDevice.getDisplayDeviceInfoLocked();
+
+        assertThat(info.displayCutout).isNull();
+        assertThat(info.roundedCorners).isNull();
+    }
+
+    private void setupCutoutAndRoundedCorners() {
+        String sampleCutout = "M 507,66\n"
+                + "a 33,33 0 1 0 66,0 33,33 0 1 0 -66,0\n"
+                + "Z\n"
+                + "@left\n";
+        // Setup some default cutout
+        when(mMockedResources.getString(
+                com.android.internal.R.string.config_mainBuiltInDisplayCutout))
+                .thenReturn(sampleCutout);
+        when(mMockedResources.getDimensionPixelSize(
+                com.android.internal.R.dimen.rounded_corner_radius)).thenReturn(5);
+    }
+
     private void assertDisplayDpi(DisplayDeviceInfo info, int expectedPort,
                                   float expectedXdpi,
                                   float expectedYDpi,
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java
index 8cfc150..7e638a8 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java
@@ -280,6 +280,7 @@
     @SmallTest
     @Test
     public void testRegisterProxy() throws Exception {
+        when(mProxyManager.displayBelongsToCaller(anyInt(), anyInt())).thenReturn(true);
         mA11yms.registerProxyForDisplay(mMockServiceClient, TEST_DISPLAY);
         verify(mProxyManager).registerProxy(eq(mMockServiceClient), eq(TEST_DISPLAY), anyInt(),
                 eq(mMockSecurityPolicy),
@@ -289,9 +290,9 @@
 
     @SmallTest
     @Test
-    public void testRegisterProxyWithoutA11yPermission() throws Exception {
+    public void testRegisterProxyWithoutA11yPermissionOrRole() throws Exception {
         doThrow(SecurityException.class).when(mMockSecurityPolicy)
-                .enforceCallingOrSelfPermission(Manifest.permission.MANAGE_ACCESSIBILITY);
+                .checkForAccessibilityPermissionOrRole();
 
         assertThrows(SecurityException.class,
                 () -> mA11yms.registerProxyForDisplay(mMockServiceClient, TEST_DISPLAY));
@@ -314,7 +315,7 @@
     @SmallTest
     @Test
     public void testRegisterProxyForDefaultDisplay() throws Exception {
-        assertThrows(IllegalArgumentException.class,
+        assertThrows(SecurityException.class,
                 () -> mA11yms.registerProxyForDisplay(mMockServiceClient, Display.DEFAULT_DISPLAY));
         verify(mProxyManager, never()).registerProxy(any(), anyInt(), anyInt(), any(),
                 any(), any(), any());
@@ -332,6 +333,7 @@
     @SmallTest
     @Test
     public void testUnRegisterProxyWithPermission() throws Exception {
+        when(mProxyManager.displayBelongsToCaller(anyInt(), anyInt())).thenReturn(true);
         mA11yms.registerProxyForDisplay(mMockServiceClient, TEST_DISPLAY);
         mA11yms.unregisterProxyForDisplay(TEST_DISPLAY);
 
@@ -340,9 +342,9 @@
 
     @SmallTest
     @Test
-    public void testUnRegisterProxyWithoutA11yPermission() {
+    public void testUnRegisterProxyWithoutA11yPermissionOrRole() {
         doThrow(SecurityException.class).when(mMockSecurityPolicy)
-                .enforceCallingOrSelfPermission(Manifest.permission.MANAGE_ACCESSIBILITY);
+                .checkForAccessibilityPermissionOrRole();
 
         assertThrows(SecurityException.class,
                 () -> mA11yms.unregisterProxyForDisplay(TEST_DISPLAY));
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationManagerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationManagerTest.java
index b0fd649..c98de7c 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationManagerTest.java
@@ -66,6 +66,7 @@
 import com.android.server.statusbar.StatusBarManagerInternal;
 
 import org.junit.Before;
+import org.junit.Ignore;
 import org.junit.Test;
 import org.mockito.Mock;
 import org.mockito.Mockito;
@@ -307,6 +308,7 @@
                 MagnificationScaleProvider.MAX_SCALE);
     }
 
+    @Ignore("b/278816260: We could refer to b/182561174#comment4 for solution.")
     @Test
     public void logTrackingTypingFocus_processScroll_logDuration() {
         WindowMagnificationManager spyWindowMagnificationManager = spy(mWindowMagnificationManager);
diff --git a/services/tests/servicestests/src/com/android/server/companion/datatransfer/contextsync/CallMetadataSyncDataTest.java b/services/tests/servicestests/src/com/android/server/companion/datatransfer/contextsync/CallMetadataSyncDataTest.java
index 8a107ad..dccc26a 100644
--- a/services/tests/servicestests/src/com/android/server/companion/datatransfer/contextsync/CallMetadataSyncDataTest.java
+++ b/services/tests/servicestests/src/com/android/server/companion/datatransfer/contextsync/CallMetadataSyncDataTest.java
@@ -18,7 +18,7 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
-import android.os.Parcel;
+import android.os.Bundle;
 import android.testing.AndroidTestingRunner;
 
 import org.junit.Test;
@@ -28,7 +28,7 @@
 public class CallMetadataSyncDataTest {
 
     @Test
-    public void call_writeToParcel_fromParcel_reconstructsSuccessfully() {
+    public void call_writeToBundle_fromBundle_reconstructsSuccessfully() {
         final CallMetadataSyncData.Call call = new CallMetadataSyncData.Call();
         final String id = "5";
         final String callerId = "callerId";
@@ -36,6 +36,7 @@
         final String appName = "appName";
         final String appIdentifier = "com.google.test";
         final int status = 1;
+        final int direction = android.companion.Telecom.Call.OUTGOING;
         final int control1 = 2;
         final int control2 = 3;
         call.setId(id);
@@ -45,14 +46,13 @@
                 new CallMetadataSyncData.CallFacilitator(appName, appIdentifier);
         call.setFacilitator(callFacilitator);
         call.setStatus(status);
+        call.setDirection(direction);
         call.addControl(control1);
         call.addControl(control2);
 
-        Parcel parcel = Parcel.obtain();
-        call.writeToParcel(parcel, /* flags= */ 0);
-        parcel.setDataPosition(0);
-        final CallMetadataSyncData.Call reconstructedCall = CallMetadataSyncData.Call.fromParcel(
-                parcel);
+        final Bundle bundle = call.writeToBundle();
+        final CallMetadataSyncData.Call reconstructedCall = CallMetadataSyncData.Call.fromBundle(
+                bundle);
 
         assertThat(reconstructedCall.getId()).isEqualTo(id);
         assertThat(reconstructedCall.getCallerId()).isEqualTo(callerId);
@@ -60,6 +60,7 @@
         assertThat(reconstructedCall.getFacilitator().getName()).isEqualTo(appName);
         assertThat(reconstructedCall.getFacilitator().getIdentifier()).isEqualTo(appIdentifier);
         assertThat(reconstructedCall.getStatus()).isEqualTo(status);
+        assertThat(reconstructedCall.getDirection()).isEqualTo(direction);
         assertThat(reconstructedCall.getControls()).containsExactly(control1, control2);
     }
 }
diff --git a/services/tests/servicestests/src/com/android/server/companion/datatransfer/contextsync/CrossDeviceCallTest.java b/services/tests/servicestests/src/com/android/server/companion/datatransfer/contextsync/CrossDeviceCallTest.java
index 6a939ab..201d8f9 100644
--- a/services/tests/servicestests/src/com/android/server/companion/datatransfer/contextsync/CrossDeviceCallTest.java
+++ b/services/tests/servicestests/src/com/android/server/companion/datatransfer/contextsync/CrossDeviceCallTest.java
@@ -149,6 +149,27 @@
     }
 
     @Test
+    public void updateCallDetails_transitionDialingToOngoing() {
+        final CrossDeviceCall crossDeviceCall = new CrossDeviceCall(
+                InstrumentationRegistry.getTargetContext(),
+                mUninitializedCallDetails, /* callAudioState= */ null);
+        crossDeviceCall.updateCallDetails(createCallDetails(Call.STATE_DIALING,
+                Call.Details.CAPABILITY_HOLD | Call.Details.CAPABILITY_MUTE));
+        assertWithMessage("Wrong status for dialing state").that(crossDeviceCall.getStatus())
+                .isEqualTo(android.companion.Telecom.Call.DIALING);
+        assertWithMessage("Wrong controls for dialing state").that(crossDeviceCall.getControls())
+                .isEqualTo(Set.of(android.companion.Telecom.END));
+        crossDeviceCall.updateCallDetails(createCallDetails(Call.STATE_ACTIVE,
+                Call.Details.CAPABILITY_HOLD | Call.Details.CAPABILITY_MUTE));
+        assertWithMessage("Wrong status for active state").that(crossDeviceCall.getStatus())
+                .isEqualTo(android.companion.Telecom.Call.ONGOING);
+        assertWithMessage("Wrong controls for active state").that(crossDeviceCall.getControls())
+                .isEqualTo(Set.of(android.companion.Telecom.END,
+                        android.companion.Telecom.MUTE,
+                        android.companion.Telecom.PUT_ON_HOLD));
+    }
+
+    @Test
     public void updateSilencedIfRinging_ringing_silenced() {
         final CrossDeviceCall crossDeviceCall = new CrossDeviceCall(
                 InstrumentationRegistry.getTargetContext(),
diff --git a/services/tests/servicestests/src/com/android/server/companion/datatransfer/contextsync/CrossDeviceSyncControllerTest.java b/services/tests/servicestests/src/com/android/server/companion/datatransfer/contextsync/CrossDeviceSyncControllerTest.java
index 7688fcb..7e392a4 100644
--- a/services/tests/servicestests/src/com/android/server/companion/datatransfer/contextsync/CrossDeviceSyncControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/companion/datatransfer/contextsync/CrossDeviceSyncControllerTest.java
@@ -162,13 +162,31 @@
     }
 
     @Test
-    public void createPhoneAccount_success() {
+    public void createPhoneAccount_sip_success() {
         final PhoneAccount phoneAccount =
                 CrossDeviceSyncController.PhoneAccountManager.createPhoneAccount(
                         new PhoneAccountHandle(
                                 new ComponentName("com.google.test", "com.google.test.Activity"),
-                                "id"), "Test App", "com.google.test");
+                                "id"), "Test App", "com.google.test", 1, false);
         assertWithMessage("Could not create phone account").that(phoneAccount).isNotNull();
+        assertWithMessage("Wrong schemes supported; should not support TEL")
+                .that(phoneAccount.supportsUriScheme(PhoneAccount.SCHEME_TEL)).isFalse();
+        assertWithMessage("Wrong schemes supported; should support SIP")
+                .that(phoneAccount.supportsUriScheme(PhoneAccount.SCHEME_SIP)).isTrue();
+    }
+
+    @Test
+    public void createPhoneAccount_tel_success() {
+        final PhoneAccount phoneAccount =
+                CrossDeviceSyncController.PhoneAccountManager.createPhoneAccount(
+                        new PhoneAccountHandle(
+                                new ComponentName("com.google.test", "com.google.test.Activity"),
+                                "id"), "Test App", "com.google.test", 1, true);
+        assertWithMessage("Could not create phone account").that(phoneAccount).isNotNull();
+        assertWithMessage("Wrong schemes supported; should support TEL")
+                .that(phoneAccount.supportsUriScheme(PhoneAccount.SCHEME_TEL)).isTrue();
+        assertWithMessage("Wrong schemes supported; should not support SIP")
+                .that(phoneAccount.supportsUriScheme(PhoneAccount.SCHEME_SIP)).isFalse();
     }
 
     @Test
@@ -214,9 +232,10 @@
     }
 
     @Test
-    public void updateCalls_newCall() {
+    public void updateCalls_newIncomingCall_added() {
         final CallMetadataSyncData.Call call = new CallMetadataSyncData.Call();
         call.setId("123abc");
+        call.setDirection(android.companion.Telecom.Call.INCOMING);
         call.setFacilitator(new CallMetadataSyncData.CallFacilitator("name", "com.android.test"));
         final CallMetadataSyncData callMetadataSyncData = new CallMetadataSyncData();
         callMetadataSyncData.addCall(call);
@@ -225,25 +244,115 @@
                         new CrossDeviceSyncController.PhoneAccountManager(mMockContext));
         callManager.updateCalls(/* associationId= */ 0, callMetadataSyncData);
         verify(mMockTelecomManager, times(1)).addNewIncomingCall(any(), any());
+        verify(mMockTelecomManager, never()).placeCall(any(), any());
     }
 
     @Test
-    public void updateCalls_newCall_noFacilitator() {
+    public void updateCalls_newOutgoingCall_added() {
         final CallMetadataSyncData.Call call = new CallMetadataSyncData.Call();
         call.setId("123abc");
+        call.setDirection(android.companion.Telecom.Call.OUTGOING);
+        call.setFacilitator(new CallMetadataSyncData.CallFacilitator("name", "com.android.test"));
+        call.setCallerId("555-555-5555");
         final CallMetadataSyncData callMetadataSyncData = new CallMetadataSyncData();
         callMetadataSyncData.addCall(call);
         final CrossDeviceSyncController.CallManager callManager =
                 new CrossDeviceSyncController.CallManager(mMockContext,
                         new CrossDeviceSyncController.PhoneAccountManager(mMockContext));
         callManager.updateCalls(/* associationId= */ 0, callMetadataSyncData);
-        verify(mMockTelecomManager, times(0)).addNewIncomingCall(any(), any());
+        verify(mMockTelecomManager, never()).addNewIncomingCall(any(), any());
+        verify(mMockTelecomManager, times(1)).placeCall(any(), any());
     }
 
     @Test
-    public void updateCalls_existingCall() {
+    public void updateCalls_newOutgoingCall_noAddress_notAdded() {
         final CallMetadataSyncData.Call call = new CallMetadataSyncData.Call();
         call.setId("123abc");
+        call.setDirection(android.companion.Telecom.Call.OUTGOING);
+        call.setFacilitator(new CallMetadataSyncData.CallFacilitator("name", "com.android.test"));
+        final CallMetadataSyncData callMetadataSyncData = new CallMetadataSyncData();
+        callMetadataSyncData.addCall(call);
+        final CrossDeviceSyncController.CallManager callManager =
+                new CrossDeviceSyncController.CallManager(mMockContext,
+                        new CrossDeviceSyncController.PhoneAccountManager(mMockContext));
+        callManager.updateCalls(/* associationId= */ 0, callMetadataSyncData);
+        verify(mMockTelecomManager, never()).addNewIncomingCall(any(), any());
+        verify(mMockTelecomManager, never()).placeCall(any(), any());
+    }
+
+    @Test
+    public void updateCalls_newIncomingCall_noFacilitator_notAdded() {
+        final CallMetadataSyncData.Call call = new CallMetadataSyncData.Call();
+        call.setId("123abc");
+        call.setDirection(android.companion.Telecom.Call.INCOMING);
+        final CallMetadataSyncData callMetadataSyncData = new CallMetadataSyncData();
+        callMetadataSyncData.addCall(call);
+        final CrossDeviceSyncController.CallManager callManager =
+                new CrossDeviceSyncController.CallManager(mMockContext,
+                        new CrossDeviceSyncController.PhoneAccountManager(mMockContext));
+        callManager.updateCalls(/* associationId= */ 0, callMetadataSyncData);
+        verify(mMockTelecomManager, never()).addNewIncomingCall(any(), any());
+        verify(mMockTelecomManager, never()).placeCall(any(), any());
+    }
+
+    @Test
+    public void updateCalls_newOutgoingCall_noFacilitator_notAdded() {
+        final CallMetadataSyncData.Call call = new CallMetadataSyncData.Call();
+        call.setId("123abc");
+        call.setDirection(android.companion.Telecom.Call.OUTGOING);
+        call.setCallerId("555-555-5555");
+        final CallMetadataSyncData callMetadataSyncData = new CallMetadataSyncData();
+        callMetadataSyncData.addCall(call);
+        final CrossDeviceSyncController.CallManager callManager =
+                new CrossDeviceSyncController.CallManager(mMockContext,
+                        new CrossDeviceSyncController.PhoneAccountManager(mMockContext));
+        callManager.updateCalls(/* associationId= */ 0, callMetadataSyncData);
+        verify(mMockTelecomManager, never()).addNewIncomingCall(any(), any());
+        verify(mMockTelecomManager, never()).placeCall(any(), any());
+    }
+
+    @Test
+    public void updateCalls_newIncomingCall_isSelfOwned_notAdded() {
+        final CallMetadataSyncData.Call call = new CallMetadataSyncData.Call();
+        call.setId("123abc::originalId");
+        call.setDirection(android.companion.Telecom.Call.INCOMING);
+        call.setFacilitator(new CallMetadataSyncData.CallFacilitator("name", "com.android.test"));
+        final CallMetadataSyncData callMetadataSyncData = new CallMetadataSyncData();
+        callMetadataSyncData.addCall(call);
+        final CrossDeviceSyncController.CallManager callManager =
+                new CrossDeviceSyncController.CallManager(mMockContext,
+                        new CrossDeviceSyncController.PhoneAccountManager(mMockContext));
+        callManager.addSelfOwnedCallId("originalId");
+        callManager.updateCalls(/* associationId= */ 0, callMetadataSyncData);
+        verify(mMockTelecomManager, never()).addNewIncomingCall(any(), any());
+        verify(mMockTelecomManager, never()).placeCall(any(), any());
+    }
+
+
+    @Test
+    public void updateCalls_newOutgoingCall_isSelfOwned_notAdded() {
+        final CallMetadataSyncData.Call call = new CallMetadataSyncData.Call();
+        call.setId("123abc::originalId");
+        call.setDirection(android.companion.Telecom.Call.OUTGOING);
+        call.setFacilitator(new CallMetadataSyncData.CallFacilitator("name", "com.android.test"));
+        call.setCallerId("555-555-5555");
+        final CallMetadataSyncData callMetadataSyncData = new CallMetadataSyncData();
+        callMetadataSyncData.addCall(call);
+        final CrossDeviceSyncController.CallManager callManager =
+                new CrossDeviceSyncController.CallManager(mMockContext,
+                        new CrossDeviceSyncController.PhoneAccountManager(mMockContext));
+        callManager.addSelfOwnedCallId("originalId");
+        callManager.updateCalls(/* associationId= */ 0, callMetadataSyncData);
+        verify(mMockTelecomManager, never()).addNewIncomingCall(any(), any());
+        verify(mMockTelecomManager, never()).placeCall(any(), any());
+    }
+
+    @Test
+    public void updateCalls_existingIncomingCall() {
+        final CallMetadataSyncData.Call call = new CallMetadataSyncData.Call();
+        call.setId("123abc");
+        call.setDirection(android.companion.Telecom.Call.INCOMING);
+        call.setFacilitator(new CallMetadataSyncData.CallFacilitator("name", "com.android.test"));
         final CallMetadataSyncData callMetadataSyncData = new CallMetadataSyncData();
         callMetadataSyncData.addCall(call);
         final CrossDeviceSyncController.CallManager callManager =
@@ -252,6 +361,25 @@
         callManager.mCallIds.put(/* associationId= */ 0, Set.of(call.getId()));
         callManager.updateCalls(/* associationId= */ 0, callMetadataSyncData);
         verify(mMockTelecomManager, never()).addNewIncomingCall(any(), any());
+        verify(mMockTelecomManager, never()).placeCall(any(), any());
+    }
+
+    @Test
+    public void updateCalls_existingOutgoingCall() {
+        final CallMetadataSyncData.Call call = new CallMetadataSyncData.Call();
+        call.setId("123abc");
+        call.setDirection(android.companion.Telecom.Call.OUTGOING);
+        call.setFacilitator(new CallMetadataSyncData.CallFacilitator("name", "com.android.test"));
+        call.setCallerId("555-555-5555");
+        final CallMetadataSyncData callMetadataSyncData = new CallMetadataSyncData();
+        callMetadataSyncData.addCall(call);
+        final CrossDeviceSyncController.CallManager callManager =
+                new CrossDeviceSyncController.CallManager(mMockContext,
+                        new CrossDeviceSyncController.PhoneAccountManager(mMockContext));
+        callManager.mCallIds.put(/* associationId= */ 0, Set.of(call.getId()));
+        callManager.updateCalls(/* associationId= */ 0, callMetadataSyncData);
+        verify(mMockTelecomManager, never()).addNewIncomingCall(any(), any());
+        verify(mMockTelecomManager, never()).placeCall(any(), any());
     }
 
     @Test
@@ -266,6 +394,7 @@
         callManager.mCallIds.put(/* associationId= */ 0, Set.of(call.getId(), "fakeCallId"));
         callManager.updateCalls(/* associationId= */ 0, callMetadataSyncData);
         verify(mMockTelecomManager, never()).addNewIncomingCall(any(), any());
+        verify(mMockTelecomManager, never()).placeCall(any(), any());
         assertWithMessage("Hasn't removed the id of the removed call")
                 .that(callManager.mCallIds)
                 .containsExactly(/* associationId= */ 0, Set.of(call.getId()));
diff --git a/services/tests/servicestests/src/com/android/server/contentcapture/ContentCaptureManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/contentcapture/ContentCaptureManagerServiceTest.java
index c872a11..0e92d22 100644
--- a/services/tests/servicestests/src/com/android/server/contentcapture/ContentCaptureManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/contentcapture/ContentCaptureManagerServiceTest.java
@@ -18,8 +18,16 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.verifyZeroInteractions;
 import static org.mockito.Mockito.when;
 
+import android.annotation.NonNull;
+import android.content.ComponentName;
 import android.content.ContentCaptureOptions;
 import android.content.Context;
 import android.content.pm.UserInfo;
@@ -29,6 +37,7 @@
 import androidx.test.filters.SmallTest;
 
 import com.android.server.LocalServices;
+import com.android.server.contentprotection.ContentProtectionBlocklistManager;
 import com.android.server.pm.UserManagerInternal;
 
 import com.google.common.collect.ImmutableList;
@@ -56,12 +65,21 @@
 
     private static final String PACKAGE_NAME = "com.test.package";
 
+    private static final ComponentName COMPONENT_NAME =
+            new ComponentName(PACKAGE_NAME, "TestClass");
+
     private static final Context sContext = ApplicationProvider.getApplicationContext();
 
     @Rule public final MockitoRule mMockitoRule = MockitoJUnit.rule();
 
     @Mock private UserManagerInternal mMockUserManagerInternal;
 
+    @Mock private ContentProtectionBlocklistManager mMockContentProtectionBlocklistManager;
+
+    private boolean mDevCfgEnableContentProtectionReceiver;
+
+    private int mContentProtectionBlocklistManagersCreated;
+
     private ContentCaptureManagerService mContentCaptureManagerService;
 
     @Before
@@ -69,46 +87,235 @@
         when(mMockUserManagerInternal.getUserInfos()).thenReturn(new UserInfo[0]);
         LocalServices.removeServiceForTest(UserManagerInternal.class);
         LocalServices.addService(UserManagerInternal.class, mMockUserManagerInternal);
-        mContentCaptureManagerService = new ContentCaptureManagerService(sContext);
+        mContentCaptureManagerService = new TestContentCaptureManagerService();
     }
 
     @Test
-    public void getOptions_notAllowlisted() {
+    public void constructor_default_doesNotCreateContentProtectionBlocklistManager() {
+        assertThat(mContentProtectionBlocklistManagersCreated).isEqualTo(0);
+        verifyZeroInteractions(mMockContentProtectionBlocklistManager);
+    }
+
+    @Test
+    public void constructor_flagDisabled_doesNotContentProtectionBlocklistManager() {
+        assertThat(mContentProtectionBlocklistManagersCreated).isEqualTo(0);
+        verifyZeroInteractions(mMockContentProtectionBlocklistManager);
+    }
+
+    @Test
+    public void constructor_flagEnabled_createsContentProtectionBlocklistManager() {
+        mDevCfgEnableContentProtectionReceiver = true;
+
+        mContentCaptureManagerService = new TestContentCaptureManagerService();
+
+        assertThat(mContentProtectionBlocklistManagersCreated).isEqualTo(1);
+        verify(mMockContentProtectionBlocklistManager).updateBlocklist(anyInt());
+    }
+
+    @Test
+    public void setFineTuneParamsFromDeviceConfig_doesNotUpdateContentProtectionBlocklist() {
+        mDevCfgEnableContentProtectionReceiver = true;
+        mContentCaptureManagerService = new TestContentCaptureManagerService();
+        mContentCaptureManagerService.mDevCfgContentProtectionAppsBlocklistSize += 100;
+        verify(mMockContentProtectionBlocklistManager).updateBlocklist(anyInt());
+
+        mContentCaptureManagerService.setFineTuneParamsFromDeviceConfig();
+
+        verifyNoMoreInteractions(mMockContentProtectionBlocklistManager);
+    }
+
+    @Test
+    public void getOptions_contentCaptureDisabled_contentProtectionDisabled() {
+        mDevCfgEnableContentProtectionReceiver = true;
+        mContentCaptureManagerService = new TestContentCaptureManagerService();
+
         ContentCaptureOptions actual =
                 mContentCaptureManagerService.mGlobalContentCaptureOptions.getOptions(
                         USER_ID, PACKAGE_NAME);
 
         assertThat(actual).isNull();
+        verify(mMockContentProtectionBlocklistManager).isAllowed(PACKAGE_NAME);
     }
 
     @Test
-    public void getOptions_allowlisted_contentCaptureReceiver() {
-        mContentCaptureManagerService.mGlobalContentCaptureOptions.setWhitelist(
-                USER_ID, ImmutableList.of(PACKAGE_NAME), /* components= */ null);
+    public void getOptions_contentCaptureDisabled_contentProtectionEnabled() {
+        when(mMockContentProtectionBlocklistManager.isAllowed(PACKAGE_NAME)).thenReturn(true);
+        mDevCfgEnableContentProtectionReceiver = true;
+        mContentCaptureManagerService = new TestContentCaptureManagerService();
 
         ContentCaptureOptions actual =
                 mContentCaptureManagerService.mGlobalContentCaptureOptions.getOptions(
                         USER_ID, PACKAGE_NAME);
 
         assertThat(actual).isNotNull();
-        assertThat(actual.enableReceiver).isTrue();
-        assertThat(actual.contentProtectionOptions.enableReceiver).isFalse();
-        assertThat(actual.whitelistedComponents).isNull();
-    }
-
-    @Test
-    public void getOptions_allowlisted_bothReceivers() {
-        mContentCaptureManagerService.mDevCfgEnableContentProtectionReceiver = true;
-        mContentCaptureManagerService.mGlobalContentCaptureOptions.setWhitelist(
-                USER_ID, ImmutableList.of(PACKAGE_NAME), /* components= */ null);
-
-        ContentCaptureOptions actual =
-                mContentCaptureManagerService.mGlobalContentCaptureOptions.getOptions(
-                        USER_ID, PACKAGE_NAME);
-
-        assertThat(actual).isNotNull();
-        assertThat(actual.enableReceiver).isTrue();
+        assertThat(actual.enableReceiver).isFalse();
+        assertThat(actual.contentProtectionOptions).isNotNull();
         assertThat(actual.contentProtectionOptions.enableReceiver).isTrue();
         assertThat(actual.whitelistedComponents).isNull();
     }
+
+    @Test
+    public void getOptions_contentCaptureEnabled_contentProtectionDisabled() {
+        mDevCfgEnableContentProtectionReceiver = true;
+        mContentCaptureManagerService = new TestContentCaptureManagerService();
+        mContentCaptureManagerService.mGlobalContentCaptureOptions.setWhitelist(
+                USER_ID, ImmutableList.of(PACKAGE_NAME), /* components= */ null);
+
+        ContentCaptureOptions actual =
+                mContentCaptureManagerService.mGlobalContentCaptureOptions.getOptions(
+                        USER_ID, PACKAGE_NAME);
+
+        assertThat(actual).isNotNull();
+        assertThat(actual.enableReceiver).isTrue();
+        assertThat(actual.contentProtectionOptions).isNotNull();
+        assertThat(actual.contentProtectionOptions.enableReceiver).isFalse();
+        assertThat(actual.whitelistedComponents).isNull();
+        verify(mMockContentProtectionBlocklistManager).isAllowed(PACKAGE_NAME);
+    }
+
+    @Test
+    public void getOptions_contentCaptureEnabled_contentProtectionEnabled() {
+        when(mMockContentProtectionBlocklistManager.isAllowed(PACKAGE_NAME)).thenReturn(true);
+        mDevCfgEnableContentProtectionReceiver = true;
+        mContentCaptureManagerService = new TestContentCaptureManagerService();
+        mContentCaptureManagerService.mGlobalContentCaptureOptions.setWhitelist(
+                USER_ID, ImmutableList.of(PACKAGE_NAME), /* components= */ null);
+
+        ContentCaptureOptions actual =
+                mContentCaptureManagerService.mGlobalContentCaptureOptions.getOptions(
+                        USER_ID, PACKAGE_NAME);
+
+        assertThat(actual).isNotNull();
+        assertThat(actual.enableReceiver).isTrue();
+        assertThat(actual.contentProtectionOptions).isNotNull();
+        assertThat(actual.contentProtectionOptions.enableReceiver).isTrue();
+        assertThat(actual.whitelistedComponents).isNull();
+    }
+
+    @Test
+    public void isWhitelisted_packageName_contentCaptureDisabled_contentProtectionDisabled() {
+        mDevCfgEnableContentProtectionReceiver = true;
+        mContentCaptureManagerService = new TestContentCaptureManagerService();
+
+        boolean actual =
+                mContentCaptureManagerService.mGlobalContentCaptureOptions.isWhitelisted(
+                        USER_ID, PACKAGE_NAME);
+
+        assertThat(actual).isFalse();
+        verify(mMockContentProtectionBlocklistManager).isAllowed(PACKAGE_NAME);
+    }
+
+    @Test
+    public void isWhitelisted_packageName_contentCaptureDisabled_contentProtectionEnabled() {
+        when(mMockContentProtectionBlocklistManager.isAllowed(PACKAGE_NAME)).thenReturn(true);
+        mDevCfgEnableContentProtectionReceiver = true;
+        mContentCaptureManagerService = new TestContentCaptureManagerService();
+
+        boolean actual =
+                mContentCaptureManagerService.mGlobalContentCaptureOptions.isWhitelisted(
+                        USER_ID, PACKAGE_NAME);
+
+        assertThat(actual).isTrue();
+    }
+
+    @Test
+    public void isWhitelisted_packageName_contentCaptureEnabled_contentProtectionNotChecked() {
+        mDevCfgEnableContentProtectionReceiver = true;
+        mContentCaptureManagerService = new TestContentCaptureManagerService();
+        mContentCaptureManagerService.mGlobalContentCaptureOptions.setWhitelist(
+                USER_ID, ImmutableList.of(PACKAGE_NAME), /* components= */ null);
+
+        boolean actual =
+                mContentCaptureManagerService.mGlobalContentCaptureOptions.isWhitelisted(
+                        USER_ID, PACKAGE_NAME);
+
+        assertThat(actual).isTrue();
+        verify(mMockContentProtectionBlocklistManager, never()).isAllowed(anyString());
+    }
+
+    @Test
+    public void isWhitelisted_componentName_contentCaptureDisabled_contentProtectionDisabled() {
+        mDevCfgEnableContentProtectionReceiver = true;
+        mContentCaptureManagerService = new TestContentCaptureManagerService();
+
+        boolean actual =
+                mContentCaptureManagerService.mGlobalContentCaptureOptions.isWhitelisted(
+                        USER_ID, COMPONENT_NAME);
+
+        assertThat(actual).isFalse();
+        verify(mMockContentProtectionBlocklistManager).isAllowed(PACKAGE_NAME);
+    }
+
+    @Test
+    public void isWhitelisted_componentName_contentCaptureDisabled_contentProtectionEnabled() {
+        when(mMockContentProtectionBlocklistManager.isAllowed(PACKAGE_NAME)).thenReturn(true);
+        mDevCfgEnableContentProtectionReceiver = true;
+        mContentCaptureManagerService = new TestContentCaptureManagerService();
+
+        boolean actual =
+                mContentCaptureManagerService.mGlobalContentCaptureOptions.isWhitelisted(
+                        USER_ID, COMPONENT_NAME);
+
+        assertThat(actual).isTrue();
+    }
+
+    @Test
+    public void isWhitelisted_componentName_contentCaptureEnabled_contentProtectionNotChecked() {
+        mDevCfgEnableContentProtectionReceiver = true;
+        mContentCaptureManagerService = new TestContentCaptureManagerService();
+        mContentCaptureManagerService.mGlobalContentCaptureOptions.setWhitelist(
+                USER_ID, /* packageNames= */ null, ImmutableList.of(COMPONENT_NAME));
+
+        boolean actual =
+                mContentCaptureManagerService.mGlobalContentCaptureOptions.isWhitelisted(
+                        USER_ID, COMPONENT_NAME);
+
+        assertThat(actual).isTrue();
+        verify(mMockContentProtectionBlocklistManager, never()).isAllowed(anyString());
+    }
+
+    @Test
+    public void isContentProtectionReceiverEnabled_withoutBlocklistManager() {
+        boolean actual =
+                mContentCaptureManagerService.mGlobalContentCaptureOptions.isWhitelisted(
+                        USER_ID, PACKAGE_NAME);
+
+        assertThat(actual).isFalse();
+        verify(mMockContentProtectionBlocklistManager, never()).isAllowed(anyString());
+    }
+
+    @Test
+    public void isContentProtectionReceiverEnabled_disabledWithFlag() {
+        mDevCfgEnableContentProtectionReceiver = true;
+        mContentCaptureManagerService = new TestContentCaptureManagerService();
+        mContentCaptureManagerService.mDevCfgEnableContentProtectionReceiver = false;
+
+        boolean actual =
+                mContentCaptureManagerService.mGlobalContentCaptureOptions.isWhitelisted(
+                        USER_ID, PACKAGE_NAME);
+
+        assertThat(actual).isFalse();
+        verify(mMockContentProtectionBlocklistManager, never()).isAllowed(anyString());
+    }
+
+    private class TestContentCaptureManagerService extends ContentCaptureManagerService {
+
+        TestContentCaptureManagerService() {
+            super(sContext);
+            this.mDevCfgEnableContentProtectionReceiver =
+                    ContentCaptureManagerServiceTest.this.mDevCfgEnableContentProtectionReceiver;
+        }
+
+        @Override
+        protected boolean getEnableContentProtectionReceiverLocked() {
+            return ContentCaptureManagerServiceTest.this.mDevCfgEnableContentProtectionReceiver;
+        }
+
+        @Override
+        protected ContentProtectionBlocklistManager createContentProtectionBlocklistManager(
+                @NonNull Context context) {
+            mContentProtectionBlocklistManagersCreated++;
+            return mMockContentProtectionBlocklistManager;
+        }
+    }
 }
diff --git a/services/tests/servicestests/src/com/android/server/contentprotection/OWNERS b/services/tests/servicestests/src/com/android/server/contentprotection/OWNERS
new file mode 100644
index 0000000..24561c5
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/contentprotection/OWNERS
@@ -0,0 +1,3 @@
+# Bug component: 544200
+
+include /core/java/android/view/contentcapture/OWNERS
diff --git a/services/tests/servicestests/src/com/android/server/contentprotection/RemoteContentProtectionServiceTest.java b/services/tests/servicestests/src/com/android/server/contentprotection/RemoteContentProtectionServiceTest.java
new file mode 100644
index 0000000..2f57fd3
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/contentprotection/RemoteContentProtectionServiceTest.java
@@ -0,0 +1,127 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.contentprotection;
+
+import static android.view.contentcapture.ContentCaptureSession.FLUSH_REASON_LOGIN_DETECTED;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.fail;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyZeroInteractions;
+
+import android.annotation.NonNull;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.pm.ParceledListSlice;
+import android.os.UserHandle;
+import android.view.contentcapture.ContentCaptureEvent;
+import android.view.contentcapture.IContentCaptureDirectManager;
+
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import com.android.internal.infra.AndroidFuture;
+import com.android.internal.infra.ServiceConnector;
+
+import com.google.common.collect.ImmutableList;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+/**
+ * Test for {@link RemoteContentProtectionService}.
+ *
+ * <p>Run with: {@code atest
+ * FrameworksServicesTests:com.android.server.contentprotection.RemoteContentProtectionServiceTest}
+ */
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class RemoteContentProtectionServiceTest {
+
+    private final Context mContext = ApplicationProvider.getApplicationContext();
+
+    @Rule public final MockitoRule mMockitoRule = MockitoJUnit.rule();
+
+    @Mock private IContentCaptureDirectManager mMockContentCaptureDirectManager;
+
+    private RemoteContentProtectionService mRemoteContentProtectionService;
+
+    private int mConnectCallCount = 0;
+
+    @Before
+    public void setup() {
+        ComponentName componentName = new ComponentName(mContext.getPackageName(), "TestClass");
+        mRemoteContentProtectionService =
+                new TestRemoteContentProtectionService(mContext, componentName);
+    }
+
+    @Test
+    public void doesNotAutoConnect() {
+        assertThat(mConnectCallCount).isEqualTo(0);
+        verifyZeroInteractions(mMockContentCaptureDirectManager);
+    }
+
+    @Test
+    public void getAutoDisconnectTimeoutMs() {
+        long actual = mRemoteContentProtectionService.getAutoDisconnectTimeoutMs();
+
+        assertThat(actual).isEqualTo(3000L);
+    }
+
+    @Test
+    public void onLoginDetected() throws Exception {
+        ContentCaptureEvent event =
+                new ContentCaptureEvent(/* sessionId= */ 1111, /* type= */ 2222);
+        ParceledListSlice<ContentCaptureEvent> events =
+                new ParceledListSlice<>(ImmutableList.of(event));
+
+        mRemoteContentProtectionService.onLoginDetected(events);
+
+        verify(mMockContentCaptureDirectManager)
+                .sendEvents(events, FLUSH_REASON_LOGIN_DETECTED, /* options= */ null);
+    }
+
+    private final class TestRemoteContentProtectionService extends RemoteContentProtectionService {
+
+        TestRemoteContentProtectionService(Context context, ComponentName componentName) {
+            super(context, componentName, UserHandle.myUserId(), /* bindAllowInstant= */ false);
+        }
+
+        @Override // from ServiceConnector
+        public synchronized AndroidFuture<IContentCaptureDirectManager> connect() {
+            mConnectCallCount++;
+            return AndroidFuture.completedFuture(mMockContentCaptureDirectManager);
+        }
+
+        @Override // from ServiceConnector
+        public boolean run(@NonNull ServiceConnector.VoidJob<IContentCaptureDirectManager> job) {
+            try {
+                job.run(mMockContentCaptureDirectManager);
+            } catch (Exception ex) {
+                fail("Unexpected exception: " + ex);
+            }
+            return true;
+        }
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
index 39de2cf..99a3b80 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
@@ -7842,7 +7842,7 @@
             throws Exception {
         int validLockTaskFeatures = LOCK_TASK_FEATURE_SYSTEM_INFO | LOCK_TASK_FEATURE_KEYGUARD
                 | LOCK_TASK_FEATURE_HOME | LOCK_TASK_FEATURE_GLOBAL_ACTIONS
-                | LOCK_TASK_FEATURE_NOTIFICATIONS;
+                | LOCK_TASK_FEATURE_NOTIFICATIONS | LOCK_TASK_FEATURE_BLOCK_ACTIVITY_START_IN_TASK;
         setDeviceOwner();
         dpm.setDeviceOwnerType(admin1, DEVICE_OWNER_TYPE_FINANCED);
 
@@ -7857,7 +7857,7 @@
     public void testSetLockTaskFeatures_financeDo_invalidLockTaskFeatures_throwsException()
             throws Exception {
         int invalidLockTaskFeatures = LOCK_TASK_FEATURE_NONE | LOCK_TASK_FEATURE_OVERVIEW
-                | LOCK_TASK_FEATURE_HOME | LOCK_TASK_FEATURE_BLOCK_ACTIVITY_START_IN_TASK;
+                | LOCK_TASK_FEATURE_HOME;
         setDeviceOwner();
         dpm.setDeviceOwnerType(admin1, DEVICE_OWNER_TYPE_FINANCED);
         // Called during setup.
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTvTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTvTest.java
new file mode 100644
index 0000000..920c376
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTvTest.java
@@ -0,0 +1,142 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.hdmi;
+
+import static com.android.server.SystemService.PHASE_SYSTEM_SERVICES_READY;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.Context;
+import android.hardware.hdmi.HdmiDeviceInfo;
+import android.os.Looper;
+import android.os.test.TestLooper;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.SmallTest;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.util.Collections;
+
+/**
+ * TV specific tests for {@link HdmiControlService} class.
+ */
+@SmallTest
+@Presubmit
+@RunWith(JUnit4.class)
+public class HdmiControlServiceTvTest {
+
+    private static final String TAG = "HdmiControlServiceTvTest";
+    private HdmiControlService mHdmiControlService;
+    private HdmiCecController mHdmiCecController;
+    private FakeNativeWrapper mNativeWrapper;
+    private HdmiEarcController mHdmiEarcController;
+    private FakeEarcNativeWrapper mEarcNativeWrapper;
+    private Looper mMyLooper;
+    private TestLooper mTestLooper = new TestLooper();
+
+    @Before
+    public void setUp() throws Exception {
+        Context context = InstrumentationRegistry.getTargetContext();
+        mMyLooper = mTestLooper.getLooper();
+
+        FakeAudioFramework audioFramework = new FakeAudioFramework();
+
+        mHdmiControlService =
+                new HdmiControlService(InstrumentationRegistry.getTargetContext(),
+                        Collections.singletonList(HdmiDeviceInfo.DEVICE_TV),
+                        audioFramework.getAudioManager(),
+                        audioFramework.getAudioDeviceVolumeManager()) {
+                    @Override
+                    int pathToPortId(int path) {
+                        return Constants.INVALID_PORT_ID + 1;
+                    }
+                };
+
+        mMyLooper = mTestLooper.getLooper();
+        mHdmiControlService.setIoLooper(mMyLooper);
+        mHdmiControlService.setHdmiCecConfig(new FakeHdmiCecConfig(context));
+        mHdmiControlService.setDeviceConfig(new FakeDeviceConfigWrapper());
+        mHdmiControlService.onBootPhase(PHASE_SYSTEM_SERVICES_READY);
+
+        mNativeWrapper = new FakeNativeWrapper();
+        mHdmiCecController = HdmiCecController.createWithNativeWrapper(
+                mHdmiControlService, mNativeWrapper, mHdmiControlService.getAtomWriter());
+        mHdmiControlService.setCecController(mHdmiCecController);
+        mEarcNativeWrapper = new FakeEarcNativeWrapper();
+        mHdmiEarcController = HdmiEarcController.createWithNativeWrapper(
+                mHdmiControlService, mEarcNativeWrapper);
+        mHdmiControlService.setEarcController(mHdmiEarcController);
+        mHdmiControlService.setHdmiMhlController(HdmiMhlControllerStub.create(
+                mHdmiControlService));
+        mHdmiControlService.initService();
+
+        mTestLooper.dispatchAll();
+    }
+
+    @Test
+    public void onCecMessage_shortPhysicalAddress_featureAbortInvalidOperand() {
+        // Invalid <Inactive Source> message.
+        HdmiCecMessage message = HdmiUtils.buildMessage("40:9D:14");
+
+        mNativeWrapper.onCecMessage(message);
+        mTestLooper.dispatchAll();
+
+        HdmiCecMessage featureAbort = HdmiCecMessageBuilder.buildFeatureAbortCommand(
+                Constants.ADDR_TV, Constants.ADDR_PLAYBACK_1, Constants.MESSAGE_INACTIVE_SOURCE,
+                Constants.ABORT_INVALID_OPERAND);
+        assertThat(mNativeWrapper.getResultMessages()).contains(featureAbort);
+    }
+
+    @Test
+    public void handleCecCommand_shortPhysicalAddress_returnsAbortInvalidOperand() {
+        // Invalid <Active Source> message.
+        HdmiCecMessage message = HdmiUtils.buildMessage("4F:82:10");
+
+        // In case of a broadcasted message <Feature Abort> is not expected.
+        // See CEC 1.4b specification, 12.2 Protocol General Rules for detail.
+        assertThat(mHdmiControlService.handleCecCommand(message))
+                .isEqualTo(Constants.ABORT_INVALID_OPERAND);
+    }
+
+    @Test
+    public void test_verifyPhysicalAddresses() {
+        // <Routing Change>
+        assertThat(mHdmiControlService
+                .verifyPhysicalAddresses(HdmiUtils.buildMessage("0F:80:10:00:40:00"))).isTrue();
+        assertThat(mHdmiControlService
+                .verifyPhysicalAddresses(HdmiUtils.buildMessage("0F:80:10:00:40"))).isFalse();
+        assertThat(mHdmiControlService
+                .verifyPhysicalAddresses(HdmiUtils.buildMessage("0F:80:10"))).isFalse();
+
+        // <System Audio Mode Request>
+        assertThat(mHdmiControlService
+                .verifyPhysicalAddresses(HdmiUtils.buildMessage("40:70:00:00"))).isTrue();
+        assertThat(mHdmiControlService
+                .verifyPhysicalAddresses(HdmiUtils.buildMessage("40:70:00"))).isFalse();
+
+        // <Active Source>
+        assertThat(mHdmiControlService
+                .verifyPhysicalAddresses(HdmiUtils.buildMessage("4F:82:10:00"))).isTrue();
+        assertThat(mHdmiControlService
+                .verifyPhysicalAddresses(HdmiUtils.buildMessage("4F:82:10"))).isFalse();
+    }
+}
diff --git a/telecomm/java/android/telecom/ParcelableConference.java b/telecomm/java/android/telecom/ParcelableConference.java
index e57c833..6dcfa6d 100644
--- a/telecomm/java/android/telecom/ParcelableConference.java
+++ b/telecomm/java/android/telecom/ParcelableConference.java
@@ -21,12 +21,12 @@
 import android.os.Parcel;
 import android.os.Parcelable;
 
+import com.android.internal.telecom.IVideoProvider;
+
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
 
-import com.android.internal.telecom.IVideoProvider;
-
 /**
  * A parcelable representation of a conference connection.
  * @hide
@@ -287,6 +287,14 @@
         return mCallDirection;
     }
 
+    public String getCallerDisplayName() {
+        return mCallerDisplayName;
+    }
+
+    public int getCallerDisplayNamePresentation() {
+        return mCallerDisplayNamePresentation;
+    }
+
     public static final @android.annotation.NonNull Parcelable.Creator<ParcelableConference> CREATOR =
             new Parcelable.Creator<ParcelableConference> () {
         @Override
diff --git a/telecomm/java/android/telecom/StatusHints.java b/telecomm/java/android/telecom/StatusHints.java
index 2faecc2..5f0c8d72 100644
--- a/telecomm/java/android/telecom/StatusHints.java
+++ b/telecomm/java/android/telecom/StatusHints.java
@@ -16,14 +16,19 @@
 
 package android.telecom;
 
+import android.annotation.Nullable;
 import android.annotation.SystemApi;
 import android.content.ComponentName;
 import android.content.Context;
 import android.graphics.drawable.Drawable;
 import android.graphics.drawable.Icon;
+import android.os.Binder;
 import android.os.Bundle;
 import android.os.Parcel;
 import android.os.Parcelable;
+import android.os.UserHandle;
+
+import com.android.internal.annotations.VisibleForTesting;
 
 import java.util.Objects;
 
@@ -33,7 +38,7 @@
 public final class StatusHints implements Parcelable {
 
     private final CharSequence mLabel;
-    private final Icon mIcon;
+    private Icon mIcon;
     private final Bundle mExtras;
 
     /**
@@ -48,11 +53,31 @@
 
     public StatusHints(CharSequence label, Icon icon, Bundle extras) {
         mLabel = label;
-        mIcon = icon;
+        mIcon = validateAccountIconUserBoundary(icon, Binder.getCallingUserHandle());
         mExtras = extras;
     }
 
     /**
+     * @param icon
+     * @hide
+     */
+    @VisibleForTesting
+    public StatusHints(@Nullable Icon icon) {
+        mLabel = null;
+        mExtras = null;
+        mIcon = icon;
+    }
+
+    /**
+     *
+     * @param icon
+     * @hide
+     */
+    public void setIcon(@Nullable Icon icon) {
+        mIcon = icon;
+    }
+
+    /**
      * @return A package used to load the icon.
      *
      * @hide
@@ -112,6 +137,30 @@
         return 0;
     }
 
+    /**
+     * Validates the StatusHints image icon to see if it's not in the calling user space.
+     * Invalidates the icon if so, otherwise returns back the original icon.
+     *
+     * @param icon
+     * @return icon (validated)
+     * @hide
+     */
+    public static Icon validateAccountIconUserBoundary(Icon icon, UserHandle callingUserHandle) {
+        // Refer to Icon#getUriString for context. The URI string is invalid for icons of
+        // incompatible types.
+        if (icon != null && (icon.getType() == Icon.TYPE_URI
+                || icon.getType() == Icon.TYPE_URI_ADAPTIVE_BITMAP)) {
+            String encodedUser = icon.getUri().getEncodedUserInfo();
+            // If there is no encoded user, the URI is calling into the calling user space
+            if (encodedUser != null) {
+                int userId = Integer.parseInt(encodedUser);
+                // Do not try to save the icon if the user id isn't in the calling user space.
+                if (userId != callingUserHandle.getIdentifier()) return null;
+            }
+        }
+        return icon;
+    }
+
     @Override
     public void writeToParcel(Parcel out, int flags) {
         out.writeCharSequence(mLabel);
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/open/MainActivityStartsSecondaryWithAlwaysExpandTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/open/MainActivityStartsSecondaryWithAlwaysExpandTest.kt
new file mode 100644
index 0000000..00316ea
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/open/MainActivityStartsSecondaryWithAlwaysExpandTest.kt
@@ -0,0 +1,140 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.flicker.activityembedding
+
+import android.platform.test.annotations.Presubmit
+import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
+import android.tools.device.flicker.legacy.FlickerBuilder
+import android.tools.device.flicker.legacy.FlickerTest
+import android.tools.device.flicker.legacy.FlickerTestFactory
+import androidx.test.filters.RequiresDevice
+import com.android.server.wm.flicker.helpers.ActivityEmbeddingAppHelper
+import org.junit.FixMethodOrder
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+import android.tools.common.datatypes.Rect
+
+/**
+ * Test launching an activity with AlwaysExpand rule.
+ *
+ * Setup: Launch A|B in split with B being the secondary activity.
+ * Transitions:
+ * A start C with alwaysExpand=true, expect C to launch in fullscreen and cover split A|B.
+ *
+ * To run this test: `atest FlickerTests:MainActivityStartsSecondaryWithAlwaysExpandTest`
+ */
+@RequiresDevice
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+class MainActivityStartsSecondaryWithAlwaysExpandTest(flicker: FlickerTest) :
+  ActivityEmbeddingTestBase(flicker) {
+
+  /** {@inheritDoc} */
+  override val transition: FlickerBuilder.() -> Unit = {
+    setup {
+      tapl.setExpectedRotationCheckEnabled(false)
+      // Launch a split
+      testApp.launchViaIntent(wmHelper)
+      testApp.launchSecondaryActivity(wmHelper)
+      startDisplayBounds =
+        wmHelper.currentState.layerState.physicalDisplayBounds ?: error("Display not found")
+    }
+    transitions {
+      // Launch C with alwaysExpand
+      testApp.launchAlwaysExpandActivity(wmHelper)
+    }
+    teardown {
+      tapl.goHome()
+      testApp.exit(wmHelper)
+    }
+  }
+
+  /** Transition begins with a split. */
+  @Presubmit
+  @Test
+  fun startsWithSplit() {
+    flicker.assertWmStart {
+      this.isAppWindowVisible(ActivityEmbeddingAppHelper.MAIN_ACTIVITY_COMPONENT)
+    }
+    flicker.assertWmStart {
+      this.isAppWindowVisible(ActivityEmbeddingAppHelper.SECONDARY_ACTIVITY_COMPONENT)
+    }
+  }
+
+
+  /** Main activity should become invisible after being covered by always expand activity. */
+  @Presubmit
+  @Test
+  fun mainActivityLayerBecomesInvisible() {
+    flicker.assertLayers {
+      isVisible(ActivityEmbeddingAppHelper.MAIN_ACTIVITY_COMPONENT)
+        .then()
+        .isInvisible(ActivityEmbeddingAppHelper.MAIN_ACTIVITY_COMPONENT)
+    }
+  }
+
+  /** Secondary activity should become invisible after being covered by always expand activity. */
+  @Presubmit
+  @Test
+  fun secondaryActivityLayerBecomesInvisible() {
+    flicker.assertLayers {
+      isVisible(ActivityEmbeddingAppHelper.SECONDARY_ACTIVITY_COMPONENT)
+        .then()
+        .isInvisible(ActivityEmbeddingAppHelper.SECONDARY_ACTIVITY_COMPONENT)
+    }
+  }
+
+  /** At the end of transition always expand activity is in fullscreen. */
+  @Presubmit
+  @Test
+  fun endsWithAlwaysExpandActivityCoveringFullScreen() {
+    flicker.assertWmEnd {
+      this.visibleRegion(ActivityEmbeddingAppHelper.ALWAYS_EXPAND_ACTIVITY_COMPONENT)
+        .coversExactly(startDisplayBounds)
+    }
+  }
+
+  /** Always expand activity is on top of the split. */
+  @Presubmit
+  @Test
+  fun endsWithAlwaysExpandActivityOnTop() {
+    flicker.assertWmEnd {
+      this.isAppWindowOnTop(
+        ActivityEmbeddingAppHelper.ALWAYS_EXPAND_ACTIVITY_COMPONENT)
+    }
+  }
+
+  companion object {
+    /** {@inheritDoc} */
+    private var startDisplayBounds = Rect.EMPTY
+    /**
+     * Creates the test configurations.
+     *
+     * See [FlickerTestFactory.nonRotationTests] for configuring screen orientation and
+     * navigation modes.
+     */
+    @Parameterized.Parameters(name = "{0}")
+    @JvmStatic
+    fun getParams(): Collection<FlickerTest> {
+      return FlickerTestFactory.nonRotationTests()
+    }
+  }
+}
+
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/OpenActivityEmbeddingPlaceholderSplitTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/open/OpenActivityEmbeddingPlaceholderSplitTest.kt
similarity index 100%
rename from tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/OpenActivityEmbeddingPlaceholderSplitTest.kt
rename to tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/open/OpenActivityEmbeddingPlaceholderSplitTest.kt
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/OpenActivityEmbeddingSecondaryToSplitTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/open/OpenActivityEmbeddingSecondaryToSplitTest.kt
similarity index 100%
rename from tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/OpenActivityEmbeddingSecondaryToSplitTest.kt
rename to tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/open/OpenActivityEmbeddingSecondaryToSplitTest.kt
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ActivityEmbeddingAppHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ActivityEmbeddingAppHelper.kt
index e019b2b..a21965e 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ActivityEmbeddingAppHelper.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ActivityEmbeddingAppHelper.kt
@@ -75,6 +75,29 @@
             .StateSyncBuilder()
             .withActivityRemoved(SECONDARY_ACTIVITY_COMPONENT)
             .waitForAndVerify()
+     }
+
+    /**
+     * Clicks the button to launch a secondary activity with alwaysExpand enabled, which will launch
+     * a fullscreen window on top of the visible region.
+     */
+    fun launchAlwaysExpandActivity(wmHelper: WindowManagerStateHelper) {
+        val launchButton =
+                uiDevice.wait(
+                        Until.findObject(
+                                By.res(getPackage(),
+                                        "launch_always_expand_activity_button")),
+                        FIND_TIMEOUT
+                )
+        require(launchButton != null) {
+            "Can't find launch always expand activity button on screen."
+        }
+        launchButton.click()
+        wmHelper
+                .StateSyncBuilder()
+                .withActivityState(ALWAYS_EXPAND_ACTIVITY_COMPONENT, PlatformConsts.STATE_RESUMED)
+                .withActivityState(MAIN_ACTIVITY_COMPONENT, PlatformConsts.STATE_PAUSED)
+                .waitForAndVerify()
     }
 
     /**
@@ -105,6 +128,9 @@
         val SECONDARY_ACTIVITY_COMPONENT =
             ActivityOptions.ActivityEmbedding.SecondaryActivity.COMPONENT.toFlickerComponent()
 
+        val ALWAYS_EXPAND_ACTIVITY_COMPONENT =
+            ActivityOptions.ActivityEmbedding.AlwaysExpandActivity.COMPONENT.toFlickerComponent()
+
         val PLACEHOLDER_PRIMARY_COMPONENT =
             ActivityOptions.ActivityEmbedding.PlaceholderPrimaryActivity.COMPONENT
                 .toFlickerComponent()
diff --git a/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml b/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml
index 1ec9ec9..dc9ff3b 100644
--- a/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml
+++ b/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml
@@ -198,6 +198,13 @@
             android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout"
             android:exported="false"/>
         <activity
+            android:name=".ActivityEmbeddingAlwaysExpandActivity"
+            android:label="ActivityEmbedding AlwaysExpand"
+            android:taskAffinity="com.android.server.wm.flicker.testapp.ActivityEmbedding"
+            android:theme="@style/CutoutShortEdges"
+            android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout"
+            android:exported="false"/>
+        <activity
             android:name=".ActivityEmbeddingPlaceholderPrimaryActivity"
             android:label="ActivityEmbedding Placeholder Primary"
             android:taskAffinity="com.android.server.wm.flicker.testapp.ActivityEmbedding"
diff --git a/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_embedding_main_layout.xml b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_embedding_main_layout.xml
index d78b9a8..f5241ca 100644
--- a/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_embedding_main_layout.xml
+++ b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_embedding_main_layout.xml
@@ -37,4 +37,12 @@
         android:onClick="launchPlaceholderSplit"
         android:text="Launch Placeholder Split" />
 
+    <Button
+        android:id="@+id/launch_always_expand_activity_button"
+        android:layout_width="wrap_content"
+        android:layout_height="48dp"
+        android:layout_centerHorizontal="true"
+        android:onClick="launchAlwaysExpandActivity"
+        android:text="Launch Always Expand Activity" />
+
 </LinearLayout>
diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityEmbeddingAlwaysExpandActivity.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityEmbeddingAlwaysExpandActivity.java
new file mode 100644
index 0000000..d9b24ed
--- /dev/null
+++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityEmbeddingAlwaysExpandActivity.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server.wm.flicker.testapp;
+
+import android.app.Activity;
+import android.graphics.Color;
+import android.os.Bundle;
+
+/**
+ * Activity with alwaysExpand=true (launched via R.id.launch_always_expand_activity_button)
+ */
+public class ActivityEmbeddingAlwaysExpandActivity extends Activity {
+  @Override
+  public void onCreate(Bundle savedInstanceState) {
+    super.onCreate(savedInstanceState);
+    setContentView(R.layout.activity_embedding_base_layout);
+    findViewById(R.id.root_activity_layout).setBackgroundColor(Color.GREEN);
+  }
+
+}
diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityEmbeddingMainActivity.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityEmbeddingMainActivity.java
index 6a7a2cc..6120254 100644
--- a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityEmbeddingMainActivity.java
+++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityEmbeddingMainActivity.java
@@ -23,6 +23,9 @@
 import android.util.Log;
 import android.view.View;
 
+import androidx.window.embedding.ActivityFilter;
+import androidx.window.embedding.ActivityRule;
+import androidx.window.embedding.RuleController;
 import androidx.window.extensions.embedding.ActivityEmbeddingComponent;
 import androidx.window.extensions.embedding.EmbeddingRule;
 import androidx.window.extensions.embedding.SplitPairRule;
@@ -30,6 +33,7 @@
 
 import com.android.server.wm.flicker.helpers.ActivityEmbeddingAppHelper;
 
+import java.util.HashSet;
 import java.util.Set;
 
 /** Main activity of the ActivityEmbedding test app to launch other embedding activities. */
@@ -50,6 +54,23 @@
                 ActivityOptions.ActivityEmbedding.SecondaryActivity.COMPONENT));
     }
 
+    /** R.id.launch_always_expand_activity_button onClick */
+    public void launchAlwaysExpandActivity(View view) {
+        final Set<ActivityFilter> activityFilters = new HashSet<>();
+        activityFilters.add(
+                new ActivityFilter(ActivityOptions.ActivityEmbedding.AlwaysExpandActivity.COMPONENT,
+                        null));
+        final ActivityRule activityRule = new ActivityRule.Builder(activityFilters)
+                .setAlwaysExpand(true)
+                .build();
+
+        RuleController rc = RuleController.getInstance(this);
+
+        rc.addRule(activityRule);
+        startActivity(new Intent().setComponent(
+                ActivityOptions.ActivityEmbedding.AlwaysExpandActivity.COMPONENT));
+    }
+
     /** R.id.launch_placeholder_split_button onClick */
     public void launchPlaceholderSplit(View view) {
         initializeSplitRules(createSplitPlaceholderRules());
diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityOptions.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityOptions.java
index 9c3226b..0f5c003 100644
--- a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityOptions.java
+++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityOptions.java
@@ -99,6 +99,12 @@
                     FLICKER_APP_PACKAGE + ".ActivityEmbeddingSecondaryActivity");
         }
 
+        public static class AlwaysExpandActivity {
+            public static final String LABEL = "ActivityEmbeddingAlwaysExpandActivity";
+            public static final ComponentName COMPONENT = new ComponentName(FLICKER_APP_PACKAGE,
+                FLICKER_APP_PACKAGE + ".ActivityEmbeddingAlwaysExpandActivity");
+        }
+
         public static class PlaceholderPrimaryActivity {
             public static final String LABEL = "ActivityEmbeddingPlaceholderPrimaryActivity";
             public static final ComponentName COMPONENT = new ComponentName(FLICKER_APP_PACKAGE,
diff --git a/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/AutoShowTest.java b/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/AutoShowTest.java
index 3d257b2..807f0c6 100644
--- a/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/AutoShowTest.java
+++ b/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/AutoShowTest.java
@@ -199,8 +199,7 @@
                         Collections.singletonList(REQUEST_FOCUS_ON_CREATE));
         TestActivity firstActivity = TestActivity.start(intent1);
         // Show Ime with InputMethodManager to ensure the keyboard is on.
-        boolean succ = callOnMainSync(firstActivity::showImeWithInputMethodManager);
-        assertThat(succ).isTrue();
+        callOnMainSync(firstActivity::showImeWithInputMethodManager);
         SystemClock.sleep(1000);
         mInstrumentation.waitForIdleSync();
 
@@ -264,8 +263,7 @@
                         Collections.singletonList(REQUEST_FOCUS_ON_CREATE));
         ImeStressTestUtil.TestActivity secondActivity = activity.startSecondTestActivity(intent2);
         // Show Ime with InputMethodManager to ensure the keyboard is shown on the second activity
-        boolean succ = callOnMainSync(secondActivity::showImeWithInputMethodManager);
-        assertThat(succ).isTrue();
+        callOnMainSync(secondActivity::showImeWithInputMethodManager);
         SystemClock.sleep(1000);
         mInstrumentation.waitForIdleSync();
         // Close the second activity
diff --git a/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/DefaultImeVisibilityTest.java b/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/DefaultImeVisibilityTest.java
index 299cbf1..0c267b2 100644
--- a/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/DefaultImeVisibilityTest.java
+++ b/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/DefaultImeVisibilityTest.java
@@ -26,8 +26,6 @@
 import static com.android.inputmethod.stresstest.ImeStressTestUtil.waitOnMainUntilImeIsHidden;
 import static com.android.inputmethod.stresstest.ImeStressTestUtil.waitOnMainUntilImeIsShown;
 
-import static com.google.common.truth.Truth.assertThat;
-
 import android.content.Intent;
 import android.platform.test.annotations.RootPermissionTest;
 import android.platform.test.rule.UnlockScreenRule;
@@ -83,14 +81,11 @@
         ImeStressTestUtil.TestActivity activity = ImeStressTestUtil.TestActivity.start(intent);
         EditText editText = activity.getEditText();
         for (int i = 0; i < NUM_TEST_ITERATIONS; i++) {
-
-            boolean showResult = callOnMainSync(activity::showImeWithInputMethodManager);
-            assertThat(showResult).isTrue();
+            callOnMainSync(activity::showImeWithInputMethodManager);
             verifyWindowAndViewFocus(editText, true, true);
             waitOnMainUntilImeIsShown(editText);
 
-            boolean hideResult = callOnMainSync(activity::hideImeWithInputMethodManager);
-            assertThat(hideResult).isTrue();
+            callOnMainSync(activity::hideImeWithInputMethodManager);
             waitOnMainUntilImeIsHidden(editText);
         }
     }
diff --git a/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/ImeOpenCloseStressTest.java b/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/ImeOpenCloseStressTest.java
index b76a4eb..5c02124 100644
--- a/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/ImeOpenCloseStressTest.java
+++ b/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/ImeOpenCloseStressTest.java
@@ -102,12 +102,10 @@
         for (int i = 0; i < iterNum; i++) {
             String msgPrefix = "Iteration #" + i + " ";
             Log.i(TAG, msgPrefix + "start");
-            boolean showResult = callOnMainSync(activity::showImeWithInputMethodManager);
-            assertThat(showResult).isEqualTo(!(hasUnfocusableWindowFlags(activity)));
+            callOnMainSync(activity::showImeWithInputMethodManager);
             verifyShowBehavior(activity);
 
-            boolean hideResult = callOnMainSync(activity::hideImeWithInputMethodManager);
-            assertThat(hideResult).isEqualTo(!(hasUnfocusableWindowFlags(activity)));
+            callOnMainSync(activity::hideImeWithInputMethodManager);
 
             verifyHideBehavior(activity);
         }
@@ -128,14 +126,12 @@
         for (int i = 0; i < NUM_TEST_ITERATIONS; i++) {
             String msgPrefix = "Iteration #" + i + " ";
             Log.i(TAG, msgPrefix + "start");
-            boolean showResult = callOnMainSync(activity::showImeWithInputMethodManager);
-            assertThat(showResult).isTrue();
+            callOnMainSync(activity::showImeWithInputMethodManager);
             waitOnMainUntil(
                     msgPrefix + "IME should be visible",
                     () -> !activity.isAnimating() && isImeShown(editText));
 
-            boolean hideResult = callOnMainSync(activity::hideImeWithInputMethodManager);
-            assertThat(hideResult).isTrue();
+            callOnMainSync(activity::hideImeWithInputMethodManager);
             waitOnMainUntil(
                     msgPrefix + "IME should be hidden",
                     () -> !activity.isAnimating() && !isImeShown(editText));
@@ -156,17 +152,13 @@
         List<Integer> intervals = new ArrayList<>();
         for (int i = 10; i < 100; i += 10) intervals.add(i);
         for (int i = 100; i < 1000; i += 50) intervals.add(i);
-        boolean firstHide = false;
         for (int intervalMillis : intervals) {
             String msgPrefix = "Interval = " + intervalMillis + " ";
             Log.i(TAG, msgPrefix + " start");
-            boolean hideResult = callOnMainSync(activity::hideImeWithInputMethodManager);
-            assertThat(hideResult).isEqualTo(firstHide);
-            firstHide = true;
+            callOnMainSync(activity::hideImeWithInputMethodManager);
             SystemClock.sleep(intervalMillis);
 
-            boolean showResult = callOnMainSync(activity::showImeWithInputMethodManager);
-            assertThat(showResult).isTrue();
+            callOnMainSync(activity::showImeWithInputMethodManager);
             verifyShowBehavior(activity);
         }
     }
@@ -247,8 +239,7 @@
         TestActivity activity = TestActivity.start(intent);
 
         // Show InputMethodManager without requesting focus
-        boolean showResult = callOnMainSync(activity::showImeWithInputMethodManager);
-        assertThat(showResult).isFalse();
+        callOnMainSync(activity::showImeWithInputMethodManager);
 
         int windowFlags = activity.getWindow().getAttributes().flags;
         EditText editText = activity.getEditText();
@@ -474,8 +465,7 @@
                         Collections.singletonList(REQUEST_FOCUS_ON_CREATE));
         TestActivity activity = TestActivity.start(intent1);
         // Show Ime with InputMethodManager to ensure the keyboard is shown on the second activity
-        boolean showResult = callOnMainSync(activity::showImeWithInputMethodManager);
-        assertThat(showResult).isEqualTo(!(hasUnfocusableWindowFlags(activity)));
+        callOnMainSync(activity::showImeWithInputMethodManager);
 
         Thread.sleep(1000);
         verifyShowBehavior(activity);
@@ -503,8 +493,7 @@
                         Collections.singletonList(REQUEST_FOCUS_ON_CREATE));
         TestActivity activity = TestActivity.start(intent);
         // Show Ime with InputMethodManager to ensure the keyboard is shown on the second activity
-        boolean showResult = callOnMainSync(activity::showImeWithInputMethodManager);
-        assertThat(showResult).isEqualTo(!(hasUnfocusableWindowFlags(activity)));
+        callOnMainSync(activity::showImeWithInputMethodManager);
         Thread.sleep(2000);
         verifyShowBehavior(activity);
 
diff --git a/wifi/tests/src/android/net/wifi/nl80211/OWNERS b/wifi/tests/src/android/net/wifi/nl80211/OWNERS
new file mode 100644
index 0000000..8a75e25
--- /dev/null
+++ b/wifi/tests/src/android/net/wifi/nl80211/OWNERS
@@ -0,0 +1 @@
+kumachang@google.com