Merge "Hide KeyChainSystemService."
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 299cb6c..e8fcdd2 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
@@ -1584,6 +1584,12 @@
         return reason;
     }
 
+    @VisibleForTesting
+    @JobScheduler.PendingJobReason
+    int getPendingJobReason(JobStatus job) {
+        return getPendingJobReason(job.getUid(), job.getNamespace(), job.getJobId());
+    }
+
     @JobScheduler.PendingJobReason
     @GuardedBy("mLock")
     private int getPendingJobReasonLocked(int uid, String namespace, int jobId) {
@@ -1694,7 +1700,8 @@
         }
     }
 
-    private void stopUserVisibleJobsInternal(@NonNull String packageName, int userId) {
+    @VisibleForTesting
+    void stopUserVisibleJobsInternal(@NonNull String packageName, int userId) {
         final int packageUid = mLocalPM.getPackageUid(packageName, 0, userId);
         if (packageUid < 0) {
             Slog.wtf(TAG, "Asked to stop jobs of an unknown package");
@@ -1716,6 +1723,21 @@
                 // to stop only that work, B's jobs would be demoted as well.
                 // TODO(255768978): make it possible to demote only the relevant subset of jobs
                 job.addInternalFlags(JobStatus.INTERNAL_FLAG_DEMOTED_BY_USER);
+
+                // The app process will be killed soon. There's no point keeping its jobs in
+                // the pending queue to try and start them.
+                if (mPendingJobQueue.remove(job)) {
+                    synchronized (mPendingJobReasonCache) {
+                        SparseIntArray jobIdToReason = mPendingJobReasonCache.get(
+                                job.getUid(), job.getNamespace());
+                        if (jobIdToReason == null) {
+                            jobIdToReason = new SparseIntArray();
+                            mPendingJobReasonCache.add(job.getUid(), job.getNamespace(),
+                                    jobIdToReason);
+                        }
+                        jobIdToReason.put(job.getJobId(), JobScheduler.PENDING_JOB_REASON_USER);
+                    }
+                }
             }
         }
     }
diff --git a/core/api/current.txt b/core/api/current.txt
index 3c2d22f..bca9913 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -20181,6 +20181,7 @@
     method public boolean hasSatellitePvt();
     method public boolean hasScheduling();
     method public boolean hasSingleShotFix();
+    method public boolean isAccumulatedDeltaRangeCapabilityKnown();
     method public void writeToParcel(@NonNull android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.location.GnssCapabilities> CREATOR;
   }
@@ -20189,6 +20190,7 @@
     ctor public GnssCapabilities.Builder();
     ctor public GnssCapabilities.Builder(@NonNull android.location.GnssCapabilities);
     method @NonNull public android.location.GnssCapabilities build();
+    method @NonNull public android.location.GnssCapabilities.Builder clearIsAccumulatedDeltaRangeCapabilityKnown();
     method @NonNull public android.location.GnssCapabilities.Builder setGnssSignalTypes(@NonNull java.util.List<android.location.GnssSignalType>);
     method @NonNull public android.location.GnssCapabilities.Builder setHasAccumulatedDeltaRange(boolean);
     method @NonNull public android.location.GnssCapabilities.Builder setHasAntennaInfo(boolean);
@@ -49977,16 +49979,24 @@
     field public static final int CLOCK_TICK = 4; // 0x4
     field public static final int CONFIRM = 16; // 0x10
     field public static final int CONTEXT_CLICK = 6; // 0x6
+    field public static final int DRAG_START = 25; // 0x19
     field @Deprecated public static final int FLAG_IGNORE_GLOBAL_SETTING = 2; // 0x2
     field public static final int FLAG_IGNORE_VIEW_SETTING = 1; // 0x1
     field public static final int GESTURE_END = 13; // 0xd
     field public static final int GESTURE_START = 12; // 0xc
+    field public static final int GESTURE_THRESHOLD_ACTIVATE = 23; // 0x17
+    field public static final int GESTURE_THRESHOLD_DEACTIVATE = 24; // 0x18
     field public static final int KEYBOARD_PRESS = 3; // 0x3
     field public static final int KEYBOARD_RELEASE = 7; // 0x7
     field public static final int KEYBOARD_TAP = 3; // 0x3
     field public static final int LONG_PRESS = 0; // 0x0
+    field public static final int NO_HAPTICS = -1; // 0xffffffff
     field public static final int REJECT = 17; // 0x11
+    field public static final int SEGMENT_FREQUENT_TICK = 27; // 0x1b
+    field public static final int SEGMENT_TICK = 26; // 0x1a
     field public static final int TEXT_HANDLE_MOVE = 9; // 0x9
+    field public static final int TOGGLE_OFF = 22; // 0x16
+    field public static final int TOGGLE_ON = 21; // 0x15
     field public static final int VIRTUAL_KEY = 1; // 0x1
     field public static final int VIRTUAL_KEY_RELEASE = 8; // 0x8
   }
@@ -51243,6 +51253,7 @@
     method @NonNull public android.view.SurfaceControl.Transaction addTransactionCommittedListener(@NonNull java.util.concurrent.Executor, @NonNull android.view.SurfaceControl.TransactionCommittedListener);
     method public void apply();
     method @NonNull public android.view.SurfaceControl.Transaction clearFrameRate(@NonNull android.view.SurfaceControl);
+    method @NonNull public android.view.SurfaceControl.Transaction clearTrustedPresentationCallback(@NonNull android.view.SurfaceControl);
     method public void close();
     method public int describeContents();
     method @NonNull public android.view.SurfaceControl.Transaction merge(@NonNull android.view.SurfaceControl.Transaction);
@@ -51263,6 +51274,7 @@
     method @NonNull public android.view.SurfaceControl.Transaction setOpaque(@NonNull android.view.SurfaceControl, boolean);
     method @NonNull public android.view.SurfaceControl.Transaction setPosition(@NonNull android.view.SurfaceControl, float, float);
     method @NonNull public android.view.SurfaceControl.Transaction setScale(@NonNull android.view.SurfaceControl, float, float);
+    method @NonNull public android.view.SurfaceControl.Transaction setTrustedPresentationCallback(@NonNull android.view.SurfaceControl, @NonNull android.view.SurfaceControl.TrustedPresentationThresholds, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Boolean>);
     method @NonNull public android.view.SurfaceControl.Transaction setVisibility(@NonNull android.view.SurfaceControl, boolean);
     method public void writeToParcel(@NonNull android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.view.SurfaceControl.Transaction> CREATOR;
@@ -51272,6 +51284,10 @@
     method public void onTransactionCommitted();
   }
 
+  public static class SurfaceControl.TrustedPresentationThresholds {
+    ctor public SurfaceControl.TrustedPresentationThresholds(@FloatRange(from=0.0f, fromInclusive=false, to=1.0f) float, @FloatRange(from=0.0f, fromInclusive=false, to=1.0f) float, @IntRange(from=1) int);
+  }
+
   public class SurfaceControlViewHost {
     ctor public SurfaceControlViewHost(@NonNull android.content.Context, @NonNull android.view.Display, @Nullable android.os.IBinder);
     method @Nullable public android.view.SurfaceControlViewHost.SurfacePackage getSurfacePackage();
@@ -60276,6 +60292,14 @@
     method @UiThread public void remove();
   }
 
+  public class SurfaceSyncGroup {
+    ctor public SurfaceSyncGroup(@NonNull String);
+    method @UiThread public boolean add(@Nullable android.view.AttachedSurfaceControl, @Nullable Runnable);
+    method public boolean add(@NonNull android.view.SurfaceControlViewHost.SurfacePackage, @Nullable Runnable);
+    method public void addTransaction(@NonNull android.view.SurfaceControl.Transaction);
+    method public void markSyncReady();
+  }
+
 }
 
 package javax.microedition.khronos.egl {
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index cf8df97..c098adb 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -457,6 +457,7 @@
     field public static final int config_systemAutomotiveProjection = 17039401; // 0x1040029
     field public static final int config_systemCompanionDeviceProvider = 17039417; // 0x1040039
     field public static final int config_systemContacts = 17039403; // 0x104002b
+    field public static final int config_systemFinancedDeviceController;
     field public static final int config_systemGallery = 17039399; // 0x1040027
     field public static final int config_systemNotificationIntelligence = 17039413; // 0x1040035
     field public static final int config_systemSettingsIntelligence = 17039426; // 0x1040042
diff --git a/core/java/android/app/ActivityManagerInternal.java b/core/java/android/app/ActivityManagerInternal.java
index ce29937..0fa1a37 100644
--- a/core/java/android/app/ActivityManagerInternal.java
+++ b/core/java/android/app/ActivityManagerInternal.java
@@ -955,4 +955,12 @@
      * @hide
      */
     public abstract void stopForegroundServiceDelegate(@NonNull ServiceConnection connection);
+
+    /**
+     * Called by PowerManager. Return whether a given procstate is allowed to hold
+     * wake locks in deep doze. Because it's called with the power manager lock held, we can't
+     * hold AM locks in it.
+     * @hide
+     */
+    public abstract boolean canHoldWakeLocksInDeepDoze(int uid, int procstate);
 }
diff --git a/core/java/android/app/IActivityManager.aidl b/core/java/android/app/IActivityManager.aidl
index 9dc8ce6..058c389 100644
--- a/core/java/android/app/IActivityManager.aidl
+++ b/core/java/android/app/IActivityManager.aidl
@@ -795,7 +795,7 @@
 
     // Start (?) of T transactions
     /**
-     * Similar to {@link #startUserInBackgroundWithListener(int userId, IProgressListener unlockProgressListener),
+     * Similar to {@link #startUserInBackgroundWithListener(int userId, IProgressListener unlockProgressListener)},
      * but setting the user as the visible user of that display (i.e., allowing the user and its
      * running profiles to launch activities on that display).
      *
@@ -806,6 +806,12 @@
     boolean startUserInBackgroundVisibleOnDisplay(int userid, int displayId);
 
     /**
+     * Similar to {@link #startProfile(int userId)}, but with a listener to report user unlock
+     * progress.
+     */
+    boolean startProfileWithListener(int userid, IProgressListener unlockProgressListener);
+
+    /**
      * Gets the ids of displays that can be used on {@link #startUserInBackgroundVisibleOnDisplay(int userId, int displayId)}.
      *
      * <p>Typically used only by automotive builds when the vehicle has multiple displays.
diff --git a/core/java/android/app/NotificationChannelGroup.java b/core/java/android/app/NotificationChannelGroup.java
index 3bd86c1..92db8b3 100644
--- a/core/java/android/app/NotificationChannelGroup.java
+++ b/core/java/android/app/NotificationChannelGroup.java
@@ -20,6 +20,7 @@
 import android.annotation.TestApi;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.Intent;
+import android.content.pm.ParceledListSlice;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.text.TextUtils;
@@ -68,7 +69,7 @@
     private CharSequence mName;
     private String mDescription;
     private boolean mBlocked;
-    private List<NotificationChannel> mChannels = new ArrayList<>();
+    private List<NotificationChannel> mChannels = new ArrayList();
     // Bitwise representation of fields that have been changed by the user
     private int mUserLockedFields;
 
@@ -106,7 +107,12 @@
         } else {
             mDescription = null;
         }
-        in.readParcelableList(mChannels, NotificationChannel.class.getClassLoader(), android.app.NotificationChannel.class);
+        if (in.readByte() != 0) {
+            mChannels = in.readParcelable(NotificationChannelGroup.class.getClassLoader(),
+                    ParceledListSlice.class).getList();
+        } else {
+            mChannels = new ArrayList<>();
+        }
         mBlocked = in.readBoolean();
         mUserLockedFields = in.readInt();
     }
@@ -138,7 +144,12 @@
         } else {
             dest.writeByte((byte) 0);
         }
-        dest.writeParcelableList(mChannels, flags);
+        if (mChannels != null) {
+            dest.writeByte((byte) 1);
+            dest.writeParcelable(new ParceledListSlice<>(mChannels), flags);
+        } else {
+            dest.writeByte((byte) 0);
+        }
         dest.writeBoolean(mBlocked);
         dest.writeInt(mUserLockedFields);
     }
diff --git a/core/java/android/service/dreams/DreamOverlayService.java b/core/java/android/service/dreams/DreamOverlayService.java
index bf5b970..6e4535b 100644
--- a/core/java/android/service/dreams/DreamOverlayService.java
+++ b/core/java/android/service/dreams/DreamOverlayService.java
@@ -36,39 +36,101 @@
 public abstract class DreamOverlayService extends Service {
     private static final String TAG = "DreamOverlayService";
     private static final boolean DEBUG = false;
-    private boolean mShowComplications;
-    private ComponentName mDreamComponent;
 
-    private IDreamOverlay mDreamOverlay = new IDreamOverlay.Stub() {
+    // The last client that started dreaming and hasn't ended
+    private OverlayClient mCurrentClient;
+
+    // An {@link IDreamOverlayClient} implementation that identifies itself when forwarding
+    // requests to the {@link DreamOverlayService}
+    private static class OverlayClient extends IDreamOverlayClient.Stub {
+        private final DreamOverlayService mService;
+        private boolean mShowComplications;
+        private ComponentName mDreamComponent;
+        IDreamOverlayCallback mDreamOverlayCallback;
+
+        OverlayClient(DreamOverlayService service) {
+            mService = service;
+        }
+
         @Override
-        public void startDream(WindowManager.LayoutParams layoutParams,
-                IDreamOverlayCallback callback, String dreamComponent,
-                boolean shouldShowComplications) {
-            mDreamOverlayCallback = callback;
+        public void startDream(WindowManager.LayoutParams params, IDreamOverlayCallback callback,
+                String dreamComponent, boolean shouldShowComplications) throws RemoteException {
             mDreamComponent = ComponentName.unflattenFromString(dreamComponent);
             mShowComplications = shouldShowComplications;
-            onStartDream(layoutParams);
+            mDreamOverlayCallback = callback;
+            mService.startDream(this, params);
+        }
+
+
+
+        @Override
+        public void wakeUp() {
+            mService.wakeUp(this, () -> {
+                try {
+                    mDreamOverlayCallback.onWakeUpComplete();
+                } catch (RemoteException e) {
+                    Log.e(TAG, "Could not notify dream of wakeUp", e);
+                }
+            });
         }
 
         @Override
         public void endDream() {
-            onEndDream();
+            mService.endDream(this);
         }
 
+        private void onExitRequested() {
+            try {
+                mDreamOverlayCallback.onExitRequested();
+            } catch (RemoteException e) {
+                Log.e(TAG, "Could not request exit:" + e);
+            }
+        }
+
+        private boolean shouldShowComplications() {
+            return mShowComplications;
+        }
+
+        private ComponentName getComponent() {
+            return mDreamComponent;
+        }
+    }
+
+    private void startDream(OverlayClient client, WindowManager.LayoutParams params) {
+        endDream(mCurrentClient);
+        mCurrentClient = client;
+        onStartDream(params);
+    }
+
+    private void endDream(OverlayClient client) {
+        if (client == null || client != mCurrentClient) {
+            return;
+        }
+
+        onEndDream();
+        mCurrentClient = null;
+    }
+
+    private void wakeUp(OverlayClient client, Runnable callback) {
+        if (mCurrentClient != client) {
+            return;
+        }
+
+        onWakeUp(callback);
+    }
+
+    private IDreamOverlay mDreamOverlay = new IDreamOverlay.Stub() {
         @Override
-        public void wakeUp() {
-            onWakeUp(() -> {
-                try {
-                    mDreamOverlayCallback.onWakeUpComplete();
-                } catch (RemoteException e) {
-                    Log.e(TAG, "Could not notify dream of wakeUp:" + e);
-                }
-            });
+        public void getClient(IDreamOverlayClientCallback callback) {
+            try {
+                callback.onDreamOverlayClient(
+                        new OverlayClient(DreamOverlayService.this));
+            } catch (RemoteException e) {
+                Log.e(TAG, "could not send client to callback", e);
+            }
         }
     };
 
-    IDreamOverlayCallback mDreamOverlayCallback;
-
     public DreamOverlayService() {
     }
 
@@ -110,18 +172,23 @@
      * This method is invoked to request the dream exit.
      */
     public final void requestExit() {
-        try {
-            mDreamOverlayCallback.onExitRequested();
-        } catch (RemoteException e) {
-            Log.e(TAG, "Could not request exit:" + e);
+        if (mCurrentClient == null) {
+            throw new IllegalStateException("requested exit with no dream present");
         }
+
+        mCurrentClient.onExitRequested();
     }
 
     /**
      * Returns whether to show complications on the dream overlay.
      */
     public final boolean shouldShowComplications() {
-        return mShowComplications;
+        if (mCurrentClient == null) {
+            throw new IllegalStateException(
+                    "requested if should show complication when no dream active");
+        }
+
+        return mCurrentClient.shouldShowComplications();
     }
 
     /**
@@ -129,6 +196,10 @@
      * @hide
      */
     public final ComponentName getDreamComponent() {
-        return mDreamComponent;
+        if (mCurrentClient == null) {
+            throw new IllegalStateException("requested dream component when no dream active");
+        }
+
+        return mCurrentClient.getComponent();
     }
 }
diff --git a/core/java/android/service/dreams/DreamService.java b/core/java/android/service/dreams/DreamService.java
index cbe1ded..6a4710f 100644
--- a/core/java/android/service/dreams/DreamService.java
+++ b/core/java/android/service/dreams/DreamService.java
@@ -248,25 +248,39 @@
     private OverlayConnection mOverlayConnection;
 
     private static class OverlayConnection extends PersistentServiceConnection<IDreamOverlay> {
-        // Overlay set during onBind.
-        private IDreamOverlay mOverlay;
+        // Retrieved Client
+        private IDreamOverlayClient mClient;
+
         // A list of pending requests to execute on the overlay.
-        private final ArrayList<Consumer<IDreamOverlay>> mConsumers = new ArrayList<>();
+        private final ArrayList<Consumer<IDreamOverlayClient>> mConsumers = new ArrayList<>();
+
+        private final IDreamOverlayClientCallback mClientCallback =
+                new IDreamOverlayClientCallback.Stub() {
+            @Override
+            public void onDreamOverlayClient(IDreamOverlayClient client) {
+                mClient = client;
+
+                for (Consumer<IDreamOverlayClient> consumer : mConsumers) {
+                    consumer.accept(mClient);
+                }
+            }
+        };
 
         private final Callback<IDreamOverlay> mCallback = new Callback<IDreamOverlay>() {
             @Override
             public void onConnected(ObservableServiceConnection<IDreamOverlay> connection,
                     IDreamOverlay service) {
-                mOverlay = service;
-                for (Consumer<IDreamOverlay> consumer : mConsumers) {
-                    consumer.accept(mOverlay);
+                try {
+                    service.getClient(mClientCallback);
+                } catch (RemoteException e) {
+                    Log.e(TAG, "could not get DreamOverlayClient", e);
                 }
             }
 
             @Override
             public void onDisconnected(ObservableServiceConnection<IDreamOverlay> connection,
                     int reason) {
-                mOverlay = null;
+                mClient = null;
             }
         };
 
@@ -296,16 +310,16 @@
             super.unbind();
         }
 
-        public void addConsumer(Consumer<IDreamOverlay> consumer) {
+        public void addConsumer(Consumer<IDreamOverlayClient> consumer) {
             execute(() -> {
                 mConsumers.add(consumer);
-                if (mOverlay != null) {
-                    consumer.accept(mOverlay);
+                if (mClient != null) {
+                    consumer.accept(mClient);
                 }
             });
         }
 
-        public void removeConsumer(Consumer<IDreamOverlay> consumer) {
+        public void removeConsumer(Consumer<IDreamOverlayClient> consumer) {
             execute(() -> mConsumers.remove(consumer));
         }
 
@@ -1379,7 +1393,7 @@
 
         mWindow.getDecorView().addOnAttachStateChangeListener(
                 new View.OnAttachStateChangeListener() {
-                    private Consumer<IDreamOverlay> mDreamStartOverlayConsumer;
+                    private Consumer<IDreamOverlayClient> mDreamStartOverlayConsumer;
 
                     @Override
                     public void onViewAttachedToWindow(View v) {
diff --git a/core/java/android/service/dreams/IDreamOverlay.aidl b/core/java/android/service/dreams/IDreamOverlay.aidl
index 0e4bd3b..7ec75a5 100644
--- a/core/java/android/service/dreams/IDreamOverlay.aidl
+++ b/core/java/android/service/dreams/IDreamOverlay.aidl
@@ -16,8 +16,7 @@
 
 package android.service.dreams;
 
-import android.service.dreams.IDreamOverlayCallback;
-import android.view.WindowManager.LayoutParams;
+import android.service.dreams.IDreamOverlayClientCallback;
 
 /**
 * {@link IDreamOverlay} provides a way for a component to annotate a dream with additional view
@@ -28,20 +27,7 @@
 */
 interface IDreamOverlay {
     /**
-    * @param params The {@link LayoutParams} for the associated DreamWindow, including the window
-                    token of the Dream Activity.
-    * @param callback The {@link IDreamOverlayCallback} for requesting actions such as exiting the
-    *                dream.
-    * @param dreamComponent The component name of the dream service requesting overlay.
-    * @param shouldShowComplications Whether the dream overlay should show complications, e.g. clock
-    *                and weather.
+    * Retrieves a client the caller can use to interact with the dream overlay.
     */
-    void startDream(in LayoutParams params, in IDreamOverlayCallback callback,
-        in String dreamComponent, in boolean shouldShowComplications);
-
-    /** Called when the dream is waking, to do any exit animations */
-    void wakeUp();
-
-    /** Called when the dream has ended. */
-    void endDream();
+    void getClient(in IDreamOverlayClientCallback callback);
 }
diff --git a/core/java/android/service/dreams/IDreamOverlayClient.aidl b/core/java/android/service/dreams/IDreamOverlayClient.aidl
new file mode 100644
index 0000000..78b7280
--- /dev/null
+++ b/core/java/android/service/dreams/IDreamOverlayClient.aidl
@@ -0,0 +1,45 @@
+/**
+ * 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.service.dreams;
+
+import android.service.dreams.IDreamOverlayCallback;
+import android.view.WindowManager.LayoutParams;
+
+/**
+* {@link IDreamOverlayClient} allows {@link DreamService} instances to act upon the dream overlay.
+*
+* @hide
+*/
+interface IDreamOverlayClient {
+    /**
+    * @param params The {@link LayoutParams} for the associated DreamWindow, including the window
+                    token of the Dream Activity.
+    * @param callback The {@link IDreamOverlayCallback} for requesting actions such as exiting the
+    *                dream.
+    * @param dreamComponent The component name of the dream service requesting overlay.
+    * @param shouldShowComplications Whether the dream overlay should show complications, e.g. clock
+    *                and weather.
+    */
+    void startDream(in LayoutParams params, in IDreamOverlayCallback callback,
+        in String dreamComponent, in boolean shouldShowComplications);
+
+    /** Called when the dream is waking, to do any exit animations */
+    void wakeUp();
+
+    /** Called when the dream has ended. */
+    void endDream();
+}
diff --git a/core/java/android/service/dreams/IDreamOverlayClientCallback.aidl b/core/java/android/service/dreams/IDreamOverlayClientCallback.aidl
new file mode 100644
index 0000000..244d999
--- /dev/null
+++ b/core/java/android/service/dreams/IDreamOverlayClientCallback.aidl
@@ -0,0 +1,30 @@
+/**
+ * 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.service.dreams;
+
+import android.service.dreams.IDreamOverlayClient;
+
+/**
+* {@link IDreamOverlayClientCallback} allows receiving a requested {@link IDreamOverlayClient}.
+* @hide
+*/
+interface IDreamOverlayClientCallback {
+    /**
+    * Called with a unique {@link IDreamOverlayClient}.
+    */
+    void onDreamOverlayClient(in IDreamOverlayClient client);
+}
diff --git a/core/java/android/speech/tts/TextToSpeech.java b/core/java/android/speech/tts/TextToSpeech.java
index 7e8622a..21b14f4 100644
--- a/core/java/android/speech/tts/TextToSpeech.java
+++ b/core/java/android/speech/tts/TextToSpeech.java
@@ -2422,6 +2422,7 @@
                     @Override
                     public void onDisconnected() {
                         onServiceDisconnected(/* componentName= */ null);
+                        mSession = null;
                     }
 
                     @Override
diff --git a/core/java/android/transparency/BinaryTransparencyManager.java b/core/java/android/transparency/BinaryTransparencyManager.java
index f6d7c61..d77bbcc 100644
--- a/core/java/android/transparency/BinaryTransparencyManager.java
+++ b/core/java/android/transparency/BinaryTransparencyManager.java
@@ -19,6 +19,7 @@
 import android.annotation.NonNull;
 import android.annotation.SystemService;
 import android.content.Context;
+import android.os.Bundle;
 import android.os.RemoteException;
 import android.util.Slog;
 
@@ -83,4 +84,36 @@
         }
     }
 
+    /**
+     * Collects the APEX information on the device.
+     *
+     * @param includeTestOnly Whether to include test only data in the returned ApexInfo.
+     * @return A List containing the APEX info.
+     * @hide
+     */
+    @NonNull
+    public List<IBinaryTransparencyService.ApexInfo> collectAllApexInfo(boolean includeTestOnly) {
+        try {
+            return mService.collectAllApexInfo(includeTestOnly);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Collects the updated preload information on the device.
+     *
+     * @return A List containing the preload info.
+     * @hide
+     */
+    @NonNull
+    public List<IBinaryTransparencyService.AppInfo> collectAllUpdatedPreloadInfo(
+            Bundle packagesToSkip) {
+        try {
+            Slog.d(TAG, "Calling backend's collectAllUpdatedPreloadInfo()");
+            return mService.collectAllUpdatedPreloadInfo(packagesToSkip);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
 }
diff --git a/core/java/android/view/HapticFeedbackConstants.java b/core/java/android/view/HapticFeedbackConstants.java
index 45b65e5..6551a18 100644
--- a/core/java/android/view/HapticFeedbackConstants.java
+++ b/core/java/android/view/HapticFeedbackConstants.java
@@ -25,6 +25,13 @@
     private HapticFeedbackConstants() {}
 
     /**
+     * No haptic feedback should be performed. Applications may use this value to indicate skipping
+     * a call to {@link View#performHapticFeedback} entirely, or else rely that it will immediately
+     * return {@code false}.
+     */
+    public static final int NO_HAPTICS = -1;
+
+    /**
      * The user has performed a long press on an object that is resulting
      * in an action being performed.
      */
@@ -145,6 +152,64 @@
     public static final int ROTARY_SCROLL_LIMIT = 20;
 
     /**
+     * The user has toggled a switch or button into the on position.
+     */
+    public static final int TOGGLE_ON = 21;
+
+    /**
+     * The user has toggled a switch or button into the off position.
+     */
+    public static final int TOGGLE_OFF = 22;
+
+    /**
+     * The user is executing a swipe/drag-style gesture, such as pull-to-refresh, where the
+     * gesture action is “eligible” at a certain threshold of movement, and can be cancelled by
+     * moving back past the threshold. This constant indicates that the user's motion has just
+     * passed the threshold for the action to be activated on release.
+     *
+     * @see #GESTURE_THRESHOLD_DEACTIVATE
+     */
+    public static final int GESTURE_THRESHOLD_ACTIVATE = 23;
+
+    /**
+     * The user is executing a swipe/drag-style gesture, such as pull-to-refresh, where the
+     * gesture action is “eligible” at a certain threshold of movement, and can be cancelled by
+     * moving back past the threshold. This constant indicates that the user's motion has just
+     * re-crossed back "under" the threshold for the action to be activated, meaning the gesture is
+     * currently in a cancelled state.
+     *
+     * @see #GESTURE_THRESHOLD_ACTIVATE
+     */
+    public static final int GESTURE_THRESHOLD_DEACTIVATE = 24;
+
+    /**
+     * The user has started a drag-and-drop gesture. The drag target has just been "picked up".
+     */
+    public static final int DRAG_START = 25;
+
+    /**
+     * The user is switching between a series of potential choices, for example items in a list
+     * or discrete points on a slider.
+     *
+     * <p>See also {@link #SEGMENT_FREQUENT_TICK} for cases where density of choices is high, and
+     * the haptics should be lighter or suppressed for a better user experience.
+     */
+    public static final int SEGMENT_TICK = 26;
+
+    /**
+     * The user is switching between a series of many potential choices, for example minutes on a
+     * clock face, or individual percentages. This constant is expected to be very soft, so as
+     * not to be uncomfortable when performed a lot in quick succession. If the device can’t make
+     * a suitably soft vibration, then it may not make any vibration.
+     *
+     * <p>Some specializations of this constant exist for specific actions, notably
+     * {@link #CLOCK_TICK} and {@link #TEXT_HANDLE_MOVE}.
+     *
+     * <p>See also {@link #SEGMENT_TICK}.
+    */
+    public static final int SEGMENT_FREQUENT_TICK = 27;
+
+    /**
      * The phone has booted with safe mode enabled.
      * This is a private constant.  Feel free to renumber as desired.
      * @hide
diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java
index fedb098..54e1a53 100644
--- a/core/java/android/view/SurfaceControl.java
+++ b/core/java/android/view/SurfaceControl.java
@@ -280,7 +280,12 @@
     private static native void nativeSetDefaultApplyToken(IBinder token);
     private static native IBinder nativeGetDefaultApplyToken();
     private static native boolean nativeBootFinished();
-
+    private static native long nativeCreateTpc(TrustedPresentationCallback callback);
+    private static native long getNativeTrustedPresentationCallbackFinalizer();
+    private static native void nativeSetTrustedPresentationCallback(long transactionObj,
+            long nativeObject, long nativeTpc, TrustedPresentationThresholds thresholds);
+    private static native void nativeClearTrustedPresentationCallback(long transactionObj,
+            long nativeObject);
 
     /**
      * Transforms that can be applied to buffers as they are displayed to a window.
@@ -465,6 +470,8 @@
     @GuardedBy("mLock")
     private int mHeight;
 
+    private TrustedPresentationCallback mTrustedPresentationCallback;
+
     private WeakReference<View> mLocalOwnerView;
 
     static GlobalTransactionWrapper sGlobalTransaction;
@@ -2400,6 +2407,79 @@
     }
 
     /**
+     * Threshold values that are sent with
+     * {@link Transaction#setTrustedPresentationCallback(SurfaceControl,
+     * TrustedPresentationThresholds, Executor, Consumer)}
+     */
+    public static class TrustedPresentationThresholds {
+        private float mMinAlpha;
+        private float mMinFractionRendered;
+        private int mStabilityRequirementMs;
+
+        /**
+         * Creates a TrustedPresentationThresholds that's used when calling
+         * {@link Transaction#setTrustedPresentationCallback(SurfaceControl,
+         * TrustedPresentationThresholds, Executor, Consumer)}
+         *
+         * @param minAlpha               The min alpha the {@link SurfaceControl} is required to
+         *                               have to be considered inside the threshold.
+         * @param minFractionRendered    The min fraction of the SurfaceControl that was resented
+         *                               to the user to be considered inside the threshold.
+         * @param stabilityRequirementMs The time in milliseconds required for the
+         *                               {@link SurfaceControl} to be in the threshold.
+         * @throws IllegalArgumentException If threshold values are invalid.
+         */
+        public TrustedPresentationThresholds(
+                @FloatRange(from = 0f, fromInclusive = false, to = 1f) float minAlpha,
+                @FloatRange(from = 0f, fromInclusive = false, to = 1f) float minFractionRendered,
+                @IntRange(from = 1) int stabilityRequirementMs) {
+            mMinAlpha = minAlpha;
+            mMinFractionRendered = minFractionRendered;
+            mStabilityRequirementMs = stabilityRequirementMs;
+
+            checkValid();
+        }
+
+        private void checkValid() {
+            if (mMinAlpha <= 0 || mMinFractionRendered <= 0 || mStabilityRequirementMs < 1) {
+                throw new IllegalArgumentException(
+                        "TrustedPresentationThresholds values are invalid");
+            }
+        }
+    }
+
+    /**
+     * Register a TrustedPresentationCallback for a particular SurfaceControl so it can be notified
+     * when the specified Threshold has been crossed.
+     *
+     * @hide
+     */
+    public abstract static class TrustedPresentationCallback {
+        private final long mNativeObject;
+
+        private static final NativeAllocationRegistry sRegistry =
+                NativeAllocationRegistry.createMalloced(
+                        TrustedPresentationCallback.class.getClassLoader(),
+                        getNativeTrustedPresentationCallbackFinalizer());
+
+        private final Runnable mFreeNativeResources;
+
+        private TrustedPresentationCallback() {
+            mNativeObject = nativeCreateTpc(this);
+            mFreeNativeResources = sRegistry.registerNativeAllocation(this, mNativeObject);
+        }
+
+        /**
+         * Invoked when the SurfaceControl that this TrustedPresentationCallback was registered for
+         * enters or exits the threshold bounds.
+         *
+         * @param inTrustedPresentationState true when the SurfaceControl entered the
+         *                                   presentation state, false when it has left.
+         */
+        public abstract void onTrustedPresentationChanged(boolean inTrustedPresentationState);
+    }
+
+    /**
      * An atomic set of changes to a set of SurfaceControl.
      */
     public static class Transaction implements Closeable, Parcelable {
@@ -3777,6 +3857,106 @@
         }
 
         /**
+         * Sets a callback to receive feedback about the presentation of a {@link SurfaceControl}.
+         * When the {@link SurfaceControl} is presented according to the passed in
+         * {@link TrustedPresentationThresholds}, it is said to "enter the state", and receives the
+         * callback with {@code true}. When the conditions fall out of thresholds, it is then
+         * said to leave the state.
+         * <p>
+         * There are a few simple thresholds:
+         * <ul>
+         *    <li>minAlpha: Lower bound on computed alpha</li>
+         *    <li>minFractionRendered: Lower bounds on fraction of pixels that were rendered</li>
+         *    <li>stabilityThresholdMs: A time that alpha and fraction rendered must remain within
+         *    bounds before we can "enter the state" </li>
+         * </ul>
+         * <p>
+         * The fraction of pixels rendered is a computation based on scale, crop
+         * and occlusion. The calculation may be somewhat counterintuitive, so we
+         * can work through an example. Imagine we have a SurfaceControl with a 100x100 buffer
+         * which is occluded by (10x100) pixels on the left, and cropped by (100x10) pixels
+         * on the top. Furthermore imagine this SurfaceControl is scaled by 0.9 in both dimensions.
+         * (c=crop,o=occluded,b=both,x=none)
+         *
+         * <blockquote>
+         * <table>
+         *   <caption></caption>
+         *   <tr><td>b</td><td>c</td><td>c</td><td>c</td></tr>
+         *   <tr><td>o</td><td>x</td><td>x</td><td>x</td></tr>
+         *   <tr><td>o</td><td>x</td><td>x</td><td>x</td></tr>
+         *   <tr><td>o</td><td>x</td><td>x</td><td>x</td></tr>
+         * </table>
+         * </blockquote>
+         *
+         *<p>
+         * We first start by computing fr=xscale*yscale=0.9*0.9=0.81, indicating
+         * that "81%" of the pixels were rendered. This corresponds to what was 100
+         * pixels being displayed in 81 pixels. This is somewhat of an abuse of
+         * language, as the information of merged pixels isn't totally lost, but
+         * we err on the conservative side.
+         * <p>
+         * We then repeat a similar process for the crop and covered regions and
+         * accumulate the results: fr = fr * (fractionNotCropped) * (fractionNotCovered)
+         * So for this example we would get 0.9*0.9*0.9*0.9=0.65...
+         * <p>
+         * Notice that this is not completely accurate, as we have double counted
+         * the region marked as b. However we only wanted a "lower bound" and so it
+         * is ok to err in this direction. Selection of the threshold will ultimately
+         * be somewhat arbitrary, and so there are some somewhat arbitrary decisions in
+         * this API as well.
+         * <p>
+         * @param sc         The {@link SurfaceControl} to set the
+         *                   {@link TrustedPresentationCallback} on
+         * @param thresholds The {@link TrustedPresentationThresholds} that will specify when the to
+         *                   invoke the callback.
+         * @param executor   The {@link Executor} where the callback will be invoked on.
+         * @param listener   The {@link Consumer} that will receive the callbacks when entered or
+         *                   exited the threshold.
+         * @return This transaction
+         * @see TrustedPresentationThresholds
+         */
+        @NonNull
+        public Transaction setTrustedPresentationCallback(@NonNull SurfaceControl sc,
+                @NonNull TrustedPresentationThresholds thresholds, @NonNull Executor executor,
+                @NonNull Consumer<Boolean> listener) {
+            checkPreconditions(sc);
+            TrustedPresentationCallback tpc = new TrustedPresentationCallback() {
+                @Override
+                public void onTrustedPresentationChanged(boolean inTrustedPresentationState) {
+                    executor.execute(
+                            () -> listener.accept(inTrustedPresentationState));
+                }
+            };
+
+            if (sc.mTrustedPresentationCallback != null) {
+                sc.mTrustedPresentationCallback.mFreeNativeResources.run();
+            }
+
+            nativeSetTrustedPresentationCallback(mNativeObject, sc.mNativeObject,
+                    tpc.mNativeObject, thresholds);
+            sc.mTrustedPresentationCallback = tpc;
+            return this;
+        }
+
+        /**
+         * Clears the {@link TrustedPresentationCallback} for a specific {@link SurfaceControl}
+         *
+         * @param sc The SurfaceControl that the {@link TrustedPresentationCallback} should be
+         *           cleared from
+         * @return This transaction
+         */
+        @NonNull
+        public Transaction clearTrustedPresentationCallback(@NonNull SurfaceControl sc) {
+            checkPreconditions(sc);
+            nativeClearTrustedPresentationCallback(mNativeObject, sc.mNativeObject);
+            if (sc.mTrustedPresentationCallback != null) {
+                sc.mTrustedPresentationCallback.mFreeNativeResources.run();
+                sc.mTrustedPresentationCallback = null;
+            }
+            return this;
+        }
+
+        /**
          * Writes the transaction to parcel, clearing the transaction as if it had been applied so
          * it can be used to store future transactions. It's the responsibility of the parcel
          * reader to apply the original transaction.
diff --git a/core/java/android/view/SurfaceControlViewHost.java b/core/java/android/view/SurfaceControlViewHost.java
index 490091b..f7bdd09 100644
--- a/core/java/android/view/SurfaceControlViewHost.java
+++ b/core/java/android/view/SurfaceControlViewHost.java
@@ -97,7 +97,8 @@
         public ISurfaceSyncGroup getSurfaceSyncGroup() {
             CompletableFuture<ISurfaceSyncGroup> surfaceSyncGroup = new CompletableFuture<>();
             mViewRoot.mHandler.post(
-                    () -> surfaceSyncGroup.complete(mViewRoot.getOrCreateSurfaceSyncGroup()));
+                    () -> surfaceSyncGroup.complete(
+                            mViewRoot.getOrCreateSurfaceSyncGroup().mISurfaceSyncGroup));
             try {
                 return surfaceSyncGroup.get(1, TimeUnit.SECONDS);
             } catch (InterruptedException | ExecutionException | TimeoutException e) {
diff --git a/core/java/android/view/SurfaceView.java b/core/java/android/view/SurfaceView.java
index e559a71..1eb87c9 100644
--- a/core/java/android/view/SurfaceView.java
+++ b/core/java/android/view/SurfaceView.java
@@ -1091,7 +1091,7 @@
                 t = syncBufferTransactionCallback.waitForTransaction();
             }
 
-            surfaceSyncGroup.addTransactionToSync(t);
+            surfaceSyncGroup.addTransaction(t);
             surfaceSyncGroup.markSyncReady();
             onDrawFinished();
         });
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index d68e085..9fa6c05 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -27270,7 +27270,8 @@
      * @param flags Additional flags as per {@link HapticFeedbackConstants}.
      */
     public boolean performHapticFeedback(int feedbackConstant, int flags) {
-        if (mAttachInfo == null) {
+        if (feedbackConstant == HapticFeedbackConstants.NO_HAPTICS
+                || mAttachInfo == null) {
             return false;
         }
         //noinspection SimplifiableIfStatement
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 84ed845..9119ea4 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -3768,7 +3768,7 @@
             Log.d(mTag, "Setup new sync=" + mWmsRequestSyncGroup.getName());
         }
 
-        mWmsRequestSyncGroup.addToSync(this);
+        mWmsRequestSyncGroup.add(this, null /* runnable */);
         Trace.traceEnd(Trace.TRACE_TAG_VIEW);
     }
 
@@ -11354,7 +11354,7 @@
                 // pendingDrawFinished.
                 if ((syncResult
                         & (SYNC_LOST_SURFACE_REWARD_IF_FOUND | SYNC_CONTEXT_IS_STOPPED)) != 0) {
-                    surfaceSyncGroup.addTransactionToSync(
+                    surfaceSyncGroup.addTransaction(
                             mBlastBufferQueue.gatherPendingTransactions(frame));
                     surfaceSyncGroup.markSyncReady();
                     return null;
@@ -11368,7 +11368,7 @@
                     mBlastBufferQueue.syncNextTransaction(new Consumer<Transaction>() {
                         @Override
                         public void accept(Transaction transaction) {
-                            surfaceSyncGroup.addTransactionToSync(transaction);
+                            surfaceSyncGroup.addTransaction(transaction);
                             surfaceSyncGroup.markSyncReady();
                         }
                     });
@@ -11391,7 +11391,7 @@
                         // since the frame didn't draw on this vsync. It's possible the frame will
                         // draw later, but it's better to not be sync than to block on a frame that
                         // may never come.
-                        surfaceSyncGroup.addTransactionToSync(
+                        surfaceSyncGroup.addTransaction(
                                 mBlastBufferQueue.gatherPendingTransactions(frame));
                         surfaceSyncGroup.markSyncReady();
                         return;
@@ -11481,7 +11481,7 @@
         if (mActiveSurfaceSyncGroup == null) {
             return;
         }
-        mActiveSurfaceSyncGroup.addToSync(syncable, false /* parentSyncGroupMerge */);
+        mActiveSurfaceSyncGroup.add(syncable, null /* Runnable */);
     }
 
     @Override
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java
index 40898d0..9bec409 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -629,114 +629,6 @@
     @interface DisplayImePolicy {}
 
     /**
-     * The root state of all the state. This is an abstract state, and the keyguard should be in
-     * one of this sub state.
-     *
-     * @hide
-     */
-    int KEYGUARD_STATE_ROOT = 0x0;
-
-    /**
-     * Keyguard is off, so activity can be shown on the screen.
-     *
-     * @hide
-     */
-    int KEYGUARD_STATE_OFF = 0x1;
-
-    /**
-     * The keyguard is off, but lock screen is still rendered on the screen. Waiting for
-     * starting unlock animation.
-     *
-     * @hide
-     */
-    int KEYGUARD_STATE_GOING_AWAY = 0x11;
-
-    /**
-     * They keyguard is on, so normal activities cannot be shown on the screen. This is an abstract
-     * state, and the keyguard should be in one of ths sub state.
-     *
-     * @hide
-     */
-    int KEYGUARD_STATE_ON = 0x2;
-
-    /**
-     * The keyguard is on and not occluded.
-     * @hide
-     */
-    int KEYGUARD_STATE_KEYGUARD_TOP = 0x21;
-
-    /**
-     * The keyguard is on, and the lock screen is shown.
-     *
-     * @hide
-     */
-    int KEYGUARD_STATE_LOCKSCREEN_SHOWN = 0x211;
-
-    /**
-     * The keyguard is on, and the AOD is shown.
-     *
-     * @hide
-     */
-    int KEYGUARD_STATE_AOD_SHOWN = 0x212;
-
-    /**
-     * The keyguard is on but it's occluded by a normal SHOW_WHEN_LOCKED activity (i.e. non
-     * occluded by Dream activity).
-     *
-     * @hide
-     */
-    int KEYGUARD_STATE_OCCLUDED = 0x22;
-
-    /**
-     * The keyguard is on but it's occluded by a Dream activity.
-     *
-     * @hide
-     */
-    int KEYGUARD_STATE_DREAMING = 0x221;
-
-    /** @hide */
-    @IntDef(prefix = { "KEYGUARD_STATE_" }, value = {
-            KEYGUARD_STATE_ROOT,
-            KEYGUARD_STATE_OFF,
-            KEYGUARD_STATE_GOING_AWAY,
-            KEYGUARD_STATE_ON,
-            KEYGUARD_STATE_KEYGUARD_TOP,
-            KEYGUARD_STATE_LOCKSCREEN_SHOWN,
-            KEYGUARD_STATE_AOD_SHOWN,
-            KEYGUARD_STATE_OCCLUDED,
-            KEYGUARD_STATE_DREAMING,
-    })
-    @interface KeyguardState {}
-
-    /**
-     * @hide
-     */
-    static String keyguardStateToString(@KeyguardState int type) {
-        switch (type) {
-            case KEYGUARD_STATE_ROOT:
-                return "ROOT";
-            case KEYGUARD_STATE_OFF:
-                return "KEYGUARD_OFF";
-            case KEYGUARD_STATE_GOING_AWAY:
-                return "KEYGUARD_GOING_AWAY";
-            case KEYGUARD_STATE_ON:
-                return "KEYGUARD_ON";
-            case KEYGUARD_STATE_KEYGUARD_TOP:
-                return "KEYGUARD_TOP";
-            case KEYGUARD_STATE_LOCKSCREEN_SHOWN:
-                return "KEYGUARD_LOCKSCREEN_SHOWN";
-            case KEYGUARD_STATE_AOD_SHOWN:
-                return "KEYGUARD_AOD_SHOWN";
-            case KEYGUARD_STATE_OCCLUDED:
-                return "KEYGUARD_OCCLUDED";
-            case KEYGUARD_STATE_DREAMING:
-                return "KEYGUARD_DREAMING";
-            default:
-                return "KEYGUARD_STATE_UNKNOWN(" + Integer.toHexString(type) + ")";
-        }
-    }
-
-    /**
      * Exception that is thrown when trying to add view whose
      * {@link LayoutParams} {@link LayoutParams#token}
      * is invalid.
diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java
index f2f4557..f4a8639 100644
--- a/core/java/android/widget/Editor.java
+++ b/core/java/android/widget/Editor.java
@@ -1637,6 +1637,10 @@
                 mSelectionModifierCursorController.resetTouchOffsets();
             }
 
+            if (mInsertModeController != null) {
+                mInsertModeController.exitInsertMode();
+            }
+
             ensureNoSelectionIfNonSelectable();
         }
     }
diff --git a/core/java/android/window/SurfaceSyncGroup.java b/core/java/android/window/SurfaceSyncGroup.java
index 12cd340..2e55041 100644
--- a/core/java/android/window/SurfaceSyncGroup.java
+++ b/core/java/android/window/SurfaceSyncGroup.java
@@ -42,14 +42,18 @@
 import java.util.function.Supplier;
 
 /**
- * Used to organize syncs for surfaces.
+ * A way for data to be gathered so multiple surfaces can be synced. This is intended to be
+ * used with AttachedSurfaceControl, SurfaceView, and SurfaceControlViewHost. This allows different
+ * parts of the system to synchronize different surfaces themselves without having to manage timing
+ * of different rendering threads.
+ * This will also allow synchronization of surfaces across multiple processes. The caller can add
+ * SurfaceControlViewHosts from another process to the SurfaceSyncGroup in a different process
+ * and this clas will ensure all the surfaces are ready before applying everything together.
  * </p>
- * See SurfaceSyncGroup.md
+ * see the <a href="https://android.googlesource.com/platform/frameworks/base/+/master/core/java/android/window/SurfaceSyncGroup.md">SurfaceSyncGroup documentation</a>
  * </p>
- *
- * @hide
  */
-public class SurfaceSyncGroup extends ISurfaceSyncGroup.Stub {
+public class SurfaceSyncGroup {
     private static final String TAG = "SurfaceSyncGroup";
     private static final boolean DEBUG = false;
 
@@ -93,6 +97,11 @@
     private ISurfaceSyncGroupCompletedListener mSurfaceSyncGroupCompletedListener;
 
     /**
+     * @hide
+     */
+    public final ISurfaceSyncGroup mISurfaceSyncGroup = new ISurfaceSyncGroupImpl();
+
+    /**
      * Token to identify this SurfaceSyncGroup. This is used to register the SurfaceSyncGroup in
      * WindowManager. This token is also sent to other processes' SurfaceSyncGroup that want to be
      * included in this SurfaceSyncGroup.
@@ -104,8 +113,8 @@
     }
 
     private static SurfaceSyncGroup getSurfaceSyncGroup(ISurfaceSyncGroup iSurfaceSyncGroup) {
-        if (iSurfaceSyncGroup instanceof SurfaceSyncGroup) {
-            return (SurfaceSyncGroup) iSurfaceSyncGroup;
+        if (iSurfaceSyncGroup instanceof ISurfaceSyncGroupImpl) {
+            return ((ISurfaceSyncGroupImpl) iSurfaceSyncGroup).getSurfaceSyncGroup();
         }
         return null;
     }
@@ -119,8 +128,10 @@
 
     /**
      * Starts a sync and will automatically apply the final, merged transaction.
+     *
+     * @param name Used for identifying and debugging.
      */
-    public SurfaceSyncGroup(String name) {
+    public SurfaceSyncGroup(@NonNull String name) {
         this(name, transaction -> {
             if (transaction != null) {
                 if (DEBUG) {
@@ -134,6 +145,7 @@
     /**
      * Creates a sync.
      *
+     * @param name                     Used for identifying and debugging.
      * @param transactionReadyConsumer The complete callback that contains the syncId and
      *                                 transaction with all the sync data merged. The Transaction
      *                                 passed back can be null.
@@ -157,19 +169,23 @@
                 Log.d(TAG, "Sending non null transaction " + transaction + " to callback for "
                         + mName);
             }
-            Trace.instant(Trace.TRACE_TAG_VIEW,
-                    "Final TransactionCallback with " + transaction + " for " + mName);
+            if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
+                Trace.instant(Trace.TRACE_TAG_VIEW,
+                        "Final TransactionCallback with " + transaction + " for " + mName);
+            }
             transactionReadyConsumer.accept(transaction);
             synchronized (mLock) {
                 // If there's a registered listener with WMS, that means we aren't actually complete
                 // until WMS notifies us that the parent has completed.
                 if (mSurfaceSyncGroupCompletedListener == null) {
-                    invokeSyncCompleteListeners();
+                    invokeSyncCompleteCallbacks();
                 }
             }
         };
 
-        Trace.instant(Trace.TRACE_TAG_VIEW, "new SurfaceSyncGroup " + mName);
+        if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
+            Trace.instant(Trace.TRACE_TAG_VIEW, "new SurfaceSyncGroup " + mName);
+        }
 
         if (DEBUG) {
             Log.d(TAG, "setupSync " + mName + " " + Debug.getCallers(2));
@@ -177,7 +193,7 @@
     }
 
     @GuardedBy("mLock")
-    private void invokeSyncCompleteListeners() {
+    private void invokeSyncCompleteCallbacks() {
         mSyncCompleteCallbacks.forEach(
                 executorRunnablePair -> executorRunnablePair.first.execute(
                         executorRunnablePair.second));
@@ -188,6 +204,7 @@
      *
      * @param executor The Executor to invoke the Runnable on
      * @param runnable The Runnable to get called
+     * @hide
      */
     public void addSyncCompleteCallback(Executor executor, Runnable runnable) {
         synchronized (mLock) {
@@ -196,13 +213,16 @@
     }
 
     /**
-     * Mark the sync set as ready to complete. No more data can be added to the specified
-     * syncId.
-     * Once the sync set is marked as ready, it will be able to complete once all Syncables in the
-     * set have completed their sync
+     * Mark the SurfaceSyncGroup as ready to complete. No more data can be added to this
+     * SurfaceSyncGroup.
+     * <p>
+     * Once the SurfaceSyncGroup is marked as ready, it will be able to complete once all child
+     * SurfaceSyncGroup have completed their sync.
      */
     public void markSyncReady() {
-        Trace.traceBegin(Trace.TRACE_TAG_VIEW, "markSyncReady " + mName);
+        if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
+            Trace.traceBegin(Trace.TRACE_TAG_VIEW, "markSyncReady " + mName);
+        }
         synchronized (mLock) {
             if (mHasWMSync) {
                 try {
@@ -213,28 +233,29 @@
             mSyncReady = true;
             checkIfSyncIsComplete();
         }
-        Trace.traceEnd(Trace.TRACE_TAG_VIEW);
+        if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
+            Trace.traceEnd(Trace.TRACE_TAG_VIEW);
+        }
     }
 
     /**
-     * Add a SurfaceView to a sync set. This is different than
-     * {@link #addToSync(AttachedSurfaceControl)} because it requires the caller to notify the start
-     * and finish drawing in order to sync.
+     * Add a SurfaceView to a SurfaceSyncGroup. This requires the caller to notify the start
+     * and finish drawing in order to sync since the client owns the rendering of the SurfaceView.
      *
      * @param surfaceView           The SurfaceView to add to the sync.
      * @param frameCallbackConsumer The callback that's invoked to allow the caller to notify
-     *                              the
-     *                              Syncer when the SurfaceView has started drawing and
-     *                              finished.
+     *                              SurfaceSyncGroup when the SurfaceView has started drawing.
      * @return true if the SurfaceView was successfully added to the SyncGroup, false otherwise.
+     * @hide
      */
     @UiThread
-    public boolean addToSync(SurfaceView surfaceView,
+    public boolean add(SurfaceView surfaceView,
             Consumer<SurfaceViewFrameCallback> frameCallbackConsumer) {
         SurfaceSyncGroup surfaceSyncGroup = new SurfaceSyncGroup(surfaceView.getName());
-        if (addToSync(surfaceSyncGroup, false /* parentSyncGroupMerge */)) {
+        if (add(surfaceSyncGroup.mISurfaceSyncGroup, false /* parentSyncGroupMerge */,
+                null /* runnable */)) {
             frameCallbackConsumer.accept(() -> surfaceView.syncNextFrame(transaction -> {
-                surfaceSyncGroup.addTransactionToSync(transaction);
+                surfaceSyncGroup.addTransaction(transaction);
                 surfaceSyncGroup.markSyncReady();
             }));
             return true;
@@ -243,54 +264,53 @@
     }
 
     /**
-     * Add an AttachedSurfaceControl to a sync set.
+     * Add an AttachedSurfaceControl to the SurfaceSyncGroup. The AttachedSurfaceControl will pause
+     * rendering to ensure the runnable can be invoked and that the sync picks up the frame that
+     * contains the changes.
      *
-     * @param viewRoot The viewRoot that will be add to the sync set.
-     * @return true if the View was successfully added to the SyncGroup, false otherwise.
-     * @see #addToSync(AttachedSurfaceControl, Runnable)
+     * @param attachedSurfaceControl The AttachedSurfaceControl that will be add to this
+     *                               SurfaceSyncGroup.
+     * @param runnable               This is run on the same thread that the call was made on, but
+     *                               after the rendering is paused and before continuing to render
+     *                               the next frame. This method will not return until the
+     *                               execution of the runnable completes. This can be used to make
+     *                               changes to the AttachedSurfaceControl, ensuring that the
+     *                               changes are included in the sync.
+     * @return true if the AttachedSurfaceControl was successfully added to the SurfaceSyncGroup,
+     * false otherwise.
      */
     @UiThread
-    public boolean addToSync(@Nullable AttachedSurfaceControl viewRoot) {
-        return addToSync(viewRoot, null /* runnable */);
-    }
-
-    /**
-     * Add an AttachedSurfaceControl to a sync set. The AttachedSurfaceControl will pause rendering
-     * to ensure the runnable can be invoked and the sync picks up the frame that contains the
-     * changes.
-     *
-     * @param viewRoot The viewRoot that will be add to the sync set.
-     * @param runnable The runnable to be invoked before adding to the sync group.
-     * @return true if the View was successfully added to the SyncGroup, false otherwise.
-     * @see #addToSync(AttachedSurfaceControl)
-     */
-    @UiThread
-    public boolean addToSync(@Nullable AttachedSurfaceControl viewRoot,
+    public boolean add(@Nullable AttachedSurfaceControl attachedSurfaceControl,
             @Nullable Runnable runnable) {
-        if (viewRoot == null) {
+        if (attachedSurfaceControl == null) {
             return false;
         }
-        SurfaceSyncGroup surfaceSyncGroup = viewRoot.getOrCreateSurfaceSyncGroup();
+        SurfaceSyncGroup surfaceSyncGroup = attachedSurfaceControl.getOrCreateSurfaceSyncGroup();
         if (surfaceSyncGroup == null) {
             return false;
         }
 
-        return addToSync(surfaceSyncGroup, false /* parentSyncGroupMerge */, runnable);
+        return add(surfaceSyncGroup, runnable);
     }
 
     /**
-     * Helper method to add a SurfaceControlViewHost.SurfacePackage to the sync group. This will
+     * Add a SurfaceControlViewHost.SurfacePackage to the SurfaceSyncGroup. This will
      * get the SurfaceSyncGroup from the SurfacePackage, which will pause rendering for the
      * SurfaceControlViewHost. The runnable will be invoked to allow the host to update the SCVH
      * in a synchronized way. Finally, it will add the SCVH to the SurfaceSyncGroup and unpause
      * rendering in the SCVH, allowing the changes to get picked up and included in the sync.
      *
-     * @param surfacePackage The SurfacePackage that should be synced
-     * @param runnable       The Runnable that's invoked before getting the frame to sync.
-     * @return true if the SCVH was successfully added to the current SyncGroup, false
-     * otherwise.
+     * @param surfacePackage The SurfacePackage that will be added to this SurfaceSyncGroup.
+     * @param runnable       This is run on the same thread that the call was made on, but
+     *                       after the rendering is paused and before continuing to render
+     *                       the next frame. This method will not return until the
+     *                       execution of the runnable completes. This can be used to make
+     *                       changes to the SurfaceControlViewHost, ensuring that the
+     *                       changes are included in the sync.
+     * @return true if the SurfaceControlViewHost was successfully added to the current
+     * SurfaceSyncGroup, false otherwise.
      */
-    public boolean addToSync(@NonNull SurfaceControlViewHost.SurfacePackage surfacePackage,
+    public boolean add(@NonNull SurfaceControlViewHost.SurfacePackage surfacePackage,
             @Nullable Runnable runnable) {
         ISurfaceSyncGroup surfaceSyncGroup;
         try {
@@ -305,20 +325,31 @@
                     + "SCVH returned null SurfaceSyncGroup");
             return false;
         }
-        return addToSync(surfaceSyncGroup, false /* parentSyncGroupMerge */, runnable);
-    }
-
-    @Override
-    public boolean addToSync(ISurfaceSyncGroup surfaceSyncGroup, boolean parentSyncGroupMerge) {
-        return addToSync(surfaceSyncGroup, parentSyncGroupMerge, null);
+        return add(surfaceSyncGroup, false /* parentSyncGroupMerge */, runnable);
     }
 
     /**
-     * Add a {@link SurfaceSyncGroup} to a sync set. The sync set will wait for all
-     * SyncableSurfaces to complete before notifying.
+     * Add a SurfaceSyncGroup to the current SurfaceSyncGroup.
      *
-     * @param surfaceSyncGroup     A SyncableSurface that implements how to handle syncing
-     *                             buffers.
+     * @param surfaceSyncGroup The SurfaceSyncGroup that will be added to this SurfaceSyncGroup.
+     * @param runnable         This is run on the same thread that the call was made on, This
+     *                         method will not return until the execution of the runnable
+     *                         completes. This can be used to make changes to the SurfaceSyncGroup,
+     *                         ensuring that the changes are included in the sync.
+     * @return true if the requested SurfaceSyncGroup was successfully added to the
+     * SurfaceSyncGroup, false otherwise.
+     * @hide
+     */
+    public boolean add(@NonNull SurfaceSyncGroup surfaceSyncGroup,
+            @Nullable Runnable runnable) {
+        return add(surfaceSyncGroup.mISurfaceSyncGroup, false /* parentSyncGroupMerge */,
+                runnable);
+    }
+
+    /**
+     * Add a {@link ISurfaceSyncGroup} to a SurfaceSyncGroup.
+     *
+     * @param surfaceSyncGroup     An ISyncableSurface that will be added to this SurfaceSyncGroup.
      * @param parentSyncGroupMerge true if the ISurfaceSyncGroup is added because its child was
      *                             added to a new SurfaceSyncGroup. That would require the code to
      *                             call newParent.addToSync(oldParent). When this occurs, we need to
@@ -327,15 +358,20 @@
      * @param runnable             The Runnable that's invoked before adding the SurfaceSyncGroup
      * @return true if the SyncGroup was successfully added to the current SyncGroup, false
      * otherwise.
+     * @hide
      */
-    public boolean addToSync(ISurfaceSyncGroup surfaceSyncGroup, boolean parentSyncGroupMerge,
+    public boolean add(ISurfaceSyncGroup surfaceSyncGroup, boolean parentSyncGroupMerge,
             @Nullable Runnable runnable) {
-        Trace.traceBegin(Trace.TRACE_TAG_VIEW,
-                "addToSync token=" + mToken.hashCode() + " parent=" + mName);
+        if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
+            Trace.traceBegin(Trace.TRACE_TAG_VIEW,
+                    "addToSync token=" + mToken.hashCode() + " parent=" + mName);
+        }
         synchronized (mLock) {
             if (mSyncReady) {
                 Log.w(TAG, "Trying to add to sync when already marked as ready " + mName);
-                Trace.traceEnd(Trace.TRACE_TAG_VIEW);
+                if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
+                    Trace.traceEnd(Trace.TRACE_TAG_VIEW);
+                }
                 return false;
             }
         }
@@ -346,7 +382,9 @@
 
         if (isLocalBinder(surfaceSyncGroup.asBinder())) {
             boolean didAddLocalSync = addLocalSync(surfaceSyncGroup, parentSyncGroupMerge);
-            Trace.traceEnd(Trace.TRACE_TAG_VIEW);
+            if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
+                Trace.traceEnd(Trace.TRACE_TAG_VIEW);
+            }
             return didAddLocalSync;
         }
 
@@ -365,14 +403,16 @@
                     @Override
                     public void onSurfaceSyncGroupComplete() {
                         synchronized (mLock) {
-                            invokeSyncCompleteListeners();
+                            invokeSyncCompleteCallbacks();
                         }
                     }
                 };
                 if (!addSyncToWm(mToken, false /* parentSyncGroupMerge */,
                         mSurfaceSyncGroupCompletedListener)) {
                     mSurfaceSyncGroupCompletedListener = null;
-                    Trace.traceEnd(Trace.TRACE_TAG_VIEW);
+                    if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
+                        Trace.traceEnd(Trace.TRACE_TAG_VIEW);
+                    }
                     return false;
                 }
                 mHasWMSync = true;
@@ -382,38 +422,35 @@
         try {
             surfaceSyncGroup.onAddedToSyncGroup(mToken, parentSyncGroupMerge);
         } catch (RemoteException e) {
-            Trace.traceEnd(Trace.TRACE_TAG_VIEW);
+            if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
+                Trace.traceEnd(Trace.TRACE_TAG_VIEW);
+            }
             return false;
         }
 
-        Trace.traceEnd(Trace.TRACE_TAG_VIEW);
+        if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
+            Trace.traceEnd(Trace.TRACE_TAG_VIEW);
+        }
         return true;
     }
 
-    @Override
-    public final boolean onAddedToSyncGroup(IBinder parentSyncGroupToken,
-            boolean parentSyncGroupMerge) {
-        Trace.traceBegin(Trace.TRACE_TAG_VIEW,
-                "onAddedToSyncGroup token=" + parentSyncGroupToken.hashCode() + " child=" + mName);
-        boolean didAdd = addSyncToWm(parentSyncGroupToken, parentSyncGroupMerge, null);
-        Trace.traceEnd(Trace.TRACE_TAG_VIEW);
-        return didAdd;
-    }
-
-
     /**
-     * Add a Transaction to this sync set. This allows the caller to provide other info that
-     * should be synced with the transactions.
+     * Add a Transaction to this SurfaceSyncGroup. This allows the caller to provide other info that
+     * should be synced with the other transactions in this SurfaceSyncGroup.
+     *
+     * @param transaction The transaction to add to the SurfaceSyncGroup.
      */
-    public void addTransactionToSync(Transaction t) {
+    public void addTransaction(@NonNull Transaction transaction) {
         synchronized (mLock) {
-            mTransaction.merge(t);
+            mTransaction.merge(transaction);
         }
     }
 
     /**
      * Invoked when the SurfaceSyncGroup has been added to another SurfaceSyncGroup and is ready
      * to proceed.
+     *
+     * @hide
      */
     public void onSyncReady() {
     }
@@ -425,23 +462,31 @@
                 Log.d(TAG, "Attempting to add remote sync to " + mName
                         + ". Setting up Sync in WindowManager.");
             }
-            Trace.traceBegin(Trace.TRACE_TAG_VIEW,
-                    "addSyncToWm=" + token.hashCode() + " group=" + mName);
+            if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
+                Trace.traceBegin(Trace.TRACE_TAG_VIEW,
+                        "addSyncToWm=" + token.hashCode() + " group=" + mName);
+            }
             AddToSurfaceSyncGroupResult addToSyncGroupResult = new AddToSurfaceSyncGroupResult();
             if (!WindowManagerGlobal.getWindowManagerService().addToSurfaceSyncGroup(token,
                     parentSyncGroupMerge, surfaceSyncGroupCompletedListener,
                     addToSyncGroupResult)) {
-                Trace.traceEnd(Trace.TRACE_TAG_VIEW);
+                if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
+                    Trace.traceEnd(Trace.TRACE_TAG_VIEW);
+                }
                 return false;
             }
 
             setTransactionCallbackFromParent(addToSyncGroupResult.mParentSyncGroup,
                     addToSyncGroupResult.mTransactionReadyCallback);
         } catch (RemoteException e) {
-            Trace.traceEnd(Trace.TRACE_TAG_VIEW);
+            if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
+                Trace.traceEnd(Trace.TRACE_TAG_VIEW);
+            }
             return false;
         }
-        Trace.traceEnd(Trace.TRACE_TAG_VIEW);
+        if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
+            Trace.traceEnd(Trace.TRACE_TAG_VIEW);
+        }
         return true;
     }
 
@@ -457,8 +502,10 @@
             return false;
         }
 
-        Trace.traceBegin(Trace.TRACE_TAG_VIEW,
-                "addLocalSync=" + childSurfaceSyncGroup.mName + " parent=" + mName);
+        if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
+            Trace.traceBegin(Trace.TRACE_TAG_VIEW,
+                    "addLocalSync=" + childSurfaceSyncGroup.mName + " parent=" + mName);
+        }
         ITransactionReadyCallback callback =
                 createTransactionReadyCallback(parentSyncGroupMerge);
 
@@ -466,8 +513,10 @@
             return false;
         }
 
-        childSurfaceSyncGroup.setTransactionCallbackFromParent(this, callback);
-        Trace.traceEnd(Trace.TRACE_TAG_VIEW);
+        childSurfaceSyncGroup.setTransactionCallbackFromParent(mISurfaceSyncGroup, callback);
+        if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
+            Trace.traceEnd(Trace.TRACE_TAG_VIEW);
+        }
         return true;
     }
 
@@ -477,9 +526,11 @@
             Log.d(TAG, "setTransactionCallbackFromParent " + mName);
         }
         boolean finished = false;
-        Trace.traceBegin(Trace.TRACE_TAG_VIEW,
-                "setTransactionCallbackFromParent " + mName + " callback="
-                        + transactionReadyCallback.hashCode());
+        if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
+            Trace.traceBegin(Trace.TRACE_TAG_VIEW,
+                    "setTransactionCallbackFromParent " + mName + " callback="
+                            + transactionReadyCallback.hashCode());
+        }
         synchronized (mLock) {
             if (mFinished) {
                 finished = true;
@@ -510,9 +561,11 @@
                 Consumer<Transaction> lastCallback = mTransactionReadyConsumer;
                 mParentSyncGroup = parentSyncGroup;
                 mTransactionReadyConsumer = (transaction) -> {
-                    Trace.traceBegin(Trace.TRACE_TAG_VIEW,
-                            "transactionReadyCallback " + mName + " callback="
-                                    + transactionReadyCallback.hashCode());
+                    if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
+                        Trace.traceBegin(Trace.TRACE_TAG_VIEW,
+                                "transactionReadyCallback " + mName + " callback="
+                                        + transactionReadyCallback.hashCode());
+                    }
                     lastCallback.accept(null);
 
                     try {
@@ -520,7 +573,9 @@
                     } catch (RemoteException e) {
                         transaction.apply();
                     }
-                    Trace.traceEnd(Trace.TRACE_TAG_VIEW);
+                    if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
+                        Trace.traceEnd(Trace.TRACE_TAG_VIEW);
+                    }
                 };
             }
         }
@@ -535,9 +590,14 @@
         } else {
             onSyncReady();
         }
-        Trace.traceEnd(Trace.TRACE_TAG_VIEW);
+        if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
+            Trace.traceEnd(Trace.TRACE_TAG_VIEW);
+        }
     }
 
+    /**
+     * @hide
+     */
     public String getName() {
         return mName;
     }
@@ -552,9 +612,11 @@
             return;
         }
 
-        Trace.instant(Trace.TRACE_TAG_VIEW,
-                "checkIfSyncIsComplete " + mName + " mSyncReady=" + mSyncReady + " mPendingSyncs="
-                        + mPendingSyncs.size());
+        if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
+            Trace.instant(Trace.TRACE_TAG_VIEW,
+                    "checkIfSyncIsComplete " + mName + " mSyncReady=" + mSyncReady
+                            + " mPendingSyncs=" + mPendingSyncs.size());
+        }
 
         if (!mSyncReady || !mPendingSyncs.isEmpty()) {
             if (DEBUG) {
@@ -574,7 +636,7 @@
     /**
      * Create an {@link ITransactionReadyCallback} that the current SurfaceSyncGroup will wait on
      * before completing. The caller must ensure that the
-     * {@link ITransactionReadyCallback#onTransactionReady(Transaction)} in order for this
+     * {@link ITransactionReadyCallback#onTransactionReady(Transaction)} is called in order for this
      * SurfaceSyncGroup to complete.
      *
      * @param parentSyncGroupMerge true if the ISurfaceSyncGroup is added because its child was
@@ -582,6 +644,7 @@
      *                             call newParent.addToSync(oldParent). When this occurs, we need to
      *                             reverse the merge order because the oldParent should always be
      *                             considered older than any other SurfaceSyncGroups.
+     * @hide
      */
     public ITransactionReadyCallback createTransactionReadyCallback(boolean parentSyncGroupMerge) {
         if (DEBUG) {
@@ -603,9 +666,11 @@
                                 mTransaction.merge(t);
                             }
                             mPendingSyncs.remove(this);
-                            Trace.instant(Trace.TRACE_TAG_VIEW,
-                                    "onTransactionReady group=" + mName + " callback="
-                                            + hashCode());
+                            if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
+                                Trace.instant(Trace.TRACE_TAG_VIEW,
+                                        "onTransactionReady group=" + mName + " callback="
+                                                + hashCode());
+                            }
                             checkIfSyncIsComplete();
                         }
                     }
@@ -618,19 +683,50 @@
                 return null;
             }
             mPendingSyncs.add(transactionReadyCallback);
-            Trace.instant(Trace.TRACE_TAG_VIEW,
-                    "createTransactionReadyCallback " + mName + " mPendingSyncs="
-                            + mPendingSyncs.size() + " transactionReady="
-                            + transactionReadyCallback.hashCode());
+            if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
+                Trace.instant(Trace.TRACE_TAG_VIEW,
+                        "createTransactionReadyCallback " + mName + " mPendingSyncs="
+                                + mPendingSyncs.size() + " transactionReady="
+                                + transactionReadyCallback.hashCode());
+            }
         }
 
         return transactionReadyCallback;
     }
 
+    private class ISurfaceSyncGroupImpl extends ISurfaceSyncGroup.Stub {
+        @Override
+        public boolean onAddedToSyncGroup(IBinder parentSyncGroupToken,
+                boolean parentSyncGroupMerge) {
+            if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
+                Trace.traceBegin(Trace.TRACE_TAG_VIEW,
+                        "onAddedToSyncGroup token=" + parentSyncGroupToken.hashCode() + " child="
+                                + mName);
+            }
+            boolean didAdd = addSyncToWm(parentSyncGroupToken, parentSyncGroupMerge, null);
+            if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
+                Trace.traceEnd(Trace.TRACE_TAG_VIEW);
+            }
+            return didAdd;
+        }
+
+        @Override
+        public boolean addToSync(ISurfaceSyncGroup surfaceSyncGroup, boolean parentSyncGroupMerge) {
+            return SurfaceSyncGroup.this.add(surfaceSyncGroup, parentSyncGroupMerge,
+                    null /* runnable */);
+        }
+
+        SurfaceSyncGroup getSurfaceSyncGroup() {
+            return SurfaceSyncGroup.this;
+        }
+    }
+
     /**
      * A frame callback that is used to synchronize SurfaceViews. The owner of the SurfaceView must
      * implement onFrameStarted when trying to sync the SurfaceView. This is to ensure the sync
      * knows when the frame is ready to add to the sync.
+     *
+     * @hide
      */
     public interface SurfaceViewFrameCallback {
         /**
diff --git a/core/java/android/window/SurfaceSyncGroup.md b/core/java/android/window/SurfaceSyncGroup.md
index 406c230..04577a2 100644
--- a/core/java/android/window/SurfaceSyncGroup.md
+++ b/core/java/android/window/SurfaceSyncGroup.md
@@ -2,7 +2,7 @@
 
 ### Overview
 
-A generic way for data to be gathered so multiple surfaces can be synced. This is intended to be used with Views, SurfaceView, and any other surface that wants to be involved in a sync. This allows different parts of the Android system to synchronize different windows and layers themselves.
+A generic way for data to be gathered so multiple surfaces can be synced. This is intended to be used with AttachedSurfaceControl, SurfaceView, SurfaceControlViewHost, and any other surface that wants to be involved in a sync. This allows different parts of the Android system to synchronize different windows and layers themselves.
 
 ### Code
 
diff --git a/core/java/com/android/internal/os/IBinaryTransparencyService.aidl b/core/java/com/android/internal/os/IBinaryTransparencyService.aidl
index ca7b83e..e782aa7 100644
--- a/core/java/com/android/internal/os/IBinaryTransparencyService.aidl
+++ b/core/java/com/android/internal/os/IBinaryTransparencyService.aidl
@@ -16,6 +16,8 @@
 
 package com.android.internal.os;
 
+import android.os.Bundle;
+
 /**
  * "Backend" interface used by {@link android.os.BinaryTransparencyManager} to talk to the
  * BinaryTransparencyService that actually implements the measurement and information aggregation
@@ -36,6 +38,9 @@
         byte[] digest;
         int digestAlgorithm;
         String[] signerDigests;
+
+        // Test only
+        String moduleName;
     }
 
     parcelable AppInfo {
@@ -51,4 +56,8 @@
         String installer;
         String originator;
     }
+
+    /** Test only */
+    List<ApexInfo> collectAllApexInfo(boolean includeTestOnly);
+    List<AppInfo> collectAllUpdatedPreloadInfo(in Bundle packagesToSkip);
 }
\ No newline at end of file
diff --git a/core/java/com/android/internal/widget/LockPatternChecker.java b/core/java/com/android/internal/widget/LockPatternChecker.java
index 5adbc58..e56c381 100644
--- a/core/java/com/android/internal/widget/LockPatternChecker.java
+++ b/core/java/com/android/internal/widget/LockPatternChecker.java
@@ -1,7 +1,10 @@
 package com.android.internal.widget;
 
+import static android.provider.DeviceConfig.NAMESPACE_AUTO_PIN_CONFIRMATION;
+
 import android.annotation.NonNull;
 import android.os.AsyncTask;
+import android.provider.DeviceConfig;
 
 import com.android.internal.widget.LockPatternUtils.RequestThrottledException;
 
@@ -117,6 +120,10 @@
             @Override
             protected void onPostExecute(Boolean result) {
                 callback.onChecked(result, mThrottleTimeout);
+                if (DeviceConfig.getBoolean(NAMESPACE_AUTO_PIN_CONFIRMATION,
+                        "enable_auto_pin_confirmation", false)) {
+                    utils.setPinLength(userId, credentialCopy.size());
+                }
                 credentialCopy.zeroize();
             }
 
diff --git a/core/java/com/android/internal/widget/LockPatternUtils.java b/core/java/com/android/internal/widget/LockPatternUtils.java
index 86fd956..2f51479 100644
--- a/core/java/com/android/internal/widget/LockPatternUtils.java
+++ b/core/java/com/android/internal/widget/LockPatternUtils.java
@@ -48,6 +48,7 @@
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.os.storage.StorageManager;
+import android.provider.DeviceConfig;
 import android.provider.Settings;
 import android.text.TextUtils;
 import android.util.Log;
@@ -151,6 +152,8 @@
     @Deprecated
     public final static String LOCKSCREEN_WIDGETS_ENABLED = "lockscreen.widgets_enabled";
 
+    public static final String PIN_LENGTH = "lockscreen.pin_length";
+
     public final static String PASSWORD_HISTORY_KEY = "lockscreen.passwordhistory";
 
     private static final String LOCK_SCREEN_OWNER_INFO = Settings.Secure.LOCK_SCREEN_OWNER_INFO;
@@ -163,9 +166,17 @@
     private static final String KNOWN_TRUST_AGENTS = "lockscreen.knowntrustagents";
     private static final String IS_TRUST_USUALLY_MANAGED = "lockscreen.istrustusuallymanaged";
 
+    private static final String AUTO_PIN_CONFIRM = "lockscreen.auto_pin_confirm";
+
     public static final String CURRENT_LSKF_BASED_PROTECTOR_ID_KEY = "sp-handle";
     public static final String PASSWORD_HISTORY_DELIMITER = ",";
 
+    /**
+     * drives the pin auto confirmation feature availability in code logic.
+     */
+    public static final String FLAG_ENABLE_AUTO_PIN_CONFIRMATION =
+            "AutoPinConfirmation__enable_auto_pin_confirmation";
+
     @UnsupportedAppUsage
     private final Context mContext;
     @UnsupportedAppUsage
@@ -566,6 +577,24 @@
     }
 
     /**
+     * Used for setting the length of the PIN set by a particular user.
+     * @param userId user id of the user whose pin length we save
+     * @param val value of length of pin
+     */
+    public void setPinLength(int userId, long val) {
+        setLong(PIN_LENGTH, val, userId);
+    }
+
+    /**
+     * Returns the length of the PIN set by a particular user.
+     * @param userId user id of the user whose pin length we have to return
+     * @return the length of the pin set by user and -1 if nothing
+     */
+    public long getPinLength(int userId) {
+        return getLong(PIN_LENGTH, -1, userId);
+    }
+
+    /**
      * Records that the user has chosen a pattern at some time, even if the pattern is
      * currently cleared.
      */
@@ -627,6 +656,38 @@
                 || isDemoUser;
     }
 
+    /**
+     * Sets the pin auto confirm capability to enabled or disabled
+     * @param enabled enables pin auto confirm capability when true
+     * @param userId user ID of the user this has effect on
+     */
+    public void setAutoPinConfirm(boolean enabled, int userId) {
+        setBoolean(AUTO_PIN_CONFIRM, enabled, userId);
+    }
+
+    /**
+     * Determines if the auto pin confirmation feature is enabled or not for current user
+     * If setting is not available, the default behaviour is disabled
+     * @param userId user ID of the user this has effect on
+     *
+     * @return true, if the entered pin should be auto confirmed
+     */
+    public boolean isAutoPinConfirmEnabled(int userId) {
+        return getBoolean(AUTO_PIN_CONFIRM, /* defaultValue= */ false, userId);
+    }
+
+    /**
+     * Whether the auto pin feature logic is available or not.
+     * @return true, if deviceConfig flag is set to true or the flag is not propagated and
+     * defaultValue is true.
+     */
+    public boolean isAutoPinConfirmFeatureAvailable() {
+        return DeviceConfig.getBoolean(
+                DeviceConfig.NAMESPACE_AUTO_PIN_CONFIRMATION,
+                FLAG_ENABLE_AUTO_PIN_CONFIRMATION,
+                /* defaultValue= */ false);
+    }
+
     /** Returns if the given quality maps to an alphabetic password */
     public static boolean isQualityAlphabeticPassword(int quality) {
         return quality >= PASSWORD_QUALITY_ALPHABETIC;
diff --git a/core/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp
index 225ed2c..97a0f50 100644
--- a/core/jni/android_view_SurfaceControl.cpp
+++ b/core/jni/android_view_SurfaceControl.cpp
@@ -246,6 +246,17 @@
     jmethodID invokeReleaseCallback;
 } gSurfaceControlClassInfo;
 
+static struct {
+    jclass clazz;
+    jfieldID mMinAlpha;
+    jfieldID mMinFractionRendered;
+    jfieldID mStabilityRequirementMs;
+} gTrustedPresentationThresholdsClassInfo;
+
+static struct {
+    jmethodID onTrustedPresentationChanged;
+} gTrustedPresentationCallbackClassInfo;
+
 constexpr ui::Dataspace pickDataspaceFromColorMode(const ui::ColorMode colorMode) {
     switch (colorMode) {
         case ui::ColorMode::DISPLAY_P3:
@@ -328,6 +339,47 @@
     }
 };
 
+class TrustedPresentationCallbackWrapper {
+public:
+    explicit TrustedPresentationCallbackWrapper(JNIEnv* env, jobject trustedPresentationListener) {
+        env->GetJavaVM(&mVm);
+        mTrustedPresentationCallback = env->NewGlobalRef(trustedPresentationListener);
+        LOG_ALWAYS_FATAL_IF(!mTrustedPresentationCallback, "Failed to make global ref");
+    }
+
+    ~TrustedPresentationCallbackWrapper() {
+        getenv()->DeleteGlobalRef(mTrustedPresentationCallback);
+    }
+
+    void onTrustedPresentationChanged(bool inTrustedPresentationState) {
+        JNIEnv* env = getenv();
+        env->CallVoidMethod(mTrustedPresentationCallback,
+                            gTrustedPresentationCallbackClassInfo.onTrustedPresentationChanged,
+                            inTrustedPresentationState);
+    }
+
+    void addCallbackRef(const sp<SurfaceComposerClient::PresentationCallbackRAII>& callbackRef) {
+        mCallbackRef = callbackRef;
+    }
+
+    static void onTrustedPresentationChangedThunk(void* context, bool inTrustedPresentationState) {
+        TrustedPresentationCallbackWrapper* listener =
+                reinterpret_cast<TrustedPresentationCallbackWrapper*>(context);
+        listener->onTrustedPresentationChanged(inTrustedPresentationState);
+    }
+
+private:
+    jobject mTrustedPresentationCallback;
+    JavaVM* mVm;
+    sp<SurfaceComposerClient::PresentationCallbackRAII> mCallbackRef;
+
+    JNIEnv* getenv() {
+        JNIEnv* env;
+        mVm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6);
+        return env;
+    }
+};
+
 // ----------------------------------------------------------------------------
 
 static jlong nativeCreateTransaction(JNIEnv* env, jclass clazz) {
@@ -1806,6 +1858,42 @@
                                                  context);
 }
 
+static void nativeSetTrustedPresentationCallback(JNIEnv* env, jclass clazz, jlong transactionObj,
+                                                 jlong nativeObject,
+                                                 jlong trustedPresentationCallbackObject,
+                                                 jobject trustedPresentationThresholds) {
+    auto transaction = reinterpret_cast<SurfaceComposerClient::Transaction*>(transactionObj);
+    auto ctrl = reinterpret_cast<SurfaceControl*>(nativeObject);
+
+    TrustedPresentationThresholds thresholds;
+    thresholds.minAlpha = env->GetFloatField(trustedPresentationThresholds,
+                                             gTrustedPresentationThresholdsClassInfo.mMinAlpha);
+    thresholds.minFractionRendered =
+            env->GetFloatField(trustedPresentationThresholds,
+                               gTrustedPresentationThresholdsClassInfo.mMinFractionRendered);
+    thresholds.stabilityRequirementMs =
+            env->GetIntField(trustedPresentationThresholds,
+                             gTrustedPresentationThresholdsClassInfo.mStabilityRequirementMs);
+
+    sp<SurfaceComposerClient::PresentationCallbackRAII> callbackRef;
+    TrustedPresentationCallbackWrapper* wrapper =
+            reinterpret_cast<TrustedPresentationCallbackWrapper*>(
+                    trustedPresentationCallbackObject);
+    transaction->setTrustedPresentationCallback(ctrl,
+                                                TrustedPresentationCallbackWrapper::
+                                                        onTrustedPresentationChangedThunk,
+                                                thresholds, wrapper, callbackRef);
+    wrapper->addCallbackRef(callbackRef);
+}
+
+static void nativeClearTrustedPresentationCallback(JNIEnv* env, jclass clazz, jlong transactionObj,
+                                                   jlong nativeObject) {
+    auto transaction = reinterpret_cast<SurfaceComposerClient::Transaction*>(transactionObj);
+    auto ctrl = reinterpret_cast<SurfaceControl*>(nativeObject);
+
+    transaction->clearTrustedPresentationCallback(ctrl);
+}
+
 class JankDataListenerWrapper : public JankDataListener {
 public:
     JankDataListenerWrapper(JNIEnv* env, jobject onJankDataListenerObject) {
@@ -1919,6 +2007,21 @@
     return error == OK ? JNI_TRUE : JNI_FALSE;
 }
 
+jlong nativeCreateTpc(JNIEnv* env, jclass clazz, jobject trustedPresentationCallback) {
+    return reinterpret_cast<jlong>(
+            new TrustedPresentationCallbackWrapper(env, trustedPresentationCallback));
+}
+
+void destroyNativeTpc(void* ptr) {
+    TrustedPresentationCallbackWrapper* callback =
+            reinterpret_cast<TrustedPresentationCallbackWrapper*>(ptr);
+    delete callback;
+}
+
+static jlong getNativeTrustedPresentationCallbackFinalizer(JNIEnv* env, jclass clazz) {
+    return static_cast<jlong>(reinterpret_cast<uintptr_t>(&destroyNativeTpc));
+}
+
 // ----------------------------------------------------------------------------
 
 SurfaceControl* android_view_SurfaceControl_getNativeSurfaceControl(JNIEnv* env,
@@ -2145,6 +2248,10 @@
              (void*)nativeSetDropInputMode },
     {"nativeAddTransactionCommittedListener", "(JLandroid/view/SurfaceControl$TransactionCommittedListener;)V",
             (void*) nativeAddTransactionCommittedListener },
+    {"nativeSetTrustedPresentationCallback", "(JJJLandroid/view/SurfaceControl$TrustedPresentationThresholds;)V",
+            (void*) nativeSetTrustedPresentationCallback },
+    {"nativeClearTrustedPresentationCallback", "(JJ)V",
+            (void*) nativeClearTrustedPresentationCallback },
     {"nativeSanitize", "(J)V",
             (void*) nativeSanitize },
     {"nativeSetDestinationFrame", "(JJIIII)V",
@@ -2155,6 +2262,9 @@
                 (void*)nativeGetDefaultApplyToken },
     {"nativeBootFinished", "()Z",
             (void*)nativeBootFinished },
+    {"nativeCreateTpc", "(Landroid/view/SurfaceControl$TrustedPresentationCallback;)J",
+            (void*)nativeCreateTpc},
+    {"getNativeTrustedPresentationCallbackFinalizer", "()J", (void*)getNativeTrustedPresentationCallbackFinalizer },
         // clang-format on
 };
 
@@ -2381,6 +2491,23 @@
     gTransactionClassInfo.mNativeObject =
             GetFieldIDOrDie(env, gTransactionClassInfo.clazz, "mNativeObject", "J");
 
+    jclass trustedPresentationThresholdsClazz =
+            FindClassOrDie(env, "android/view/SurfaceControl$TrustedPresentationThresholds");
+    gTrustedPresentationThresholdsClassInfo.clazz =
+            MakeGlobalRefOrDie(env, trustedPresentationThresholdsClazz);
+    gTrustedPresentationThresholdsClassInfo.mMinAlpha =
+            GetFieldIDOrDie(env, trustedPresentationThresholdsClazz, "mMinAlpha", "F");
+    gTrustedPresentationThresholdsClassInfo.mMinFractionRendered =
+            GetFieldIDOrDie(env, trustedPresentationThresholdsClazz, "mMinFractionRendered", "F");
+    gTrustedPresentationThresholdsClassInfo.mStabilityRequirementMs =
+            GetFieldIDOrDie(env, trustedPresentationThresholdsClazz, "mStabilityRequirementMs",
+                            "I");
+
+    jclass trustedPresentationCallbackClazz =
+            FindClassOrDie(env, "android/view/SurfaceControl$TrustedPresentationCallback");
+    gTrustedPresentationCallbackClassInfo.onTrustedPresentationChanged =
+            GetMethodIDOrDie(env, trustedPresentationCallbackClazz, "onTrustedPresentationChanged",
+                             "(Z)V");
     return err;
 }
 
diff --git a/core/proto/android/server/windowmanagerservice.proto b/core/proto/android/server/windowmanagerservice.proto
index b112e7a..82e1777 100644
--- a/core/proto/android/server/windowmanagerservice.proto
+++ b/core/proto/android/server/windowmanagerservice.proto
@@ -117,7 +117,7 @@
     repeated KeyguardOccludedProto keyguard_occluded_states = 2 [deprecated=true];
     optional bool aod_showing = 3;
     repeated KeyguardPerDisplayProto keyguard_per_display = 4;
-    optional bool keyguard_going_away = 5 [deprecated=true];
+    optional bool keyguard_going_away = 5;
 }
 
 message KeyguardOccludedProto {
@@ -134,7 +134,7 @@
     optional bool keyguard_showing = 2;
     optional bool aod_showing = 3;
     optional bool keyguard_occluded = 4;
-    optional bool keyguard_going_away = 5 [deprecated=true];
+    optional bool keyguard_going_away = 5;
 }
 
 /* represents PhoneWindowManager */
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index d880296..a07d85e 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -2180,6 +2180,8 @@
     <bool name="config_enableDefaultNotes">false</bool>
     <!-- Whether the default notes role for work profile should be enabled. -->
     <bool name="config_enableDefaultNotesForWorkProfile">false</bool>
+    <!-- The name of the package that will hold the system financed device controller role. -->
+    <string name="config_systemFinancedDeviceController" translatable="false">com.android.devicelockcontroller</string>
 
     <!-- The name of the package that will handle updating the device management role. -->
     <string name="config_devicePolicyManagementUpdater" translatable="false"></string>
diff --git a/core/res/res/values/public-staging.xml b/core/res/res/values/public-staging.xml
index f7ed38c..0ec3ef7 100644
--- a/core/res/res/values/public-staging.xml
+++ b/core/res/res/values/public-staging.xml
@@ -146,6 +146,8 @@
     <public name="config_systemWearHealthService" />
     <!-- @hide @SystemApi -->
     <public name="config_defaultNotes" />
+    <!-- @hide @SystemApi -->
+    <public name="config_systemFinancedDeviceController" />
   </staging-public-group>
 
   <staging-public-group type="dimen" first-id="0x01ca0000">
diff --git a/core/tests/notificationtests/Android.bp b/core/tests/notificationtests/Android.bp
deleted file mode 100644
index 1c0e39d..0000000
--- a/core/tests/notificationtests/Android.bp
+++ /dev/null
@@ -1,24 +0,0 @@
-package {
-    // See: http://go/android-license-faq
-    // A large-scale-change added 'default_applicable_licenses' to import
-    // all of the 'license_kinds' from "frameworks_base_license"
-    // to get the below license kinds:
-    //   SPDX-license-identifier-Apache-2.0
-    default_applicable_licenses: ["frameworks_base_license"],
-}
-
-android_test {
-    name: "NotificationStressTests",
-    // Include all test java files.
-    srcs: ["src/**/*.java"],
-    libs: [
-        "android.test.runner",
-        "android.test.base",
-    ],
-    // Could build against SDK if it wasn't for the @RepetitiveTest annotation.
-    platform_apis: true,
-    static_libs: [
-        "junit",
-        "ub-uiautomator",
-    ],
-}
diff --git a/core/tests/notificationtests/AndroidManifest.xml b/core/tests/notificationtests/AndroidManifest.xml
deleted file mode 100644
index 51e530a..0000000
--- a/core/tests/notificationtests/AndroidManifest.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2011 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
-          package="com.android.notification.tests" >
-
-    <application >
-        <uses-library android:name="android.test.runner" />
-    </application>
-
-    <instrumentation
-        android:name="android.test.InstrumentationTestRunner"
-        android:targetPackage="com.android.notification.tests"
-        android:label="Notification Stress Tests" />
-
-</manifest>
diff --git a/core/tests/notificationtests/OWNERS b/core/tests/notificationtests/OWNERS
deleted file mode 100644
index 396fd12..0000000
--- a/core/tests/notificationtests/OWNERS
+++ /dev/null
@@ -1 +0,0 @@
-include /services/core/java/com/android/server/notification/OWNERS
diff --git a/core/tests/notificationtests/src/android/app/NotificationStressTest.java b/core/tests/notificationtests/src/android/app/NotificationStressTest.java
deleted file mode 100644
index b2914d8..0000000
--- a/core/tests/notificationtests/src/android/app/NotificationStressTest.java
+++ /dev/null
@@ -1,142 +0,0 @@
-/*
- * Copyright (C) 2011 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.app;
-
-import android.content.Context;
-import android.content.Intent;
-import android.net.Uri;
-import android.os.Build;
-import android.os.RemoteException;
-import android.os.SystemClock;
-import android.support.test.uiautomator.UiDevice;
-import android.test.InstrumentationTestCase;
-import android.test.RepetitiveTest;
-import android.util.Log;
-
-import java.lang.InterruptedException;
-import java.lang.reflect.Method;
-import java.util.Random;
-
-/**
- * Test which spams notification manager with a large number of notifications, for both stress and
- * performance testing.
- */
-public class NotificationStressTest extends InstrumentationTestCase {
-
-    private static final int NUM_ITERATIONS = 200;
-    private static final int NUM_ITERATIONS_2 = 30;
-    private static final int LONG_TIMEOUT = 2000;
-    // 49 notifications per app: defined as Variable MAX_PACKAGE_NOTIFICATIONS in
-    // NotificationManagerService.java
-    private static final int MAX_NOTIFCATIONS = 49;
-    private static final int[] ICONS = new int[] {
-            android.R.drawable.stat_notify_call_mute,
-            android.R.drawable.stat_notify_chat,
-            android.R.drawable.stat_notify_error,
-            android.R.drawable.stat_notify_missed_call,
-            android.R.drawable.stat_notify_more,
-            android.R.drawable.stat_notify_sdcard,
-            android.R.drawable.stat_notify_sdcard_prepare,
-            android.R.drawable.stat_notify_sdcard_usb,
-            android.R.drawable.stat_notify_sync,
-            android.R.drawable.stat_notify_sync_noanim,
-            android.R.drawable.stat_notify_voicemail,
-    };
-
-    private final Random mRandom = new Random();
-    private Context mContext;
-    private NotificationManager mNotificationManager;
-    private UiDevice mDevice = null;
-    private int mNotifyId = 0;
-
-    @Override
-    protected void setUp() throws Exception {
-        super.setUp();
-        mDevice = UiDevice.getInstance(getInstrumentation());
-        mContext = getInstrumentation().getContext();
-        mNotificationManager = (NotificationManager) mContext.getSystemService(
-                Context.NOTIFICATION_SERVICE);
-        mDevice.setOrientationNatural();
-        mNotificationManager.cancelAll();
-    }
-
-    @Override
-    protected void tearDown() throws Exception {
-        mDevice.unfreezeRotation();
-        mNotificationManager.cancelAll();
-        mDevice.waitForIdle();
-        super.tearDown();
-    }
-
-    @RepetitiveTest(numIterations = NUM_ITERATIONS)
-    public void testNotificationStress() {
-        // Cancel one of every five notifications to vary load on notification manager
-        if (mNotifyId % 5 == 4) {
-            mNotificationManager.cancel(mNotifyId - 4);
-        }
-        sendNotification(mNotifyId++, "testNotificationStressNotify");
-    }
-
-    @RepetitiveTest(numIterations = NUM_ITERATIONS_2)
-    public void testNotificationsWithShadeStress() throws Exception {
-        mDevice.openNotification();
-        Thread.sleep(LONG_TIMEOUT);
-        for (int j = 0; j < MAX_NOTIFCATIONS; j++) {
-            sendNotification(mNotifyId++, "testNotificationStressNotify");
-        }
-        Thread.sleep(LONG_TIMEOUT);
-        assertTrue(mNotificationManager.getActiveNotifications().length == MAX_NOTIFCATIONS);
-        for (int j = 0; j < MAX_NOTIFCATIONS; j++) {
-            mNotificationManager.cancel(--mNotifyId);
-        }
-        if (isLockScreen()) {
-            fail("Notification stress test failed, back to lockscreen");
-        }
-    }
-
-    private void sendNotification(int id, CharSequence text) {
-        // Fill in arbitrary content
-        Intent intent = new Intent(Intent.ACTION_VIEW).setPackage(mContext.getPackageName());
-        PendingIntent pendingIntent = PendingIntent.getActivity(mContext, 0, intent,
-                PendingIntent.FLAG_MUTABLE);
-        CharSequence title = text + " " + id;
-        CharSequence subtitle = String.valueOf(System.currentTimeMillis());
-        // Create "typical" notification with random icon
-        Notification notification = new Notification.Builder(mContext)
-                .setSmallIcon(ICONS[mRandom.nextInt(ICONS.length)])
-                .setTicker(text)
-                .setWhen(System.currentTimeMillis())
-                .setContentTitle(title)
-                .setContentText(subtitle)
-                .setContentIntent(pendingIntent)
-                .setPriority(Notification.PRIORITY_HIGH)
-                .build();
-        mNotificationManager.notify(id, notification);
-        //update rate limit is 50 notifications/second.
-        SystemClock.sleep(20);
-    }
-
-    private boolean isLockScreen() {
-        KeyguardManager myKM = (KeyguardManager) mContext
-                .getSystemService(Context.KEYGUARD_SERVICE);
-        if (myKM.inKeyguardRestrictedInputMode()) {
-            return true;
-        } else {
-            return false;
-        }
-    }
-}
diff --git a/keystore/java/android/security/KeyStoreException.java b/keystore/java/android/security/KeyStoreException.java
index 6536e43..cb75779 100644
--- a/keystore/java/android/security/KeyStoreException.java
+++ b/keystore/java/android/security/KeyStoreException.java
@@ -265,7 +265,7 @@
     private static int initializeRkpStatusForRegularErrors(int errorCode) {
         // Check if the system code mistakenly called a constructor of KeyStoreException with
         // the OUT_OF_KEYS error code but without RKP status.
-        if (isRkpRelatedError(errorCode)) {
+        if (errorCode == ResponseCode.OUT_OF_KEYS) {
             Log.e(TAG, "RKP error code without RKP status");
             // Set RKP status to RKP_SERVER_REFUSED_ISSUANCE so that the caller never retries.
             return RKP_SERVER_REFUSED_ISSUANCE;
@@ -301,7 +301,7 @@
         super(message);
         mErrorCode = errorCode;
         mRkpStatus = rkpStatus;
-        if (!isRkpRelatedError(mErrorCode)) {
+        if (mErrorCode != ResponseCode.OUT_OF_KEYS) {
             Log.e(TAG, "Providing RKP status for error code " + errorCode + " has no effect.");
         }
     }
@@ -338,7 +338,7 @@
     public boolean isTransientFailure() {
         PublicErrorInformation failureInfo = getErrorInformation(mErrorCode);
         // Special-case handling for RKP failures:
-        if (mRkpStatus != RKP_SUCCESS && isRkpRelatedError(mErrorCode)) {
+        if (mRkpStatus != RKP_SUCCESS && mErrorCode == ResponseCode.OUT_OF_KEYS) {
             switch (mRkpStatus) {
                 case RKP_TEMPORARILY_UNAVAILABLE:
                 case RKP_FETCHING_PENDING_CONNECTIVITY:
@@ -376,11 +376,6 @@
         return (failureInfo.indicators & IS_SYSTEM_ERROR) != 0;
     }
 
-    private static boolean isRkpRelatedError(int errorCode) {
-        return errorCode == ResponseCode.OUT_OF_KEYS
-                  || errorCode == ResponseCode.OUT_OF_KEYS_REQUIRES_UPGRADE;
-    }
-
     /**
      * Returns the re-try policy for transient failures. Valid only if
      * {@link #isTransientFailure()} returns {@code True}.
@@ -388,7 +383,7 @@
     @RetryPolicy
     public int getRetryPolicy() {
         PublicErrorInformation failureInfo = getErrorInformation(mErrorCode);
-        // Special-case handling for RKP failures:
+        // Special-case handling for RKP failures (To be removed in API 34)
         if (mRkpStatus != RKP_SUCCESS) {
             switch (mRkpStatus) {
                 case RKP_TEMPORARILY_UNAVAILABLE:
@@ -404,10 +399,14 @@
                             ? RETRY_WITH_EXPONENTIAL_BACKOFF : RETRY_NEVER;
             }
         }
-        if ((failureInfo.indicators & IS_TRANSIENT_ERROR) != 0) {
-            return RETRY_WITH_EXPONENTIAL_BACKOFF;
-        } else {
-            return RETRY_NEVER;
+        switch (mErrorCode) {
+            case ResponseCode.OUT_OF_KEYS_REQUIRES_SYSTEM_UPGRADE:
+                return RETRY_AFTER_NEXT_REBOOT;
+            case ResponseCode.OUT_OF_KEYS_PENDING_INTERNET_CONNECTIVITY:
+                return RETRY_WHEN_CONNECTIVITY_AVAILABLE;
+            default:
+                return (failureInfo.indicators & IS_TRANSIENT_ERROR) != 0
+                        ? RETRY_WITH_EXPONENTIAL_BACKOFF : RETRY_NEVER;
         }
     }
 
@@ -657,8 +656,16 @@
                 new PublicErrorInformation(0, ERROR_KEY_DOES_NOT_EXIST));
         sErrorCodeToFailureInfo.put(ResponseCode.OUT_OF_KEYS,
                 new PublicErrorInformation(IS_SYSTEM_ERROR, ERROR_ATTESTATION_KEYS_UNAVAILABLE));
-        sErrorCodeToFailureInfo.put(ResponseCode.OUT_OF_KEYS_REQUIRES_UPGRADE,
+        sErrorCodeToFailureInfo.put(ResponseCode.OUT_OF_KEYS_REQUIRES_SYSTEM_UPGRADE,
                 new PublicErrorInformation(IS_SYSTEM_ERROR | IS_TRANSIENT_ERROR,
                         ERROR_DEVICE_REQUIRES_UPGRADE_FOR_ATTESTATION));
+        sErrorCodeToFailureInfo.put(ResponseCode.OUT_OF_KEYS_PENDING_INTERNET_CONNECTIVITY,
+                new PublicErrorInformation(IS_SYSTEM_ERROR | IS_TRANSIENT_ERROR,
+                        ERROR_ATTESTATION_KEYS_UNAVAILABLE));
+        sErrorCodeToFailureInfo.put(ResponseCode.OUT_OF_KEYS_TRANSIENT_ERROR,
+                new PublicErrorInformation(IS_SYSTEM_ERROR | IS_TRANSIENT_ERROR,
+                        ERROR_ATTESTATION_KEYS_UNAVAILABLE));
+        sErrorCodeToFailureInfo.put(ResponseCode.OUT_OF_KEYS_PERMANENT_ERROR,
+                new PublicErrorInformation(IS_SYSTEM_ERROR, ERROR_ATTESTATION_KEYS_UNAVAILABLE));
     }
 }
diff --git a/libs/WindowManager/Shell/Android.bp b/libs/WindowManager/Shell/Android.bp
index f615ad6..0f45219 100644
--- a/libs/WindowManager/Shell/Android.bp
+++ b/libs/WindowManager/Shell/Android.bp
@@ -47,6 +47,7 @@
         "src/com/android/wm/shell/sysui/ShellSharedConstants.java",
         "src/com/android/wm/shell/common/TransactionPool.java",
         "src/com/android/wm/shell/animation/Interpolators.java",
+        "src/com/android/wm/shell/pip/PipContentOverlay.java",
         "src/com/android/wm/shell/startingsurface/SplashScreenExitAnimationUtils.java",
     ],
     path: "src",
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipContentOverlay.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipContentOverlay.java
index 7096a64..283b1ec 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipContentOverlay.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipContentOverlay.java
@@ -16,6 +16,7 @@
 
 package com.android.wm.shell.pip;
 
+import android.annotation.Nullable;
 import android.content.Context;
 import android.content.res.TypedArray;
 import android.graphics.Color;
@@ -41,6 +42,11 @@
         }
     }
 
+    @Nullable
+    public SurfaceControl getLeash() {
+        return mLeash;
+    }
+
     /**
      * Animates the internal {@link #mLeash} by a given fraction.
      * @param atomicTx {@link SurfaceControl.Transaction} to operate, you should not explicitly
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
index 36ce0a6..a3e05f2 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
@@ -64,7 +64,6 @@
 import static com.android.wm.shell.transition.TransitionAnimationHelper.edgeExtendWindow;
 import static com.android.wm.shell.transition.TransitionAnimationHelper.getTransitionBackgroundColorIfSet;
 import static com.android.wm.shell.transition.TransitionAnimationHelper.loadAttributeAnimation;
-import static com.android.wm.shell.transition.TransitionAnimationHelper.sDisableCustomTaskAnimationProperty;
 
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
@@ -568,7 +567,6 @@
         final boolean isTask = change.getTaskInfo() != null;
         final TransitionInfo.AnimationOptions options = info.getAnimationOptions();
         final int overrideType = options != null ? options.getType() : ANIM_NONE;
-        final boolean canCustomContainer = !isTask || !sDisableCustomTaskAnimationProperty;
         final Rect endBounds = Transitions.isClosingType(changeMode)
                 ? mRotator.getEndBoundsInStartRotation(change)
                 : change.getEndAbsBounds();
@@ -591,7 +589,7 @@
         } else if (type == TRANSIT_RELAUNCH) {
             a = mTransitionAnimation.createRelaunchAnimation(endBounds, mInsets, endBounds);
         } else if (overrideType == ANIM_CUSTOM
-                && (canCustomContainer || options.getOverrideTaskTransition())) {
+                && (!isTask || options.getOverrideTaskTransition())) {
             a = mTransitionAnimation.loadAnimationRes(options.getPackageName(), enter
                     ? options.getEnterResId() : options.getExitResId());
         } else if (overrideType == ANIM_OPEN_CROSS_PROFILE_APPS && enter) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/TransitionAnimationHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/TransitionAnimationHelper.java
index 6af81f1..1549355 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/TransitionAnimationHelper.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/TransitionAnimationHelper.java
@@ -43,7 +43,6 @@
 import android.graphics.PixelFormat;
 import android.graphics.Rect;
 import android.graphics.Shader;
-import android.os.SystemProperties;
 import android.view.Surface;
 import android.view.SurfaceControl;
 import android.view.animation.Animation;
@@ -59,21 +58,6 @@
 /** The helper class that provides methods for adding styles to transition animations. */
 public class TransitionAnimationHelper {
 
-    /**
-     * Restrict ability of activities overriding transition animation in a way such that
-     * an activity can do it only when the transition happens within a same task.
-     *
-     * @see android.app.Activity#overridePendingTransition(int, int)
-     */
-    private static final String DISABLE_CUSTOM_TASK_ANIMATION_PROPERTY =
-            "persist.wm.disable_custom_task_animation";
-
-    /**
-     * @see #DISABLE_CUSTOM_TASK_ANIMATION_PROPERTY
-     */
-    static final boolean sDisableCustomTaskAnimationProperty =
-            SystemProperties.getBoolean(DISABLE_CUSTOM_TASK_ANIMATION_PROPERTY, true);
-
     /** Loads the animation that is defined through attribute id for the given transition. */
     @Nullable
     public static Animation loadAttributeAnimation(@NonNull TransitionInfo info,
@@ -86,7 +70,6 @@
         final boolean isTask = change.getTaskInfo() != null;
         final TransitionInfo.AnimationOptions options = info.getAnimationOptions();
         final int overrideType = options != null ? options.getType() : ANIM_NONE;
-        final boolean canCustomContainer = !isTask || !sDisableCustomTaskAnimationProperty;
         final boolean isDream =
                 isTask && change.getTaskInfo().topActivityType == ACTIVITY_TYPE_DREAM;
         int animAttr = 0;
@@ -158,7 +141,7 @@
 
         Animation a = null;
         if (animAttr != 0) {
-            if (overrideType == ANIM_FROM_STYLE && canCustomContainer) {
+            if (overrideType == ANIM_FROM_STYLE && !isTask) {
                 a = transitionAnimation
                         .loadAnimationAttr(options.getPackageName(), options.getAnimations(),
                                 animAttr, translucent);
diff --git a/libs/hwui/jni/android_graphics_ColorSpace.cpp b/libs/hwui/jni/android_graphics_ColorSpace.cpp
index 232fd71..63d3f83 100644
--- a/libs/hwui/jni/android_graphics_ColorSpace.cpp
+++ b/libs/hwui/jni/android_graphics_ColorSpace.cpp
@@ -18,7 +18,6 @@
 
 #include "SkColor.h"
 #include "SkColorSpace.h"
-#include "SkHalf.h"
 
 using namespace android;
 
@@ -40,15 +39,58 @@
 
 ///////////////////////////////////////////////////////////////////////////////
 
+#if defined(__ANDROID__) // __fp16 is not defined on non-Android builds
 static float halfToFloat(uint16_t bits) {
-#ifdef __ANDROID__ // __fp16 is not defined on non-Android builds
     __fp16 h;
     memcpy(&h, &bits, 2);
     return (float)h;
-#else
-    return SkHalfToFloat(bits);
-#endif
 }
+#else
+// This is Skia's implementation of SkHalfToFloat, which is
+// based on Fabien Giesen's half_to_float_fast2()
+// see https://fgiesen.wordpress.com/2012/03/28/half-to-float-done-quic/
+static uint16_t halfMantissa(uint16_t h) {
+    return h & 0x03ff;
+}
+
+static uint16_t halfExponent(uint16_t h) {
+    return (h >> 10) & 0x001f;
+}
+
+static uint16_t halfSign(uint16_t h) {
+    return h >> 15;
+}
+
+union FloatUIntUnion {
+    uint32_t mUInt;    // this must come first for the initializations below to work
+    float    mFloat;
+};
+
+static float halfToFloat(uint16_t bits) {
+    static const FloatUIntUnion magic = { 126 << 23 };
+    FloatUIntUnion o;
+
+    if (halfExponent(bits) == 0) {
+        // Zero / Denormal
+        o.mUInt = magic.mUInt + halfMantissa(bits);
+        o.mFloat -= magic.mFloat;
+    } else {
+        // Set mantissa
+        o.mUInt = halfMantissa(bits) << 13;
+        // Set exponent
+        if (halfExponent(bits) == 0x1f) {
+            // Inf/NaN
+            o.mUInt |= (255 << 23);
+        } else {
+            o.mUInt |= ((127 - 15 + halfExponent(bits)) << 23);
+        }
+    }
+
+    // Set sign
+    o.mUInt |= (halfSign(bits) << 31);
+    return o.mFloat;
+}
+#endif // defined(__ANDROID__)
 
 SkColor4f GraphicsJNI::convertColorLong(jlong color) {
     if ((color & 0x3f) == 0) {
diff --git a/location/java/android/location/GnssCapabilities.java b/location/java/android/location/GnssCapabilities.java
index 11b5833..c6f32c2 100644
--- a/location/java/android/location/GnssCapabilities.java
+++ b/location/java/android/location/GnssCapabilities.java
@@ -129,21 +129,26 @@
      * @hide
      */
     public static GnssCapabilities empty() {
-        return new GnssCapabilities(0, 0, 0, Collections.emptyList());
+        return new GnssCapabilities(/*topFlags=*/ 0, /*isAdrCapabilityKnown=*/ false,
+                /*measurementCorrectionsFlags=*/ 0, /*powerFlags=*/ 0, /*gnssSignalTypes=*/
+                Collections.emptyList());
     }
 
     private final @TopHalCapabilityFlags int mTopFlags;
+    private final boolean mIsAdrCapabilityKnown;
     private final @SubHalMeasurementCorrectionsCapabilityFlags int mMeasurementCorrectionsFlags;
     private final @SubHalPowerCapabilityFlags int mPowerFlags;
     private final @NonNull List<GnssSignalType> mGnssSignalTypes;
 
     private GnssCapabilities(
             @TopHalCapabilityFlags int topFlags,
+            boolean isAdrCapabilityKnown,
             @SubHalMeasurementCorrectionsCapabilityFlags int measurementCorrectionsFlags,
             @SubHalPowerCapabilityFlags int powerFlags,
             @NonNull List<GnssSignalType> gnssSignalTypes) {
         Objects.requireNonNull(gnssSignalTypes);
         mTopFlags = topFlags;
+        mIsAdrCapabilityKnown = isAdrCapabilityKnown;
         mMeasurementCorrectionsFlags = measurementCorrectionsFlags;
         mPowerFlags = powerFlags;
         mGnssSignalTypes = Collections.unmodifiableList(gnssSignalTypes);
@@ -154,12 +159,13 @@
      *
      * @hide
      */
-    public GnssCapabilities withTopHalFlags(@TopHalCapabilityFlags int flags) {
-        if (mTopFlags == flags) {
+    public GnssCapabilities withTopHalFlags(@TopHalCapabilityFlags int flags,
+            boolean isAdrCapabilityKnown) {
+        if (mTopFlags == flags && mIsAdrCapabilityKnown == isAdrCapabilityKnown) {
             return this;
         } else {
-            return new GnssCapabilities(flags, mMeasurementCorrectionsFlags, mPowerFlags,
-                    mGnssSignalTypes);
+            return new GnssCapabilities(flags, isAdrCapabilityKnown,
+                    mMeasurementCorrectionsFlags, mPowerFlags, mGnssSignalTypes);
         }
     }
 
@@ -174,7 +180,7 @@
         if (mMeasurementCorrectionsFlags == flags) {
             return this;
         } else {
-            return new GnssCapabilities(mTopFlags, flags, mPowerFlags,
+            return new GnssCapabilities(mTopFlags, mIsAdrCapabilityKnown, flags, mPowerFlags,
                     mGnssSignalTypes);
         }
     }
@@ -189,8 +195,8 @@
         if (mPowerFlags == flags) {
             return this;
         } else {
-            return new GnssCapabilities(mTopFlags, mMeasurementCorrectionsFlags, flags,
-                    mGnssSignalTypes);
+            return new GnssCapabilities(mTopFlags, mIsAdrCapabilityKnown,
+                    mMeasurementCorrectionsFlags, flags, mGnssSignalTypes);
         }
     }
 
@@ -204,8 +210,8 @@
         if (mGnssSignalTypes.equals(gnssSignalTypes)) {
             return this;
         } else {
-            return new GnssCapabilities(mTopFlags, mMeasurementCorrectionsFlags, mPowerFlags,
-                    new ArrayList<>(gnssSignalTypes));
+            return new GnssCapabilities(mTopFlags, mIsAdrCapabilityKnown,
+                    mMeasurementCorrectionsFlags, mPowerFlags, new ArrayList<>(gnssSignalTypes));
         }
     }
 
@@ -372,16 +378,30 @@
      * Returns {@code true} if GNSS chipset supports accumulated delta range, {@code false}
      * otherwise.
      *
+     * <p>The value is only known if {@link #isAccumulatedDeltaRangeCapabilityKnown()} is
+     * true.
+     *
      * <p>The accumulated delta range information can be queried in
      * {@link android.location.GnssMeasurement#getAccumulatedDeltaRangeState()},
      * {@link android.location.GnssMeasurement#getAccumulatedDeltaRangeMeters()}, and
      * {@link android.location.GnssMeasurement#getAccumulatedDeltaRangeUncertaintyMeters()}.
      */
     public boolean hasAccumulatedDeltaRange() {
+        if (!mIsAdrCapabilityKnown) {
+            throw new IllegalStateException("Accumulated delta range capability is unknown.");
+        }
         return (mTopFlags & TOP_HAL_CAPABILITY_ACCUMULATED_DELTA_RANGE) != 0;
     }
 
     /**
+     * Returns {@code true} if {@link #hasAccumulatedDeltaRange()} is known, {@code false}
+     * otherwise.
+     */
+    public boolean isAccumulatedDeltaRangeCapabilityKnown() {
+        return mIsAdrCapabilityKnown;
+    }
+
+    /**
      * Returns {@code true} if GNSS chipset supports line-of-sight satellite identification
      * measurement corrections, {@code false} otherwise.
      */
@@ -488,6 +508,7 @@
 
         GnssCapabilities that = (GnssCapabilities) o;
         return mTopFlags == that.mTopFlags
+                && mIsAdrCapabilityKnown == that.mIsAdrCapabilityKnown
                 && mMeasurementCorrectionsFlags == that.mMeasurementCorrectionsFlags
                 && mPowerFlags == that.mPowerFlags
                 && mGnssSignalTypes.equals(that.mGnssSignalTypes);
@@ -495,15 +516,16 @@
 
     @Override
     public int hashCode() {
-        return Objects.hash(mTopFlags, mMeasurementCorrectionsFlags, mPowerFlags, mGnssSignalTypes);
+        return Objects.hash(mTopFlags, mIsAdrCapabilityKnown, mMeasurementCorrectionsFlags,
+                mPowerFlags, mGnssSignalTypes);
     }
 
     public static final @NonNull Creator<GnssCapabilities> CREATOR =
             new Creator<GnssCapabilities>() {
                 @Override
                 public GnssCapabilities createFromParcel(Parcel in) {
-                    return new GnssCapabilities(in.readInt(), in.readInt(), in.readInt(),
-                            in.createTypedArrayList(GnssSignalType.CREATOR));
+                    return new GnssCapabilities(in.readInt(), in.readBoolean(), in.readInt(),
+                            in.readInt(), in.createTypedArrayList(GnssSignalType.CREATOR));
                 }
 
                 @Override
@@ -520,6 +542,7 @@
     @Override
     public void writeToParcel(@NonNull Parcel parcel, int flags) {
         parcel.writeInt(mTopFlags);
+        parcel.writeBoolean(mIsAdrCapabilityKnown);
         parcel.writeInt(mMeasurementCorrectionsFlags);
         parcel.writeInt(mPowerFlags);
         parcel.writeTypedList(mGnssSignalTypes);
@@ -574,8 +597,10 @@
         if (hasMeasurementCorrectionsForDriving()) {
             builder.append("MEASUREMENT_CORRECTIONS_FOR_DRIVING ");
         }
-        if (hasAccumulatedDeltaRange()) {
+        if (mIsAdrCapabilityKnown && hasAccumulatedDeltaRange()) {
             builder.append("ACCUMULATED_DELTA_RANGE ");
+        } else if (!mIsAdrCapabilityKnown) {
+            builder.append("ACCUMULATED_DELTA_RANGE(unknown) ");
         }
         if (hasMeasurementCorrectionsLosSats()) {
             builder.append("LOS_SATS ");
@@ -622,12 +647,14 @@
     public static final class Builder {
 
         private @TopHalCapabilityFlags int mTopFlags;
+        private boolean mIsAdrCapabilityKnown;
         private @SubHalMeasurementCorrectionsCapabilityFlags int mMeasurementCorrectionsFlags;
         private @SubHalPowerCapabilityFlags int mPowerFlags;
         private @NonNull List<GnssSignalType> mGnssSignalTypes;
 
         public Builder() {
             mTopFlags = 0;
+            mIsAdrCapabilityKnown = false;
             mMeasurementCorrectionsFlags = 0;
             mPowerFlags = 0;
             mGnssSignalTypes = Collections.emptyList();
@@ -635,6 +662,7 @@
 
         public Builder(@NonNull GnssCapabilities capabilities) {
             mTopFlags = capabilities.mTopFlags;
+            mIsAdrCapabilityKnown = capabilities.mIsAdrCapabilityKnown;
             mMeasurementCorrectionsFlags = capabilities.mMeasurementCorrectionsFlags;
             mPowerFlags = capabilities.mPowerFlags;
             mGnssSignalTypes = capabilities.mGnssSignalTypes;
@@ -768,12 +796,22 @@
          * Sets accumulated delta range capability.
          */
         public @NonNull Builder setHasAccumulatedDeltaRange(boolean capable) {
+            mIsAdrCapabilityKnown = true;
             mTopFlags = setFlag(mTopFlags, TOP_HAL_CAPABILITY_ACCUMULATED_DELTA_RANGE,
                     capable);
             return this;
         }
 
         /**
+         * Clears accumulated delta range capability and sets it as unknown.
+         */
+        public @NonNull Builder clearIsAccumulatedDeltaRangeCapabilityKnown() {
+            mIsAdrCapabilityKnown = false;
+            mTopFlags = setFlag(mTopFlags, TOP_HAL_CAPABILITY_ACCUMULATED_DELTA_RANGE, false);
+            return this;
+        }
+
+        /**
          * Sets measurement corrections line-of-sight satellites capability.
          */
         public @NonNull Builder setHasMeasurementCorrectionsLosSats(boolean capable) {
@@ -864,8 +902,8 @@
          * Builds a new GnssCapabilities.
          */
         public @NonNull GnssCapabilities build() {
-            return new GnssCapabilities(mTopFlags, mMeasurementCorrectionsFlags, mPowerFlags,
-                    new ArrayList<>(mGnssSignalTypes));
+            return new GnssCapabilities(mTopFlags, mIsAdrCapabilityKnown,
+                    mMeasurementCorrectionsFlags, mPowerFlags, new ArrayList<>(mGnssSignalTypes));
         }
 
         private static int setFlag(int value, int flag, boolean set) {
diff --git a/packages/SettingsLib/Spa/build.gradle b/packages/SettingsLib/Spa/build.gradle
index ded6251..9117524a 100644
--- a/packages/SettingsLib/Spa/build.gradle
+++ b/packages/SettingsLib/Spa/build.gradle
@@ -19,12 +19,12 @@
         BUILD_TOOLS_VERSION = "30.0.3"
         MIN_SDK = 21
         TARGET_SDK = 33
-        jetpack_compose_version = '1.4.0-alpha03'
-        jetpack_compose_compiler_version = '1.3.2'
+        jetpack_compose_version = '1.4.0-alpha05'
+        jetpack_compose_compiler_version = '1.4.0'
     }
 }
 plugins {
     id 'com.android.application' version '7.3.1' apply false
     id 'com.android.library' version '7.3.1' apply false
-    id 'org.jetbrains.kotlin.android' version '1.7.20' apply false
+    id 'org.jetbrains.kotlin.android' version '1.8.0' apply false
 }
diff --git a/packages/SettingsLib/Spa/spa/build.gradle b/packages/SettingsLib/Spa/spa/build.gradle
index bf4ad75..e035615 100644
--- a/packages/SettingsLib/Spa/spa/build.gradle
+++ b/packages/SettingsLib/Spa/spa/build.gradle
@@ -75,9 +75,9 @@
     api "androidx.slice:slice-builders:1.1.0-alpha02"
     api "androidx.slice:slice-core:1.1.0-alpha02"
     api "androidx.slice:slice-view:1.1.0-alpha02"
-    api "androidx.compose.material3:material3:1.1.0-alpha03"
+    api "androidx.compose.material3:material3:1.1.0-alpha05"
     api "androidx.compose.material:material-icons-extended:$jetpack_compose_version"
-    api "androidx.compose.runtime:runtime-livedata:$jetpack_compose_version"
+    api "androidx.compose.runtime:runtime-livedata:1.4.0-alpha04"
     api "androidx.compose.ui:ui-tooling-preview:$jetpack_compose_version"
     api "androidx.lifecycle:lifecycle-livedata-ktx:$jetpack_lifecycle_version"
     api "androidx.lifecycle:lifecycle-runtime-compose:$jetpack_lifecycle_version"
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/debug/DebugActivity.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/debug/DebugActivity.kt
index f7cbdae..72f662f 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/debug/DebugActivity.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/debug/DebugActivity.kt
@@ -62,7 +62,7 @@
 /**
  * The Debug Activity to display all Spa Pages & Entries.
  * One can open the debug activity by:
- *   $ adb shell am start -n <Package>/com.android.settingslib.spa.framework.debug.DebugActivity
+ *   $ adb shell am start -n <Package>/com.android.settingslib.spa.debug.DebugActivity
  * For gallery, Package = com.android.settingslib.spa.gallery
  */
 class DebugActivity : ComponentActivity() {
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/CustomizedAppBar.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/CustomizedAppBar.kt
index ae88ed7..e8b5b19 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/CustomizedAppBar.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/CustomizedAppBar.kt
@@ -50,6 +50,8 @@
 import androidx.compose.runtime.SideEffect
 import androidx.compose.runtime.Stable
 import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
 import androidx.compose.runtime.rememberUpdatedState
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
@@ -61,6 +63,7 @@
 import androidx.compose.ui.layout.LastBaseline
 import androidx.compose.ui.layout.Layout
 import androidx.compose.ui.layout.layoutId
+import androidx.compose.ui.layout.onGloballyPositioned
 import androidx.compose.ui.platform.LocalDensity
 import androidx.compose.ui.semantics.clearAndSetSemantics
 import androidx.compose.ui.text.TextStyle
@@ -116,7 +119,7 @@
         actions = actions,
         colors = topAppBarColors(),
         windowInsets = TopAppBarDefaults.windowInsets,
-        maxHeight = 176.dp,
+        maxHeightWithoutTitle = 120.dp,
         pinnedHeight = ContainerHeight,
         scrollBehavior = scrollBehavior,
     )
@@ -258,8 +261,8 @@
  * A two-rows top app bar that is designed to be called by the Large and Medium top app bar
  * composables.
  *
- * @throws [IllegalArgumentException] if the given [maxHeight] is equal or smaller than the
- * [pinnedHeight]
+ * @throws [IllegalArgumentException] if the given [maxHeightWithoutTitle] is equal or smaller than
+ * the [pinnedHeight]
  */
 @OptIn(ExperimentalMaterial3Api::class)
 @Composable
@@ -274,29 +277,31 @@
     actions: @Composable RowScope.() -> Unit,
     windowInsets: WindowInsets,
     colors: TopAppBarColors,
-    maxHeight: Dp,
+    maxHeightWithoutTitle: Dp,
     pinnedHeight: Dp,
     scrollBehavior: TopAppBarScrollBehavior?
 ) {
-    if (maxHeight <= pinnedHeight) {
+    if (maxHeightWithoutTitle <= pinnedHeight) {
         throw IllegalArgumentException(
             "A TwoRowsTopAppBar max height should be greater than its pinned height"
         )
     }
     val pinnedHeightPx: Float
-    val maxHeightPx: Float
+    val density = LocalDensity.current
+    val maxHeightPx = density.run {
+        remember { mutableStateOf((maxHeightWithoutTitle + pinnedHeight).toPx()) }
+    }
     val titleBottomPaddingPx: Int
-    LocalDensity.current.run {
+    density.run {
         pinnedHeightPx = pinnedHeight.toPx()
-        maxHeightPx = maxHeight.toPx()
         titleBottomPaddingPx = titleBottomPadding.roundToPx()
     }
 
     // Sets the app bar's height offset limit to hide just the bottom title area and keep top title
     // visible when collapsed.
     SideEffect {
-        if (scrollBehavior?.state?.heightOffsetLimit != pinnedHeightPx - maxHeightPx) {
-            scrollBehavior?.state?.heightOffsetLimit = pinnedHeightPx - maxHeightPx
+        if (scrollBehavior?.state?.heightOffsetLimit != pinnedHeightPx - maxHeightPx.value) {
+            scrollBehavior?.state?.heightOffsetLimit = pinnedHeightPx - maxHeightPx.value
         }
     }
 
@@ -366,12 +371,19 @@
             )
             TopAppBarLayout(
                 modifier = Modifier.clipToBounds(),
-                heightPx = maxHeightPx - pinnedHeightPx + (scrollBehavior?.state?.heightOffset
-                    ?: 0f),
+                heightPx = maxHeightPx.value - pinnedHeightPx +
+                    (scrollBehavior?.state?.heightOffset ?: 0f),
                 navigationIconContentColor = colors.navigationIconContentColor,
                 titleContentColor = colors.titleContentColor,
                 actionIconContentColor = colors.actionIconContentColor,
-                title = title,
+                title = {
+                    Box(modifier = Modifier.onGloballyPositioned { coordinates ->
+                        density.run {
+                            maxHeightPx.value =
+                                maxHeightWithoutTitle.toPx() + coordinates.size.height.toFloat()
+                        }
+                    }) { title() }
+                },
                 titleTextStyle = titleTextStyle,
                 titleAlpha = bottomTitleAlpha,
                 titleVerticalArrangement = Arrangement.Bottom,
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt
index c729b09..17a94b86 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt
@@ -41,6 +41,7 @@
 import androidx.annotation.UiThread
 import com.android.internal.annotations.VisibleForTesting
 import com.android.internal.policy.ScreenDecorationsUtils
+import java.lang.IllegalArgumentException
 import kotlin.math.roundToInt
 
 private const val TAG = "ActivityLaunchAnimator"
@@ -338,13 +339,24 @@
              * Return a [Controller] that will animate and expand [view] into the opening window.
              *
              * Important: The view must be attached to a [ViewGroup] when calling this function and
-             * during the animation. For safety, this method will return null when it is not.
+             * during the animation. For safety, this method will return null when it is not. The
+             * view must also implement [LaunchableView], otherwise this method will throw.
              *
              * Note: The background of [view] should be a (rounded) rectangle so that it can be
              * properly animated.
              */
             @JvmStatic
             fun fromView(view: View, cujType: Int? = null): Controller? {
+                // Make sure the View we launch from implements LaunchableView to avoid visibility
+                // issues.
+                if (view !is LaunchableView) {
+                    throw IllegalArgumentException(
+                        "An ActivityLaunchAnimator.Controller was created from a View that does " +
+                            "not implement LaunchableView. This can lead to subtle bugs where the" +
+                            " visibility of the View we are launching from is not what we expected."
+                    )
+                }
+
                 if (view.parent !is ViewGroup) {
                     Log.e(
                         TAG,
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt
index e91a671..b8d78fb 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt
@@ -40,6 +40,7 @@
 import com.android.systemui.animation.back.applyTo
 import com.android.systemui.animation.back.floatingSystemSurfacesForSysUi
 import com.android.systemui.animation.back.onBackAnimationCallbackFrom
+import java.lang.IllegalArgumentException
 import kotlin.math.roundToInt
 
 private const val TAG = "DialogLaunchAnimator"
@@ -157,12 +158,23 @@
              * Create a [Controller] that can animate [source] to and from a dialog.
              *
              * Important: The view must be attached to a [ViewGroup] when calling this function and
-             * during the animation. For safety, this method will return null when it is not.
+             * during the animation. For safety, this method will return null when it is not. The
+             * view must also implement [LaunchableView], otherwise this method will throw.
              *
              * Note: The background of [view] should be a (rounded) rectangle so that it can be
              * properly animated.
              */
             fun fromView(source: View, cuj: DialogCuj? = null): Controller? {
+                // Make sure the View we launch from implements LaunchableView to avoid visibility
+                // issues.
+                if (source !is LaunchableView) {
+                    throw IllegalArgumentException(
+                        "A DialogLaunchAnimator.Controller was created from a View that does not " +
+                            "implement LaunchableView. This can lead to subtle bugs where the " +
+                            "visibility of the View we are launching from is not what we expected."
+                    )
+                }
+
                 if (source.parent !is ViewGroup) {
                     Log.e(
                         TAG,
@@ -249,23 +261,6 @@
             }
                 ?: controller
 
-        if (
-            animatedParent == null &&
-                controller is ViewDialogLaunchAnimatorController &&
-                controller.source !is LaunchableView
-        ) {
-            // Make sure the View we launch from implements LaunchableView to avoid visibility
-            // issues. Given that we don't own dialog decorViews so we can't enforce it for launches
-            // from a dialog.
-            // TODO(b/243636422): Throw instead of logging to enforce this.
-            Log.w(
-                TAG,
-                "A dialog was launched from a View that does not implement LaunchableView. This " +
-                    "can lead to subtle bugs where the visibility of the View we are " +
-                    "launching from is not what we expected."
-            )
-        }
-
         // Make sure we don't run the launch animation from the same source twice at the same time.
         if (openedDialogs.any { it.controller.sourceIdentity == controller.sourceIdentity }) {
             Log.e(
@@ -613,10 +608,16 @@
                 }
 
                 // Animate that view with the background. Throw if we didn't find one, because
-                // otherwise
-                // it's not clear what we should animate.
+                // otherwise it's not clear what we should animate.
+                if (viewGroupWithBackground == null) {
+                    error("Unable to find ViewGroup with background")
+                }
+
+                if (viewGroupWithBackground !is LaunchableView) {
+                    error("The animated ViewGroup with background must implement LaunchableView")
+                }
+
                 viewGroupWithBackground
-                    ?: throw IllegalStateException("Unable to find ViewGroup with background")
             } else {
                 // We will make the dialog window (and therefore its DecorView) fullscreen to make
                 // it possible to animate outside its bounds.
@@ -639,7 +640,7 @@
                     FrameLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT)
                 )
 
-                val dialogContentWithBackground = FrameLayout(dialog.context)
+                val dialogContentWithBackground = LaunchableFrameLayout(dialog.context)
                 dialogContentWithBackground.background = decorView.background
 
                 // Make the window background transparent. Note that setting the window (or
@@ -720,7 +721,10 @@
 
         // Make the background view invisible until we start the animation. We use the transition
         // visibility like GhostView does so that we don't mess up with the accessibility tree (see
-        // b/204944038#comment17).
+        // b/204944038#comment17). Given that this background implements LaunchableView, we call
+        // setShouldBlockVisibilityChanges() early so that the current visibility (VISIBLE) is
+        // restored at the end of the animation.
+        dialogContentWithBackground.setShouldBlockVisibilityChanges(true)
         dialogContentWithBackground.setTransitionVisibility(View.INVISIBLE)
 
         // Make sure the dialog is visible instantly and does not do any window animation.
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewLaunchAnimatorController.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewLaunchAnimatorController.kt
index 26aa0e8..23e3a01 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewLaunchAnimatorController.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewLaunchAnimatorController.kt
@@ -34,6 +34,7 @@
 import android.view.ViewGroupOverlay
 import android.widget.FrameLayout
 import com.android.internal.jank.InteractionJankMonitor
+import java.lang.IllegalArgumentException
 import java.util.LinkedList
 import kotlin.math.min
 import kotlin.math.roundToInt
@@ -46,7 +47,8 @@
  * of the ghosted view.
  *
  * Important: [ghostedView] must be attached to a [ViewGroup] when calling this function and during
- * the animation.
+ * the animation. It must also implement [LaunchableView], otherwise an exception will be thrown
+ * during this controller instantiation.
  *
  * Note: Avoid instantiating this directly and call [ActivityLaunchAnimator.Controller.fromView]
  * whenever possible instead.
@@ -101,6 +103,15 @@
     private val background: Drawable?
 
     init {
+        // Make sure the View we launch from implements LaunchableView to avoid visibility issues.
+        if (ghostedView !is LaunchableView) {
+            throw IllegalArgumentException(
+                "A GhostedViewLaunchAnimatorController was created from a View that does not " +
+                    "implement LaunchableView. This can lead to subtle bugs where the visibility " +
+                    "of the View we are launching from is not what we expected."
+            )
+        }
+
         /** Find the first view with a background in [view] and its children. */
         fun findBackground(view: View): Drawable? {
             if (view.background != null) {
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/LaunchableFrameLayout.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/LaunchableFrameLayout.kt
new file mode 100644
index 0000000..2eb503b
--- /dev/null
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/LaunchableFrameLayout.kt
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.animation
+
+import android.content.Context
+import android.util.AttributeSet
+import android.widget.FrameLayout
+
+/** A [FrameLayout] that also implements [LaunchableView]. */
+open class LaunchableFrameLayout : FrameLayout, LaunchableView {
+    private val delegate =
+        LaunchableViewDelegate(
+            this,
+            superSetVisibility = { super.setVisibility(it) },
+        )
+
+    constructor(context: Context) : super(context)
+    constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)
+    constructor(
+        context: Context,
+        attrs: AttributeSet?,
+        defStyleAttr: Int
+    ) : super(context, attrs, defStyleAttr)
+
+    constructor(
+        context: Context,
+        attrs: AttributeSet?,
+        defStyleAttr: Int,
+        defStyleRes: Int
+    ) : super(context, attrs, defStyleAttr, defStyleRes)
+
+    override fun setShouldBlockVisibilityChanges(block: Boolean) {
+        delegate.setShouldBlockVisibilityChanges(block)
+    }
+
+    override fun setVisibility(visibility: Int) {
+        delegate.setVisibility(visibility)
+    }
+}
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/ViewDialogLaunchAnimatorController.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/ViewDialogLaunchAnimatorController.kt
index 9257f99..46d5a5c 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/ViewDialogLaunchAnimatorController.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/ViewDialogLaunchAnimatorController.kt
@@ -25,7 +25,7 @@
 /** A [DialogLaunchAnimator.Controller] that can animate a [View] from/to a dialog. */
 class ViewDialogLaunchAnimatorController
 internal constructor(
-    internal val source: View,
+    private val source: View,
     override val cuj: DialogCuj?,
 ) : DialogLaunchAnimator.Controller {
     override val viewRoot: ViewRootImpl?
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/ViewRootSync.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/ViewRootSync.kt
index fd9355d..e4f6db5 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/ViewRootSync.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/ViewRootSync.kt
@@ -30,8 +30,8 @@
 
         val syncGroup = SurfaceSyncGroup("SysUIAnimation")
         syncGroup.addSyncCompleteCallback(view.context.mainExecutor) { then() }
-        syncGroup.addToSync(view.rootSurfaceControl)
-        syncGroup.addToSync(otherView.rootSurfaceControl)
+        syncGroup.add(view.rootSurfaceControl, null /* runnable */)
+        syncGroup.add(otherView.rootSurfaceControl, null /* runnable */)
         syncGroup.markSyncReady()
     }
 
diff --git a/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/ripple/RippleAnimation.kt b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/ripple/RippleAnimation.kt
index 0e3d41c..7897934 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/ripple/RippleAnimation.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/ripple/RippleAnimation.kt
@@ -48,7 +48,7 @@
         animator.addUpdateListener { updateListener ->
             val now = updateListener.currentPlayTime
             val progress = updateListener.animatedValue as Float
-            rippleShader.progress = progress
+            rippleShader.rawProgress = progress
             rippleShader.distortionStrength = if (config.shouldDistort) 1 - progress else 0f
             rippleShader.time = now.toFloat()
         }
diff --git a/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/ripple/RippleShader.kt b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/ripple/RippleShader.kt
index 9058510..4322d53 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/ripple/RippleShader.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/ripple/RippleShader.kt
@@ -18,6 +18,7 @@
 import android.graphics.PointF
 import android.graphics.RuntimeShader
 import android.util.MathUtils
+import com.android.systemui.animation.Interpolators
 import com.android.systemui.surfaceeffects.shaderutil.SdfShaderLibrary
 import com.android.systemui.surfaceeffects.shaderutil.ShaderUtilLibrary
 
@@ -47,7 +48,6 @@
             """
             uniform vec2 in_center;
             uniform vec2 in_size;
-            uniform float in_progress;
             uniform float in_cornerRadius;
             uniform float in_thickness;
             uniform float in_time;
@@ -162,21 +162,15 @@
         maxSize.y = height
     }
 
-    /** Progress of the ripple. Float value between [0, 1]. */
-    var progress: Float = 0.0f
+    /**
+     * Linear progress of the ripple. Float value between [0, 1].
+     *
+     * <p>Note that the progress here is expected to be linear without any curve applied.
+     */
+    var rawProgress: Float = 0.0f
         set(value) {
             field = value
-            setFloatUniform("in_progress", value)
-            val curvedProg = 1 - (1 - value) * (1 - value) * (1 - value)
-
-            currentWidth = maxSize.x * curvedProg
-            currentHeight = maxSize.y * curvedProg
-            setFloatUniform("in_size", /* width= */ currentWidth, /* height= */ currentHeight)
-            setFloatUniform("in_thickness", maxSize.y * curvedProg * 0.5f)
-            // radius should not exceed width and height values.
-            setFloatUniform("in_cornerRadius", Math.min(maxSize.x, maxSize.y) * curvedProg)
-
-            setFloatUniform("in_blur", MathUtils.lerp(1.25f, 0.5f, value))
+            progress = Interpolators.STANDARD.getInterpolation(value)
 
             val fadeIn = subProgress(0f, 0.1f, value)
             val fadeOutNoise = subProgress(0.4f, 1f, value)
@@ -191,6 +185,20 @@
             setFloatUniform("in_fadeRing", Math.min(fadeIn, 1 - fadeOutRipple))
         }
 
+    /** Progress with Standard easing curve applied. */
+    private var progress: Float = 0.0f
+        set(value) {
+            currentWidth = maxSize.x * value
+            currentHeight = maxSize.y * value
+            setFloatUniform("in_size", currentWidth, currentHeight)
+
+            setFloatUniform("in_thickness", maxSize.y * value * 0.5f)
+            // radius should not exceed width and height values.
+            setFloatUniform("in_cornerRadius", Math.min(maxSize.x, maxSize.y) * value)
+
+            setFloatUniform("in_blur", MathUtils.lerp(1.25f, 0.5f, value))
+        }
+
     /** Play time since the start of the effect. */
     var time: Float = 0.0f
         set(value) {
@@ -220,7 +228,7 @@
     var distortionStrength: Float = 0.0f
         set(value) {
             field = value
-            setFloatUniform("in_distort_radial", 75 * progress * value)
+            setFloatUniform("in_distort_radial", 75 * rawProgress * value)
             setFloatUniform("in_distort_xy", 75 * value)
         }
 
diff --git a/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/ripple/RippleView.kt b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/ripple/RippleView.kt
index b37c734..bc4796a 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/ripple/RippleView.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/ripple/RippleView.kt
@@ -77,7 +77,7 @@
         rippleShader = RippleShader(rippleShape)
 
         rippleShader.color = RippleAnimationConfig.RIPPLE_DEFAULT_COLOR
-        rippleShader.progress = 0f
+        rippleShader.rawProgress = 0f
         rippleShader.sparkleStrength = RippleAnimationConfig.RIPPLE_SPARKLE_STRENGTH
         rippleShader.pixelDensity = resources.displayMetrics.density
 
@@ -93,7 +93,7 @@
         animator.addUpdateListener { updateListener ->
             val now = updateListener.currentPlayTime
             val progress = updateListener.animatedValue as Float
-            rippleShader.progress = progress
+            rippleShader.rawProgress = progress
             rippleShader.distortionStrength = 1 - progress
             rippleShader.time = now.toFloat()
             invalidate()
@@ -142,25 +142,13 @@
         }
         // To reduce overdraw, we mask the effect to a circle or a rectangle that's bigger than the
         // active effect area. Values here should be kept in sync with the animation implementation
-        // in the ripple shader.
+        // in the ripple shader. (Twice bigger)
         if (rippleShape == RippleShape.CIRCLE) {
-            val maskRadius =
-                (1 -
-                    (1 - rippleShader.progress) *
-                        (1 - rippleShader.progress) *
-                        (1 - rippleShader.progress)) * maxWidth
+            val maskRadius = rippleShader.currentWidth
             canvas.drawCircle(centerX, centerY, maskRadius, ripplePaint)
         } else {
-            val maskWidth =
-                (1 -
-                    (1 - rippleShader.progress) *
-                        (1 - rippleShader.progress) *
-                        (1 - rippleShader.progress)) * maxWidth * 2
-            val maskHeight =
-                (1 -
-                    (1 - rippleShader.progress) *
-                        (1 - rippleShader.progress) *
-                        (1 - rippleShader.progress)) * maxHeight * 2
+            val maskWidth = rippleShader.currentWidth * 2
+            val maskHeight = rippleShader.currentHeight * 2
             canvas.drawRect(
                 /* left= */ centerX - maskWidth,
                 /* top= */ centerY - maskHeight,
diff --git a/packages/SystemUI/res/layout/dream_overlay_home_controls_chip.xml b/packages/SystemUI/res/layout/dream_overlay_home_controls_chip.xml
index de96e97..446bb01 100644
--- a/packages/SystemUI/res/layout/dream_overlay_home_controls_chip.xml
+++ b/packages/SystemUI/res/layout/dream_overlay_home_controls_chip.xml
@@ -20,7 +20,7 @@
     android:layout_width="wrap_content"
     android:paddingVertical="@dimen/dream_overlay_complication_home_controls_padding">
 
-    <ImageView
+    <com.android.systemui.common.ui.view.LaunchableImageView
         android:id="@+id/home_controls_chip"
         android:layout_height="@dimen/keyguard_affordance_fixed_height"
         android:layout_width="@dimen/keyguard_affordance_fixed_width"
diff --git a/packages/SystemUI/res/layout/global_actions_grid_lite.xml b/packages/SystemUI/res/layout/global_actions_grid_lite.xml
index 5588fd3..a64c9ae 100644
--- a/packages/SystemUI/res/layout/global_actions_grid_lite.xml
+++ b/packages/SystemUI/res/layout/global_actions_grid_lite.xml
@@ -33,7 +33,7 @@
       app:layout_constraintStart_toStartOf="parent"
       app:layout_constraintEnd_toEndOf="parent"
       android:layout_weight="1">
-    <androidx.constraintlayout.widget.ConstraintLayout
+    <com.android.systemui.common.ui.view.LaunchableConstraintLayout
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
         android:id="@android:id/list"
@@ -55,6 +55,6 @@
           app:flow_horizontalGap="@dimen/global_actions_lite_padding"
           app:flow_verticalGap="@dimen/global_actions_lite_padding"
           app:flow_horizontalStyle="packed"/>
-    </androidx.constraintlayout.widget.ConstraintLayout>
+    </com.android.systemui.common.ui.view.LaunchableConstraintLayout>
   </com.android.systemui.globalactions.GlobalActionsLayoutLite>
 </androidx.constraintlayout.widget.ConstraintLayout>
diff --git a/packages/SystemUI/res/layout/keyguard_qs_user_switch.xml b/packages/SystemUI/res/layout/keyguard_qs_user_switch.xml
index 6f33623..07c428b 100644
--- a/packages/SystemUI/res/layout/keyguard_qs_user_switch.xml
+++ b/packages/SystemUI/res/layout/keyguard_qs_user_switch.xml
@@ -24,7 +24,7 @@
     android:layout_gravity="end">
     <!-- We add a background behind the UserAvatarView with the same color and with a circular shape
          so that this view can be expanded into a Dialog or an Activity. -->
-    <FrameLayout
+    <com.android.systemui.animation.LaunchableFrameLayout
         android:id="@+id/kg_multi_user_avatar_with_background"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
@@ -42,5 +42,5 @@
             systemui:framePadding="0dp"
             systemui:frameWidth="0dp">
         </com.android.systemui.statusbar.phone.UserAvatarView>
-    </FrameLayout>
+    </com.android.systemui.animation.LaunchableFrameLayout>
 </FrameLayout>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/media_recommendation_view.xml b/packages/SystemUI/res/layout/media_recommendation_view.xml
new file mode 100644
index 0000000..101fad9
--- /dev/null
+++ b/packages/SystemUI/res/layout/media_recommendation_view.xml
@@ -0,0 +1,65 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ 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
+  -->
+<!-- Layout for media recommendation item inside QSPanel carousel -->
+<merge xmlns:android="http://schemas.android.com/apk/res/android">
+    <!-- Album cover -->
+    <ImageView
+        android:id="@+id/media_cover"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:translationZ="0dp"
+        android:scaleType="centerCrop"
+        android:adjustViewBounds="true"
+        android:clipToOutline="true"
+        android:background="@drawable/bg_smartspace_media_item"/>
+
+    <!-- App icon -->
+    <com.android.internal.widget.CachingIconView
+        android:id="@+id/media_rec_app_icon"
+        android:layout_width="@dimen/qs_media_rec_icon_top_margin"
+        android:layout_height="@dimen/qs_media_rec_icon_top_margin"
+        android:layout_marginStart="@dimen/qs_media_info_spacing"
+        android:layout_marginTop="@dimen/qs_media_info_spacing"/>
+
+    <!-- Artist name -->
+    <TextView
+        android:id="@+id/media_title"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_marginStart="@dimen/qs_media_info_spacing"
+        android:layout_marginEnd="@dimen/qs_media_info_spacing"
+        android:layout_marginBottom="@dimen/qs_media_rec_album_title_bottom_margin"
+        android:fontFamily="@*android:string/config_headlineFontFamilyMedium"
+        android:singleLine="true"
+        android:textSize="12sp"
+        android:gravity="top"
+        android:layout_gravity="bottom"/>
+
+    <!-- Album name -->
+    <TextView
+        android:id="@+id/media_subtitle"
+        android:layout_width="match_parent"
+        android:layout_height="@dimen/qs_media_rec_album_subtitle_height"
+        android:layout_marginEnd="@dimen/qs_media_info_spacing"
+        android:layout_marginStart="@dimen/qs_media_info_spacing"
+        android:layout_marginBottom="@dimen/qs_media_info_spacing"
+        android:fontFamily="@*android:string/config_headlineFontFamily"
+        android:singleLine="true"
+        android:textSize="11sp"
+        android:gravity="center_vertical"
+        android:layout_gravity="bottom"/>
+</merge>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/media_recommendations.xml b/packages/SystemUI/res/layout/media_recommendations.xml
new file mode 100644
index 0000000..65fc19c
--- /dev/null
+++ b/packages/SystemUI/res/layout/media_recommendations.xml
@@ -0,0 +1,75 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ 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
+  -->
+
+<!-- Layout for media recommendations inside QSPanel carousel -->
+<com.android.systemui.util.animation.TransitionLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/media_recommendations_updated"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:clipChildren="false"
+    android:clipToPadding="false"
+    android:forceHasOverlappingRendering="false"
+    android:background="@drawable/qs_media_background"
+    android:theme="@style/MediaPlayer">
+
+    <!-- This view just ensures the full media player is a certain height. -->
+    <View
+        android:id="@+id/sizing_view"
+        android:layout_width="match_parent"
+        android:layout_height="@dimen/qs_media_session_height_expanded" />
+
+    <TextView
+        android:id="@+id/media_rec_title"
+        style="@style/MediaPlayer.Recommendation.Header"
+        android:text="@string/controls_media_smartspace_rec_header"/>
+
+    <FrameLayout
+        android:id="@+id/media_cover1_container"
+        style="@style/MediaPlayer.Recommendation.AlbumContainer.Updated"
+        >
+
+        <include
+            layout="@layout/media_recommendation_view"/>
+
+    </FrameLayout>
+
+
+    <FrameLayout
+        android:id="@+id/media_cover2_container"
+        style="@style/MediaPlayer.Recommendation.AlbumContainer.Updated"
+        >
+
+        <include
+            layout="@layout/media_recommendation_view"/>
+
+    </FrameLayout>
+
+    <FrameLayout
+        android:id="@+id/media_cover3_container"
+        style="@style/MediaPlayer.Recommendation.AlbumContainer.Updated"
+        >
+
+        <include
+            layout="@layout/media_recommendation_view"/>
+
+    </FrameLayout>
+
+    <include
+        layout="@layout/media_long_press_menu" />
+
+</com.android.systemui.util.animation.TransitionLayout>
diff --git a/packages/SystemUI/res/layout/media_session_view.xml b/packages/SystemUI/res/layout/media_session_view.xml
index abc8337..f2e114b 100644
--- a/packages/SystemUI/res/layout/media_session_view.xml
+++ b/packages/SystemUI/res/layout/media_session_view.xml
@@ -106,7 +106,7 @@
         app:layout_constrainedWidth="true"
         app:layout_constraintWidth_min="@dimen/min_clickable_item_size"
         app:layout_constraintHeight_min="@dimen/min_clickable_item_size">
-        <LinearLayout
+        <com.android.systemui.common.ui.view.LaunchableLinearLayout
             android:id="@+id/media_seamless_button"
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
@@ -135,7 +135,7 @@
                 android:textDirection="locale"
                 android:textSize="12sp"
                 android:lineHeight="16sp" />
-        </LinearLayout>
+        </com.android.systemui.common.ui.view.LaunchableLinearLayout>
     </LinearLayout>
 
     <!-- Song name -->
diff --git a/packages/SystemUI/res/layout/ongoing_call_chip.xml b/packages/SystemUI/res/layout/ongoing_call_chip.xml
index c949ba0..18d231c 100644
--- a/packages/SystemUI/res/layout/ongoing_call_chip.xml
+++ b/packages/SystemUI/res/layout/ongoing_call_chip.xml
@@ -23,7 +23,7 @@
     android:layout_gravity="center_vertical|start"
     android:layout_marginStart="5dp"
 >
-    <LinearLayout
+    <com.android.systemui.common.ui.view.LaunchableLinearLayout
         android:id="@+id/ongoing_call_chip_background"
         android:layout_width="wrap_content"
         android:layout_height="@dimen/ongoing_appops_chip_height"
@@ -55,5 +55,5 @@
             android:textColor="?android:attr/colorPrimary"
         />
 
-    </LinearLayout>
+    </com.android.systemui.common.ui.view.LaunchableLinearLayout>
 </FrameLayout>
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index d01bc4a..f5db5ec 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -831,4 +831,8 @@
     <string name="config_wallpaperPickerPackage" translatable="false">
         com.android.wallpaper
     </string>
+    
+    <!-- Whether the floating rotation button should be on the left/right in the device's natural
+         orientation -->
+    <bool name="floating_rotation_button_position_left">true</bool>
 </resources>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index a1b8fe5..9e001e0 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -1068,8 +1068,13 @@
     <!-- Size of Smartspace media recommendations cards in the QSPanel carousel -->
     <dimen name="qs_media_rec_icon_top_margin">16dp</dimen>
     <dimen name="qs_media_rec_album_size">88dp</dimen>
+    <dimen name="qs_media_rec_album_width">110dp</dimen>
+    <dimen name="qs_media_rec_album_height_expanded">108dp</dimen>
+    <dimen name="qs_media_rec_album_height_collapsed">77dp</dimen>
     <dimen name="qs_media_rec_album_side_margin">16dp</dimen>
     <dimen name="qs_media_rec_album_bottom_margin">8dp</dimen>
+    <dimen name="qs_media_rec_album_title_bottom_margin">22dp</dimen>
+    <dimen name="qs_media_rec_album_subtitle_height">12dp</dimen>
 
     <!-- Media tap-to-transfer chip for sender device -->
     <dimen name="media_ttt_chip_outer_padding">16dp</dimen>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index d14207a..575dc94 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -2464,6 +2464,8 @@
     <string name="controls_media_smartspace_rec_item_description">Play <xliff:g id="song_name" example="Daily mix">%1$s</xliff:g> by <xliff:g id="artist_name" example="Various artists">%2$s</xliff:g> from <xliff:g id="app_label" example="Spotify">%3$s</xliff:g></string>
     <!-- Description for Smartspace recommendation's media item which doesn't have artist info, including information for the media's title and the source app [CHAR LIMIT=NONE]-->
     <string name="controls_media_smartspace_rec_item_no_artist_description">Play <xliff:g id="song_name" example="Daily mix">%1$s</xliff:g> from <xliff:g id="app_label" example="Spotify">%2$s</xliff:g></string>
+    <!-- Header title for Smartspace recommendation card within media controls. [CHAR_LIMIT=30] -->
+    <string name="controls_media_smartspace_rec_header">For You</string>
 
     <!--- ****** Media tap-to-transfer ****** -->
     <!-- Text for a button to undo the media transfer. [CHAR LIMIT=20] -->
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index f8f5e83..dd87e91 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -678,6 +678,17 @@
 
     <style name="MediaPlayer.Recommendation"/>
 
+    <style name="MediaPlayer.Recommendation.Header">
+        <item name="android:layout_width">wrap_content</item>
+        <item name="android:layout_height">wrap_content</item>
+        <item name="android:layout_marginTop">@dimen/qs_media_padding</item>
+        <item name="android:layout_marginStart">@dimen/qs_media_padding</item>
+        <item name="android:fontFamily">=@*android:string/config_headlineFontFamilyMedium</item>
+        <item name="android:singleLine">true</item>
+        <item name="android:textSize">14sp</item>
+        <item name="android:textColor">?android:attr/textColorPrimary</item>
+    </style>
+
     <style name="MediaPlayer.Recommendation.AlbumContainer">
         <item name="android:layout_width">@dimen/qs_media_rec_album_size</item>
         <item name="android:layout_height">@dimen/qs_media_rec_album_size</item>
@@ -686,6 +697,12 @@
         <item name="android:layout_marginBottom">@dimen/qs_media_rec_album_bottom_margin</item>
     </style>
 
+    <style name="MediaPlayer.Recommendation.AlbumContainer.Updated">
+        <item name="android:layout_width">@dimen/qs_media_rec_album_width</item>
+        <item name="android:background">@drawable/qs_media_light_source</item>
+        <item name="android:layout_marginTop">@dimen/qs_media_info_spacing</item>
+    </style>
+
     <style name="MediaPlayer.Recommendation.Album">
         <item name="android:backgroundTint">@color/media_player_album_bg</item>
     </style>
diff --git a/packages/SystemUI/res/xml/media_recommendations_view_collapsed.xml b/packages/SystemUI/res/xml/media_recommendations_view_collapsed.xml
new file mode 100644
index 0000000..d3be3c7
--- /dev/null
+++ b/packages/SystemUI/res/xml/media_recommendations_view_collapsed.xml
@@ -0,0 +1,64 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ 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
+  -->
+<ConstraintSet
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    >
+
+    <Constraint
+        android:id="@+id/sizing_view"
+        android:layout_width="match_parent"
+        android:layout_height="@dimen/qs_media_session_height_collapsed"
+        />
+
+    <Constraint
+        android:id="@+id/media_rec_title"
+        style="@style/MediaPlayer.Recommendation.Header"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toTopOf="parent"/>
+
+    <Constraint
+        android:id="@+id/media_cover1_container"
+        style="@style/MediaPlayer.Recommendation.AlbumContainer.Updated"
+        android:layout_height="@dimen/qs_media_rec_album_height_collapsed"
+        android:layout_marginEnd="@dimen/qs_media_info_spacing"
+        android:layout_marginStart="@dimen/qs_media_padding"
+        app:layout_constraintTop_toBottomOf="@+id/media_rec_title"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintEnd_toStartOf="@id/media_cover2_container"/>
+
+
+    <Constraint
+        android:id="@+id/media_cover2_container"
+        style="@style/MediaPlayer.Recommendation.AlbumContainer.Updated"
+        android:layout_height="@dimen/qs_media_rec_album_height_collapsed"
+        android:layout_marginEnd="@dimen/qs_media_info_spacing"
+        app:layout_constraintTop_toBottomOf="@+id/media_rec_title"
+        app:layout_constraintStart_toEndOf="@id/media_cover1_container"
+        app:layout_constraintEnd_toStartOf="@id/media_cover3_container"/>
+
+    <Constraint
+        android:id="@+id/media_cover3_container"
+        style="@style/MediaPlayer.Recommendation.AlbumContainer.Updated"
+        android:layout_height="@dimen/qs_media_rec_album_height_collapsed"
+        android:layout_marginEnd="@dimen/qs_media_padding"
+        app:layout_constraintTop_toBottomOf="@+id/media_rec_title"
+        app:layout_constraintStart_toEndOf="@id/media_cover2_container"
+        app:layout_constraintEnd_toEndOf="parent"/>
+
+
+</ConstraintSet>
diff --git a/packages/SystemUI/res/xml/media_recommendations_view_expanded.xml b/packages/SystemUI/res/xml/media_recommendations_view_expanded.xml
new file mode 100644
index 0000000..88c7055
--- /dev/null
+++ b/packages/SystemUI/res/xml/media_recommendations_view_expanded.xml
@@ -0,0 +1,71 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ 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
+  -->
+<ConstraintSet
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    >
+
+    <Constraint
+        android:id="@+id/sizing_view"
+        android:layout_width="match_parent"
+        android:layout_height="@dimen/qs_media_session_height_expanded"
+        />
+
+    <Constraint
+        android:id="@+id/media_rec_title"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginTop="@dimen/qs_media_padding"
+        android:layout_marginStart="@dimen/qs_media_padding"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toTopOf="parent"
+        android:fontFamily="@*android:string/config_headlineFontFamilyMedium"
+        android:singleLine="true"
+        android:textSize="14sp"
+        android:textColor="@color/notification_primary_text_color"/>
+
+    <Constraint
+        android:id="@+id/media_cover1_container"
+        style="@style/MediaPlayer.Recommendation.AlbumContainer.Updated"
+        android:layout_height="@dimen/qs_media_rec_album_height_expanded"
+        android:layout_marginEnd="@dimen/qs_media_info_spacing"
+        android:layout_marginStart="@dimen/qs_media_padding"
+        app:layout_constraintTop_toBottomOf="@+id/media_rec_title"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintEnd_toStartOf="@id/media_cover2_container"/>
+
+
+    <Constraint
+        android:id="@+id/media_cover2_container"
+        style="@style/MediaPlayer.Recommendation.AlbumContainer.Updated"
+        android:layout_height="@dimen/qs_media_rec_album_height_expanded"
+        android:layout_marginEnd="@dimen/qs_media_info_spacing"
+        app:layout_constraintTop_toBottomOf="@+id/media_rec_title"
+        app:layout_constraintStart_toEndOf="@id/media_cover1_container"
+        app:layout_constraintEnd_toStartOf="@id/media_cover3_container"/>
+
+    <Constraint
+        android:id="@+id/media_cover3_container"
+        style="@style/MediaPlayer.Recommendation.AlbumContainer.Updated"
+        android:layout_height="@dimen/qs_media_rec_album_height_expanded"
+        android:layout_marginEnd="@dimen/qs_media_padding"
+        app:layout_constraintTop_toBottomOf="@+id/media_rec_title"
+        app:layout_constraintStart_toEndOf="@id/media_cover2_container"
+        app:layout_constraintEnd_toEndOf="parent"/>
+
+
+</ConstraintSet>
diff --git a/packages/SystemUI/shared/src/com/android/systemui/flags/Flag.kt b/packages/SystemUI/shared/src/com/android/systemui/flags/Flag.kt
index 196f7f0..c9a25b0 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/flags/Flag.kt
+++ b/packages/SystemUI/shared/src/com/android/systemui/flags/Flag.kt
@@ -47,10 +47,6 @@
     val resourceId: Int
 }
 
-interface DeviceConfigFlag<T> : Flag<T> {
-    val default: T
-}
-
 interface SysPropFlag<T> : Flag<T> {
     val default: T
 }
@@ -80,8 +76,8 @@
 
     private constructor(parcel: Parcel) : this(
         id = parcel.readInt(),
-        name = parcel.readString(),
-        namespace = parcel.readString(),
+        name = parcel.readString() ?: "",
+        namespace = parcel.readString() ?: "",
         default = parcel.readBoolean(),
         teamfood = parcel.readBoolean(),
         overridden = parcel.readBoolean()
@@ -137,21 +133,6 @@
 ) : ResourceFlag<Boolean>
 
 /**
- * A Flag that can reads its overrides from DeviceConfig.
- *
- * This is generally useful for flags that come from or are used _outside_ of SystemUI.
- *
- * Prefer [UnreleasedFlag] and [ReleasedFlag].
- */
-data class DeviceConfigBooleanFlag constructor(
-    override val id: Int,
-    override val name: String,
-    override val namespace: String,
-    override val default: Boolean = false,
-    override val teamfood: Boolean = false
-) : DeviceConfigFlag<Boolean>
-
-/**
  * A Flag that can reads its overrides from System Properties.
  *
  * This is generally useful for flags that come from or are used _outside_ of SystemUI.
@@ -186,8 +167,8 @@
 
     private constructor(parcel: Parcel) : this(
         id = parcel.readInt(),
-        name = parcel.readString(),
-        namespace = parcel.readString(),
+        name = parcel.readString() ?: "",
+        namespace = parcel.readString() ?: "",
         default = parcel.readString() ?: ""
     )
 
@@ -226,8 +207,8 @@
 
     private constructor(parcel: Parcel) : this(
         id = parcel.readInt(),
-        name = parcel.readString(),
-        namespace = parcel.readString(),
+        name = parcel.readString() ?: "",
+        namespace = parcel.readString() ?: "",
         default = parcel.readInt()
     )
 
@@ -266,8 +247,8 @@
 
     private constructor(parcel: Parcel) : this(
         id = parcel.readInt(),
-        name = parcel.readString(),
-        namespace = parcel.readString(),
+        name = parcel.readString() ?: "",
+        namespace = parcel.readString() ?: "",
         default = parcel.readLong()
     )
 
@@ -298,8 +279,8 @@
 
     private constructor(parcel: Parcel) : this(
         id = parcel.readInt(),
-        name = parcel.readString(),
-        namespace = parcel.readString(),
+        name = parcel.readString() ?: "",
+        namespace = parcel.readString() ?: "",
         default = parcel.readFloat()
     )
 
@@ -338,8 +319,8 @@
 
     private constructor(parcel: Parcel) : this(
         id = parcel.readInt(),
-        name = parcel.readString(),
-        namespace = parcel.readString(),
+        name = parcel.readString() ?: "",
+        namespace = parcel.readString() ?: "",
         default = parcel.readDouble()
     )
 
diff --git a/packages/SystemUI/shared/src/com/android/systemui/flags/FlagListenable.kt b/packages/SystemUI/shared/src/com/android/systemui/flags/FlagListenable.kt
index 195ba465..72a4fab 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/flags/FlagListenable.kt
+++ b/packages/SystemUI/shared/src/com/android/systemui/flags/FlagListenable.kt
@@ -34,7 +34,7 @@
     /** An event representing the change */
     interface FlagEvent {
         /** the id of the flag which changed */
-        val flagId: Int
+        val flagName: String
         /** if all listeners alerted invoke this method, the restart will be skipped */
         fun requestNoRestart()
     }
diff --git a/packages/SystemUI/shared/src/com/android/systemui/flags/FlagManager.kt b/packages/SystemUI/shared/src/com/android/systemui/flags/FlagManager.kt
index d85292a..da1641c 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/flags/FlagManager.kt
+++ b/packages/SystemUI/shared/src/com/android/systemui/flags/FlagManager.kt
@@ -39,7 +39,7 @@
         const val ACTION_GET_FLAGS = "com.android.systemui.action.GET_FLAGS"
         const val FLAGS_PERMISSION = "com.android.systemui.permission.FLAGS"
         const val ACTION_SYSUI_STARTED = "com.android.systemui.STARTED"
-        const val EXTRA_ID = "id"
+        const val EXTRA_NAME = "name"
         const val EXTRA_VALUE = "value"
         const val EXTRA_FLAGS = "flags"
         private const val SETTINGS_PREFIX = "systemui/flags"
@@ -56,7 +56,7 @@
      * that the restart be suppressed
      */
     var onSettingsChangedAction: Consumer<Boolean>? = null
-    var clearCacheAction: Consumer<Int>? = null
+    var clearCacheAction: Consumer<String>? = null
     private val listeners: MutableSet<PerFlagListener> = mutableSetOf()
     private val settingsObserver: ContentObserver = SettingsObserver()
 
@@ -96,35 +96,42 @@
      * Returns the stored value or null if not set.
      * This API is used by TheFlippinApp.
      */
-    fun isEnabled(id: Int): Boolean? = readFlagValue(id, BooleanFlagSerializer)
+    fun isEnabled(name: String): Boolean? = readFlagValue(name, BooleanFlagSerializer)
 
     /**
      * Sets the value of a boolean flag.
      * This API is used by TheFlippinApp.
      */
-    fun setFlagValue(id: Int, enabled: Boolean) {
-        val intent = createIntent(id)
+    fun setFlagValue(name: String, enabled: Boolean) {
+        val intent = createIntent(name)
         intent.putExtra(EXTRA_VALUE, enabled)
 
         context.sendBroadcast(intent)
     }
 
-    fun eraseFlag(id: Int) {
-        val intent = createIntent(id)
+    fun eraseFlag(name: String) {
+        val intent = createIntent(name)
 
         context.sendBroadcast(intent)
     }
 
     /** Returns the stored value or null if not set.  */
+    // TODO(b/265188950): Remove method this once ids are fully deprecated.
     fun <T> readFlagValue(id: Int, serializer: FlagSerializer<T>): T? {
-        val data = settings.getString(idToSettingsKey(id))
+        val data = settings.getStringFromSecure(idToSettingsKey(id))
+        return serializer.fromSettingsData(data)
+    }
+
+    /** Returns the stored value or null if not set.  */
+    fun <T> readFlagValue(name: String, serializer: FlagSerializer<T>): T? {
+        val data = settings.getString(nameToSettingsKey(name))
         return serializer.fromSettingsData(data)
     }
 
     override fun addListener(flag: Flag<*>, listener: FlagListenable.Listener) {
         synchronized(listeners) {
             val registerNeeded = listeners.isEmpty()
-            listeners.add(PerFlagListener(flag.id, listener))
+            listeners.add(PerFlagListener(flag.name, listener))
             if (registerNeeded) {
                 settings.registerContentObserver(SETTINGS_PREFIX, true, settingsObserver)
             }
@@ -143,38 +150,38 @@
         }
     }
 
-    private fun createIntent(id: Int): Intent {
+    private fun createIntent(name: String): Intent {
         val intent = Intent(ACTION_SET_FLAG)
         intent.setPackage(RECEIVING_PACKAGE)
-        intent.putExtra(EXTRA_ID, id)
+        intent.putExtra(EXTRA_NAME, name)
 
         return intent
     }
 
+    // TODO(b/265188950): Remove method this once ids are fully deprecated.
     fun idToSettingsKey(id: Int): String {
         return "$SETTINGS_PREFIX/$id"
     }
 
+    fun nameToSettingsKey(name: String): String {
+        return "$SETTINGS_PREFIX/$name"
+    }
+
     inner class SettingsObserver : ContentObserver(handler) {
         override fun onChange(selfChange: Boolean, uri: Uri?) {
             if (uri == null) {
                 return
             }
             val parts = uri.pathSegments
-            val idStr = parts[parts.size - 1]
-            val id = try {
-                idStr.toInt()
-            } catch (e: NumberFormatException) {
-                return
-            }
-            clearCacheAction?.accept(id)
-            dispatchListenersAndMaybeRestart(id, onSettingsChangedAction)
+            val name = parts[parts.size - 1]
+            clearCacheAction?.accept(name)
+            dispatchListenersAndMaybeRestart(name, onSettingsChangedAction)
         }
     }
 
-    fun dispatchListenersAndMaybeRestart(id: Int, restartAction: Consumer<Boolean>?) {
+    fun dispatchListenersAndMaybeRestart(name: String, restartAction: Consumer<Boolean>?) {
         val filteredListeners: List<FlagListenable.Listener> = synchronized(listeners) {
-            listeners.mapNotNull { if (it.id == id) it.listener else null }
+            listeners.mapNotNull { if (it.name == name) it.listener else null }
         }
         // If there are no listeners, there's nothing to dispatch to, and nothing to suppress it.
         if (filteredListeners.isEmpty()) {
@@ -185,7 +192,7 @@
         val suppressRestartList: List<Boolean> = filteredListeners.map { listener ->
             var didRequestNoRestart = false
             val event = object : FlagListenable.FlagEvent {
-                override val flagId = id
+                override val flagName = name
                 override fun requestNoRestart() {
                     didRequestNoRestart = true
                 }
@@ -198,7 +205,7 @@
         restartAction?.accept(suppressRestart)
     }
 
-    private data class PerFlagListener(val id: Int, val listener: FlagListenable.Listener)
+    private data class PerFlagListener(val name: String, val listener: FlagListenable.Listener)
 }
 
 class NoFlagResultsException : Exception(
diff --git a/packages/SystemUI/shared/src/com/android/systemui/flags/FlagSettingsHelper.kt b/packages/SystemUI/shared/src/com/android/systemui/flags/FlagSettingsHelper.kt
index 742bb0b..6beb851 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/flags/FlagSettingsHelper.kt
+++ b/packages/SystemUI/shared/src/com/android/systemui/flags/FlagSettingsHelper.kt
@@ -22,7 +22,10 @@
 
 class FlagSettingsHelper(private val contentResolver: ContentResolver) {
 
-    fun getString(key: String): String? = Settings.Secure.getString(contentResolver, key)
+    // TODO(b/265188950): Remove method this once ids are fully deprecated.
+    fun getStringFromSecure(key: String): String? = Settings.Secure.getString(contentResolver, key)
+
+    fun getString(key: String): String? = Settings.Global.getString(contentResolver, key)
 
     fun registerContentObserver(
         name: String,
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/FloatingRotationButton.java b/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/FloatingRotationButton.java
index 857cc462..5d036fb 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/FloatingRotationButton.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/FloatingRotationButton.java
@@ -35,6 +35,7 @@
 import android.view.animation.AccelerateDecelerateInterpolator;
 import android.widget.FrameLayout;
 
+import androidx.annotation.BoolRes;
 import androidx.core.view.OneShotPreDrawListener;
 
 import com.android.systemui.shared.rotation.FloatingRotationButtonPositionCalculator.Position;
@@ -65,6 +66,8 @@
     private final int mTaskbarBottomMarginResource;
     @DimenRes
     private final int mButtonDiameterResource;
+    @BoolRes
+    private final int mFloatingRotationBtnPositionLeftResource;
 
     private AnimatedVectorDrawable mAnimatedDrawable;
     private boolean mIsShowing;
@@ -84,7 +87,7 @@
             @LayoutRes int layout, @IdRes int keyButtonId, @DimenRes int minMargin,
             @DimenRes int roundedContentPadding, @DimenRes int taskbarLeftMargin,
             @DimenRes int taskbarBottomMargin, @DimenRes int buttonDiameter,
-            @DimenRes int rippleMaxWidth) {
+            @DimenRes int rippleMaxWidth, @BoolRes int floatingRotationBtnPositionLeftResource) {
         mWindowManager = context.getSystemService(WindowManager.class);
         mKeyButtonContainer = (ViewGroup) LayoutInflater.from(context).inflate(layout, null);
         mKeyButtonView = mKeyButtonContainer.findViewById(keyButtonId);
@@ -100,6 +103,7 @@
         mTaskbarLeftMarginResource = taskbarLeftMargin;
         mTaskbarBottomMarginResource = taskbarBottomMargin;
         mButtonDiameterResource = buttonDiameter;
+        mFloatingRotationBtnPositionLeftResource = floatingRotationBtnPositionLeftResource;
 
         updateDimensionResources();
     }
@@ -116,8 +120,11 @@
         int taskbarMarginBottom =
                 res.getDimensionPixelSize(mTaskbarBottomMarginResource);
 
+        boolean floatingRotationButtonPositionLeft =
+                res.getBoolean(mFloatingRotationBtnPositionLeftResource);
+
         mPositionCalculator = new FloatingRotationButtonPositionCalculator(defaultMargin,
-                taskbarMarginLeft, taskbarMarginBottom);
+                taskbarMarginLeft, taskbarMarginBottom, floatingRotationButtonPositionLeft);
 
         final int diameter = res.getDimensionPixelSize(mButtonDiameterResource);
         mContainerSize = diameter + Math.max(defaultMargin, Math.max(taskbarMarginLeft,
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/FloatingRotationButtonPositionCalculator.kt b/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/FloatingRotationButtonPositionCalculator.kt
index ec3c073..40e43a9 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/FloatingRotationButtonPositionCalculator.kt
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/FloatingRotationButtonPositionCalculator.kt
@@ -10,7 +10,8 @@
 class FloatingRotationButtonPositionCalculator(
     private val defaultMargin: Int,
     private val taskbarMarginLeft: Int,
-    private val taskbarMarginBottom: Int
+    private val taskbarMarginBottom: Int,
+    private val floatingRotationButtonPositionLeft: Boolean
 ) {
 
     fun calculatePosition(
@@ -18,7 +19,6 @@
         taskbarVisible: Boolean,
         taskbarStashed: Boolean
     ): Position {
-
         val isTaskbarSide = currentRotation == Surface.ROTATION_0
             || currentRotation == Surface.ROTATION_90
         val useTaskbarMargin = isTaskbarSide && taskbarVisible && !taskbarStashed
@@ -55,11 +55,21 @@
     )
 
     private fun resolveGravity(rotation: Int): Int =
-        when (rotation) {
-            Surface.ROTATION_0 -> Gravity.BOTTOM or Gravity.LEFT
-            Surface.ROTATION_90 -> Gravity.BOTTOM or Gravity.RIGHT
-            Surface.ROTATION_180 -> Gravity.TOP or Gravity.RIGHT
-            Surface.ROTATION_270 -> Gravity.TOP or Gravity.LEFT
-            else -> throw IllegalArgumentException("Invalid rotation $rotation")
+        if (floatingRotationButtonPositionLeft) {
+            when (rotation) {
+                Surface.ROTATION_0 -> Gravity.BOTTOM or Gravity.LEFT
+                Surface.ROTATION_90 -> Gravity.BOTTOM or Gravity.RIGHT
+                Surface.ROTATION_180 -> Gravity.TOP or Gravity.RIGHT
+                Surface.ROTATION_270 -> Gravity.TOP or Gravity.LEFT
+                else -> throw IllegalArgumentException("Invalid rotation $rotation")
+            }
+        } else {
+            when (rotation) {
+                Surface.ROTATION_0 -> Gravity.BOTTOM or Gravity.RIGHT
+                Surface.ROTATION_90 -> Gravity.TOP or Gravity.RIGHT
+                Surface.ROTATION_180 -> Gravity.TOP or Gravity.LEFT
+                Surface.ROTATION_270 -> Gravity.BOTTOM or Gravity.LEFT
+                else -> throw IllegalArgumentException("Invalid rotation $rotation")
+            }
         }
 }
diff --git a/packages/SystemUI/src-debug/com/android/systemui/flags/FlagsFactory.kt b/packages/SystemUI/src-debug/com/android/systemui/flags/FlagsFactory.kt
index 05372fe..31234cf 100644
--- a/packages/SystemUI/src-debug/com/android/systemui/flags/FlagsFactory.kt
+++ b/packages/SystemUI/src-debug/com/android/systemui/flags/FlagsFactory.kt
@@ -35,7 +35,7 @@
         teamfood: Boolean = false
     ): UnreleasedFlag {
         val flag = UnreleasedFlag(id = id, name = name, namespace = namespace, teamfood = teamfood)
-        FlagsFactory.checkForDupesAndAdd(flag)
+        checkForDupesAndAdd(flag)
         return flag
     }
 
@@ -46,7 +46,7 @@
         teamfood: Boolean = false
     ): ReleasedFlag {
         val flag = ReleasedFlag(id = id, name = name, namespace = namespace, teamfood = teamfood)
-        FlagsFactory.checkForDupesAndAdd(flag)
+        checkForDupesAndAdd(flag)
         return flag
     }
 
@@ -65,7 +65,7 @@
                 resourceId = resourceId,
                 teamfood = teamfood
             )
-        FlagsFactory.checkForDupesAndAdd(flag)
+        checkForDupesAndAdd(flag)
         return flag
     }
 
@@ -77,18 +77,13 @@
     ): SysPropBooleanFlag {
         val flag =
             SysPropBooleanFlag(id = id, name = name, namespace = "systemui", default = default)
-        FlagsFactory.checkForDupesAndAdd(flag)
+        checkForDupesAndAdd(flag)
         return flag
     }
 
     private fun checkForDupesAndAdd(flag: Flag<*>) {
         if (flagMap.containsKey(flag.name)) {
-            throw IllegalArgumentException("Name {flag.name} is already registered")
-        }
-        flagMap.forEach {
-            if (it.value.id == flag.id) {
-                throw IllegalArgumentException("Name {flag.id} is already registered")
-            }
+            throw IllegalArgumentException("Name {$flag.name} is already registered")
         }
         flagMap[flag.name] = flag
     }
diff --git a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
index 1680b47..3a940e9 100644
--- a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
+++ b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
@@ -54,7 +54,6 @@
 import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.filter
 import kotlinx.coroutines.launch
-import java.io.PrintWriter
 import java.util.Locale
 import java.util.TimeZone
 import java.util.concurrent.Executor
@@ -309,15 +308,6 @@
             resources.getDimensionPixelSize(R.dimen.large_clock_text_size).toFloat())
     }
 
-    /**
-     * Dump information for debugging
-     */
-    fun dump(pw: PrintWriter) {
-        pw.println(this)
-        clock?.dump(pw)
-        regionSampler?.dump(pw)
-    }
-
     @VisibleForTesting
     internal fun listenForDozeAmount(scope: CoroutineScope): Job {
         return scope.launch {
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputViewController.java
index 7da27b1..baaef19 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputViewController.java
@@ -103,6 +103,7 @@
 
     @Override
     public void reset() {
+        super.reset();
         // start fresh
         mDismissing = false;
         mView.resetPasswordText(false /* animate */, false /* announce */);
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
index 0bb5e25..0685794 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
@@ -45,6 +45,7 @@
 import com.android.systemui.plugins.log.LogLevel;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.shared.clocks.ClockRegistry;
+import com.android.systemui.shared.regionsampling.RegionSampler;
 import com.android.systemui.statusbar.lockscreen.LockscreenSmartspaceController;
 import com.android.systemui.statusbar.notification.AnimatableProperty;
 import com.android.systemui.statusbar.notification.PropertyAnimator;
@@ -445,6 +446,10 @@
         if (clock != null) {
             clock.dump(pw);
         }
+        final RegionSampler regionSampler = mClockEventController.getRegionSampler();
+        if (regionSampler != null) {
+            regionSampler.dump(pw);
+        }
     }
 
     /** Gets the animations for the current clock. */
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java
index d1c9a30..b143c5b 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java
@@ -121,6 +121,7 @@
 
     @Override
     public void reset() {
+        mMessageAreaController.setMessage("", false);
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/ChooserSelector.kt b/packages/SystemUI/src/com/android/systemui/ChooserSelector.kt
index 9ac45b3..227f0ace 100644
--- a/packages/SystemUI/src/com/android/systemui/ChooserSelector.kt
+++ b/packages/SystemUI/src/com/android/systemui/ChooserSelector.kt
@@ -34,7 +34,7 @@
     override fun start() {
         coroutineScope.launch {
             val listener = FlagListenable.Listener { event ->
-                if (event.flagId == Flags.CHOOSER_UNBUNDLED.id) {
+                if (event.flagName == Flags.CHOOSER_UNBUNDLED.name) {
                     launch { updateUnbundledChooserEnabled() }
                     event.requestNoRestart()
                 }
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleView.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleView.kt
index 04a2689..53ab6d6 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleView.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleView.kt
@@ -86,7 +86,7 @@
 
     init {
         rippleShader.color = 0xffffffff.toInt() // default color
-        rippleShader.progress = 0f
+        rippleShader.rawProgress = 0f
         rippleShader.sparkleStrength = RIPPLE_SPARKLE_STRENGTH
         ripplePaint.shader = rippleShader
 
@@ -269,7 +269,7 @@
             duration = AuthRippleController.RIPPLE_ANIMATION_DURATION
             addUpdateListener { animator ->
                 val now = animator.currentPlayTime
-                rippleShader.progress = animator.animatedValue as Float
+                rippleShader.rawProgress = animator.animatedValue as Float
                 rippleShader.time = now.toFloat()
 
                 invalidate()
@@ -342,7 +342,7 @@
     override fun onDraw(canvas: Canvas?) {
         // To reduce overdraw, we mask the effect to a circle whose radius is big enough to cover
         // the active effect area. Values here should be kept in sync with the
-        // animation implementation in the ripple shader.
+        // animation implementation in the ripple shader. (Twice bigger)
         if (drawDwell) {
             val maskRadius = (1 - (1 - dwellShader.progress) * (1 - dwellShader.progress) *
                     (1 - dwellShader.progress)) * dwellRadius * 2f
@@ -351,10 +351,8 @@
         }
 
         if (drawRipple) {
-            val mask = (1 - (1 - rippleShader.progress) * (1 - rippleShader.progress) *
-                    (1 - rippleShader.progress)) * radius * 2f
             canvas?.drawCircle(origin.x.toFloat(), origin.y.toFloat(),
-                    mask, ripplePaint)
+                    rippleShader.currentWidth, ripplePaint)
         }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.kt
index 9bccafb..2d0d52e 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.kt
@@ -33,7 +33,6 @@
 import com.android.systemui.flags.Flags
 import com.android.systemui.keyguard.domain.interactor.AlternateBouncerInteractor
 import com.android.systemui.keyguard.domain.interactor.PrimaryBouncerInteractor
-import com.android.systemui.keyguard.shared.constants.KeyguardBouncerConstants
 import com.android.systemui.lifecycle.repeatWhenAttached
 import com.android.systemui.plugins.statusbar.StatusBarStateController
 import com.android.systemui.shade.ShadeExpansionListener
@@ -83,7 +82,6 @@
     ) {
     private val useExpandedOverlay: Boolean =
         featureFlags.isEnabled(Flags.UDFPS_NEW_TOUCH_DETECTION)
-    private val isModernBouncerEnabled: Boolean = featureFlags.isEnabled(Flags.MODERN_BOUNCER)
     private val isModernAlternateBouncerEnabled: Boolean =
         featureFlags.isEnabled(Flags.MODERN_ALTERNATE_BOUNCER)
     private var showingUdfpsBouncer = false
@@ -109,12 +107,6 @@
                 )
             }
         }
-    /**
-     * Hidden amount of input (pin/pattern/password) bouncer. This is used
-     * [KeyguardBouncerConstants.EXPANSION_VISIBLE] (0f) to
-     * [KeyguardBouncerConstants.EXPANSION_HIDDEN] (1f). Only used for the non-modernBouncer.
-     */
-    private var inputBouncerHiddenAmount = KeyguardBouncerConstants.EXPANSION_HIDDEN
     private var inputBouncerExpansion = 0f // only used for modernBouncer
 
     private val stateListener: StatusBarStateController.StateListener =
@@ -253,15 +245,13 @@
     }
 
     init {
-        if (isModernBouncerEnabled || isModernAlternateBouncerEnabled) {
-            view.repeatWhenAttached {
-                // repeatOnLifecycle CREATED (as opposed to STARTED) because the Bouncer expansion
-                // can make the view not visible; and we still want to listen for events
-                // that may make the view visible again.
-                repeatOnLifecycle(Lifecycle.State.CREATED) {
-                    if (isModernBouncerEnabled) listenForBouncerExpansion(this)
-                    if (isModernAlternateBouncerEnabled) listenForAlternateBouncerVisibility(this)
-                }
+        view.repeatWhenAttached {
+            // repeatOnLifecycle CREATED (as opposed to STARTED) because the Bouncer expansion
+            // can make the view not visible; and we still want to listen for events
+            // that may make the view visible again.
+            repeatOnLifecycle(Lifecycle.State.CREATED) {
+                listenForBouncerExpansion(this)
+                if (isModernAlternateBouncerEnabled) listenForAlternateBouncerVisibility(this)
             }
         }
     }
@@ -333,7 +323,6 @@
 
     override fun dump(pw: PrintWriter, args: Array<String>) {
         super.dump(pw, args)
-        pw.println("isModernBouncerEnabled=$isModernBouncerEnabled")
         pw.println("isModernAlternateBouncerEnabled=$isModernAlternateBouncerEnabled")
         pw.println("showingUdfpsAltBouncer=$showingUdfpsBouncer")
         pw.println(
@@ -353,11 +342,7 @@
         pw.println("udfpsRequestedByApp=$udfpsRequested")
         pw.println("launchTransitionFadingAway=$launchTransitionFadingAway")
         pw.println("lastDozeAmount=$lastDozeAmount")
-        if (isModernBouncerEnabled) {
-            pw.println("inputBouncerExpansion=$inputBouncerExpansion")
-        } else {
-            pw.println("inputBouncerHiddenAmount=$inputBouncerHiddenAmount")
-        }
+        pw.println("inputBouncerExpansion=$inputBouncerExpansion")
         view.dump(pw)
     }
 
@@ -384,7 +369,6 @@
         } else {
             keyguardUpdateMonitor.requestFaceAuthOnOccludingApp(false)
         }
-        updateBouncerHiddenAmount()
         updateAlpha()
         updatePauseAuth()
         return true
@@ -425,19 +409,11 @@
     }
 
     fun isBouncerExpansionGreaterThan(bouncerExpansionThreshold: Float): Boolean {
-        return if (isModernBouncerEnabled) {
-            inputBouncerExpansion >= bouncerExpansionThreshold
-        } else {
-            inputBouncerHiddenAmount < bouncerExpansionThreshold
-        }
+        return inputBouncerExpansion >= bouncerExpansionThreshold
     }
 
     fun isInputBouncerFullyVisible(): Boolean {
-        return if (isModernBouncerEnabled) {
-            inputBouncerExpansion == 1f
-        } else {
-            keyguardViewManager.isBouncerShowing && !alternateBouncerInteractor.isVisibleState()
-        }
+        return inputBouncerExpansion == 1f
     }
 
     override fun listenForTouchesOutsideView(): Boolean {
@@ -489,11 +465,7 @@
     }
 
     private fun getInputBouncerHiddenAmt(): Float {
-        return if (isModernBouncerEnabled) {
-            1f - inputBouncerExpansion
-        } else {
-            inputBouncerHiddenAmount
-        }
+        return 1f - inputBouncerExpansion
     }
 
     /** Update the scale factor based on the device's resolution. */
@@ -501,19 +473,6 @@
         udfpsController.mOverlayParams?.scaleFactor?.let { view.setScaleFactor(it) }
     }
 
-    private fun updateBouncerHiddenAmount() {
-        if (isModernBouncerEnabled) {
-            return
-        }
-        val altBouncerShowing = alternateBouncerInteractor.isVisibleState()
-        if (altBouncerShowing || !keyguardViewManager.primaryBouncerIsOrWillBeShowing()) {
-            inputBouncerHiddenAmount = 1f
-        } else if (keyguardViewManager.isBouncerShowing) {
-            // input bouncer is fully showing
-            inputBouncerHiddenAmount = 0f
-        }
-    }
-
     private val legacyAlternateBouncer: LegacyAlternateBouncer =
         object : LegacyAlternateBouncer {
             override fun showAlternateBouncer(): Boolean {
diff --git a/packages/SystemUI/src/com/android/systemui/common/ui/view/LaunchableConstraintLayout.kt b/packages/SystemUI/src/com/android/systemui/common/ui/view/LaunchableConstraintLayout.kt
new file mode 100644
index 0000000..9763665
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/common/ui/view/LaunchableConstraintLayout.kt
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.common.ui.view
+
+import android.content.Context
+import android.util.AttributeSet
+import androidx.constraintlayout.widget.ConstraintLayout
+import com.android.systemui.animation.LaunchableView
+import com.android.systemui.animation.LaunchableViewDelegate
+
+/** A [ConstraintLayout] that also implements [LaunchableView]. */
+open class LaunchableConstraintLayout : ConstraintLayout, LaunchableView {
+    private val delegate =
+        LaunchableViewDelegate(
+            this,
+            superSetVisibility = { super.setVisibility(it) },
+        )
+
+    constructor(context: Context) : super(context)
+    constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)
+    constructor(
+        context: Context,
+        attrs: AttributeSet?,
+        defStyleAttr: Int
+    ) : super(context, attrs, defStyleAttr)
+
+    constructor(
+        context: Context,
+        attrs: AttributeSet?,
+        defStyleAttr: Int,
+        defStyleRes: Int
+    ) : super(context, attrs, defStyleAttr, defStyleRes)
+
+    override fun setShouldBlockVisibilityChanges(block: Boolean) {
+        delegate.setShouldBlockVisibilityChanges(block)
+    }
+
+    override fun setVisibility(visibility: Int) {
+        delegate.setVisibility(visibility)
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/common/ui/view/LaunchableLinearLayout.kt b/packages/SystemUI/src/com/android/systemui/common/ui/view/LaunchableLinearLayout.kt
index ddde628..2edac52 100644
--- a/packages/SystemUI/src/com/android/systemui/common/ui/view/LaunchableLinearLayout.kt
+++ b/packages/SystemUI/src/com/android/systemui/common/ui/view/LaunchableLinearLayout.kt
@@ -23,7 +23,7 @@
 import com.android.systemui.animation.LaunchableViewDelegate
 
 /** A [LinearLayout] that also implements [LaunchableView]. */
-class LaunchableLinearLayout : LinearLayout, LaunchableView {
+open class LaunchableLinearLayout : LinearLayout, LaunchableView {
     private val delegate =
         LaunchableViewDelegate(
             this,
diff --git a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebug.java b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebug.java
index 267e036..61e4c32 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebug.java
+++ b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebug.java
@@ -19,7 +19,7 @@
 import static com.android.systemui.flags.FlagManager.ACTION_GET_FLAGS;
 import static com.android.systemui.flags.FlagManager.ACTION_SET_FLAG;
 import static com.android.systemui.flags.FlagManager.EXTRA_FLAGS;
-import static com.android.systemui.flags.FlagManager.EXTRA_ID;
+import static com.android.systemui.flags.FlagManager.EXTRA_NAME;
 import static com.android.systemui.flags.FlagManager.EXTRA_VALUE;
 import static com.android.systemui.flags.FlagsCommonModule.ALL_FLAGS;
 
@@ -40,6 +40,7 @@
 
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.util.settings.GlobalSettings;
 import com.android.systemui.util.settings.SecureSettings;
 
 import org.jetbrains.annotations.NotNull;
@@ -73,14 +74,15 @@
 
     private final FlagManager mFlagManager;
     private final Context mContext;
+    private final GlobalSettings mGlobalSettings;
     private final SecureSettings mSecureSettings;
     private final Resources mResources;
     private final SystemPropertiesHelper mSystemProperties;
     private final ServerFlagReader mServerFlagReader;
-    private final Map<Integer, Flag<?>> mAllFlags;
-    private final Map<Integer, Boolean> mBooleanFlagCache = new TreeMap<>();
-    private final Map<Integer, String> mStringFlagCache = new TreeMap<>();
-    private final Map<Integer, Integer> mIntFlagCache = new TreeMap<>();
+    private final Map<String, Flag<?>> mAllFlags;
+    private final Map<String, Boolean> mBooleanFlagCache = new TreeMap<>();
+    private final Map<String, String> mStringFlagCache = new TreeMap<>();
+    private final Map<String, Integer> mIntFlagCache = new TreeMap<>();
     private final Restarter mRestarter;
 
     private final ServerFlagReader.ChangeListener mOnPropertiesChanged =
@@ -95,14 +97,16 @@
     public FeatureFlagsDebug(
             FlagManager flagManager,
             Context context,
+            GlobalSettings globalSettings,
             SecureSettings secureSettings,
             SystemPropertiesHelper systemProperties,
             @Main Resources resources,
             ServerFlagReader serverFlagReader,
-            @Named(ALL_FLAGS) Map<Integer, Flag<?>> allFlags,
+            @Named(ALL_FLAGS) Map<String, Flag<?>> allFlags,
             Restarter restarter) {
         mFlagManager = flagManager;
         mContext = context;
+        mGlobalSettings = globalSettings;
         mSecureSettings = secureSettings;
         mResources = resources;
         mSystemProperties = systemProperties;
@@ -134,96 +138,103 @@
     }
 
     private boolean isEnabledInternal(@NotNull BooleanFlag flag) {
-        int id = flag.getId();
-        if (!mBooleanFlagCache.containsKey(id)) {
-            mBooleanFlagCache.put(id,
+        String name = flag.getName();
+        if (!mBooleanFlagCache.containsKey(name)) {
+            mBooleanFlagCache.put(name,
                     readBooleanFlagInternal(flag, flag.getDefault()));
         }
 
-        return mBooleanFlagCache.get(id);
+        return mBooleanFlagCache.get(name);
     }
 
     @Override
     public boolean isEnabled(@NonNull ResourceBooleanFlag flag) {
-        int id = flag.getId();
-        if (!mBooleanFlagCache.containsKey(id)) {
-            mBooleanFlagCache.put(id,
+        String name = flag.getName();
+        if (!mBooleanFlagCache.containsKey(name)) {
+            mBooleanFlagCache.put(name,
                     readBooleanFlagInternal(flag, mResources.getBoolean(flag.getResourceId())));
         }
 
-        return mBooleanFlagCache.get(id);
+        return mBooleanFlagCache.get(name);
     }
 
     @Override
     public boolean isEnabled(@NonNull SysPropBooleanFlag flag) {
-        int id = flag.getId();
-        if (!mBooleanFlagCache.containsKey(id)) {
+        String name = flag.getName();
+        if (!mBooleanFlagCache.containsKey(name)) {
             // Use #readFlagValue to get the default. That will allow it to fall through to
             // teamfood if need be.
             mBooleanFlagCache.put(
-                    id,
+                    name,
                     mSystemProperties.getBoolean(
                             flag.getName(),
                             readBooleanFlagInternal(flag, flag.getDefault())));
         }
 
-        return mBooleanFlagCache.get(id);
+        return mBooleanFlagCache.get(name);
     }
 
     @NonNull
     @Override
     public String getString(@NonNull StringFlag flag) {
-        int id = flag.getId();
-        if (!mStringFlagCache.containsKey(id)) {
-            mStringFlagCache.put(id,
-                    readFlagValueInternal(id, flag.getDefault(), StringFlagSerializer.INSTANCE));
+        String name = flag.getName();
+        if (!mStringFlagCache.containsKey(name)) {
+            mStringFlagCache.put(name,
+                    readFlagValueInternal(
+                            flag.getId(), name, flag.getDefault(), StringFlagSerializer.INSTANCE));
         }
 
-        return mStringFlagCache.get(id);
+        return mStringFlagCache.get(name);
     }
 
     @NonNull
     @Override
     public String getString(@NonNull ResourceStringFlag flag) {
-        int id = flag.getId();
-        if (!mStringFlagCache.containsKey(id)) {
-            mStringFlagCache.put(id,
-                    readFlagValueInternal(id, mResources.getString(flag.getResourceId()),
+        String name = flag.getName();
+        if (!mStringFlagCache.containsKey(name)) {
+            mStringFlagCache.put(name,
+                    readFlagValueInternal(
+                            flag.getId(), name, mResources.getString(flag.getResourceId()),
                             StringFlagSerializer.INSTANCE));
         }
 
-        return mStringFlagCache.get(id);
+        return mStringFlagCache.get(name);
     }
 
 
     @NonNull
     @Override
     public int getInt(@NonNull IntFlag flag) {
-        int id = flag.getId();
-        if (!mIntFlagCache.containsKey(id)) {
-            mIntFlagCache.put(id,
-                    readFlagValueInternal(id, flag.getDefault(), IntFlagSerializer.INSTANCE));
+        String name = flag.getName();
+        if (!mIntFlagCache.containsKey(name)) {
+            mIntFlagCache.put(name,
+                    readFlagValueInternal(
+                            flag.getId(), name, flag.getDefault(), IntFlagSerializer.INSTANCE));
         }
 
-        return mIntFlagCache.get(id);
+        return mIntFlagCache.get(name);
     }
 
     @NonNull
     @Override
     public int getInt(@NonNull ResourceIntFlag flag) {
-        int id = flag.getId();
-        if (!mIntFlagCache.containsKey(id)) {
-            mIntFlagCache.put(id,
-                    readFlagValueInternal(id, mResources.getInteger(flag.getResourceId()),
+        String name = flag.getName();
+        if (!mIntFlagCache.containsKey(name)) {
+            mIntFlagCache.put(name,
+                    readFlagValueInternal(
+                            flag.getId(), name, mResources.getInteger(flag.getResourceId()),
                             IntFlagSerializer.INSTANCE));
         }
 
-        return mIntFlagCache.get(id);
+        return mIntFlagCache.get(name);
     }
 
     /** Specific override for Boolean flags that checks against the teamfood list.*/
     private boolean readBooleanFlagInternal(Flag<Boolean> flag, boolean defaultValue) {
-        Boolean result = readBooleanFlagOverride(flag.getId());
+        Boolean result = readBooleanFlagOverride(flag.getName());
+        if (result == null) {
+            result = readBooleanFlagOverride(flag.getId());
+        }
         boolean hasServerOverride = mServerFlagReader.hasOverride(
                 flag.getNamespace(), flag.getName());
 
@@ -232,7 +243,7 @@
         if (!hasServerOverride
                 && !defaultValue
                 && result == null
-                && flag.getId() != Flags.TEAMFOOD.getId()
+                && !flag.getName().equals(Flags.TEAMFOOD.getName())
                 && flag.getTeamfood()) {
             return isEnabled(Flags.TEAMFOOD);
         }
@@ -245,16 +256,31 @@
         return readFlagValueInternal(id, BooleanFlagSerializer.INSTANCE);
     }
 
+    private Boolean readBooleanFlagOverride(String name) {
+        return readFlagValueInternal(name, BooleanFlagSerializer.INSTANCE);
+    }
+
+    // TODO(b/265188950): Remove id from this method once ids are fully deprecated.
     @NonNull
     private <T> T readFlagValueInternal(
-            int id, @NonNull T defaultValue, FlagSerializer<T> serializer) {
+            int id, String name, @NonNull T defaultValue, FlagSerializer<T> serializer) {
         requireNonNull(defaultValue, "defaultValue");
-        T result = readFlagValueInternal(id, serializer);
-        return result == null ? defaultValue : result;
+        T resultForName = readFlagValueInternal(name, serializer);
+        if (resultForName == null) {
+            T resultForId = readFlagValueInternal(id, serializer);
+            if (resultForId == null) {
+                return defaultValue;
+            } else {
+                setFlagValue(name, resultForId, serializer);
+                return resultForId;
+            }
+        }
+        return resultForName;
     }
 
 
     /** Returns the stored value or null if not set. */
+    // TODO(b/265188950): Remove method this once ids are fully deprecated.
     @Nullable
     private <T> T readFlagValueInternal(int id, FlagSerializer<T> serializer) {
         try {
@@ -265,51 +291,71 @@
         return null;
     }
 
-    private <T> void setFlagValue(int id, @NonNull T value, FlagSerializer<T> serializer) {
+    /** Returns the stored value or null if not set. */
+    @Nullable
+    private <T> T readFlagValueInternal(String name, FlagSerializer<T> serializer) {
+        try {
+            return mFlagManager.readFlagValue(name, serializer);
+        } catch (Exception e) {
+            eraseInternal(name);
+        }
+        return null;
+    }
+
+    private <T> void setFlagValue(String name, @NonNull T value, FlagSerializer<T> serializer) {
         requireNonNull(value, "Cannot set a null value");
-        T currentValue = readFlagValueInternal(id, serializer);
+        T currentValue = readFlagValueInternal(name, serializer);
         if (Objects.equals(currentValue, value)) {
-            Log.i(TAG, "Flag id " + id + " is already " + value);
+            Log.i(TAG, "Flag id " + name + " is already " + value);
             return;
         }
         final String data = serializer.toSettingsData(value);
         if (data == null) {
-            Log.w(TAG, "Failed to set id " + id + " to " + value);
+            Log.w(TAG, "Failed to set id " + name + " to " + value);
             return;
         }
-        mSecureSettings.putStringForUser(mFlagManager.idToSettingsKey(id), data,
+        mGlobalSettings.putStringForUser(mFlagManager.nameToSettingsKey(name), data,
                 UserHandle.USER_CURRENT);
-        Log.i(TAG, "Set id " + id + " to " + value);
-        removeFromCache(id);
-        mFlagManager.dispatchListenersAndMaybeRestart(id, this::restartSystemUI);
+        Log.i(TAG, "Set id " + name + " to " + value);
+        removeFromCache(name);
+        mFlagManager.dispatchListenersAndMaybeRestart(name, this::restartSystemUI);
     }
 
     <T> void eraseFlag(Flag<T> flag) {
         if (flag instanceof SysPropFlag) {
             mSystemProperties.erase(((SysPropFlag<T>) flag).getName());
-            dispatchListenersAndMaybeRestart(flag.getId(), this::restartAndroid);
+            dispatchListenersAndMaybeRestart(flag.getName(), this::restartAndroid);
         } else {
-            eraseFlag(flag.getId());
+            eraseFlag(flag.getName());
         }
     }
 
     /** Erase a flag's overridden value if there is one. */
-    private void eraseFlag(int id) {
-        eraseInternal(id);
-        removeFromCache(id);
-        dispatchListenersAndMaybeRestart(id, this::restartSystemUI);
+    private void eraseFlag(String name) {
+        eraseInternal(name);
+        removeFromCache(name);
+        dispatchListenersAndMaybeRestart(name, this::restartSystemUI);
     }
 
-    private void dispatchListenersAndMaybeRestart(int id, Consumer<Boolean> restartAction) {
-        mFlagManager.dispatchListenersAndMaybeRestart(id, restartAction);
+    private void dispatchListenersAndMaybeRestart(String name, Consumer<Boolean> restartAction) {
+        mFlagManager.dispatchListenersAndMaybeRestart(name, restartAction);
     }
 
-    /** Works just like {@link #eraseFlag(int)} except that it doesn't restart SystemUI. */
+    /** Works just like {@link #eraseFlag(String)} except that it doesn't restart SystemUI. */
+    // TODO(b/265188950): Remove method this once ids are fully deprecated.
     private void eraseInternal(int id) {
-        // We can't actually "erase" things from sysprops, but we can set them to empty!
-        mSecureSettings.putStringForUser(mFlagManager.idToSettingsKey(id), "",
+        // We can't actually "erase" things from settings, but we can set them to empty!
+        mGlobalSettings.putStringForUser(mFlagManager.idToSettingsKey(id), "",
                 UserHandle.USER_CURRENT);
-        Log.i(TAG, "Erase id " + id);
+        Log.i(TAG, "Erase name " + id);
+    }
+
+    /** Works just like {@link #eraseFlag(String)} except that it doesn't restart SystemUI. */
+    private void eraseInternal(String name) {
+        // We can't actually "erase" things from settings, but we can set them to empty!
+        mGlobalSettings.putStringForUser(mFlagManager.nameToSettingsKey(name), "",
+                UserHandle.USER_CURRENT);
+        Log.i(TAG, "Erase name " + name);
     }
 
     @Override
@@ -340,13 +386,13 @@
 
     void setBooleanFlagInternal(Flag<?> flag, boolean value) {
         if (flag instanceof BooleanFlag) {
-            setFlagValue(flag.getId(), value, BooleanFlagSerializer.INSTANCE);
+            setFlagValue(flag.getName(), value, BooleanFlagSerializer.INSTANCE);
         } else if (flag instanceof ResourceBooleanFlag) {
-            setFlagValue(flag.getId(), value, BooleanFlagSerializer.INSTANCE);
+            setFlagValue(flag.getName(), value, BooleanFlagSerializer.INSTANCE);
         } else if (flag instanceof SysPropBooleanFlag) {
             // Store SysProp flags in SystemProperties where they can read by outside parties.
             mSystemProperties.setBoolean(((SysPropBooleanFlag) flag).getName(), value);
-            dispatchListenersAndMaybeRestart(flag.getId(),
+            dispatchListenersAndMaybeRestart(flag.getName(),
                     FeatureFlagsDebug.this::restartAndroid);
         } else {
             throw new IllegalArgumentException("Unknown flag type");
@@ -355,9 +401,9 @@
 
     void setStringFlagInternal(Flag<?> flag, String value) {
         if (flag instanceof StringFlag) {
-            setFlagValue(flag.getId(), value, StringFlagSerializer.INSTANCE);
+            setFlagValue(flag.getName(), value, StringFlagSerializer.INSTANCE);
         } else if (flag instanceof ResourceStringFlag) {
-            setFlagValue(flag.getId(), value, StringFlagSerializer.INSTANCE);
+            setFlagValue(flag.getName(), value, StringFlagSerializer.INSTANCE);
         } else {
             throw new IllegalArgumentException("Unknown flag type");
         }
@@ -365,9 +411,9 @@
 
     void setIntFlagInternal(Flag<?> flag, int value) {
         if (flag instanceof IntFlag) {
-            setFlagValue(flag.getId(), value, IntFlagSerializer.INSTANCE);
+            setFlagValue(flag.getName(), value, IntFlagSerializer.INSTANCE);
         } else if (flag instanceof ResourceIntFlag) {
-            setFlagValue(flag.getId(), value, IntFlagSerializer.INSTANCE);
+            setFlagValue(flag.getName(), value, IntFlagSerializer.INSTANCE);
         } else {
             throw new IllegalArgumentException("Unknown flag type");
         }
@@ -406,17 +452,17 @@
                 Log.w(TAG, "No extras");
                 return;
             }
-            int id = extras.getInt(EXTRA_ID);
-            if (id <= 0) {
-                Log.w(TAG, "ID not set or less than  or equal to 0: " + id);
+            String name = extras.getString(EXTRA_NAME);
+            if (name == null || name.isEmpty()) {
+                Log.w(TAG, "NAME not set or is empty: " + name);
                 return;
             }
 
-            if (!mAllFlags.containsKey(id)) {
-                Log.w(TAG, "Tried to set unknown id: " + id);
+            if (!mAllFlags.containsKey(name)) {
+                Log.w(TAG, "Tried to set unknown name: " + name);
                 return;
             }
-            Flag<?> flag = mAllFlags.get(id);
+            Flag<?> flag = mAllFlags.get(name);
 
             if (!extras.containsKey(EXTRA_VALUE)) {
                 eraseFlag(flag);
@@ -453,13 +499,16 @@
 
             if (f instanceof ReleasedFlag) {
                 enabled = isEnabled((ReleasedFlag) f);
-                overridden = readBooleanFlagOverride(f.getId()) != null;
+                overridden = readBooleanFlagOverride(f.getName()) != null
+                            || readBooleanFlagOverride(f.getId()) != null;
             } else if (f instanceof UnreleasedFlag) {
                 enabled = isEnabled((UnreleasedFlag) f);
-                overridden = readBooleanFlagOverride(f.getId()) != null;
+                overridden = readBooleanFlagOverride(f.getName()) != null
+                            || readBooleanFlagOverride(f.getId()) != null;
             } else if (f instanceof ResourceBooleanFlag) {
                 enabled = isEnabled((ResourceBooleanFlag) f);
-                overridden = readBooleanFlagOverride(f.getId()) != null;
+                overridden = readBooleanFlagOverride(f.getName()) != null
+                            || readBooleanFlagOverride(f.getId()) != null;
             } else if (f instanceof SysPropBooleanFlag) {
                 // TODO(b/223379190): Teamfood not supported for sysprop flags yet.
                 enabled = isEnabled((SysPropBooleanFlag) f);
@@ -481,9 +530,9 @@
         }
     };
 
-    private void removeFromCache(int id) {
-        mBooleanFlagCache.remove(id);
-        mStringFlagCache.remove(id);
+    private void removeFromCache(String name) {
+        mBooleanFlagCache.remove(name);
+        mStringFlagCache.remove(name);
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsRelease.java b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsRelease.java
index 8bddacc..7e14237 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsRelease.java
+++ b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsRelease.java
@@ -21,18 +21,16 @@
 import static java.util.Objects.requireNonNull;
 
 import android.content.res.Resources;
-import android.util.SparseArray;
-import android.util.SparseBooleanArray;
 
 import androidx.annotation.NonNull;
 
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Main;
-import com.android.systemui.util.DeviceConfigProxy;
 
 import org.jetbrains.annotations.NotNull;
 
 import java.io.PrintWriter;
+import java.util.HashMap;
 import java.util.Map;
 
 import javax.inject.Inject;
@@ -50,12 +48,11 @@
 
     private final Resources mResources;
     private final SystemPropertiesHelper mSystemProperties;
-    private final DeviceConfigProxy mDeviceConfigProxy;
     private final ServerFlagReader mServerFlagReader;
     private final Restarter mRestarter;
-    private final Map<Integer, Flag<?>> mAllFlags;
-    SparseBooleanArray mBooleanCache = new SparseBooleanArray();
-    SparseArray<String> mStringCache = new SparseArray<>();
+    private final Map<String, Flag<?>> mAllFlags;
+    private final Map<String, Boolean> mBooleanCache = new HashMap<>();
+    private final Map<String, String> mStringCache = new HashMap<>();
 
     private final ServerFlagReader.ChangeListener mOnPropertiesChanged =
             new ServerFlagReader.ChangeListener() {
@@ -69,13 +66,11 @@
     public FeatureFlagsRelease(
             @Main Resources resources,
             SystemPropertiesHelper systemProperties,
-            DeviceConfigProxy deviceConfigProxy,
             ServerFlagReader serverFlagReader,
-            @Named(ALL_FLAGS) Map<Integer, Flag<?>> allFlags,
+            @Named(ALL_FLAGS) Map<String, Flag<?>> allFlags,
             Restarter restarter) {
         mResources = resources;
         mSystemProperties = systemProperties;
-        mDeviceConfigProxy = deviceConfigProxy;
         mServerFlagReader = serverFlagReader;
         mAllFlags = allFlags;
         mRestarter = restarter;
@@ -106,50 +101,48 @@
 
     @Override
     public boolean isEnabled(ResourceBooleanFlag flag) {
-        int cacheIndex = mBooleanCache.indexOfKey(flag.getId());
-        if (cacheIndex < 0) {
-            return isEnabled(flag.getId(), mResources.getBoolean(flag.getResourceId()));
+        if (!mBooleanCache.containsKey(flag.getName())) {
+            return isEnabled(flag.getName(), mResources.getBoolean(flag.getResourceId()));
         }
 
-        return mBooleanCache.valueAt(cacheIndex);
+        return mBooleanCache.get(flag.getName());
     }
 
     @Override
     public boolean isEnabled(SysPropBooleanFlag flag) {
-        int cacheIndex = mBooleanCache.indexOfKey(flag.getId());
-        if (cacheIndex < 0) {
+        if (!mBooleanCache.containsKey(flag.getName())) {
             return isEnabled(
-                    flag.getId(), mSystemProperties.getBoolean(flag.getName(), flag.getDefault()));
+                    flag.getName(),
+                    mSystemProperties.getBoolean(flag.getName(), flag.getDefault()));
         }
 
-        return mBooleanCache.valueAt(cacheIndex);
+        return mBooleanCache.get(flag.getName());
     }
 
-    private boolean isEnabled(int key, boolean defaultValue) {
-        mBooleanCache.append(key, defaultValue);
+    private boolean isEnabled(String name, boolean defaultValue) {
+        mBooleanCache.put(name, defaultValue);
         return defaultValue;
     }
 
     @NonNull
     @Override
     public String getString(@NonNull StringFlag flag) {
-        return getString(flag.getId(), flag.getDefault());
+        return getString(flag.getName(), flag.getDefault());
     }
 
     @NonNull
     @Override
     public String getString(@NonNull ResourceStringFlag flag) {
-        int cacheIndex = mStringCache.indexOfKey(flag.getId());
-        if (cacheIndex < 0) {
-            return getString(flag.getId(),
+        if (!mStringCache.containsKey(flag.getName())) {
+            return getString(flag.getName(),
                     requireNonNull(mResources.getString(flag.getResourceId())));
         }
 
-        return mStringCache.valueAt(cacheIndex);
+        return mStringCache.get(flag.getName());
     }
 
-    private String getString(int key, String defaultValue) {
-        mStringCache.append(key, defaultValue);
+    private String getString(String name, String defaultValue) {
+        mStringCache.put(name, defaultValue);
         return defaultValue;
     }
 
@@ -169,11 +162,17 @@
     public void dump(@NonNull PrintWriter pw, @NonNull String[] args) {
         pw.println("can override: false");
         Map<String, Flag<?>> knownFlags = FlagsFactory.INSTANCE.getKnownFlags();
+        pw.println("Booleans: ");
         for (Map.Entry<String, Flag<?>> nameToFlag : knownFlags.entrySet()) {
             Flag<?> flag = nameToFlag.getValue();
-            int id = flag.getId();
+            if (!(flag instanceof BooleanFlag)
+                    || !(flag instanceof ResourceBooleanFlag)
+                    || !(flag instanceof SysPropBooleanFlag)) {
+                continue;
+            }
+
             boolean def = false;
-            if (mBooleanCache.indexOfKey(flag.getId()) < 0) {
+            if (!mBooleanCache.containsKey(flag.getName())) {
                 if (flag instanceof SysPropBooleanFlag) {
                     SysPropBooleanFlag f = (SysPropBooleanFlag) flag;
                     def = mSystemProperties.getBoolean(f.getName(), f.getDefault());
@@ -185,15 +184,32 @@
                     def = f.getDefault();
                 }
             }
-            pw.println("  sysui_flag_" + id + ": " + (mBooleanCache.get(id, def)));
+            pw.println(
+                    "  " + flag.getName() + ": "
+                            + (mBooleanCache.getOrDefault(flag.getName(), def)));
         }
-        int numStrings = mStringCache.size();
-        pw.println("Strings: " + numStrings);
-        for (int i = 0; i < numStrings; i++) {
-            final int id = mStringCache.keyAt(i);
-            final String value = mStringCache.valueAt(i);
-            final int length = value.length();
-            pw.println("  sysui_flag_" + id + ": [length=" + length + "] \"" + value + "\"");
+
+        pw.println("Strings: ");
+        for (Map.Entry<String, Flag<?>> nameToFlag : knownFlags.entrySet()) {
+            Flag<?> flag = nameToFlag.getValue();
+            if (!(flag instanceof StringFlag)
+                    || !(flag instanceof ResourceStringFlag)) {
+                continue;
+            }
+
+            String def = "";
+            if (!mBooleanCache.containsKey(flag.getName())) {
+                if (flag instanceof ResourceStringFlag) {
+                    ResourceStringFlag f = (ResourceStringFlag) flag;
+                    def = mResources.getString(f.getResourceId());
+                } else if (flag instanceof StringFlag) {
+                    StringFlag f = (StringFlag) flag;
+                    def = f.getDefault();
+                }
+            }
+            String value = mStringCache.getOrDefault(flag.getName(), def);
+            pw.println(
+                    "  " + flag.getName() + ": [length=" + value.length() + "] \"" + value + "\"");
         }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/flags/FlagCommand.java b/packages/SystemUI/src/com/android/systemui/flags/FlagCommand.java
index b7fc0e4..daf9429 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/FlagCommand.java
+++ b/packages/SystemUI/src/com/android/systemui/flags/FlagCommand.java
@@ -39,12 +39,12 @@
     private final List<String> mOffCommands = List.of("false", "off", "0", "disable");
     private final List<String> mSetCommands = List.of("set", "put");
     private final FeatureFlagsDebug mFeatureFlags;
-    private final Map<Integer, Flag<?>> mAllFlags;
+    private final Map<String, Flag<?>> mAllFlags;
 
     @Inject
     FlagCommand(
             FeatureFlagsDebug featureFlags,
-            @Named(ALL_FLAGS) Map<Integer, Flag<?>> allFlags
+            @Named(ALL_FLAGS) Map<String, Flag<?>> allFlags
     ) {
         mFeatureFlags = featureFlags;
         mAllFlags = allFlags;
@@ -53,30 +53,22 @@
     @Override
     public void execute(@NonNull PrintWriter pw, @NonNull List<String> args) {
         if (args.size() == 0) {
-            pw.println("Error: no flag id supplied");
+            pw.println("Error: no flag name supplied");
             help(pw);
             pw.println();
             printKnownFlags(pw);
             return;
         }
 
-        int id = 0;
-        try {
-            id = Integer.parseInt(args.get(0));
-            if (!mAllFlags.containsKey(id)) {
-                pw.println("Unknown flag id: " + id);
-                pw.println();
-                printKnownFlags(pw);
-                return;
-            }
-        } catch (NumberFormatException e) {
-            id = flagNameToId(args.get(0));
-            if (id == 0) {
-                pw.println("Invalid flag. Must an integer id or flag name: " + args.get(0));
-                return;
-            }
+        String name = args.get(0);
+        if (!mAllFlags.containsKey(name)) {
+            pw.println("Unknown flag name: " + name);
+            pw.println();
+            printKnownFlags(pw);
+            return;
         }
-        Flag<?> flag = mAllFlags.get(id);
+
+        Flag<?> flag = mAllFlags.get(name);
 
         String cmd = "";
         if (args.size() > 1) {
@@ -117,7 +109,7 @@
                 return;
             }
 
-            pw.println("Flag " + id + " is " + newValue);
+            pw.println("Flag " + name + " is " + newValue);
             pw.flush();  // Next command will restart sysui, so flush before we do so.
             if (shouldSet) {
                 mFeatureFlags.setBooleanFlagInternal(flag, newValue);
@@ -136,11 +128,11 @@
                     return;
                 }
                 String value = args.get(2);
-                pw.println("Setting Flag " + id + " to " + value);
+                pw.println("Setting Flag " + name + " to " + value);
                 pw.flush();  // Next command will restart sysui, so flush before we do so.
                 mFeatureFlags.setStringFlagInternal(flag, args.get(2));
             } else {
-                pw.println("Flag " + id + " is " + getStringFlag(flag));
+                pw.println("Flag " + name + " is " + getStringFlag(flag));
             }
             return;
         } else if (isIntFlag(flag)) {
@@ -155,11 +147,11 @@
                     return;
                 }
                 int value = Integer.parseInt(args.get(2));
-                pw.println("Setting Flag " + id + " to " + value);
+                pw.println("Setting Flag " + name + " to " + value);
                 pw.flush();  // Next command will restart sysui, so flush before we do so.
                 mFeatureFlags.setIntFlagInternal(flag, value);
             } else {
-                pw.println("Flag " + id + " is " + getIntFlag(flag));
+                pw.println("Flag " + name + " is " + getIntFlag(flag));
             }
             return;
         }
@@ -182,8 +174,7 @@
     private boolean isBooleanFlag(Flag<?> flag) {
         return (flag instanceof BooleanFlag)
                 || (flag instanceof ResourceBooleanFlag)
-                || (flag instanceof SysPropFlag)
-                || (flag instanceof DeviceConfigBooleanFlag);
+                || (flag instanceof SysPropFlag);
     }
 
     private boolean isBooleanFlagEnabled(Flag<?> flag) {
@@ -252,15 +243,14 @@
         for (int i = 0; i < longestFieldName - "Flag Name".length() + 1; i++) {
             pw.print(" ");
         }
-        pw.println("ID   Value");
+        pw.println(" Value");
         for (int i = 0; i < longestFieldName; i++) {
             pw.print("=");
         }
-        pw.println(" ==== ========");
+        pw.println(" ========");
         for (String fieldName : fields.keySet()) {
             Flag<?> flag = fields.get(fieldName);
-            int id = flag.getId();
-            if (id == 0 || !mAllFlags.containsKey(id)) {
+            if (!mAllFlags.containsKey(flag.getName())) {
                 continue;
             }
             pw.print(fieldName);
@@ -268,9 +258,9 @@
             for (int i = 0; i < longestFieldName - fieldWidth + 1; i++) {
                 pw.print(" ");
             }
-            pw.printf("%-4d ", id);
+            pw.print(" ");
             if (isBooleanFlag(flag)) {
-                pw.println(isBooleanFlagEnabled(mAllFlags.get(id)));
+                pw.println(isBooleanFlagEnabled(flag));
             } else if (isStringFlag(flag)) {
                 pw.println(getStringFlag(flag));
             } else if (isIntFlag(flag)) {
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index a6977e1..fa0ce24 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -129,13 +129,6 @@
     val LOCKSCREEN_CUSTOM_CLOCKS = unreleasedFlag(207, "lockscreen_custom_clocks", teamfood = true)
 
     /**
-     * Flag to enable the usage of the new bouncer data source. This is a refactor of and eventual
-     * replacement of KeyguardBouncer.java.
-     */
-    // TODO(b/254512385): Tracking Bug
-    @JvmField val MODERN_BOUNCER = releasedFlag(208, "modern_bouncer")
-
-    /**
      * Whether the clock on a wide lock screen should use the new "stepping" animation for moving
      * the digits when the clock moves.
      */
@@ -364,6 +357,10 @@
     // TODO(b/266157412): Tracking Bug
     val MEDIA_RETAIN_SESSIONS = unreleasedFlag(913, "media_retain_sessions")
 
+    // TODO(b/266739309): Tracking Bug
+    @JvmField
+    val MEDIA_RECOMMENDATION_CARD_UPDATE = unreleasedFlag(914, "media_recommendation_card_update")
+
     // 1000 - dock
     val SIMULATE_DOCK_THROUGH_CHARGING = releasedFlag(1000, "simulate_dock_through_charging")
 
diff --git a/packages/SystemUI/src/com/android/systemui/flags/FlagsCommonModule.kt b/packages/SystemUI/src/com/android/systemui/flags/FlagsCommonModule.kt
index 8442230..0054d26 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/FlagsCommonModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/FlagsCommonModule.kt
@@ -28,8 +28,8 @@
         @JvmStatic
         @Provides
         @Named(ALL_FLAGS)
-        fun providesAllFlags(): Map<Int, Flag<*>> {
-            return FlagsFactory.knownFlags.map { it.value.id to it.value }.toMap()
+        fun providesAllFlags(): Map<String, Flag<*>> {
+            return FlagsFactory.knownFlags
         }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/flags/ServerFlagReader.kt b/packages/SystemUI/src/com/android/systemui/flags/ServerFlagReader.kt
index ae05c46..a02b795 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/ServerFlagReader.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/ServerFlagReader.kt
@@ -54,10 +54,11 @@
                 return
             }
 
+
             for ((listener, flags) in listeners) {
                 propLoop@ for (propName in properties.keyset) {
                     for (flag in flags) {
-                        if (propName == getServerOverrideName(flag.id)) {
+                        if (propName == getServerOverrideName(flag.id) || propName == flag.name) {
                             listener.onChange()
                             break@propLoop
                         }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBouncerViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBouncerViewBinder.kt
index 5e46c5d..c1731e0 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBouncerViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBouncerViewBinder.kt
@@ -106,13 +106,6 @@
                             hostViewController.appear(
                                 SystemBarUtils.getStatusBarHeight(view.context)
                             )
-                        }
-                    }
-
-                    launch {
-                        viewModel.showWithFullExpansion.collect { model ->
-                            hostViewController.resetSecurityContainer()
-                            hostViewController.showPromptReason(model.promptReason)
                             hostViewController.onResume()
                         }
                     }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBouncerViewModel.kt
index c6002d6..737c35d 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBouncerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBouncerViewModel.kt
@@ -20,12 +20,10 @@
 import com.android.systemui.keyguard.data.BouncerView
 import com.android.systemui.keyguard.data.BouncerViewDelegate
 import com.android.systemui.keyguard.domain.interactor.PrimaryBouncerInteractor
-import com.android.systemui.keyguard.shared.constants.KeyguardBouncerConstants.EXPANSION_VISIBLE
 import com.android.systemui.keyguard.shared.model.BouncerShowMessageModel
 import com.android.systemui.keyguard.shared.model.KeyguardBouncerModel
 import javax.inject.Inject
 import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.filter
 import kotlinx.coroutines.flow.map
 
 /** Models UI state for the lock screen bouncer; handles user input. */
@@ -44,10 +42,6 @@
     /** Observe whether bouncer is showing. */
     val show: Flow<KeyguardBouncerModel> = interactor.show
 
-    /** Observe visible expansion when bouncer is showing. */
-    val showWithFullExpansion: Flow<KeyguardBouncerModel> =
-        interactor.show.filter { it.expansionAmount == EXPANSION_VISIBLE }
-
     /** Observe whether bouncer is hiding. */
     val hide: Flow<Unit> = interactor.hide
 
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/models/recommendation/RecommendationViewHolder.kt b/packages/SystemUI/src/com/android/systemui/media/controls/models/recommendation/RecommendationViewHolder.kt
index 1a10b18..8c1ec16 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/models/recommendation/RecommendationViewHolder.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/models/recommendation/RecommendationViewHolder.kt
@@ -21,6 +21,7 @@
 import android.view.ViewGroup
 import android.widget.ImageView
 import android.widget.TextView
+import com.android.internal.widget.CachingIconView
 import com.android.systemui.R
 import com.android.systemui.media.controls.models.GutsViewHolder
 import com.android.systemui.media.controls.ui.IlluminationDrawable
@@ -29,18 +30,15 @@
 private const val TAG = "RecommendationViewHolder"
 
 /** ViewHolder for a Smartspace media recommendation. */
-class RecommendationViewHolder private constructor(itemView: View) {
+class RecommendationViewHolder private constructor(itemView: View, updatedView: Boolean) {
 
     val recommendations = itemView as TransitionLayout
 
     // Recommendation screen
-    val cardIcon = itemView.requireViewById<ImageView>(R.id.recommendation_card_icon)
-    val mediaCoverItems =
-        listOf<ImageView>(
-            itemView.requireViewById(R.id.media_cover1),
-            itemView.requireViewById(R.id.media_cover2),
-            itemView.requireViewById(R.id.media_cover3)
-        )
+    lateinit var cardIcon: ImageView
+    lateinit var mediaAppIcons: List<CachingIconView>
+    lateinit var cardTitle: TextView
+
     val mediaCoverContainers =
         listOf<ViewGroup>(
             itemView.requireViewById(R.id.media_cover1_container),
@@ -48,21 +46,45 @@
             itemView.requireViewById(R.id.media_cover3_container)
         )
     val mediaTitles: List<TextView> =
-        listOf(
-            itemView.requireViewById(R.id.media_title1),
-            itemView.requireViewById(R.id.media_title2),
-            itemView.requireViewById(R.id.media_title3)
-        )
+        if (updatedView) {
+            mediaCoverContainers.map { it.requireViewById(R.id.media_title) }
+        } else {
+            listOf(
+                itemView.requireViewById(R.id.media_title1),
+                itemView.requireViewById(R.id.media_title2),
+                itemView.requireViewById(R.id.media_title3)
+            )
+        }
     val mediaSubtitles: List<TextView> =
-        listOf(
-            itemView.requireViewById(R.id.media_subtitle1),
-            itemView.requireViewById(R.id.media_subtitle2),
-            itemView.requireViewById(R.id.media_subtitle3)
-        )
+        if (updatedView) {
+            mediaCoverContainers.map { it.requireViewById(R.id.media_subtitle) }
+        } else {
+            listOf(
+                itemView.requireViewById(R.id.media_subtitle1),
+                itemView.requireViewById(R.id.media_subtitle2),
+                itemView.requireViewById(R.id.media_subtitle3)
+            )
+        }
 
+    val mediaCoverItems: List<ImageView> =
+        if (updatedView) {
+            mediaCoverContainers.map { it.requireViewById(R.id.media_cover) }
+        } else {
+            listOf(
+                itemView.requireViewById(R.id.media_cover1),
+                itemView.requireViewById(R.id.media_cover2),
+                itemView.requireViewById(R.id.media_cover3)
+            )
+        }
     val gutsViewHolder = GutsViewHolder(itemView)
 
     init {
+        if (updatedView) {
+            mediaAppIcons = mediaCoverContainers.map { it.requireViewById(R.id.media_rec_app_icon) }
+            cardTitle = itemView.requireViewById(R.id.media_rec_title)
+        } else {
+            cardIcon = itemView.requireViewById<ImageView>(R.id.recommendation_card_icon)
+        }
         (recommendations.background as IlluminationDrawable).let { background ->
             mediaCoverContainers.forEach { background.registerLightSource(it) }
             background.registerLightSource(gutsViewHolder.cancel)
@@ -83,36 +105,52 @@
          * @param parent Parent of inflated view.
          */
         @JvmStatic
-        fun create(inflater: LayoutInflater, parent: ViewGroup): RecommendationViewHolder {
+        fun create(
+            inflater: LayoutInflater,
+            parent: ViewGroup,
+            updatedView: Boolean,
+        ): RecommendationViewHolder {
             val itemView =
-                inflater.inflate(
-                    R.layout.media_smartspace_recommendations,
-                    parent,
-                    false /* attachToRoot */
-                )
+                if (updatedView) {
+                    inflater.inflate(
+                        R.layout.media_recommendations,
+                        parent,
+                        false /* attachToRoot */
+                    )
+                } else {
+                    inflater.inflate(
+                        R.layout.media_smartspace_recommendations,
+                        parent,
+                        false /* attachToRoot */
+                    )
+                }
             // Because this media view (a TransitionLayout) is used to measure and layout the views
             // in various states before being attached to its parent, we can't depend on the default
             // LAYOUT_DIRECTION_INHERIT to correctly resolve the ltr direction.
             itemView.layoutDirection = View.LAYOUT_DIRECTION_LOCALE
-            return RecommendationViewHolder(itemView)
+            return RecommendationViewHolder(itemView, updatedView)
         }
 
         // Res Ids for the control components on the recommendation view.
         val controlsIds =
             setOf(
                 R.id.recommendation_card_icon,
+                R.id.media_rec_title,
                 R.id.media_cover1,
                 R.id.media_cover2,
                 R.id.media_cover3,
+                R.id.media_cover,
                 R.id.media_cover1_container,
                 R.id.media_cover2_container,
                 R.id.media_cover3_container,
                 R.id.media_title1,
                 R.id.media_title2,
                 R.id.media_title3,
+                R.id.media_title,
                 R.id.media_subtitle1,
                 R.id.media_subtitle2,
-                R.id.media_subtitle3
+                R.id.media_subtitle3,
+                R.id.media_subtitle,
             )
 
         val mediaTitlesAndSubtitlesIds =
@@ -120,9 +158,11 @@
                 R.id.media_title1,
                 R.id.media_title2,
                 R.id.media_title3,
+                R.id.media_title,
                 R.id.media_subtitle1,
                 R.id.media_subtitle2,
-                R.id.media_subtitle3
+                R.id.media_subtitle3,
+                R.id.media_subtitle,
             )
 
         val mediaContainersIds =
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselController.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselController.kt
index e7f7647..b2ad155 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselController.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselController.kt
@@ -43,6 +43,7 @@
 import com.android.systemui.media.controls.models.recommendation.SmartspaceMediaData
 import com.android.systemui.media.controls.pipeline.MediaDataManager
 import com.android.systemui.media.controls.ui.MediaControlPanel.SMARTSPACE_CARD_DISMISS_EVENT
+import com.android.systemui.media.controls.util.MediaFlags
 import com.android.systemui.media.controls.util.MediaUiEventLogger
 import com.android.systemui.media.controls.util.SmallHash
 import com.android.systemui.plugins.ActivityStarter
@@ -88,7 +89,8 @@
     falsingManager: FalsingManager,
     dumpManager: DumpManager,
     private val logger: MediaUiEventLogger,
-    private val debugLogger: MediaCarouselControllerLogger
+    private val debugLogger: MediaCarouselControllerLogger,
+    private val mediaFlags: MediaFlags,
 ) : Dumpable {
     /** The current width of the carousel */
     private var currentCarouselWidth: Int = 0
@@ -647,7 +649,11 @@
 
             val newRecs = mediaControlPanelFactory.get()
             newRecs.attachRecommendation(
-                RecommendationViewHolder.create(LayoutInflater.from(context), mediaContent)
+                RecommendationViewHolder.create(
+                    LayoutInflater.from(context),
+                    mediaContent,
+                    mediaFlags.isRecommendationCardUpdateEnabled()
+                )
             )
             newRecs.mediaViewController.sizeChangedListener = this::updateCarouselDimensions
             val lp =
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 39dd733..2fd4f27 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
@@ -62,6 +62,7 @@
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 import androidx.annotation.UiThread;
+import androidx.appcompat.content.res.AppCompatResources;
 import androidx.constraintlayout.widget.ConstraintSet;
 
 import com.android.internal.annotations.VisibleForTesting;
@@ -755,43 +756,16 @@
         int width = mMediaViewHolder.getAlbumView().getMeasuredWidth();
         int height = mMediaViewHolder.getAlbumView().getMeasuredHeight();
 
-        // WallpaperColors.fromBitmap takes a good amount of time. We do that work
-        // on the background executor to avoid stalling animations on the UI Thread.
         mBackgroundExecutor.execute(() -> {
             // Album art
             ColorScheme mutableColorScheme = null;
             Drawable artwork;
             boolean isArtworkBound;
             Icon artworkIcon = data.getArtwork();
-            WallpaperColors wallpaperColors = null;
-            if (artworkIcon != null) {
-                if (artworkIcon.getType() == Icon.TYPE_BITMAP
-                        || artworkIcon.getType() == Icon.TYPE_ADAPTIVE_BITMAP) {
-                    // Avoids extra processing if this is already a valid bitmap
-                    wallpaperColors = WallpaperColors
-                            .fromBitmap(artworkIcon.getBitmap());
-                } else {
-                    Drawable artworkDrawable = artworkIcon.loadDrawable(mContext);
-                    if (artworkDrawable != null) {
-                        wallpaperColors = WallpaperColors
-                                .fromDrawable(artworkIcon.loadDrawable(mContext));
-                    }
-                }
-            }
+            WallpaperColors wallpaperColors = getWallpaperColor(artworkIcon);
             if (wallpaperColors != null) {
                 mutableColorScheme = new ColorScheme(wallpaperColors, true, Style.CONTENT);
-                Drawable albumArt = getScaledBackground(artworkIcon, width, height);
-                GradientDrawable gradient = (GradientDrawable) mContext
-                        .getDrawable(R.drawable.qs_media_scrim);
-                gradient.setColors(new int[] {
-                        ColorUtilKt.getColorWithAlpha(
-                                MediaColorSchemesKt.backgroundStartFromScheme(mutableColorScheme),
-                                0.25f),
-                        ColorUtilKt.getColorWithAlpha(
-                                MediaColorSchemesKt.backgroundEndFromScheme(mutableColorScheme),
-                                0.9f),
-                });
-                artwork = new LayerDrawable(new Drawable[] { albumArt, gradient });
+                artwork = addGradientToIcon(artworkIcon, mutableColorScheme, width, height);
                 isArtworkBound = true;
             } else {
                 // If there's no artwork, use colors from the app icon
@@ -870,6 +844,96 @@
         });
     }
 
+    private void bindRecommendationArtwork(
+            SmartspaceAction recommendation,
+            String packageName,
+            int itemIndex
+    ) {
+        final int traceCookie = recommendation.hashCode();
+        final String traceName =
+                "MediaControlPanel#bindRecommendationArtwork<" + packageName + ">";
+        Trace.beginAsyncSection(traceName, traceCookie);
+
+        // Capture width & height from views in foreground for artwork scaling in background
+        int width = mRecommendationViewHolder.getMediaCoverContainers().get(0).getMeasuredWidth();
+        int height = mRecommendationViewHolder.getMediaCoverContainers().get(0).getMeasuredHeight();
+
+        mBackgroundExecutor.execute(() -> {
+            // Album art
+            ColorScheme mutableColorScheme = null;
+            Drawable artwork;
+            Icon artworkIcon = recommendation.getIcon();
+            WallpaperColors wallpaperColors = getWallpaperColor(artworkIcon);
+            if (wallpaperColors != null) {
+                mutableColorScheme = new ColorScheme(wallpaperColors, true, Style.CONTENT);
+                artwork = addGradientToIcon(artworkIcon, mutableColorScheme, width, height);
+            } else {
+                artwork = new ColorDrawable(Color.TRANSPARENT);
+            }
+
+            mMainExecutor.execute(() -> {
+                // Bind the artwork drawable to media cover.
+                ImageView mediaCover =
+                        mRecommendationViewHolder.getMediaCoverItems().get(itemIndex);
+                mediaCover.setImageDrawable(artwork);
+
+                // Set up the app icon.
+                ImageView appIconView = mRecommendationViewHolder.getMediaAppIcons().get(itemIndex);
+                appIconView.clearColorFilter();
+                try {
+                    Drawable icon = mContext.getPackageManager()
+                            .getApplicationIcon(packageName);
+                    appIconView.setImageDrawable(icon);
+                } catch (PackageManager.NameNotFoundException e) {
+                    Log.w(TAG, "Cannot find icon for package " + packageName, e);
+                    appIconView.setImageResource(R.drawable.ic_music_note);
+                }
+                Trace.endAsyncSection(traceName, traceCookie);
+            });
+        });
+    }
+
+    // This method should be called from a background thread. WallpaperColors.fromBitmap takes a
+    // good amount of time. We do that work on the background executor to avoid stalling animations
+    // on the UI Thread.
+    private WallpaperColors getWallpaperColor(Icon artworkIcon) {
+        if (artworkIcon != null) {
+            if (artworkIcon.getType() == Icon.TYPE_BITMAP
+                    || artworkIcon.getType() == Icon.TYPE_ADAPTIVE_BITMAP) {
+                // Avoids extra processing if this is already a valid bitmap
+                return WallpaperColors
+                        .fromBitmap(artworkIcon.getBitmap());
+            } else {
+                Drawable artworkDrawable = artworkIcon.loadDrawable(mContext);
+                if (artworkDrawable != null) {
+                    return WallpaperColors
+                            .fromDrawable(artworkIcon.loadDrawable(mContext));
+                }
+            }
+        }
+        return null;
+    }
+
+    private LayerDrawable addGradientToIcon(
+            Icon artworkIcon,
+            ColorScheme mutableColorScheme,
+            int width,
+            int height
+    ) {
+        Drawable albumArt = getScaledBackground(artworkIcon, width, height);
+        GradientDrawable gradient = (GradientDrawable) AppCompatResources
+                .getDrawable(mContext, R.drawable.qs_media_scrim);
+        gradient.setColors(new int[] {
+                ColorUtilKt.getColorWithAlpha(
+                        MediaColorSchemesKt.backgroundStartFromScheme(mutableColorScheme),
+                        0.25f),
+                ColorUtilKt.getColorWithAlpha(
+                        MediaColorSchemesKt.backgroundEndFromScheme(mutableColorScheme),
+                        0.9f),
+        });
+        return new LayerDrawable(new Drawable[] { albumArt, gradient });
+    }
+
     private void scaleTransitionDrawableLayer(TransitionDrawable transitionDrawable, int layer,
             int targetWidth, int targetHeight) {
         Drawable drawable = transitionDrawable.getDrawable(layer);
@@ -1227,8 +1291,10 @@
         PackageManager packageManager = mContext.getPackageManager();
         // Set up media source app's logo.
         Drawable icon = packageManager.getApplicationIcon(applicationInfo);
-        ImageView headerLogoImageView = mRecommendationViewHolder.getCardIcon();
-        headerLogoImageView.setImageDrawable(icon);
+        if (!mFeatureFlags.isEnabled(Flags.MEDIA_RECOMMENDATION_CARD_UPDATE)) {
+            ImageView headerLogoImageView = mRecommendationViewHolder.getCardIcon();
+            headerLogoImageView.setImageDrawable(icon);
+        }
         fetchAndUpdateRecommendationColors(icon);
 
         // Set up media rec card's tap action if applicable.
@@ -1248,7 +1314,15 @@
 
             // Set up media item cover.
             ImageView mediaCoverImageView = mediaCoverItems.get(itemIndex);
-            mediaCoverImageView.setImageIcon(recommendation.getIcon());
+            if (mFeatureFlags.isEnabled(Flags.MEDIA_RECOMMENDATION_CARD_UPDATE)) {
+                bindRecommendationArtwork(
+                        recommendation,
+                        data.getPackageName(),
+                        itemIndex
+                );
+            } else {
+                mediaCoverImageView.setImageIcon(recommendation.getIcon());
+            }
 
             // Set up the media item's click listener if applicable.
             ViewGroup mediaCoverContainer = mediaCoverContainers.get(itemIndex);
@@ -1278,7 +1352,6 @@
                                 recommendation.getTitle(), artistName, appName));
             }
 
-
             // Set up title
             CharSequence title = recommendation.getTitle();
             hasTitle |= !TextUtils.isEmpty(title);
@@ -1356,6 +1429,10 @@
         int textPrimaryColor = MediaColorSchemesKt.textPrimaryFromScheme(colorScheme);
         int textSecondaryColor = MediaColorSchemesKt.textSecondaryFromScheme(colorScheme);
 
+        if (mFeatureFlags.isEnabled(Flags.MEDIA_RECOMMENDATION_CARD_UPDATE)) {
+            mRecommendationViewHolder.getCardTitle().setTextColor(textPrimaryColor);
+        }
+
         mRecommendationViewHolder.getRecommendations()
                 .setBackgroundTintList(ColorStateList.valueOf(backgroundColor));
         mRecommendationViewHolder.getMediaTitles().forEach(
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaViewController.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaViewController.kt
index 2ec7be6..1e6002c 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaViewController.kt
@@ -25,6 +25,7 @@
 import com.android.systemui.media.controls.models.player.MediaViewHolder
 import com.android.systemui.media.controls.models.recommendation.RecommendationViewHolder
 import com.android.systemui.media.controls.ui.MediaCarouselController.Companion.calculateAlpha
+import com.android.systemui.media.controls.util.MediaFlags
 import com.android.systemui.statusbar.policy.ConfigurationController
 import com.android.systemui.util.animation.MeasurementOutput
 import com.android.systemui.util.animation.TransitionLayout
@@ -45,7 +46,8 @@
     private val context: Context,
     private val configurationController: ConfigurationController,
     private val mediaHostStatesManager: MediaHostStatesManager,
-    private val logger: MediaViewLogger
+    private val logger: MediaViewLogger,
+    private val mediaFlags: MediaFlags,
 ) {
 
     /**
@@ -646,8 +648,13 @@
                 expandedLayout.load(context, R.xml.media_session_expanded)
             }
             TYPE.RECOMMENDATION -> {
-                collapsedLayout.load(context, R.xml.media_recommendation_collapsed)
-                expandedLayout.load(context, R.xml.media_recommendation_expanded)
+                if (mediaFlags.isRecommendationCardUpdateEnabled()) {
+                    collapsedLayout.load(context, R.xml.media_recommendations_view_collapsed)
+                    expandedLayout.load(context, R.xml.media_recommendations_view_expanded)
+                } else {
+                    collapsedLayout.load(context, R.xml.media_recommendation_collapsed)
+                    expandedLayout.load(context, R.xml.media_recommendation_expanded)
+                }
             }
         }
         refreshState()
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaFlags.kt b/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaFlags.kt
index ab03930..81efa36 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaFlags.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaFlags.kt
@@ -51,4 +51,8 @@
      * whether the underlying notification was dismissed
      */
     fun isRetainingPlayersEnabled() = featureFlags.isEnabled(Flags.MEDIA_RETAIN_SESSIONS)
+
+    /** Check whether we show the updated recommendation card. */
+    fun isRecommendationCardUpdateEnabled() =
+        featureFlags.isEnabled(Flags.MEDIA_RECOMMENDATION_CARD_UPDATE)
 }
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java
index 4e08050..dc75538 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java
@@ -45,6 +45,7 @@
 import android.widget.TextView;
 
 import androidx.annotation.NonNull;
+import androidx.annotation.VisibleForTesting;
 import androidx.recyclerview.widget.RecyclerView;
 
 import com.android.settingslib.Utils;
@@ -142,11 +143,12 @@
         final TextView mVolumeValueText;
         final ImageView mTitleIcon;
         final ProgressBar mProgressBar;
-        final MediaOutputSeekbar mSeekBar;
         final LinearLayout mTwoLineLayout;
         final ImageView mStatusIcon;
         final CheckBox mCheckBox;
         final ViewGroup mEndTouchArea;
+        @VisibleForTesting
+        MediaOutputSeekbar mSeekBar;
         private String mDeviceId;
         private ValueAnimator mCornerAnimator;
         private ValueAnimator mVolumeAnimator;
@@ -390,6 +392,7 @@
                         mTitleIcon.setVisibility(View.VISIBLE);
                         mVolumeValueText.setVisibility(View.GONE);
                     }
+                    mController.logInteractionAdjustVolume(device);
                     mIsDragging = false;
                 }
             });
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java
index 1587e62..5f5c686 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java
@@ -141,7 +141,7 @@
     @VisibleForTesting
     LocalMediaManager mLocalMediaManager;
     @VisibleForTesting
-    private MediaOutputMetricLogger mMetricLogger;
+    MediaOutputMetricLogger mMetricLogger;
     private int mCurrentState;
 
     private int mColorItemContent;
@@ -870,12 +870,15 @@
     }
 
     void adjustVolume(MediaDevice device, int volume) {
-        mMetricLogger.logInteractionAdjustVolume(device);
         ThreadUtils.postOnBackgroundThread(() -> {
             device.requestSetVolume(volume);
         });
     }
 
+    void logInteractionAdjustVolume(MediaDevice device) {
+        mMetricLogger.logInteractionAdjustVolume(device);
+    }
+
     String getPackageName() {
         return mPackageName;
     }
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/ReceiverChipRippleView.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/ReceiverChipRippleView.kt
index f8785fc..f1acae8 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/ReceiverChipRippleView.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/ReceiverChipRippleView.kt
@@ -79,9 +79,9 @@
         animator.addUpdateListener { updateListener ->
             val now = updateListener.currentPlayTime
             val progress = updateListener.animatedValue as Float
-            rippleShader.progress = startingPercentage + (progress * (1 - startingPercentage))
-            rippleShader.distortionStrength = 1 - rippleShader.progress
-            rippleShader.pixelDensity = 1 - rippleShader.progress
+            rippleShader.rawProgress = startingPercentage + (progress * (1 - startingPercentage))
+            rippleShader.distortionStrength = 1 - rippleShader.rawProgress
+            rippleShader.pixelDensity = 1 - rippleShader.rawProgress
             rippleShader.time = now.toFloat()
             invalidate()
         }
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorComponent.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorComponent.kt
index 1edb837..e665d83 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorComponent.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorComponent.kt
@@ -34,8 +34,8 @@
 import com.android.systemui.mediaprojection.appselector.data.ShellRecentTaskListProvider
 import com.android.systemui.mediaprojection.appselector.view.MediaProjectionRecentsViewController
 import com.android.systemui.mediaprojection.appselector.view.TaskPreviewSizeProvider
-import com.android.systemui.settings.UserTracker
-import com.android.systemui.shared.system.ActivityManagerWrapper
+import com.android.systemui.mediaprojection.devicepolicy.MediaProjectionDevicePolicyModule
+import com.android.systemui.mediaprojection.devicepolicy.PersonalProfile
 import com.android.systemui.statusbar.phone.ConfigurationControllerImpl
 import com.android.systemui.statusbar.policy.ConfigurationController
 import dagger.Binds
@@ -54,13 +54,12 @@
 
 @Qualifier @Retention(AnnotationRetention.BINARY) annotation class HostUserHandle
 
-@Qualifier @Retention(AnnotationRetention.BINARY) annotation class PersonalProfile
-
-@Qualifier @Retention(AnnotationRetention.BINARY) annotation class WorkProfile
-
 @Retention(AnnotationRetention.RUNTIME) @Scope annotation class MediaProjectionAppSelectorScope
 
-@Module(subcomponents = [MediaProjectionAppSelectorComponent::class])
+@Module(
+    subcomponents = [MediaProjectionAppSelectorComponent::class],
+    includes = [MediaProjectionDevicePolicyModule::class]
+)
 interface MediaProjectionModule {
     @Binds
     @IntoMap
@@ -110,20 +109,6 @@
         ): ConfigurationController = ConfigurationControllerImpl(activity)
 
         @Provides
-        @PersonalProfile
-        @MediaProjectionAppSelectorScope
-        fun personalUserHandle(activityManagerWrapper: ActivityManagerWrapper): UserHandle {
-            // Current foreground user is the 'personal' profile
-            return UserHandle.of(activityManagerWrapper.currentUserId)
-        }
-
-        @Provides
-        @WorkProfile
-        @MediaProjectionAppSelectorScope
-        fun workProfileUserHandle(userTracker: UserTracker): UserHandle? =
-            userTracker.userProfiles.find { it.isManagedProfile }?.userHandle
-
-        @Provides
         @HostUserHandle
         @MediaProjectionAppSelectorScope
         fun hostUserHandle(activity: MediaProjectionAppSelectorActivity): UserHandle {
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionBlockerEmptyStateProvider.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionBlockerEmptyStateProvider.kt
new file mode 100644
index 0000000..829b0dd
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionBlockerEmptyStateProvider.kt
@@ -0,0 +1,70 @@
+/*
+ * 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.mediaprojection.appselector
+
+import android.content.Context
+import android.os.UserHandle
+import com.android.internal.R as AndroidR
+import com.android.internal.app.AbstractMultiProfilePagerAdapter.EmptyState
+import com.android.internal.app.AbstractMultiProfilePagerAdapter.EmptyStateProvider
+import com.android.internal.app.ResolverListAdapter
+import com.android.systemui.R
+import com.android.systemui.mediaprojection.devicepolicy.PersonalProfile
+import com.android.systemui.mediaprojection.devicepolicy.ScreenCaptureDevicePolicyResolver
+import javax.inject.Inject
+
+@MediaProjectionAppSelectorScope
+class MediaProjectionBlockerEmptyStateProvider
+@Inject
+constructor(
+    @HostUserHandle private val hostAppHandle: UserHandle,
+    @PersonalProfile private val personalProfileHandle: UserHandle,
+    private val policyResolver: ScreenCaptureDevicePolicyResolver,
+    private val context: Context
+) : EmptyStateProvider {
+
+    override fun getEmptyState(resolverListAdapter: ResolverListAdapter): EmptyState? {
+        val screenCaptureAllowed =
+            policyResolver.isScreenCaptureAllowed(
+                targetAppUserHandle = resolverListAdapter.userHandle,
+                hostAppUserHandle = hostAppHandle
+            )
+
+        val isHostAppInPersonalProfile = hostAppHandle == personalProfileHandle
+
+        val subtitle =
+            if (isHostAppInPersonalProfile) {
+                AndroidR.string.resolver_cant_share_with_personal_apps_explanation
+            } else {
+                AndroidR.string.resolver_cant_share_with_work_apps_explanation
+            }
+
+        if (!screenCaptureAllowed) {
+            return object : EmptyState {
+                override fun getSubtitle(): String = context.resources.getString(subtitle)
+                override fun getTitle(): String =
+                    context.resources.getString(
+                        R.string.screen_capturing_disabled_by_policy_dialog_title
+                    )
+                override fun onEmptyStateShown() {
+                    // TODO(b/237397740) report analytics
+                }
+                override fun shouldSkipDataRebuild(): Boolean = true
+            }
+        }
+        return null
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/devicepolicy/MediaProjectionDevicePolicyModule.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/devicepolicy/MediaProjectionDevicePolicyModule.kt
new file mode 100644
index 0000000..13b71a7
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/devicepolicy/MediaProjectionDevicePolicyModule.kt
@@ -0,0 +1,43 @@
+/*
+ * 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.mediaprojection.devicepolicy
+
+import android.os.UserHandle
+import com.android.systemui.settings.UserTracker
+import com.android.systemui.shared.system.ActivityManagerWrapper
+import dagger.Module
+import dagger.Provides
+import javax.inject.Qualifier
+
+@Qualifier @Retention(AnnotationRetention.BINARY) annotation class WorkProfile
+
+@Qualifier @Retention(AnnotationRetention.BINARY) annotation class PersonalProfile
+
+/** Module for media projection device policy related dependencies */
+@Module
+class MediaProjectionDevicePolicyModule {
+    @Provides
+    @PersonalProfile
+    fun personalUserHandle(activityManagerWrapper: ActivityManagerWrapper): UserHandle {
+        // Current foreground user is the 'personal' profile
+        return UserHandle.of(activityManagerWrapper.currentUserId)
+    }
+
+    @Provides
+    @WorkProfile
+    fun workProfileUserHandle(userTracker: UserTracker): UserHandle? =
+        userTracker.userProfiles.find { it.isManagedProfile }?.userHandle
+}
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/devicepolicy/ScreenCaptureDevicePolicyResolver.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/devicepolicy/ScreenCaptureDevicePolicyResolver.kt
new file mode 100644
index 0000000..6bd33e7
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/devicepolicy/ScreenCaptureDevicePolicyResolver.kt
@@ -0,0 +1,125 @@
+/*
+ * 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.mediaprojection.devicepolicy
+
+import android.app.admin.DevicePolicyManager
+import android.os.UserHandle
+import android.os.UserManager
+import javax.inject.Inject
+
+/**
+ * Utility class to resolve if screen capture allowed for a particular target app/host app pair. It
+ * caches the state of the policies, so you need to create a new instance of this class if you want
+ * to react to updated policies state.
+ */
+class ScreenCaptureDevicePolicyResolver
+@Inject
+constructor(
+    private val devicePolicyManager: DevicePolicyManager,
+    private val userManager: UserManager,
+    @PersonalProfile private val personalProfileUserHandle: UserHandle,
+    @WorkProfile private val workProfileUserHandle: UserHandle?
+) {
+
+    /**
+     * Returns true if [hostAppUserHandle] is allowed to perform screen capture of
+     * [targetAppUserHandle]
+     */
+    fun isScreenCaptureAllowed(
+        targetAppUserHandle: UserHandle,
+        hostAppUserHandle: UserHandle,
+    ): Boolean {
+        if (hostAppUserHandle.isWorkProfile() && workProfileScreenCaptureDisabled) {
+            // Disable screen capturing as host apps should not capture the screen
+            return false
+        }
+
+        if (!hostAppUserHandle.isWorkProfile() && personalProfileScreenCaptureDisabled) {
+            // Disable screen capturing as personal apps should not capture the screen
+            return false
+        }
+
+        if (targetAppUserHandle.isWorkProfile()) {
+            // Work profile target
+            if (workProfileScreenCaptureDisabled) {
+                // Do not allow sharing work profile apps as work profile capturing is disabled
+                return false
+            }
+        } else {
+            // Personal profile target
+            if (hostAppUserHandle.isWorkProfile() && disallowSharingIntoManagedProfile) {
+                // Do not allow sharing of personal apps into work profile apps
+                return false
+            }
+
+            if (personalProfileScreenCaptureDisabled) {
+                // Disable screen capturing as personal apps should not be captured
+                return false
+            }
+        }
+
+        return true
+    }
+
+    /**
+     * Returns true if [hostAppUserHandle] is NOT allowed to capture an app from any profile,
+     * could be useful to finish the screen capture flow as soon as possible when the screen
+     * could not be captured at all.
+     */
+    fun isScreenCaptureCompletelyDisabled(hostAppUserHandle: UserHandle): Boolean {
+        val isWorkAppsCaptureDisabled =
+                if (workProfileUserHandle != null) {
+                    !isScreenCaptureAllowed(
+                            targetAppUserHandle = workProfileUserHandle,
+                            hostAppUserHandle = hostAppUserHandle
+                    )
+                } else true
+
+        val isPersonalAppsCaptureDisabled =
+                !isScreenCaptureAllowed(
+                        targetAppUserHandle = personalProfileUserHandle,
+                        hostAppUserHandle = hostAppUserHandle
+                )
+
+        return isWorkAppsCaptureDisabled && isPersonalAppsCaptureDisabled
+    }
+
+    private val personalProfileScreenCaptureDisabled: Boolean by lazy {
+        devicePolicyManager.getScreenCaptureDisabled(
+            /* admin */ null,
+            personalProfileUserHandle.identifier
+        )
+    }
+
+    private val workProfileScreenCaptureDisabled: Boolean by lazy {
+        workProfileUserHandle?.let {
+            devicePolicyManager.getScreenCaptureDisabled(/* admin */ null, it.identifier)
+        }
+            ?: false
+    }
+
+    private val disallowSharingIntoManagedProfile: Boolean by lazy {
+        workProfileUserHandle?.let {
+            userManager.hasUserRestrictionForUser(
+                UserManager.DISALLOW_SHARE_INTO_MANAGED_PROFILE,
+                it
+            )
+        }
+            ?: false
+    }
+
+    private fun UserHandle?.isWorkProfile(): Boolean = this == workProfileUserHandle
+}
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/devicepolicy/ScreenCaptureDisabledDialog.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/devicepolicy/ScreenCaptureDisabledDialog.kt
new file mode 100644
index 0000000..a6b3da0
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/devicepolicy/ScreenCaptureDisabledDialog.kt
@@ -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.systemui.mediaprojection.devicepolicy
+
+import android.content.Context
+import com.android.systemui.R
+import com.android.systemui.statusbar.phone.SystemUIDialog
+
+/** Dialog that shows that screen capture is disabled on this device. */
+class ScreenCaptureDisabledDialog(context: Context) : SystemUIDialog(context) {
+
+    init {
+        setTitle(context.getString(R.string.screen_capturing_disabled_by_policy_dialog_title))
+        setMessage(
+            context.getString(R.string.screen_capturing_disabled_by_policy_dialog_description)
+        )
+        setIcon(R.drawable.ic_cast)
+        setButton(BUTTON_POSITIVE, context.getString(android.R.string.ok)) { _, _ -> cancel() }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java
index 1a3be8e..63fb499 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java
@@ -301,7 +301,8 @@
                 R.dimen.floating_rotation_button_taskbar_left_margin,
                 R.dimen.floating_rotation_button_taskbar_bottom_margin,
                 R.dimen.floating_rotation_button_diameter,
-                R.dimen.key_button_ripple_max_width);
+                R.dimen.key_button_ripple_max_width,
+                R.bool.floating_rotation_button_position_left);
         mRotationButtonController = new RotationButtonController(mLightContext, mLightIconColor,
                 mDarkIconColor, R.drawable.ic_sysbar_rotate_button_ccw_start_0,
                 R.drawable.ic_sysbar_rotate_button_ccw_start_90,
diff --git a/packages/SystemUI/src/com/android/systemui/privacy/OngoingPrivacyChip.kt b/packages/SystemUI/src/com/android/systemui/privacy/OngoingPrivacyChip.kt
index 2a6ca1a..8ad2f86 100644
--- a/packages/SystemUI/src/com/android/systemui/privacy/OngoingPrivacyChip.kt
+++ b/packages/SystemUI/src/com/android/systemui/privacy/OngoingPrivacyChip.kt
@@ -17,11 +17,11 @@
 import android.content.Context
 import android.util.AttributeSet
 import android.view.ViewGroup
-import android.widget.FrameLayout
 import android.widget.ImageView
 import android.widget.LinearLayout
 import com.android.settingslib.Utils
 import com.android.systemui.R
+import com.android.systemui.animation.LaunchableFrameLayout
 import com.android.systemui.statusbar.events.BackgroundAnimatableView
 
 class OngoingPrivacyChip @JvmOverloads constructor(
@@ -29,7 +29,7 @@
     attrs: AttributeSet? = null,
     defStyleAttrs: Int = 0,
     defStyleRes: Int = 0
-) : FrameLayout(context, attrs, defStyleAttrs, defStyleRes), BackgroundAnimatableView {
+) : LaunchableFrameLayout(context, attrs, defStyleAttrs, defStyleRes), BackgroundAnimatableView {
 
     private var iconMargin = 0
     private var iconSize = 0
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java b/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java
index 100853c..98af9df 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java
@@ -314,7 +314,6 @@
         if (!TILES_SETTING.equals(key)) {
             return;
         }
-        Log.d(TAG, "Recreating tiles");
         if (newValue == null && UserManager.isDeviceInDemoMode(mContext)) {
             newValue = mContext.getResources().getString(R.string.quick_settings_tiles_retail_mode);
         }
@@ -327,6 +326,7 @@
             }
         }
         if (tileSpecs.equals(mTileSpecs) && currentUser == mCurrentUser) return;
+        Log.d(TAG, "Recreating tiles: " + tileSpecs);
         mTiles.entrySet().stream().filter(tile -> !tileSpecs.contains(tile.getKey())).forEach(
                 tile -> {
                     Log.d(TAG, "Destroying tile: " + tile.getKey());
@@ -372,6 +372,8 @@
                             Log.d(TAG, "Destroying not available tile: " + tileSpec);
                             mQSLogger.logTileDestroyed(tileSpec, "Tile not available");
                         }
+                    } else {
+                        Log.d(TAG, "No factory for a spec: " + tileSpec);
                     }
                 } catch (Throwable t) {
                     Log.w(TAG, "Error creating tile for spec: " + tileSpec, t);
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
index 7ed6e3e..60fa865 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
@@ -160,12 +160,10 @@
 
         // This view is not part of the newly inflated expanded status bar.
         mBrightnessMirror = mView.findViewById(R.id.brightness_mirror_container);
-        if (featureFlags.isEnabled(Flags.MODERN_BOUNCER)) {
-            KeyguardBouncerViewBinder.bind(
-                    mView.findViewById(R.id.keyguard_bouncer_container),
-                    keyguardBouncerViewModel,
-                    keyguardBouncerComponentFactory);
-        }
+        KeyguardBouncerViewBinder.bind(
+                mView.findViewById(R.id.keyguard_bouncer_container),
+                keyguardBouncerViewModel,
+                keyguardBouncerComponentFactory);
 
         if (featureFlags.isEnabled(Flags.UNOCCLUSION_TRANSITION)) {
             collectFlow(mView, keyguardTransitionInteractor.getLockscreenToDreamingTransition(),
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/AlphaOptimizedButton.java b/packages/SystemUI/src/com/android/systemui/statusbar/AlphaOptimizedButton.java
index 87c12c2..6577cf6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/AlphaOptimizedButton.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/AlphaOptimizedButton.java
@@ -20,10 +20,21 @@
 import android.util.AttributeSet;
 import android.widget.Button;
 
+import com.android.systemui.animation.LaunchableView;
+import com.android.systemui.animation.LaunchableViewDelegate;
+
+import kotlin.Unit;
+
 /**
  * A Button which doesn't have overlapping drawing commands
  */
-public class AlphaOptimizedButton extends Button {
+public class AlphaOptimizedButton extends Button implements LaunchableView {
+    private LaunchableViewDelegate mDelegate = new LaunchableViewDelegate(this,
+            (visibility) -> {
+                super.setVisibility(visibility);
+                return Unit.INSTANCE;
+            });
+
     public AlphaOptimizedButton(Context context) {
         super(context);
     }
@@ -45,4 +56,14 @@
     public boolean hasOverlappingRendering() {
         return false;
     }
+
+    @Override
+    public void setShouldBlockVisibilityChanges(boolean block) {
+        mDelegate.setShouldBlockVisibilityChanges(block);
+    }
+
+    @Override
+    public void setVisibility(int visibility) {
+        mDelegate.setVisibility(visibility);
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/DeviceProvisionedCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/DeviceProvisionedCoordinator.java
index 058042c..0c95eab 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/DeviceProvisionedCoordinator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/DeviceProvisionedCoordinator.java
@@ -71,11 +71,9 @@
      * marking them as relevant for setup are allowed to show when device is unprovisioned
      */
     private boolean showNotificationEvenIfUnprovisioned(StatusBarNotification sbn) {
-        final boolean hasPermission = checkUidPermission(
-                Manifest.permission.NOTIFICATION_DURING_SETUP,
-                sbn.getUid()) == PackageManager.PERMISSION_GRANTED;
-        return hasPermission
-                && sbn.getNotification().extras.getBoolean(Notification.EXTRA_ALLOW_DURING_SETUP);
+        // system_server checks the permission so systemui can just check whether the
+        // extra exists
+        return sbn.getNotification().extras.getBoolean(Notification.EXTRA_ALLOW_DURING_SETUP);
     }
 
     private int checkUidPermission(String permission, int uid) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/userswitcher/StatusBarUserSwitcherContainer.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/userswitcher/StatusBarUserSwitcherContainer.kt
index 2c8677d..2d80edb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/userswitcher/StatusBarUserSwitcherContainer.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/userswitcher/StatusBarUserSwitcherContainer.kt
@@ -19,14 +19,14 @@
 import android.content.Context
 import android.util.AttributeSet
 import android.widget.ImageView
-import android.widget.LinearLayout
 import android.widget.TextView
 import com.android.systemui.R
+import com.android.systemui.common.ui.view.LaunchableLinearLayout
 
 class StatusBarUserSwitcherContainer(
     context: Context?,
     attrs: AttributeSet?
-) : LinearLayout(context, attrs) {
+) : LaunchableLinearLayout(context, attrs) {
     lateinit var text: TextView
         private set
     lateinit var avatar: ImageView
diff --git a/packages/SystemUI/src/com/android/systemui/util/animation/TransitionLayout.kt b/packages/SystemUI/src/com/android/systemui/util/animation/TransitionLayout.kt
index 08ee0af..56c5d3b 100644
--- a/packages/SystemUI/src/com/android/systemui/util/animation/TransitionLayout.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/animation/TransitionLayout.kt
@@ -27,6 +27,8 @@
 import android.widget.TextView
 import androidx.constraintlayout.widget.ConstraintLayout
 import androidx.constraintlayout.widget.ConstraintSet
+import com.android.systemui.animation.LaunchableView
+import com.android.systemui.animation.LaunchableViewDelegate
 import com.android.systemui.statusbar.CrossFadeHelper
 
 /**
@@ -38,7 +40,7 @@
     context: Context,
     attrs: AttributeSet? = null,
     defStyleAttr: Int = 0
-) : ConstraintLayout(context, attrs, defStyleAttr) {
+) : ConstraintLayout(context, attrs, defStyleAttr), LaunchableView {
 
     private val boundsRect = Rect()
     private val originalGoneChildrenSet: MutableSet<Int> = mutableSetOf()
@@ -50,7 +52,11 @@
 
     private var desiredMeasureWidth = 0
     private var desiredMeasureHeight = 0
-    private var transitionVisibility = View.VISIBLE
+    private val delegate =
+        LaunchableViewDelegate(
+            this,
+            superSetVisibility = { super.setVisibility(it) },
+        )
 
     /**
      * The measured state of this view which is the one we will lay ourselves out with. This
@@ -83,11 +89,12 @@
         }
     }
 
-    override fun setTransitionVisibility(visibility: Int) {
-        // We store the last transition visibility assigned to this view to restore it later if
-        // necessary.
-        super.setTransitionVisibility(visibility)
-        transitionVisibility = visibility
+    override fun setShouldBlockVisibilityChanges(block: Boolean) {
+        delegate.setShouldBlockVisibilityChanges(block)
+    }
+
+    override fun setVisibility(visibility: Int) {
+        delegate.setVisibility(visibility)
     }
 
     override fun onFinishInflate() {
@@ -173,14 +180,6 @@
         translationY = currentState.translation.y
 
         CrossFadeHelper.fadeIn(this, currentState.alpha)
-
-        // CrossFadeHelper#fadeIn will change this view visibility, which overrides the transition
-        // visibility. We set the transition visibility again to make sure that this view plays well
-        // with GhostView, which sets the transition visibility and is used for activity launch
-        // animations.
-        if (transitionVisibility != View.VISIBLE) {
-            setTransitionVisibility(transitionVisibility)
-        }
     }
 
     private fun applyCurrentStateOnPredraw() {
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt
index 00b2fbe..b9c23d4 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt
@@ -15,7 +15,6 @@
  */
 package com.android.keyguard
 
-import com.android.systemui.statusbar.CommandQueue
 import android.content.BroadcastReceiver
 import android.testing.AndroidTestingRunner
 import android.view.View
@@ -34,6 +33,7 @@
 import com.android.systemui.plugins.ClockFaceController
 import com.android.systemui.plugins.ClockFaceEvents
 import com.android.systemui.plugins.log.LogBuffer
+import com.android.systemui.statusbar.CommandQueue
 import com.android.systemui.statusbar.policy.BatteryController
 import com.android.systemui.statusbar.policy.ConfigurationController
 import com.android.systemui.util.mockito.any
@@ -41,8 +41,6 @@
 import com.android.systemui.util.mockito.capture
 import com.android.systemui.util.mockito.eq
 import com.android.systemui.util.mockito.mock
-import java.util.TimeZone
-import java.util.concurrent.Executor
 import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.runBlocking
 import kotlinx.coroutines.yield
@@ -57,8 +55,10 @@
 import org.mockito.Mockito.never
 import org.mockito.Mockito.times
 import org.mockito.Mockito.verify
-import org.mockito.Mockito.`when` as whenever
 import org.mockito.junit.MockitoJUnit
+import java.util.*
+import java.util.concurrent.Executor
+import org.mockito.Mockito.`when` as whenever
 
 @RunWith(AndroidTestingRunner::class)
 @SmallTest
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardAbsKeyInputViewControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardAbsKeyInputViewControllerTest.java
index 1059543..50645e5 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardAbsKeyInputViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardAbsKeyInputViewControllerTest.java
@@ -150,4 +150,11 @@
                 getContext().getResources().getString(R.string.kg_prompt_reason_restart_password),
                 false);
     }
+
+
+    @Test
+    public void testReset() {
+        mKeyguardAbsKeyInputViewController.reset();
+        verify(mKeyguardMessageAreaController).setMessage("", false);
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/ChooserSelectorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/ChooserSelectorTest.kt
index 81d0034..32edf8f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/ChooserSelectorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/ChooserSelectorTest.kt
@@ -148,7 +148,7 @@
 
         // Act
         whenever(mockFeatureFlags.isEnabled(any<UnreleasedFlag>())).thenReturn(true)
-        flagListener.value.onFlagChanged(TestFlagEvent(Flags.CHOOSER_UNBUNDLED.id))
+        flagListener.value.onFlagChanged(TestFlagEvent(Flags.CHOOSER_UNBUNDLED.name))
 
         // Assert
         verify(mockPackageManager, times(2)).setComponentEnabledSetting(
@@ -175,7 +175,7 @@
 
         // Act
         whenever(mockFeatureFlags.isEnabled(any<UnreleasedFlag>())).thenReturn(false)
-        flagListener.value.onFlagChanged(TestFlagEvent(Flags.CHOOSER_UNBUNDLED.id))
+        flagListener.value.onFlagChanged(TestFlagEvent(Flags.CHOOSER_UNBUNDLED.name))
 
         // Assert
         verify(mockPackageManager, times(2)).setComponentEnabledSetting(
@@ -198,13 +198,13 @@
 
         // Act
         whenever(mockFeatureFlags.isEnabled(any<UnreleasedFlag>())).thenReturn(false)
-        flagListener.value.onFlagChanged(TestFlagEvent(Flags.CHOOSER_UNBUNDLED.id + 1))
+        flagListener.value.onFlagChanged(TestFlagEvent("other flag"))
 
         // Assert
         verifyZeroInteractions(mockPackageManager)
     }
 
-    private class TestFlagEvent(override val flagId: Int) : FlagListenable.FlagEvent {
+    private class TestFlagEvent(override val flagName: String) : FlagListenable.FlagEvent {
         override fun requestNoRestart() {}
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/animation/ActivityLaunchAnimatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/animation/ActivityLaunchAnimatorTest.kt
index a61cd23..578e1d4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/animation/ActivityLaunchAnimatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/animation/ActivityLaunchAnimatorTest.kt
@@ -15,6 +15,7 @@
 import android.view.RemoteAnimationTarget
 import android.view.SurfaceControl
 import android.view.ViewGroup
+import android.widget.FrameLayout
 import android.widget.LinearLayout
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
@@ -26,6 +27,7 @@
 import junit.framework.AssertionFailedError
 import kotlin.concurrent.thread
 import org.junit.After
+import org.junit.Assert.assertThrows
 import org.junit.Before
 import org.junit.Rule
 import org.junit.Test
@@ -195,6 +197,13 @@
         verify(controller).onLaunchAnimationStart(anyBoolean())
     }
 
+    @Test
+    fun creatingControllerFromNormalViewThrows() {
+        assertThrows(IllegalArgumentException::class.java) {
+            ActivityLaunchAnimator.Controller.fromView(FrameLayout(mContext))
+        }
+    }
+
     private fun fakeWindow(): RemoteAnimationTarget {
         val bounds = Rect(10 /* left */, 20 /* top */, 30 /* right */, 40 /* bottom */)
         val taskInfo = ActivityManager.RunningTaskInfo()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/animation/DialogLaunchAnimatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/animation/DialogLaunchAnimatorTest.kt
index cac4a0e..1e62fd23 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/animation/DialogLaunchAnimatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/animation/DialogLaunchAnimatorTest.kt
@@ -26,6 +26,7 @@
 import junit.framework.Assert.assertTrue
 import org.junit.After
 import org.junit.Assert.assertNotEquals
+import org.junit.Assert.assertThrows
 import org.junit.Before
 import org.junit.Rule
 import org.junit.Test
@@ -260,6 +261,13 @@
         assertThat(touchSurface.visibility).isEqualTo(View.GONE)
     }
 
+    @Test
+    fun creatingControllerFromNormalViewThrows() {
+        assertThrows(IllegalArgumentException::class.java) {
+            DialogLaunchAnimator.Controller.fromView(FrameLayout(mContext))
+        }
+    }
+
     private fun createAndShowDialog(
         animator: DialogLaunchAnimator = dialogLaunchAnimator,
     ): TestDialog {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/animation/GhostedViewLaunchAnimatorControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/animation/GhostedViewLaunchAnimatorControllerTest.kt
index 3696ec5..0798d73 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/animation/GhostedViewLaunchAnimatorControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/animation/GhostedViewLaunchAnimatorControllerTest.kt
@@ -16,58 +16,34 @@
 
 package com.android.systemui.animation
 
-import android.graphics.drawable.Drawable
 import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper
-import android.view.View
-import android.view.ViewGroup
-import android.view.ViewParent
+import android.widget.FrameLayout
 import androidx.test.filters.SmallTest
-import com.android.internal.jank.InteractionJankMonitor
 import com.android.systemui.SysuiTestCase
-import org.junit.Before
+import org.junit.Assert.assertThrows
 import org.junit.Test
 import org.junit.runner.RunWith
-import org.mockito.ArgumentMatchers.any
-import org.mockito.ArgumentMatchers.anyInt
-import org.mockito.Mock
-import org.mockito.Mockito.`when` as whenever
-import org.mockito.MockitoAnnotations
 
 @SmallTest
 @RunWith(AndroidTestingRunner::class)
 @TestableLooper.RunWithLooper
 class GhostedViewLaunchAnimatorControllerTest : SysuiTestCase() {
-    @Mock lateinit var interactionJankMonitor: InteractionJankMonitor
-    @Mock lateinit var view: View
-    @Mock lateinit var rootView: ViewGroup
-    @Mock lateinit var viewParent: ViewParent
-    @Mock lateinit var drawable: Drawable
-    lateinit var controller: GhostedViewLaunchAnimatorController
-
-    @Before
-    fun setup() {
-        MockitoAnnotations.initMocks(this)
-        whenever(view.rootView).thenReturn(rootView)
-        whenever(view.background).thenReturn(drawable)
-        whenever(view.height).thenReturn(0)
-        whenever(view.width).thenReturn(0)
-        whenever(view.parent).thenReturn(viewParent)
-        whenever(view.visibility).thenReturn(View.VISIBLE)
-        whenever(view.invalidate()).then { /* NO-OP */ }
-        whenever(view.getLocationOnScreen(any())).then { /* NO-OP */ }
-        whenever(interactionJankMonitor.begin(any(), anyInt())).thenReturn(true)
-        whenever(interactionJankMonitor.end(anyInt())).thenReturn(true)
-        controller = GhostedViewLaunchAnimatorController(view, 0, interactionJankMonitor)
-    }
-
     @Test
     fun animatingOrphanViewDoesNotCrash() {
         val state = LaunchAnimator.State(top = 0, bottom = 0, left = 0, right = 0)
 
+        val controller = GhostedViewLaunchAnimatorController(LaunchableFrameLayout(mContext))
         controller.onIntentStarted(willAnimate = true)
         controller.onLaunchAnimationStart(isExpandingFullyAbove = true)
         controller.onLaunchAnimationProgress(state, progress = 0f, linearProgress = 0f)
         controller.onLaunchAnimationEnd(isExpandingFullyAbove = true)
     }
+
+    @Test
+    fun creatingControllerFromNormalViewThrows() {
+        assertThrows(IllegalArgumentException::class.java) {
+            GhostedViewLaunchAnimatorController(FrameLayout(mContext))
+        }
+    }
 }
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerBaseTest.java
index 498cc29..dbbc266 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerBaseTest.java
@@ -147,7 +147,6 @@
 
     protected UdfpsKeyguardViewController createUdfpsKeyguardViewController(
             boolean useModernBouncer, boolean useExpandedOverlay) {
-        mFeatureFlags.set(Flags.MODERN_BOUNCER, useModernBouncer);
         mFeatureFlags.set(Flags.MODERN_ALTERNATE_BOUNCER, useModernBouncer);
         mFeatureFlags.set(Flags.UDFPS_NEW_TOUCH_DETECTION, useExpandedOverlay);
         UdfpsKeyguardViewController controller = new UdfpsKeyguardViewController(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java
index b73bfc3..9d4bef6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java
@@ -31,6 +31,8 @@
 import android.os.RemoteException;
 import android.service.dreams.IDreamOverlay;
 import android.service.dreams.IDreamOverlayCallback;
+import android.service.dreams.IDreamOverlayClient;
+import android.service.dreams.IDreamOverlayClientCallback;
 import android.testing.AndroidTestingRunner;
 import android.view.View;
 import android.view.ViewGroup;
@@ -61,6 +63,7 @@
 import org.mockito.ArgumentCaptor;
 import org.mockito.Captor;
 import org.mockito.Mock;
+import org.mockito.Mockito;
 import org.mockito.MockitoAnnotations;
 
 @SmallTest
@@ -189,13 +192,25 @@
                 mDreamOverlayCallbackController);
     }
 
-    @Test
-    public void testOnStartMetricsLogged() throws Exception {
+    public IDreamOverlayClient getClient() throws RemoteException {
         final IBinder proxy = mService.onBind(new Intent());
         final IDreamOverlay overlay = IDreamOverlay.Stub.asInterface(proxy);
+        final IDreamOverlayClientCallback callback =
+                Mockito.mock(IDreamOverlayClientCallback.class);
+        overlay.getClient(callback);
+        final ArgumentCaptor<IDreamOverlayClient> clientCaptor =
+                ArgumentCaptor.forClass(IDreamOverlayClient.class);
+        verify(callback).onDreamOverlayClient(clientCaptor.capture());
+
+        return clientCaptor.getValue();
+    }
+
+    @Test
+    public void testOnStartMetricsLogged() throws Exception {
+        final IDreamOverlayClient client = getClient();
 
         // Inform the overlay service of dream starting.
-        overlay.startDream(mWindowParams, mDreamOverlayCallback, DREAM_COMPONENT,
+        client.startDream(mWindowParams, mDreamOverlayCallback, DREAM_COMPONENT,
                 false /*shouldShowComplication*/);
         mMainExecutor.runAllReady();
 
@@ -206,11 +221,10 @@
 
     @Test
     public void testOverlayContainerViewAddedToWindow() throws Exception {
-        final IBinder proxy = mService.onBind(new Intent());
-        final IDreamOverlay overlay = IDreamOverlay.Stub.asInterface(proxy);
+        final IDreamOverlayClient client = getClient();
 
         // Inform the overlay service of dream starting.
-        overlay.startDream(mWindowParams, mDreamOverlayCallback, DREAM_COMPONENT,
+        client.startDream(mWindowParams, mDreamOverlayCallback, DREAM_COMPONENT,
                 false /*shouldShowComplication*/);
         mMainExecutor.runAllReady();
 
@@ -219,11 +233,10 @@
 
     @Test
     public void testDreamOverlayContainerViewControllerInitialized() throws Exception {
-        final IBinder proxy = mService.onBind(new Intent());
-        final IDreamOverlay overlay = IDreamOverlay.Stub.asInterface(proxy);
+        final IDreamOverlayClient client = getClient();
 
         // Inform the overlay service of dream starting.
-        overlay.startDream(mWindowParams, mDreamOverlayCallback, DREAM_COMPONENT,
+        client.startDream(mWindowParams, mDreamOverlayCallback, DREAM_COMPONENT,
                 false /*shouldShowComplication*/);
         mMainExecutor.runAllReady();
 
@@ -237,11 +250,10 @@
                 .thenReturn(mDreamOverlayContainerViewParent)
                 .thenReturn(null);
 
-        final IBinder proxy = mService.onBind(new Intent());
-        final IDreamOverlay overlay = IDreamOverlay.Stub.asInterface(proxy);
+        final IDreamOverlayClient client = getClient();
 
         // Inform the overlay service of dream starting.
-        overlay.startDream(mWindowParams, mDreamOverlayCallback, DREAM_COMPONENT,
+        client.startDream(mWindowParams, mDreamOverlayCallback, DREAM_COMPONENT,
                 false /*shouldShowComplication*/);
         mMainExecutor.runAllReady();
 
@@ -250,11 +262,10 @@
 
     @Test
     public void testShouldShowComplicationsSetByStartDream() throws RemoteException {
-        final IBinder proxy = mService.onBind(new Intent());
-        final IDreamOverlay overlay = IDreamOverlay.Stub.asInterface(proxy);
+        final IDreamOverlayClient client = getClient();
 
         // Inform the overlay service of dream starting.
-        overlay.startDream(mWindowParams, mDreamOverlayCallback, DREAM_COMPONENT,
+        client.startDream(mWindowParams, mDreamOverlayCallback, DREAM_COMPONENT,
                 true /*shouldShowComplication*/);
 
         assertThat(mService.shouldShowComplications()).isTrue();
@@ -262,11 +273,10 @@
 
     @Test
     public void testLowLightSetByStartDream() throws RemoteException {
-        final IBinder proxy = mService.onBind(new Intent());
-        final IDreamOverlay overlay = IDreamOverlay.Stub.asInterface(proxy);
+        final IDreamOverlayClient client = getClient();
 
         // Inform the overlay service of dream starting.
-        overlay.startDream(mWindowParams, mDreamOverlayCallback,
+        client.startDream(mWindowParams, mDreamOverlayCallback,
                 LOW_LIGHT_COMPONENT.flattenToString(), false /*shouldShowComplication*/);
         mMainExecutor.runAllReady();
 
@@ -276,11 +286,10 @@
 
     @Test
     public void testOnEndDream() throws RemoteException {
-        final IBinder proxy = mService.onBind(new Intent());
-        final IDreamOverlay overlay = IDreamOverlay.Stub.asInterface(proxy);
+        final IDreamOverlayClient client = getClient();
 
         // Inform the overlay service of dream starting.
-        overlay.startDream(mWindowParams, mDreamOverlayCallback,
+        client.startDream(mWindowParams, mDreamOverlayCallback,
                 LOW_LIGHT_COMPONENT.flattenToString(), false /*shouldShowComplication*/);
         mMainExecutor.runAllReady();
 
@@ -302,11 +311,10 @@
 
     @Test
     public void testDestroy() throws RemoteException {
-        final IBinder proxy = mService.onBind(new Intent());
-        final IDreamOverlay overlay = IDreamOverlay.Stub.asInterface(proxy);
+        final IDreamOverlayClient client = getClient();
 
         // Inform the overlay service of dream starting.
-        overlay.startDream(mWindowParams, mDreamOverlayCallback,
+        client.startDream(mWindowParams, mDreamOverlayCallback,
                 LOW_LIGHT_COMPONENT.flattenToString(), false /*shouldShowComplication*/);
         mMainExecutor.runAllReady();
 
@@ -346,15 +354,14 @@
 
     @Test
     public void testDecorViewNotAddedToWindowAfterDestroy() throws Exception {
-        final IBinder proxy = mService.onBind(new Intent());
-        final IDreamOverlay overlay = IDreamOverlay.Stub.asInterface(proxy);
+        final IDreamOverlayClient client = getClient();
 
         // Destroy the service.
         mService.onDestroy();
         mMainExecutor.runAllReady();
 
         // Inform the overlay service of dream starting.
-        overlay.startDream(mWindowParams, mDreamOverlayCallback, DREAM_COMPONENT,
+        client.startDream(mWindowParams, mDreamOverlayCallback, DREAM_COMPONENT,
                 false /*shouldShowComplication*/);
         mMainExecutor.runAllReady();
 
@@ -372,11 +379,10 @@
 
     @Test
     public void testResetCurrentOverlayWhenConnectedToNewDream() throws RemoteException {
-        final IBinder proxy = mService.onBind(new Intent());
-        final IDreamOverlay overlay = IDreamOverlay.Stub.asInterface(proxy);
+        final IDreamOverlayClient client = getClient();
 
         // Inform the overlay service of dream starting. Do not show dream complications.
-        overlay.startDream(mWindowParams, mDreamOverlayCallback, DREAM_COMPONENT,
+        client.startDream(mWindowParams, mDreamOverlayCallback, DREAM_COMPONENT,
                 false /*shouldShowComplication*/);
         mMainExecutor.runAllReady();
 
@@ -393,7 +399,7 @@
         // New dream starting with dream complications showing. Note that when a new dream is
         // binding to the dream overlay service, it receives the same instance of IBinder as the
         // first one.
-        overlay.startDream(mWindowParams, mDreamOverlayCallback, DREAM_COMPONENT,
+        client.startDream(mWindowParams, mDreamOverlayCallback, DREAM_COMPONENT,
                 true /*shouldShowComplication*/);
         mMainExecutor.runAllReady();
 
@@ -412,11 +418,10 @@
 
     @Test
     public void testWakeUp() throws RemoteException {
-        final IBinder proxy = mService.onBind(new Intent());
-        final IDreamOverlay overlay = IDreamOverlay.Stub.asInterface(proxy);
+        final IDreamOverlayClient client = getClient();
 
         // Inform the overlay service of dream starting.
-        overlay.startDream(mWindowParams, mDreamOverlayCallback, DREAM_COMPONENT,
+        client.startDream(mWindowParams, mDreamOverlayCallback, DREAM_COMPONENT,
                 true /*shouldShowComplication*/);
         mMainExecutor.runAllReady();
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/DreamHomeControlsComplicationTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/DreamHomeControlsComplicationTest.java
index 89c7280..a4cf15c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/DreamHomeControlsComplicationTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/DreamHomeControlsComplicationTest.java
@@ -31,13 +31,13 @@
 import android.content.Context;
 import android.testing.AndroidTestingRunner;
 import android.view.View;
-import android.widget.ImageView;
 
 import androidx.test.filters.SmallTest;
 
 import com.android.internal.logging.UiEventLogger;
 import com.android.systemui.R;
 import com.android.systemui.SysuiTestCase;
+import com.android.systemui.common.ui.view.LaunchableImageView;
 import com.android.systemui.controls.ControlsServiceInfo;
 import com.android.systemui.controls.controller.ControlsController;
 import com.android.systemui.controls.controller.StructureInfo;
@@ -90,7 +90,7 @@
     private View mView;
 
     @Mock
-    private ImageView mHomeControlsView;
+    private LaunchableImageView mHomeControlsView;
 
     @Mock
     private ActivityStarter mActivityStarter;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/FakeFeatureFlagsTest.kt b/packages/SystemUI/tests/src/com/android/systemui/flags/FakeFeatureFlagsTest.kt
index 170a70f..35f0f6c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/flags/FakeFeatureFlagsTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/flags/FakeFeatureFlagsTest.kt
@@ -125,7 +125,7 @@
         flags.set(unreleasedFlag, false)
         flags.set(unreleasedFlag, false)
 
-        listener.verifyInOrder(unreleasedFlag.id, unreleasedFlag.id)
+        listener.verifyInOrder(unreleasedFlag.name, unreleasedFlag.name)
     }
 
     @Test
@@ -137,7 +137,7 @@
         flags.set(stringFlag, "Test")
         flags.set(stringFlag, "Test")
 
-        listener.verifyInOrder(stringFlag.id)
+        listener.verifyInOrder(stringFlag.name)
     }
 
     @Test
@@ -149,7 +149,7 @@
         flags.removeListener(listener)
         flags.set(unreleasedFlag, false)
 
-        listener.verifyInOrder(unreleasedFlag.id)
+        listener.verifyInOrder(unreleasedFlag.name)
     }
 
     @Test
@@ -162,7 +162,7 @@
         flags.removeListener(listener)
         flags.set(stringFlag, "Other")
 
-        listener.verifyInOrder(stringFlag.id)
+        listener.verifyInOrder(stringFlag.name)
     }
 
     @Test
@@ -175,7 +175,7 @@
         flags.set(releasedFlag, true)
         flags.set(unreleasedFlag, true)
 
-        listener.verifyInOrder(releasedFlag.id, unreleasedFlag.id)
+        listener.verifyInOrder(releasedFlag.name, unreleasedFlag.name)
     }
 
     @Test
@@ -191,7 +191,7 @@
         flags.set(releasedFlag, false)
         flags.set(unreleasedFlag, false)
 
-        listener.verifyInOrder(releasedFlag.id, unreleasedFlag.id)
+        listener.verifyInOrder(releasedFlag.name, unreleasedFlag.name)
     }
 
     @Test
@@ -204,8 +204,8 @@
 
         flags.set(releasedFlag, true)
 
-        listener1.verifyInOrder(releasedFlag.id)
-        listener2.verifyInOrder(releasedFlag.id)
+        listener1.verifyInOrder(releasedFlag.name)
+        listener2.verifyInOrder(releasedFlag.name)
     }
 
     @Test
@@ -220,18 +220,18 @@
         flags.removeListener(listener2)
         flags.set(releasedFlag, false)
 
-        listener1.verifyInOrder(releasedFlag.id, releasedFlag.id)
-        listener2.verifyInOrder(releasedFlag.id)
+        listener1.verifyInOrder(releasedFlag.name, releasedFlag.name)
+        listener2.verifyInOrder(releasedFlag.name)
     }
 
     class VerifyingListener : FlagListenable.Listener {
-        var flagEventIds = mutableListOf<Int>()
+        var flagEventNames = mutableListOf<String>()
         override fun onFlagChanged(event: FlagListenable.FlagEvent) {
-            flagEventIds.add(event.flagId)
+            flagEventNames.add(event.flagName)
         }
 
-        fun verifyInOrder(vararg eventIds: Int) {
-            assertThat(flagEventIds).containsExactlyElementsIn(eventIds.asList())
+        fun verifyInOrder(vararg eventNames: String) {
+            assertThat(flagEventNames).containsExactlyElementsIn(eventNames.asList())
         }
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsDebugTest.kt b/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsDebugTest.kt
index 7592cc5..d8bbd04 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsDebugTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsDebugTest.kt
@@ -23,12 +23,11 @@
 import android.content.res.Resources.NotFoundException
 import android.test.suitebuilder.annotation.SmallTest
 import com.android.systemui.SysuiTestCase
-import com.android.systemui.statusbar.commandline.CommandRegistry
-import com.android.systemui.util.DeviceConfigProxyFake
 import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.eq
 import com.android.systemui.util.mockito.nullable
 import com.android.systemui.util.mockito.withArgCaptor
+import com.android.systemui.util.settings.GlobalSettings
 import com.android.systemui.util.settings.SecureSettings
 import com.google.common.truth.Truth.assertThat
 import org.junit.Assert
@@ -62,21 +61,20 @@
     @Mock
     private lateinit var mockContext: Context
     @Mock
+    private lateinit var globalSettings: GlobalSettings
+    @Mock
     private lateinit var secureSettings: SecureSettings
     @Mock
     private lateinit var systemProperties: SystemPropertiesHelper
     @Mock
     private lateinit var resources: Resources
     @Mock
-    private lateinit var commandRegistry: CommandRegistry
-    @Mock
     private lateinit var restarter: Restarter
-    private val flagMap = mutableMapOf<Int, Flag<*>>()
+    private val flagMap = mutableMapOf<String, Flag<*>>()
     private lateinit var broadcastReceiver: BroadcastReceiver
-    private lateinit var clearCacheAction: Consumer<Int>
+    private lateinit var clearCacheAction: Consumer<String>
     private val serverFlagReader = ServerFlagReaderFake()
 
-    private val deviceConfig = DeviceConfigProxyFake()
     private val teamfoodableFlagA = UnreleasedFlag(
         500, name = "a", namespace = "test", teamfood = true
     )
@@ -87,11 +85,13 @@
     @Before
     fun setup() {
         MockitoAnnotations.initMocks(this)
-        flagMap.put(teamfoodableFlagA.id, teamfoodableFlagA)
-        flagMap.put(teamfoodableFlagB.id, teamfoodableFlagB)
+        flagMap.put(Flags.TEAMFOOD.name, Flags.TEAMFOOD)
+        flagMap.put(teamfoodableFlagA.name, teamfoodableFlagA)
+        flagMap.put(teamfoodableFlagB.name, teamfoodableFlagB)
         mFeatureFlagsDebug = FeatureFlagsDebug(
             flagManager,
             mockContext,
+            globalSettings,
             secureSettings,
             systemProperties,
             resources,
@@ -110,14 +110,14 @@
         clearCacheAction = withArgCaptor {
             verify(flagManager).clearCacheAction = capture()
         }
-        whenever(flagManager.idToSettingsKey(any())).thenAnswer { "key-${it.arguments[0]}" }
+        whenever(flagManager.nameToSettingsKey(any())).thenAnswer { "key-${it.arguments[0]}" }
     }
 
     @Test
     fun readBooleanFlag() {
         // Remember that the TEAMFOOD flag is id#1 and has special behavior.
-        whenever(flagManager.readFlagValue<Boolean>(eq(3), any())).thenReturn(true)
-        whenever(flagManager.readFlagValue<Boolean>(eq(4), any())).thenReturn(false)
+        whenever(flagManager.readFlagValue<Boolean>(eq("3"), any())).thenReturn(true)
+        whenever(flagManager.readFlagValue<Boolean>(eq("4"), any())).thenReturn(false)
 
         assertThat(
             mFeatureFlagsDebug.isEnabled(
@@ -141,7 +141,7 @@
             mFeatureFlagsDebug.isEnabled(
                 ReleasedFlag(
                     4,
-                    name = "3",
+                    name = "4",
                     namespace = "test"
                 )
             )
@@ -150,7 +150,7 @@
             mFeatureFlagsDebug.isEnabled(
                 UnreleasedFlag(
                     5,
-                    name = "4",
+                    name = "5",
                     namespace = "test"
                 )
             )
@@ -159,7 +159,8 @@
 
     @Test
     fun teamFoodFlag_False() {
-        whenever(flagManager.readFlagValue<Boolean>(eq(1), any())).thenReturn(false)
+        whenever(flagManager.readFlagValue<Boolean>(
+            eq(Flags.TEAMFOOD.name), any())).thenReturn(false)
         assertThat(mFeatureFlagsDebug.isEnabled(teamfoodableFlagA)).isFalse()
         assertThat(mFeatureFlagsDebug.isEnabled(teamfoodableFlagB)).isTrue()
 
@@ -170,7 +171,8 @@
 
     @Test
     fun teamFoodFlag_True() {
-        whenever(flagManager.readFlagValue<Boolean>(eq(1), any())).thenReturn(true)
+        whenever(flagManager.readFlagValue<Boolean>(
+            eq(Flags.TEAMFOOD.name), any())).thenReturn(true)
         assertThat(mFeatureFlagsDebug.isEnabled(teamfoodableFlagA)).isTrue()
         assertThat(mFeatureFlagsDebug.isEnabled(teamfoodableFlagB)).isTrue()
 
@@ -181,11 +183,12 @@
 
     @Test
     fun teamFoodFlag_Overridden() {
-        whenever(flagManager.readFlagValue<Boolean>(eq(teamfoodableFlagA.id), any()))
+        whenever(flagManager.readFlagValue<Boolean>(eq(teamfoodableFlagA.name), any()))
             .thenReturn(true)
-        whenever(flagManager.readFlagValue<Boolean>(eq(teamfoodableFlagB.id), any()))
+        whenever(flagManager.readFlagValue<Boolean>(eq(teamfoodableFlagB.name), any()))
             .thenReturn(false)
-        whenever(flagManager.readFlagValue<Boolean>(eq(1), any())).thenReturn(true)
+        whenever(flagManager.readFlagValue<Boolean>(
+            eq(Flags.TEAMFOOD.name), any())).thenReturn(true)
         assertThat(mFeatureFlagsDebug.isEnabled(teamfoodableFlagA)).isTrue()
         assertThat(mFeatureFlagsDebug.isEnabled(teamfoodableFlagB)).isFalse()
 
@@ -202,8 +205,8 @@
         whenever(resources.getBoolean(1004)).thenAnswer { throw NameNotFoundException() }
         whenever(resources.getBoolean(1005)).thenAnswer { throw NameNotFoundException() }
 
-        whenever(flagManager.readFlagValue<Boolean>(eq(3), any())).thenReturn(true)
-        whenever(flagManager.readFlagValue<Boolean>(eq(5), any())).thenReturn(false)
+        whenever(flagManager.readFlagValue<Boolean>(eq("3"), any())).thenReturn(true)
+        whenever(flagManager.readFlagValue<Boolean>(eq("5"), any())).thenReturn(false)
 
         assertThat(
             mFeatureFlagsDebug.isEnabled(
@@ -255,8 +258,8 @@
 
     @Test
     fun readStringFlag() {
-        whenever(flagManager.readFlagValue<String>(eq(3), any())).thenReturn("foo")
-        whenever(flagManager.readFlagValue<String>(eq(4), any())).thenReturn("bar")
+        whenever(flagManager.readFlagValue<String>(eq("3"), any())).thenReturn("foo")
+        whenever(flagManager.readFlagValue<String>(eq("4"), any())).thenReturn("bar")
         assertThat(mFeatureFlagsDebug.getString(StringFlag(1, "1", "test", "biz"))).isEqualTo("biz")
         assertThat(mFeatureFlagsDebug.getString(StringFlag(2, "2", "test", "baz"))).isEqualTo("baz")
         assertThat(mFeatureFlagsDebug.getString(StringFlag(3, "3", "test", "buz"))).isEqualTo("foo")
@@ -272,9 +275,9 @@
         whenever(resources.getString(1005)).thenAnswer { throw NameNotFoundException() }
         whenever(resources.getString(1006)).thenAnswer { throw NameNotFoundException() }
 
-        whenever(flagManager.readFlagValue<String>(eq(3), any())).thenReturn("override3")
-        whenever(flagManager.readFlagValue<String>(eq(4), any())).thenReturn("override4")
-        whenever(flagManager.readFlagValue<String>(eq(6), any())).thenReturn("override6")
+        whenever(flagManager.readFlagValue<String>(eq("3"), any())).thenReturn("override3")
+        whenever(flagManager.readFlagValue<String>(eq("4"), any())).thenReturn("override4")
+        whenever(flagManager.readFlagValue<String>(eq("6"), any())).thenReturn("override6")
 
         assertThat(
             mFeatureFlagsDebug.getString(
@@ -322,8 +325,8 @@
 
     @Test
     fun readIntFlag() {
-        whenever(flagManager.readFlagValue<Int>(eq(3), any())).thenReturn(22)
-        whenever(flagManager.readFlagValue<Int>(eq(4), any())).thenReturn(48)
+        whenever(flagManager.readFlagValue<Int>(eq("3"), any())).thenReturn(22)
+        whenever(flagManager.readFlagValue<Int>(eq("4"), any())).thenReturn(48)
         assertThat(mFeatureFlagsDebug.getInt(IntFlag(1, "1", "test", 12))).isEqualTo(12)
         assertThat(mFeatureFlagsDebug.getInt(IntFlag(2, "2", "test", 93))).isEqualTo(93)
         assertThat(mFeatureFlagsDebug.getInt(IntFlag(3, "3", "test", 8))).isEqualTo(22)
@@ -368,12 +371,12 @@
         broadcastReceiver.onReceive(mockContext, Intent())
         broadcastReceiver.onReceive(mockContext, Intent("invalid action"))
         broadcastReceiver.onReceive(mockContext, Intent(FlagManager.ACTION_SET_FLAG))
-        setByBroadcast(0, false) // unknown id does nothing
-        setByBroadcast(1, "string") // wrong type does nothing
-        setByBroadcast(2, 123) // wrong type does nothing
-        setByBroadcast(3, false) // wrong type does nothing
-        setByBroadcast(4, 123) // wrong type does nothing
-        verifyNoMoreInteractions(flagManager, secureSettings)
+        setByBroadcast("0", false) // unknown id does nothing
+        setByBroadcast("1", "string") // wrong type does nothing
+        setByBroadcast("2", 123) // wrong type does nothing
+        setByBroadcast("3", false) // wrong type does nothing
+        setByBroadcast("4", 123) // wrong type does nothing
+        verifyNoMoreInteractions(flagManager, globalSettings)
     }
 
     @Test
@@ -383,16 +386,16 @@
         // trying to erase an id not in the map does nothing
         broadcastReceiver.onReceive(
             mockContext,
-            Intent(FlagManager.ACTION_SET_FLAG).putExtra(FlagManager.EXTRA_ID, 0)
+            Intent(FlagManager.ACTION_SET_FLAG).putExtra(FlagManager.EXTRA_NAME, "")
         )
-        verifyNoMoreInteractions(flagManager, secureSettings)
+        verifyNoMoreInteractions(flagManager, globalSettings)
 
         // valid id with no value puts empty string in the setting
         broadcastReceiver.onReceive(
             mockContext,
-            Intent(FlagManager.ACTION_SET_FLAG).putExtra(FlagManager.EXTRA_ID, 1)
+            Intent(FlagManager.ACTION_SET_FLAG).putExtra(FlagManager.EXTRA_NAME, "1")
         )
-        verifyPutData(1, "", numReads = 0)
+        verifyPutData("1", "", numReads = 0)
     }
 
     @Test
@@ -402,51 +405,51 @@
         addFlag(ResourceBooleanFlag(3, "3", "test", 1003))
         addFlag(ResourceBooleanFlag(4, "4", "test", 1004))
 
-        setByBroadcast(1, false)
-        verifyPutData(1, "{\"type\":\"boolean\",\"value\":false}")
+        setByBroadcast("1", false)
+        verifyPutData("1", "{\"type\":\"boolean\",\"value\":false}")
 
-        setByBroadcast(2, true)
-        verifyPutData(2, "{\"type\":\"boolean\",\"value\":true}")
+        setByBroadcast("2", true)
+        verifyPutData("2", "{\"type\":\"boolean\",\"value\":true}")
 
-        setByBroadcast(3, false)
-        verifyPutData(3, "{\"type\":\"boolean\",\"value\":false}")
+        setByBroadcast("3", false)
+        verifyPutData("3", "{\"type\":\"boolean\",\"value\":false}")
 
-        setByBroadcast(4, true)
-        verifyPutData(4, "{\"type\":\"boolean\",\"value\":true}")
+        setByBroadcast("4", true)
+        verifyPutData("4", "{\"type\":\"boolean\",\"value\":true}")
     }
 
     @Test
     fun setStringFlag() {
-        addFlag(StringFlag(1, "flag1", "1", "test"))
+        addFlag(StringFlag(1, "1", "1", "test"))
         addFlag(ResourceStringFlag(2, "2", "test", 1002))
 
-        setByBroadcast(1, "override1")
-        verifyPutData(1, "{\"type\":\"string\",\"value\":\"override1\"}")
+        setByBroadcast("1", "override1")
+        verifyPutData("1", "{\"type\":\"string\",\"value\":\"override1\"}")
 
-        setByBroadcast(2, "override2")
-        verifyPutData(2, "{\"type\":\"string\",\"value\":\"override2\"}")
+        setByBroadcast("2", "override2")
+        verifyPutData("2", "{\"type\":\"string\",\"value\":\"override2\"}")
     }
 
     @Test
     fun setFlag_ClearsCache() {
         val flag1 = addFlag(StringFlag(1, "1", "test", "flag1"))
-        whenever(flagManager.readFlagValue<String>(eq(1), any())).thenReturn("original")
+        whenever(flagManager.readFlagValue<String>(eq("1"), any())).thenReturn("original")
 
         // gets the flag & cache it
         assertThat(mFeatureFlagsDebug.getString(flag1)).isEqualTo("original")
-        verify(flagManager).readFlagValue(eq(1), eq(StringFlagSerializer))
+        verify(flagManager, times(1)).readFlagValue(eq("1"), eq(StringFlagSerializer))
 
         // hit the cache
         assertThat(mFeatureFlagsDebug.getString(flag1)).isEqualTo("original")
         verifyNoMoreInteractions(flagManager)
 
         // set the flag
-        setByBroadcast(1, "new")
-        verifyPutData(1, "{\"type\":\"string\",\"value\":\"new\"}", numReads = 2)
-        whenever(flagManager.readFlagValue<String>(eq(1), any())).thenReturn("new")
+        setByBroadcast("1", "new")
+        verifyPutData("1", "{\"type\":\"string\",\"value\":\"new\"}", numReads = 2)
+        whenever(flagManager.readFlagValue<String>(eq("1"), any())).thenReturn("new")
 
         assertThat(mFeatureFlagsDebug.getString(flag1)).isEqualTo("new")
-        verify(flagManager, times(3)).readFlagValue(eq(1), eq(StringFlagSerializer))
+        verify(flagManager, times(3)).readFlagValue(eq("1"), eq(StringFlagSerializer))
     }
 
     @Test
@@ -463,7 +466,6 @@
         val flag = UnreleasedFlag(100, name = "100", namespace = "test")
 
         serverFlagReader.setFlagValue(flag.namespace, flag.name, true)
-
         assertThat(mFeatureFlagsDebug.isEnabled(flag)).isTrue()
     }
 
@@ -503,26 +505,26 @@
         assertThat(dump).contains(" sysui_flag_7: [length=9] \"override7\"\n")
     }
 
-    private fun verifyPutData(id: Int, data: String, numReads: Int = 1) {
-        inOrder(flagManager, secureSettings).apply {
-            verify(flagManager, times(numReads)).readFlagValue(eq(id), any<FlagSerializer<*>>())
-            verify(flagManager).idToSettingsKey(eq(id))
-            verify(secureSettings).putStringForUser(eq("key-$id"), eq(data), anyInt())
-            verify(flagManager).dispatchListenersAndMaybeRestart(eq(id), any())
+    private fun verifyPutData(name: String, data: String, numReads: Int = 1) {
+        inOrder(flagManager, globalSettings).apply {
+            verify(flagManager, times(numReads)).readFlagValue(eq(name), any<FlagSerializer<*>>())
+            verify(flagManager).nameToSettingsKey(eq(name))
+            verify(globalSettings).putStringForUser(eq("key-$name"), eq(data), anyInt())
+            verify(flagManager).dispatchListenersAndMaybeRestart(eq(name), any())
         }.verifyNoMoreInteractions()
-        verifyNoMoreInteractions(flagManager, secureSettings)
+        verifyNoMoreInteractions(flagManager, globalSettings)
     }
 
-    private fun setByBroadcast(id: Int, value: Serializable?) {
+    private fun setByBroadcast(name: String, value: Serializable?) {
         val intent = Intent(FlagManager.ACTION_SET_FLAG)
-        intent.putExtra(FlagManager.EXTRA_ID, id)
+        intent.putExtra(FlagManager.EXTRA_NAME, name)
         intent.putExtra(FlagManager.EXTRA_VALUE, value)
         broadcastReceiver.onReceive(mockContext, intent)
     }
 
     private fun <F : Flag<*>> addFlag(flag: F): F {
-        val old = flagMap.put(flag.id, flag)
-        check(old == null) { "Flag ${flag.id} already registered" }
+        val old = flagMap.put(flag.name, flag)
+        check(old == null) { "Flag ${flag.name} already registered" }
         return flag
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsReleaseTest.kt b/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsReleaseTest.kt
index d5b5a4a..4c6028c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsReleaseTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsReleaseTest.kt
@@ -19,7 +19,6 @@
 import android.content.res.Resources
 import android.test.suitebuilder.annotation.SmallTest
 import com.android.systemui.SysuiTestCase
-import com.android.systemui.util.DeviceConfigProxyFake
 import com.google.common.truth.Truth.assertThat
 import org.junit.Assert.assertThrows
 import org.junit.Before
@@ -39,9 +38,8 @@
     @Mock private lateinit var mResources: Resources
     @Mock private lateinit var mSystemProperties: SystemPropertiesHelper
     @Mock private lateinit var restarter: Restarter
-    private val flagMap = mutableMapOf<Int, Flag<*>>()
+    private val flagMap = mutableMapOf<String, Flag<*>>()
     private val serverFlagReader = ServerFlagReaderFake()
-    private val deviceConfig = DeviceConfigProxyFake()
 
     @Before
     fun setup() {
@@ -49,7 +47,6 @@
         mFeatureFlagsRelease = FeatureFlagsRelease(
             mResources,
             mSystemProperties,
-            deviceConfig,
             serverFlagReader,
             flagMap,
             restarter)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/FlagCommandTest.kt b/packages/SystemUI/tests/src/com/android/systemui/flags/FlagCommandTest.kt
index fea91c5..28131b5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/flags/FlagCommandTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/flags/FlagCommandTest.kt
@@ -32,7 +32,7 @@
 
     @Mock private lateinit var featureFlags: FeatureFlagsDebug
     @Mock private lateinit var pw: PrintWriter
-    private val flagMap = mutableMapOf<Int, Flag<*>>()
+    private val flagMap = mutableMapOf<String, Flag<*>>()
     private val flagA = UnreleasedFlag(500, "500", "test")
     private val flagB = ReleasedFlag(501, "501", "test")
     private val stringFlag = StringFlag(502, "502", "test", "abracadabra")
@@ -53,59 +53,59 @@
             (invocation.getArgument(0) as IntFlag).default
         }
 
-        flagMap.put(flagA.id, flagA)
-        flagMap.put(flagB.id, flagB)
-        flagMap.put(stringFlag.id, stringFlag)
-        flagMap.put(intFlag.id, intFlag)
+        flagMap.put(flagA.name, flagA)
+        flagMap.put(flagB.name, flagB)
+        flagMap.put(stringFlag.name, stringFlag)
+        flagMap.put(intFlag.name, intFlag)
 
         cmd = FlagCommand(featureFlags, flagMap)
     }
 
     @Test
     fun readBooleanFlagCommand() {
-        cmd.execute(pw, listOf(flagA.id.toString()))
+        cmd.execute(pw, listOf(flagA.name))
         Mockito.verify(featureFlags).isEnabled(flagA)
     }
 
     @Test
     fun readStringFlagCommand() {
-        cmd.execute(pw, listOf(stringFlag.id.toString()))
+        cmd.execute(pw, listOf(stringFlag.name))
         Mockito.verify(featureFlags).getString(stringFlag)
     }
 
     @Test
     fun readIntFlag() {
-        cmd.execute(pw, listOf(intFlag.id.toString()))
+        cmd.execute(pw, listOf(intFlag.name))
         Mockito.verify(featureFlags).getInt(intFlag)
     }
 
     @Test
     fun setBooleanFlagCommand() {
-        cmd.execute(pw, listOf(flagB.id.toString(), "on"))
+        cmd.execute(pw, listOf(flagB.name, "on"))
         Mockito.verify(featureFlags).setBooleanFlagInternal(flagB, true)
     }
 
     @Test
     fun setStringFlagCommand() {
-        cmd.execute(pw, listOf(stringFlag.id.toString(), "set", "foobar"))
+        cmd.execute(pw, listOf(stringFlag.name, "set", "foobar"))
         Mockito.verify(featureFlags).setStringFlagInternal(stringFlag, "foobar")
     }
 
     @Test
     fun setIntFlag() {
-        cmd.execute(pw, listOf(intFlag.id.toString(), "put", "123"))
+        cmd.execute(pw, listOf(intFlag.name, "put", "123"))
         Mockito.verify(featureFlags).setIntFlagInternal(intFlag, 123)
     }
 
     @Test
     fun toggleBooleanFlagCommand() {
-        cmd.execute(pw, listOf(flagB.id.toString(), "toggle"))
+        cmd.execute(pw, listOf(flagB.name, "toggle"))
         Mockito.verify(featureFlags).setBooleanFlagInternal(flagB, false)
     }
 
     @Test
     fun eraseFlagCommand() {
-        cmd.execute(pw, listOf(flagA.id.toString(), "erase"))
+        cmd.execute(pw, listOf(flagA.name, "erase"))
         Mockito.verify(featureFlags).eraseFlag(flagA)
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/FlagManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/flags/FlagManagerTest.kt
index fca7e96..e679d47 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/flags/FlagManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/flags/FlagManagerTest.kt
@@ -87,14 +87,14 @@
     @Test
     fun testObserverClearsCache() {
         val listener = mock<FlagListenable.Listener>()
-        val clearCacheAction = mock<Consumer<Int>>()
+        val clearCacheAction = mock<Consumer<String>>()
         mFlagManager.clearCacheAction = clearCacheAction
         mFlagManager.addListener(ReleasedFlag(1, "1", "test"), listener)
         val observer = withArgCaptor<ContentObserver> {
             verify(mFlagSettingsHelper).registerContentObserver(any(), any(), capture())
         }
         observer.onChange(false, flagUri(1))
-        verify(clearCacheAction).accept(eq(1))
+        verify(clearCacheAction).accept(eq("1"))
     }
 
     @Test
@@ -110,14 +110,14 @@
         val flagEvent1 = withArgCaptor<FlagListenable.FlagEvent> {
             verify(listener1).onFlagChanged(capture())
         }
-        assertThat(flagEvent1.flagId).isEqualTo(1)
+        assertThat(flagEvent1.flagName).isEqualTo("1")
         verifyNoMoreInteractions(listener1, listener10)
 
         observer.onChange(false, flagUri(10))
         val flagEvent10 = withArgCaptor<FlagListenable.FlagEvent> {
             verify(listener10).onFlagChanged(capture())
         }
-        assertThat(flagEvent10.flagId).isEqualTo(10)
+        assertThat(flagEvent10.flagName).isEqualTo("10")
         verifyNoMoreInteractions(listener1, listener10)
     }
 
@@ -130,18 +130,18 @@
         mFlagManager.addListener(ReleasedFlag(1, "1", "test"), listener1)
         mFlagManager.addListener(ReleasedFlag(10, "10", "test"), listener10)
 
-        mFlagManager.dispatchListenersAndMaybeRestart(1, null)
+        mFlagManager.dispatchListenersAndMaybeRestart("1", null)
         val flagEvent1 = withArgCaptor<FlagListenable.FlagEvent> {
             verify(listener1).onFlagChanged(capture())
         }
-        assertThat(flagEvent1.flagId).isEqualTo(1)
+        assertThat(flagEvent1.flagName).isEqualTo("1")
         verifyNoMoreInteractions(listener1, listener10)
 
-        mFlagManager.dispatchListenersAndMaybeRestart(10, null)
+        mFlagManager.dispatchListenersAndMaybeRestart("10", null)
         val flagEvent10 = withArgCaptor<FlagListenable.FlagEvent> {
             verify(listener10).onFlagChanged(capture())
         }
-        assertThat(flagEvent10.flagId).isEqualTo(10)
+        assertThat(flagEvent10.flagName).isEqualTo("10")
         verifyNoMoreInteractions(listener1, listener10)
     }
 
@@ -151,25 +151,25 @@
         mFlagManager.addListener(ReleasedFlag(1, "1", "test"), listener)
         mFlagManager.addListener(ReleasedFlag(10, "10", "test"), listener)
 
-        mFlagManager.dispatchListenersAndMaybeRestart(1, null)
+        mFlagManager.dispatchListenersAndMaybeRestart("1", null)
         val flagEvent1 = withArgCaptor<FlagListenable.FlagEvent> {
             verify(listener).onFlagChanged(capture())
         }
-        assertThat(flagEvent1.flagId).isEqualTo(1)
+        assertThat(flagEvent1.flagName).isEqualTo("1")
         verifyNoMoreInteractions(listener)
 
-        mFlagManager.dispatchListenersAndMaybeRestart(10, null)
+        mFlagManager.dispatchListenersAndMaybeRestart("10", null)
         val flagEvent10 = withArgCaptor<FlagListenable.FlagEvent> {
             verify(listener, times(2)).onFlagChanged(capture())
         }
-        assertThat(flagEvent10.flagId).isEqualTo(10)
+        assertThat(flagEvent10.flagName).isEqualTo("10")
         verifyNoMoreInteractions(listener)
     }
 
     @Test
     fun testRestartWithNoListeners() {
         val restartAction = mock<Consumer<Boolean>>()
-        mFlagManager.dispatchListenersAndMaybeRestart(1, restartAction)
+        mFlagManager.dispatchListenersAndMaybeRestart("1", restartAction)
         verify(restartAction).accept(eq(false))
         verifyNoMoreInteractions(restartAction)
     }
@@ -180,7 +180,7 @@
         mFlagManager.addListener(ReleasedFlag(1, "1", "test")) { event ->
             event.requestNoRestart()
         }
-        mFlagManager.dispatchListenersAndMaybeRestart(1, restartAction)
+        mFlagManager.dispatchListenersAndMaybeRestart("1", restartAction)
         verify(restartAction).accept(eq(true))
         verifyNoMoreInteractions(restartAction)
     }
@@ -191,7 +191,7 @@
         mFlagManager.addListener(ReleasedFlag(10, "10", "test")) { event ->
             event.requestNoRestart()
         }
-        mFlagManager.dispatchListenersAndMaybeRestart(1, restartAction)
+        mFlagManager.dispatchListenersAndMaybeRestart("1", restartAction)
         verify(restartAction).accept(eq(false))
         verifyNoMoreInteractions(restartAction)
     }
@@ -205,7 +205,7 @@
         mFlagManager.addListener(ReleasedFlag(10, "10", "test")) {
             // do not request
         }
-        mFlagManager.dispatchListenersAndMaybeRestart(1, restartAction)
+        mFlagManager.dispatchListenersAndMaybeRestart("1", restartAction)
         verify(restartAction).accept(eq(false))
         verifyNoMoreInteractions(restartAction)
     }
@@ -214,31 +214,31 @@
     fun testReadBooleanFlag() {
         // test that null string returns null
         whenever(mFlagSettingsHelper.getString(any())).thenReturn(null)
-        assertThat(mFlagManager.readFlagValue(1, BooleanFlagSerializer)).isNull()
+        assertThat(mFlagManager.readFlagValue("1", BooleanFlagSerializer)).isNull()
 
         // test that empty string returns null
         whenever(mFlagSettingsHelper.getString(any())).thenReturn("")
-        assertThat(mFlagManager.readFlagValue(1, BooleanFlagSerializer)).isNull()
+        assertThat(mFlagManager.readFlagValue("1", BooleanFlagSerializer)).isNull()
 
         // test false
         whenever(mFlagSettingsHelper.getString(any()))
             .thenReturn("{\"type\":\"boolean\",\"value\":false}")
-        assertThat(mFlagManager.readFlagValue(1, BooleanFlagSerializer)).isFalse()
+        assertThat(mFlagManager.readFlagValue("1", BooleanFlagSerializer)).isFalse()
 
         // test true
         whenever(mFlagSettingsHelper.getString(any()))
             .thenReturn("{\"type\":\"boolean\",\"value\":true}")
-        assertThat(mFlagManager.readFlagValue(1, BooleanFlagSerializer)).isTrue()
+        assertThat(mFlagManager.readFlagValue("1", BooleanFlagSerializer)).isTrue()
 
         // Reading a value of a different type should just return null
         whenever(mFlagSettingsHelper.getString(any()))
             .thenReturn("{\"type\":\"string\",\"value\":\"foo\"}")
-        assertThat(mFlagManager.readFlagValue(1, BooleanFlagSerializer)).isNull()
+        assertThat(mFlagManager.readFlagValue("1", BooleanFlagSerializer)).isNull()
 
         // Reading a value that isn't json should throw an exception
         assertThrows(InvalidFlagStorageException::class.java) {
             whenever(mFlagSettingsHelper.getString(any())).thenReturn("1")
-            mFlagManager.readFlagValue(1, BooleanFlagSerializer)
+            mFlagManager.readFlagValue("1", BooleanFlagSerializer)
         }
     }
 
@@ -257,31 +257,31 @@
     fun testReadStringFlag() {
         // test that null string returns null
         whenever(mFlagSettingsHelper.getString(any())).thenReturn(null)
-        assertThat(mFlagManager.readFlagValue(1, StringFlagSerializer)).isNull()
+        assertThat(mFlagManager.readFlagValue("1", StringFlagSerializer)).isNull()
 
         // test that empty string returns null
         whenever(mFlagSettingsHelper.getString(any())).thenReturn("")
-        assertThat(mFlagManager.readFlagValue(1, StringFlagSerializer)).isNull()
+        assertThat(mFlagManager.readFlagValue("1", StringFlagSerializer)).isNull()
 
         // test json with the empty string value returns empty string
         whenever(mFlagSettingsHelper.getString(any()))
             .thenReturn("{\"type\":\"string\",\"value\":\"\"}")
-        assertThat(mFlagManager.readFlagValue(1, StringFlagSerializer)).isEqualTo("")
+        assertThat(mFlagManager.readFlagValue("1", StringFlagSerializer)).isEqualTo("")
 
         // test string with value is returned
         whenever(mFlagSettingsHelper.getString(any()))
             .thenReturn("{\"type\":\"string\",\"value\":\"foo\"}")
-        assertThat(mFlagManager.readFlagValue(1, StringFlagSerializer)).isEqualTo("foo")
+        assertThat(mFlagManager.readFlagValue("1", StringFlagSerializer)).isEqualTo("foo")
 
         // Reading a value of a different type should just return null
         whenever(mFlagSettingsHelper.getString(any()))
             .thenReturn("{\"type\":\"boolean\",\"value\":false}")
-        assertThat(mFlagManager.readFlagValue(1, StringFlagSerializer)).isNull()
+        assertThat(mFlagManager.readFlagValue("1", StringFlagSerializer)).isNull()
 
         // Reading a value that isn't json should throw an exception
         assertThrows(InvalidFlagStorageException::class.java) {
             whenever(mFlagSettingsHelper.getString(any())).thenReturn("1")
-            mFlagManager.readFlagValue(1, StringFlagSerializer)
+            mFlagManager.readFlagValue("1", StringFlagSerializer)
         }
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaCarouselControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaCarouselControllerTest.kt
index e4e95e5..5e5dc8b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaCarouselControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaCarouselControllerTest.kt
@@ -33,6 +33,7 @@
 import com.android.systemui.media.controls.pipeline.EMPTY_SMARTSPACE_MEDIA_DATA
 import com.android.systemui.media.controls.pipeline.MediaDataManager
 import com.android.systemui.media.controls.ui.MediaHierarchyManager.Companion.LOCATION_QS
+import com.android.systemui.media.controls.util.MediaFlags
 import com.android.systemui.media.controls.util.MediaUiEventLogger
 import com.android.systemui.plugins.ActivityStarter
 import com.android.systemui.plugins.FalsingManager
@@ -87,6 +88,7 @@
     @Mock lateinit var smartspaceMediaData: SmartspaceMediaData
     @Mock lateinit var mediaCarousel: MediaScrollView
     @Mock lateinit var pageIndicator: PageIndicator
+    @Mock lateinit var mediaFlags: MediaFlags
     @Captor lateinit var listener: ArgumentCaptor<MediaDataManager.Listener>
     @Captor
     lateinit var configListener: ArgumentCaptor<ConfigurationController.ConfigurationListener>
@@ -114,7 +116,8 @@
                 falsingManager,
                 dumpManager,
                 logger,
-                debugLogger
+                debugLogger,
+                mediaFlags,
             )
         verify(configurationController).addCallback(capture(configListener))
         verify(mediaDataManager).addListener(capture(listener))
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 b35dd26..ce22b19 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
@@ -204,6 +204,9 @@
     @Mock private lateinit var coverContainer1: ViewGroup
     @Mock private lateinit var coverContainer2: ViewGroup
     @Mock private lateinit var coverContainer3: ViewGroup
+    @Mock private lateinit var recAppIconItem: CachingIconView
+    @Mock private lateinit var recCardTitle: TextView
+    @Mock private lateinit var coverItem: ImageView
     private lateinit var coverItem1: ImageView
     private lateinit var coverItem2: ImageView
     private lateinit var coverItem3: ImageView
@@ -220,6 +223,7 @@
             this.set(Flags.UMO_TURBULENCE_NOISE, false)
             this.set(Flags.MEDIA_FALSING_PENALTY, true)
             this.set(Flags.MEDIA_EXPLICIT_INDICATOR, true)
+            this.set(Flags.MEDIA_RECOMMENDATION_CARD_UPDATE, false)
         }
 
     @JvmField @Rule val mockito = MockitoJUnit.rule()
@@ -2059,6 +2063,51 @@
     }
 
     @Test
+    fun bindRecommendation_setAfterExecutors() {
+        fakeFeatureFlag.set(Flags.MEDIA_RECOMMENDATION_CARD_UPDATE, true)
+        whenever(recommendationViewHolder.mediaAppIcons)
+            .thenReturn(listOf(recAppIconItem, recAppIconItem, recAppIconItem))
+        whenever(recommendationViewHolder.cardTitle).thenReturn(recCardTitle)
+        whenever(recommendationViewHolder.mediaCoverItems)
+            .thenReturn(listOf(coverItem, coverItem, coverItem))
+
+        val bmp = Bitmap.createBitmap(10, 10, Bitmap.Config.ARGB_8888)
+        val canvas = Canvas(bmp)
+        canvas.drawColor(Color.RED)
+        val albumArt = Icon.createWithBitmap(bmp)
+        val data =
+            smartspaceData.copy(
+                recommendations =
+                    listOf(
+                        SmartspaceAction.Builder("id1", "title1")
+                            .setSubtitle("subtitle1")
+                            .setIcon(albumArt)
+                            .setExtras(Bundle.EMPTY)
+                            .build(),
+                        SmartspaceAction.Builder("id2", "title2")
+                            .setSubtitle("subtitle1")
+                            .setIcon(albumArt)
+                            .setExtras(Bundle.EMPTY)
+                            .build(),
+                        SmartspaceAction.Builder("id3", "title3")
+                            .setSubtitle("subtitle1")
+                            .setIcon(albumArt)
+                            .setExtras(Bundle.EMPTY)
+                            .build()
+                    )
+            )
+
+        player.attachRecommendation(recommendationViewHolder)
+        player.bindRecommendation(data)
+        bgExecutor.runAllReady()
+        mainExecutor.runAllReady()
+
+        verify(recCardTitle).setTextColor(any<Int>())
+        verify(recAppIconItem, times(3)).setImageDrawable(any(Drawable::class.java))
+        verify(coverItem, times(3)).setImageDrawable(any(Drawable::class.java))
+    }
+
+    @Test
     fun onButtonClick_touchRippleFlagEnabled_playsTouchRipple() {
         fakeFeatureFlag.set(Flags.UMO_SURFACE_RIPPLE, true)
         val semanticActions =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaViewControllerTest.kt
index 4ed6d7c..2f7eac2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaViewControllerTest.kt
@@ -22,6 +22,7 @@
 import androidx.test.filters.SmallTest
 import com.android.systemui.R
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.media.controls.util.MediaFlags
 import com.android.systemui.util.animation.MeasurementInput
 import com.android.systemui.util.animation.TransitionLayout
 import com.android.systemui.util.animation.TransitionViewState
@@ -55,6 +56,7 @@
     @Mock private lateinit var mediaTitleWidgetState: WidgetState
     @Mock private lateinit var mediaSubTitleWidgetState: WidgetState
     @Mock private lateinit var mediaContainerWidgetState: WidgetState
+    @Mock private lateinit var mediaFlags: MediaFlags
 
     val delta = 0.1F
 
@@ -64,7 +66,13 @@
     fun setup() {
         MockitoAnnotations.initMocks(this)
         mediaViewController =
-            MediaViewController(context, configurationController, mediaHostStatesManager, logger)
+            MediaViewController(
+                context,
+                configurationController,
+                mediaHostStatesManager,
+                logger,
+                mediaFlags,
+            )
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputAdapterTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputAdapterTest.java
index 77daa3f..5564774 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputAdapterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputAdapterTest.java
@@ -31,6 +31,7 @@
 import android.testing.AndroidTestingRunner;
 import android.view.View;
 import android.widget.LinearLayout;
+import android.widget.SeekBar;
 
 import androidx.core.graphics.drawable.IconCompat;
 import androidx.test.filters.SmallTest;
@@ -45,6 +46,8 @@
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -72,6 +75,8 @@
     private IconCompat mIconCompat = mock(IconCompat.class);
     private View mDialogLaunchView = mock(View.class);
 
+    @Captor
+    private ArgumentCaptor<SeekBar.OnSeekBarChangeListener> mOnSeekBarChangeListenerCaptor;
     private MediaOutputAdapter mMediaOutputAdapter;
     private MediaOutputAdapter.MediaDeviceViewHolder mViewHolder;
     private List<MediaDevice> mMediaDevices = new ArrayList<>();
@@ -352,6 +357,24 @@
     }
 
     @Test
+    public void onBindViewHolder_dragSeekbar_setsVolume() {
+        mOnSeekBarChangeListenerCaptor = ArgumentCaptor.forClass(
+                SeekBar.OnSeekBarChangeListener.class);
+        MediaOutputSeekbar mSpySeekbar = spy(mViewHolder.mSeekBar);
+        mViewHolder.mSeekBar = mSpySeekbar;
+        when(mMediaDevice1.getMaxVolume()).thenReturn(TEST_MAX_VOLUME);
+        when(mMediaDevice1.getCurrentVolume()).thenReturn(TEST_MAX_VOLUME);
+        mMediaOutputAdapter.onBindViewHolder(mViewHolder, 0);
+
+        verify(mViewHolder.mSeekBar).setOnSeekBarChangeListener(
+                mOnSeekBarChangeListenerCaptor.capture());
+
+        mOnSeekBarChangeListenerCaptor.getValue().onStopTrackingTouch(mViewHolder.mSeekBar);
+        assertThat(mViewHolder.mSeekBar.getVisibility()).isEqualTo(View.VISIBLE);
+        verify(mMediaOutputController).logInteractionAdjustVolume(mMediaDevice1);
+    }
+
+    @Test
     public void onBindViewHolder_bindSelectableDevice_verifyView() {
         List<MediaDevice> selectableDevices = new ArrayList<>();
         selectableDevices.add(mMediaDevice2);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputControllerTest.java
index 117751c..7c36e46 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputControllerTest.java
@@ -93,6 +93,11 @@
     private static final String TEST_SONG = "test_song";
     private static final String TEST_SESSION_ID = "test_session_id";
     private static final String TEST_SESSION_NAME = "test_session_name";
+    private final DialogLaunchAnimator mDialogLaunchAnimator = mock(DialogLaunchAnimator.class);
+    private final ActivityLaunchAnimator.Controller mActivityLaunchAnimatorController = mock(
+            ActivityLaunchAnimator.Controller.class);
+    private final NearbyMediaDevicesManager mNearbyMediaDevicesManager = mock(
+            NearbyMediaDevicesManager.class);
     // Mock
     private MediaController mMediaController = mock(MediaController.class);
     private MediaSessionManager mMediaSessionManager = mock(MediaSessionManager.class);
@@ -111,12 +116,7 @@
     private KeyguardManager mKeyguardManager = mock(KeyguardManager.class);
     private PowerExemptionManager mPowerExemptionManager = mock(PowerExemptionManager.class);
     private CommonNotifCollection mNotifCollection = mock(CommonNotifCollection.class);
-    private final DialogLaunchAnimator mDialogLaunchAnimator = mock(DialogLaunchAnimator.class);
     private FeatureFlags mFlags = mock(FeatureFlags.class);
-    private final ActivityLaunchAnimator.Controller mActivityLaunchAnimatorController = mock(
-            ActivityLaunchAnimator.Controller.class);
-    private final NearbyMediaDevicesManager mNearbyMediaDevicesManager = mock(
-            NearbyMediaDevicesManager.class);
     private View mDialogLaunchView = mock(View.class);
     private MediaOutputController.Callback mCallback = mock(MediaOutputController.Callback.class);
 
@@ -522,6 +522,17 @@
     }
 
     @Test
+    public void logInteractionAdjustVolume_triggersFromMetricLogger() {
+        MediaOutputMetricLogger spyMediaOutputMetricLogger = spy(
+                mMediaOutputController.mMetricLogger);
+        mMediaOutputController.mMetricLogger = spyMediaOutputMetricLogger;
+
+        mMediaOutputController.logInteractionAdjustVolume(mMediaDevice1);
+
+        verify(spyMediaOutputMetricLogger).logInteractionAdjustVolume(mMediaDevice1);
+    }
+
+    @Test
     public void getSessionVolumeMax_triggersFromLocalMediaManager() {
         mMediaOutputController.getSessionVolumeMax();
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/devicepolicy/ScreenCaptureDevicePolicyResolverTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/devicepolicy/ScreenCaptureDevicePolicyResolverTest.kt
new file mode 100644
index 0000000..e8b6f9b
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/devicepolicy/ScreenCaptureDevicePolicyResolverTest.kt
@@ -0,0 +1,703 @@
+/*
+ * 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.mediaprojection.devicepolicy
+
+import android.app.admin.DevicePolicyManager
+import android.os.UserHandle
+import android.os.UserManager
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.util.mockito.eq
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertWithMessage
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+import org.junit.runners.Parameterized.Parameters
+import org.mockito.ArgumentMatchers.any
+
+abstract class BaseScreenCaptureDevicePolicyResolverTest(private val precondition: Preconditions) :
+    SysuiTestCase() {
+
+    abstract class Preconditions(
+        val personalScreenCaptureDisabled: Boolean,
+        val workScreenCaptureDisabled: Boolean,
+        val disallowShareIntoManagedProfile: Boolean
+    )
+
+    protected val devicePolicyManager: DevicePolicyManager = mock()
+    protected val userManager: UserManager = mock()
+
+    protected val personalUserHandle: UserHandle = UserHandle.of(123)
+    protected val workUserHandle: UserHandle = UserHandle.of(456)
+
+    protected val policyResolver =
+        ScreenCaptureDevicePolicyResolver(
+            devicePolicyManager,
+            userManager,
+            personalUserHandle,
+            workUserHandle
+        )
+
+    @Before
+    fun setUp() {
+        setUpPolicies()
+    }
+
+    private fun setUpPolicies() {
+        whenever(
+                devicePolicyManager.getScreenCaptureDisabled(
+                    any(),
+                    eq(personalUserHandle.identifier)
+                )
+            )
+            .thenReturn(precondition.personalScreenCaptureDisabled)
+
+        whenever(devicePolicyManager.getScreenCaptureDisabled(any(), eq(workUserHandle.identifier)))
+            .thenReturn(precondition.workScreenCaptureDisabled)
+
+        whenever(
+                userManager.hasUserRestrictionForUser(
+                    eq(UserManager.DISALLOW_SHARE_INTO_MANAGED_PROFILE),
+                    eq(workUserHandle)
+                )
+            )
+            .thenReturn(precondition.disallowShareIntoManagedProfile)
+    }
+}
+
+@RunWith(Parameterized::class)
+@SmallTest
+class IsAllowedScreenCaptureDevicePolicyResolverTest(
+    private val test: IsScreenCaptureAllowedTestCase
+) : BaseScreenCaptureDevicePolicyResolverTest(test.given) {
+
+    companion object {
+        @Parameters(name = "{0}")
+        @JvmStatic
+        fun getParams() =
+            listOf(
+                IsScreenCaptureAllowedTestCase(
+                    given =
+                        Preconditions(
+                            isHostInWorkProfile = false,
+                            isTargetInWorkProfile = false,
+                            personalScreenCaptureDisabled = false,
+                            workScreenCaptureDisabled = false,
+                            disallowShareIntoManagedProfile = false,
+                        ),
+                    expectedScreenCaptureAllowed = true,
+                ),
+                IsScreenCaptureAllowedTestCase(
+                    given =
+                        Preconditions(
+                            isHostInWorkProfile = false,
+                            isTargetInWorkProfile = false,
+                            personalScreenCaptureDisabled = false,
+                            workScreenCaptureDisabled = false,
+                            disallowShareIntoManagedProfile = true,
+                        ),
+                    expectedScreenCaptureAllowed = true,
+                ),
+                IsScreenCaptureAllowedTestCase(
+                    given =
+                        Preconditions(
+                            isHostInWorkProfile = false,
+                            isTargetInWorkProfile = false,
+                            personalScreenCaptureDisabled = false,
+                            workScreenCaptureDisabled = true,
+                            disallowShareIntoManagedProfile = false,
+                        ),
+                    expectedScreenCaptureAllowed = true,
+                ),
+                IsScreenCaptureAllowedTestCase(
+                    given =
+                        Preconditions(
+                            isHostInWorkProfile = false,
+                            isTargetInWorkProfile = false,
+                            personalScreenCaptureDisabled = false,
+                            workScreenCaptureDisabled = true,
+                            disallowShareIntoManagedProfile = true,
+                        ),
+                    expectedScreenCaptureAllowed = true,
+                ),
+                IsScreenCaptureAllowedTestCase(
+                    given =
+                        Preconditions(
+                            isHostInWorkProfile = false,
+                            isTargetInWorkProfile = false,
+                            personalScreenCaptureDisabled = true,
+                            workScreenCaptureDisabled = false,
+                            disallowShareIntoManagedProfile = false
+                        ),
+                    expectedScreenCaptureAllowed = false,
+                ),
+                IsScreenCaptureAllowedTestCase(
+                    given =
+                        Preconditions(
+                            isHostInWorkProfile = false,
+                            isTargetInWorkProfile = false,
+                            personalScreenCaptureDisabled = true,
+                            workScreenCaptureDisabled = false,
+                            disallowShareIntoManagedProfile = true
+                        ),
+                    expectedScreenCaptureAllowed = false,
+                ),
+                IsScreenCaptureAllowedTestCase(
+                    given =
+                        Preconditions(
+                            isHostInWorkProfile = false,
+                            isTargetInWorkProfile = false,
+                            personalScreenCaptureDisabled = true,
+                            workScreenCaptureDisabled = true,
+                            disallowShareIntoManagedProfile = false
+                        ),
+                    expectedScreenCaptureAllowed = false,
+                ),
+                IsScreenCaptureAllowedTestCase(
+                    given =
+                        Preconditions(
+                            isHostInWorkProfile = false,
+                            isTargetInWorkProfile = false,
+                            personalScreenCaptureDisabled = true,
+                            workScreenCaptureDisabled = true,
+                            disallowShareIntoManagedProfile = true
+                        ),
+                    expectedScreenCaptureAllowed = false,
+                ),
+                IsScreenCaptureAllowedTestCase(
+                    given =
+                        Preconditions(
+                            isHostInWorkProfile = false,
+                            isTargetInWorkProfile = true,
+                            personalScreenCaptureDisabled = false,
+                            workScreenCaptureDisabled = false,
+                            disallowShareIntoManagedProfile = false
+                        ),
+                    expectedScreenCaptureAllowed = true,
+                ),
+                IsScreenCaptureAllowedTestCase(
+                    given =
+                        Preconditions(
+                            isHostInWorkProfile = false,
+                            isTargetInWorkProfile = true,
+                            personalScreenCaptureDisabled = false,
+                            workScreenCaptureDisabled = false,
+                            disallowShareIntoManagedProfile = true
+                        ),
+                    expectedScreenCaptureAllowed = true,
+                ),
+                IsScreenCaptureAllowedTestCase(
+                    given =
+                        Preconditions(
+                            isHostInWorkProfile = false,
+                            isTargetInWorkProfile = true,
+                            personalScreenCaptureDisabled = false,
+                            workScreenCaptureDisabled = true,
+                            disallowShareIntoManagedProfile = false
+                        ),
+                    expectedScreenCaptureAllowed = false,
+                ),
+                IsScreenCaptureAllowedTestCase(
+                    given =
+                        Preconditions(
+                            isHostInWorkProfile = false,
+                            isTargetInWorkProfile = true,
+                            personalScreenCaptureDisabled = false,
+                            workScreenCaptureDisabled = true,
+                            disallowShareIntoManagedProfile = true
+                        ),
+                    expectedScreenCaptureAllowed = false,
+                ),
+                IsScreenCaptureAllowedTestCase(
+                    given =
+                        Preconditions(
+                            isHostInWorkProfile = false,
+                            isTargetInWorkProfile = true,
+                            personalScreenCaptureDisabled = true,
+                            workScreenCaptureDisabled = false,
+                            disallowShareIntoManagedProfile = false
+                        ),
+                    expectedScreenCaptureAllowed = false,
+                ),
+                IsScreenCaptureAllowedTestCase(
+                    given =
+                        Preconditions(
+                            isHostInWorkProfile = false,
+                            isTargetInWorkProfile = true,
+                            personalScreenCaptureDisabled = true,
+                            workScreenCaptureDisabled = false,
+                            disallowShareIntoManagedProfile = true
+                        ),
+                    expectedScreenCaptureAllowed = false,
+                ),
+                IsScreenCaptureAllowedTestCase(
+                    given =
+                        Preconditions(
+                            isHostInWorkProfile = false,
+                            isTargetInWorkProfile = true,
+                            personalScreenCaptureDisabled = true,
+                            workScreenCaptureDisabled = true,
+                            disallowShareIntoManagedProfile = false
+                        ),
+                    expectedScreenCaptureAllowed = false,
+                ),
+                IsScreenCaptureAllowedTestCase(
+                    given =
+                        Preconditions(
+                            isHostInWorkProfile = false,
+                            isTargetInWorkProfile = true,
+                            personalScreenCaptureDisabled = true,
+                            workScreenCaptureDisabled = true,
+                            disallowShareIntoManagedProfile = true
+                        ),
+                    expectedScreenCaptureAllowed = false,
+                ),
+                IsScreenCaptureAllowedTestCase(
+                    given =
+                        Preconditions(
+                            isHostInWorkProfile = true,
+                            isTargetInWorkProfile = false,
+                            personalScreenCaptureDisabled = false,
+                            workScreenCaptureDisabled = false,
+                            disallowShareIntoManagedProfile = false
+                        ),
+                    expectedScreenCaptureAllowed = true,
+                ),
+                IsScreenCaptureAllowedTestCase(
+                    given =
+                        Preconditions(
+                            isHostInWorkProfile = true,
+                            isTargetInWorkProfile = false,
+                            personalScreenCaptureDisabled = false,
+                            workScreenCaptureDisabled = false,
+                            disallowShareIntoManagedProfile = true
+                        ),
+                    expectedScreenCaptureAllowed = false,
+                ),
+                IsScreenCaptureAllowedTestCase(
+                    given =
+                        Preconditions(
+                            isHostInWorkProfile = true,
+                            isTargetInWorkProfile = false,
+                            personalScreenCaptureDisabled = false,
+                            workScreenCaptureDisabled = true,
+                            disallowShareIntoManagedProfile = false
+                        ),
+                    expectedScreenCaptureAllowed = false,
+                ),
+                IsScreenCaptureAllowedTestCase(
+                    given =
+                        Preconditions(
+                            isHostInWorkProfile = true,
+                            isTargetInWorkProfile = false,
+                            personalScreenCaptureDisabled = false,
+                            workScreenCaptureDisabled = true,
+                            disallowShareIntoManagedProfile = true
+                        ),
+                    expectedScreenCaptureAllowed = false,
+                ),
+                IsScreenCaptureAllowedTestCase(
+                    given =
+                        Preconditions(
+                            isHostInWorkProfile = true,
+                            isTargetInWorkProfile = false,
+                            personalScreenCaptureDisabled = true,
+                            workScreenCaptureDisabled = false,
+                            disallowShareIntoManagedProfile = false
+                        ),
+                    expectedScreenCaptureAllowed = false,
+                ),
+                IsScreenCaptureAllowedTestCase(
+                    given =
+                        Preconditions(
+                            isHostInWorkProfile = true,
+                            isTargetInWorkProfile = false,
+                            personalScreenCaptureDisabled = true,
+                            workScreenCaptureDisabled = false,
+                            disallowShareIntoManagedProfile = true
+                        ),
+                    expectedScreenCaptureAllowed = false,
+                ),
+                IsScreenCaptureAllowedTestCase(
+                    given =
+                        Preconditions(
+                            isHostInWorkProfile = true,
+                            isTargetInWorkProfile = false,
+                            personalScreenCaptureDisabled = true,
+                            workScreenCaptureDisabled = true,
+                            disallowShareIntoManagedProfile = false
+                        ),
+                    expectedScreenCaptureAllowed = false,
+                ),
+                IsScreenCaptureAllowedTestCase(
+                    given =
+                        Preconditions(
+                            isHostInWorkProfile = true,
+                            isTargetInWorkProfile = false,
+                            personalScreenCaptureDisabled = true,
+                            workScreenCaptureDisabled = true,
+                            disallowShareIntoManagedProfile = true
+                        ),
+                    expectedScreenCaptureAllowed = false,
+                ),
+                IsScreenCaptureAllowedTestCase(
+                    given =
+                        Preconditions(
+                            isHostInWorkProfile = true,
+                            isTargetInWorkProfile = true,
+                            personalScreenCaptureDisabled = false,
+                            workScreenCaptureDisabled = false,
+                            disallowShareIntoManagedProfile = false
+                        ),
+                    expectedScreenCaptureAllowed = true,
+                ),
+                IsScreenCaptureAllowedTestCase(
+                    given =
+                        Preconditions(
+                            isHostInWorkProfile = true,
+                            isTargetInWorkProfile = true,
+                            personalScreenCaptureDisabled = false,
+                            workScreenCaptureDisabled = false,
+                            disallowShareIntoManagedProfile = true
+                        ),
+                    expectedScreenCaptureAllowed = true,
+                ),
+                IsScreenCaptureAllowedTestCase(
+                    given =
+                        Preconditions(
+                            isHostInWorkProfile = true,
+                            isTargetInWorkProfile = true,
+                            personalScreenCaptureDisabled = false,
+                            workScreenCaptureDisabled = true,
+                            disallowShareIntoManagedProfile = false
+                        ),
+                    expectedScreenCaptureAllowed = false,
+                ),
+                IsScreenCaptureAllowedTestCase(
+                    given =
+                        Preconditions(
+                            isHostInWorkProfile = true,
+                            isTargetInWorkProfile = true,
+                            personalScreenCaptureDisabled = false,
+                            workScreenCaptureDisabled = true,
+                            disallowShareIntoManagedProfile = true
+                        ),
+                    expectedScreenCaptureAllowed = false,
+                ),
+                IsScreenCaptureAllowedTestCase(
+                    given =
+                        Preconditions(
+                            isHostInWorkProfile = true,
+                            isTargetInWorkProfile = true,
+                            personalScreenCaptureDisabled = true,
+                            workScreenCaptureDisabled = false,
+                            disallowShareIntoManagedProfile = false
+                        ),
+                    expectedScreenCaptureAllowed = true,
+                ),
+                IsScreenCaptureAllowedTestCase(
+                    given =
+                        Preconditions(
+                            isHostInWorkProfile = true,
+                            isTargetInWorkProfile = true,
+                            personalScreenCaptureDisabled = true,
+                            workScreenCaptureDisabled = false,
+                            disallowShareIntoManagedProfile = true
+                        ),
+                    expectedScreenCaptureAllowed = true,
+                ),
+                IsScreenCaptureAllowedTestCase(
+                    given =
+                        Preconditions(
+                            isHostInWorkProfile = true,
+                            isTargetInWorkProfile = true,
+                            personalScreenCaptureDisabled = true,
+                            workScreenCaptureDisabled = true,
+                            disallowShareIntoManagedProfile = false
+                        ),
+                    expectedScreenCaptureAllowed = false,
+                ),
+                IsScreenCaptureAllowedTestCase(
+                    given =
+                        Preconditions(
+                            isHostInWorkProfile = true,
+                            isTargetInWorkProfile = true,
+                            personalScreenCaptureDisabled = true,
+                            workScreenCaptureDisabled = true,
+                            disallowShareIntoManagedProfile = true
+                        ),
+                    expectedScreenCaptureAllowed = false,
+                ),
+            )
+    }
+
+    class Preconditions(
+        personalScreenCaptureDisabled: Boolean,
+        workScreenCaptureDisabled: Boolean,
+        disallowShareIntoManagedProfile: Boolean,
+        val isHostInWorkProfile: Boolean,
+        val isTargetInWorkProfile: Boolean,
+    ) :
+        BaseScreenCaptureDevicePolicyResolverTest.Preconditions(
+            personalScreenCaptureDisabled,
+            workScreenCaptureDisabled,
+            disallowShareIntoManagedProfile
+        )
+
+    data class IsScreenCaptureAllowedTestCase(
+        val given: Preconditions,
+        val expectedScreenCaptureAllowed: Boolean
+    ) {
+        override fun toString(): String =
+            "isScreenCaptureAllowed: " +
+                "host[${if (given.isHostInWorkProfile) "work" else "personal"} profile], " +
+                "target[${if (given.isTargetInWorkProfile) "work" else "personal"} profile], " +
+                "personal screen capture disabled = ${given.personalScreenCaptureDisabled}, " +
+                "work screen capture disabled = ${given.workScreenCaptureDisabled}, " +
+                "disallow share into managed profile = ${given.disallowShareIntoManagedProfile}, " +
+                "expected screen capture allowed = $expectedScreenCaptureAllowed"
+    }
+
+    @Test
+    fun test() {
+        val targetAppUserHandle =
+            if (test.given.isTargetInWorkProfile) workUserHandle else personalUserHandle
+        val hostAppUserHandle =
+            if (test.given.isHostInWorkProfile) workUserHandle else personalUserHandle
+
+        val screenCaptureAllowed =
+            policyResolver.isScreenCaptureAllowed(targetAppUserHandle, hostAppUserHandle)
+
+        assertWithMessage("Screen capture policy resolved incorrectly")
+            .that(screenCaptureAllowed)
+            .isEqualTo(test.expectedScreenCaptureAllowed)
+    }
+}
+
+@RunWith(Parameterized::class)
+@SmallTest
+class IsCompletelyNotAllowedScreenCaptureDevicePolicyResolverTest(
+    private val test: IsScreenCaptureCompletelyDisabledTestCase
+) : BaseScreenCaptureDevicePolicyResolverTest(test.given) {
+
+    companion object {
+        @Parameters(name = "{0}")
+        @JvmStatic
+        fun getParams() =
+            listOf(
+                IsScreenCaptureCompletelyDisabledTestCase(
+                    given =
+                        Preconditions(
+                            isHostInWorkProfile = false,
+                            personalScreenCaptureDisabled = false,
+                            workScreenCaptureDisabled = false,
+                            disallowShareIntoManagedProfile = false
+                        ),
+                    expectedScreenCaptureCompletelyDisabled = false,
+                ),
+                IsScreenCaptureCompletelyDisabledTestCase(
+                    given =
+                        Preconditions(
+                            isHostInWorkProfile = false,
+                            personalScreenCaptureDisabled = false,
+                            workScreenCaptureDisabled = false,
+                            disallowShareIntoManagedProfile = true
+                        ),
+                    expectedScreenCaptureCompletelyDisabled = false,
+                ),
+                IsScreenCaptureCompletelyDisabledTestCase(
+                    given =
+                        Preconditions(
+                            isHostInWorkProfile = false,
+                            personalScreenCaptureDisabled = false,
+                            workScreenCaptureDisabled = true,
+                            disallowShareIntoManagedProfile = false
+                        ),
+                    expectedScreenCaptureCompletelyDisabled = false,
+                ),
+                IsScreenCaptureCompletelyDisabledTestCase(
+                    given =
+                        Preconditions(
+                            isHostInWorkProfile = false,
+                            personalScreenCaptureDisabled = false,
+                            workScreenCaptureDisabled = true,
+                            disallowShareIntoManagedProfile = true
+                        ),
+                    expectedScreenCaptureCompletelyDisabled = false,
+                ),
+                IsScreenCaptureCompletelyDisabledTestCase(
+                    given =
+                        Preconditions(
+                            isHostInWorkProfile = false,
+                            personalScreenCaptureDisabled = true,
+                            workScreenCaptureDisabled = false,
+                            disallowShareIntoManagedProfile = false
+                        ),
+                    expectedScreenCaptureCompletelyDisabled = true,
+                ),
+                IsScreenCaptureCompletelyDisabledTestCase(
+                    given =
+                        Preconditions(
+                            isHostInWorkProfile = false,
+                            personalScreenCaptureDisabled = true,
+                            workScreenCaptureDisabled = false,
+                            disallowShareIntoManagedProfile = true
+                        ),
+                    expectedScreenCaptureCompletelyDisabled = true,
+                ),
+                IsScreenCaptureCompletelyDisabledTestCase(
+                    given =
+                        Preconditions(
+                            isHostInWorkProfile = false,
+                            personalScreenCaptureDisabled = true,
+                            workScreenCaptureDisabled = true,
+                            disallowShareIntoManagedProfile = false
+                        ),
+                    expectedScreenCaptureCompletelyDisabled = true,
+                ),
+                IsScreenCaptureCompletelyDisabledTestCase(
+                    given =
+                        Preconditions(
+                            isHostInWorkProfile = false,
+                            personalScreenCaptureDisabled = true,
+                            workScreenCaptureDisabled = true,
+                            disallowShareIntoManagedProfile = true
+                        ),
+                    expectedScreenCaptureCompletelyDisabled = true,
+                ),
+                IsScreenCaptureCompletelyDisabledTestCase(
+                    given =
+                        Preconditions(
+                            isHostInWorkProfile = true,
+                            personalScreenCaptureDisabled = false,
+                            workScreenCaptureDisabled = false,
+                            disallowShareIntoManagedProfile = false
+                        ),
+                    expectedScreenCaptureCompletelyDisabled = false,
+                ),
+                IsScreenCaptureCompletelyDisabledTestCase(
+                    given =
+                        Preconditions(
+                            isHostInWorkProfile = true,
+                            personalScreenCaptureDisabled = false,
+                            workScreenCaptureDisabled = false,
+                            disallowShareIntoManagedProfile = true
+                        ),
+                    expectedScreenCaptureCompletelyDisabled = false,
+                ),
+                IsScreenCaptureCompletelyDisabledTestCase(
+                    given =
+                        Preconditions(
+                            isHostInWorkProfile = true,
+                            personalScreenCaptureDisabled = false,
+                            workScreenCaptureDisabled = true,
+                            disallowShareIntoManagedProfile = false
+                        ),
+                    expectedScreenCaptureCompletelyDisabled = true,
+                ),
+                IsScreenCaptureCompletelyDisabledTestCase(
+                    given =
+                        Preconditions(
+                            isHostInWorkProfile = true,
+                            personalScreenCaptureDisabled = false,
+                            workScreenCaptureDisabled = true,
+                            disallowShareIntoManagedProfile = true
+                        ),
+                    expectedScreenCaptureCompletelyDisabled = true,
+                ),
+                IsScreenCaptureCompletelyDisabledTestCase(
+                    given =
+                        Preconditions(
+                            isHostInWorkProfile = true,
+                            personalScreenCaptureDisabled = true,
+                            workScreenCaptureDisabled = false,
+                            disallowShareIntoManagedProfile = false
+                        ),
+                    expectedScreenCaptureCompletelyDisabled = false,
+                ),
+                IsScreenCaptureCompletelyDisabledTestCase(
+                    given =
+                        Preconditions(
+                            isHostInWorkProfile = true,
+                            personalScreenCaptureDisabled = true,
+                            workScreenCaptureDisabled = false,
+                            disallowShareIntoManagedProfile = true
+                        ),
+                    expectedScreenCaptureCompletelyDisabled = false,
+                ),
+                IsScreenCaptureCompletelyDisabledTestCase(
+                    given =
+                        Preconditions(
+                            isHostInWorkProfile = true,
+                            personalScreenCaptureDisabled = true,
+                            workScreenCaptureDisabled = true,
+                            disallowShareIntoManagedProfile = false
+                        ),
+                    expectedScreenCaptureCompletelyDisabled = true,
+                ),
+                IsScreenCaptureCompletelyDisabledTestCase(
+                    given =
+                        Preconditions(
+                            isHostInWorkProfile = true,
+                            personalScreenCaptureDisabled = true,
+                            workScreenCaptureDisabled = true,
+                            disallowShareIntoManagedProfile = true
+                        ),
+                    expectedScreenCaptureCompletelyDisabled = true,
+                )
+            )
+    }
+
+    class Preconditions(
+        personalScreenCaptureDisabled: Boolean,
+        workScreenCaptureDisabled: Boolean,
+        disallowShareIntoManagedProfile: Boolean,
+        val isHostInWorkProfile: Boolean,
+    ) :
+        BaseScreenCaptureDevicePolicyResolverTest.Preconditions(
+            personalScreenCaptureDisabled,
+            workScreenCaptureDisabled,
+            disallowShareIntoManagedProfile
+        )
+
+    data class IsScreenCaptureCompletelyDisabledTestCase(
+        val given: Preconditions,
+        val expectedScreenCaptureCompletelyDisabled: Boolean
+    ) {
+        override fun toString(): String =
+            "isScreenCaptureCompletelyDisabled: " +
+                "host[${if (given.isHostInWorkProfile) "work" else "personal"} profile], " +
+                "personal screen capture disabled = ${given.personalScreenCaptureDisabled}, " +
+                "work screen capture disabled = ${given.workScreenCaptureDisabled}, " +
+                "disallow share into managed profile = ${given.disallowShareIntoManagedProfile}, " +
+                "expected screen capture completely disabled = $expectedScreenCaptureCompletelyDisabled"
+    }
+
+    @Test
+    fun test() {
+        val hostAppUserHandle =
+            if (test.given.isHostInWorkProfile) workUserHandle else personalUserHandle
+
+        val completelyDisabled = policyResolver.isScreenCaptureCompletelyDisabled(hostAppUserHandle)
+
+        assertWithMessage("Screen capture policy resolved incorrectly")
+            .that(completelyDisabled)
+            .isEqualTo(test.expectedScreenCaptureCompletelyDisabled)
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/gestural/FloatingRotationButtonPositionCalculatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/gestural/FloatingRotationButtonPositionCalculatorTest.kt
index 36e02cb..bc67df6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/gestural/FloatingRotationButtonPositionCalculatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/gestural/FloatingRotationButtonPositionCalculatorTest.kt
@@ -16,40 +16,50 @@
 internal class FloatingRotationButtonPositionCalculatorTest(private val testCase: TestCase)
     : SysuiTestCase() {
 
-    private val calculator = FloatingRotationButtonPositionCalculator(
-        MARGIN_DEFAULT, MARGIN_TASKBAR_LEFT, MARGIN_TASKBAR_BOTTOM
-    )
-
     @Test
     fun calculatePosition() {
-        val position = calculator.calculatePosition(
+        val position = testCase.calculator.calculatePosition(
             testCase.rotation,
             testCase.taskbarVisible,
             testCase.taskbarStashed
         )
-
         assertThat(position).isEqualTo(testCase.expectedPosition)
     }
 
     internal class TestCase(
+        val calculator: FloatingRotationButtonPositionCalculator,
         val rotation: Int,
         val taskbarVisible: Boolean,
         val taskbarStashed: Boolean,
         val expectedPosition: Position
     ) {
         override fun toString(): String =
-            "when rotation = $rotation, " +
+            "when calculator = $calculator, " +
+                "rotation = $rotation, " +
                 "taskbarVisible = $taskbarVisible, " +
                 "taskbarStashed = $taskbarStashed - " +
                 "expected $expectedPosition"
     }
 
     companion object {
+        private const val MARGIN_DEFAULT = 10
+        private const val MARGIN_TASKBAR_LEFT = 20
+        private const val MARGIN_TASKBAR_BOTTOM = 30
+
+        private val posLeftCalculator = FloatingRotationButtonPositionCalculator(
+            MARGIN_DEFAULT, MARGIN_TASKBAR_LEFT, MARGIN_TASKBAR_BOTTOM, true
+        )
+        private val posRightCalculator = FloatingRotationButtonPositionCalculator(
+            MARGIN_DEFAULT, MARGIN_TASKBAR_LEFT, MARGIN_TASKBAR_BOTTOM, false
+        )
+
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
         fun getParams(): Collection<TestCase> =
             listOf(
+                // Position left
                 TestCase(
+                    calculator = posLeftCalculator,
                     rotation = Surface.ROTATION_0,
                     taskbarVisible = false,
                     taskbarStashed = false,
@@ -60,6 +70,7 @@
                     )
                 ),
                 TestCase(
+                    calculator = posLeftCalculator,
                     rotation = Surface.ROTATION_90,
                     taskbarVisible = false,
                     taskbarStashed = false,
@@ -70,6 +81,7 @@
                     )
                 ),
                 TestCase(
+                    calculator = posLeftCalculator,
                     rotation = Surface.ROTATION_180,
                     taskbarVisible = false,
                     taskbarStashed = false,
@@ -80,6 +92,7 @@
                     )
                 ),
                 TestCase(
+                    calculator = posLeftCalculator,
                     rotation = Surface.ROTATION_270,
                     taskbarVisible = false,
                     taskbarStashed = false,
@@ -90,6 +103,7 @@
                     )
                 ),
                 TestCase(
+                    calculator = posLeftCalculator,
                     rotation = Surface.ROTATION_0,
                     taskbarVisible = true,
                     taskbarStashed = false,
@@ -100,6 +114,7 @@
                     )
                 ),
                 TestCase(
+                    calculator = posLeftCalculator,
                     rotation = Surface.ROTATION_0,
                     taskbarVisible = true,
                     taskbarStashed = true,
@@ -110,6 +125,7 @@
                     )
                 ),
                 TestCase(
+                    calculator = posLeftCalculator,
                     rotation = Surface.ROTATION_90,
                     taskbarVisible = true,
                     taskbarStashed = false,
@@ -118,11 +134,86 @@
                         translationX = -MARGIN_TASKBAR_LEFT,
                         translationY = -MARGIN_TASKBAR_BOTTOM
                     )
+                ),
+
+                // Position right
+                TestCase(
+                    calculator = posRightCalculator,
+                    rotation = Surface.ROTATION_0,
+                    taskbarVisible = false,
+                    taskbarStashed = false,
+                    expectedPosition = Position(
+                        gravity = Gravity.BOTTOM or Gravity.RIGHT,
+                        translationX = -MARGIN_DEFAULT,
+                        translationY = -MARGIN_DEFAULT
+                    )
+                ),
+                TestCase(
+                    calculator = posRightCalculator,
+                    rotation = Surface.ROTATION_90,
+                    taskbarVisible = false,
+                    taskbarStashed = false,
+                    expectedPosition = Position(
+                        gravity = Gravity.TOP or Gravity.RIGHT,
+                        translationX = -MARGIN_DEFAULT,
+                        translationY = MARGIN_DEFAULT
+                    )
+                ),
+                TestCase(
+                    calculator = posRightCalculator,
+                    rotation = Surface.ROTATION_180,
+                    taskbarVisible = false,
+                    taskbarStashed = false,
+                    expectedPosition = Position(
+                        gravity = Gravity.TOP or Gravity.LEFT,
+                        translationX = MARGIN_DEFAULT,
+                        translationY = MARGIN_DEFAULT
+                    )
+                ),
+                TestCase(
+                    calculator = posRightCalculator,
+                    rotation = Surface.ROTATION_270,
+                    taskbarVisible = false,
+                    taskbarStashed = false,
+                    expectedPosition = Position(
+                        gravity = Gravity.BOTTOM or Gravity.LEFT,
+                        translationX = MARGIN_DEFAULT,
+                        translationY = -MARGIN_DEFAULT
+                    )
+                ),
+                TestCase(
+                    calculator = posRightCalculator,
+                    rotation = Surface.ROTATION_0,
+                    taskbarVisible = true,
+                    taskbarStashed = false,
+                    expectedPosition = Position(
+                        gravity = Gravity.BOTTOM or Gravity.RIGHT,
+                        translationX = -MARGIN_TASKBAR_LEFT,
+                        translationY = -MARGIN_TASKBAR_BOTTOM
+                    )
+                ),
+                TestCase(
+                    calculator = posRightCalculator,
+                    rotation = Surface.ROTATION_0,
+                    taskbarVisible = true,
+                    taskbarStashed = true,
+                    expectedPosition = Position(
+                        gravity = Gravity.BOTTOM or Gravity.RIGHT,
+                        translationX = -MARGIN_DEFAULT,
+                        translationY = -MARGIN_DEFAULT
+                    )
+                ),
+                TestCase(
+                    calculator = posRightCalculator,
+                    rotation = Surface.ROTATION_90,
+                    taskbarVisible = true,
+                    taskbarStashed = false,
+                    expectedPosition = Position(
+                        gravity = Gravity.TOP or Gravity.RIGHT,
+                        translationX = -MARGIN_TASKBAR_LEFT,
+                        translationY = MARGIN_TASKBAR_BOTTOM
+                    )
                 )
             )
-
-        private const val MARGIN_DEFAULT = 10
-        private const val MARGIN_TASKBAR_LEFT = 20
-        private const val MARGIN_TASKBAR_BOTTOM = 30
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/external/CustomTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/external/CustomTileTest.kt
index a7733a2..d6dfc85 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/external/CustomTileTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/external/CustomTileTest.kt
@@ -35,6 +35,7 @@
 import com.android.internal.logging.MetricsLogger
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.animation.ActivityLaunchAnimator
+import com.android.systemui.animation.LaunchableFrameLayout
 import com.android.systemui.classifier.FalsingManagerFake
 import com.android.systemui.plugins.ActivityStarter
 import com.android.systemui.plugins.qs.QSTile
@@ -342,7 +343,7 @@
         val tile = CustomTile.create(customTileBuilder, TILE_SPEC, mContext)
         tile.qsTile.activityLaunchForClick = pi
 
-        tile.handleClick(mock(View::class.java))
+        tile.handleClick(mock(LaunchableFrameLayout::class.java))
 
         testableLooper.processAllMessages()
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
index 4c76825..e5d5e3b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
@@ -45,13 +45,16 @@
 import com.android.systemui.statusbar.phone.PhoneStatusBarViewController
 import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager
 import com.android.systemui.statusbar.window.StatusBarWindowStateController
+import com.android.systemui.util.mockito.any
 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
 import org.mockito.Mockito.anyFloat
+import org.mockito.Mockito.mock
 import org.mockito.Mockito.never
 import org.mockito.Mockito.verify
 import org.mockito.Mockito.`when` as whenever
@@ -102,7 +105,6 @@
     @Mock
     private lateinit var alternateBouncerInteractor: AlternateBouncerInteractor
     @Mock lateinit var keyguardBouncerComponentFactory: KeyguardBouncerComponent.Factory
-    @Mock lateinit var keyguardBouncerContainer: ViewGroup
     @Mock lateinit var keyguardBouncerComponent: KeyguardBouncerComponent
     @Mock lateinit var keyguardHostViewController: KeyguardHostViewController
     @Mock lateinit var keyguardTransitionInteractor: KeyguardTransitionInteractor
@@ -116,6 +118,12 @@
     fun setUp() {
         MockitoAnnotations.initMocks(this)
         whenever(view.bottom).thenReturn(VIEW_BOTTOM)
+        whenever(view.findViewById<ViewGroup>(R.id.keyguard_bouncer_container))
+                .thenReturn(mock(ViewGroup::class.java))
+        whenever(keyguardBouncerComponentFactory.create(any(ViewGroup::class.java)))
+                .thenReturn(keyguardBouncerComponent)
+        whenever(keyguardBouncerComponent.keyguardHostViewController)
+                .thenReturn(keyguardHostViewController)
         underTest = NotificationShadeWindowViewController(
             lockscreenShadeTransitionController,
             FalsingCollectorFake(),
@@ -275,6 +283,7 @@
 
     @Test
     fun testGetBouncerContainer() {
+        Mockito.clearInvocations(view)
         underTest.bouncerContainer
         verify(view).findViewById<ViewGroup>(R.id.keyguard_bouncer_container)
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.java
index d435624..5cc3ef1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.java
@@ -29,9 +29,11 @@
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 import android.view.MotionEvent;
+import android.view.ViewGroup;
 
 import androidx.test.filters.SmallTest;
 
+import com.android.keyguard.KeyguardHostViewController;
 import com.android.keyguard.LockIconViewController;
 import com.android.keyguard.dagger.KeyguardBouncerComponent;
 import com.android.systemui.R;
@@ -94,6 +96,8 @@
     @Mock private FeatureFlags mFeatureFlags;
     @Mock private KeyguardBouncerViewModel mKeyguardBouncerViewModel;
     @Mock private KeyguardBouncerComponent.Factory mKeyguardBouncerComponentFactory;
+    @Mock private KeyguardBouncerComponent mKeyguardBouncerComponent;
+    @Mock private KeyguardHostViewController mKeyguardHostViewController;
     @Mock private NotificationInsetsController mNotificationInsetsController;
     @Mock private AlternateBouncerInteractor mAlternateBouncerInteractor;
     @Mock private KeyguardTransitionInteractor mKeyguardTransitionInteractor;
@@ -110,6 +114,12 @@
         when(mView.findViewById(R.id.notification_stack_scroller))
                 .thenReturn(mNotificationStackScrollLayout);
 
+        when(mView.findViewById(R.id.keyguard_bouncer_container)).thenReturn(mock(ViewGroup.class));
+        when(mKeyguardBouncerComponentFactory.create(any(ViewGroup.class))).thenReturn(
+                mKeyguardBouncerComponent);
+        when(mKeyguardBouncerComponent.getKeyguardHostViewController()).thenReturn(
+                mKeyguardHostViewController);
+
         when(mStatusBarStateController.isDozing()).thenReturn(false);
         mDependency.injectTestDependency(ShadeController.class, mShadeController);
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/DeviceProvisionedCoordinatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/DeviceProvisionedCoordinatorTest.java
index 701cf95..af52459 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/DeviceProvisionedCoordinatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/DeviceProvisionedCoordinatorTest.java
@@ -117,10 +117,6 @@
         extras.putBoolean(SHOW_WHEN_UNPROVISIONED_FLAG, true);
         mNotification.extras = extras;
 
-        // GIVEN notification has the permission to display during setup
-        when(mIPackageManager.checkUidPermission(SETUP_NOTIF_PERMISSION, NOTIF_UID))
-                .thenReturn(PackageManager.PERMISSION_GRANTED);
-
         // THEN don't filter out the notification
         assertFalse(mDeviceProvisionedFilter.shouldFilterOut(mEntry, 0));
     }
@@ -130,15 +126,10 @@
         // GIVEN device is unprovisioned
         when(mDeviceProvisionedController.isDeviceProvisioned()).thenReturn(false);
 
-        // GIVEN notification has a flag to allow the notification during setup
+        // GIVEN notification does not have the flag to allow the notification during setup
         Bundle extras = new Bundle();
-        extras.putBoolean(SHOW_WHEN_UNPROVISIONED_FLAG, true);
         mNotification.extras = extras;
 
-        // GIVEN notification does NOT have permission to display during setup
-        when(mIPackageManager.checkUidPermission(SETUP_NOTIF_PERMISSION, NOTIF_UID))
-                .thenReturn(PackageManager.PERMISSION_DENIED);
-
         // THEN filter out the notification
         assertTrue(mDeviceProvisionedFilter.shouldFilterOut(mEntry, 0));
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java
index 4559a23..9e23d54 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java
@@ -82,19 +82,14 @@
 
 import java.util.Arrays;
 import java.util.List;
+import java.util.function.Consumer;
 
 @SmallTest
 @RunWith(AndroidTestingRunner.class)
 @RunWithLooper
 public class ExpandableNotificationRowTest extends SysuiTestCase {
 
-    private ExpandableNotificationRow mGroupRow;
-    private ExpandableNotificationRow mNotifRow;
-    private ExpandableNotificationRow mPublicRow;
-
     private NotificationTestHelper mNotificationTestHelper;
-    boolean mHeadsUpAnimatingAway = false;
-
     @Rule public MockitoRule mockito = MockitoJUnit.rule();
 
     @Before
@@ -105,112 +100,108 @@
                 mDependency,
                 TestableLooper.get(this));
         mNotificationTestHelper.setDefaultInflationFlags(FLAG_CONTENT_VIEW_ALL);
+
         FakeFeatureFlags fakeFeatureFlags = new FakeFeatureFlags();
         fakeFeatureFlags.set(Flags.NOTIFICATION_ANIMATE_BIG_PICTURE, true);
         mNotificationTestHelper.setFeatureFlags(fakeFeatureFlags);
-        // create a standard private notification row
-        Notification normalNotif = mNotificationTestHelper.createNotification();
-        normalNotif.publicVersion = null;
-        mNotifRow = mNotificationTestHelper.createRow(normalNotif);
+    }
+
+    @Test
+    public void testUpdateBackgroundColors_isRecursive() throws Exception {
+        ExpandableNotificationRow group = mNotificationTestHelper.createGroup();
+        group.setTintColor(Color.RED);
+        group.getChildNotificationAt(0).setTintColor(Color.GREEN);
+        group.getChildNotificationAt(1).setTintColor(Color.BLUE);
+
+        assertThat(group.getCurrentBackgroundTint()).isEqualTo(Color.RED);
+        assertThat(group.getChildNotificationAt(0).getCurrentBackgroundTint())
+                .isEqualTo(Color.GREEN);
+        assertThat(group.getChildNotificationAt(1).getCurrentBackgroundTint())
+                .isEqualTo(Color.BLUE);
+
+        group.updateBackgroundColors();
+
+        int resetTint = group.getCurrentBackgroundTint();
+        assertThat(resetTint).isNotEqualTo(Color.RED);
+        assertThat(group.getChildNotificationAt(0).getCurrentBackgroundTint())
+                .isEqualTo(resetTint);
+        assertThat(group.getChildNotificationAt(1).getCurrentBackgroundTint())
+                .isEqualTo(resetTint);
+    }
+
+    @Test
+    public void testSetSensitiveOnNotifRowNotifiesOfHeightChange() throws Exception {
+        // GIVEN a sensitive notification row that's currently redacted
+        ExpandableNotificationRow row = mNotificationTestHelper.createRow();
+        measureAndLayout(row);
+        row.setHideSensitiveForIntrinsicHeight(true);
+        row.setSensitive(true, true);
+        assertThat(row.getShowingLayout()).isSameInstanceAs(row.getPublicLayout());
+        assertThat(row.getIntrinsicHeight()).isGreaterThan(0);
+
+        // GIVEN that the row has a height change listener
+        OnHeightChangedListener listener = mock(OnHeightChangedListener.class);
+        row.setOnHeightChangedListener(listener);
+
+        // WHEN the row is set to no longer be sensitive
+        row.setSensitive(false, true);
+
+        // VERIFY that the height change listener is invoked
+        assertThat(row.getShowingLayout()).isSameInstanceAs(row.getPrivateLayout());
+        assertThat(row.getIntrinsicHeight()).isGreaterThan(0);
+        verify(listener).onHeightChanged(eq(row), eq(false));
+    }
+
+    @Test
+    public void testSetSensitiveOnGroupRowNotifiesOfHeightChange() throws Exception {
+        // GIVEN a sensitive group row that's currently redacted
+        ExpandableNotificationRow group = mNotificationTestHelper.createGroup();
+        measureAndLayout(group);
+        group.setHideSensitiveForIntrinsicHeight(true);
+        group.setSensitive(true, true);
+        assertThat(group.getShowingLayout()).isSameInstanceAs(group.getPublicLayout());
+        assertThat(group.getIntrinsicHeight()).isGreaterThan(0);
+
+        // GIVEN that the row has a height change listener
+        OnHeightChangedListener listener = mock(OnHeightChangedListener.class);
+        group.setOnHeightChangedListener(listener);
+
+        // WHEN the row is set to no longer be sensitive
+        group.setSensitive(false, true);
+
+        // VERIFY that the height change listener is invoked
+        assertThat(group.getShowingLayout()).isSameInstanceAs(group.getPrivateLayout());
+        assertThat(group.getIntrinsicHeight()).isGreaterThan(0);
+        verify(listener).onHeightChanged(eq(group), eq(false));
+    }
+
+    @Test
+    public void testSetSensitiveOnPublicRowDoesNotNotifyOfHeightChange() throws Exception {
         // create a notification row whose public version is identical
         Notification publicNotif = mNotificationTestHelper.createNotification();
         publicNotif.publicVersion = mNotificationTestHelper.createNotification();
-        mPublicRow = mNotificationTestHelper.createRow(publicNotif);
-        // create a group row
-        mGroupRow = mNotificationTestHelper.createGroup();
-        mGroupRow.setHeadsUpAnimatingAwayListener(
-                animatingAway -> mHeadsUpAnimatingAway = animatingAway);
+        ExpandableNotificationRow publicRow = mNotificationTestHelper.createRow(publicNotif);
 
-    }
-
-    @Test
-    public void testUpdateBackgroundColors_isRecursive() {
-        mGroupRow.setTintColor(Color.RED);
-        mGroupRow.getChildNotificationAt(0).setTintColor(Color.GREEN);
-        mGroupRow.getChildNotificationAt(1).setTintColor(Color.BLUE);
-
-        assertThat(mGroupRow.getCurrentBackgroundTint()).isEqualTo(Color.RED);
-        assertThat(mGroupRow.getChildNotificationAt(0).getCurrentBackgroundTint())
-                .isEqualTo(Color.GREEN);
-        assertThat(mGroupRow.getChildNotificationAt(1).getCurrentBackgroundTint())
-                .isEqualTo(Color.BLUE);
-
-        mGroupRow.updateBackgroundColors();
-
-        int resetTint = mGroupRow.getCurrentBackgroundTint();
-        assertThat(resetTint).isNotEqualTo(Color.RED);
-        assertThat(mGroupRow.getChildNotificationAt(0).getCurrentBackgroundTint())
-                .isEqualTo(resetTint);
-        assertThat(mGroupRow.getChildNotificationAt(1).getCurrentBackgroundTint())
-                .isEqualTo(resetTint);
-    }
-
-    @Test
-    public void testSetSensitiveOnNotifRowNotifiesOfHeightChange() throws InterruptedException {
-        // GIVEN a sensitive notification row that's currently redacted
-        measureAndLayout(mNotifRow);
-        mNotifRow.setHideSensitiveForIntrinsicHeight(true);
-        mNotifRow.setSensitive(true, true);
-        assertThat(mNotifRow.getShowingLayout()).isSameInstanceAs(mNotifRow.getPublicLayout());
-        assertThat(mNotifRow.getIntrinsicHeight()).isGreaterThan(0);
-
-        // GIVEN that the row has a height change listener
-        OnHeightChangedListener listener = mock(OnHeightChangedListener.class);
-        mNotifRow.setOnHeightChangedListener(listener);
-
-        // WHEN the row is set to no longer be sensitive
-        mNotifRow.setSensitive(false, true);
-
-        // VERIFY that the height change listener is invoked
-        assertThat(mNotifRow.getShowingLayout()).isSameInstanceAs(mNotifRow.getPrivateLayout());
-        assertThat(mNotifRow.getIntrinsicHeight()).isGreaterThan(0);
-        verify(listener).onHeightChanged(eq(mNotifRow), eq(false));
-    }
-
-    @Test
-    public void testSetSensitiveOnGroupRowNotifiesOfHeightChange() {
-        // GIVEN a sensitive group row that's currently redacted
-        measureAndLayout(mGroupRow);
-        mGroupRow.setHideSensitiveForIntrinsicHeight(true);
-        mGroupRow.setSensitive(true, true);
-        assertThat(mGroupRow.getShowingLayout()).isSameInstanceAs(mGroupRow.getPublicLayout());
-        assertThat(mGroupRow.getIntrinsicHeight()).isGreaterThan(0);
-
-        // GIVEN that the row has a height change listener
-        OnHeightChangedListener listener = mock(OnHeightChangedListener.class);
-        mGroupRow.setOnHeightChangedListener(listener);
-
-        // WHEN the row is set to no longer be sensitive
-        mGroupRow.setSensitive(false, true);
-
-        // VERIFY that the height change listener is invoked
-        assertThat(mGroupRow.getShowingLayout()).isSameInstanceAs(mGroupRow.getPrivateLayout());
-        assertThat(mGroupRow.getIntrinsicHeight()).isGreaterThan(0);
-        verify(listener).onHeightChanged(eq(mGroupRow), eq(false));
-    }
-
-    @Test
-    public void testSetSensitiveOnPublicRowDoesNotNotifyOfHeightChange() {
         // GIVEN a sensitive public row that's currently redacted
-        measureAndLayout(mPublicRow);
-        mPublicRow.setHideSensitiveForIntrinsicHeight(true);
-        mPublicRow.setSensitive(true, true);
-        assertThat(mPublicRow.getShowingLayout()).isSameInstanceAs(mPublicRow.getPublicLayout());
-        assertThat(mPublicRow.getIntrinsicHeight()).isGreaterThan(0);
+        measureAndLayout(publicRow);
+        publicRow.setHideSensitiveForIntrinsicHeight(true);
+        publicRow.setSensitive(true, true);
+        assertThat(publicRow.getShowingLayout()).isSameInstanceAs(publicRow.getPublicLayout());
+        assertThat(publicRow.getIntrinsicHeight()).isGreaterThan(0);
 
         // GIVEN that the row has a height change listener
         OnHeightChangedListener listener = mock(OnHeightChangedListener.class);
-        mPublicRow.setOnHeightChangedListener(listener);
+        publicRow.setOnHeightChangedListener(listener);
 
         // WHEN the row is set to no longer be sensitive
-        mPublicRow.setSensitive(false, true);
+        publicRow.setSensitive(false, true);
 
         // VERIFY that the height change listener is not invoked, because the height didn't change
-        assertThat(mPublicRow.getShowingLayout()).isSameInstanceAs(mPublicRow.getPrivateLayout());
-        assertThat(mPublicRow.getIntrinsicHeight()).isGreaterThan(0);
-        assertThat(mPublicRow.getPrivateLayout().getMinHeight())
-                .isEqualTo(mPublicRow.getPublicLayout().getMinHeight());
-        verify(listener, never()).onHeightChanged(eq(mPublicRow), eq(false));
+        assertThat(publicRow.getShowingLayout()).isSameInstanceAs(publicRow.getPrivateLayout());
+        assertThat(publicRow.getIntrinsicHeight()).isGreaterThan(0);
+        assertThat(publicRow.getPrivateLayout().getMinHeight())
+                .isEqualTo(publicRow.getPublicLayout().getMinHeight());
+        verify(listener, never()).onHeightChanged(eq(publicRow), eq(false));
     }
 
     private void measureAndLayout(ExpandableNotificationRow row) {
@@ -227,36 +218,43 @@
     }
 
     @Test
-    public void testGroupSummaryNotShowingIconWhenPublic() {
-        mGroupRow.setSensitive(true, true);
-        mGroupRow.setHideSensitiveForIntrinsicHeight(true);
-        assertTrue(mGroupRow.isSummaryWithChildren());
-        assertFalse(mGroupRow.isShowingIcon());
+    public void testGroupSummaryNotShowingIconWhenPublic() throws Exception {
+        ExpandableNotificationRow group = mNotificationTestHelper.createGroup();
+
+        group.setSensitive(true, true);
+        group.setHideSensitiveForIntrinsicHeight(true);
+        assertTrue(group.isSummaryWithChildren());
+        assertFalse(group.isShowingIcon());
     }
 
     @Test
-    public void testNotificationHeaderVisibleWhenAnimating() {
-        mGroupRow.setSensitive(true, true);
-        mGroupRow.setHideSensitive(true, false, 0, 0);
-        mGroupRow.setHideSensitive(false, true, 0, 0);
-        assertEquals(View.VISIBLE, mGroupRow.getChildrenContainer().getVisibleWrapper()
+    public void testNotificationHeaderVisibleWhenAnimating() throws Exception {
+        ExpandableNotificationRow group = mNotificationTestHelper.createGroup();
+
+        group.setSensitive(true, true);
+        group.setHideSensitive(true, false, 0, 0);
+        group.setHideSensitive(false, true, 0, 0);
+        assertEquals(View.VISIBLE, group.getChildrenContainer().getVisibleWrapper()
                 .getNotificationHeader().getVisibility());
     }
 
     @Test
-    public void testUserLockedResetEvenWhenNoChildren() {
-        mGroupRow.setUserLocked(true);
-        mGroupRow.setUserLocked(false);
+    public void testUserLockedResetEvenWhenNoChildren() throws Exception {
+        ExpandableNotificationRow group = mNotificationTestHelper.createGroup();
+
+        group.setUserLocked(true);
+        group.setUserLocked(false);
         assertFalse("The childrencontainer should not be userlocked but is, the state "
-                + "seems out of sync.", mGroupRow.getChildrenContainer().isUserLocked());
+                + "seems out of sync.", group.getChildrenContainer().isUserLocked());
     }
 
     @Test
-    public void testReinflatedOnDensityChange() {
+    public void testReinflatedOnDensityChange() throws Exception {
+        ExpandableNotificationRow row = mNotificationTestHelper.createRow();
         NotificationChildrenContainer mockContainer = mock(NotificationChildrenContainer.class);
-        mNotifRow.setChildrenContainer(mockContainer);
+        row.setChildrenContainer(mockContainer);
 
-        mNotifRow.onDensityOrFontScaleChanged();
+        row.onDensityOrFontScaleChanged();
 
         verify(mockContainer).reInflateViews(any(), any());
     }
@@ -299,64 +297,73 @@
     @Test
     public void testAboveShelfChangedListenerCalledWhenGoingBelow() throws Exception {
         ExpandableNotificationRow row = mNotificationTestHelper.createRow();
-        row.setHeadsUp(true);
         AboveShelfChangedListener listener = mock(AboveShelfChangedListener.class);
         row.setAboveShelfChangedListener(listener);
+        Mockito.reset(listener);
+        row.setHeadsUp(true);
         row.setAboveShelf(false);
         verify(listener).onAboveShelfStateChanged(false);
     }
 
     @Test
     public void testClickSound() throws Exception {
-        assertTrue("Should play sounds by default.", mGroupRow.isSoundEffectsEnabled());
+        ExpandableNotificationRow group = mNotificationTestHelper.createGroup();
+
+        assertTrue("Should play sounds by default.", group.isSoundEffectsEnabled());
         StatusBarStateController mock = mNotificationTestHelper.getStatusBarStateController();
         when(mock.isDozing()).thenReturn(true);
-        mGroupRow.setSecureStateProvider(()-> false);
+        group.setSecureStateProvider(()-> false);
         assertFalse("Shouldn't play sounds when dark and trusted.",
-                mGroupRow.isSoundEffectsEnabled());
-        mGroupRow.setSecureStateProvider(()-> true);
+                group.isSoundEffectsEnabled());
+        group.setSecureStateProvider(()-> true);
         assertTrue("Should always play sounds when not trusted.",
-                mGroupRow.isSoundEffectsEnabled());
+                group.isSoundEffectsEnabled());
     }
 
     @Test
-    public void testSetDismissed_longPressListenerRemoved() {
+    public void testSetDismissed_longPressListenerRemoved() throws Exception {
+        ExpandableNotificationRow group = mNotificationTestHelper.createGroup();
+
         ExpandableNotificationRow.LongPressListener listener =
                 mock(ExpandableNotificationRow.LongPressListener.class);
-        mGroupRow.setLongPressListener(listener);
-        mGroupRow.doLongClickCallback(0,0);
-        verify(listener, times(1)).onLongPress(eq(mGroupRow), eq(0), eq(0),
+        group.setLongPressListener(listener);
+        group.doLongClickCallback(0, 0);
+        verify(listener, times(1)).onLongPress(eq(group), eq(0), eq(0),
                 any(NotificationMenuRowPlugin.MenuItem.class));
         reset(listener);
 
-        mGroupRow.dismiss(true);
-        mGroupRow.doLongClickCallback(0,0);
-        verify(listener, times(0)).onLongPress(eq(mGroupRow), eq(0), eq(0),
+        group.dismiss(true);
+        group.doLongClickCallback(0, 0);
+        verify(listener, times(0)).onLongPress(eq(group), eq(0), eq(0),
                 any(NotificationMenuRowPlugin.MenuItem.class));
     }
 
     @Test
-    public void testFeedback_noHeader() {
+    public void testFeedback_noHeader() throws Exception {
+        ExpandableNotificationRow groupRow = mNotificationTestHelper.createGroup();
+
         // public notification is custom layout - no header
-        mGroupRow.setSensitive(true, true);
-        mGroupRow.setOnFeedbackClickListener(null);
-        mGroupRow.setFeedbackIcon(null);
+        groupRow.setSensitive(true, true);
+        groupRow.setOnFeedbackClickListener(null);
+        groupRow.setFeedbackIcon(null);
     }
 
     @Test
-    public void testFeedback_header() {
+    public void testFeedback_header() throws Exception {
+        ExpandableNotificationRow group = mNotificationTestHelper.createGroup();
+
         NotificationContentView publicLayout = mock(NotificationContentView.class);
-        mGroupRow.setPublicLayout(publicLayout);
+        group.setPublicLayout(publicLayout);
         NotificationContentView privateLayout = mock(NotificationContentView.class);
-        mGroupRow.setPrivateLayout(privateLayout);
+        group.setPrivateLayout(privateLayout);
         NotificationChildrenContainer mockContainer = mock(NotificationChildrenContainer.class);
         when(mockContainer.getNotificationChildCount()).thenReturn(1);
-        mGroupRow.setChildrenContainer(mockContainer);
+        group.setChildrenContainer(mockContainer);
 
         final boolean show = true;
         final FeedbackIcon icon = new FeedbackIcon(
                 R.drawable.ic_feedback_alerted, R.string.notification_feedback_indicator_alerted);
-        mGroupRow.setFeedbackIcon(icon);
+        group.setFeedbackIcon(icon);
 
         verify(mockContainer, times(1)).setFeedbackIcon(icon);
         verify(privateLayout, times(1)).setFeedbackIcon(icon);
@@ -364,43 +371,60 @@
     }
 
     @Test
-    public void testFeedbackOnClick() {
+    public void testFeedbackOnClick() throws Exception {
+        ExpandableNotificationRow group = mNotificationTestHelper.createGroup();
+
         ExpandableNotificationRow.CoordinateOnClickListener l = mock(
                 ExpandableNotificationRow.CoordinateOnClickListener.class);
         View view = mock(View.class);
 
-        mGroupRow.setOnFeedbackClickListener(l);
+        group.setOnFeedbackClickListener(l);
 
-        mGroupRow.getFeedbackOnClickListener().onClick(view);
+        group.getFeedbackOnClickListener().onClick(view);
         verify(l, times(1)).onClick(any(), anyInt(), anyInt(), any());
     }
 
     @Test
-    public void testHeadsUpAnimatingAwayListener() {
-        mGroupRow.setHeadsUpAnimatingAway(true);
-        Assert.assertEquals(true, mHeadsUpAnimatingAway);
-        mGroupRow.setHeadsUpAnimatingAway(false);
-        Assert.assertEquals(false, mHeadsUpAnimatingAway);
+    public void testHeadsUpAnimatingAwayListener() throws Exception {
+        ExpandableNotificationRow group = mNotificationTestHelper.createGroup();
+        Consumer<Boolean> headsUpListener = mock(Consumer.class);
+        AboveShelfChangedListener aboveShelfChangedListener = mock(AboveShelfChangedListener.class);
+        group.setHeadsUpAnimatingAwayListener(headsUpListener);
+        group.setAboveShelfChangedListener(aboveShelfChangedListener);
+
+        group.setHeadsUpAnimatingAway(true);
+        verify(headsUpListener).accept(true);
+        verify(aboveShelfChangedListener).onAboveShelfStateChanged(true);
+
+        group.setHeadsUpAnimatingAway(false);
+        verify(headsUpListener).accept(false);
+        verify(aboveShelfChangedListener).onAboveShelfStateChanged(false);
     }
 
     @Test
-    public void testIsBlockingHelperShowing_isCorrectlyUpdated() {
-        mGroupRow.setBlockingHelperShowing(true);
-        assertTrue(mGroupRow.isBlockingHelperShowing());
+    public void testIsBlockingHelperShowing_isCorrectlyUpdated() throws Exception {
+        ExpandableNotificationRow group = mNotificationTestHelper.createGroup();
 
-        mGroupRow.setBlockingHelperShowing(false);
-        assertFalse(mGroupRow.isBlockingHelperShowing());
+        group.setBlockingHelperShowing(true);
+        assertTrue(group.isBlockingHelperShowing());
+
+        group.setBlockingHelperShowing(false);
+        assertFalse(group.isBlockingHelperShowing());
     }
 
     @Test
-    public void testGetNumUniqueChildren_defaultChannel() {
-        assertEquals(1, mGroupRow.getNumUniqueChannels());
+    public void testGetNumUniqueChildren_defaultChannel() throws Exception {
+        ExpandableNotificationRow groupRow = mNotificationTestHelper.createGroup();
+
+        assertEquals(1, groupRow.getNumUniqueChannels());
     }
 
     @Test
-    public void testGetNumUniqueChildren_multiChannel() {
+    public void testGetNumUniqueChildren_multiChannel() throws Exception {
+        ExpandableNotificationRow group = mNotificationTestHelper.createGroup();
+
         List<ExpandableNotificationRow> childRows =
-                mGroupRow.getChildrenContainer().getAttachedChildren();
+                group.getChildrenContainer().getAttachedChildren();
         // Give each child a unique channel id/name.
         int i = 0;
         for (ExpandableNotificationRow childRow : childRows) {
@@ -412,25 +436,29 @@
             i++;
         }
 
-        assertEquals(3, mGroupRow.getNumUniqueChannels());
+        assertEquals(3, group.getNumUniqueChannels());
     }
 
     @Test
     public void testIconScrollXAfterTranslationAndReset() throws Exception {
-        mGroupRow.setDismissUsingRowTranslationX(false);
-        mGroupRow.setTranslation(50);
-        assertEquals(50, -mGroupRow.getEntry().getIcons().getShelfIcon().getScrollX());
+        ExpandableNotificationRow group = mNotificationTestHelper.createGroup();
 
-        mGroupRow.resetTranslation();
-        assertEquals(0, mGroupRow.getEntry().getIcons().getShelfIcon().getScrollX());
+        group.setDismissUsingRowTranslationX(false);
+        group.setTranslation(50);
+        assertEquals(50, -group.getEntry().getIcons().getShelfIcon().getScrollX());
+
+        group.resetTranslation();
+        assertEquals(0, group.getEntry().getIcons().getShelfIcon().getScrollX());
     }
 
     @Test
-    public void testIsExpanded_userExpanded() {
-        mGroupRow.setExpandable(true);
-        Assert.assertFalse(mGroupRow.isExpanded());
-        mGroupRow.setUserExpanded(true);
-        Assert.assertTrue(mGroupRow.isExpanded());
+    public void testIsExpanded_userExpanded() throws Exception {
+        ExpandableNotificationRow group = mNotificationTestHelper.createGroup();
+
+        group.setExpandable(true);
+        Assert.assertFalse(group.isExpanded());
+        group.setUserExpanded(true);
+        Assert.assertTrue(group.isExpanded());
     }
 
     @Test
@@ -549,72 +577,80 @@
     }
 
     @Test
-    public void applyRoundnessAndInv_should_be_immediately_applied_on_childrenContainer_legacy() {
-        mGroupRow.useRoundnessSourceTypes(false);
-        Assert.assertEquals(0f, mGroupRow.getBottomRoundness(), 0.001f);
-        Assert.assertEquals(0f, mGroupRow.getChildrenContainer().getBottomRoundness(), 0.001f);
+    public void applyRoundnessAndInv_should_be_immediately_applied_on_childrenContainer_legacy()
+            throws Exception {
+        ExpandableNotificationRow group = mNotificationTestHelper.createGroup();
+        group.useRoundnessSourceTypes(false);
+        Assert.assertEquals(0f, group.getBottomRoundness(), 0.001f);
+        Assert.assertEquals(0f, group.getChildrenContainer().getBottomRoundness(), 0.001f);
 
-        mGroupRow.requestBottomRoundness(1f, SourceType.from(""), false);
+        group.requestBottomRoundness(1f, SourceType.from(""), false);
 
-        Assert.assertEquals(1f, mGroupRow.getBottomRoundness(), 0.001f);
-        Assert.assertEquals(1f, mGroupRow.getChildrenContainer().getBottomRoundness(), 0.001f);
+        Assert.assertEquals(1f, group.getBottomRoundness(), 0.001f);
+        Assert.assertEquals(1f, group.getChildrenContainer().getBottomRoundness(), 0.001f);
     }
 
     @Test
-    public void applyRoundnessAndInvalidate_should_be_immediately_applied_on_childrenContainer() {
-        mGroupRow.useRoundnessSourceTypes(true);
-        Assert.assertEquals(0f, mGroupRow.getBottomRoundness(), 0.001f);
-        Assert.assertEquals(0f, mGroupRow.getChildrenContainer().getBottomRoundness(), 0.001f);
+    public void applyRoundnessAndInvalidate_should_be_immediately_applied_on_childrenContainer()
+            throws Exception {
+        ExpandableNotificationRow group = mNotificationTestHelper.createGroup();
+        group.useRoundnessSourceTypes(true);
+        Assert.assertEquals(0f, group.getBottomRoundness(), 0.001f);
+        Assert.assertEquals(0f, group.getChildrenContainer().getBottomRoundness(), 0.001f);
 
-        mGroupRow.requestBottomRoundness(1f, SourceType.from(""), false);
+        group.requestBottomRoundness(1f, SourceType.from(""), false);
 
-        Assert.assertEquals(1f, mGroupRow.getBottomRoundness(), 0.001f);
-        Assert.assertEquals(1f, mGroupRow.getChildrenContainer().getBottomRoundness(), 0.001f);
+        Assert.assertEquals(1f, group.getBottomRoundness(), 0.001f);
+        Assert.assertEquals(1f, group.getChildrenContainer().getBottomRoundness(), 0.001f);
     }
 
     @Test
     public void testSetContentAnimationRunning_Run() throws Exception {
         // Create views for the notification row.
+        ExpandableNotificationRow row = mNotificationTestHelper.createRow();
         NotificationContentView publicLayout = mock(NotificationContentView.class);
-        mNotifRow.setPublicLayout(publicLayout);
+        row.setPublicLayout(publicLayout);
         NotificationContentView privateLayout = mock(NotificationContentView.class);
-        mNotifRow.setPrivateLayout(privateLayout);
+        row.setPrivateLayout(privateLayout);
 
-        mNotifRow.setAnimationRunning(true);
+        row.setAnimationRunning(true);
         verify(publicLayout, times(1)).setContentAnimationRunning(true);
         verify(privateLayout, times(1)).setContentAnimationRunning(true);
     }
 
     @Test
-    public void testSetContentAnimationRunning_Stop() {
+    public void testSetContentAnimationRunning_Stop() throws Exception {
         // Create views for the notification row.
+        ExpandableNotificationRow row = mNotificationTestHelper.createRow();
         NotificationContentView publicLayout = mock(NotificationContentView.class);
-        mNotifRow.setPublicLayout(publicLayout);
+        row.setPublicLayout(publicLayout);
         NotificationContentView privateLayout = mock(NotificationContentView.class);
-        mNotifRow.setPrivateLayout(privateLayout);
+        row.setPrivateLayout(privateLayout);
 
-        mNotifRow.setAnimationRunning(false);
+        row.setAnimationRunning(false);
         verify(publicLayout, times(1)).setContentAnimationRunning(false);
         verify(privateLayout, times(1)).setContentAnimationRunning(false);
     }
 
     @Test
-    public void testSetContentAnimationRunningInGroupChild_Run() {
-        // Creates parent views on mGroupRow.
+    public void testSetContentAnimationRunningInGroupChild_Run() throws Exception {
+        // Creates parent views on groupRow.
+        ExpandableNotificationRow groupRow = mNotificationTestHelper.createGroup();
         NotificationContentView publicParentLayout = mock(NotificationContentView.class);
-        mGroupRow.setPublicLayout(publicParentLayout);
+        groupRow.setPublicLayout(publicParentLayout);
         NotificationContentView privateParentLayout = mock(NotificationContentView.class);
-        mGroupRow.setPrivateLayout(privateParentLayout);
+        groupRow.setPrivateLayout(privateParentLayout);
 
-        // Create child views on mNotifRow.
+        // Create child views on row.
+        ExpandableNotificationRow row = mNotificationTestHelper.createRow();
         NotificationContentView publicChildLayout = mock(NotificationContentView.class);
-        mNotifRow.setPublicLayout(publicChildLayout);
+        row.setPublicLayout(publicChildLayout);
         NotificationContentView privateChildLayout = mock(NotificationContentView.class);
-        mNotifRow.setPrivateLayout(privateChildLayout);
-        when(mNotifRow.isGroupExpanded()).thenReturn(true);
-        setMockChildrenContainer(mGroupRow, mNotifRow);
+        row.setPrivateLayout(privateChildLayout);
+        when(row.isGroupExpanded()).thenReturn(true);
+        setMockChildrenContainer(groupRow, row);
 
-        mGroupRow.setAnimationRunning(true);
+        groupRow.setAnimationRunning(true);
         verify(publicParentLayout, times(1)).setContentAnimationRunning(true);
         verify(privateParentLayout, times(1)).setContentAnimationRunning(true);
         // The child layouts should be started too.
@@ -624,23 +660,25 @@
 
 
     @Test
-    public void testSetIconAnimationRunningGroup_Run() {
+    public void testSetIconAnimationRunningGroup_Run() throws Exception {
         // Create views for a group row.
+        ExpandableNotificationRow group = mNotificationTestHelper.createGroup();
+        ExpandableNotificationRow child = mNotificationTestHelper.createRow();
         NotificationContentView publicParentLayout = mock(NotificationContentView.class);
-        mGroupRow.setPublicLayout(publicParentLayout);
+        group.setPublicLayout(publicParentLayout);
         NotificationContentView privateParentLayout = mock(NotificationContentView.class);
-        mGroupRow.setPrivateLayout(privateParentLayout);
-        when(mGroupRow.isGroupExpanded()).thenReturn(true);
+        group.setPrivateLayout(privateParentLayout);
+        when(group.isGroupExpanded()).thenReturn(true);
 
-        // Sets up mNotifRow as a child ExpandableNotificationRow.
+        // Add the child to the group.
         NotificationContentView publicChildLayout = mock(NotificationContentView.class);
-        mNotifRow.setPublicLayout(publicChildLayout);
+        child.setPublicLayout(publicChildLayout);
         NotificationContentView privateChildLayout = mock(NotificationContentView.class);
-        mNotifRow.setPrivateLayout(privateChildLayout);
-        when(mNotifRow.isGroupExpanded()).thenReturn(true);
+        child.setPrivateLayout(privateChildLayout);
+        when(child.isGroupExpanded()).thenReturn(true);
 
         NotificationChildrenContainer mockContainer =
-                setMockChildrenContainer(mGroupRow, mNotifRow);
+                setMockChildrenContainer(group, child);
 
         // Mock the children view wrappers, and give them each an icon.
         NotificationViewWrapper mockViewWrapper = mock(NotificationViewWrapper.class);
@@ -663,7 +701,7 @@
         AnimatedVectorDrawable lowPriVectorDrawable = mock(AnimatedVectorDrawable.class);
         setDrawableIconsInImageView(mockLowPriorityIcon, lowPriDrawable, lowPriVectorDrawable);
 
-        mGroupRow.setAnimationRunning(true);
+        group.setAnimationRunning(true);
         verify(drawable, times(1)).start();
         verify(vectorDrawable, times(1)).start();
         verify(lowPriDrawable, times(1)).start();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java
index e6f6a8d..aca9c56 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java
@@ -40,7 +40,6 @@
 import android.content.Intent;
 import android.content.pm.LauncherApps;
 import android.graphics.drawable.Icon;
-import android.os.Handler;
 import android.os.UserHandle;
 import android.service.notification.StatusBarNotification;
 import android.testing.TestableLooper;
@@ -49,7 +48,6 @@
 import android.widget.RemoteViews;
 
 import com.android.internal.logging.MetricsLogger;
-import com.android.internal.logging.UiEventLogger;
 import com.android.systemui.TestableDependency;
 import com.android.systemui.classifier.FalsingCollectorFake;
 import com.android.systemui.classifier.FalsingManagerFake;
@@ -57,7 +55,6 @@
 import com.android.systemui.media.controls.util.MediaFeatureFlag;
 import com.android.systemui.media.dialog.MediaOutputDialogFactory;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.shade.ShadeExpansionStateManager;
 import com.android.systemui.statusbar.NotificationMediaManager;
 import com.android.systemui.statusbar.NotificationRemoteInputManager;
 import com.android.systemui.statusbar.NotificationShadeWindowController;
@@ -68,7 +65,6 @@
 import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
 import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection;
 import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener;
-import com.android.systemui.statusbar.notification.collection.provider.VisualStabilityProvider;
 import com.android.systemui.statusbar.notification.collection.render.GroupExpansionManager;
 import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager;
 import com.android.systemui.statusbar.notification.icon.IconBuilder;
@@ -77,11 +73,8 @@
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow.ExpandableNotificationRowLogger;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow.OnExpandClickListener;
 import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationFlag;
-import com.android.systemui.statusbar.phone.ConfigurationControllerImpl;
 import com.android.systemui.statusbar.phone.HeadsUpManagerPhone;
 import com.android.systemui.statusbar.phone.KeyguardBypassController;
-import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper;
-import com.android.systemui.statusbar.policy.HeadsUpManagerLogger;
 import com.android.systemui.statusbar.policy.InflatedSmartReplyState;
 import com.android.systemui.statusbar.policy.InflatedSmartReplyViewHolder;
 import com.android.systemui.statusbar.policy.SmartReplyConstants;
@@ -121,12 +114,12 @@
     private final GroupMembershipManager mGroupMembershipManager;
     private final GroupExpansionManager mGroupExpansionManager;
     private ExpandableNotificationRow mRow;
-    private HeadsUpManagerPhone mHeadsUpManager;
+    private final HeadsUpManagerPhone mHeadsUpManager;
     private final NotifBindPipeline mBindPipeline;
     private final NotifCollectionListener mBindPipelineEntryListener;
     private final RowContentBindStage mBindStage;
     private final IconManager mIconManager;
-    private StatusBarStateController mStatusBarStateController;
+    private final StatusBarStateController mStatusBarStateController;
     private final PeopleNotificationIdentifier mPeopleNotificationIdentifier;
     public final OnUserInteractionCallback mOnUserInteractionCallback;
     public final Runnable mFutureDismissalRunnable;
@@ -146,19 +139,7 @@
         mStatusBarStateController = mock(StatusBarStateController.class);
         mGroupMembershipManager = mock(GroupMembershipManager.class);
         mGroupExpansionManager = mock(GroupExpansionManager.class);
-        mHeadsUpManager = new HeadsUpManagerPhone(
-                mContext,
-                mock(HeadsUpManagerLogger.class),
-                mStatusBarStateController,
-                mock(KeyguardBypassController.class),
-                mock(GroupMembershipManager.class),
-                mock(VisualStabilityProvider.class),
-                mock(ConfigurationControllerImpl.class),
-                new Handler(mTestLooper.getLooper()),
-                mock(AccessibilityManagerWrapper.class),
-                mock(UiEventLogger.class),
-                mock(ShadeExpansionStateManager.class)
-        );
+        mHeadsUpManager = mock(HeadsUpManagerPhone.class);
         mIconManager = new IconManager(
                 mock(CommonNotifCollection.class),
                 mock(LauncherApps.class),
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FakeFeatureFlags.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FakeFeatureFlags.kt
index 6c82cef..b94f816e 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FakeFeatureFlags.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FakeFeatureFlags.kt
@@ -38,12 +38,6 @@
         }
     }
 
-    fun set(flag: DeviceConfigBooleanFlag, value: Boolean) {
-        if (booleanFlags.put(flag.id, value)?.let { value != it } != false) {
-            notifyFlagChanged(flag)
-        }
-    }
-
     fun set(flag: ResourceBooleanFlag, value: Boolean) {
         if (booleanFlags.put(flag.id, value)?.let { value != it } != false) {
             notifyFlagChanged(flag)
@@ -73,7 +67,7 @@
             listeners.forEach { listener ->
                 listener.onFlagChanged(
                     object : FlagListenable.FlagEvent {
-                        override val flagId = flag.id
+                        override val flagName = flag.name
                         override fun requestNoRestart() {}
                     }
                 )
diff --git a/services/companion/java/com/android/server/companion/presence/CompanionDevicePresenceMonitor.java b/services/companion/java/com/android/server/companion/presence/CompanionDevicePresenceMonitor.java
index 1e96324..d7a77cd 100644
--- a/services/companion/java/com/android/server/companion/presence/CompanionDevicePresenceMonitor.java
+++ b/services/companion/java/com/android/server/companion/presence/CompanionDevicePresenceMonitor.java
@@ -164,6 +164,12 @@
 
     @Override
     public void onBluetoothCompanionDeviceDisconnected(int associationId) {
+        // If disconnected device is also a BLE device, skip the 2-minute timer and mark it as gone.
+        boolean isConnectableBleDevice = mNearbyBleDevices.remove(associationId);
+        if (DEBUG && isConnectableBleDevice) {
+            Log.d(TAG, "Bluetooth device disconnect was detected."
+                    + " Pre-emptively marking the BLE device as lost.");
+        }
         onDeviceGone(mConnectedBtDevices, associationId, /* sourceLoggingTag */ "bt");
     }
 
diff --git a/services/core/java/com/android/server/BinaryTransparencyService.java b/services/core/java/com/android/server/BinaryTransparencyService.java
index 66fa746..f93f504 100644
--- a/services/core/java/com/android/server/BinaryTransparencyService.java
+++ b/services/core/java/com/android/server/BinaryTransparencyService.java
@@ -93,10 +93,8 @@
 import java.security.PublicKey;
 import java.security.cert.CertificateException;
 import java.util.ArrayList;
-import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
-import java.util.Set;
 import java.util.concurrent.Executors;
 import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.stream.Collectors;
@@ -300,15 +298,16 @@
                     + " and is now updated to: " + currentTimeMs);
             mMeasurementsLastRecordedMs = currentTimeMs;
 
-            Set<String> packagesMeasured = new HashSet<>();
+            Bundle packagesMeasured = new Bundle();
 
             // measure all APEXs first
             if (DEBUG) {
                 Slog.d(TAG, "Measuring APEXs...");
             }
-            List<IBinaryTransparencyService.ApexInfo> allApexInfo = collectAllApexInfo();
+            List<IBinaryTransparencyService.ApexInfo> allApexInfo = collectAllApexInfo(
+                    /* includeTestOnly */ false);
             for (IBinaryTransparencyService.ApexInfo apexInfo : allApexInfo) {
-                packagesMeasured.add(apexInfo.packageName);
+                packagesMeasured.putBoolean(apexInfo.packageName, true);
 
                 recordApexInfo(apexInfo);
             }
@@ -321,7 +320,7 @@
             List<IBinaryTransparencyService.AppInfo> allUpdatedPreloadInfo =
                     collectAllUpdatedPreloadInfo(packagesMeasured);
             for (IBinaryTransparencyService.AppInfo appInfo : allUpdatedPreloadInfo) {
-                packagesMeasured.add(appInfo.packageName);
+                packagesMeasured.putBoolean(appInfo.packageName, true);
                 writeAppInfoToLog(appInfo);
             }
             if (DEBUG) {
@@ -334,7 +333,7 @@
                 List<IBinaryTransparencyService.AppInfo> allMbaInfo =
                         collectAllMbaInfo(packagesMeasured);
                 for (IBinaryTransparencyService.AppInfo appInfo : allUpdatedPreloadInfo) {
-                    packagesMeasured.add(appInfo.packageName);
+                    packagesMeasured.putBoolean(appInfo.packageName, true);
                     writeAppInfoToLog(appInfo);
                 }
             }
@@ -345,7 +344,9 @@
             }
         }
 
-        private List<IBinaryTransparencyService.ApexInfo> collectAllApexInfo() {
+        @Override
+        public List<IBinaryTransparencyService.ApexInfo> collectAllApexInfo(
+                boolean includeTestOnly) {
             var results = new ArrayList<IBinaryTransparencyService.ApexInfo>();
             for (PackageInfo packageInfo : getCurrentInstalledApexs()) {
                 PackageState packageState = mPackageManagerInternal.getPackageStateInternal(
@@ -371,13 +372,19 @@
                 apexInfo.signerDigests =
                         computePackageSignerSha256Digests(packageState.getSigningInfo());
 
+                if (includeTestOnly) {
+                    apexInfo.moduleName = apexPackageNameToModuleName(
+                            packageState.getPackageName());
+                }
+
                 results.add(apexInfo);
             }
             return results;
         }
 
-        private List<IBinaryTransparencyService.AppInfo> collectAllUpdatedPreloadInfo(
-                Set<String> packagesToSkip) {
+        @Override
+        public List<IBinaryTransparencyService.AppInfo> collectAllUpdatedPreloadInfo(
+                Bundle packagesToSkip) {
             final var results = new ArrayList<IBinaryTransparencyService.AppInfo>();
 
             PackageManager pm = mContext.getPackageManager();
@@ -385,7 +392,7 @@
                 if (!packageState.isUpdatedSystemApp()) {
                     return;
                 }
-                if (packagesToSkip.contains(packageState.getPackageName())) {
+                if (packagesToSkip.containsKey(packageState.getPackageName())) {
                     return;
                 }
 
@@ -413,11 +420,10 @@
             return results;
         }
 
-        private List<IBinaryTransparencyService.AppInfo> collectAllMbaInfo(
-                Set<String> packagesToSkip) {
+        public List<IBinaryTransparencyService.AppInfo> collectAllMbaInfo(Bundle packagesToSkip) {
             var results = new ArrayList<IBinaryTransparencyService.AppInfo>();
             for (PackageInfo packageInfo : getNewlyInstalledMbas()) {
-                if (packagesToSkip.contains(packageInfo.packageName)) {
+                if (packagesToSkip.containsKey(packageInfo.packageName)) {
                     continue;
                 }
                 PackageState packageState = mPackageManagerInternal.getPackageStateInternal(
@@ -1657,11 +1663,7 @@
     private String getOriginalApexPreinstalledLocation(String packageName,
             String currentInstalledLocation) {
         try {
-            // It appears that only apexd knows the preinstalled location, and it uses module name
-            // as the identifier instead of package name. Given the input is a package name, we
-            // need to covert to module name.
-            final String moduleName = ApexManager.getInstance().getApexModuleNameForPackageName(
-                    packageName);
+            final String moduleName = apexPackageNameToModuleName(packageName);
             IApexService apexService = IApexService.Stub.asInterface(
                     Binder.allowBlocking(ServiceManager.waitForService("apexservice")));
             for (ApexInfo info : apexService.getAllPackages()) {
@@ -1675,6 +1677,13 @@
         return APEX_PRELOAD_LOCATION_ERROR;
     }
 
+    private String apexPackageNameToModuleName(String packageName) {
+        // It appears that only apexd knows the preinstalled location, and it uses module name as
+        // the identifier instead of package name. Given the input is a package name, we need to
+        // covert to module name.
+        return ApexManager.getInstance().getApexModuleNameForPackageName(packageName);
+    }
+
     /**
      * Wrapper method to call into IBICS to get a list of all newly installed MBAs.
      *
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 10d50c2..57a89e3 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -32,6 +32,7 @@
 import static android.app.ActivityManager.INSTR_FLAG_NO_RESTART;
 import static android.app.ActivityManager.INTENT_SENDER_ACTIVITY;
 import static android.app.ActivityManager.PROCESS_CAPABILITY_ALL;
+import static android.app.ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE;
 import static android.app.ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND;
 import static android.app.ActivityManager.PROCESS_STATE_NONEXISTENT;
 import static android.app.ActivityManager.PROCESS_STATE_TOP;
@@ -3341,6 +3342,7 @@
         }
 
         mBatteryStatsService.noteProcessDied(app.info.uid, pid);
+        mOomAdjuster.updateShortFgsOwner(app.info.uid, pid, false);
 
         if (!app.isKilled()) {
             if (!fromBinderDied) {
@@ -16727,6 +16729,12 @@
     }
 
     @Override
+    public boolean startProfileWithListener(@UserIdInt int userId,
+            @Nullable IProgressListener unlockListener) {
+        return mUserController.startProfile(userId, unlockListener);
+    }
+
+    @Override
     public boolean stopProfile(@UserIdInt int userId) {
         return mUserController.stopProfile(userId);
     }
@@ -18305,6 +18313,23 @@
         public void unregisterStrictModeCallback(int callingPid) {
             mStrictModeCallbacks.remove(callingPid);
         }
+
+        @Override
+        public boolean canHoldWakeLocksInDeepDoze(int uid, int procstate) {
+            // This method is called with the PowerManager lock held. Do not hold AM here.
+
+            // If the procstate is high enough, it's always allowed.
+            if (procstate <= PROCESS_STATE_BOUND_FOREGROUND_SERVICE) {
+                return true;
+            }
+            // IF it's too low, it's not allowed.
+            if (procstate > PROCESS_STATE_IMPORTANT_FOREGROUND) {
+                return false;
+            }
+            // If it's PROCESS_STATE_IMPORTANT_FOREGROUND, then we allow it only wheen the UID
+            // has a SHORT_FGS.
+            return mOomAdjuster.hasUidShortForegroundService(uid);
+        }
     }
 
     long inputDispatchingTimedOut(int pid, final boolean aboveSystem, TimeoutRecord timeoutRecord) {
diff --git a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
index 1e97285..3ab1cd7 100644
--- a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
+++ b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
@@ -39,6 +39,7 @@
 import static com.android.server.am.AppBatteryTracker.BatteryUsage.BATTERY_USAGE_COUNT;
 import static com.android.server.am.LowMemDetector.ADJ_MEM_FACTOR_NOTHING;
 
+import android.annotation.UserIdInt;
 import android.app.Activity;
 import android.app.ActivityManager;
 import android.app.ActivityOptions;
@@ -113,10 +114,12 @@
 
 import com.android.internal.compat.CompatibilityChangeConfig;
 import com.android.internal.util.MemInfoReader;
+import com.android.server.LocalServices;
 import com.android.server.am.LowMemDetector.MemFactor;
 import com.android.server.am.nano.Capabilities;
 import com.android.server.am.nano.Capability;
 import com.android.server.compat.PlatformCompat;
+import com.android.server.pm.UserManagerInternal;
 import com.android.server.utils.Slogf;
 
 import dalvik.annotation.optimization.NeverCompile;
@@ -536,18 +539,32 @@
 
     private class ProgressWaiter extends IProgressListener.Stub {
         private final CountDownLatch mFinishedLatch = new CountDownLatch(1);
+        private final @UserIdInt int mUserId;
+
+        private ProgressWaiter(@UserIdInt int userId) {
+            mUserId = userId;
+        }
 
         @Override
         public void onStarted(int id, Bundle extras) {}
 
         @Override
-        public void onProgress(int id, int progress, Bundle extras) {}
+        public void onProgress(int id, int progress, Bundle extras) {
+            Slogf.d(TAG, "ProgressWaiter[user=%d]: onProgress(%d, %d)", mUserId, id, progress);
+        }
 
         @Override
         public void onFinished(int id, Bundle extras) {
+            Slogf.d(TAG, "ProgressWaiter[user=%d]: onFinished(%d)", mUserId, id);
             mFinishedLatch.countDown();
         }
 
+        @Override
+        public String toString() {
+            return "ProgressWaiter[userId=" + mUserId + ", finished="
+                    + (mFinishedLatch.getCount() == 0) + "]";
+        }
+
         public boolean waitForFinish(long timeoutMillis) {
             try {
                 return mFinishedLatch.await(timeoutMillis, TimeUnit.MILLISECONDS);
@@ -2183,6 +2200,7 @@
         boolean wait = false;
         String opt;
         int displayId = Display.INVALID_DISPLAY;
+        boolean forceInvisible = false;
         while ((opt = getNextOption()) != null) {
             switch(opt) {
                 case "-w":
@@ -2191,32 +2209,47 @@
                 case "--display":
                     displayId = getDisplayIdFromNextArg();
                     break;
+                case "--force-invisible":
+                    forceInvisible = true;
+                    break;
                 default:
                     getErrPrintWriter().println("Error: unknown option: " + opt);
                     return -1;
             }
         }
-        int userId = Integer.parseInt(getNextArgRequired());
-
-        final ProgressWaiter waiter = wait ? new ProgressWaiter() : null;
+        final int userId = Integer.parseInt(getNextArgRequired());
+        final boolean callStartProfile = !forceInvisible && isProfile(userId);
+        final ProgressWaiter waiter = wait ? new ProgressWaiter(userId) : null;
+        Slogf.d(TAG, "runStartUser(): userId=%d, display=%d, waiter=%s, callStartProfile=%b, "
+                + "forceInvisible=%b", userId,  displayId, waiter, callStartProfile,
+                forceInvisible);
 
         boolean success;
-        String displaySuffix;
+        String displaySuffix = "";
 
         Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "shell_runStartUser" + userId);
         try {
-            if (displayId == Display.INVALID_DISPLAY) {
+            if (callStartProfile) {
+                Slogf.d(TAG, "calling startProfileWithListener(%d, %s)", userId, waiter);
+                // startProfileWithListener() will start the profile visible (as long its parent is
+                // the current user), while startUserInBackgroundWithListener() will always start
+                // the user (or profile) invisible
+                success = mInterface.startProfileWithListener(userId, waiter);
+            } else if (displayId == Display.INVALID_DISPLAY) {
+                Slogf.d(TAG, "calling startUserInBackgroundWithListener(%d)", userId);
                 success = mInterface.startUserInBackgroundWithListener(userId, waiter);
-                displaySuffix = "";
             } else {
                 if (!UserManager.isVisibleBackgroundUsersEnabled()) {
                     pw.println("Not supported");
                     return -1;
                 }
+                Slogf.d(TAG, "calling startUserInBackgroundVisibleOnDisplay(%d,%d)", userId,
+                        displayId);
                 success = mInterface.startUserInBackgroundVisibleOnDisplay(userId, displayId);
                 displaySuffix = " on display " + displayId;
             }
             if (wait && success) {
+                Slogf.d(TAG, "waiting %d ms", USER_OPERATION_TIMEOUT_MS);
                 success = waiter.waitForFinish(USER_OPERATION_TIMEOUT_MS);
             }
         } finally {
@@ -2273,27 +2306,40 @@
     }
 
     static final class StopUserCallback extends IStopUserCallback.Stub {
+        private final @UserIdInt int mUserId;
         private boolean mFinished = false;
 
+        private StopUserCallback(@UserIdInt int userId) {
+            mUserId = userId;
+        }
+
         public synchronized void waitForFinish() {
             try {
                 while (!mFinished) wait();
             } catch (InterruptedException e) {
                 throw new IllegalStateException(e);
             }
+            Slogf.d(TAG, "user %d finished stopping", mUserId);
         }
 
         @Override
         public synchronized void userStopped(int userId) {
+            Slogf.d(TAG, "StopUserCallback: userStopped(%d)", userId);
             mFinished = true;
             notifyAll();
         }
 
         @Override
         public synchronized void userStopAborted(int userId) {
+            Slogf.d(TAG, "StopUserCallback: userStopAborted(%d)", userId);
             mFinished = true;
             notifyAll();
         }
+
+        @Override
+        public String toString() {
+            return "ProgressWaiter[userId=" + mUserId + ", finished=" + mFinished + "]";
+        }
     }
 
     int runStopUser(PrintWriter pw) throws RemoteException {
@@ -2310,10 +2356,11 @@
                 return -1;
             }
         }
-        int user = Integer.parseInt(getNextArgRequired());
-        StopUserCallback callback = wait ? new StopUserCallback() : null;
+        int userId = Integer.parseInt(getNextArgRequired());
+        StopUserCallback callback = wait ? new StopUserCallback(userId) : null;
 
-        int res = mInterface.stopUser(user, force, callback);
+        Slogf.d(TAG, "Calling stopUser(%d, %b, %s)", userId, force, callback);
+        int res = mInterface.stopUser(userId, force, callback);
         if (res != ActivityManager.USER_OP_SUCCESS) {
             String txt = "";
             switch (res) {
@@ -2321,13 +2368,13 @@
                     txt = " (Can't stop current user)";
                     break;
                 case ActivityManager.USER_OP_UNKNOWN_USER:
-                    txt = " (Unknown user " + user + ")";
+                    txt = " (Unknown user " + userId + ")";
                     break;
                 case ActivityManager.USER_OP_ERROR_IS_SYSTEM:
                     txt = " (System user cannot be stopped)";
                     break;
                 case ActivityManager.USER_OP_ERROR_RELATED_USERS_CANNOT_STOP:
-                    txt = " (Can't stop user " + user
+                    txt = " (Can't stop user " + userId
                             + " - one of its related users can't be stopped)";
                     break;
             }
@@ -3822,6 +3869,11 @@
         return new Resources(AssetManager.getSystem(), metrics, config);
     }
 
+    private boolean isProfile(@UserIdInt int userId) {
+        final UserManagerInternal umi = LocalServices.getService(UserManagerInternal.class);
+        return umi.getProfileParentId(userId) != userId;
+    }
+
     @Override
     public void onHelp() {
         PrintWriter pw = getOutPrintWriter();
@@ -4052,13 +4104,18 @@
             pw.println("      execution of that user if it is currently stopped.");
             pw.println("  get-current-user");
             pw.println("      Returns id of the current foreground user.");
-            pw.println("  start-user [-w] [--display DISPLAY_ID] <USER_ID>");
+            pw.println("  start-user [-w] [--display DISPLAY_ID] [--force-invisible] <USER_ID>");
             pw.println("      Start USER_ID in background if it is currently stopped;");
             pw.println("      use switch-user if you want to start the user in foreground.");
             pw.println("      -w: wait for start-user to complete and the user to be unlocked.");
-            pw.println("      --display <DISPLAY_ID>: allows the user to launch activities in the");
-            pw.println("        given display, when supported (typically on automotive builds");
-            pw.println("        wherethe vehicle has multiple displays)");
+            pw.println("      --display <DISPLAY_ID>: starts the user visible in that display, "
+                    + "which allows the user to launch activities on it.");
+            pw.println("        (not supported on all devices; typically only on automotive builds "
+                    + "where the vehicle has passenger displays)");
+            pw.println("      --force-invisible: always start the user invisible, even if it's a "
+                    + "profile.");
+            pw.println("        (by default, a profile is visible in the default display when its "
+                    + "parent is the current foreground user)");
             pw.println("  unlock-user <USER_ID>");
             pw.println("      Unlock the given user.  This will only work if the user doesn't");
             pw.println("      have an LSKF (PIN/pattern/password).");
diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java
index 114e2c1..e02dda6 100644
--- a/services/core/java/com/android/server/am/OomAdjuster.java
+++ b/services/core/java/com/android/server/am/OomAdjuster.java
@@ -126,6 +126,7 @@
 import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.Slog;
+import android.util.SparseSetArray;
 import android.util.proto.ProtoOutputStream;
 
 import com.android.internal.annotations.CompositeRWLock;
@@ -364,6 +365,19 @@
     @GuardedBy("mService")
     private boolean mPendingFullOomAdjUpdate = false;
 
+    /**
+     * PIDs that has a SHORT_SERVICE. We need to access it with the PowerManager lock held,
+     * so we use a fine-grained lock here.
+     */
+    @GuardedBy("mPidsWithShortFgs")
+    private final ArraySet<Integer> mPidsWithShortFgs = new ArraySet<>();
+
+    /**
+     * UIDs -> PIDs map, used with mPidsWithShortFgs.
+     */
+    @GuardedBy("mPidsWithShortFgs")
+    private final SparseSetArray<Integer> mUidsToPidsWithShortFgs = new SparseSetArray<>();
+
     /** Overrideable by a test */
     @VisibleForTesting
     protected boolean isChangeEnabled(@CachedCompatChangeId int cachedCompatChangeId,
@@ -1849,6 +1863,11 @@
 
         int capabilityFromFGS = 0; // capability from foreground service.
 
+        final boolean hasForegroundServices = psr.hasForegroundServices();
+        final boolean hasNonShortForegroundServices = psr.hasNonShortForegroundServices();
+        final boolean hasShortForegroundServices = hasForegroundServices
+                && !psr.areAllShortForegroundServicesProcstateTimedOut(now);
+
         // Adjust for FGS or "has-overlay-ui".
         if (adj > PERCEPTIBLE_APP_ADJ
                 || procState > PROCESS_STATE_FOREGROUND_SERVICE) {
@@ -1856,7 +1875,7 @@
             int newAdj = 0;
             int newProcState = 0;
 
-            if (psr.hasForegroundServices() && psr.hasNonShortForegroundServices()) {
+            if (hasForegroundServices && hasNonShortForegroundServices) {
                 // For regular (non-short) FGS.
                 adjType = "fg-service";
                 newAdj = PERCEPTIBLE_APP_ADJ;
@@ -1867,11 +1886,11 @@
                 newAdj = PERCEPTIBLE_APP_ADJ;
                 newProcState = PROCESS_STATE_IMPORTANT_FOREGROUND;
 
-            } else if (psr.hasForegroundServices()) {
+            } else if (hasForegroundServices) {
                 // If we get here, hasNonShortForegroundServices() must be false.
 
                 // TODO(short-service): Proactively run OomAjudster when the grace period finish.
-                if (psr.areAllShortForegroundServicesProcstateTimedOut(now)) {
+                if (!hasShortForegroundServices) {
                     // All the short-FGSes within this process are timed out. Don't promote to FGS.
                     // TODO(short-service): Should we set some unique oom-adj to make it detectable,
                     // in a long trace?
@@ -1907,6 +1926,7 @@
                 }
             }
         }
+        updateShortFgsOwner(psr.mApp.uid, psr.mApp.mPid, hasShortForegroundServices);
 
         // If the app was recently in the foreground and moved to a foreground service status,
         // allow it to get a higher rank in memory for some time, compared to other foreground
@@ -3337,4 +3357,40 @@
             mCachedAppOptimizer.unfreezeAppLSP(app, oomAdjReason);
         }
     }
+
+    /**
+     * Update {@link #mPidsWithShortFgs} and {@link #mUidsToPidsWithShortFgs} to keep track
+     * of which UID/PID has a short FGS.
+     *
+     * TODO(short-FGS): Remove it and all the relevant code once SHORT_FGS use the FGS procstate.
+     */
+    void updateShortFgsOwner(int uid, int pid, boolean add) {
+        synchronized (mPidsWithShortFgs) {
+            if (add) {
+                mUidsToPidsWithShortFgs.add(uid, pid);
+                mPidsWithShortFgs.add(pid);
+            } else {
+                mUidsToPidsWithShortFgs.remove(uid, pid);
+                mPidsWithShortFgs.remove(pid);
+            }
+        }
+    }
+
+    /**
+     * Whether a UID has a (non-timed-out) short FGS or not.
+     * It's indirectly called by PowerManager, so we can't hold the AM lock in it.
+     */
+    boolean hasUidShortForegroundService(int uid) {
+        synchronized (mPidsWithShortFgs) {
+            final ArraySet<Integer> pids = mUidsToPidsWithShortFgs.get(uid);
+            if (pids == null || pids.size() == 0) {
+                return false;
+            }
+            for (int i = pids.size() - 1; i >= 0; i--) {
+                final int pid = pids.valueAt(i);
+                return mPidsWithShortFgs.contains(pid);
+            }
+        }
+        return false;
+    }
 }
diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java
index 8ce9889..b2e4740 100644
--- a/services/core/java/com/android/server/am/UserController.java
+++ b/services/core/java/com/android/server/am/UserController.java
@@ -1470,7 +1470,11 @@
      * @param userId the id of the user to start.
      * @return true if the operation was successful.
      */
-    boolean startProfile(final @UserIdInt int userId) {
+    boolean startProfile(@UserIdInt int userId) {
+        return startProfile(userId, /* unlockListener= */ null);
+    }
+
+    boolean startProfile(@UserIdInt int userId, @Nullable IProgressListener unlockListener) {
         if (mInjector.checkCallingPermission(android.Manifest.permission.MANAGE_USERS)
                 == PackageManager.PERMISSION_DENIED && mInjector.checkCallingPermission(
                 android.Manifest.permission.INTERACT_ACROSS_USERS_FULL)
@@ -1491,7 +1495,7 @@
         }
 
         return startUserNoChecks(userId, Display.DEFAULT_DISPLAY,
-                USER_START_MODE_BACKGROUND_VISIBLE, /* unlockListener= */ null);
+                USER_START_MODE_BACKGROUND_VISIBLE, unlockListener);
     }
 
     @VisibleForTesting
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index b4992d7..88fc51a3 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -3757,8 +3757,19 @@
         return false;
     }
 
-     /*package*/ void onSetStreamVolume(int streamType, int index, int flags, int device,
-            String caller, boolean hasModifyAudioSettings) {
+    /**
+     * Update stream volume, ringer mode and mute status after a volume index change
+     * @param streamType
+     * @param index
+     * @param flags
+     * @param device the device for which the volume is changed
+     * @param caller
+     * @param hasModifyAudioSettings
+     * @param canChangeMute true if the origin of this event is one where the mute state should be
+     *                      updated following the change in volume index
+     */
+    /*package*/ void onSetStreamVolume(int streamType, int index, int flags, int device,
+            String caller, boolean hasModifyAudioSettings, boolean canChangeMute) {
         final int stream = mStreamVolumeAlias[streamType];
         setStreamVolumeInt(stream, index, device, false, caller, hasModifyAudioSettings);
         // setting volume on ui sounds stream type also controls silent mode
@@ -3768,10 +3779,8 @@
                     TAG + ".onSetStreamVolume", false /*external*/);
         }
         // setting non-zero volume for a muted stream unmutes the stream and vice versa
-        // (only when changing volume for the current device),
         // except for BT SCO stream where only explicit mute is allowed to comply to BT requirements
-        if ((streamType != AudioSystem.STREAM_BLUETOOTH_SCO)
-                && (getDeviceForStream(stream) == device)) {
+        if ((streamType != AudioSystem.STREAM_BLUETOOTH_SCO) && canChangeMute) {
             // As adjustStreamVolume with muteAdjust flags mute/unmutes stream and aliased streams.
             muteAliasStreams(stream, index == 0);
         }
@@ -4494,7 +4503,10 @@
 
         if (!mSoundDoseHelper.willDisplayWarningAfterCheckVolume(streamType, index, device,
                 flags)) {
-            onSetStreamVolume(streamType, index, flags, device, caller, hasModifyAudioSettings);
+            onSetStreamVolume(streamType, index, flags, device, caller, hasModifyAudioSettings,
+                    // ada is non-null when called from setDeviceVolume,
+                    // which shouldn't update the mute state
+                    ada == null /*canChangeMute*/);
             index = mStreamStates[streamType].getIndex(device);
         }
 
diff --git a/services/core/java/com/android/server/audio/AudioSystemAdapter.java b/services/core/java/com/android/server/audio/AudioSystemAdapter.java
index 7839ada..7af7ed5 100644
--- a/services/core/java/com/android/server/audio/AudioSystemAdapter.java
+++ b/services/core/java/com/android/server/audio/AudioSystemAdapter.java
@@ -37,8 +37,12 @@
 import com.android.internal.annotations.GuardedBy;
 
 import java.io.PrintWriter;
+import java.time.Instant;
+import java.time.ZoneId;
+import java.time.format.DateTimeFormatter;
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Locale;
 import java.util.Map;
 import java.util.concurrent.ConcurrentHashMap;
 
@@ -68,11 +72,14 @@
     private String[] mMethodNames = {"getDevicesForAttributes"};
 
     private static final boolean USE_CACHE_FOR_GETDEVICES = true;
+    private static final Object sDeviceCacheLock = new Object();
     private ConcurrentHashMap<Pair<AudioAttributes, Boolean>, ArrayList<AudioDeviceAttributes>>
             mLastDevicesForAttr = new ConcurrentHashMap<>();
+    @GuardedBy("sDeviceCacheLock")
     private ConcurrentHashMap<Pair<AudioAttributes, Boolean>, ArrayList<AudioDeviceAttributes>>
             mDevicesForAttrCache;
-    private final Object mDeviceCacheLock = new Object();
+    @GuardedBy("sDeviceCacheLock")
+    private long mDevicesForAttributesCacheClearTimeMs = System.currentTimeMillis();
     private int[] mMethodCacheHit;
     /**
      * Map that stores all attributes + forVolume pairs that are registered for
@@ -249,9 +256,11 @@
             AudioSystem.setRoutingCallback(sSingletonDefaultAdapter);
             AudioSystem.setVolumeRangeInitRequestCallback(sSingletonDefaultAdapter);
             if (USE_CACHE_FOR_GETDEVICES) {
-                sSingletonDefaultAdapter.mDevicesForAttrCache =
-                        new ConcurrentHashMap<>(AudioSystem.getNumStreamTypes());
-                sSingletonDefaultAdapter.mMethodCacheHit = new int[NB_MEASUREMENTS];
+                synchronized (sDeviceCacheLock) {
+                    sSingletonDefaultAdapter.mDevicesForAttrCache =
+                            new ConcurrentHashMap<>(AudioSystem.getNumStreamTypes());
+                    sSingletonDefaultAdapter.mMethodCacheHit = new int[NB_MEASUREMENTS];
+                }
             }
             if (ENABLE_GETDEVICES_STATS) {
                 sSingletonDefaultAdapter.mMethodCallCounter = new int[NB_MEASUREMENTS];
@@ -265,8 +274,9 @@
         if (DEBUG_CACHE) {
             Log.d(TAG, "---- clearing cache ----------");
         }
-        synchronized (mDeviceCacheLock) {
+        synchronized (sDeviceCacheLock) {
             if (mDevicesForAttrCache != null) {
+                mDevicesForAttributesCacheClearTimeMs = System.currentTimeMillis();
                 // Save latest cache to determine routing updates
                 mLastDevicesForAttr.putAll(mDevicesForAttrCache);
 
@@ -298,7 +308,7 @@
         if (USE_CACHE_FOR_GETDEVICES) {
             ArrayList<AudioDeviceAttributes> res;
             final Pair<AudioAttributes, Boolean> key = new Pair(attributes, forVolume);
-            synchronized (mDeviceCacheLock) {
+            synchronized (sDeviceCacheLock) {
                 res = mDevicesForAttrCache.get(key);
                 if (res == null) {
                     res = AudioSystem.getDevicesForAttributes(attributes, forVolume);
@@ -656,23 +666,31 @@
      */
     public void dump(PrintWriter pw) {
         pw.println("\nAudioSystemAdapter:");
-        pw.println(" mDevicesForAttrCache:");
-        if (mDevicesForAttrCache != null) {
-            for (Map.Entry<Pair<AudioAttributes, Boolean>, ArrayList<AudioDeviceAttributes>>
-                    entry : mDevicesForAttrCache.entrySet()) {
-                final AudioAttributes attributes = entry.getKey().first;
-                try {
-                    final int stream = attributes.getVolumeControlStream();
-                    pw.println("\t" + attributes + " forVolume: " + entry.getKey().second
-                            + " stream: "
-                            + AudioSystem.STREAM_NAMES[stream] + "(" + stream + ")");
-                    for (AudioDeviceAttributes devAttr : entry.getValue()) {
-                        pw.println("\t\t" + devAttr);
+        final DateTimeFormatter formatter = DateTimeFormatter
+                .ofPattern("MM-dd HH:mm:ss:SSS")
+                .withLocale(Locale.US)
+                .withZone(ZoneId.systemDefault());
+        synchronized (sDeviceCacheLock) {
+            pw.println(" last cache clear time: " + formatter.format(
+                    Instant.ofEpochMilli(mDevicesForAttributesCacheClearTimeMs)));
+            pw.println(" mDevicesForAttrCache:");
+            if (mDevicesForAttrCache != null) {
+                for (Map.Entry<Pair<AudioAttributes, Boolean>, ArrayList<AudioDeviceAttributes>>
+                        entry : mDevicesForAttrCache.entrySet()) {
+                    final AudioAttributes attributes = entry.getKey().first;
+                    try {
+                        final int stream = attributes.getVolumeControlStream();
+                        pw.println("\t" + attributes + " forVolume: " + entry.getKey().second
+                                + " stream: "
+                                + AudioSystem.STREAM_NAMES[stream] + "(" + stream + ")");
+                        for (AudioDeviceAttributes devAttr : entry.getValue()) {
+                            pw.println("\t\t" + devAttr);
+                        }
+                    } catch (IllegalArgumentException e) {
+                        // dump could fail if attributes do not map to a stream.
+                        pw.println("\t dump failed for attributes: " + attributes);
+                        Log.e(TAG, "dump failed", e);
                     }
-                } catch (IllegalArgumentException e) {
-                    // dump could fail if attributes do not map to a stream.
-                    pw.println("\t dump failed for attributes: " + attributes);
-                    Log.e(TAG, "dump failed", e);
                 }
             }
         }
diff --git a/services/core/java/com/android/server/audio/SoundDoseHelper.java b/services/core/java/com/android/server/audio/SoundDoseHelper.java
index bc61b37..42031c6 100644
--- a/services/core/java/com/android/server/audio/SoundDoseHelper.java
+++ b/services/core/java/com/android/server/audio/SoundDoseHelper.java
@@ -404,7 +404,8 @@
                         mPendingVolumeCommand.mIndex,
                         mPendingVolumeCommand.mFlags,
                         mPendingVolumeCommand.mDevice,
-                        callingPackage, true /*hasModifyAudioSettings*/);
+                        callingPackage, true /*hasModifyAudioSettings*/,
+                        true /*canChangeMute*/);
                 mPendingVolumeCommand = null;
             }
         }
diff --git a/services/core/java/com/android/server/display/AutomaticBrightnessController.java b/services/core/java/com/android/server/display/AutomaticBrightnessController.java
index 05464c8..592daa6 100644
--- a/services/core/java/com/android/server/display/AutomaticBrightnessController.java
+++ b/services/core/java/com/android/server/display/AutomaticBrightnessController.java
@@ -402,7 +402,6 @@
             boolean userChangedAutoBrightnessAdjustment, int displayPolicy,
             boolean shouldResetShortTermModel) {
         mState = state;
-        mHbmController.setAutoBrightnessEnabled(mState);
         // While dozing, the application processor may be suspended which will prevent us from
         // receiving new information from the light sensor. On some devices, we may be able to
         // switch to a wake-up light sensor instead but for now we will simply disable the sensor
@@ -466,7 +465,6 @@
         mHandler.sendEmptyMessage(MSG_RUN_UPDATE);
     }
 
-    @VisibleForTesting
     float getAmbientLux() {
         return mAmbientLux;
     }
diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java
index fc2a4e5..abb0ff6 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController.java
@@ -719,7 +719,8 @@
     }
 
     @Override
-    public void setBrightnessToFollow(float leadDisplayBrightness, float nits) {
+    public void setBrightnessToFollow(float leadDisplayBrightness, float nits, float ambientLux) {
+        mHbmController.onAmbientLuxChange(ambientLux);
         if (mAutomaticBrightnessController == null || nits < 0) {
             mBrightnessToFollow = leadDisplayBrightness;
         } else {
@@ -751,7 +752,8 @@
         }
         for (int i = 0; i < followers.size(); i++) {
             DisplayPowerControllerInterface follower = followers.valueAt(i);
-            follower.setBrightnessToFollow(PowerManager.BRIGHTNESS_INVALID_FLOAT, /* nits= */ -1);
+            follower.setBrightnessToFollow(PowerManager.BRIGHTNESS_INVALID_FLOAT, /* nits= */ -1,
+                    /* ambientLux= */ 0);
         }
     }
 
@@ -1523,6 +1525,9 @@
                     mShouldResetShortTermModel);
             mShouldResetShortTermModel = false;
         }
+        mHbmController.setAutoBrightnessEnabled(mUseAutoBrightness
+                ? AutomaticBrightnessController.AUTO_BRIGHTNESS_ENABLED
+                : AutomaticBrightnessController.AUTO_BRIGHTNESS_DISABLED);
 
         if (mBrightnessTracker != null) {
             mBrightnessTracker.setBrightnessConfiguration(mBrightnessConfiguration);
@@ -1634,9 +1639,12 @@
             mAppliedThrottling = false;
         }
 
+        float ambientLux = mAutomaticBrightnessController == null ? 0
+                : mAutomaticBrightnessController.getAmbientLux();
         for (int i = 0; i < displayBrightnessFollowers.size(); i++) {
             DisplayPowerControllerInterface follower = displayBrightnessFollowers.valueAt(i);
-            follower.setBrightnessToFollow(rawBrightnessState, convertToNits(rawBrightnessState));
+            follower.setBrightnessToFollow(rawBrightnessState, convertToNits(rawBrightnessState),
+                    ambientLux);
         }
 
         if (updateScreenBrightnessSetting) {
diff --git a/services/core/java/com/android/server/display/DisplayPowerController2.java b/services/core/java/com/android/server/display/DisplayPowerController2.java
index a744415..2e91bdb 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController2.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController2.java
@@ -1236,6 +1236,9 @@
                     mShouldResetShortTermModel);
             mShouldResetShortTermModel = false;
         }
+        mHbmController.setAutoBrightnessEnabled(mUseAutoBrightness
+                ? AutomaticBrightnessController.AUTO_BRIGHTNESS_ENABLED
+                : AutomaticBrightnessController.AUTO_BRIGHTNESS_DISABLED);
 
         if (mBrightnessTracker != null) {
             mBrightnessTracker.setBrightnessConfiguration(mBrightnessConfiguration);
@@ -1347,9 +1350,12 @@
             mAppliedThrottling = false;
         }
 
+        float ambientLux = mAutomaticBrightnessController == null ? 0
+                : mAutomaticBrightnessController.getAmbientLux();
         for (int i = 0; i < displayBrightnessFollowers.size(); i++) {
             DisplayPowerControllerInterface follower = displayBrightnessFollowers.valueAt(i);
-            follower.setBrightnessToFollow(rawBrightnessState, convertToNits(rawBrightnessState));
+            follower.setBrightnessToFollow(rawBrightnessState, convertToNits(rawBrightnessState),
+                    ambientLux);
         }
 
         if (updateScreenBrightnessSetting) {
@@ -2138,7 +2144,8 @@
     }
 
     @Override
-    public void setBrightnessToFollow(float leadDisplayBrightness, float nits) {
+    public void setBrightnessToFollow(float leadDisplayBrightness, float nits, float ambientLux) {
+        mHbmController.onAmbientLuxChange(ambientLux);
         if (mAutomaticBrightnessController == null || nits < 0) {
             mDisplayBrightnessController.setBrightnessToFollow(leadDisplayBrightness);
         } else {
@@ -2219,7 +2226,8 @@
         }
         for (int i = 0; i < followers.size(); i++) {
             DisplayPowerControllerInterface follower = followers.valueAt(i);
-            follower.setBrightnessToFollow(PowerManager.BRIGHTNESS_INVALID_FLOAT, /* nits= */ -1);
+            follower.setBrightnessToFollow(PowerManager.BRIGHTNESS_INVALID_FLOAT, /* nits= */ -1,
+                    /* ambientLux= */ 0);
         }
     }
 
diff --git a/services/core/java/com/android/server/display/DisplayPowerControllerInterface.java b/services/core/java/com/android/server/display/DisplayPowerControllerInterface.java
index a95ac74..4612ec9 100644
--- a/services/core/java/com/android/server/display/DisplayPowerControllerInterface.java
+++ b/services/core/java/com/android/server/display/DisplayPowerControllerInterface.java
@@ -173,9 +173,11 @@
      * displays.
      * @param leadDisplayBrightness The brightness of the lead display in the set of concurrent
      *                              displays
-     * @param nits The brightness value in nits if the device supports nits
+     * @param nits The brightness value in nits if the device supports nits. Set to a negative
+     *             number otherwise.
+     * @param ambientLux The lux value that will be passed to {@link HighBrightnessModeController}
      */
-    void setBrightnessToFollow(float leadDisplayBrightness, float nits);
+    void setBrightnessToFollow(float leadDisplayBrightness, float nits, float ambientLux);
 
     /**
      * Add an additional display that will copy the brightness value from this display. This is used
diff --git a/services/core/java/com/android/server/location/gnss/ExponentialBackOff.java b/services/core/java/com/android/server/location/gnss/ExponentialBackOff.java
index 05a534f..aab52ce 100644
--- a/services/core/java/com/android/server/location/gnss/ExponentialBackOff.java
+++ b/services/core/java/com/android/server/location/gnss/ExponentialBackOff.java
@@ -44,5 +44,14 @@
     void reset() {
         mCurrentIntervalMillis = mInitIntervalMillis / MULTIPLIER;
     }
+
+    @Override
+    public String toString() {
+        return "ExponentialBackOff{"
+                + "mInitIntervalMillis=" + mInitIntervalMillis
+                + ", mMaxIntervalMillis=" + mMaxIntervalMillis
+                + ", mCurrentIntervalMillis=" + mCurrentIntervalMillis
+                + '}';
+    }
 }
 
diff --git a/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java b/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java
index 262013e..e48412a 100644
--- a/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java
+++ b/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java
@@ -1695,6 +1695,7 @@
         pw.println("mFixInterval=" + mFixInterval);
         pw.print(mGnssMetrics.dumpGnssMetricsAsText());
         if (dumpAll) {
+            mNetworkTimeHelper.dump(pw);
             pw.println("mSupportsPsds=" + mSupportsPsds);
             pw.println(
                     "PsdsServerConfigured=" + mGnssConfiguration.isLongTermPsdsServerConfigured());
diff --git a/services/core/java/com/android/server/location/gnss/NetworkTimeHelper.java b/services/core/java/com/android/server/location/gnss/NetworkTimeHelper.java
index 72d6f70..3a25146 100644
--- a/services/core/java/com/android/server/location/gnss/NetworkTimeHelper.java
+++ b/services/core/java/com/android/server/location/gnss/NetworkTimeHelper.java
@@ -15,9 +15,14 @@
  */
 package com.android.server.location.gnss;
 
+import android.annotation.CurrentTimeMillisLong;
+import android.annotation.ElapsedRealtimeLong;
+import android.annotation.NonNull;
 import android.content.Context;
 import android.os.Looper;
 
+import java.io.PrintWriter;
+
 /**
  * An abstraction for use by {@link GnssLocationProvider}. This class allows switching between
  * implementations with a compile-time constant change, which is less risky than rolling back a
@@ -32,15 +37,16 @@
      * the looper passed to {@link #create(Context, Looper, InjectTimeCallback)}.
      */
     interface InjectTimeCallback {
-        void injectTime(long unixEpochTimeMillis, long elapsedRealtimeMillis,
-                int uncertaintyMillis);
+        void injectTime(@CurrentTimeMillisLong long unixEpochTimeMillis,
+                @ElapsedRealtimeLong long elapsedRealtimeMillis, int uncertaintyMillis);
     }
 
     /**
      * Creates the {@link NetworkTimeHelper} instance for use by {@link GnssLocationProvider}.
      */
     static NetworkTimeHelper create(
-            Context context, Looper looper, InjectTimeCallback injectTimeCallback) {
+            @NonNull Context context, @NonNull Looper looper,
+            @NonNull InjectTimeCallback injectTimeCallback) {
         return new NtpNetworkTimeHelper(context, looper, injectTimeCallback);
     }
 
@@ -72,4 +78,8 @@
      */
     abstract void onNetworkAvailable();
 
+    /**
+     * Dumps internal state during bugreports useful for debugging.
+     */
+    abstract void dump(@NonNull PrintWriter pw);
 }
diff --git a/services/core/java/com/android/server/location/gnss/NtpNetworkTimeHelper.java b/services/core/java/com/android/server/location/gnss/NtpNetworkTimeHelper.java
index 479dbda..d682164 100644
--- a/services/core/java/com/android/server/location/gnss/NtpNetworkTimeHelper.java
+++ b/services/core/java/com/android/server/location/gnss/NtpNetworkTimeHelper.java
@@ -23,12 +23,17 @@
 import android.os.Looper;
 import android.os.PowerManager;
 import android.os.PowerManager.WakeLock;
+import android.os.SystemClock;
+import android.util.IndentingPrintWriter;
+import android.util.LocalLog;
 import android.util.Log;
 import android.util.NtpTrustedTime;
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 
+import java.io.PrintWriter;
+
 /**
  * Handles injecting network time to GNSS by explicitly making NTP requests when needed.
  */
@@ -60,6 +65,9 @@
     private static final long WAKELOCK_TIMEOUT_MILLIS = 60 * 1000;
     private static final String WAKELOCK_KEY = "NtpTimeHelper";
 
+    private final LocalLog mDumpLog = new LocalLog(10, /*useLocalTimestamps=*/false);
+
+    @GuardedBy("this")
     private final ExponentialBackOff mNtpBackOff = new ExponentialBackOff(RETRY_INTERVAL,
             MAX_RETRY_INTERVAL);
 
@@ -103,16 +111,39 @@
 
     @Override
     void demandUtcTimeInjection() {
-        retrieveAndInjectNtpTime();
+        retrieveAndInjectNtpTime("demandUtcTimeInjection");
     }
 
     @Override
     synchronized void onNetworkAvailable() {
         if (mInjectNtpTimeState == STATE_PENDING_NETWORK) {
-            retrieveAndInjectNtpTime();
+            retrieveAndInjectNtpTime("onNetworkAvailable");
         }
     }
 
+    @Override
+    void dump(PrintWriter pw) {
+        pw.println("NtpNetworkTimeHelper:");
+
+        IndentingPrintWriter ipw = new IndentingPrintWriter(pw, "  ");
+        ipw.increaseIndent();
+        synchronized (this) {
+            ipw.println("mInjectNtpTimeState=" + mInjectNtpTimeState);
+            ipw.println("mPeriodicTimeInjection=" + mPeriodicTimeInjection);
+            ipw.println("mNtpBackOff=" + mNtpBackOff);
+        }
+
+        ipw.println("Debug log:");
+        ipw.increaseIndent();
+        mDumpLog.dump(ipw);
+        ipw.decreaseIndent();
+
+        ipw.println("NtpTrustedTime:");
+        ipw.increaseIndent();
+        mNtpTime.dump(ipw);
+        ipw.decreaseIndent();
+    }
+
     /**
      * @return {@code true} if there is a network available for outgoing connections,
      * {@code false} otherwise.
@@ -122,14 +153,14 @@
         return activeNetworkInfo != null && activeNetworkInfo.isConnected();
     }
 
-    private synchronized void retrieveAndInjectNtpTime() {
+    private synchronized void retrieveAndInjectNtpTime(String reason) {
         if (mInjectNtpTimeState == STATE_RETRIEVING_AND_INJECTING) {
             // already downloading data
             return;
         }
         if (!isNetworkConnected()) {
             // try to inject the cached NTP time
-            injectCachedNtpTime();
+            maybeInjectCachedNtpTime(reason + "[Network not connected]");
             // try again when network is up
             mInjectNtpTimeState = STATE_PENDING_NETWORK;
             return;
@@ -143,7 +174,8 @@
 
     /** {@link NtpTrustedTime#forceRefresh} is a blocking network operation. */
     private void blockingGetNtpTimeAndInject() {
-        long delay;
+        long debugId = SystemClock.elapsedRealtime();
+        long delayMillis;
 
         // force refresh NTP cache when outdated
         boolean refreshSuccess = true;
@@ -158,26 +190,30 @@
 
             // only update when NTP time is fresh
             // If refreshSuccess is false, cacheAge does not drop down.
-            if (injectCachedNtpTime()) {
-                delay = NTP_INTERVAL;
+            String injectReason = "blockingGetNtpTimeAndInject:"
+                    + ", debugId=" + debugId
+                    + ", refreshSuccess=" + refreshSuccess;
+            if (maybeInjectCachedNtpTime(injectReason)) {
+                delayMillis = NTP_INTERVAL;
                 mNtpBackOff.reset();
             } else {
-                Log.e(TAG, "requestTime failed");
-                delay = mNtpBackOff.nextBackoffMillis();
+                logWarn("maybeInjectCachedNtpTime() returned false");
+                delayMillis = mNtpBackOff.nextBackoffMillis();
             }
 
-            if (DEBUG) {
-                Log.d(TAG, String.format(
-                        "mPeriodicTimeInjection=%s, refreshSuccess=%s, delay=%s",
-                        mPeriodicTimeInjection,
-                        refreshSuccess,
-                        delay));
-            }
             if (mPeriodicTimeInjection || !refreshSuccess) {
+                String debugMsg = "blockingGetNtpTimeAndInject: Scheduling later NTP retrieval"
+                                + ", debugId=" + debugId
+                                + ", mPeriodicTimeInjection=" + mPeriodicTimeInjection
+                                + ", refreshSuccess=" + refreshSuccess
+                                + ", delayMillis=" + delayMillis;
+                logDebug(debugMsg);
+
                 // Schedule next NTP injection.
                 // Since this is delayed, the wake lock is released right away, and will be held
                 // again when the delayed task runs.
-                mHandler.postDelayed(this::retrieveAndInjectNtpTime, delay);
+                String reason = "scheduled: debugId=" + debugId;
+                mHandler.postDelayed(() -> retrieveAndInjectNtpTime(reason), delayMillis);
             }
         }
         // release wake lock held by task
@@ -185,24 +221,41 @@
     }
 
     /** Returns true if successfully inject cached NTP time. */
-    private synchronized boolean injectCachedNtpTime() {
+    private synchronized boolean maybeInjectCachedNtpTime(String reason) {
         NtpTrustedTime.TimeResult ntpResult = mNtpTime.getCachedTimeResult();
         if (ntpResult == null || ntpResult.getAgeMillis() >= NTP_INTERVAL) {
+            String debugMsg = "maybeInjectCachedNtpTime: Not injecting latest NTP time"
+                    + ", reason=" + reason
+                    + ", ntpResult=" + ntpResult;
+            logDebug(debugMsg);
+
             return false;
         }
 
         long unixEpochTimeMillis = ntpResult.getTimeMillis();
+        long currentTimeMillis = System.currentTimeMillis();
+        String debugMsg = "maybeInjectCachedNtpTime: Injecting latest NTP time"
+                + ", reason=" + reason
+                + ", ntpResult=" + ntpResult
+                + ", System time offset millis=" + (unixEpochTimeMillis - currentTimeMillis);
+        logDebug(debugMsg);
+
         long timeReferenceMillis = ntpResult.getElapsedRealtimeMillis();
         int uncertaintyMillis = ntpResult.getUncertaintyMillis();
-        if (DEBUG) {
-            long currentTimeMillis = System.currentTimeMillis();
-            Log.d(TAG, "NTP server returned: " + unixEpochTimeMillis
-                    + " ntpResult: " + ntpResult
-                    + " system time offset: " + (unixEpochTimeMillis - currentTimeMillis));
-        }
-
         mHandler.post(() -> mCallback.injectTime(unixEpochTimeMillis, timeReferenceMillis,
                 uncertaintyMillis));
         return true;
     }
+
+    private void logWarn(String logMsg) {
+        mDumpLog.log(logMsg);
+        Log.e(TAG, logMsg);
+    }
+
+    private void logDebug(String debugMsg) {
+        mDumpLog.log(debugMsg);
+        if (DEBUG) {
+            Log.d(TAG, debugMsg);
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/location/gnss/hal/GnssNative.java b/services/core/java/com/android/server/location/gnss/hal/GnssNative.java
index edb2e5b..7618419 100644
--- a/services/core/java/com/android/server/location/gnss/hal/GnssNative.java
+++ b/services/core/java/com/android/server/location/gnss/hal/GnssNative.java
@@ -1127,13 +1127,14 @@
     }
 
     @NativeEntryPoint
-    void setTopHalCapabilities(@GnssCapabilities.TopHalCapabilityFlags int capabilities) {
+    void setTopHalCapabilities(@GnssCapabilities.TopHalCapabilityFlags int capabilities,
+            boolean isAdrCapabilityKnown) {
         // Here the bits specified by 'capabilities' are turned on. It is handled differently from
         // sub hal because top hal capabilities could be set by HIDL HAL and/or AIDL HAL. Each of
         // them possesses a different set of capabilities.
         mTopFlags |= capabilities;
         GnssCapabilities oldCapabilities = mCapabilities;
-        mCapabilities = oldCapabilities.withTopHalFlags(mTopFlags);
+        mCapabilities = oldCapabilities.withTopHalFlags(mTopFlags, isAdrCapabilityKnown);
         onCapabilitiesChanged(oldCapabilities, mCapabilities);
     }
 
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsService.java b/services/core/java/com/android/server/locksettings/LockSettingsService.java
index 48e0c30..ebc18bc 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsService.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsService.java
@@ -29,6 +29,7 @@
 import static android.content.Context.KEYGUARD_SERVICE;
 import static android.content.pm.PackageManager.PERMISSION_GRANTED;
 import static android.os.UserHandle.USER_ALL;
+import static android.provider.DeviceConfig.NAMESPACE_AUTO_PIN_CONFIRMATION;
 
 import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_NONE;
 import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PASSWORD;
@@ -1689,6 +1690,13 @@
         if (newCredential.isPattern()) {
             setBoolean(LockPatternUtils.PATTERN_EVER_CHOSEN_KEY, true, userHandle);
         }
+        if (DeviceConfig.getBoolean(NAMESPACE_AUTO_PIN_CONFIRMATION,
+                "enable_auto_pin_confirmation", /* defaultValue= */ false)) {
+            if (newCredential.isPin()) {
+                setLong(LockPatternUtils.PIN_LENGTH, newCredential.size(), userHandle);
+            }
+        }
+
         updatePasswordHistory(newCredential, userHandle);
         mContext.getSystemService(TrustManager.class).reportEnabledTrustAgentsChanged(userHandle);
     }
diff --git a/services/core/java/com/android/server/notification/ManagedServices.java b/services/core/java/com/android/server/notification/ManagedServices.java
index 669ea95..03e4f09 100644
--- a/services/core/java/com/android/server/notification/ManagedServices.java
+++ b/services/core/java/com/android/server/notification/ManagedServices.java
@@ -1740,6 +1740,14 @@
             return ManagedServices.this;
         }
 
+        public IInterface getService() {
+            return service;
+        }
+
+        public boolean isSystem() {
+            return isSystem;
+        }
+
         @Override
         public String toString() {
             return new StringBuilder("ManagedServiceInfo[")
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index b06c411..5ab4a42 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -521,6 +521,8 @@
     private PackageManager mPackageManagerClient;
     PackageManagerInternal mPackageManagerInternal;
     private PermissionPolicyInternal mPermissionPolicyInternal;
+
+    private PermissionManagerServiceInternal mPermissionInternal;
     AudioManager mAudioManager;
     AudioManagerInternal mAudioManagerInternal;
     // Can be null for wear
@@ -2210,7 +2212,8 @@
             TelephonyManager telephonyManager, ActivityManagerInternal ami,
             MultiRateLimiter toastRateLimiter, PermissionHelper permissionHelper,
             UsageStatsManagerInternal usageStatsManagerInternal,
-            TelecomManager telecomManager, NotificationChannelLogger channelLogger) {
+            TelecomManager telecomManager, NotificationChannelLogger channelLogger,
+            PermissionManagerServiceInternal permInternal) {
         mHandler = handler;
         Resources resources = getContext().getResources();
         mMaxPackageEnqueueRate = Settings.Global.getFloat(getContext().getContentResolver(),
@@ -2228,6 +2231,7 @@
         mPackageManagerClient = packageManagerClient;
         mPackageManagerInternal = LocalServices.getService(PackageManagerInternal.class);
         mPermissionPolicyInternal = LocalServices.getService(PermissionPolicyInternal.class);
+        mPermissionInternal = permInternal;
         mUmInternal = LocalServices.getService(UserManagerInternal.class);
         mUsageStatsManagerInternal = usageStatsManagerInternal;
         mAppOps = appOps;
@@ -2539,7 +2543,8 @@
                         AppGlobals.getPermissionManager()),
                 LocalServices.getService(UsageStatsManagerInternal.class),
                 getContext().getSystemService(TelecomManager.class),
-                new NotificationChannelLoggerImpl());
+                new NotificationChannelLoggerImpl(),
+                LocalServices.getService(PermissionManagerServiceInternal.class));
 
         publishBinderService(Context.NOTIFICATION_SERVICE, mService, /* allowIsolated= */ false,
                 DUMP_FLAG_PRIORITY_CRITICAL | DUMP_FLAG_PRIORITY_NORMAL);
@@ -6689,17 +6694,30 @@
                 (userId == UserHandle.USER_ALL) ? USER_SYSTEM : userId);
         Notification.addFieldsFromContext(ai, notification);
 
-        int canColorize = mPackageManagerClient.checkPermission(
-                android.Manifest.permission.USE_COLORIZED_NOTIFICATIONS, pkg);
+        int canColorize = mPermissionInternal.checkPermission(
+                android.Manifest.permission.USE_COLORIZED_NOTIFICATIONS, pkg, userId);
         if (canColorize == PERMISSION_GRANTED) {
             notification.flags |= Notification.FLAG_CAN_COLORIZE;
         } else {
             notification.flags &= ~Notification.FLAG_CAN_COLORIZE;
         }
 
+        if (notification.extras.getBoolean(Notification.EXTRA_ALLOW_DURING_SETUP, false)) {
+            int hasShowDuringSetupPerm = mPermissionInternal.checkPermission(
+                    android.Manifest.permission.NOTIFICATION_DURING_SETUP, pkg, userId);
+            if (hasShowDuringSetupPerm != PERMISSION_GRANTED) {
+                notification.extras.remove(Notification.EXTRA_ALLOW_DURING_SETUP);
+                if (DBG) {
+                    Slog.w(TAG, "warning: pkg " + pkg + " attempting to show during setup"
+                            + " without holding perm "
+                            + Manifest.permission.NOTIFICATION_DURING_SETUP);
+                }
+            }
+        }
+
         if (notification.fullScreenIntent != null && ai.targetSdkVersion >= Build.VERSION_CODES.Q) {
-            int fullscreenIntentPermission = mPackageManagerClient.checkPermission(
-                    android.Manifest.permission.USE_FULL_SCREEN_INTENT, pkg);
+            int fullscreenIntentPermission = mPermissionInternal.checkPermission(
+                    android.Manifest.permission.USE_FULL_SCREEN_INTENT, pkg, userId);
             if (fullscreenIntentPermission != PERMISSION_GRANTED) {
                 notification.fullScreenIntent = null;
                 Slog.w(TAG, "Package " + pkg +
@@ -6719,7 +6737,7 @@
 
         // Ensure MediaStyle has correct permissions for remote device extras
         if (notification.isStyle(Notification.MediaStyle.class)) {
-            int hasMediaContentControlPermission = mPackageManager.checkPermission(
+            int hasMediaContentControlPermission = mPermissionInternal.checkPermission(
                     android.Manifest.permission.MEDIA_CONTENT_CONTROL, pkg, userId);
             if (hasMediaContentControlPermission != PERMISSION_GRANTED) {
                 notification.extras.remove(Notification.EXTRA_MEDIA_REMOTE_DEVICE);
@@ -6734,7 +6752,7 @@
 
         // Ensure only allowed packages have a substitute app name
         if (notification.extras.containsKey(Notification.EXTRA_SUBSTITUTE_APP_NAME)) {
-            int hasSubstituteAppNamePermission = mPackageManager.checkPermission(
+            int hasSubstituteAppNamePermission = mPermissionInternal.checkPermission(
                     permission.SUBSTITUTE_NOTIFICATION_APP_NAME, pkg, userId);
             if (hasSubstituteAppNamePermission != PERMISSION_GRANTED) {
                 notification.extras.remove(Notification.EXTRA_SUBSTITUTE_APP_NAME);
@@ -8338,15 +8356,9 @@
     }
 
     private boolean isExemptFromRateLimiting(String pkg, int userId) {
-        boolean isExemptFromRateLimiting = false;
-        try {
-            isExemptFromRateLimiting = mPackageManager.checkPermission(
-                    android.Manifest.permission.UNLIMITED_TOASTS, pkg, userId)
-                    == PackageManager.PERMISSION_GRANTED;
-        } catch (RemoteException e) {
-            Slog.e(TAG, "Failed to connect with package manager");
-        }
-        return isExemptFromRateLimiting;
+        return mPermissionInternal.checkPermission(
+                android.Manifest.permission.UNLIMITED_TOASTS, pkg, userId)
+                == PackageManager.PERMISSION_GRANTED;
     }
 
     /** Reports rate limiting toasts compat change (used when the toast was blocked). */
@@ -9962,8 +9974,9 @@
      * instance per user, we want to filter out interactions that are not for the user that the
      * given NAS is bound in.
      */
-    private boolean isInteractionVisibleToListener(ManagedServiceInfo info, int userId) {
-        boolean isAssistantService = isServiceTokenValid(info.service);
+    @VisibleForTesting
+    boolean isInteractionVisibleToListener(ManagedServiceInfo info, int userId) {
+        boolean isAssistantService = isServiceTokenValid(info.getService());
         return !isAssistantService || info.isSameUser(userId);
     }
 
@@ -9992,13 +10005,9 @@
     boolean canUseManagedServices(String pkg, Integer userId, String requiredPermission) {
         boolean canUseManagedServices = true;
         if (requiredPermission != null) {
-            try {
-                if (mPackageManager.checkPermission(requiredPermission, pkg, userId)
-                        != PackageManager.PERMISSION_GRANTED) {
-                    canUseManagedServices = false;
-                }
-            } catch (RemoteException e) {
-                Slog.e(TAG, "can't talk to pm", e);
+            if (mPermissionInternal.checkPermission(requiredPermission, pkg, userId)
+                    != PackageManager.PERMISSION_GRANTED) {
+                canUseManagedServices = false;
             }
         }
 
@@ -11265,7 +11274,7 @@
                 }
 
                 BackgroundThread.getHandler().post(() -> {
-                    if (info.isSystem || hasCompanionDevice(info)) {
+                    if (info.isSystem() || hasCompanionDevice(info)) {
                         notifyNotificationChannelGroupChanged(
                                 info, pkg, user, group, modificationType);
                     }
@@ -11348,7 +11357,7 @@
         private void notifyNotificationChannelGroupChanged(ManagedServiceInfo info,
                 final String pkg, final UserHandle user, final NotificationChannelGroup group,
                 final int modificationType) {
-            final INotificationListener listener = (INotificationListener) info.service;
+            final INotificationListener listener = (INotificationListener) info.getService();
             try {
                 listener.onNotificationChannelGroupModification(pkg, user, group, modificationType);
             } catch (RemoteException ex) {
diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java
index a4c9baa..5ba75a5 100644
--- a/services/core/java/com/android/server/pm/InstallPackageHelper.java
+++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java
@@ -2516,10 +2516,10 @@
             // will be null whereas dataOwnerPkg will contain information about the package
             // which was uninstalled while keeping its data.
             AndroidPackage dataOwnerPkg = mPm.mPackages.get(packageName);
+            PackageSetting dataOwnerPs = mPm.mSettings.getPackageLPr(packageName);
             if (dataOwnerPkg  == null) {
-                PackageSetting ps = mPm.mSettings.getPackageLPr(packageName);
-                if (ps != null) {
-                    dataOwnerPkg = ps.getPkg();
+                if (dataOwnerPs != null) {
+                    dataOwnerPkg = dataOwnerPs.getPkg();
                 }
             }
 
@@ -2547,6 +2547,7 @@
             if (dataOwnerPkg != null && !dataOwnerPkg.isSdkLibrary()) {
                 if (!PackageManagerServiceUtils.isDowngradePermitted(installFlags,
                         dataOwnerPkg.isDebuggable())) {
+                    // Downgrade is not permitted; a lower version of the app will not be allowed
                     try {
                         PackageManagerServiceUtils.checkDowngrade(dataOwnerPkg, pkgLite);
                     } catch (PackageManagerException e) {
@@ -2555,6 +2556,24 @@
                         return Pair.create(
                                 PackageManager.INSTALL_FAILED_VERSION_DOWNGRADE, errorMsg);
                     }
+                } else if (dataOwnerPs.isSystem()) {
+                    // Downgrade is permitted, but system apps can't be downgraded below
+                    // the version preloaded onto the system image
+                    final PackageSetting disabledPs = mPm.mSettings.getDisabledSystemPkgLPr(
+                            dataOwnerPs);
+                    if (disabledPs != null) {
+                        dataOwnerPkg = disabledPs.getPkg();
+                    }
+                    try {
+                        PackageManagerServiceUtils.checkDowngrade(dataOwnerPkg, pkgLite);
+                    } catch (PackageManagerException e) {
+                        String errorMsg = "System app: " + packageName + " cannot be downgraded to"
+                                + " older than its preloaded version on the system image. "
+                                + e.getMessage();
+                        Slog.w(TAG, errorMsg);
+                        return Pair.create(
+                                PackageManager.INSTALL_FAILED_VERSION_DOWNGRADE, errorMsg);
+                    }
                 }
             }
         }
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index e0bcc0e..db939d9 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -3770,6 +3770,9 @@
     private boolean setKeyguardOccludedLw(boolean isOccluded, boolean notify) {
         if (DEBUG_KEYGUARD) Slog.d(TAG, "setKeyguardOccluded occluded=" + isOccluded);
         mKeyguardOccludedChanged = false;
+        if (isKeyguardOccluded() == isOccluded) {
+            return false;
+        }
         mKeyguardDelegate.setOccluded(isOccluded, notify);
         return mKeyguardDelegate.isShowing();
     }
@@ -5712,44 +5715,53 @@
         switch (effectId) {
             case HapticFeedbackConstants.CONTEXT_CLICK:
             case HapticFeedbackConstants.GESTURE_END:
+            case HapticFeedbackConstants.GESTURE_THRESHOLD_ACTIVATE:
             case HapticFeedbackConstants.ROTARY_SCROLL_TICK:
+            case HapticFeedbackConstants.SEGMENT_TICK:
                 return VibrationEffect.get(VibrationEffect.EFFECT_TICK);
+
             case HapticFeedbackConstants.TEXT_HANDLE_MOVE:
                 if (!mHapticTextHandleEnabled) {
                     return null;
                 }
                 // fallthrough
             case HapticFeedbackConstants.CLOCK_TICK:
+            case HapticFeedbackConstants.SEGMENT_FREQUENT_TICK:
                 return VibrationEffect.get(VibrationEffect.EFFECT_TEXTURE_TICK);
+
             case HapticFeedbackConstants.KEYBOARD_RELEASE:
             case HapticFeedbackConstants.VIRTUAL_KEY_RELEASE:
             case HapticFeedbackConstants.ENTRY_BUMP:
             case HapticFeedbackConstants.DRAG_CROSSING:
                 return VibrationEffect.get(VibrationEffect.EFFECT_TICK, false);
+
             case HapticFeedbackConstants.KEYBOARD_TAP: // == KEYBOARD_PRESS
             case HapticFeedbackConstants.VIRTUAL_KEY:
             case HapticFeedbackConstants.EDGE_RELEASE:
+            case HapticFeedbackConstants.CALENDAR_DATE:
             case HapticFeedbackConstants.CONFIRM:
             case HapticFeedbackConstants.GESTURE_START:
             case HapticFeedbackConstants.ROTARY_SCROLL_ITEM_FOCUS:
             case HapticFeedbackConstants.ROTARY_SCROLL_LIMIT:
                 return VibrationEffect.get(VibrationEffect.EFFECT_CLICK);
+
             case HapticFeedbackConstants.LONG_PRESS:
             case HapticFeedbackConstants.LONG_PRESS_POWER_BUTTON:
+            case HapticFeedbackConstants.DRAG_START:
             case HapticFeedbackConstants.EDGE_SQUEEZE:
                 return VibrationEffect.get(VibrationEffect.EFFECT_HEAVY_CLICK);
+
             case HapticFeedbackConstants.REJECT:
                 return VibrationEffect.get(VibrationEffect.EFFECT_DOUBLE_CLICK);
 
-            case HapticFeedbackConstants.CALENDAR_DATE:
-                return VibrationEffect.get(VibrationEffect.EFFECT_CLICK);
             case HapticFeedbackConstants.SAFE_MODE_ENABLED:
                 pattern = mSafeModeEnabledVibePattern;
                 break;
 
             case HapticFeedbackConstants.ASSISTANT_BUTTON:
                 if (mVibrator.areAllPrimitivesSupported(
-                        VibrationEffect.Composition.PRIMITIVE_QUICK_RISE)) {
+                        VibrationEffect.Composition.PRIMITIVE_QUICK_RISE,
+                        VibrationEffect.Composition.PRIMITIVE_TICK)) {
                     // quiet ramp, short pause, then sharp tick
                     return VibrationEffect.startComposition()
                             .addPrimitive(VibrationEffect.Composition.PRIMITIVE_QUICK_RISE, 0.25f)
@@ -5759,6 +5771,22 @@
                 // fallback for devices without composition support
                 return VibrationEffect.get(VibrationEffect.EFFECT_HEAVY_CLICK);
 
+            case HapticFeedbackConstants.GESTURE_THRESHOLD_DEACTIVATE:
+                return getScaledPrimitiveOrElseEffect(
+                        VibrationEffect.Composition.PRIMITIVE_TICK, 0.4f,
+                        VibrationEffect.EFFECT_TEXTURE_TICK);
+
+            case HapticFeedbackConstants.TOGGLE_ON:
+                return getScaledPrimitiveOrElseEffect(
+                        VibrationEffect.Composition.PRIMITIVE_TICK, 0.5f,
+                        VibrationEffect.EFFECT_TICK);
+
+            case HapticFeedbackConstants.TOGGLE_OFF:
+                return getScaledPrimitiveOrElseEffect(
+                        VibrationEffect.Composition.PRIMITIVE_LOW_TICK, 0.2f,
+                        VibrationEffect.EFFECT_TEXTURE_TICK);
+
+            case HapticFeedbackConstants.NO_HAPTICS:
             default:
                 return null;
         }
@@ -5774,6 +5802,17 @@
         }
     }
 
+    private VibrationEffect getScaledPrimitiveOrElseEffect(int primitiveId, float scale,
+            int elseEffectId) {
+        if (mVibrator.areAllPrimitivesSupported(primitiveId)) {
+            return VibrationEffect.startComposition()
+                    .addPrimitive(primitiveId, scale)
+                    .compose();
+        } else {
+            return VibrationEffect.get(elseEffectId);
+        }
+    }
+
     private VibrationAttributes getVibrationAttributes(int effectId) {
         switch (effectId) {
             case HapticFeedbackConstants.EDGE_SQUEEZE:
diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java
index c29ab09..e8cb4e2 100644
--- a/services/core/java/com/android/server/power/PowerManagerService.java
+++ b/services/core/java/com/android/server/power/PowerManagerService.java
@@ -40,6 +40,7 @@
 import android.annotation.RequiresPermission;
 import android.annotation.UserIdInt;
 import android.app.ActivityManager;
+import android.app.ActivityManagerInternal;
 import android.app.AppOpsManager;
 import android.app.SynchronousUserSwitchObserver;
 import android.content.BroadcastReceiver;
@@ -311,6 +312,7 @@
     private SettingsObserver mSettingsObserver;
     private DreamManagerInternal mDreamManager;
     private LogicalLight mAttentionLight;
+    private ActivityManagerInternal mAmInternal;
 
     private final InattentiveSleepWarningController mInattentiveSleepWarningOverlayController;
     private final AmbientDisplaySuppressionController mAmbientDisplaySuppressionController;
@@ -1237,6 +1239,7 @@
             mDisplayManagerInternal = getLocalService(DisplayManagerInternal.class);
             mPolicy = getLocalService(WindowManagerPolicy.class);
             mBatteryManagerInternal = getLocalService(BatteryManagerInternal.class);
+            mAmInternal = getLocalService(ActivityManagerInternal.class);
             mAttentionDetector.systemReady(mContext);
 
             SensorManager sensorManager = new SystemSensorManager(mContext, mHandler.getLooper());
@@ -4077,9 +4080,8 @@
                     final UidState state = wakeLock.mUidState;
                     if (Arrays.binarySearch(mDeviceIdleWhitelist, appid) < 0 &&
                             Arrays.binarySearch(mDeviceIdleTempWhitelist, appid) < 0 &&
-                            state.mProcState != ActivityManager.PROCESS_STATE_NONEXISTENT &&
-                            state.mProcState >
-                                    ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE) {
+                            (mAmInternal != null && !mAmInternal.canHoldWakeLocksInDeepDoze(
+                                    state.mUid, state.mProcState))) {
                         disabled = true;
                     }
                 }
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 24fe518..0c97d7d 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -444,6 +444,9 @@
     // finished destroying itself.
     private static final int DESTROY_TIMEOUT = 10 * 1000;
 
+    // Rounding tolerance to be used in aspect ratio computations
+    private static final float ASPECT_RATIO_ROUNDING_TOLERANCE = 0.005f;
+
     final ActivityTaskManagerService mAtmService;
     @NonNull
     final ActivityInfo info; // activity info provided by developer in AndroidManifest
@@ -8325,8 +8328,12 @@
             if (screenResolvedBounds.width() <= parentAppBounds.width()) {
                 float positionMultiplier = mLetterboxUiController.getHorizontalPositionMultiplier(
                         newParentConfiguration);
-                offsetX = (int) Math.ceil((parentAppBounds.width() - screenResolvedBounds.width())
-                        * positionMultiplier);
+                offsetX = Math.max(0, (int) Math.ceil((parentAppBounds.width()
+                        - screenResolvedBounds.width()) * positionMultiplier)
+                        // This is added to make sure that insets added inside
+                        // CompatDisplayInsets#getContainerBounds() do not break the alignment
+                        // provided by the positionMultiplier
+                        - screenResolvedBounds.left + parentAppBounds.left);
             }
         }
 
@@ -8336,8 +8343,12 @@
             if (screenResolvedBounds.height() <= parentAppBounds.height()) {
                 float positionMultiplier = mLetterboxUiController.getVerticalPositionMultiplier(
                         newParentConfiguration);
-                offsetY = (int) Math.ceil((parentAppBounds.height() - screenResolvedBounds.height())
-                        * positionMultiplier);
+                offsetY = Math.max(0, (int) Math.ceil((parentAppBounds.height()
+                        - screenResolvedBounds.height()) * positionMultiplier)
+                        // This is added to make sure that insets added inside
+                        // CompatDisplayInsets#getContainerBounds() do not break the alignment
+                        // provided by the positionMultiplier
+                        - screenResolvedBounds.top + parentAppBounds.top);
             }
         }
 
@@ -9037,7 +9048,7 @@
         int activityWidth = containingAppWidth;
         int activityHeight = containingAppHeight;
 
-        if (containingRatio > desiredAspectRatio) {
+        if (containingRatio - desiredAspectRatio > ASPECT_RATIO_ROUNDING_TOLERANCE) {
             if (containingAppWidth < containingAppHeight) {
                 // Width is the shorter side, so we use that to figure-out what the max. height
                 // should be given the aspect ratio.
@@ -9047,7 +9058,7 @@
                 // should be given the aspect ratio.
                 activityWidth = (int) ((activityHeight * desiredAspectRatio) + 0.5f);
             }
-        } else if (containingRatio < desiredAspectRatio) {
+        } else if (desiredAspectRatio - containingRatio > ASPECT_RATIO_ROUNDING_TOLERANCE) {
             boolean adjustWidth;
             switch (getRequestedConfigurationOrientation()) {
                 case ORIENTATION_LANDSCAPE:
@@ -10129,6 +10140,7 @@
                     isLandscape ? shortSide : longSide);
         }
 
+        // TODO(b/267151420): Explore removing getContainerBounds() from CompatDisplayInsets.
         /** Gets the horizontal centered container bounds for size compatibility mode. */
         void getContainerBounds(Rect outAppBounds, Rect outBounds, int rotation, int orientation,
                 boolean orientationRequested, boolean isFixedToUserRotation) {
diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java
index cfcf459..d759ff5 100644
--- a/services/core/java/com/android/server/wm/DisplayPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayPolicy.java
@@ -1804,7 +1804,8 @@
                 dc.getDisplayPolicy().simulateLayoutDisplay(df);
                 final InsetsState insetsState = df.mInsetsState;
                 final Rect displayFrame = insetsState.getDisplayFrame();
-                final Insets decor = calculateDecorInsetsWithInternalTypes(insetsState);
+                final Insets decor = insetsState.calculateInsets(displayFrame, DECOR_TYPES,
+                        true /* ignoreVisibility */);
                 final Insets statusBar = insetsState.calculateInsets(displayFrame,
                         Type.statusBars(), true /* ignoreVisibility */);
                 mNonDecorInsets.set(decor.left, decor.top, decor.right, decor.bottom);
@@ -1836,17 +1837,8 @@
             }
         }
 
-        // TODO (b/235842600): Use public type once we can treat task bar as navigation bar.
-        static final int[] INTERNAL_DECOR_TYPES;
-        static {
-            final ArraySet<Integer> decorTypes = InsetsState.toInternalType(
-                    Type.displayCutout() | Type.navigationBars());
-            decorTypes.remove(ITYPE_EXTRA_NAVIGATION_BAR);
-            INTERNAL_DECOR_TYPES = new int[decorTypes.size()];
-            for (int i = 0; i < INTERNAL_DECOR_TYPES.length; i++) {
-                INTERNAL_DECOR_TYPES[i] = decorTypes.valueAt(i);
-            }
-        }
+
+        static final int DECOR_TYPES = Type.displayCutout() | Type.navigationBars();
 
         private final DisplayContent mDisplayContent;
         private final Info[] mInfoForRotation = new Info[4];
@@ -1873,20 +1865,6 @@
                 info.mNeedUpdate = true;
             }
         }
-
-        // TODO (b/235842600): Remove this method once we can treat task bar as navigation bar.
-        private static Insets calculateDecorInsetsWithInternalTypes(InsetsState state) {
-            final Rect frame = state.getDisplayFrame();
-            Insets insets = Insets.NONE;
-            for (int i = INTERNAL_DECOR_TYPES.length - 1; i >= 0; i--) {
-                final InsetsSource source = state.peekSource(INTERNAL_DECOR_TYPES[i]);
-                if (source != null) {
-                    insets = Insets.max(source.calculateInsets(frame, true /* ignoreVisibility */),
-                            insets);
-                }
-            }
-            return insets;
-        }
     }
 
     /**
diff --git a/services/core/java/com/android/server/wm/DisplayRotation.java b/services/core/java/com/android/server/wm/DisplayRotation.java
index 6f7ff5c..3404279 100644
--- a/services/core/java/com/android/server/wm/DisplayRotation.java
+++ b/services/core/java/com/android/server/wm/DisplayRotation.java
@@ -249,11 +249,13 @@
         mDeskDockRotation = readRotation(R.integer.config_deskDockRotation);
         mUndockedHdmiRotation = readRotation(R.integer.config_undockedHdmiRotation);
 
-        mRotation = readDefaultDisplayRotation(displayAddress);
+        int defaultRotation = readDefaultDisplayRotation(displayAddress);
+        mRotation = defaultRotation;
 
         if (isDefaultDisplay) {
             final Handler uiHandler = UiThread.getHandler();
-            mOrientationListener = new OrientationListener(mContext, uiHandler);
+            mOrientationListener =
+                    new OrientationListener(mContext, uiHandler, defaultRotation);
             mOrientationListener.setCurrentRotation(mRotation);
             mSettingsObserver = new SettingsObserver(uiHandler);
             mSettingsObserver.observe();
@@ -1735,8 +1737,9 @@
     private class OrientationListener extends WindowOrientationListener implements Runnable {
         transient boolean mEnabled;
 
-        OrientationListener(Context context, Handler handler) {
-            super(context, handler);
+        OrientationListener(Context context, Handler handler,
+                @Surface.Rotation int defaultRotation) {
+            super(context, handler, defaultRotation);
         }
 
         @Override
diff --git a/services/core/java/com/android/server/wm/KeyguardController.java b/services/core/java/com/android/server/wm/KeyguardController.java
index 718a9e3..e9badef 100644
--- a/services/core/java/com/android/server/wm/KeyguardController.java
+++ b/services/core/java/com/android/server/wm/KeyguardController.java
@@ -20,63 +20,47 @@
 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
 import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
 import static android.view.Display.DEFAULT_DISPLAY;
-import static android.view.WindowManager.KEYGUARD_STATE_AOD_SHOWN;
-import static android.view.WindowManager.KEYGUARD_STATE_DREAMING;
-import static android.view.WindowManager.KEYGUARD_STATE_GOING_AWAY;
-import static android.view.WindowManager.KEYGUARD_STATE_KEYGUARD_TOP;
-import static android.view.WindowManager.KEYGUARD_STATE_LOCKSCREEN_SHOWN;
-import static android.view.WindowManager.KEYGUARD_STATE_OCCLUDED;
-import static android.view.WindowManager.KEYGUARD_STATE_OFF;
-import static android.view.WindowManager.KEYGUARD_STATE_ON;
-import static android.view.WindowManager.KEYGUARD_STATE_ROOT;
-import static android.view.WindowManager.TRANSIT_CLOSE;
 import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY;
+import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY_NO_ANIMATION;
+import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY_SUBTLE_ANIMATION;
+import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY_TO_LAUNCHER_CLEAR_SNAPSHOT;
+import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY_TO_SHADE;
 import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY_WITH_WALLPAPER;
-import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_LOCKED;
 import static android.view.WindowManager.TRANSIT_KEYGUARD_GOING_AWAY;
 import static android.view.WindowManager.TRANSIT_KEYGUARD_OCCLUDE;
 import static android.view.WindowManager.TRANSIT_KEYGUARD_UNOCCLUDE;
 import static android.view.WindowManager.TRANSIT_OPEN;
 import static android.view.WindowManager.TRANSIT_TO_BACK;
-import static android.view.WindowManager.TRANSIT_TO_FRONT;
-import static android.view.WindowManager.TRANSIT_WAKE;
-import static android.view.WindowManager.keyguardStateToString;
-import static android.view.WindowManager.transitTypeToString;
-import static android.window.TransitionInfo.FLAG_OCCLUDES_KEYGUARD;
-
+import static android.view.WindowManagerPolicyConstants.KEYGUARD_GOING_AWAY_FLAG_NO_WINDOW_ANIMATIONS;
+import static android.view.WindowManagerPolicyConstants.KEYGUARD_GOING_AWAY_FLAG_SUBTLE_WINDOW_ANIMATIONS;
+import static android.view.WindowManagerPolicyConstants.KEYGUARD_GOING_AWAY_FLAG_TO_LAUNCHER_CLEAR_SNAPSHOT;
+import static android.view.WindowManagerPolicyConstants.KEYGUARD_GOING_AWAY_FLAG_TO_SHADE;
+import static android.view.WindowManagerPolicyConstants.KEYGUARD_GOING_AWAY_FLAG_WITH_WALLPAPER;
 
 import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER;
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_ATM;
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_WITH_CLASS_NAME;
 import static com.android.server.wm.ActivityTaskSupervisor.PRESERVE_WINDOWS;
 import static com.android.server.wm.KeyguardControllerProto.AOD_SHOWING;
+import static com.android.server.wm.KeyguardControllerProto.KEYGUARD_GOING_AWAY;
 import static com.android.server.wm.KeyguardControllerProto.KEYGUARD_PER_DISPLAY;
 import static com.android.server.wm.KeyguardControllerProto.KEYGUARD_SHOWING;
 
-import android.annotation.NonNull;
 import android.annotation.Nullable;
-import android.app.ActivityManager;
-import android.app.ActivityOptions;
-import android.os.Debug;
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.os.Trace;
 import android.util.Slog;
 import android.util.SparseArray;
 import android.util.proto.ProtoOutputStream;
+import android.view.Display;
 import android.view.WindowManager;
-import android.view.WindowManager.KeyguardState;
-import android.window.TransitionInfo;
 
 import com.android.internal.policy.IKeyguardDismissCallback;
 import com.android.server.inputmethod.InputMethodManagerInternal;
 import com.android.server.policy.WindowManagerPolicy;
-import com.android.server.wm.utils.StateMachine;
 
 import java.io.PrintWriter;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Optional;
 
 /**
  * Controls Keyguard occluding, dismissing and transitions depending on what kind of activities are
@@ -85,138 +69,281 @@
  * Note that everything in this class should only be accessed with the AM lock being held.
  */
 class KeyguardController {
-    private static final String TAG = TAG_WITH_CLASS_NAME ? "KeyguardController" : TAG_ATM;
 
-    private static final boolean DEBUG = true;
+    private static final String TAG = TAG_WITH_CLASS_NAME ? "KeyguardController" : TAG_ATM;
 
     static final String KEYGUARD_SLEEP_TOKEN_TAG = "keyguard";
 
     private static final int DEFER_WAKE_TRANSITION_TIMEOUT_MS = 5000;
 
-    private final ActivityTaskManagerService mService;
     private final ActivityTaskSupervisor mTaskSupervisor;
-    private final ActivityTaskManagerInternal.SleepTokenAcquirer mSleepTokenAcquirer;
-    private boolean mWaitingForWakeTransition;
     private WindowManagerService mWindowManager;
 
+    private final SparseArray<KeyguardDisplayState> mDisplayStates = new SparseArray<>();
+    private final ActivityTaskManagerService mService;
     private RootWindowContainer mRootWindowContainer;
-
-    private final SparseArray<DisplayState> mDisplayStates = new SparseArray<>();
-
-    @NonNull private final ServiceDelegate mServiceDelegate = new ServiceDelegate();
+    private final ActivityTaskManagerInternal.SleepTokenAcquirer mSleepTokenAcquirer;
+    private boolean mWaitingForWakeTransition;
 
     KeyguardController(ActivityTaskManagerService service,
             ActivityTaskSupervisor taskSupervisor) {
         mService = service;
         mTaskSupervisor = taskSupervisor;
-        mSleepTokenAcquirer = service.new SleepTokenAcquirerImpl(KEYGUARD_SLEEP_TOKEN_TAG);
+        mSleepTokenAcquirer = mService.new SleepTokenAcquirerImpl(KEYGUARD_SLEEP_TOKEN_TAG);
     }
 
     void setWindowManager(WindowManagerService windowManager) {
         mWindowManager = windowManager;
         mRootWindowContainer = mService.mRootWindowContainer;
-        mService.getTransitionController().registerLegacyListener(
-                new WindowManagerInternal.AppTransitionListener() {
-                    @Override
-                    public int onAppTransitionStartingLocked(TransitionInfo info) {
-                        final List<TransitionInfo.Change> changes = info.getChanges();
-                        if (changes.size() == 0) {
-                            Slog.e(TAG, "TransitionInfo doesn't contain change: " + info);
-                            return 0;
-                        }
-                        final ActivityManager.RunningTaskInfo taskInfo =
-                                changes.get(0).getTaskInfo();
-                        if (taskInfo == null) {
-                            Slog.e(TAG, "No RunningTaskInfo: " + info);
-                            return 0;
-                        }
-
-                        // TODO(b/242856311): Filtering condition is defined here and in SysUI
-                        // Keyguard service, which need to be in sync. For a long term, we should
-                        // define a new API for notifying occlude status from WMS to SysUI, and
-                        // the filtering logic should only exist in WM Shell.
-                        if ((info.getFlags() & TRANSIT_FLAG_KEYGUARD_LOCKED) == 0) {
-                            return 0;
-                        }
-
-                        boolean occludeOpeningApp = false;
-                        boolean occludeClosingApp = false;
-                        for (int i = 0; i < changes.size(); ++i) {
-                            final TransitionInfo.Change change = changes.get(i);
-                            if (change.hasFlags(FLAG_OCCLUDES_KEYGUARD)) {
-                                if (change.getMode() == TRANSIT_OPEN
-                                        || change.getMode() == TRANSIT_TO_FRONT) {
-                                    occludeOpeningApp = true;
-                                }
-                                if (change.getMode() == TRANSIT_CLOSE
-                                        || change.getMode() == TRANSIT_TO_BACK) {
-                                    occludeClosingApp = true;
-                                }
-                            }
-                        }
-                        final DisplayState state = getDisplayState(taskInfo.displayId);
-                        if (occludeOpeningApp && !occludeClosingApp) {
-                            state.commitOccludedStatus(true /* occluded */);
-                        } else if (!occludeOpeningApp && occludeClosingApp) {
-                            state.commitOccludedStatus(false /* occluded */);
-                        }
-                        return 0;
-                    }
-                });
     }
 
     boolean isAodShowing(int displayId) {
-        return getDisplayState(displayId).isIn(KEYGUARD_STATE_AOD_SHOWN);
+        return getDisplayState(displayId).mAodShowing;
     }
 
     /**
-     * @return {@code true} if either Keyguard or AOD are showing.
+     * @return true if either Keyguard or AOD are showing, not going away, and not being occluded
+     *         on the given display, false otherwise.
      */
     boolean isKeyguardOrAodShowing(int displayId) {
-        return getDisplayState(displayId).isIn(KEYGUARD_STATE_KEYGUARD_TOP);
+        final KeyguardDisplayState state = getDisplayState(displayId);
+        return (state.mKeyguardShowing || state.mAodShowing)
+                && !state.mKeyguardGoingAway
+                && !isDisplayOccluded(displayId);
     }
 
     /**
-     * @return {@codd true} if lock screen is showing.
+     * @return {@code true} for default display when AOD is showing, not going away. Otherwise, same
+     *         as {@link #isKeyguardOrAodShowing(int)}
+     * TODO(b/125198167): Replace isKeyguardOrAodShowing() by this logic.
      */
-    boolean isLocksScreenShowing(int displayId) {
-        // NOTE: This is only used by WindowManagerService#notifyKeyguardTrustedChanged
-        return getDisplayState(displayId).isIn(KEYGUARD_STATE_LOCKSCREEN_SHOWN);
+    boolean isKeyguardUnoccludedOrAodShowing(int displayId) {
+        final KeyguardDisplayState state = getDisplayState(displayId);
+        if (displayId == DEFAULT_DISPLAY && state.mAodShowing) {
+            return !state.mKeyguardGoingAway;
+        }
+        return isKeyguardOrAodShowing(displayId);
     }
 
     /**
-     * @return {@code true} if Keyguard is either showing or occluded.
+     * @return true if Keyguard is showing, not going away, and not being occluded on the given
+     *         display, false otherwise
+     */
+    boolean isKeyguardShowing(int displayId) {
+        final KeyguardDisplayState state = getDisplayState(displayId);
+        return state.mKeyguardShowing && !state.mKeyguardGoingAway
+                && !isDisplayOccluded(displayId);
+    }
+
+    /**
+     * @return true if Keyguard is either showing or occluded, but not going away
      */
     boolean isKeyguardLocked(int displayId) {
-        return getDisplayState(displayId).isIn(KEYGUARD_STATE_ON);
+        final KeyguardDisplayState state = getDisplayState(displayId);
+        return state.mKeyguardShowing && !state.mKeyguardGoingAway;
     }
 
     /**
+     *
      * @return true if the activity is controlling keyguard state.
      */
     boolean topActivityOccludesKeyguard(ActivityRecord r) {
-        return getDisplayState(r.getDisplayId()).topActivityOccludesKeyguard(r);
+        return getDisplayState(r.getDisplayId()).mTopOccludesActivity == r;
     }
 
     /**
      * @return {@code true} if the keyguard is going away, {@code false} otherwise.
      */
     boolean isKeyguardGoingAway(int displayId) {
-        return getDisplayState(displayId).isIn(KEYGUARD_STATE_GOING_AWAY);
+        final KeyguardDisplayState state = getDisplayState(displayId);
+        // Also check keyguard showing in case value is stale.
+        return state.mKeyguardGoingAway && state.mKeyguardShowing;
     }
 
     /**
-     * Checks whether the top activity occludes the keyguard.
+     * Update the Keyguard showing state.
      */
-    boolean isDisplayOccluded(int displayId) {
-        return getDisplayState(displayId).isIn(KEYGUARD_STATE_OCCLUDED);
+    void setKeyguardShown(int displayId, boolean keyguardShowing, boolean aodShowing) {
+        if (mRootWindowContainer.getDisplayContent(displayId).isKeyguardAlwaysUnlocked()) {
+            Slog.i(TAG, "setKeyguardShown ignoring always unlocked display " + displayId);
+            return;
+        }
+
+        final KeyguardDisplayState state = getDisplayState(displayId);
+        final boolean aodChanged = aodShowing != state.mAodShowing;
+        final boolean aodRemoved = state.mAodShowing && !aodShowing;
+        // If keyguard is going away, but SystemUI aborted the transition, need to reset state.
+        // Do not reset keyguardChanged status when only AOD is removed.
+        final boolean keyguardChanged = (keyguardShowing != state.mKeyguardShowing)
+                || (state.mKeyguardGoingAway && keyguardShowing && !aodRemoved);
+        if (aodRemoved) {
+            updateDeferTransitionForAod(false /* waiting */);
+        }
+        if (!keyguardChanged && !aodChanged) {
+            setWakeTransitionReady();
+            return;
+        }
+        EventLogTags.writeWmSetKeyguardShown(
+                displayId,
+                keyguardShowing ? 1 : 0,
+                aodShowing ? 1 : 0,
+                state.mKeyguardGoingAway ? 1 : 0,
+                "setKeyguardShown");
+
+        // Update the task snapshot if the screen will not be turned off. To make sure that the
+        // unlocking animation can animate consistent content. The conditions are:
+        // - Either AOD or keyguard changes to be showing. So if the states change individually,
+        //   the later one can be skipped to avoid taking snapshot again. While it still accepts
+        //   if both of them change to show at the same time.
+        // - Keyguard was not going away. Because if it was, the closing transition is able to
+        //   handle the snapshot.
+        // - The display state is ON. Because if AOD is not on or pulsing, the display state will
+        //   be OFF or DOZE (the path of screen off may have handled it).
+        if (((aodShowing ^ keyguardShowing) || (aodShowing && aodChanged && keyguardChanged))
+                && !state.mKeyguardGoingAway && Display.isOnState(
+                        mRootWindowContainer.getDefaultDisplay().getDisplayInfo().state)) {
+            mWindowManager.mTaskSnapshotController.snapshotForSleeping(DEFAULT_DISPLAY);
+        }
+
+        state.mKeyguardShowing = keyguardShowing;
+        state.mAodShowing = aodShowing;
+
+        if (keyguardChanged) {
+            // Irrelevant to AOD.
+            state.mKeyguardGoingAway = false;
+            if (keyguardShowing) {
+                state.mDismissalRequested = false;
+            }
+        }
+
+        // Update the sleep token first such that ensureActivitiesVisible has correct sleep token
+        // state when evaluating visibilities.
+        updateKeyguardSleepToken();
+        mRootWindowContainer.ensureActivitiesVisible(null, 0, !PRESERVE_WINDOWS);
+        InputMethodManagerInternal.get().updateImeWindowStatus(false /* disableImeIcon */);
+        setWakeTransitionReady();
+        if (aodChanged) {
+            // Ensure the new state takes effect.
+            mWindowManager.mWindowPlacerLocked.performSurfacePlacement();
+        }
+    }
+
+    private void setWakeTransitionReady() {
+        if (mWindowManager.mAtmService.getTransitionController().getCollectingTransitionType()
+                == WindowManager.TRANSIT_WAKE) {
+            mWindowManager.mAtmService.getTransitionController().setReady(
+                    mRootWindowContainer.getDefaultDisplay());
+        }
     }
 
     /**
-     * @return Whether the dream activity is on top of default display.
+     * Called when Keyguard is going away.
+     *
+     * @param flags See {@link WindowManagerPolicy#KEYGUARD_GOING_AWAY_FLAG_TO_SHADE}
+     *              etc.
      */
-    boolean isShowingDream() {
-        return getDisplayState(DEFAULT_DISPLAY).isIn(KEYGUARD_STATE_DREAMING);
+    void keyguardGoingAway(int displayId, int flags) {
+        final KeyguardDisplayState state = getDisplayState(displayId);
+        if (!state.mKeyguardShowing || state.mKeyguardGoingAway) {
+            return;
+        }
+        Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "keyguardGoingAway");
+        mService.deferWindowLayout();
+        state.mKeyguardGoingAway = true;
+        try {
+            EventLogTags.writeWmSetKeyguardShown(
+                    displayId,
+                    1 /* keyguardShowing */,
+                    state.mAodShowing ? 1 : 0,
+                    1 /* keyguardGoingAway */,
+                    "keyguardGoingAway");
+            final int transitFlags = convertTransitFlags(flags);
+            final DisplayContent dc = mRootWindowContainer.getDefaultDisplay();
+            dc.prepareAppTransition(TRANSIT_KEYGUARD_GOING_AWAY, transitFlags);
+            // We are deprecating TRANSIT_KEYGUARD_GOING_AWAY for Shell transition and use
+            // TRANSIT_FLAG_KEYGUARD_GOING_AWAY to indicate that it should animate keyguard going
+            // away.
+            dc.mAtmService.getTransitionController().requestTransitionIfNeeded(
+                    TRANSIT_TO_BACK, transitFlags, null /* trigger */, dc);
+            updateKeyguardSleepToken();
+
+            // Some stack visibility might change (e.g. docked stack)
+            mRootWindowContainer.resumeFocusedTasksTopActivities();
+            mRootWindowContainer.ensureActivitiesVisible(null, 0, !PRESERVE_WINDOWS);
+            mRootWindowContainer.addStartingWindowsForVisibleActivities();
+            mWindowManager.executeAppTransition();
+        } finally {
+            mService.continueWindowLayout();
+            Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
+        }
+    }
+
+    void dismissKeyguard(IBinder token, IKeyguardDismissCallback callback, CharSequence message) {
+        final ActivityRecord activityRecord = ActivityRecord.forTokenLocked(token);
+        if (activityRecord == null || !activityRecord.visibleIgnoringKeyguard) {
+            failCallback(callback);
+            return;
+        }
+        Slog.i(TAG, "Activity requesting to dismiss Keyguard: " + activityRecord);
+
+        // If the client has requested to dismiss the keyguard and the Activity has the flag to
+        // turn the screen on, wakeup the screen if it's the top Activity.
+        if (activityRecord.getTurnScreenOnFlag() && activityRecord.isTopRunningActivity()) {
+            mTaskSupervisor.wakeUp("dismissKeyguard");
+        }
+
+        mWindowManager.dismissKeyguard(callback, message);
+    }
+
+    private void failCallback(IKeyguardDismissCallback callback) {
+        try {
+            callback.onDismissError();
+        } catch (RemoteException e) {
+            Slog.w(TAG, "Failed to call callback", e);
+        }
+    }
+
+    private int convertTransitFlags(int keyguardGoingAwayFlags) {
+        int result = TRANSIT_FLAG_KEYGUARD_GOING_AWAY;
+        if ((keyguardGoingAwayFlags & KEYGUARD_GOING_AWAY_FLAG_TO_SHADE) != 0) {
+            result |= TRANSIT_FLAG_KEYGUARD_GOING_AWAY_TO_SHADE;
+        }
+        if ((keyguardGoingAwayFlags & KEYGUARD_GOING_AWAY_FLAG_NO_WINDOW_ANIMATIONS) != 0) {
+            result |= TRANSIT_FLAG_KEYGUARD_GOING_AWAY_NO_ANIMATION;
+        }
+        if ((keyguardGoingAwayFlags & KEYGUARD_GOING_AWAY_FLAG_WITH_WALLPAPER) != 0) {
+            result |= TRANSIT_FLAG_KEYGUARD_GOING_AWAY_WITH_WALLPAPER;
+        }
+        if ((keyguardGoingAwayFlags & KEYGUARD_GOING_AWAY_FLAG_SUBTLE_WINDOW_ANIMATIONS) != 0) {
+            result |= TRANSIT_FLAG_KEYGUARD_GOING_AWAY_SUBTLE_ANIMATION;
+        }
+        if ((keyguardGoingAwayFlags
+                & KEYGUARD_GOING_AWAY_FLAG_TO_LAUNCHER_CLEAR_SNAPSHOT) != 0) {
+            result |= TRANSIT_FLAG_KEYGUARD_GOING_AWAY_TO_LAUNCHER_CLEAR_SNAPSHOT;
+        }
+        return result;
+    }
+
+    /**
+     * @return True if we may show an activity while Keyguard is showing because we are in the
+     *         process of dismissing it anyways, false otherwise.
+     */
+    boolean canShowActivityWhileKeyguardShowing(ActivityRecord r) {
+        // Allow to show it when we are about to dismiss Keyguard. This isn't allowed if r is
+        // already the dismissing activity, in which case we don't allow it to repeatedly dismiss
+        // Keyguard.
+        final KeyguardDisplayState state = getDisplayState(r.getDisplayId());
+        return r.containsDismissKeyguardWindow() && canDismissKeyguard() && !state.mAodShowing
+                && (state.mDismissalRequested
+                || (r.canShowWhenLocked() && state.mDismissingKeyguardActivity != r));
+    }
+
+    /**
+     * @return True if we may show an activity while Keyguard is occluded, false otherwise.
+     */
+    boolean canShowWhileOccluded(boolean dismissKeyguard, boolean showWhenLocked) {
+        return showWhenLocked || dismissKeyguard
+                && !mWindowManager.isKeyguardSecure(mService.getCurrentUserId());
     }
 
     /**
@@ -224,15 +351,181 @@
      *
      * @return true if {@param r} is visible taken Keyguard state into account, false otherwise
      */
-    boolean checkKeyguardVisibility(@NonNull ActivityRecord r) {
+    boolean checkKeyguardVisibility(ActivityRecord r) {
         if (r.mDisplayContent.canShowWithInsecureKeyguard() && canDismissKeyguard()) {
             return true;
         }
-        return getDisplayState(r.mDisplayContent.getDisplayId()).checkKeyguardVisibility(r);
+
+        if (isKeyguardOrAodShowing(r.mDisplayContent.getDisplayId())) {
+            // If keyguard is showing, nothing is visible, except if we are able to dismiss Keyguard
+            // right away and AOD isn't visible.
+            return canShowActivityWhileKeyguardShowing(r);
+        } else if (isKeyguardLocked(r.getDisplayId())) {
+            return canShowWhileOccluded(r.containsDismissKeyguardWindow(), r.canShowWhenLocked());
+        } else {
+            return true;
+        }
+    }
+
+    /**
+     * Makes sure to update lockscreen occluded/dismiss/turnScreenOn state if needed before
+     * completing set all visibility
+     * ({@link ActivityTaskSupervisor#beginActivityVisibilityUpdate}).
+     */
+    void updateVisibility() {
+        for (int displayNdx = mRootWindowContainer.getChildCount() - 1;
+             displayNdx >= 0; displayNdx--) {
+            final DisplayContent display = mRootWindowContainer.getChildAt(displayNdx);
+            if (display.isRemoving() || display.isRemoved()) continue;
+            final KeyguardDisplayState state = getDisplayState(display.mDisplayId);
+            state.updateVisibility(this, display);
+            if (state.mRequestDismissKeyguard) {
+                handleDismissKeyguard(display.getDisplayId());
+            }
+        }
+    }
+
+    /**
+     * Called when occluded state changed.
+     *
+     * @param topActivity the activity that controls the state whether keyguard should
+     *      be occluded. That is the activity to be shown on top of keyguard if it requests so.
+     */
+    private void handleOccludedChanged(int displayId, @Nullable ActivityRecord topActivity) {
+        // TODO(b/113840485): Handle app transition for individual display, and apply occluded
+        // state change to secondary displays.
+        // For now, only default display fully supports occluded change. Other displays only
+        // updates keyguard sleep token on that display.
+        if (displayId != DEFAULT_DISPLAY) {
+            updateKeyguardSleepToken(displayId);
+            return;
+        }
+
+        mWindowManager.mPolicy.onKeyguardOccludedChangedLw(isDisplayOccluded(DEFAULT_DISPLAY));
+        if (isKeyguardLocked(displayId)) {
+            mService.deferWindowLayout();
+            try {
+                mRootWindowContainer.getDefaultDisplay()
+                        .requestTransitionAndLegacyPrepare(
+                                isDisplayOccluded(DEFAULT_DISPLAY)
+                                        ? TRANSIT_KEYGUARD_OCCLUDE
+                                        : TRANSIT_KEYGUARD_UNOCCLUDE, 0 /* flags */);
+                updateKeyguardSleepToken(DEFAULT_DISPLAY);
+                mWindowManager.executeAppTransition();
+            } finally {
+                mService.continueWindowLayout();
+            }
+        }
+        dismissMultiWindowModeForTaskIfNeeded(displayId, topActivity != null
+                ? topActivity.getRootTask() : null);
+    }
+
+    /**
+     * Called when keyguard going away state changed.
+     */
+    private void handleKeyguardGoingAwayChanged(DisplayContent dc) {
+        mService.deferWindowLayout();
+        try {
+            dc.prepareAppTransition(TRANSIT_KEYGUARD_GOING_AWAY, 0 /* transitFlags */);
+            // We are deprecating TRANSIT_KEYGUARD_GOING_AWAY for Shell transition and use
+            // TRANSIT_FLAG_KEYGUARD_GOING_AWAY to indicate that it should animate keyguard going
+            // away.
+            dc.mAtmService.getTransitionController().requestTransitionIfNeeded(
+                    TRANSIT_OPEN, TRANSIT_FLAG_KEYGUARD_GOING_AWAY, null /* trigger */, dc);
+            updateKeyguardSleepToken();
+            mWindowManager.executeAppTransition();
+        } finally {
+            mService.continueWindowLayout();
+        }
+    }
+
+    /**
+     * Called when somebody wants to dismiss the Keyguard via the flag.
+     */
+    private void handleDismissKeyguard(int displayId) {
+        // We only allow dismissing Keyguard via the flag when Keyguard is secure for legacy
+        // reasons, because that's how apps used to dismiss Keyguard in the secure case. In the
+        // insecure case, we actually show it on top of the lockscreen. See #canShowWhileOccluded.
+        if (!mWindowManager.isKeyguardSecure(mService.getCurrentUserId())) {
+            return;
+        }
+
+        mWindowManager.dismissKeyguard(null /* callback */, null /* message */);
+        final KeyguardDisplayState state = getDisplayState(displayId);
+        state.mDismissalRequested = true;
+
+        // If we are about to unocclude the Keyguard, but we can dismiss it without security,
+        // we immediately dismiss the Keyguard so the activity gets shown without a flicker.
+        final DisplayContent dc = mRootWindowContainer.getDefaultDisplay();
+        if (state.mKeyguardShowing && canDismissKeyguard()
+                && dc.mAppTransition.containsTransitRequest(TRANSIT_KEYGUARD_UNOCCLUDE)) {
+            mWindowManager.executeAppTransition();
+        }
+    }
+
+    boolean isDisplayOccluded(int displayId) {
+        return getDisplayState(displayId).mOccluded;
+    }
+
+    /**
+     * @return true if Keyguard can be currently dismissed without entering credentials.
+     */
+    boolean canDismissKeyguard() {
+        return mWindowManager.mPolicy.isKeyguardTrustedLw()
+                || !mWindowManager.isKeyguardSecure(mService.getCurrentUserId());
+    }
+
+    /**
+     * @return Whether the dream activity is on top of default display.
+     */
+    boolean isShowingDream() {
+        return getDisplayState(DEFAULT_DISPLAY).mShowingDream;
+    }
+
+    private void dismissMultiWindowModeForTaskIfNeeded(int displayId,
+            @Nullable Task currentTaskControllingOcclusion) {
+        // TODO(b/113840485): Handle docked stack for individual display.
+        if (!getDisplayState(displayId).mKeyguardShowing || !isDisplayOccluded(DEFAULT_DISPLAY)) {
+            return;
+        }
+
+        // Dismiss freeform windowing mode
+        if (currentTaskControllingOcclusion == null) {
+            return;
+        }
+        if (currentTaskControllingOcclusion.inFreeformWindowingMode()) {
+            currentTaskControllingOcclusion.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
+        }
+    }
+
+    private void updateKeyguardSleepToken() {
+        for (int displayNdx = mRootWindowContainer.getChildCount() - 1;
+             displayNdx >= 0; displayNdx--) {
+            final DisplayContent display = mRootWindowContainer.getChildAt(displayNdx);
+            updateKeyguardSleepToken(display.mDisplayId);
+        }
+    }
+
+    private void updateKeyguardSleepToken(int displayId) {
+        final KeyguardDisplayState state = getDisplayState(displayId);
+        if (isKeyguardUnoccludedOrAodShowing(displayId)) {
+            state.mSleepTokenAcquirer.acquire(displayId);
+        } else {
+            state.mSleepTokenAcquirer.release(displayId);
+        }
+    }
+
+    private KeyguardDisplayState getDisplayState(int displayId) {
+        KeyguardDisplayState state = mDisplayStates.get(displayId);
+        if (state == null) {
+            state = new KeyguardDisplayState(mService, displayId, mSleepTokenAcquirer);
+            mDisplayStates.append(displayId, state);
+        }
+        return state;
     }
 
     void onDisplayRemoved(int displayId) {
-        final DisplayState state = mDisplayStates.get(displayId);
+        final KeyguardDisplayState state = mDisplayStates.get(displayId);
         if (state != null) {
             state.onRemoved();
             mDisplayStates.remove(displayId);
@@ -245,15 +538,7 @@
         }
     };
 
-    /**
-     * Update if app transition should be deferred until AOD state changes.
-     *
-     * <p>Note: This is used for defer app transition before the device fully wakes up, since during
-     * wake up process, activities life cycle can be messed up due to a display sleep token.
-     *
-     * @param waiting {@code true} to defer an app transition, {@code false} to continue an app
-     *        transition.
-     */
+    // Defer transition until AOD dismissed.
     void updateDeferTransitionForAod(boolean waiting) {
         if (waiting == mWaitingForWakeTransition) {
             return;
@@ -264,1115 +549,212 @@
         // if AOD is showing, defer the wake transition until AOD state changed.
         if (waiting && isAodShowing(DEFAULT_DISPLAY)) {
             mWaitingForWakeTransition = true;
-            mService.getTransitionController().deferTransitionReady();
+            mWindowManager.mAtmService.getTransitionController().deferTransitionReady();
             mWindowManager.mH.postDelayed(mResetWaitTransition, DEFER_WAKE_TRANSITION_TIMEOUT_MS);
         } else if (!waiting) {
             // dismiss the deferring if the AOD state change or cancel awake.
             mWaitingForWakeTransition = false;
-            mService.getTransitionController().continueTransitionReady();
+            mWindowManager.mAtmService.getTransitionController().continueTransitionReady();
             mWindowManager.mH.removeCallbacks(mResetWaitTransition);
         }
     }
 
-    /**
-     * TODO(b/242851358): Remove this function once SysUI migrate to the new API.
-     */
-    @KeyguardState private static int convertToState(int displayId, boolean keyguardShowing,
-            boolean aodShowing) {
-        if (displayId == DEFAULT_DISPLAY) {
-            if (aodShowing) {
-                return KEYGUARD_STATE_AOD_SHOWN;
-            } else if (keyguardShowing) {
-                return KEYGUARD_STATE_LOCKSCREEN_SHOWN;
-            } else {
-                return KEYGUARD_STATE_OFF;
-            }
-        } else {
-            if (keyguardShowing || aodShowing) {
-                return KEYGUARD_STATE_LOCKSCREEN_SHOWN;
-            } else {
-                return KEYGUARD_STATE_OFF;
-            }
-        }
-    }
 
-    /**
-     * Update the Keyguard showing state.
-     *
-     * @deprecated Use {@link #setKeyguardState(int, int)} instead. See b/242851358
-     */
-    @Deprecated
-    void setKeyguardShown(int displayId, boolean keyguardShowing, boolean aodShowing) {
-        final DisplayState state = getDisplayState(displayId);
-        EventLogTags.writeWmSetKeyguardShown(
-                displayId,
-                keyguardShowing ? 1 : 0,
-                aodShowing ? 1 : 0,
-                state.isIn(KEYGUARD_STATE_GOING_AWAY) ? 1 : 0,
-                "setKeyguardShown");
-        setKeyguardState(displayId, convertToState(displayId, keyguardShowing, aodShowing));
-    }
-
-    /**
-     * Set keyguard state.
-     */
-    private void setKeyguardState(int displayId, @KeyguardState int newState) {
-        if (mRootWindowContainer.getDisplayContent(displayId).isKeyguardAlwaysUnlocked()) {
-            Slog.i(TAG, "setKeyguardShown ignoring always unlocked display " + displayId);
-            return;
-        }
-        if (newState != KEYGUARD_STATE_LOCKSCREEN_SHOWN
-                && newState != KEYGUARD_STATE_AOD_SHOWN
-                && newState != KEYGUARD_STATE_OFF
-                && newState != KEYGUARD_STATE_GOING_AWAY) {
-            Slog.i(TAG, "Invalid state is requested: displayId=" + displayId
-                    + ", state=" + keyguardStateToString(newState)
-                    + ", stack=" + Debug.getCallers(30));
-            return;
-        }
-        if (isKeyguardLocked(displayId) && newState == KEYGUARD_STATE_OFF) {
-            newState = KEYGUARD_STATE_GOING_AWAY;
-        }
-
-        final DisplayState state = getDisplayState(displayId);
-        // SysUI requests to show LOCKSCREEN, but the keyguard is already occluded. Ignore the
-        // requests.
-        if (state.isIn(KEYGUARD_STATE_OCCLUDED)
-                && StateMachine.isIn(newState, KEYGUARD_STATE_LOCKSCREEN_SHOWN)) {
-            Slog.w(TAG, "Ignore setKeyguardState request: OCCLUDE -> LOCK_SCREEN_SHOWN");
-            return;
-        }
-        // SysUI requests to show AOD_SHOWN again. This can happen when SysUI still uses the old
-        // API and enables AOD first, then lock screen, i.e. #setLockScreenShown(false, true), then
-        // #setLockScreenShown(true, true)
-        if (state.isIn(KEYGUARD_STATE_AOD_SHOWN)
-                && StateMachine.isIn(newState, KEYGUARD_STATE_AOD_SHOWN)) {
-            Slog.w(TAG, "Ignore setKeyguardState request: AOD_SHOWN -> AOD_SHOWN");
-            return;
-        }
-        if (state.isIn(KEYGUARD_STATE_OFF)
-                && StateMachine.isIn(newState, KEYGUARD_STATE_GOING_AWAY)) {
-            Slog.w(TAG, "Ignore setKeyguardState request: OFF -> GOING_AWAY");
-            return;
-        }
-        if (state.isIn(KEYGUARD_STATE_AOD_SHOWN)
-                && StateMachine.isIn(newState, KEYGUARD_STATE_LOCKSCREEN_SHOWN)) {
-            ActivityRecord top = getTopNonFinishingActivity(displayId);
-            if (canOcclude(top)) {
-                newState = isTopActivityDreaming(displayId) ? KEYGUARD_STATE_DREAMING
-                        : KEYGUARD_STATE_OCCLUDED;
-            }
-        }
-        state.setKeyguardState(newState);
-    }
-
-    /**
-     * Called when Keyguard is going away.
-     *
-     * @param flags See {@link WindowManagerPolicy#KEYGUARD_GOING_AWAY_FLAG_TO_SHADE}
-     *              etc.
-     *
-     * @deprecated Use {@link #setKeyguardState(int, int)}
-     */
-    void keyguardGoingAway(int displayId, int flags) {
-        // TODO(b/242851358): Remove IActivityTaskManagerService#keyguardGoingAway and SysUI should
-        // request the state change via #setKeyguardState.
-        final DisplayState state = getDisplayState(displayId);
-        EventLogTags.writeWmSetKeyguardShown(
-                displayId,
-                state.isIn(KEYGUARD_STATE_LOCKSCREEN_SHOWN) ? 1 : 0,
-                state.isIn(KEYGUARD_STATE_AOD_SHOWN) ? 1 : 0,
-                1 /* keyguardGoingAway */,
-                "keyguardGoingAway");
-        setKeyguardState(displayId, KEYGUARD_STATE_GOING_AWAY);
-    }
-
-    /**
-     * Makes sure to update lockscreen state if needed before completing set all visibility
-     * ({@link ActivityTaskSupervisor#beginActivityVisibilityUpdate}).
-     */
-    void updateVisibility() {
-        for (int displayNdx = mRootWindowContainer.getChildCount() - 1;
-                displayNdx >= 0; displayNdx--) {
-            final DisplayContent display = mRootWindowContainer.getChildAt(displayNdx);
-            if (display.isRemoving() || display.isRemoved()) continue;
-            final DisplayState state = getDisplayState(display.mDisplayId);
-            state.updateVisibility();
-        }
-    }
-
-    void dismissKeyguard(IBinder token, IKeyguardDismissCallback callback, CharSequence message) {
-        boolean ok;
-        final ActivityRecord r = ActivityRecord.forTokenLocked(token);
-        if (r == null) {
-            ok = false;
-        } else {
-            final DisplayState state = getDisplayState(r.getDisplayId());
-            ok = state.dismissKeyguard(r, callback, message);
-        }
-
-        if (!ok) {
-            try {
-                callback.onDismissError();
-            } catch (RemoteException e) {
-                Slog.w(TAG, "Failed to call callback", e);
-            }
-        }
-    }
-
-    private boolean isKeyguardSecure() {
-        return mWindowManager.isKeyguardSecure(mService.getCurrentUserId());
-    }
-
-    /**
-     * @return true if Keyguard can be currently dismissed without entering credentials.
-     */
-    private boolean canDismissKeyguard() {
-        return mWindowManager.mPolicy.isKeyguardTrustedLw() || !isKeyguardSecure();
-    }
-
-    private boolean canOcclude(@Nullable ActivityRecord r) {
-        if (r == null) {
-            return false;
-        }
-        // FLAG_CAN_SHOW_WITH_INSECURE_KEYGUARD only apply for secondary display.
-        if (r.getDisplayId() != DEFAULT_DISPLAY) {
-            final DisplayContent dc = r.getDisplayContent();
-            if (dc != null && dc.canShowWithInsecureKeyguard() && canDismissKeyguard()) {
-                return true;
-            }
-        }
-        // FLAG_DISMISS_KEYGUARD activity
-        //   Insecure: Treat as FLAG_SHOW_WHEN_LOCKED
-        //   Trusted: Actually dismiss Keyguard.
-        //   Secure: Show bouncer.
-        return r.canShowWhenLocked() || (r.containsDismissKeyguardWindow() && !isKeyguardSecure());
-    }
-
-    private boolean isTopActivityDreaming(int displayId) {
-        final DisplayContent dc = mRootWindowContainer.getDisplayContent(displayId);
-        final ActivityRecord top = getTopNonFinishingActivity(displayId);
-        return dc.getDisplayPolicy().isShowingDreamLw()
-                && top != null && top.getActivityType() == ACTIVITY_TYPE_DREAM;
-    }
-
-    @Nullable private ActivityRecord getTopNonFinishingActivity(int displayId) {
-        final DisplayContent dc = mRootWindowContainer.getDisplayContent(displayId);
-        final Task rootTask = dc == null ? null : dc.getRootTask(t ->
-                t != null && t.isFocusableAndVisible() && !t.inPinnedWindowingMode());
-        return rootTask != null ? rootTask.getTopNonFinishingActivity() : null;
-    }
-
-    private DisplayState getDisplayState(int displayId) {
-        DisplayState state = mDisplayStates.get(displayId);
-        if (state == null) {
-            state = new DisplayState(displayId, mServiceDelegate);
-            mDisplayStates.append(displayId, state);
-        }
-        return state;
-    }
-
-    void dumpDebug(ProtoOutputStream proto, long fieldId) {
-        final DisplayState default_state = getDisplayState(DEFAULT_DISPLAY);
-        final long token = proto.start(fieldId);
-        proto.write(AOD_SHOWING, default_state.isIn(KEYGUARD_STATE_AOD_SHOWN));
-        proto.write(KEYGUARD_SHOWING, default_state.isIn(KEYGUARD_STATE_LOCKSCREEN_SHOWN));
-        writeDisplayStatesToProto(proto, KEYGUARD_PER_DISPLAY);
-        proto.end(token);
-    }
-
-    private void writeDisplayStatesToProto(ProtoOutputStream proto, long fieldId) {
-        for (int i = 0; i < mDisplayStates.size(); i++) {
-            mDisplayStates.valueAt(i).dumpDebug(proto, fieldId);
-        }
-    }
-
-    void dump(PrintWriter pw, String prefix) {
-        pw.print(prefix);
-        pw.println("KeyguardController:");
-        for (int i = 0; i < mDisplayStates.size(); i++) {
-            mDisplayStates.valueAt(i).dump(pw, prefix);
-        }
-        pw.println();
-    }
-
-    /**
-     * Interface for {@link DisplayState} to access non-local information.
-     * <p>
-     * Keep this interface as small as possible, and don't let {@link DisplayState} access arbitrary
-     * large classes such as ActivityTaskSupervisor, which makes managing dependency complicated.
-     */
-    private final class ServiceDelegate {
-        boolean isKeyguardSecure() {
-            return KeyguardController.this.isKeyguardSecure();
-        }
-
-        boolean canOcclude(@Nullable ActivityRecord r) {
-            return KeyguardController.this.canOcclude(r);
-        }
-
-        boolean canDismissKeyguard() {
-            return KeyguardController.this.canDismissKeyguard();
-        }
-
-        boolean isDeviceInteractive() {
-            return mService.mWindowManager.mPowerManager.isInteractive();
-        }
-
-        void dismissKeyguard(IKeyguardDismissCallback callback, CharSequence message) {
-            mWindowManager.dismissKeyguard(callback, message);
-        }
-
-        @Nullable
-        ActivityRecord getTopNonFinishingActivity(int displayId) {
-            return KeyguardController.this.getTopNonFinishingActivity(displayId);
-        }
-
-        boolean isTopActivityDreaming(int displayId) {
-            return KeyguardController.this.isTopActivityDreaming(displayId);
-        }
-
-        void wakeUp(String reason) {
-            mTaskSupervisor.wakeUp(reason);
-        }
-
-        void forceSyncOccludedStatus(boolean occluded) {
-            if (DEBUG) {
-                Slog.d(TAG, "forceSyncOccludedStatus: occluded=" + occluded);
-            }
-            mWindowManager.mPolicy.onKeyguardOccludedChangedLw(occluded);
-            mWindowManager.mPolicy.applyKeyguardOcclusionChange(true /* notify */);
-        }
-
-        void snapshotForSleeping(int displayId) {
-            if (displayId == DEFAULT_DISPLAY) {
-                mWindowManager.mTaskSnapshotController.snapshotForSleeping(displayId);
-            }
-        }
-
-        void notifyKeyguardOccludeChanged(boolean occluded) {
-            // TODO: This updates status of KeyguardDelegate. Once we delete occlude status from
-            // KeyguardDelegate, we should remove WindowManagerPolicy#onKeyguardOccludedChangedLw.
-            mWindowManager.mPolicy.onKeyguardOccludedChangedLw(occluded);
-        }
-
-        void collect(@NonNull WindowContainer wc) {
-            mService.getTransitionController().collect(wc);
-        }
-
-        void requestTransitionIfNeeded(int displayId, @WindowManager.TransitionType int transit,
-                @WindowManager.TransitionFlags int flags) {
-            if (displayId != DEFAULT_DISPLAY) {
-                return;
-            }
-            if (DEBUG) {
-                Slog.d(TAG, "requestTransitionIfNeeded: display=" + displayId + ", transit="
-                        + transitTypeToString(transit));
-            }
-
-            final DisplayContent dc = mRootWindowContainer.getDisplayContent(displayId);
-            if (dc == null) {
-                Slog.e(TAG, "No DisplayContent exists: displayId=" + displayId);
-                return;
-            }
-
-            if (transit == TRANSIT_KEYGUARD_GOING_AWAY) {
-                dc.prepareAppTransition(TRANSIT_KEYGUARD_GOING_AWAY, flags);
-                // We are deprecating TRANSIT_KEYGUARD_GOING_AWAY for Shell transition and use
-                // TRANSIT_FLAG_KEYGUARD_GOING_AWAY to indicate that it should animate keyguard
-                // going away.
-                mService.getTransitionController().requestTransitionIfNeeded(
-                        TRANSIT_TO_BACK, flags, null /* trigger */, dc);
-            } else {
-                dc.requestTransitionAndLegacyPrepare(transit, flags);
-            }
-        }
-
-        void acquireSleepToken(int displayId, boolean ensureActivitiesVisible) {
-            mSleepTokenAcquirer.acquire(displayId);
-            if (ensureActivitiesVisible) {
-                mRootWindowContainer.ensureActivitiesVisible(null, 0, !PRESERVE_WINDOWS);
-            }
-        }
-
-        void releaseSleepToken(int displayId, boolean resumeTopActivities) {
-            mSleepTokenAcquirer.release(displayId);
-            if (resumeTopActivities) {
-                mRootWindowContainer.resumeFocusedTasksTopActivities();
-                mRootWindowContainer.ensureActivitiesVisible(null, 0, !PRESERVE_WINDOWS);
-                mRootWindowContainer.addStartingWindowsForVisibleActivities();
-
-            }
-        }
-
-        void deferWindowLayout() {
-            mService.deferWindowLayout();
-        }
-
-        void continueWindowLayout() {
-            mService.continueWindowLayout();
-        }
-
-        void executeAppTransition() {
-            mWindowManager.executeAppTransition();
-        }
-
-        private void updateDeferTransitionForAod(boolean waiting) {
-            KeyguardController.this.updateDeferTransitionForAod(waiting);
-        }
-
-        private void setWakeTransitionReady() {
-            if (mService.getTransitionController().getCollectingTransitionType() == TRANSIT_WAKE) {
-                mService.getTransitionController().setReady(
-                        mRootWindowContainer.getDefaultDisplay());
-            }
-        }
-
-        void requestLayoutRedoWallpaper(int displayId) {
-            final DisplayContent dc = mRootWindowContainer.getDisplayContent(displayId);
-            if (!dc.isSleeping() && dc.mWallpaperController.getWallpaperTarget() != null) {
-                dc.pendingLayoutChanges |= FINISH_LAYOUT_REDO_WALLPAPER;
-            }
-        }
-    };
-
-    private static class KeyguardDisplayStateMachine extends StateMachine {
-        static final int EVENT_DISMISS_KEYGUARD_ACTIVITY = 1;
-        static final int EVENT_SHOW_WHEN_LOCKED_ACTIVITY = 2;
-        static final int EVENT_CHECK_KEYGUARD_VISIBILITY = 3;
-        static final int EVENT_TOP_ACTIVITY_OCCLUDES_KEYGUARD = 4;
-        static final int EVENT_LAYOUT_CHANGES = 5;
-        static final int EVENT_DUMP = 6;
-        static final int EVENT_DISMISS_KEYGUARD_API = 7;
-        static final int EVENT_TURN_SCREEN_ON_ACTIVITY = 8;
-        final int mDisplayId;
-
-        static final class CheckKeyguardVisibilityParam {
-            boolean mRet;
-            @NonNull final ActivityRecord mActivity;
-
-            CheckKeyguardVisibilityParam(@NonNull ActivityRecord activity) {
-                mActivity = activity;
-            }
-        }
-
-        static final class DismissKeyguardParam {
-            boolean mRet;
-            @NonNull final ActivityRecord mActivity;
-            @Nullable final IKeyguardDismissCallback mCallback;
-            @Nullable final CharSequence mMessage;
-
-            DismissKeyguardParam(@NonNull ActivityRecord activity,
-                    @Nullable IKeyguardDismissCallback callback, @Nullable CharSequence message) {
-                mActivity = activity;
-                mCallback = callback;
-                mMessage = message;
-            }
-        }
-
-        static final class TopActivityOccludesKeyguardParam {
-            boolean mRet;
-            @NonNull final ActivityRecord mActivity;
-
-            TopActivityOccludesKeyguardParam(@NonNull ActivityRecord activity) {
-                mActivity = activity;
-            }
-        }
-
-        static final class DumpParam {
-            ArrayList<String> mRet = new ArrayList<>();
-            String mPrefix;
-
-            DumpParam(@NonNull String prefix) {
-                mPrefix = prefix;
-            }
-        }
-
-        KeyguardDisplayStateMachine(int displayId, @KeyguardState int initialState) {
-            super(initialState);
-            mDisplayId = displayId;
-        }
-
-        @Nullable
-        @Override
-        public Handler addStateHandler(int state, Handler handler) {
-            Handler prevHandler = super.addStateHandler(state, handler);
-            if (prevHandler != null) {
-                throw new IllegalStateException(
-                        "Duplicate state handler registration: display=" + mDisplayId
-                                + ", state=" + state);
-            }
-            return null;
-        }
-
-        @Override
-        public void transit(@KeyguardState int newState) {
-            if (DEBUG) {
-                StringBuilder sb = new StringBuilder();
-                sb.append("[ ");
-                for (Command cmd : getCommands()) {
-                    sb.append(cmd);
-                    sb.append(' ');
-                }
-                sb.append(" ]");
-                Slog.d(TAG, "State change: display=" + mDisplayId
-                        + ", current=" + keyguardStateToString(getCurrentState())
-                        + ", lastRequested=" + keyguardStateToString(getState())
-                        + ", newState=" + keyguardStateToString(newState)
-                        + ", command=" + sb
-                        + ", stack=" + Debug.getCallers(30));
-            }
-            super.transit(newState);
-        }
-
-        @Override
-        public void enter(@KeyguardState int state) {
-            if (DEBUG) {
-                Slog.d(TAG, "enter: display=" + mDisplayId + ", state="
-                        + keyguardStateToString(state));
-            }
-            super.enter(state);
-        }
-
-        @Override
-        public void exit(@KeyguardState int state) {
-            if (DEBUG) {
-                Slog.d(TAG, "exit: display=" + mDisplayId + ", state="
-                        + keyguardStateToString(state));
-            }
-            super.exit(state);
-        }
-
-        void handleDismissKeyguardActivity() {
-            handle(EVENT_DISMISS_KEYGUARD_ACTIVITY, null /* param */);
-        }
-
-        void handleTurnScreenOnActivity() {
-            handle(EVENT_TURN_SCREEN_ON_ACTIVITY, null /* param */);
-        }
-
-        boolean handleDismissKeyguard(@NonNull ActivityRecord r,
-                @Nullable IKeyguardDismissCallback callback, @Nullable CharSequence message) {
-            DismissKeyguardParam param = new DismissKeyguardParam(r, callback, message);
-            handle(EVENT_DISMISS_KEYGUARD_API, param);
-            return param.mRet;
-        }
-
-        void handleShowWhenLockedActivity() {
-            handle(EVENT_SHOW_WHEN_LOCKED_ACTIVITY, null /* param */);
-        }
-
-        void handleLayoutChanges() {
-            handle(EVENT_LAYOUT_CHANGES, null /* param */);
-        }
-
-        boolean checkKeyguardVisibility(@NonNull ActivityRecord r) {
-            final CheckKeyguardVisibilityParam param = new CheckKeyguardVisibilityParam(r);
-            handle(EVENT_CHECK_KEYGUARD_VISIBILITY, param);
-            return param.mRet;
-        }
-
-        boolean topActivityOccludesKeyguard(@NonNull ActivityRecord r) {
-            final TopActivityOccludesKeyguardParam param = new TopActivityOccludesKeyguardParam(r);
-            handle(EVENT_TOP_ACTIVITY_OCCLUDES_KEYGUARD, param);
-            return param.mRet;
-        }
-
-        ArrayList<String> handleDump(String prefix) {
-            final DumpParam param = new DumpParam(prefix);
-            handle(EVENT_DUMP, param);
-            return param.mRet;
-        }
-    }
-
-    /**
-     * Helper class for implementing handler in type-safe way.
-     */
-    private abstract static class Handler implements StateMachine.Handler {
-        @Override
-        public final boolean handle(int event, @Nullable Object param) {
-            switch (event) {
-                case KeyguardDisplayStateMachine.EVENT_DISMISS_KEYGUARD_ACTIVITY:
-                    return handleDismissKeyguardActivity();
-                case KeyguardDisplayStateMachine.EVENT_TURN_SCREEN_ON_ACTIVITY:
-                    return handleTurnScreenOnActivity();
-                case KeyguardDisplayStateMachine.EVENT_DISMISS_KEYGUARD_API: {
-                    final KeyguardDisplayStateMachine.DismissKeyguardParam typedParam =
-                            (KeyguardDisplayStateMachine.DismissKeyguardParam) param;
-                    Optional<Boolean> ret = handleDismissKeyguard(typedParam.mActivity,
-                            typedParam.mCallback, typedParam.mMessage);
-                    if (ret.isPresent()) {
-                        typedParam.mRet = ret.get();
-                        return true;
-                    }
-                    return false;
-                }
-                case KeyguardDisplayStateMachine.EVENT_SHOW_WHEN_LOCKED_ACTIVITY:
-                    return handleShowWhenLockedActivity();
-                case KeyguardDisplayStateMachine.EVENT_CHECK_KEYGUARD_VISIBILITY: {
-                    final KeyguardDisplayStateMachine.CheckKeyguardVisibilityParam typedParam =
-                            (KeyguardDisplayStateMachine.CheckKeyguardVisibilityParam) param;
-                    Optional<Boolean> ret = checkKeyguardVisibility(typedParam.mActivity);
-                    if (ret.isPresent()) {
-                        typedParam.mRet = ret.get();
-                        return true;
-                    }
-                    return false;
-                }
-                case KeyguardDisplayStateMachine.EVENT_TOP_ACTIVITY_OCCLUDES_KEYGUARD: {
-                    final KeyguardDisplayStateMachine.TopActivityOccludesKeyguardParam typedParam =
-                            (KeyguardDisplayStateMachine.TopActivityOccludesKeyguardParam) param;
-                    Optional<Boolean> ret = topActivityOccludesKeyguardParam(typedParam.mActivity);
-                    if (ret.isPresent()) {
-                        typedParam.mRet = ret.get();
-                        return true;
-                    }
-                    return false;
-                }
-                case KeyguardDisplayStateMachine.EVENT_LAYOUT_CHANGES:
-                    return handleLayoutChanges();
-                case KeyguardDisplayStateMachine.EVENT_DUMP:
-                    final KeyguardDisplayStateMachine.DumpParam typedParam =
-                            (KeyguardDisplayStateMachine.DumpParam) param;
-                    String dumpInfo = handleDump(typedParam.mPrefix);
-                    if (dumpInfo != null) {
-                        typedParam.mRet.add(dumpInfo);
-                    }
-                    // keep collecting information for dump up to top status.
-                    return false;
-                default:
-                    Slog.e(TAG, "Handler.handle(): Unknown event(" + event + ")");
-                    return false;
-            }
-        }
-
-        Optional<Boolean> checkKeyguardVisibility(@NonNull ActivityRecord activity) {
-            return Optional.empty();
-        }
-
-        Optional<Boolean> topActivityOccludesKeyguardParam(@NonNull ActivityRecord r) {
-            return Optional.empty();
-        }
-
-        /**
-         * Handle flags in the activity which request to dismiss the keyguard.
-         *
-         * @see ActivityOptions#setDismissKeyguard()
-         * @see WindowManager.LayoutParams#FLAG_DISMISS_KEYGUARD
-         */
-        boolean handleDismissKeyguardActivity() {
-            return false;
-        }
-
-        /**
-         * Handle flags in the activity which request to turn the screen on. This must be called
-         * after dismiss keyguard flag is handled.
-         */
-        boolean handleTurnScreenOnActivity() {
-            return false;
-        }
-        /**
-         * Handle flags in the activity which decides if the activity can be shown on top of the
-         * keyguard.
-         *
-         * @see android.app.Activity#setShowWhenLocked(boolean)
-         * @see android.app.Activity#setInheritShowWhenLocked(boolean)
-         */
-        boolean handleShowWhenLockedActivity() {
-            return false;
-        }
-
-        /**
-         * Request relayout if necessary.
-         */
-        boolean handleLayoutChanges() {
-            return false;
-        }
-
-        /**
-         * Called when the activity requests to dismiss the keyguard via KeyguardManager APIs.
-         *
-         * @param r The activity which requested to dismiss the keyguard.
-         * @return Present if the state handles, delegate to its parent state otherwise. When the
-         *         value is present, the value is {@code true} if the keyguard dismiss request is
-         *         processed, {@code false} otherwise.
-         */
-        Optional<Boolean> handleDismissKeyguard(@NonNull ActivityRecord r,
-                @Nullable IKeyguardDismissCallback callback, @Nullable CharSequence message) {
-            return Optional.empty();
-        }
-
-        @Nullable String handleDump(@NonNull String prefix) {
-            return null;
-        }
-    }
-
-    private static class DisplayState {
+    /** Represents Keyguard state per individual display. */
+    private static class KeyguardDisplayState {
         private final int mDisplayId;
-        @NonNull private final ServiceDelegate mServiceDelegate;
-        private final KeyguardDisplayStateMachine mStateMachine;
+        private boolean mKeyguardShowing;
+        private boolean mAodShowing;
+        private boolean mKeyguardGoingAway;
+        private boolean mDismissalRequested;
+        private boolean mOccluded;
+        private boolean mShowingDream;
 
-        // TODO: Set watchdog timer to sync mLastNotifiedOccludedState == isIn(OCCLUDED)
-        private boolean mLastNotifiedOccludedState = false;
+        private ActivityRecord mTopOccludesActivity;
+        private ActivityRecord mDismissingKeyguardActivity;
+        private ActivityRecord mTopTurnScreenOnActivity;
 
-        // Top activity which has a window with FLAG_DISMISS_KEYGUARD flag. Valid only when the
-        // current state is KEYGUARD_STATE_ON or one of its sub states.
-        @Nullable private ActivityRecord mDismissingKeyguardActivity;
+        private boolean mRequestDismissKeyguard;
+        private final ActivityTaskManagerService mService;
+        private final ActivityTaskManagerInternal.SleepTokenAcquirer mSleepTokenAcquirer;
 
-        // KeyguardController has requested to dismiss keyguard via IWindowManager#dismissKeyguard.
-        // Reset this to false again, once the KeyguardController status is updated.
-        private boolean mDismissalRequested = false;
-
-        DisplayState(int displayId, @NonNull ServiceDelegate serviceDelegate) {
+        KeyguardDisplayState(ActivityTaskManagerService service, int displayId,
+                ActivityTaskManagerInternal.SleepTokenAcquirer acquirer) {
+            mService = service;
             mDisplayId = displayId;
-            mServiceDelegate = serviceDelegate;
-            mStateMachine = new KeyguardDisplayStateMachine(displayId, KEYGUARD_STATE_OFF);
-
-            mStateMachine.addStateHandler(KEYGUARD_STATE_ROOT, new Handler() {
-                @Override
-                Optional<Boolean> checkKeyguardVisibility(@NonNull ActivityRecord activity) {
-                    return Optional.of(false);
-                }
-
-                @Override
-                Optional<Boolean> topActivityOccludesKeyguardParam(@NonNull ActivityRecord r) {
-                    return Optional.of(false);
-                }
-            });
-
-            mStateMachine.addStateHandler(KEYGUARD_STATE_OFF, new Handler() {
-                @Override
-                public Optional<Boolean> checkKeyguardVisibility(@NonNull ActivityRecord r) {
-                    return Optional.of(true);
-                }
-
-                @Override
-                Optional<Boolean> handleDismissKeyguard(
-                        @NonNull ActivityRecord r, @Nullable IKeyguardDismissCallback callback,
-                        @Nullable CharSequence message) {
-                    // Keyguard is not shown, so we don't handle the request to dismiss the
-                    // keyguard.
-                    return Optional.of(false);
-                }
-            });
-
-            mStateMachine.addStateHandler(KEYGUARD_STATE_GOING_AWAY, new Handler() {
-                @Override
-                public void enter() {
-                    mServiceDelegate.deferWindowLayout();
-                    try {
-                        mServiceDelegate.requestTransitionIfNeeded(mDisplayId,
-                                TRANSIT_KEYGUARD_GOING_AWAY,
-                                TRANSIT_FLAG_KEYGUARD_GOING_AWAY
-                                        | TRANSIT_FLAG_KEYGUARD_GOING_AWAY_WITH_WALLPAPER);
-                        // Some stack visibility might change (e.g. docked stack)
-                        mServiceDelegate.releaseSleepToken(mDisplayId,
-                                true /* resumeTopActivities */);
-                        mServiceDelegate.executeAppTransition();
-                    } finally {
-                        mServiceDelegate.continueWindowLayout();
-                        Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
-                    }
-                }
-            });
-
-            mStateMachine.addStateHandler(KEYGUARD_STATE_ON, new Handler() {
-                public boolean handleDismissKeyguardActivity() {
-                    final ActivityRecord lastDismissingKeyguardActivity =
-                            mDismissingKeyguardActivity;
-                    final ActivityRecord top = mServiceDelegate.getTopNonFinishingActivity(
-                            mDisplayId);
-                    mDismissingKeyguardActivity =
-                            (top != null && top.containsDismissKeyguardWindow()) ? top : null;
-                    if (lastDismissingKeyguardActivity != mDismissingKeyguardActivity
-                            && mDismissingKeyguardActivity != null
-                            && mServiceDelegate.isKeyguardSecure()) {
-                        // We only allow dismissing Keyguard via the flag when Keyguard is secure
-                        // for legacy reasons, because that's how apps used to dismiss Keyguard in
-                        // the secure case. In the insecure case, we actually show it on top of the
-                        // lockscreen. See #canShowWhileOccluded.
-                        mDismissalRequested = true;
-                        mServiceDelegate.dismissKeyguard(null, null);
-                    }
-                    return true;
-                }
-
-                @Override
-                Optional<Boolean> handleDismissKeyguard(@NonNull ActivityRecord r,
-                        @Nullable IKeyguardDismissCallback callback,
-                        @Nullable CharSequence message) {
-                    if (!r.visibleIgnoringKeyguard) {
-                        return Optional.of(false);
-                    }
-                    if (DEBUG) {
-                        Slog.d(TAG, "Activity requesting to dismiss Keyguard: " + r);
-                    }
-                    // If the client has requested to dismiss the keyguard and the Activity has the
-                    // flag to turn the screen on, wakeup the screen if it's the top Activity.
-                    // Note that it's possible that the client requests to dismiss the keyguard
-                    // before the activity adds a window. In this case the flag set on the window
-                    // is not yet visible from ActivityRecord, so we need to check the flag again
-                    // when the activity adds a window later. See #handleTurnScreenOnActivity().
-                    if (r.getTurnScreenOnFlag() && r.isTopRunningActivity()) {
-                        mServiceDelegate.wakeUp("ON/handleDismissKeyguard");
-                        r.setCurrentLaunchCanTurnScreenOn(false);
-                    }
-                    mDismissalRequested = true;
-                    mServiceDelegate.dismissKeyguard(callback, message);
-                    return Optional.of(true);
-                }
-
-                @Override
-                public void enter() {
-                    // Update the task snapshot if the screen will not be turned off. To make sure
-                    // that the unlocking animation can animate consistent content.
-                    mServiceDelegate.snapshotForSleeping(mDisplayId);
-                }
-
-                @Nullable
-                @Override
-                String handleDump(@NonNull String prefix) {
-                    StringBuffer sb = new StringBuffer();
-                    sb.append(prefix)
-                            .append("  mDismissingKeyguardActivity=")
-                            .append(mDismissingKeyguardActivity)
-                            .append("\n");
-                    return sb.toString();
-                }
-            });
-
-            mStateMachine.addStateHandler(KEYGUARD_STATE_OCCLUDED, new Handler() {
-                ActivityRecord mTopOccludesActivity;
-
-                @Override
-                Optional<Boolean> checkKeyguardVisibility(@NonNull ActivityRecord activity) {
-                    return Optional.of(mServiceDelegate.canOcclude(activity));
-                }
-
-                @Override
-                public boolean handleShowWhenLockedActivity() {
-                    final ActivityRecord top = mServiceDelegate.getTopNonFinishingActivity(
-                            mDisplayId);
-                    final ActivityRecord topOccludesActivity = mServiceDelegate.canOcclude(top)
-                            ? top : null;
-                    if (mTopOccludesActivity == topOccludesActivity) {
-                        return true;
-                    }
-                    // Launch SHOW_WHEN_LOCKED or INHERIT_SHOW_WHEN_LOCKED activity on top of an
-                    // occluding activity.
-                    mTopOccludesActivity = topOccludesActivity;
-                    if (mServiceDelegate.isTopActivityDreaming(mDisplayId)) {
-                        // Dream activity is launched on top of the previous SHOW_WHEN_LOCKED
-                        // activity.
-                        setKeyguardState(KEYGUARD_STATE_DREAMING);
-                    } else if (topOccludesActivity == null) {
-                        // SHOW_WHEN_LOCKED activity finishes.
-                        setKeyguardState(KEYGUARD_STATE_LOCKSCREEN_SHOWN);
-                    }
-                    return true;
-                }
-
-                @Override
-                boolean handleLayoutChanges() {
-                    // The occluding activity may be translucent or not fill screen. Then let
-                    // wallpaper to check whether it should set itself as target to avoid blank
-                    // background.
-                    if (!mTopOccludesActivity.fillsParent()) {
-                        mServiceDelegate.requestLayoutRedoWallpaper(mDisplayId);
-                    }
-                    return true;
-                }
-
-                @Override
-                Optional<Boolean> topActivityOccludesKeyguardParam(@NonNull ActivityRecord r) {
-                    return Optional.of(mTopOccludesActivity == r);
-                }
-
-                @Override
-                public void enter() {
-                    mTopOccludesActivity = mServiceDelegate.getTopNonFinishingActivity(mDisplayId);
-                    if (!mServiceDelegate.canOcclude(mTopOccludesActivity)) {
-                        Slog.e(TAG, "enter(OCCLUDE): no occluding activity");
-                        setKeyguardState(KEYGUARD_STATE_LOCKSCREEN_SHOWN);
-                        return;
-                    }
-
-                    if (DEBUG) {
-                        Slog.d(TAG, "handleOccludedChanged: display=" + mDisplayId
-                                + ", topActivity=" + mTopOccludesActivity);
-                    }
-                    // Collect the participates for shell transition, so that transition won't
-                    // happen too early since the transition was set ready.
-                    mServiceDelegate.collect(mTopOccludesActivity);
-                    // TODO(b/113840485): Handle app transition for individual display, and apply
-                    // occluded state change to secondary displays. For now, only default display
-                    // fully supports occluded change. Other displays only updates keyguard sleep
-                    // token on that display.
-                    if (mDisplayId != DEFAULT_DISPLAY) {
-                        mServiceDelegate.releaseSleepToken(mDisplayId,
-                                false /* resumeTopActivities */);
-                        return;
-                    }
-
-                    if (mTopOccludesActivity.getTurnScreenOnFlag()
-                            && mTopOccludesActivity.currentLaunchCanTurnScreenOn()
-                            && !mServiceDelegate.isDeviceInteractive()) {
-                        mServiceDelegate.wakeUp("OCCLUDE/enter");
-                        mTopOccludesActivity.setCurrentLaunchCanTurnScreenOn(false);
-                    }
-
-                    mServiceDelegate.notifyKeyguardOccludeChanged(true /* occluded */);
-                    mServiceDelegate.deferWindowLayout();
-                    try {
-                        mServiceDelegate.requestTransitionIfNeeded(mDisplayId,
-                                TRANSIT_KEYGUARD_OCCLUDE, 0 /* flags */);
-                        mServiceDelegate.releaseSleepToken(mDisplayId,
-                                false /* resumeTopActivities */);
-                        mServiceDelegate.executeAppTransition();
-                    } finally {
-                        mServiceDelegate.continueWindowLayout();
-                    }
-                    // Dismiss freeform windowing mode
-                    final Task currentTaskControllingOcclusion = mTopOccludesActivity.getRootTask();
-                    if (currentTaskControllingOcclusion != null
-                            && currentTaskControllingOcclusion.inFreeformWindowingMode()) {
-                        currentTaskControllingOcclusion.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
-                    }
-                }
-
-                @Override
-                public void exit() {
-                    mTopOccludesActivity = null;
-                    if (DEBUG) {
-                        Slog.d(TAG, "handleOccludedChanged: topActivity=" + null);
-                    }
-                    // TODO(b/113840485): Handle app transition for individual display, and apply
-                    // occluded state change to secondary displays.
-                    // For now, only default display fully supports occluded change. Other displays
-                    // only updates keyguard sleep token on that display.
-                    if (mDisplayId != DEFAULT_DISPLAY) {
-                        mServiceDelegate.acquireSleepToken(
-                                mDisplayId, false /* ensureActivitiesVisible */);
-                        return;
-                    }
-
-                    mServiceDelegate.notifyKeyguardOccludeChanged(false /* occluded */);
-                    mServiceDelegate.deferWindowLayout();
-                    try {
-                        mServiceDelegate.requestTransitionIfNeeded(mDisplayId,
-                                TRANSIT_KEYGUARD_UNOCCLUDE, 0 /* flags */);
-                        mServiceDelegate.acquireSleepToken(
-                                mDisplayId, false /* ensureActivitiesVisible */);
-                        mServiceDelegate.executeAppTransition();
-                    } finally {
-                        mServiceDelegate.continueWindowLayout();
-                    }
-                }
-
-                @Nullable
-                @Override
-                String handleDump(@NonNull String prefix) {
-                    StringBuffer sb = new StringBuffer();
-                    sb.append(prefix)
-                            .append("  mTopOccludesActivity=")
-                            .append(mTopOccludesActivity)
-                            .append("\n");
-                    return sb.toString();
-                }
-            });
-
-            mStateMachine.addStateHandler(KEYGUARD_STATE_KEYGUARD_TOP, new Handler() {
-                @Override
-                public boolean handleDismissKeyguardActivity() {
-                    final ActivityRecord top = mServiceDelegate.getTopNonFinishingActivity(
-                            mDisplayId);
-                    if (top != null && top.mDismissKeyguard) {
-                        // Top activity has been launched with ActivityOptions#setDismissKeyguard.
-                        // Authentication has already been passed, so we can turn off the keyguard
-                        // immediately.
-                        top.mDismissKeyguard = false;
-                        setKeyguardState(KEYGUARD_STATE_GOING_AWAY);
-                        // Collect the participates for shell transition, so that transition won't
-                        // happen too early since the transition was set ready.
-                        mServiceDelegate.collect(top);
-                        return true;
-                    }
-                    return false;
-                }
-
-                @Override
-                public void enter() {
-                    mServiceDelegate.acquireSleepToken(mDisplayId,
-                            true /* ensureActivitiesVisible */);
-                    InputMethodManagerInternal.get().updateImeWindowStatus(
-                            false /* disableImeIcon */);
-                    mServiceDelegate.setWakeTransitionReady();
-                }
-
-                @Override
-                public void exit() {
-                    // Sleep token is released in enter() action in other states, since we need
-                    // to call requestTransition() before updating visibility of the activities.
-                }
-            });
-
-            mStateMachine.addStateHandler(KEYGUARD_STATE_LOCKSCREEN_SHOWN, new Handler() {
-                @Override
-                public Optional<Boolean> checkKeyguardVisibility(@NonNull ActivityRecord r) {
-                    // If lock screen is showing, nothing is visible, except if we are able to
-                    // dismiss Keyguard right away. This isn't allowed if r is already the
-                    // dismissing activity, in which case we don't allow it to repeatedly
-                    // dismiss Keyguard.
-                    return Optional.of(r.containsDismissKeyguardWindow()
-                            && mServiceDelegate.canDismissKeyguard()
-                            && (mDismissalRequested
-                            || (r.canShowWhenLocked() && mDismissingKeyguardActivity != r)));
-                }
-
-                @Override
-                public boolean handleShowWhenLockedActivity() {
-                    final ActivityRecord top = mServiceDelegate.getTopNonFinishingActivity(
-                            mDisplayId);
-                    final ActivityRecord topOccludesActivity = mServiceDelegate.canOcclude(top)
-                            ? top : null;
-                    if (topOccludesActivity != null) {
-                        setKeyguardState(mServiceDelegate.isTopActivityDreaming(mDisplayId)
-                                ? KEYGUARD_STATE_DREAMING : KEYGUARD_STATE_OCCLUDED);
-                    }
-                    return true;
-                }
-            });
-
-            mStateMachine.addStateHandler(KEYGUARD_STATE_AOD_SHOWN, new Handler() {
-                // Top activity which has FLAG_TURN_SCREEN_ON flag.
-                @Nullable private ActivityRecord mTopTurnScreenOnActivity;
-
-                @Override
-                public Optional<Boolean> checkKeyguardVisibility(@NonNull ActivityRecord r) {
-                    return Optional.of(false);
-                }
-
-                @Override
-                boolean handleTurnScreenOnActivity() {
-                    final ActivityRecord lastTopTurnScreenOnActivity = mTopTurnScreenOnActivity;
-                    final ActivityRecord top = mServiceDelegate.getTopNonFinishingActivity(
-                            mDisplayId);
-                    mTopTurnScreenOnActivity = (top != null && top.getTurnScreenOnFlag()
-                            && top.currentLaunchCanTurnScreenOn()) ? top : null;
-                    if (mTopTurnScreenOnActivity != lastTopTurnScreenOnActivity
-                            && mTopTurnScreenOnActivity != null
-                            && !mServiceDelegate.isDeviceInteractive()
-                            && mDismissalRequested) {
-                        mServiceDelegate.wakeUp("AOD_SHOWN/handleTurnScreenOnActivity");
-                        mTopTurnScreenOnActivity.setCurrentLaunchCanTurnScreenOn(false);
-                    }
-                    return true;
-                }
-
-                @Override
-                public void enter() {
-                    if (mLastNotifiedOccludedState) {
-                        if (mDisplayId == DEFAULT_DISPLAY) {
-                            mServiceDelegate.forceSyncOccludedStatus(false);
-                        }
-                        commitOccludedStatus(false);
-                    }
-                }
-
-                @Override
-                public void exit() {
-                    mServiceDelegate.updateDeferTransitionForAod(false /* waiting */);
-                }
-
-                @Nullable
-                @Override
-                String handleDump(@NonNull String prefix) {
-                    StringBuffer sb = new StringBuffer();
-                    sb.append(prefix)
-                            .append("  mTopTurnScreenOnActivity=")
-                            .append(mTopTurnScreenOnActivity)
-                            .append("\n");
-                    return sb.toString();
-                }
-            });
+            mSleepTokenAcquirer = acquirer;
         }
 
         void onRemoved() {
-            mServiceDelegate.releaseSleepToken(mDisplayId, false /* resumeTopActivities */);
+            mTopOccludesActivity = null;
+            mDismissingKeyguardActivity = null;
+            mTopTurnScreenOnActivity = null;
+            mSleepTokenAcquirer.release(mDisplayId);
         }
 
-        void updateVisibility() {
-            mStateMachine.handleDismissKeyguardActivity();
-            mStateMachine.handleTurnScreenOnActivity();
-            mStateMachine.handleShowWhenLockedActivity();
-            mStateMachine.handleLayoutChanges();
+        /**
+         * Updates keyguard status if the top task could be visible. The top task may occlude
+         * keyguard, request to dismiss keyguard or make insecure keyguard go away based on its
+         * properties.
+         */
+        void updateVisibility(KeyguardController controller, DisplayContent display) {
+            final boolean lastOccluded = mOccluded;
+            final boolean lastKeyguardGoingAway = mKeyguardGoingAway;
+
+            final ActivityRecord lastDismissKeyguardActivity = mDismissingKeyguardActivity;
+            final ActivityRecord lastTurnScreenOnActivity = mTopTurnScreenOnActivity;
+
+            mRequestDismissKeyguard = false;
+            mOccluded = false;
+            mShowingDream = false;
+
+            mTopOccludesActivity = null;
+            mDismissingKeyguardActivity = null;
+            mTopTurnScreenOnActivity = null;
+
+            boolean occludedByActivity = false;
+            final Task task = getRootTaskForControllingOccluding(display);
+            final ActivityRecord top = task != null ? task.getTopNonFinishingActivity() : null;
+            if (top != null) {
+                if (top.containsDismissKeyguardWindow()) {
+                    mDismissingKeyguardActivity = top;
+                }
+                if (top.getTurnScreenOnFlag() && top.currentLaunchCanTurnScreenOn()) {
+                    mTopTurnScreenOnActivity = top;
+                }
+
+                if (top.mDismissKeyguard && mKeyguardShowing) {
+                    mKeyguardGoingAway = true;
+                } else if (top.canShowWhenLocked()) {
+                    mTopOccludesActivity = top;
+                }
+                top.mDismissKeyguard = false;
+
+                // Only the top activity may control occluded, as we can't occlude the Keyguard
+                // if the top app doesn't want to occlude it.
+                occludedByActivity = mTopOccludesActivity != null
+                        || (mDismissingKeyguardActivity != null
+                        && task.topRunningActivity() == mDismissingKeyguardActivity
+                        && controller.canShowWhileOccluded(
+                                true /* dismissKeyguard */, false /* showWhenLocked */));
+                // FLAG_CAN_SHOW_WITH_INSECURE_KEYGUARD only apply for secondary display.
+                if (mDisplayId != DEFAULT_DISPLAY) {
+                    occludedByActivity |= display.canShowWithInsecureKeyguard()
+                            && controller.canDismissKeyguard();
+                }
+            }
+
+            mShowingDream = display.getDisplayPolicy().isShowingDreamLw() && (top != null
+                    && top.getActivityType() == ACTIVITY_TYPE_DREAM);
+            mOccluded = mShowingDream || occludedByActivity;
+            mRequestDismissKeyguard = lastDismissKeyguardActivity != mDismissingKeyguardActivity
+                    && !mOccluded && !mKeyguardGoingAway
+                    && mDismissingKeyguardActivity != null;
+            if (mOccluded && mKeyguardShowing && !display.isSleeping() && !top.fillsParent()
+                    && display.mWallpaperController.getWallpaperTarget() == null) {
+                // The occluding activity may be translucent or not fill screen. Then let wallpaper
+                // to check whether it should set itself as target to avoid blank background.
+                display.pendingLayoutChanges |= FINISH_LAYOUT_REDO_WALLPAPER;
+            }
+
+            if (mTopTurnScreenOnActivity != lastTurnScreenOnActivity
+                    && mTopTurnScreenOnActivity != null
+                    && !mService.mWindowManager.mPowerManager.isInteractive()
+                    && (mRequestDismissKeyguard || occludedByActivity)) {
+                controller.mTaskSupervisor.wakeUp("handleTurnScreenOn");
+                mTopTurnScreenOnActivity.setCurrentLaunchCanTurnScreenOn(false);
+            }
+
+            boolean hasChange = false;
+            if (lastOccluded != mOccluded) {
+                controller.handleOccludedChanged(mDisplayId, mTopOccludesActivity);
+                hasChange = true;
+            } else if (!lastKeyguardGoingAway && mKeyguardGoingAway) {
+                controller.handleKeyguardGoingAwayChanged(display);
+                hasChange = true;
+            }
+            // Collect the participates for shell transition, so that transition won't happen too
+            // early since the transition was set ready.
+            if (hasChange && top != null && (mOccluded || mKeyguardGoingAway)) {
+                display.mTransitionController.collect(top);
+            }
         }
 
-        boolean dismissKeyguard(@NonNull ActivityRecord r,
-                @Nullable IKeyguardDismissCallback callback,
-                @Nullable CharSequence message) {
-            return mStateMachine.handleDismissKeyguard(r, callback, message);
+        /**
+         * Gets the stack used to check the occluded state.
+         * <p>
+         * Only the top non-pinned activity of the focusable stack on each display can control its
+         * occlusion state.
+         */
+        @Nullable
+        private Task getRootTaskForControllingOccluding(DisplayContent display) {
+            return display.getRootTask(task ->
+                    task != null && task.isFocusableAndVisible() && !task.inPinnedWindowingMode());
         }
 
-        void commitOccludedStatus(boolean occluded) {
-            mLastNotifiedOccludedState = occluded;
-        }
-
-        void setKeyguardState(@KeyguardState int newState) {
-            mDismissalRequested = false;
-            mStateMachine.transit(newState);
-        }
-
-        boolean isKeyguardTop() {
-            return mStateMachine.isIn(KEYGUARD_STATE_KEYGUARD_TOP);
-        }
-
-        boolean isIn(@KeyguardState int category) {
-            return mStateMachine.isIn(category);
-        }
-
-        boolean topActivityOccludesKeyguard(@NonNull ActivityRecord r) {
-            return mStateMachine.topActivityOccludesKeyguard(r);
-        }
-
-        boolean checkKeyguardVisibility(@NonNull ActivityRecord r) {
-            return mStateMachine.checkKeyguardVisibility(r);
+        void dumpStatus(PrintWriter pw, String prefix) {
+            final StringBuilder sb = new StringBuilder();
+            sb.append(prefix);
+            sb.append(" KeyguardShowing=")
+                    .append(mKeyguardShowing)
+                    .append(" AodShowing=")
+                    .append(mAodShowing)
+                    .append(" KeyguardGoingAway=")
+                    .append(mKeyguardGoingAway)
+                    .append(" DismissalRequested=")
+                    .append(mDismissalRequested)
+                    .append("  Occluded=")
+                    .append(mOccluded)
+                    .append(" DismissingKeyguardActivity=")
+                    .append(mDismissingKeyguardActivity)
+                    .append(" TurnScreenOnActivity=")
+                    .append(mTopTurnScreenOnActivity)
+                    .append(" at display=")
+                    .append(mDisplayId);
+            pw.println(sb.toString());
         }
 
         void dumpDebug(ProtoOutputStream proto, long fieldId) {
             final long token = proto.start(fieldId);
             proto.write(KeyguardPerDisplayProto.DISPLAY_ID, mDisplayId);
-            proto.write(KeyguardPerDisplayProto.KEYGUARD_SHOWING,
-                    isIn(KEYGUARD_STATE_LOCKSCREEN_SHOWN));
-            proto.write(KeyguardPerDisplayProto.AOD_SHOWING, isIn(KEYGUARD_STATE_AOD_SHOWN));
-            proto.write(KeyguardPerDisplayProto.KEYGUARD_OCCLUDED, isIn(KEYGUARD_STATE_OCCLUDED));
+            proto.write(KeyguardPerDisplayProto.KEYGUARD_SHOWING, mKeyguardShowing);
+            proto.write(KeyguardPerDisplayProto.AOD_SHOWING, mAodShowing);
+            proto.write(KeyguardPerDisplayProto.KEYGUARD_OCCLUDED, mOccluded);
+            proto.write(KeyguardPerDisplayProto.KEYGUARD_GOING_AWAY, mKeyguardGoingAway);
             proto.end(token);
         }
+    }
 
-        void dump(PrintWriter pw, String prefix) {
-            StringBuffer sb = new StringBuffer();
-            sb.append(prefix)
-                    .append("* display=")
-                    .append(mDisplayId)
-                    .append("\n");
-            sb.append(prefix)
-                    .append("  state=")
-                    .append(keyguardStateToString(mStateMachine.getState()))
-                    .append("\n");
-            sb.append(prefix)
-                    .append("  mLastNotifiedOccludedState=")
-                    .append(mLastNotifiedOccludedState)
-                    .append("\n");
-            sb.append(prefix)
-                    .append("  mDismissalRequested=")
-                    .append(mDismissalRequested)
-                    .append("\n");
-            pw.print(sb.toString());
+    void dump(PrintWriter pw, String prefix) {
+        final KeyguardDisplayState default_state = getDisplayState(DEFAULT_DISPLAY);
+        pw.println(prefix + "KeyguardController:");
+        pw.println(prefix + "  mKeyguardShowing=" + default_state.mKeyguardShowing);
+        pw.println(prefix + "  mAodShowing=" + default_state.mAodShowing);
+        pw.println(prefix + "  mKeyguardGoingAway=" + default_state.mKeyguardGoingAway);
+        dumpDisplayStates(pw, prefix);
+        pw.println(prefix + "  mDismissalRequested=" + default_state.mDismissalRequested);
+        pw.println();
+    }
 
-            ArrayList<String> dumpInfo = mStateMachine.handleDump(prefix);
-            for (int i = dumpInfo.size() - 1; i >= 0; --i) {
-                pw.print(dumpInfo.get(i));
-            }
+    void dumpDebug(ProtoOutputStream proto, long fieldId) {
+        final KeyguardDisplayState default_state = getDisplayState(DEFAULT_DISPLAY);
+        final long token = proto.start(fieldId);
+        proto.write(AOD_SHOWING, default_state.mAodShowing);
+        proto.write(KEYGUARD_SHOWING, default_state.mKeyguardShowing);
+        proto.write(KEYGUARD_GOING_AWAY, default_state.mKeyguardGoingAway);
+        writeDisplayStatesToProto(proto, KEYGUARD_PER_DISPLAY);
+        proto.end(token);
+    }
+
+    private void dumpDisplayStates(PrintWriter pw, String prefix) {
+        for (int i = 0; i < mDisplayStates.size(); i++) {
+            mDisplayStates.valueAt(i).dumpStatus(pw, prefix);
+        }
+    }
+
+    private void writeDisplayStatesToProto(ProtoOutputStream proto, long fieldId) {
+        for (int i = 0; i < mDisplayStates.size(); i++) {
+            mDisplayStates.valueAt(i).dumpDebug(proto, fieldId);
         }
     }
 }
diff --git a/services/core/java/com/android/server/wm/SurfaceSyncGroupController.java b/services/core/java/com/android/server/wm/SurfaceSyncGroupController.java
index 75691ca..1711845 100644
--- a/services/core/java/com/android/server/wm/SurfaceSyncGroupController.java
+++ b/services/core/java/com/android/server/wm/SurfaceSyncGroupController.java
@@ -63,7 +63,7 @@
         if (callback == null) {
             return false;
         }
-        outAddToSyncGroupResult.mParentSyncGroup = root;
+        outAddToSyncGroupResult.mParentSyncGroup = root.mISurfaceSyncGroup;
         outAddToSyncGroupResult.mTransactionReadyCallback = callback;
         return true;
     }
diff --git a/services/core/java/com/android/server/wm/TransitionController.java b/services/core/java/com/android/server/wm/TransitionController.java
index 80965a7..5e116ba 100644
--- a/services/core/java/com/android/server/wm/TransitionController.java
+++ b/services/core/java/com/android/server/wm/TransitionController.java
@@ -730,7 +730,7 @@
 
     void dispatchLegacyAppTransitionStarting(TransitionInfo info, long statusBarTransitionDelay) {
         for (int i = 0; i < mLegacyListeners.size(); ++i) {
-            mLegacyListeners.get(i).onAppTransitionStartingLocked(info);
+            // TODO(shell-transitions): handle (un)occlude transition.
             mLegacyListeners.get(i).onAppTransitionStartingLocked(
                     SystemClock.uptimeMillis() + statusBarTransitionDelay,
                     AnimationAdapter.STATUS_BAR_TRANSITION_DURATION);
diff --git a/services/core/java/com/android/server/wm/WindowManagerInternal.java b/services/core/java/com/android/server/wm/WindowManagerInternal.java
index ad6bd3c..1282acb 100644
--- a/services/core/java/com/android/server/wm/WindowManagerInternal.java
+++ b/services/core/java/com/android/server/wm/WindowManagerInternal.java
@@ -44,7 +44,6 @@
 import android.view.WindowInfo;
 import android.view.WindowManager.DisplayImePolicy;
 import android.view.inputmethod.ImeTracker;
-import android.window.TransitionInfo;
 
 import com.android.internal.policy.KeyInterceptionInfo;
 import com.android.server.input.InputManagerService;
@@ -213,7 +212,7 @@
      * Abstract class to be notified about {@link com.android.server.wm.AppTransition} events. Held
      * as an abstract class so a listener only needs to implement the methods of its interest.
      */
-    public abstract static class AppTransitionListener {
+    public static abstract class AppTransitionListener {
 
         /**
          * Called when an app transition is being setup and about to be executed.
@@ -252,20 +251,6 @@
         }
 
         /**
-         * Called when an app transition gets started when WM shell is enabled.
-         *
-         * @param info Information about what is changing during a transition.
-         *
-         * @return Return any bit set of {@link WindowManagerPolicy#FINISH_LAYOUT_REDO_LAYOUT},
-         * {@link WindowManagerPolicy#FINISH_LAYOUT_REDO_CONFIG},
-         * {@link WindowManagerPolicy#FINISH_LAYOUT_REDO_WALLPAPER},
-         * or {@link WindowManagerPolicy#FINISH_LAYOUT_REDO_ANIM}.
-         */
-        public int onAppTransitionStartingLocked(TransitionInfo info) {
-            return 0;
-        }
-
-        /**
          * Called when an app transition is finished running.
          *
          * @param token the token for app whose transition has finished
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index f6cb068..a596eed 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -3132,7 +3132,7 @@
     @Override
     public void notifyKeyguardTrustedChanged() {
         synchronized (mGlobalLock) {
-            if (mAtmService.mKeyguardController.isLocksScreenShowing(DEFAULT_DISPLAY)) {
+            if (mAtmService.mKeyguardController.isKeyguardShowing(DEFAULT_DISPLAY)) {
                 mRoot.ensureActivitiesVisible(null, 0, false /* preserveWindows */);
             }
         }
diff --git a/services/core/java/com/android/server/wm/WindowOrientationListener.java b/services/core/java/com/android/server/wm/WindowOrientationListener.java
index 3e165e4..14c816d 100644
--- a/services/core/java/com/android/server/wm/WindowOrientationListener.java
+++ b/services/core/java/com/android/server/wm/WindowOrientationListener.java
@@ -88,14 +88,19 @@
 
     private final Object mLock = new Object();
 
+    @Surface.Rotation
+    private final int mDefaultRotation;
+
     /**
      * Creates a new WindowOrientationListener.
      *
      * @param context for the WindowOrientationListener.
      * @param handler Provides the Looper for receiving sensor updates.
+     * @param defaultRotation Default rotation of the display.
      */
-    public WindowOrientationListener(Context context, Handler handler) {
-        this(context, handler, SensorManager.SENSOR_DELAY_UI);
+    public WindowOrientationListener(Context context, Handler handler,
+            @Surface.Rotation int defaultRotation) {
+        this(context, handler, defaultRotation, SensorManager.SENSOR_DELAY_UI);
     }
 
     /**
@@ -103,7 +108,7 @@
      *
      * @param context for the WindowOrientationListener.
      * @param handler Provides the Looper for receiving sensor updates.
-     * @param wmService WindowManagerService to read the device config from.
+     * @param defaultRotation Default rotation of the display.
      * @param rate at which sensor events are processed (see also
      * {@link android.hardware.SensorManager SensorManager}). Use the default
      * value of {@link android.hardware.SensorManager#SENSOR_DELAY_NORMAL
@@ -111,10 +116,11 @@
      *
      * This constructor is private since no one uses it.
      */
-    private WindowOrientationListener(
-            Context context, Handler handler, int rate) {
+    private WindowOrientationListener(Context context, Handler handler,
+            @Surface.Rotation int defaultRotation, int rate) {
         mContext = context;
         mHandler = handler;
+        mDefaultRotation = defaultRotation;
         mSensorManager = (SensorManager) context.getSystemService(Context.SENSOR_SERVICE);
         mRate = rate;
         List<Sensor> l = mSensorManager.getSensorList(Sensor.TYPE_DEVICE_ORIENTATION);
@@ -1159,7 +1165,7 @@
                                 "Reusing the last rotation resolution: " + mLastRotationResolution);
                         finalizeRotation(mLastRotationResolution);
                     } else {
-                        finalizeRotation(Surface.ROTATION_0);
+                        finalizeRotation(mDefaultRotation);
                     }
                     return;
                 }
diff --git a/services/core/jni/gnss/GnssCallback.cpp b/services/core/jni/gnss/GnssCallback.cpp
index 3c1ac1e..60eed8e6 100644
--- a/services/core/jni/gnss/GnssCallback.cpp
+++ b/services/core/jni/gnss/GnssCallback.cpp
@@ -95,7 +95,7 @@
     method_reportSvStatus = env->GetMethodID(clazz, "reportSvStatus", "(I[I[F[F[F[F[F)V");
     method_reportNmea = env->GetMethodID(clazz, "reportNmea", "(J)V");
 
-    method_setTopHalCapabilities = env->GetMethodID(clazz, "setTopHalCapabilities", "(I)V");
+    method_setTopHalCapabilities = env->GetMethodID(clazz, "setTopHalCapabilities", "(IZ)V");
     method_setSignalTypeCapabilities =
             env->GetMethodID(clazz, "setSignalTypeCapabilities", "(Ljava/util/List;)V");
     method_setGnssYearOfHardware = env->GetMethodID(clazz, "setGnssYearOfHardware", "(I)V");
@@ -120,8 +120,10 @@
 
 Status GnssCallbackAidl::gnssSetCapabilitiesCb(const int capabilities) {
     ALOGD("%s: %du\n", __func__, capabilities);
+    bool isAdrCapabilityKnown = (getInterfaceVersion() >= 3) ? true : false;
     JNIEnv* env = getJniEnv();
-    env->CallVoidMethod(mCallbacksObj, method_setTopHalCapabilities, capabilities);
+    env->CallVoidMethod(mCallbacksObj, method_setTopHalCapabilities, capabilities,
+                        isAdrCapabilityKnown);
     checkAndClearExceptionFromCallback(env, __FUNCTION__);
     return Status::ok();
 }
@@ -409,7 +411,8 @@
     ALOGD("%s: %du\n", __func__, capabilities);
 
     JNIEnv* env = getJniEnv();
-    env->CallVoidMethod(mCallbacksObj, method_setTopHalCapabilities, capabilities);
+    env->CallVoidMethod(mCallbacksObj, method_setTopHalCapabilities, capabilities,
+                        /* isAdrCapabilityKnown= */ false);
     checkAndClearExceptionFromCallback(env, __FUNCTION__);
     return Void();
 }
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index c9c9c55..9c64054 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -16363,7 +16363,9 @@
 
     private boolean hasAccountFeatures(AccountManager am, Account account, String[] features) {
         try {
-            return am.hasFeatures(account, features, null, null).getResult();
+            // TODO(267156507): Restore without blocking binder thread
+            return false;
+//            return am.hasFeatures(account, features, null, null).getResult();
         } catch (Exception e) {
             Slogf.w(LOG_TAG, "Failed to get account feature", e);
             return false;
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/JobSchedulerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/job/JobSchedulerServiceTest.java
index 7281fafc..ed78e72 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/JobSchedulerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/JobSchedulerServiceTest.java
@@ -30,7 +30,9 @@
 import static com.android.server.job.JobSchedulerService.sElapsedRealtimeClock;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyBoolean;
@@ -95,6 +97,8 @@
     private ActivityManagerInternal mActivityMangerInternal;
     @Mock
     private Context mContext;
+    @Mock
+    private PackageManagerInternal mPackageManagerInternal;
 
     private class TestJobSchedulerService extends JobSchedulerService {
         TestJobSchedulerService(Context context) {
@@ -121,6 +125,8 @@
                 .when(() -> LocalServices.getService(AppStandbyInternal.class));
         doReturn(mock(BatteryManagerInternal.class))
                 .when(() -> LocalServices.getService(BatteryManagerInternal.class));
+        doReturn(mPackageManagerInternal)
+                .when(() -> LocalServices.getService(PackageManagerInternal.class));
         doReturn(mock(UsageStatsManagerInternal.class))
                 .when(() -> LocalServices.getService(UsageStatsManagerInternal.class));
         when(mContext.getString(anyInt())).thenReturn("some_test_string");
@@ -138,9 +144,6 @@
         // Used in JobConcurrencyManager.
         doReturn(mock(UserManagerInternal.class))
                 .when(() -> LocalServices.getService(UserManagerInternal.class));
-        // Used in JobStatus.
-        doReturn(mock(PackageManagerInternal.class))
-                .when(() -> LocalServices.getService(PackageManagerInternal.class));
         // Called via IdleController constructor.
         when(mContext.getPackageManager()).thenReturn(mock(PackageManager.class));
         when(mContext.getResources()).thenReturn(mock(Resources.class));
@@ -197,8 +200,13 @@
 
     private JobStatus createJobStatus(String testTag, JobInfo.Builder jobInfoBuilder,
             int callingUid) {
+        return createJobStatus(testTag, jobInfoBuilder, callingUid, "com.android.test");
+    }
+
+    private JobStatus createJobStatus(String testTag, JobInfo.Builder jobInfoBuilder,
+            int callingUid, String sourcePkg) {
         return JobStatus.createFromJobInfo(
-                jobInfoBuilder.build(), callingUid, "com.android.test", 0, "JSSTest", testTag);
+                jobInfoBuilder.build(), callingUid, sourcePkg, 0, "JSSTest", testTag);
     }
 
     private void grantRunLongJobsPermission(boolean grant) {
@@ -1238,4 +1246,57 @@
                             0, "JSSTest", ""));
         }
     }
+
+    /** Tests that jobs are removed from the pending list if the user stops the app. */
+    @Test
+    public void testUserStopRemovesPending() {
+        spyOn(mService);
+
+        JobStatus job1a = createJobStatus("testUserStopRemovesPending",
+                createJobInfo(1), 1, "pkg1");
+        JobStatus job1b = createJobStatus("testUserStopRemovesPending",
+                createJobInfo(2), 1, "pkg1");
+        JobStatus job2a = createJobStatus("testUserStopRemovesPending",
+                createJobInfo(1), 2, "pkg2");
+        JobStatus job2b = createJobStatus("testUserStopRemovesPending",
+                createJobInfo(2), 2, "pkg2");
+        doReturn(1).when(mPackageManagerInternal).getPackageUid("pkg1", 0, 0);
+        doReturn(11).when(mPackageManagerInternal).getPackageUid("pkg1", 0, 1);
+        doReturn(2).when(mPackageManagerInternal).getPackageUid("pkg2", 0, 0);
+
+        mService.getPendingJobQueue().clear();
+        mService.getPendingJobQueue().add(job1a);
+        mService.getPendingJobQueue().add(job1b);
+        mService.getPendingJobQueue().add(job2a);
+        mService.getPendingJobQueue().add(job2b);
+        mService.getJobStore().add(job1a);
+        mService.getJobStore().add(job1b);
+        mService.getJobStore().add(job2a);
+        mService.getJobStore().add(job2b);
+
+        mService.stopUserVisibleJobsInternal("pkg1", 1);
+        assertEquals(4, mService.getPendingJobQueue().size());
+        assertTrue(mService.getPendingJobQueue().contains(job1a));
+        assertTrue(mService.getPendingJobQueue().contains(job1b));
+        assertTrue(mService.getPendingJobQueue().contains(job2a));
+        assertTrue(mService.getPendingJobQueue().contains(job2b));
+
+        mService.stopUserVisibleJobsInternal("pkg1", 0);
+        assertEquals(2, mService.getPendingJobQueue().size());
+        assertFalse(mService.getPendingJobQueue().contains(job1a));
+        assertEquals(JobScheduler.PENDING_JOB_REASON_USER, mService.getPendingJobReason(job1a));
+        assertFalse(mService.getPendingJobQueue().contains(job1b));
+        assertEquals(JobScheduler.PENDING_JOB_REASON_USER, mService.getPendingJobReason(job1b));
+        assertTrue(mService.getPendingJobQueue().contains(job2a));
+        assertTrue(mService.getPendingJobQueue().contains(job2b));
+
+        mService.stopUserVisibleJobsInternal("pkg2", 0);
+        assertEquals(0, mService.getPendingJobQueue().size());
+        assertFalse(mService.getPendingJobQueue().contains(job1a));
+        assertFalse(mService.getPendingJobQueue().contains(job1b));
+        assertFalse(mService.getPendingJobQueue().contains(job2a));
+        assertEquals(JobScheduler.PENDING_JOB_REASON_USER, mService.getPendingJobReason(job2a));
+        assertFalse(mService.getPendingJobQueue().contains(job2b));
+        assertEquals(JobScheduler.PENDING_JOB_REASON_USER, mService.getPendingJobReason(job2b));
+    }
 }
diff --git a/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperManagerServiceTests.java b/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperManagerServiceTests.java
index de54537..9b48114 100644
--- a/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperManagerServiceTests.java
+++ b/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperManagerServiceTests.java
@@ -201,7 +201,7 @@
     public void setUp() {
         MockitoAnnotations.initMocks(this);
 
-        ExtendedMockito.doAnswer(invocation ->  {
+        ExtendedMockito.doAnswer(invocation -> {
             int userId = (invocation.getArgument(0));
             return getWallpaperTestDir(userId);
         }).when(() -> WallpaperUtils.getWallpaperDir(anyInt()));
@@ -315,7 +315,8 @@
 
         spyOn(mService.mWallpaperDisplayHelper);
         doReturn(true).when(mService.mWallpaperDisplayHelper)
-                .isUsableDisplay(any(Display.class), mService.mLastWallpaper.connection.mClientUid);
+                .isUsableDisplay(any(Display.class),
+                        eq(mService.mLastWallpaper.connection.mClientUid));
         mService.mLastWallpaper.connection.attachEngine(mock(IWallpaperEngine.class),
                 DEFAULT_DISPLAY);
 
diff --git a/services/tests/servicestests/src/com/android/server/am/OomAdjusterTests.java b/services/tests/servicestests/src/com/android/server/am/OomAdjusterTests.java
index 8cbed2c..4412cfe 100644
--- a/services/tests/servicestests/src/com/android/server/am/OomAdjusterTests.java
+++ b/services/tests/servicestests/src/com/android/server/am/OomAdjusterTests.java
@@ -305,4 +305,60 @@
         assertEquals("Interaction event time was not updated correctly.",
                 interactionEventTime, mProcessRecord.mState.getInteractionEventTime());
     }
+
+    private void updateShortFgsOwner(int uid, int pid, boolean add) {
+        sService.mOomAdjuster.updateShortFgsOwner(uid, pid, add);
+    }
+
+    private void assertHasUidShortForegroundService(int uid, boolean expected) {
+        assertEquals(expected, sService.mOomAdjuster.hasUidShortForegroundService(uid));
+    }
+
+    @Test
+    public void testHasUidShortForegroundService() {
+        assertHasUidShortForegroundService(1, false);
+        assertHasUidShortForegroundService(2, false);
+        assertHasUidShortForegroundService(3, false);
+        assertHasUidShortForegroundService(100, false);
+        assertHasUidShortForegroundService(101, false);
+
+        updateShortFgsOwner(1, 100, true);
+        assertHasUidShortForegroundService(1, true);
+        assertHasUidShortForegroundService(100, false);
+        assertHasUidShortForegroundService(2, false);
+
+        updateShortFgsOwner(1, 101, true);
+        assertHasUidShortForegroundService(1, true);
+        assertHasUidShortForegroundService(101, false);
+        assertHasUidShortForegroundService(2, false);
+
+        updateShortFgsOwner(2, 200, true);
+        assertHasUidShortForegroundService(1, true);
+        assertHasUidShortForegroundService(2, true);
+        assertHasUidShortForegroundService(200, false);
+
+        updateShortFgsOwner(1, 101, false);
+        assertHasUidShortForegroundService(1, true);
+        assertHasUidShortForegroundService(2, true);
+
+        updateShortFgsOwner(1, 99, false); // unused PID
+        assertHasUidShortForegroundService(1, true);
+        assertHasUidShortForegroundService(2, true);
+
+        updateShortFgsOwner(1, 100, false);
+        assertHasUidShortForegroundService(1, false);
+        assertHasUidShortForegroundService(2, true);
+
+        updateShortFgsOwner(1, 100, true);
+        assertHasUidShortForegroundService(1, true);
+        assertHasUidShortForegroundService(2, true);
+
+        updateShortFgsOwner(2, 200, false);
+        assertHasUidShortForegroundService(1, true);
+        assertHasUidShortForegroundService(2, false);
+
+        updateShortFgsOwner(2, 201, true);
+        assertHasUidShortForegroundService(1, true);
+        assertHasUidShortForegroundService(2, true);
+    }
 }
diff --git a/services/tests/servicestests/src/com/android/server/dreams/DreamOverlayServiceTest.java b/services/tests/servicestests/src/com/android/server/dreams/DreamOverlayServiceTest.java
new file mode 100644
index 0000000..6c73f71
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/dreams/DreamOverlayServiceTest.java
@@ -0,0 +1,175 @@
+/*
+ * 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.dreams;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+
+import android.content.ComponentName;
+import android.content.Intent;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.service.dreams.DreamOverlayService;
+import android.service.dreams.IDreamOverlay;
+import android.service.dreams.IDreamOverlayCallback;
+import android.service.dreams.IDreamOverlayClient;
+import android.service.dreams.IDreamOverlayClientCallback;
+import android.view.WindowManager;
+
+import androidx.annotation.NonNull;
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+
+/**
+ * A collection of tests to exercise {@link DreamOverlayService}.
+ */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class DreamOverlayServiceTest {
+    private static final ComponentName FIRST_DREAM_COMPONENT =
+            ComponentName.unflattenFromString("com.foo.bar/.DreamService");
+    private static final ComponentName SECOND_DREAM_COMPONENT =
+            ComponentName.unflattenFromString("com.foo.baz/.DreamService");
+
+    @Mock
+    WindowManager.LayoutParams mLayoutParams;
+
+    @Mock
+    IDreamOverlayCallback mOverlayCallback;
+
+    /**
+     * {@link TestDreamOverlayService} is a simple {@link DreamOverlayService} implementation for
+     * tracking interactions across {@link IDreamOverlay} binder interface. The service reports
+     * interactions to a {@link Monitor} instance provided at construction.
+     */
+    private static class TestDreamOverlayService extends DreamOverlayService {
+        /**
+         * An interface implemented to be informed when the corresponding methods in
+         * {@link TestDreamOverlayService} are invoked.
+         */
+        interface Monitor {
+            void onStartDream();
+            void onEndDream();
+            void onWakeUp();
+        }
+
+        private final Monitor mMonitor;
+
+        TestDreamOverlayService(Monitor monitor) {
+            super();
+            mMonitor = monitor;
+        }
+
+        @Override
+        public void onStartDream(@NonNull WindowManager.LayoutParams layoutParams) {
+            mMonitor.onStartDream();
+        }
+
+        @Override
+        public void onEndDream() {
+            mMonitor.onEndDream();
+            super.onEndDream();
+        }
+
+        @Override
+        public void onWakeUp(@NonNull Runnable onCompleteCallback) {
+            mMonitor.onWakeUp();
+            super.onWakeUp(onCompleteCallback);
+        }
+    }
+
+    /**
+     * A {@link IDreamOverlayClientCallback} implementation that captures the requested client.
+     */
+    private static class OverlayClientCallback extends IDreamOverlayClientCallback.Stub {
+        public IDreamOverlayClient retrievedClient;
+        @Override
+        public void onDreamOverlayClient(IDreamOverlayClient client) throws RemoteException {
+            retrievedClient = client;
+        }
+    }
+
+    @Before
+    public void setup() {
+        MockitoAnnotations.initMocks(this);
+    }
+
+    /**
+     * Verifies that only the currently started dream is able to affect the overlay.
+     */
+    @Test
+    public void testOverlayClientInteraction() throws RemoteException {
+        final TestDreamOverlayService.Monitor monitor = Mockito.mock(
+                TestDreamOverlayService.Monitor.class);
+        final TestDreamOverlayService service = new TestDreamOverlayService(monitor);
+        final IBinder binder = service.onBind(new Intent());
+        final IDreamOverlay overlay = IDreamOverlay.Stub.asInterface(binder);
+
+        // Create two overlay clients and ensure they are unique.
+        final IDreamOverlayClient firstClient = getClient(overlay);
+        assertThat(firstClient).isNotNull();
+
+        final IDreamOverlayClient secondClient = getClient(overlay);
+        assertThat(secondClient).isNotNull();
+
+        assertThat(firstClient).isNotEqualTo(secondClient);
+
+        // Start a dream with the first client and ensure the dream is now active from the
+        // overlay's perspective.
+        firstClient.startDream(mLayoutParams, mOverlayCallback,
+                FIRST_DREAM_COMPONENT.flattenToString(), false);
+
+
+        verify(monitor).onStartDream();
+        assertThat(service.getDreamComponent()).isEqualTo(FIRST_DREAM_COMPONENT);
+
+        Mockito.clearInvocations(monitor);
+
+        // Start a dream from the second client and verify that the overlay has both cycled to
+        // the new dream (ended/started).
+        secondClient.startDream(mLayoutParams, mOverlayCallback,
+                SECOND_DREAM_COMPONENT.flattenToString(), false);
+
+        verify(monitor).onEndDream();
+        verify(monitor).onStartDream();
+        assertThat(service.getDreamComponent()).isEqualTo(SECOND_DREAM_COMPONENT);
+
+        Mockito.clearInvocations(monitor);
+
+        // Verify that interactions with the first, now inactive client don't affect the overlay.
+        firstClient.endDream();
+        verify(monitor, never()).onEndDream();
+
+        firstClient.wakeUp();
+        verify(monitor, never()).onWakeUp();
+    }
+
+    private static IDreamOverlayClient getClient(IDreamOverlay overlay) throws RemoteException {
+        final OverlayClientCallback callback = new OverlayClientCallback();
+        overlay.getClient(callback);
+        return callback.retrievedClient;
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java
index a0fb3de..d71deaf 100644
--- a/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java
@@ -16,6 +16,7 @@
 
 package com.android.server.power;
 
+import static android.app.ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE;
 import static android.app.ActivityManager.PROCESS_STATE_BOUND_TOP;
 import static android.app.ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE;
 import static android.app.AppOpsManager.MODE_ALLOWED;
@@ -29,6 +30,8 @@
 
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
+import static org.mockito.AdditionalMatchers.gt;
+import static org.mockito.AdditionalMatchers.leq;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyInt;
@@ -150,6 +153,9 @@
     @Mock
     private InattentiveSleepWarningController mInattentiveSleepWarningControllerMock;
 
+    @Mock
+    private ActivityManagerInternal mActivityManagerInternal;
+
     private PowerManagerService mService;
     private ContextWrapper mContextSpy;
     private BatteryReceiver mBatteryReceiver;
@@ -205,6 +211,7 @@
         addLocalServiceMock(ActivityManagerInternal.class, mActivityManagerInternalMock);
         addLocalServiceMock(AttentionManagerInternal.class, mAttentionManagerInternalMock);
         addLocalServiceMock(DreamManagerInternal.class, mDreamManagerInternalMock);
+        addLocalServiceMock(ActivityManagerInternal.class, mActivityManagerInternal);
 
         mContextSpy = spy(new ContextWrapper(ApplicationProvider.getApplicationContext()));
         mResourcesSpy = spy(mContextSpy.getResources());
@@ -221,6 +228,14 @@
 
         mClock = new OffsettableClock.Stopped();
         mTestLooper = new TestLooper(mClock::now);
+
+        // Set up canHoldWakeLocksInDeepDoze.
+        // - procstate <= PROCESS_STATE_BOUND_FOREGROUND_SERVICE -> true
+        // - procstate >  PROCESS_STATE_BOUND_FOREGROUND_SERVICE -> false
+        when(mActivityManagerInternal.canHoldWakeLocksInDeepDoze(
+                anyInt(), leq(PROCESS_STATE_BOUND_FOREGROUND_SERVICE))).thenReturn(true);
+        when(mActivityManagerInternal.canHoldWakeLocksInDeepDoze(
+                anyInt(), gt(PROCESS_STATE_BOUND_FOREGROUND_SERVICE))).thenReturn(false);
     }
 
     private PowerManagerService createService() {
diff --git a/services/tests/servicestests/src/com/android/server/wm/WindowOrientationListenerTest.java b/services/tests/servicestests/src/com/android/server/wm/WindowOrientationListenerTest.java
index 3de65c1..1b3a199 100644
--- a/services/tests/servicestests/src/com/android/server/wm/WindowOrientationListenerTest.java
+++ b/services/tests/servicestests/src/com/android/server/wm/WindowOrientationListenerTest.java
@@ -132,6 +132,19 @@
         assertThat(mFinalizedRotation).isEqualTo(DEFAULT_SENSOR_ROTATION);
     }
 
+    @Test
+    public void testOnSensorChanged_screenLocked_doNotCallRotationResolverReturnDefaultRotation() {
+        mWindowOrientationListener = new TestableWindowOrientationListener(mMockContext,
+                mHandler, /* defaultRotation */ Surface.ROTATION_180);
+        mWindowOrientationListener.mRotationResolverService = mFakeRotationResolverInternal;
+        mWindowOrientationListener.mIsScreenLocked = true;
+
+        mWindowOrientationListener.mOrientationJudge.onSensorChanged(mFakeSensorEvent);
+
+        assertThat(mWindowOrientationListener.mIsOnProposedRotationChangedCalled).isFalse();
+        assertThat(mFinalizedRotation).isEqualTo(Surface.ROTATION_180);
+    }
+
     static final class TestableRotationResolver extends RotationResolverInternal {
         @Surface.Rotation
         RotationResolverCallbackInternal mCallback;
@@ -166,21 +179,17 @@
         }
     }
 
-    @Test
-    public void testOnSensorChanged_inLockScreen_doNotCallRotationResolver() {
-        mWindowOrientationListener.mIsScreenLocked = true;
-
-        mWindowOrientationListener.mOrientationJudge.onSensorChanged(mFakeSensorEvent);
-
-        assertThat(mWindowOrientationListener.mIsOnProposedRotationChangedCalled).isFalse();
-    }
-
     final class TestableWindowOrientationListener extends WindowOrientationListener {
         private boolean mIsOnProposedRotationChangedCalled = false;
         private boolean mIsScreenLocked;
 
         TestableWindowOrientationListener(Context context, Handler handler) {
-            super(context, handler);
+            this(context, handler, Surface.ROTATION_0);
+        }
+
+        TestableWindowOrientationListener(Context context, Handler handler,
+                @Surface.Rotation int defaultRotation) {
+            super(context, handler, defaultRotation);
             this.mOrientationJudge = new OrientationSensorJudge();
         }
 
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenersTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenersTest.java
index 581cf43..90d1488 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenersTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenersTest.java
@@ -28,24 +28,29 @@
 import static junit.framework.Assert.assertFalse;
 import static junit.framework.Assert.assertTrue;
 
+import static org.junit.Assert.fail;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.ArgumentMatchers.intThat;
 import static org.mockito.ArgumentMatchers.nullable;
 import static org.mockito.Mockito.atLeast;
+import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.doNothing;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.reset;
 import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
 import android.app.INotificationManager;
 import android.app.Notification;
 import android.app.NotificationChannel;
+import android.app.NotificationChannelGroup;
 import android.app.NotificationManager;
 import android.content.ComponentName;
 import android.content.pm.IPackageManager;
@@ -54,7 +59,10 @@
 import android.content.pm.VersionedPackage;
 import android.content.res.Resources;
 import android.os.Bundle;
+import android.os.Parcel;
+import android.os.RemoteException;
 import android.os.UserHandle;
+import android.service.notification.INotificationListener;
 import android.service.notification.NotificationListenerFilter;
 import android.service.notification.NotificationListenerService;
 import android.service.notification.NotificationRankingUpdate;
@@ -63,10 +71,10 @@
 import android.testing.TestableContext;
 import android.util.ArraySet;
 import android.util.Pair;
-import android.util.Xml;
-
 import com.android.modules.utils.TypedXmlPullParser;
 import com.android.modules.utils.TypedXmlSerializer;
+import android.util.Xml;
+
 import com.android.server.UiServiceTestCase;
 
 import com.google.common.collect.ImmutableList;
@@ -113,6 +121,8 @@
         getContext().setMockPackageManager(mPm);
         doNothing().when(mContext).sendBroadcastAsUser(any(), any(), any());
 
+        when(mNm.isInteractionVisibleToListener(any(), anyInt())).thenReturn(true);
+
         mListeners = spy(mNm.new NotificationListeners(
                 mContext, new Object(), mock(ManagedServices.UserProfiles.class), miPm));
         when(mNm.getBinderService()).thenReturn(mINm);
@@ -596,4 +606,44 @@
         verify(mPmi).grantImplicitAccess(sbn.getUserId(), null, UserHandle.getAppId(33),
                 sbn.getUid(), false, false);
     }
+
+    @Test
+    public void testUpdateGroup_notifyTwoListeners() throws Exception {
+        final NotificationChannelGroup updated = new NotificationChannelGroup("id", "name");
+        updated.setChannels(ImmutableList.of(
+                new NotificationChannel("a", "a", 1), new NotificationChannel("b", "b", 2)));
+        updated.setBlocked(true);
+
+        ManagedServices.ManagedServiceInfo i1 = getParcelingListener(updated);
+        ManagedServices.ManagedServiceInfo i2= getParcelingListener(updated);
+        when(mListeners.getServices()).thenReturn(ImmutableList.of(i1, i2));
+        NotificationChannelGroup existing = new NotificationChannelGroup("id", "name");
+
+        mListeners.notifyNotificationChannelGroupChanged("pkg", UserHandle.of(0), updated, 0);
+        Thread.sleep(500);
+
+        verify(((INotificationListener) i1.getService()), times(1))
+                .onNotificationChannelGroupModification(anyString(), any(), any(), anyInt());
+    }
+
+    private ManagedServices.ManagedServiceInfo getParcelingListener(
+            final NotificationChannelGroup toParcel)
+            throws RemoteException {
+        ManagedServices.ManagedServiceInfo i1 = mock(ManagedServices.ManagedServiceInfo.class);
+        when(i1.isSystem()).thenReturn(true);
+        INotificationListener l1 = mock(INotificationListener.class);
+        when(i1.enabledAndUserMatches(anyInt())).thenReturn(true);
+        doAnswer(invocationOnMock -> {
+            try {
+                toParcel.writeToParcel(Parcel.obtain(), 0);
+            } catch (Exception e) {
+                fail("Failed to parcel group to listener");
+                return e;
+
+            }
+            return null;
+        }).when(l1).onNotificationChannelGroupModification(anyString(), any(), any(), anyInt());
+        when(i1.getService()).thenReturn(l1);
+        return i1;
+    }
 }
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
index 5f8a2b5..20e8cd4 100755
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -19,6 +19,7 @@
 import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND;
 import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_VISIBLE;
 import static android.app.ActivityTaskManager.INVALID_TASK_ID;
+import static android.app.Notification.EXTRA_ALLOW_DURING_SETUP;
 import static android.app.Notification.FLAG_AUTO_CANCEL;
 import static android.app.Notification.FLAG_BUBBLE;
 import static android.app.Notification.FLAG_CAN_COLORIZE;
@@ -217,6 +218,7 @@
 import com.android.server.notification.NotificationManagerService.NotificationListeners;
 import com.android.server.pm.PackageManagerService;
 import com.android.server.pm.UserManagerInternal;
+import com.android.server.pm.permission.PermissionManagerServiceInternal;
 import com.android.server.policy.PermissionPolicyInternal;
 import com.android.server.statusbar.StatusBarManagerInternal;
 import com.android.server.uri.UriGrantsManagerInternal;
@@ -305,6 +307,9 @@
     ActivityManager mActivityManager;
     @Mock
     TelecomManager mTelecomManager;
+
+    @Mock
+    PermissionManagerServiceInternal mPermissionInternal;
     @Mock
     Resources mResources;
     @Mock
@@ -518,7 +523,7 @@
                 mAppOpsManager, mAppOpsService, mUm, mHistoryManager, mStatsManager,
                 mock(TelephonyManager.class),
                 mAmi, mToastRateLimiter, mPermissionHelper, mock(UsageStatsManagerInternal.class),
-                mTelecomManager, mLogger);
+                mTelecomManager, mLogger, mPermissionInternal);
         // Return first true for RoleObserver main-thread check
         when(mMainLooper.isCurrentThread()).thenReturn(true).thenReturn(false);
         mService.onBootPhase(SystemService.PHASE_SYSTEM_SERVICES_READY, mMainLooper);
@@ -4168,8 +4173,35 @@
     }
 
     @Test
+    public void testNoNotificationDuringSetupPermission() throws Exception {
+        when(mPermissionInternal.checkPermission(any(), any(), anyInt()))
+                .thenReturn(PERMISSION_DENIED);
+        Bundle extras = new Bundle();
+        extras.putBoolean(EXTRA_ALLOW_DURING_SETUP, true);
+        Notification.Builder nb = new Notification.Builder(mContext,
+                mTestNotificationChannel.getId())
+                .setContentTitle("foo")
+                .addExtras(extras)
+                .setSmallIcon(android.R.drawable.sym_def_app_icon);
+        StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, 1,
+                "testNoNotificationDuringSetupPermission", mUid, 0,
+                nb.build(), UserHandle.getUserHandleForUid(mUid), null, 0);
+        NotificationRecord nr = new NotificationRecord(mContext, sbn, mTestNotificationChannel);
+
+        mBinderService.enqueueNotificationWithTag(PKG, PKG, sbn.getTag(),
+                nr.getSbn().getId(), nr.getSbn().getNotification(), nr.getSbn().getUserId());
+        waitForIdle();
+
+        NotificationRecord posted = mService.findNotificationLocked(
+                PKG, nr.getSbn().getTag(), nr.getSbn().getId(), nr.getSbn().getUserId());
+
+        assertFalse(posted.getNotification().extras.containsKey(EXTRA_ALLOW_DURING_SETUP));
+    }
+
+    @Test
     public void testNoFakeColorizedPermission() throws Exception {
-        when(mPackageManagerClient.checkPermission(any(), any())).thenReturn(PERMISSION_DENIED);
+        when(mPermissionInternal.checkPermission(any(), any(), anyInt()))
+                .thenReturn(PERMISSION_DENIED);
         Notification.Builder nb = new Notification.Builder(mContext,
                 mTestNotificationChannel.getId())
                 .setContentTitle("foo")
@@ -4194,7 +4226,7 @@
     @Test
     public void testMediaStyleRemote_hasPermission() throws RemoteException {
         String deviceName = "device";
-        when(mPackageManager.checkPermission(
+        when(mPermissionInternal.checkPermission(
                 eq(android.Manifest.permission.MEDIA_CONTENT_CONTROL), any(), anyInt()))
                 .thenReturn(PERMISSION_GRANTED);
         Notification.MediaStyle style = new Notification.MediaStyle();
@@ -4223,7 +4255,7 @@
     @Test
     public void testMediaStyleRemote_noPermission() throws RemoteException {
         String deviceName = "device";
-        when(mPackageManager.checkPermission(
+        when(mPermissionInternal.checkPermission(
                 eq(android.Manifest.permission.MEDIA_CONTENT_CONTROL), any(), anyInt()))
                 .thenReturn(PERMISSION_DENIED);
         Notification.MediaStyle style = new Notification.MediaStyle();
@@ -4251,7 +4283,7 @@
     @Test
     public void testSubstituteAppName_hasPermission() throws RemoteException {
         String subName = "Substitute Name";
-        when(mPackageManager.checkPermission(
+        when(mPermissionInternal.checkPermission(
                 eq(android.Manifest.permission.SUBSTITUTE_NOTIFICATION_APP_NAME), any(), anyInt()))
                 .thenReturn(PERMISSION_GRANTED);
         Bundle extras = new Bundle();
@@ -4278,7 +4310,7 @@
 
     @Test
     public void testSubstituteAppName_noPermission() throws RemoteException {
-        when(mPackageManager.checkPermission(
+        when(mPermissionInternal.checkPermission(
                 eq(android.Manifest.permission.SUBSTITUTE_NOTIFICATION_APP_NAME), any(), anyInt()))
                 .thenReturn(PERMISSION_DENIED);
         Bundle extras = new Bundle();
@@ -5788,7 +5820,7 @@
 
     @Test
     public void testCanUseManagedServices_hasPermission() throws Exception {
-        when(mPackageManager.checkPermission("perm", "pkg", 0))
+        when(mPermissionInternal.checkPermission("perm", "pkg", 0))
                 .thenReturn(PackageManager.PERMISSION_GRANTED);
 
         assertEquals(true, mService.canUseManagedServices("pkg", 0, "perm"));
@@ -5796,7 +5828,7 @@
 
     @Test
     public void testCanUseManagedServices_noPermission() throws Exception {
-        when(mPackageManager.checkPermission("perm", "pkg", 0))
+        when(mPermissionInternal.checkPermission("perm", "pkg", 0))
                 .thenReturn(PackageManager.PERMISSION_DENIED);
 
         assertEquals(false, mService.canUseManagedServices("pkg", 0, "perm"));
@@ -6751,7 +6783,7 @@
 
     private void setIfPackageHasPermissionToAvoidToastRateLimiting(
             String pkg, boolean hasPermission) throws Exception {
-        when(mPackageManager.checkPermission(android.Manifest.permission.UNLIMITED_TOASTS,
+        when(mPermissionInternal.checkPermission(android.Manifest.permission.UNLIMITED_TOASTS,
                 pkg, UserHandle.getUserId(mUid)))
                 .thenReturn(hasPermission ? PERMISSION_GRANTED : PERMISSION_DENIED);
     }
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
index 19f3189..f0f9204 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
@@ -93,6 +93,7 @@
 import android.os.AsyncTask;
 import android.os.Build;
 import android.os.Bundle;
+import android.os.Parcel;
 import android.os.RemoteCallback;
 import android.os.RemoteException;
 import android.os.UserHandle;
@@ -2449,6 +2450,35 @@
     }
 
     @Test
+    public void testGetNotificationChannelGroup() throws Exception {
+        NotificationChannelGroup notDeleted = new NotificationChannelGroup("not", "deleted");
+        NotificationChannel base =
+                new NotificationChannel("not deleted", "belongs to notDeleted", IMPORTANCE_DEFAULT);
+        base.setGroup("not");
+        NotificationChannel convo =
+                new NotificationChannel("convo", "belongs to notDeleted", IMPORTANCE_DEFAULT);
+        convo.setGroup("not");
+        convo.setConversationId("not deleted", "banana");
+
+        mHelper.createNotificationChannelGroup(PKG_N_MR1, UID_N_MR1, notDeleted, true);
+        mHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1, base, true, false);
+        mHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1, convo, true, false);
+        mHelper.createNotificationChannelGroup(PKG_N_MR1, UID_N_MR1, notDeleted, true);
+
+        NotificationChannelGroup g
+                = mHelper.getNotificationChannelGroup(notDeleted.getId(), PKG_N_MR1, UID_N_MR1);
+        Parcel parcel = Parcel.obtain();
+        g.writeToParcel(parcel, 0);
+        parcel.setDataPosition(0);
+
+        NotificationChannelGroup g2
+                = mHelper.getNotificationChannelGroup(notDeleted.getId(), PKG_N_MR1, UID_N_MR1);
+        Parcel parcel2 = Parcel.obtain();
+        g2.writeToParcel(parcel2, 0);
+        parcel2.setDataPosition(0);
+    }
+
+    @Test
     public void testOnUserRemoved() throws Exception {
         int[] user0Uids = {98, 235, 16, 3782};
         int[] user1Uids = new int[user0Uids.length];
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/RoleObserverTest.java b/services/tests/uiservicestests/src/com/android/server/notification/RoleObserverTest.java
index 98c156e..3a01c2d 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/RoleObserverTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/RoleObserverTest.java
@@ -72,6 +72,7 @@
 import com.android.server.lights.LightsManager;
 import com.android.server.notification.NotificationManagerService.NotificationAssistants;
 import com.android.server.notification.NotificationManagerService.NotificationListeners;
+import com.android.server.pm.permission.PermissionManagerServiceInternal;
 import com.android.server.uri.UriGrantsManagerInternal;
 import com.android.server.utils.quota.MultiRateLimiter;
 import com.android.server.wm.ActivityTaskManagerInternal;
@@ -168,8 +169,9 @@
                     mock(StatsManager.class), mock(TelephonyManager.class),
                     mock(ActivityManagerInternal.class),
                     mock(MultiRateLimiter.class), mock(PermissionHelper.class),
-                    mock(UsageStatsManagerInternal.class), mock (TelecomManager.class),
-                    mock(NotificationChannelLogger.class));
+                    mock(UsageStatsManagerInternal.class), mock(TelecomManager.class),
+                    mock(NotificationChannelLogger.class),
+                    mock(PermissionManagerServiceInternal.class));
         } catch (SecurityException e) {
             if (!e.getMessage().contains("Permission Denial: not allowed to send broadcast")) {
                 throw e;
diff --git a/services/tests/wmtests/AndroidManifest.xml b/services/tests/wmtests/AndroidManifest.xml
index b51feb3..593ee4a 100644
--- a/services/tests/wmtests/AndroidManifest.xml
+++ b/services/tests/wmtests/AndroidManifest.xml
@@ -90,9 +90,6 @@
             android:foregroundServiceType="mediaProjection"
             android:enabled="true">
         </service>
-
-        <service android:name="com.android.server.wm.scvh.EmbeddedSCVHService"
-            android:process="com.android.server.wm.scvh.embedded_process" />
     </application>
 
     <instrumentation
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
index 9fda204..e663245 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
@@ -1475,6 +1475,7 @@
         // Make keyguard locked and set the top activity show-when-locked.
         KeyguardController keyguardController = activity.mTaskSupervisor.getKeyguardController();
         int displayId = activity.getDisplayId();
+        doReturn(true).when(keyguardController).isKeyguardLocked(eq(displayId));
         final ActivityRecord topActivity = new ActivityBuilder(mAtm).setTask(task).build();
         topActivity.setVisibleRequested(true);
         topActivity.nowVisible = true;
@@ -1484,24 +1485,18 @@
                 anyBoolean() /* preserveWindows */, anyBoolean() /* notifyClients */);
         topActivity.setShowWhenLocked(true);
 
-        try {
-            keyguardController.setKeyguardShown(displayId, true, false);
+        // Verify the stack-top activity is occluded keyguard.
+        assertEquals(topActivity, task.topRunningActivity());
+        assertTrue(keyguardController.isDisplayOccluded(DEFAULT_DISPLAY));
 
-            // Verify the stack-top activity is occluded keyguard.
-            assertEquals(topActivity, task.topRunningActivity());
-            assertTrue(keyguardController.isDisplayOccluded(DEFAULT_DISPLAY));
+        // Finish the top activity
+        topActivity.setState(PAUSED, "true");
+        topActivity.finishing = true;
+        topActivity.completeFinishing("test");
 
-            // Finish the top activity
-            topActivity.setState(PAUSED, "true");
-            topActivity.finishing = true;
-            topActivity.completeFinishing("test");
-
-            // Verify new top activity does not occlude keyguard.
-            assertEquals(activity, task.topRunningActivity());
-            assertFalse(keyguardController.isDisplayOccluded(DEFAULT_DISPLAY));
-        } finally {
-            keyguardController.setKeyguardShown(displayId, false, false);
-        }
+        // Verify new top activity does not occlude keyguard.
+        assertEquals(activity, task.topRunningActivity());
+        assertFalse(keyguardController.isDisplayOccluded(DEFAULT_DISPLAY));
     }
 
     /**
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityTaskManagerServiceTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityTaskManagerServiceTests.java
index 305260b..3dcae91 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityTaskManagerServiceTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityTaskManagerServiceTests.java
@@ -252,12 +252,7 @@
         mAtm.setLockScreenShown(true, true);
         mRootWindowContainer.forAllDisplays(displayContent -> {
             assertTrue(displayContent.isKeyguardLocked());
-            // Only default display supports AOD.
-            if (displayContent.isDefaultDisplay) {
-                assertTrue(displayContent.isAodShowing());
-            } else {
-                assertFalse(displayContent.isAodShowing());
-            }
+            assertTrue(displayContent.isAodShowing());
         });
 
         // Check setLockScreenShown unlocking both displays
diff --git a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
index 91e875f..1ab8c1f 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
@@ -2926,6 +2926,39 @@
     }
 
     @Test
+    public void testUpdateResolvedBoundsHorizontalPosition_leftInsets_appCentered() {
+        // Set up folded display
+        final DisplayContent display = new TestDisplayContent.Builder(mAtm, 1100, 2100)
+                .setCanRotate(true)
+                .build();
+        display.setIgnoreOrientationRequest(true);
+        final DisplayPolicy policy = display.getDisplayPolicy();
+        DisplayPolicy.DecorInsets.Info decorInfo = policy.getDecorInsetsInfo(ROTATION_90,
+                display.mBaseDisplayHeight, display.mBaseDisplayWidth);
+        decorInfo.mNonDecorInsets.set(130, 0,  60, 0);
+        spyOn(policy);
+        doReturn(decorInfo).when(policy).getDecorInsetsInfo(ROTATION_90,
+                display.mBaseDisplayHeight, display.mBaseDisplayWidth);
+        mWm.mLetterboxConfiguration.setLetterboxVerticalPositionMultiplier(0.5f);
+
+        setUpApp(display);
+        prepareUnresizable(mActivity, SCREEN_ORIENTATION_PORTRAIT);
+
+        // Resize the display to simulate unfolding in portrait
+        resizeDisplay(mTask.mDisplayContent, 2200, 1800);
+        assertTrue(mActivity.inSizeCompatMode());
+
+        // Simulate real display not taking non-decor insets into consideration
+        display.getWindowConfiguration().setAppBounds(0, 0, 2200, 1800);
+
+        // Rotate display to landscape
+        rotateDisplay(mActivity.mDisplayContent, ROTATION_90);
+
+        // App is centered
+        assertEquals(mActivity.getBounds(), new Rect(350, 50, 1450, 2150));
+    }
+
+    @Test
     public void testUpdateResolvedBoundsHorizontalPosition_left() {
         // Display configured as (2800, 1400).
         assertHorizontalPositionForDifferentDisplayConfigsForPortraitActivity(
@@ -3071,6 +3104,20 @@
     }
 
     @Test
+    public void testApplyAspectRatio_containingRatioAlmostEqualToMaxRatio_boundsUnchanged() {
+        setUpDisplaySizeWithApp(1981, 2576);
+        mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
+        mWm.mLetterboxConfiguration.setLetterboxVerticalPositionMultiplier(0.5f);
+
+        final Rect originalBounds = new Rect(mActivity.getBounds());
+        prepareUnresizable(mActivity, 1.3f, SCREEN_ORIENTATION_UNSPECIFIED);
+
+        // The containing aspect ratio is now 1.3003534, while the desired aspect ratio is 1.3. The
+        // bounds of the activity should not be changed as the difference is too small
+        assertEquals(mActivity.getBounds(), originalBounds);
+    }
+
+    @Test
     public void testUpdateResolvedBoundsHorizontalPosition_activityFillParentWidth() {
         // When activity width equals parent width, multiplier shouldn't have any effect.
         assertHorizontalPositionForDifferentDisplayConfigsForLandscapeActivity(
@@ -3082,6 +3129,39 @@
     }
 
     @Test
+    public void testUpdateResolvedBoundsVerticalPosition_topInsets_appCentered() {
+        // Set up folded display
+        final DisplayContent display = new TestDisplayContent.Builder(mAtm, 2100, 1100)
+                .setCanRotate(true)
+                .build();
+        display.setIgnoreOrientationRequest(true);
+        final DisplayPolicy policy = display.getDisplayPolicy();
+        DisplayPolicy.DecorInsets.Info decorInfo = policy.getDecorInsetsInfo(ROTATION_90,
+                display.mBaseDisplayHeight, display.mBaseDisplayWidth);
+        decorInfo.mNonDecorInsets.set(0, 130,  0, 60);
+        spyOn(policy);
+        doReturn(decorInfo).when(policy).getDecorInsetsInfo(ROTATION_90,
+                display.mBaseDisplayHeight, display.mBaseDisplayWidth);
+        mWm.mLetterboxConfiguration.setLetterboxVerticalPositionMultiplier(0.5f);
+
+        setUpApp(display);
+        prepareUnresizable(mActivity, SCREEN_ORIENTATION_LANDSCAPE);
+
+        // Resize the display to simulate unfolding in portrait
+        resizeDisplay(mTask.mDisplayContent, 1800, 2200);
+        assertTrue(mActivity.inSizeCompatMode());
+
+        // Simulate real display not taking non-decor insets into consideration
+        display.getWindowConfiguration().setAppBounds(0, 0, 1800, 2200);
+
+        // Rotate display to landscape
+        rotateDisplay(mActivity.mDisplayContent, ROTATION_90);
+
+        // App is centered
+        assertEquals(mActivity.getBounds(), new Rect(50, 350, 2150, 1450));
+    }
+
+    @Test
     public void testUpdateResolvedBoundsVerticalPosition_top() {
         // Display configured as (1400, 2800).
         assertVerticalPositionForDifferentDisplayConfigsForLandscapeActivity(
diff --git a/services/tests/wmtests/src/com/android/server/wm/SurfaceSyncGroupTest.java b/services/tests/wmtests/src/com/android/server/wm/SurfaceSyncGroupTest.java
index f655242..22d72ed 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SurfaceSyncGroupTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SurfaceSyncGroupTest.java
@@ -55,7 +55,7 @@
         SurfaceSyncGroup syncGroup = new SurfaceSyncGroup(TAG);
         syncGroup.addSyncCompleteCallback(mExecutor, finishedLatch::countDown);
         SyncTarget syncTarget = new SyncTarget();
-        syncGroup.addToSync(syncTarget, false /* parentSyncGroupMerge */);
+        syncGroup.add(syncTarget, null /* runnable */);
         syncGroup.markSyncReady();
 
         syncTarget.onBufferReady();
@@ -73,9 +73,9 @@
         SyncTarget syncTarget2 = new SyncTarget();
         SyncTarget syncTarget3 = new SyncTarget();
 
-        syncGroup.addToSync(syncTarget1, false /* parentSyncGroupMerge */);
-        syncGroup.addToSync(syncTarget2, false /* parentSyncGroupMerge */);
-        syncGroup.addToSync(syncTarget3, false /* parentSyncGroupMerge */);
+        syncGroup.add(syncTarget1, null /* runnable */);
+        syncGroup.add(syncTarget2, null /* runnable */);
+        syncGroup.add(syncTarget3, null /* runnable */);
         syncGroup.markSyncReady();
 
         syncTarget1.onBufferReady();
@@ -97,11 +97,11 @@
         SyncTarget syncTarget1 = new SyncTarget();
         SyncTarget syncTarget2 = new SyncTarget();
 
-        assertTrue(syncGroup.addToSync(syncTarget1, false /* parentSyncGroupMerge */));
+        assertTrue(syncGroup.add(syncTarget1, null /* runnable */));
         syncGroup.markSyncReady();
         // Adding to a sync that has been completed is also invalid since the sync id has been
         // cleared.
-        assertFalse(syncGroup.addToSync(syncTarget2, false /* parentSyncGroupMerge */));
+        assertFalse(syncGroup.add(syncTarget2, null /* runnable */));
     }
 
     @Test
@@ -117,8 +117,8 @@
         SyncTarget syncTarget1 = new SyncTarget();
         SyncTarget syncTarget2 = new SyncTarget();
 
-        assertTrue(syncGroup1.addToSync(syncTarget1, false /* parentSyncGroupMerge */));
-        assertTrue(syncGroup2.addToSync(syncTarget2, false /* parentSyncGroupMerge */));
+        assertTrue(syncGroup1.add(syncTarget1, null /* runnable */));
+        assertTrue(syncGroup2.add(syncTarget2, null /* runnable */));
         syncGroup1.markSyncReady();
         syncGroup2.markSyncReady();
 
@@ -147,10 +147,10 @@
         SyncTarget syncTarget1 = new SyncTarget();
         SyncTarget syncTarget2 = new SyncTarget();
 
-        assertTrue(syncGroup1.addToSync(syncTarget1, false /* parentSyncGroupMerge */));
-        assertTrue(syncGroup2.addToSync(syncTarget2, false /* parentSyncGroupMerge */));
+        assertTrue(syncGroup1.add(syncTarget1, null /* runnable */));
+        assertTrue(syncGroup2.add(syncTarget2, null /* runnable */));
         syncGroup1.markSyncReady();
-        syncGroup2.addToSync(syncGroup1, false /* parentSyncGroupMerge */);
+        syncGroup2.add(syncGroup1, null /* runnable */);
         syncGroup2.markSyncReady();
 
         // Finish syncTarget2 first to test that the syncGroup is not complete until the merged sync
@@ -183,8 +183,8 @@
         SyncTarget syncTarget1 = new SyncTarget();
         SyncTarget syncTarget2 = new SyncTarget();
 
-        assertTrue(syncGroup1.addToSync(syncTarget1, false /* parentSyncGroupMerge */));
-        assertTrue(syncGroup2.addToSync(syncTarget2, false /* parentSyncGroupMerge */));
+        assertTrue(syncGroup1.add(syncTarget1, null /* runnable */));
+        assertTrue(syncGroup2.add(syncTarget2, null /* runnable */));
         syncGroup1.markSyncReady();
         syncTarget1.onBufferReady();
 
@@ -192,7 +192,7 @@
         finishedLatch1.await(5, TimeUnit.SECONDS);
         assertEquals(0, finishedLatch1.getCount());
 
-        syncGroup2.addToSync(syncGroup1, false /* parentSyncGroupMerge */);
+        syncGroup2.add(syncGroup1, null /* runnable */);
         syncGroup2.markSyncReady();
         syncTarget2.onBufferReady();
 
@@ -216,12 +216,12 @@
         SyncTarget syncTarget2 = new SyncTarget();
         SyncTarget syncTarget3 = new SyncTarget();
 
-        assertTrue(syncGroup1.addToSync(syncTarget1, false /* parentSyncGroupMerge */));
-        assertTrue(syncGroup1.addToSync(syncTarget2, false /* parentSyncGroupMerge */));
+        assertTrue(syncGroup1.add(syncTarget1, null /* runnable */));
+        assertTrue(syncGroup1.add(syncTarget2, null /* runnable */));
 
         // Add syncTarget1 to syncGroup2 so it forces syncGroup1 into syncGroup2
-        assertTrue(syncGroup2.addToSync(syncTarget1, false /* parentSyncGroupMerge */));
-        assertTrue(syncGroup2.addToSync(syncTarget3, false /* parentSyncGroupMerge */));
+        assertTrue(syncGroup2.add(syncTarget1, null /* runnable */));
+        assertTrue(syncGroup2.add(syncTarget3, null /* runnable */));
 
         syncGroup1.markSyncReady();
         syncGroup2.markSyncReady();
@@ -261,13 +261,13 @@
         SyncTarget syncTarget2 = new SyncTarget();
         SyncTarget syncTarget3 = new SyncTarget();
 
-        assertTrue(syncGroup1.addToSync(syncTarget1, false /* parentSyncGroupMerge */));
-        assertTrue(syncGroup1.addToSync(syncTarget2, false /* parentSyncGroupMerge */));
+        assertTrue(syncGroup1.add(syncTarget1, null /* runnable */));
+        assertTrue(syncGroup1.add(syncTarget2, null /* runnable */));
         syncTarget2.onBufferReady();
 
         // Add syncTarget1 to syncGroup2 so it forces syncGroup1 into syncGroup2
-        assertTrue(syncGroup2.addToSync(syncTarget1, false /* parentSyncGroupMerge */));
-        assertTrue(syncGroup2.addToSync(syncTarget3, false /* parentSyncGroupMerge */));
+        assertTrue(syncGroup2.add(syncTarget1, null /* runnable */));
+        assertTrue(syncGroup2.add(syncTarget3, null /* runnable */));
 
         syncGroup1.markSyncReady();
         syncGroup2.markSyncReady();
@@ -303,7 +303,9 @@
         SurfaceSyncGroup.setTransactionFactory(() -> targetTransaction);
 
         SyncTarget syncTarget = new SyncTarget();
-        assertTrue(syncGroup.addToSync(syncTarget, true /* parentSyncGroupMerge */));
+        assertTrue(
+                syncGroup.add(syncTarget.mISurfaceSyncGroup, true /* parentSyncGroupMerge */,
+                        null /* runnable */));
         syncTarget.markSyncReady();
 
         // When parentSyncGroupMerge is true, the transaction passed in merges the main SyncGroup
@@ -328,7 +330,7 @@
         SurfaceSyncGroup.setTransactionFactory(() -> targetTransaction);
 
         SyncTarget syncTarget = new SyncTarget();
-        assertTrue(syncGroup.addToSync(syncTarget, false /* parentSyncGroupMerge */));
+        assertTrue(syncGroup.add(syncTarget, null /* runnable */));
         syncTarget.markSyncReady();
 
         // When parentSyncGroupMerge is false, the transaction passed in should not merge
@@ -343,9 +345,9 @@
         SurfaceSyncGroup syncGroup = new SurfaceSyncGroup(TAG);
         syncGroup.addSyncCompleteCallback(mExecutor, finishedLatch::countDown);
         SyncTarget syncTarget = new SyncTarget();
-        syncGroup.addToSync(syncTarget, false /* parentSyncGroupMerge */);
+        syncGroup.add(syncTarget, null /* runnable */);
         // Add the syncTarget to the same syncGroup and ensure it doesn't crash.
-        syncGroup.addToSync(syncTarget, false /* parentSyncGroupMerge */);
+        syncGroup.add(syncTarget, null /* runnable */);
         syncGroup.markSyncReady();
 
         syncTarget.onBufferReady();
diff --git a/services/tests/wmtests/src/com/android/server/wm/SurfaceSyncGroupContinuousTest.java b/services/tests/wmtests/src/com/android/server/wm/SurfaceViewSyncContinuousTest.java
similarity index 60%
rename from services/tests/wmtests/src/com/android/server/wm/SurfaceSyncGroupContinuousTest.java
rename to services/tests/wmtests/src/com/android/server/wm/SurfaceViewSyncContinuousTest.java
index eef7cc2..ad7314c 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SurfaceSyncGroupContinuousTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SurfaceViewSyncContinuousTest.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2022 The Android Open Source Project
+ * 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.
@@ -22,15 +22,10 @@
 
 import android.app.KeyguardManager;
 import android.os.PowerManager;
-import android.platform.test.annotations.Presubmit;
-import android.view.SurfaceControl;
 import android.view.cts.surfacevalidator.CapturedActivity;
-import android.window.SurfaceSyncGroup;
 
 import androidx.test.rule.ActivityTestRule;
 
-import com.android.server.wm.scvh.SyncValidatorSCVHTestCase;
-
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
@@ -38,7 +33,7 @@
 
 import java.util.Objects;
 
-public class SurfaceSyncGroupContinuousTest {
+public class SurfaceViewSyncContinuousTest {
     @Rule
     public TestName mName = new TestName();
 
@@ -59,33 +54,10 @@
             pressWakeupButton();
             pressUnlockButton();
         }
-        SurfaceSyncGroup.setTransactionFactory(SurfaceControl.Transaction::new);
     }
 
     @Test
     public void testSurfaceViewSyncDuringResize() throws Throwable {
-        mCapturedActivity.verifyTest(new SurfaceSyncGroupValidatorTestCase(), mName);
-    }
-
-    @Test
-    public void testSurfaceControlViewHostIPCSync_Fast() throws Throwable {
-        mCapturedActivity.verifyTest(
-                new SyncValidatorSCVHTestCase(0 /* delayMs */, false /* overrideDefaultDuration */),
-                mName);
-    }
-
-    @Test
-    public void testSurfaceControlViewHostIPCSync_Slow() throws Throwable {
-        mCapturedActivity.verifyTest(new SyncValidatorSCVHTestCase(100 /* delayMs */,
-                false /* overrideDefaultDuration */), mName);
-    }
-
-    @Test
-    @Presubmit
-    public void testSurfaceControlViewHostIPCSync_Short() throws Throwable {
-        mCapturedActivity.setMinimumCaptureDurationMs(5000);
-        mCapturedActivity.verifyTest(
-                new SyncValidatorSCVHTestCase(0 /* delayMs */, true /* overrideDefaultDuration */),
-                mName);
+        mCapturedActivity.verifyTest(new SurfaceViewSyncValidatorTestCase(), mName);
     }
 }
diff --git a/services/tests/wmtests/src/com/android/server/wm/SurfaceSyncGroupValidatorTestCase.java b/services/tests/wmtests/src/com/android/server/wm/SurfaceViewSyncValidatorTestCase.java
similarity index 95%
rename from services/tests/wmtests/src/com/android/server/wm/SurfaceSyncGroupValidatorTestCase.java
rename to services/tests/wmtests/src/com/android/server/wm/SurfaceViewSyncValidatorTestCase.java
index 1fa0c23..93c1bad 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SurfaceSyncGroupValidatorTestCase.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SurfaceViewSyncValidatorTestCase.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2022 The Android Open Source Project
+ * 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.
@@ -41,7 +41,7 @@
  * never an empty area (black color). The test uses {@link SurfaceSyncGroup} class to gather the
  * content it wants to synchronize.
  */
-public class SurfaceSyncGroupValidatorTestCase implements ISurfaceValidatorTestCase {
+public class SurfaceViewSyncValidatorTestCase implements ISurfaceValidatorTestCase {
     private static final String TAG = "SurfaceSyncGroupValidatorTestCase";
 
     private final Runnable mRunnable = new Runnable() {
@@ -76,7 +76,7 @@
         public void surfaceChanged(@NonNull SurfaceHolder holder, int format, int width,
                 int height) {
             if (mSyncGroup != null) {
-                mSyncGroup.addToSync(mSurfaceView, frameCallback ->
+                mSyncGroup.add(mSurfaceView, frameCallback ->
                         mRenderingThread.setFrameCallback(frameCallback));
                 mSyncGroup.markSyncReady();
                 mSyncGroup = null;
@@ -133,7 +133,7 @@
 
         mRenderingThread.pauseRendering();
         mSyncGroup = new SurfaceSyncGroup(TAG);
-        mSyncGroup.addToSync(mParent.getRootSurfaceControl());
+        mSyncGroup.add(mParent.getRootSurfaceControl(), null /* runanble */);
 
         ViewGroup.LayoutParams svParams = mSurfaceView.getLayoutParams();
         svParams.height = height;
diff --git a/services/tests/wmtests/src/com/android/server/wm/scvh/EmbeddedSCVHService.java b/services/tests/wmtests/src/com/android/server/wm/scvh/EmbeddedSCVHService.java
deleted file mode 100644
index 3bd577c..0000000
--- a/services/tests/wmtests/src/com/android/server/wm/scvh/EmbeddedSCVHService.java
+++ /dev/null
@@ -1,127 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.wm.scvh;
-
-import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
-
-import android.annotation.Nullable;
-import android.app.Service;
-import android.content.Context;
-import android.content.Intent;
-import android.graphics.Color;
-import android.graphics.PixelFormat;
-import android.hardware.display.DisplayManager;
-import android.os.Handler;
-import android.os.IBinder;
-import android.os.Looper;
-import android.os.RemoteException;
-import android.view.Display;
-import android.view.SurfaceControl.Transaction;
-import android.view.SurfaceControlViewHost;
-import android.view.View;
-import android.view.WindowManager;
-import android.widget.FrameLayout;
-
-import androidx.annotation.NonNull;
-
-public class EmbeddedSCVHService extends Service {
-    private static final String TAG = "SCVHEmbeddedService";
-    private SurfaceControlViewHost mVr;
-
-    private Handler mHandler;
-
-    @Override
-    public void onCreate() {
-        super.onCreate();
-        mHandler = new Handler(Looper.getMainLooper());
-    }
-
-    @Nullable
-    @Override
-    public IBinder onBind(Intent intent) {
-        // Return the interface
-        return new AttachEmbeddedWindow();
-    }
-
-    public static class SlowView extends View {
-        private long mDelayMs;
-        public SlowView(Context context) {
-            super(context);
-        }
-
-        @Override
-        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
-            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
-            try {
-                Thread.sleep(mDelayMs);
-            } catch (InterruptedException e) {
-            }
-        }
-
-        public void setDelay(long delayMs) {
-            mDelayMs = delayMs;
-        }
-    }
-
-    private class AttachEmbeddedWindow extends IAttachEmbeddedWindow.Stub {
-        @Override
-        public void attachEmbedded(IBinder hostToken, int width,
-                int height, int displayId, long delayMs, IAttachEmbeddedWindowCallback callback) {
-            mHandler.post(() -> {
-                Context context = EmbeddedSCVHService.this;
-                Display display = getApplicationContext().getSystemService(
-                        DisplayManager.class).getDisplay(displayId);
-                mVr = new SurfaceControlViewHost(context, display, hostToken);
-                FrameLayout content = new FrameLayout(context);
-
-                SlowView slowView = new SlowView(context);
-                slowView.setDelay(delayMs);
-                slowView.setBackgroundColor(Color.BLUE);
-                content.addView(slowView);
-                WindowManager.LayoutParams lp = new WindowManager.LayoutParams(width, height,
-                        TYPE_APPLICATION, 0, PixelFormat.OPAQUE);
-                lp.setTitle("EmbeddedWindow");
-                mVr.setView(content, lp);
-
-                content.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() {
-                    @Override
-                    public void onViewAttachedToWindow(@NonNull View v) {
-                        // First frame isn't included in the sync so don't notify the host about the
-                        // surface package until the first draw has completed.
-                        Transaction transaction = new Transaction().addTransactionCommittedListener(
-                                getMainExecutor(), () -> {
-                                    try {
-                                        callback.onEmbeddedWindowAttached(mVr.getSurfacePackage());
-                                    } catch (RemoteException e) {
-                                    }
-                                });
-                        v.getRootSurfaceControl().applyTransactionOnDraw(transaction);
-                    }
-
-                    @Override
-                    public void onViewDetachedFromWindow(@NonNull View v) {
-                    }
-                });
-            });
-        }
-
-        @Override
-        public void relayout(WindowManager.LayoutParams lp) {
-            mHandler.post(() -> mVr.relayout(lp));
-        }
-    }
-}
diff --git a/services/tests/wmtests/src/com/android/server/wm/scvh/IAttachEmbeddedWindow.aidl b/services/tests/wmtests/src/com/android/server/wm/scvh/IAttachEmbeddedWindow.aidl
deleted file mode 100644
index 3439567..0000000
--- a/services/tests/wmtests/src/com/android/server/wm/scvh/IAttachEmbeddedWindow.aidl
+++ /dev/null
@@ -1,27 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.wm.scvh;
-
-import android.view.SurfaceControlViewHost.SurfacePackage;
-import android.os.IBinder;
-import com.android.server.wm.scvh.IAttachEmbeddedWindowCallback;
-import android.view.WindowManager.LayoutParams;
-
-interface IAttachEmbeddedWindow {
-    void attachEmbedded(IBinder hostToken, int width, int height, int displayId, long delayMs, IAttachEmbeddedWindowCallback callback);
-    void relayout(in LayoutParams lp);
-}
\ No newline at end of file
diff --git a/services/tests/wmtests/src/com/android/server/wm/scvh/IAttachEmbeddedWindowCallback.aidl b/services/tests/wmtests/src/com/android/server/wm/scvh/IAttachEmbeddedWindowCallback.aidl
deleted file mode 100644
index 92abfc8..0000000
--- a/services/tests/wmtests/src/com/android/server/wm/scvh/IAttachEmbeddedWindowCallback.aidl
+++ /dev/null
@@ -1,23 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.wm.scvh;
-
-import android.view.SurfaceControlViewHost.SurfacePackage;
-
-interface IAttachEmbeddedWindowCallback {
-    void onEmbeddedWindowAttached(in SurfacePackage surfacePackage);
-}
\ No newline at end of file
diff --git a/services/tests/wmtests/src/com/android/server/wm/scvh/SyncValidatorSCVHTestCase.java b/services/tests/wmtests/src/com/android/server/wm/scvh/SyncValidatorSCVHTestCase.java
deleted file mode 100644
index 07dac8d..0000000
--- a/services/tests/wmtests/src/com/android/server/wm/scvh/SyncValidatorSCVHTestCase.java
+++ /dev/null
@@ -1,241 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.wm.scvh;
-
-import static junit.framework.Assert.assertEquals;
-import static junit.framework.Assert.assertNotNull;
-
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.ServiceConnection;
-import android.graphics.Color;
-import android.graphics.PixelFormat;
-import android.graphics.Point;
-import android.os.Handler;
-import android.os.IBinder;
-import android.os.Looper;
-import android.os.RemoteException;
-import android.util.Log;
-import android.view.Gravity;
-import android.view.SurfaceControlViewHost.SurfacePackage;
-import android.view.SurfaceHolder;
-import android.view.SurfaceView;
-import android.view.ViewGroup;
-import android.view.WindowManager;
-import android.view.cts.surfacevalidator.ISurfaceValidatorTestCase;
-import android.view.cts.surfacevalidator.PixelChecker;
-import android.widget.FrameLayout;
-import android.window.SurfaceSyncGroup;
-
-import androidx.annotation.NonNull;
-
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-
-public class SyncValidatorSCVHTestCase implements ISurfaceValidatorTestCase {
-    private static final String TAG = "SCVHSyncValidatorTestCase";
-
-    private final Point[] mSizes = new Point[]{new Point(500, 500), new Point(700, 400),
-            new Point(300, 800), new Point(200, 200)};
-    private int mLastSizeIndex = 1;
-
-    private final long mDelayMs;
-    private final boolean mOverrideDefaultDuration;
-
-    public SyncValidatorSCVHTestCase(long delayMs, boolean overrideDefaultDuration) {
-        mDelayMs = delayMs;
-        mOverrideDefaultDuration = overrideDefaultDuration;
-    }
-
-    private final Runnable mRunnable = new Runnable() {
-        @Override
-        public void run() {
-            Point size = mSizes[mLastSizeIndex % mSizes.length];
-            Runnable svResizeRunnable = () -> {
-                ViewGroup.LayoutParams svParams = mSurfaceView.getLayoutParams();
-                svParams.width = size.x;
-                svParams.height = size.y;
-                mSurfaceView.setLayoutParams(svParams);
-            };
-            Runnable resizeRunnable = () -> {
-                try {
-                    final WindowManager.LayoutParams lp = new WindowManager.LayoutParams(size.x,
-                            size.y,
-                            WindowManager.LayoutParams.TYPE_APPLICATION, 0,
-                            PixelFormat.TRANSPARENT);
-                    mIAttachEmbeddedWindow.relayout(lp);
-                } catch (RemoteException e) {
-                }
-            };
-
-            SurfaceSyncGroup syncGroup = new SurfaceSyncGroup(TAG);
-            syncGroup.addToSync(mSurfaceView.getRootSurfaceControl(), svResizeRunnable);
-            syncGroup.addToSync(mSurfacePackage, resizeRunnable);
-            syncGroup.markSyncReady();
-
-            mLastSizeIndex++;
-
-            mHandler.postDelayed(this, mDelayMs + 50);
-        }
-    };
-
-    private Handler mHandler;
-    private SurfaceView mSurfaceView;
-
-    private final CountDownLatch mReadyLatch = new CountDownLatch(1);
-    private boolean mSurfaceCreated;
-    private boolean mIsAttached;
-    private final Object mLock = new Object();
-    private int mDisplayId;
-    private IAttachEmbeddedWindow mIAttachEmbeddedWindow;
-    private SurfacePackage mSurfacePackage;
-
-    final SurfaceHolder.Callback mCallback = new SurfaceHolder.Callback() {
-        @Override
-        public void surfaceCreated(@NonNull SurfaceHolder holder) {
-            synchronized (mLock) {
-                mSurfaceCreated = true;
-            }
-            if (isReadyToAttach()) {
-                attachEmbedded();
-            }
-        }
-
-        @Override
-        public void surfaceChanged(@NonNull SurfaceHolder holder, int format, int width,
-                int height) {
-        }
-
-        @Override
-        public void surfaceDestroyed(@NonNull SurfaceHolder holder) {
-        }
-    };
-
-    @Override
-    public PixelChecker getChecker() {
-        return new PixelChecker(Color.BLACK) {
-            @Override
-            public boolean checkPixels(int matchingPixelCount, int width, int height) {
-                // Content has been set up yet.
-                if (mReadyLatch.getCount() > 0) {
-                    return true;
-                }
-                return matchingPixelCount == 0;
-            }
-        };
-    }
-
-    private final ServiceConnection mConnection = new ServiceConnection() {
-        // Called when the connection with the service is established
-        public void onServiceConnected(ComponentName className, IBinder service) {
-            Log.d(TAG, "Service Connected");
-            synchronized (mLock) {
-                mIAttachEmbeddedWindow = IAttachEmbeddedWindow.Stub.asInterface(service);
-            }
-            if (isReadyToAttach()) {
-                attachEmbedded();
-            }
-        }
-
-        public void onServiceDisconnected(ComponentName className) {
-            Log.d(TAG, "Service Disconnected");
-            mIAttachEmbeddedWindow = null;
-            synchronized (mLock) {
-                mIsAttached = false;
-            }
-        }
-    };
-
-    private boolean isReadyToAttach() {
-        synchronized (mLock) {
-            if (!mSurfaceCreated) {
-                Log.d(TAG, "surface is not created");
-            }
-            if (mIAttachEmbeddedWindow == null) {
-                Log.d(TAG, "Service is not attached");
-            }
-            if (mIsAttached) {
-                Log.d(TAG, "Already attached");
-            }
-
-            return mSurfaceCreated && mIAttachEmbeddedWindow != null && !mIsAttached;
-        }
-    }
-
-    private void attachEmbedded() {
-        synchronized (mLock) {
-            mIsAttached = true;
-        }
-        try {
-            mIAttachEmbeddedWindow.attachEmbedded(mSurfaceView.getHostToken(), mSizes[0].x,
-                    mSizes[0].y, mDisplayId, mDelayMs, new IAttachEmbeddedWindowCallback.Stub() {
-                        @Override
-                        public void onEmbeddedWindowAttached(SurfacePackage surfacePackage) {
-                            mHandler.post(() -> {
-                                mSurfacePackage = surfacePackage;
-                                mSurfaceView.setChildSurfacePackage(surfacePackage);
-                                mReadyLatch.countDown();
-                            });
-                        }
-                    });
-        } catch (RemoteException e) {
-        }
-    }
-
-    @Override
-    public void start(Context context, FrameLayout parent) {
-        mDisplayId = context.getDisplayId();
-        mHandler = new Handler(Looper.getMainLooper());
-
-        Intent intent = new Intent(context, EmbeddedSCVHService.class);
-        intent.setAction(EmbeddedSCVHService.class.getName());
-        context.bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
-
-        mSurfaceView = new SurfaceView(context);
-        mSurfaceView.getHolder().addCallback(mCallback);
-
-        FrameLayout.LayoutParams layoutParams = new FrameLayout.LayoutParams(mSizes[0].x,
-                mSizes[0].y);
-        layoutParams.gravity = Gravity.CENTER;
-        parent.addView(mSurfaceView, layoutParams);
-    }
-
-    @Override
-    public void waitForReady() {
-
-        try {
-            mReadyLatch.await(5, TimeUnit.SECONDS);
-        } catch (InterruptedException e) {
-        }
-
-        assertEquals("Timed out waiting for setup", 0, mReadyLatch.getCount());
-        assertNotNull("SurfacePackage is null", mSurfacePackage);
-
-        mHandler.post(mRunnable);
-    }
-
-    @Override
-    public void end() {
-        mHandler.removeCallbacks(mRunnable);
-    }
-
-    @Override
-    public boolean hasAnimation() {
-        return !mOverrideDefaultDuration;
-    }
-}
diff --git a/services/texttospeech/java/com/android/server/texttospeech/TextToSpeechManagerPerUserService.java b/services/texttospeech/java/com/android/server/texttospeech/TextToSpeechManagerPerUserService.java
index 55cbc72..f5f9b84 100644
--- a/services/texttospeech/java/com/android/server/texttospeech/TextToSpeechManagerPerUserService.java
+++ b/services/texttospeech/java/com/android/server/texttospeech/TextToSpeechManagerPerUserService.java
@@ -83,7 +83,7 @@
             ServiceConnector.Impl<ITextToSpeechService> {
 
         private final String mEngine;
-        private final ITextToSpeechSessionCallback mCallback;
+        private ITextToSpeechSessionCallback mCallback;
         private final DeathRecipient mUnbindOnDeathHandler;
 
         static void start(Context context, @UserIdInt int userId, String engine,
@@ -156,6 +156,7 @@
                 } catch (NoSuchElementException ex) {
                     Slog.d(TAG, "The death recipient was not linked.");
                 }
+                mCallback = null;
             }
         }
 
diff --git a/telephony/java/android/telephony/SubscriptionManager.java b/telephony/java/android/telephony/SubscriptionManager.java
index 189a08c..7404adb 100644
--- a/telephony/java/android/telephony/SubscriptionManager.java
+++ b/telephony/java/android/telephony/SubscriptionManager.java
@@ -160,6 +160,10 @@
     private static final String CACHE_KEY_SUBSCRIPTION_MANAGER_SERVICE_PROPERTY =
             "cache_key.telephony.subscription_manager_service";
 
+    /** The temporarily cache key to indicate whether subscription manager service is enabled. */
+    private static final String CACHE_KEY_SUBSCRIPTION_MANAGER_SERVICE_ENABLED_PROPERTY =
+            "cache_key.telephony.subscription_manager_service_enabled";
+
     /** @hide */
     public static final String GET_SIM_SPECIFIC_SETTINGS_METHOD_NAME = "getSimSpecificSettings";
 
@@ -340,6 +344,12 @@
                     CACHE_KEY_SUBSCRIPTION_MANAGER_SERVICE_PROPERTY,
                     INVALID_PHONE_INDEX);
 
+    //TODO: Removed before U AOSP public release.
+    private static VoidPropertyInvalidatedCache<Boolean> sIsSubscriptionManagerServiceEnabled =
+            new VoidPropertyInvalidatedCache<>(ISub::isSubscriptionManagerServiceEnabled,
+                    CACHE_KEY_SUBSCRIPTION_MANAGER_SERVICE_ENABLED_PROPERTY,
+                    false);
+
     /**
      * Generates a content {@link Uri} used to receive updates on simInfo change
      * on the given subscriptionId
@@ -1358,8 +1368,6 @@
 
     private final Context mContext;
 
-    private static boolean sIsSubscriptionManagerServiceEnabled = false;
-
     // Cache of Resource that has been created in getResourcesForSubId. Key is a Pair containing
     // the Context and subId.
     private static final Map<Pair<Context, Integer>, Resources> sResourcesCache =
@@ -1445,9 +1453,6 @@
     public SubscriptionManager(Context context) {
         if (DBG) logd("SubscriptionManager created");
         mContext = context;
-
-        sIsSubscriptionManagerServiceEnabled = mContext.getResources().getBoolean(
-                com.android.internal.R.bool.config_using_subscription_manager_service);
     }
 
     /**
@@ -1456,8 +1461,9 @@
      *
      * @hide
      */
+    //TODO: Removed before U AOSP public release.
     public static boolean isSubscriptionManagerServiceEnabled() {
-        return sIsSubscriptionManagerServiceEnabled;
+        return sIsSubscriptionManagerServiceEnabled.query(null);
     }
 
     private NetworkPolicyManager getNetworkPolicyManager() {
@@ -3971,6 +3977,13 @@
         PropertyInvalidatedCache.invalidateCache(CACHE_KEY_SUBSCRIPTION_MANAGER_SERVICE_PROPERTY);
     }
 
+    /** @hide */
+    //TODO: Removed before U AOSP public release.
+    public static void invalidateSubscriptionManagerServiceEnabledCaches() {
+        PropertyInvalidatedCache.invalidateCache(
+                CACHE_KEY_SUBSCRIPTION_MANAGER_SERVICE_ENABLED_PROPERTY);
+    }
+
     /**
      * Allows a test process to disable client-side caching operations.
      *
@@ -3992,6 +4005,8 @@
         sGetSlotIndexCache.disableLocal();
         sGetSubIdCache.disableLocal();
         sGetPhoneIdCache.disableLocal();
+
+        sIsSubscriptionManagerServiceEnabled.disableLocal();
     }
 
     /**
@@ -4014,6 +4029,8 @@
         sGetSlotIndexCache.clear();
         sGetSubIdCache.clear();
         sGetPhoneIdCache.clear();
+
+        sIsSubscriptionManagerServiceEnabled.clear();
     }
 
     /**
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index 83b9098..0921834 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -16355,6 +16355,7 @@
 
     /**
      * Setup sIPhoneSubInfo for testing.
+     *
      * @hide
      */
     @VisibleForTesting
@@ -16365,9 +16366,21 @@
     }
 
     /**
-     * Whether device can connect to 5G network when two SIMs are active.
+     * Setup sISub for testing.
+     *
      * @hide
-     * TODO b/153669716: remove or make system API.
+     */
+    @VisibleForTesting
+    public static void setupISubForTest(ISub iSub) {
+        synchronized (sCacheLock) {
+            sISub = iSub;
+        }
+    }
+
+    /**
+     * Whether device can connect to 5G network when two SIMs are active.
+     *
+     * @hide TODO b/153669716: remove or make system API.
      */
     public boolean canConnectTo5GInDsdsMode() {
         ITelephony telephony = getITelephony();
diff --git a/telephony/java/com/android/internal/telephony/ISub.aidl b/telephony/java/com/android/internal/telephony/ISub.aidl
index 25a714a..defa046 100644
--- a/telephony/java/com/android/internal/telephony/ISub.aidl
+++ b/telephony/java/com/android/internal/telephony/ISub.aidl
@@ -356,4 +356,11 @@
        * @hide
        */
        List<SubscriptionInfo> getSubscriptionInfoListAssociatedWithUser(in UserHandle userHandle);
+
+       /**
+        * @return {@code true} if using SubscriptionManagerService instead of
+        * SubscriptionController.
+        */
+       //TODO: Removed before U AOSP public release.
+       boolean isSubscriptionManagerServiceEnabled();
 }
diff --git a/tests/BinaryTransparencyHostTest/Android.bp b/tests/BinaryTransparencyHostTest/Android.bp
new file mode 100644
index 0000000..142e3dd
--- /dev/null
+++ b/tests/BinaryTransparencyHostTest/Android.bp
@@ -0,0 +1,42 @@
+// 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 {
+    // See: http://go/android-license-faq
+    // A large-scale-change added 'default_applicable_licenses' to import
+    // all of the 'license_kinds' from "frameworks_base_license"
+    // to get the below license kinds:
+    //   SPDX-license-identifier-Apache-2.0
+    default_applicable_licenses: ["frameworks_base_license"],
+}
+
+java_test_host {
+    name: "BinaryTransparencyHostTest",
+    srcs: ["src/**/*.java"],
+    libs: [
+        "tradefed",
+        "compatibility-tradefed",
+        "compatibility-host-util",
+    ],
+    static_libs: [
+        "truth-prebuilt",
+    ],
+    data: [
+        ":BinaryTransparencyTestApp",
+        ":EasterEgg",
+    ],
+    test_suites: [
+        "general-tests",
+    ],
+}
diff --git a/tests/BinaryTransparencyHostTest/AndroidTest.xml b/tests/BinaryTransparencyHostTest/AndroidTest.xml
new file mode 100644
index 0000000..e0d11c0
--- /dev/null
+++ b/tests/BinaryTransparencyHostTest/AndroidTest.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+<configuration description="Binary Transparency integration test">
+    <option name="test-suite-tag" value="apct" />
+
+    <!-- Service is not exposed to apps. Disable SELinux for testing purpose. -->
+    <target_preparer class="com.android.tradefed.targetprep.DisableSELinuxTargetPreparer" />
+
+    <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+        <option name="cleanup-apks" value="true" />
+        <option name="test-file-name" value="BinaryTransparencyTestApp.apk" />
+    </target_preparer>
+
+    <test class="com.android.compatibility.common.tradefed.testtype.JarHostTest" >
+        <option name="jar" value="BinaryTransparencyHostTest.jar" />
+        <option name="runtime-hint" value="1m" />
+    </test>
+</configuration>
diff --git a/tests/BinaryTransparencyHostTest/OWNERS b/tests/BinaryTransparencyHostTest/OWNERS
new file mode 100644
index 0000000..ca84550
--- /dev/null
+++ b/tests/BinaryTransparencyHostTest/OWNERS
@@ -0,0 +1 @@
+include /core/java/android/transparency/OWNERS
diff --git a/tests/BinaryTransparencyHostTest/src/android/transparency/test/BinaryTransparencyHostTest.java b/tests/BinaryTransparencyHostTest/src/android/transparency/test/BinaryTransparencyHostTest.java
new file mode 100644
index 0000000..84bed92
--- /dev/null
+++ b/tests/BinaryTransparencyHostTest/src/android/transparency/test/BinaryTransparencyHostTest.java
@@ -0,0 +1,77 @@
+/*
+ * 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.transparency.test;
+
+import static org.junit.Assert.assertTrue;
+
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
+import com.android.tradefed.testtype.junit4.DeviceTestRunOptions;
+import com.android.tradefed.util.CommandResult;
+import com.android.tradefed.util.CommandStatus;
+
+import org.junit.After;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+// TODO: Add @Presubmit
+@RunWith(DeviceJUnit4ClassRunner.class)
+public final class BinaryTransparencyHostTest extends BaseHostJUnit4Test {
+    private static final String PACKAGE_NAME = "android.transparency.test.app";
+
+    @After
+    public void tearDown() throws Exception {
+        uninstallPackage("com.android.egg");
+    }
+
+    @Test
+    public void testCollectAllApexInfo() throws Exception {
+        var options = new DeviceTestRunOptions(PACKAGE_NAME);
+        options.setTestClassName(PACKAGE_NAME + ".BinaryTransparencyTest");
+        options.setTestMethodName("testCollectAllApexInfo");
+
+        // Collect APEX package names from /apex, then pass them as expectation to be verified.
+        CommandResult result = getDevice().executeShellV2Command(
+                "ls -d /apex/*/ |grep -v @ |grep -v /apex/sharedlibs |cut -d/ -f3");
+        assertTrue(result.getStatus() == CommandStatus.SUCCESS);
+        String[] packageNames = result.getStdout().split("\n");
+        for (var i = 0; i < packageNames.length; i++) {
+            options.addInstrumentationArg("apex-" + String.valueOf(i), packageNames[i]);
+        }
+        options.addInstrumentationArg("apex-number", Integer.toString(packageNames.length));
+        runDeviceTests(options);
+    }
+
+    @Test
+    public void testCollectAllUpdatedPreloadInfo() throws Exception {
+        installPackage("EasterEgg.apk");
+        runDeviceTest("testCollectAllUpdatedPreloadInfo");
+    }
+
+    @Test
+    public void testMeasureMbas() throws Exception {
+        // TODO(265244016): figure out a way to install an MBA
+    }
+
+    private void runDeviceTest(String method) throws DeviceNotAvailableException {
+        var options = new DeviceTestRunOptions(PACKAGE_NAME);
+        options.setTestClassName(PACKAGE_NAME + ".BinaryTransparencyTest");
+        options.setTestMethodName(method);
+        runDeviceTests(options);
+    }
+}
diff --git a/tests/BinaryTransparencyHostTest/test-app/Android.bp b/tests/BinaryTransparencyHostTest/test-app/Android.bp
new file mode 100644
index 0000000..b5193dd
--- /dev/null
+++ b/tests/BinaryTransparencyHostTest/test-app/Android.bp
@@ -0,0 +1,40 @@
+// 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 {
+    // See: http://go/android-license-faq
+    // A large-scale-change added 'default_applicable_licenses' to import
+    // all of the 'license_kinds' from "frameworks_base_license"
+    // to get the below license kinds:
+    //   SPDX-license-identifier-Apache-2.0
+    default_applicable_licenses: ["frameworks_base_license"],
+}
+
+android_test_helper_app {
+    name: "BinaryTransparencyTestApp",
+    manifest: "AndroidManifest.xml",
+    srcs: ["src/**/*.java"],
+    static_libs: [
+        "androidx.test.core",
+        "compatibility-device-util-axt",
+        "junit",
+    ],
+    test_suites: [
+        "general-tests",
+    ],
+    platform_apis: true,
+    dex_preopt: {
+        enabled: false,
+    },
+}
diff --git a/tests/BinaryTransparencyHostTest/test-app/AndroidManifest.xml b/tests/BinaryTransparencyHostTest/test-app/AndroidManifest.xml
new file mode 100644
index 0000000..42e616e
--- /dev/null
+++ b/tests/BinaryTransparencyHostTest/test-app/AndroidManifest.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * 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.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="android.transparency.test.app">
+    <application>
+        <uses-library android:name="android.test.runner" />
+    </application>
+
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+        android:label="APCT tests for binary transparency"
+        android:targetPackage="android.transparency.test.app" />
+</manifest>
diff --git a/tests/BinaryTransparencyHostTest/test-app/src/android/transparency/test/app/BinaryTransparencyTest.java b/tests/BinaryTransparencyHostTest/test-app/src/android/transparency/test/app/BinaryTransparencyTest.java
new file mode 100644
index 0000000..aedb366
--- /dev/null
+++ b/tests/BinaryTransparencyHostTest/test-app/src/android/transparency/test/app/BinaryTransparencyTest.java
@@ -0,0 +1,112 @@
+/*
+ * 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.transparency.test.app;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import android.content.Context;
+import android.os.Bundle;
+import android.transparency.BinaryTransparencyManager;
+import android.util.Log;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.internal.os.IBinaryTransparencyService.AppInfo;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.HashSet;
+import java.util.HexFormat;
+import java.util.stream.Collectors;
+
+@RunWith(AndroidJUnit4.class)
+public class BinaryTransparencyTest {
+    private static final String TAG = "BinaryTransparencyTest";
+
+    private BinaryTransparencyManager mBt;
+
+    @Before
+    public void setUp() {
+        Context context = InstrumentationRegistry.getInstrumentation().getContext();
+        mBt = context.getSystemService(BinaryTransparencyManager.class);
+    }
+
+    @Test
+    public void testCollectAllApexInfo() {
+        // Prepare the expectation received from host's shell command
+        Bundle args = InstrumentationRegistry.getArguments();
+        assertThat(args).isNotNull();
+        int number = Integer.valueOf(args.getString("apex-number"));
+        assertThat(number).isGreaterThan(0);
+        var expectedApexNames = new HashSet<String>();
+        for (var i = 0; i < number; i++) {
+            String moduleName = args.getString("apex-" + Integer.toString(i));
+            expectedApexNames.add(moduleName);
+        }
+
+        // Action
+        var apexInfoList = mBt.collectAllApexInfo(/* includeTestOnly */ true);
+
+        // Verify actual apex names
+        var actualApexesNames = apexInfoList.stream().map((apex) -> apex.moduleName)
+                .collect(Collectors.toList());
+        assertThat(actualApexesNames).containsExactlyElementsIn(expectedApexNames);
+
+        // Perform more valitidy checks
+        var digestsSeen = new HashSet<String>();
+        var hexFormatter = HexFormat.of();
+        for (var apex : apexInfoList) {
+            Log.d(TAG, "Verifying " + apex.packageName + " / " + apex.moduleName);
+
+            assertThat(apex.longVersion).isGreaterThan(0);
+            assertThat(apex.digestAlgorithm).isGreaterThan(0);
+            assertThat(apex.signerDigests).asList().containsNoneOf(null, "");
+
+            assertThat(apex.digest).isNotNull();
+            String digestHex = hexFormatter.formatHex(apex.digest);
+            boolean isNew = digestsSeen.add(digestHex);
+            assertWithMessage(
+                    "Digest should be unique, but received a dup: " + digestHex)
+                    .that(isNew).isTrue();
+        }
+    }
+
+    @Test
+    public void testCollectAllUpdatedPreloadInfo() {
+        var preloadInfoList = mBt.collectAllUpdatedPreloadInfo(new Bundle());
+        assertThat(preloadInfoList).isNotEmpty();  // because we just installed from the host side
+        AppInfo updatedPreload = null;
+        for (var preload : preloadInfoList) {
+            Log.d(TAG, "Received " + preload.packageName);
+            if (preload.packageName.equals("com.android.egg")) {
+                assertWithMessage("Received the same package").that(updatedPreload).isNull();
+                updatedPreload = preload;
+            }
+        }
+
+        // Verify
+        assertThat(updatedPreload.longVersion).isGreaterThan(0);
+        assertThat(updatedPreload.digestAlgorithm).isGreaterThan(0);
+        assertThat(updatedPreload.digest).isNotEmpty();
+        assertThat(updatedPreload.mbaStatus).isEqualTo(/* MBA_STATUS_UPDATED_PRELOAD */ 2);
+        assertThat(updatedPreload.signerDigests).asList().containsNoneOf(null, "");
+    }
+}
diff --git a/tests/SurfaceControlViewHostTest/src/com/android/test/viewembed/SurfaceControlViewHostSyncTest.java b/tests/SurfaceControlViewHostTest/src/com/android/test/viewembed/SurfaceControlViewHostSyncTest.java
index ea727b9..359eb35 100644
--- a/tests/SurfaceControlViewHostTest/src/com/android/test/viewembed/SurfaceControlViewHostSyncTest.java
+++ b/tests/SurfaceControlViewHostTest/src/com/android/test/viewembed/SurfaceControlViewHostSyncTest.java
@@ -131,8 +131,8 @@
 
         if (mSync) {
             SurfaceSyncGroup syncGroup = new SurfaceSyncGroup(TAG);
-            syncGroup.addToSync(getWindow().getRootSurfaceControl(), svResizeRunnable);
-            syncGroup.addToSync(mSurfacePackage, resizeRunnable);
+            syncGroup.add(getWindow().getRootSurfaceControl(), svResizeRunnable);
+            syncGroup.add(mSurfacePackage, resizeRunnable);
             syncGroup.markSyncReady();
         } else {
             svResizeRunnable.run();
diff --git a/tests/SurfaceViewSyncTest/src/com/android/test/SurfaceViewSyncActivity.java b/tests/SurfaceViewSyncTest/src/com/android/test/SurfaceViewSyncActivity.java
index d5983d0..d1f2112 100644
--- a/tests/SurfaceViewSyncTest/src/com/android/test/SurfaceViewSyncActivity.java
+++ b/tests/SurfaceViewSyncTest/src/com/android/test/SurfaceViewSyncActivity.java
@@ -90,7 +90,7 @@
 
         if (mEnableSyncSwitch.isChecked()) {
             mSyncGroup = new SurfaceSyncGroup(TAG);
-            mSyncGroup.addToSync(container.getRootSurfaceControl());
+            mSyncGroup.add(container.getRootSurfaceControl(), null /* runnable */);
         }
 
         ViewGroup.LayoutParams svParams = mSurfaceView.getLayoutParams();
@@ -114,7 +114,7 @@
                 mRenderingThread.renderFrame(null, width, height);
                 return;
             }
-            mSyncGroup.addToSync(mSurfaceView, frameCallback ->
+            mSyncGroup.add(mSurfaceView, frameCallback ->
                     mRenderingThread.renderFrame(frameCallback, width, height));
             mSyncGroup.markSyncReady();
             mSyncGroup = null;