Merge "Add UserManagerInternal#getDisplaysAssignedToUser() method." into udc-dev
diff --git a/TEST_MAPPING b/TEST_MAPPING
index 9610f2c..81a3479 100644
--- a/TEST_MAPPING
+++ b/TEST_MAPPING
@@ -65,6 +65,15 @@
       ]
     },
     {
+      "name": "FrameworksInputMethodSystemServerTests",
+      "options": [
+        {"include-filter": "com.android.server.inputmethod"},
+        {"exclude-annotation": "android.platform.test.annotations.FlakyTest"},
+        {"exclude-annotation": "androidx.test.filters.FlakyTest"},
+        {"exclude-annotation": "org.junit.Ignore"}
+      ]
+    },
+    {
       "name": "ExtServicesUnitTests",
       "options": [
         {
diff --git a/config/preloaded-classes b/config/preloaded-classes
index 95070bd..09ec220 100644
--- a/config/preloaded-classes
+++ b/config/preloaded-classes
@@ -14799,7 +14799,6 @@
 java.util.ImmutableCollections$Set12
 java.util.ImmutableCollections$SetN
 java.util.ImmutableCollections$SubList
-java.util.ImmutableCollections
 java.util.InputMismatchException
 java.util.Iterator
 java.util.JumboEnumSet$EnumSetIterator
diff --git a/config/preloaded-classes-denylist b/config/preloaded-classes-denylist
index 502d8c6..a413bbd 100644
--- a/config/preloaded-classes-denylist
+++ b/config/preloaded-classes-denylist
@@ -9,4 +9,5 @@
 android.net.rtp.AudioStream
 android.net.rtp.RtpStream
 java.util.concurrent.ThreadLocalRandom
+java.util.ImmutableCollections
 com.android.internal.jank.InteractionJankMonitor$InstanceHolder
diff --git a/core/api/current.txt b/core/api/current.txt
index 6078712f..dd4f213 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -8332,7 +8332,7 @@
     field public static final int RESULT_FAILURE_STORAGE_LIMIT_REACHED = 3; // 0x3
     field public static final int RESULT_FAILURE_UNKNOWN = -1; // 0xffffffff
     field public static final int RESULT_POLICY_CLEARED = 2; // 0x2
-    field public static final int RESULT_SUCCESS = 0; // 0x0
+    field public static final int RESULT_POLICY_SET = 0; // 0x0
   }
 
   public final class PreferentialNetworkServiceConfig implements android.os.Parcelable {
@@ -9678,6 +9678,7 @@
     method @Nullable public String getAttributionTag();
     method @Nullable public android.content.AttributionSource getNext();
     method @Nullable public String getPackageName();
+    method public int getPid();
     method public int getUid();
     method public boolean isTrusted(@NonNull android.content.Context);
     method @NonNull public static android.content.AttributionSource myAttributionSource();
@@ -9692,6 +9693,7 @@
     method @NonNull public android.content.AttributionSource.Builder setAttributionTag(@Nullable String);
     method @NonNull public android.content.AttributionSource.Builder setNext(@Nullable android.content.AttributionSource);
     method @NonNull public android.content.AttributionSource.Builder setPackageName(@Nullable String);
+    method @NonNull public android.content.AttributionSource.Builder setPid(int);
   }
 
   public abstract class BroadcastReceiver {
@@ -12812,6 +12814,7 @@
     field public static final long MAXIMUM_VERIFICATION_TIMEOUT = 3600000L; // 0x36ee80L
     field public static final int PERMISSION_DENIED = -1; // 0xffffffff
     field public static final int PERMISSION_GRANTED = 0; // 0x0
+    field public static final String PROPERTY_COMPAT_OVERRIDE_LANDSCAPE_TO_PORTRAIT = "android.camera.PROPERTY_COMPAT_OVERRIDE_LANDSCAPE_TO_PORTRAIT";
     field public static final String PROPERTY_MEDIA_CAPABILITIES = "android.media.PROPERTY_MEDIA_CAPABILITIES";
     field public static final String PROPERTY_SELF_CERTIFIED_NETWORK_CAPABILITIES = "android.net.PROPERTY_SELF_CERTIFIED_NETWORK_CAPABILITIES";
     field public static final String PROPERTY_SPECIAL_USE_FGS_SUBTYPE = "android.app.PROPERTY_SPECIAL_USE_FGS_SUBTYPE";
@@ -27342,7 +27345,9 @@
     field public static final int TIME_SHIFT_STATUS_UNAVAILABLE = 2; // 0x2
     field public static final int TIME_SHIFT_STATUS_UNKNOWN = 0; // 0x0
     field public static final int TIME_SHIFT_STATUS_UNSUPPORTED = 1; // 0x1
+    field public static final String TV_MESSAGE_KEY_RAW_DATA = "android.media.tv.TvInputManager.raw_data";
     field public static final String TV_MESSAGE_KEY_STREAM_ID = "android.media.tv.TvInputManager.stream_id";
+    field public static final String TV_MESSAGE_KEY_SUBTYPE = "android.media.tv.TvInputManager.subtype";
     field public static final int TV_MESSAGE_TYPE_CLOSED_CAPTION = 2; // 0x2
     field public static final int TV_MESSAGE_TYPE_OTHER = 1000; // 0x3e8
     field public static final int TV_MESSAGE_TYPE_WATERMARK = 1; // 0x1
@@ -33609,6 +33614,7 @@
     method @Deprecated public static final boolean supportsProcesses();
     field public static final int BLUETOOTH_UID = 1002; // 0x3ea
     field public static final int FIRST_APPLICATION_UID = 10000; // 0x2710
+    field public static final int INVALID_PID = -1; // 0xffffffff
     field public static final int INVALID_UID = -1; // 0xffffffff
     field public static final int LAST_APPLICATION_UID = 19999; // 0x4e1f
     field public static final int PHONE_UID = 1001; // 0x3e9
@@ -41554,7 +41560,6 @@
 
   public abstract class RecognitionService extends android.app.Service {
     ctor public RecognitionService();
-    method public void clearModelDownloadListener(@NonNull android.content.Intent, @NonNull android.content.AttributionSource);
     method public int getMaxConcurrentSessionsCount();
     method public final android.os.IBinder onBind(android.content.Intent);
     method protected abstract void onCancel(android.speech.RecognitionService.Callback);
@@ -41564,7 +41569,7 @@
     method protected abstract void onStopListening(android.speech.RecognitionService.Callback);
     method public void onTriggerModelDownload(@NonNull android.content.Intent);
     method public void onTriggerModelDownload(@NonNull android.content.Intent, @NonNull android.content.AttributionSource);
-    method public void setModelDownloadListener(@NonNull android.content.Intent, @NonNull android.content.AttributionSource, @NonNull android.speech.ModelDownloadListener);
+    method public void onTriggerModelDownload(@NonNull android.content.Intent, @NonNull android.content.AttributionSource, @NonNull android.speech.ModelDownloadListener);
     field public static final String SERVICE_INTERFACE = "android.speech.RecognitionService";
     field public static final String SERVICE_META_DATA = "android.speech";
   }
@@ -41689,18 +41694,17 @@
   public class SpeechRecognizer {
     method @MainThread public void cancel();
     method public void checkRecognitionSupport(@NonNull android.content.Intent, @NonNull java.util.concurrent.Executor, @NonNull android.speech.RecognitionSupportCallback);
-    method public void clearModelDownloadListener(@NonNull android.content.Intent);
     method @MainThread @NonNull public static android.speech.SpeechRecognizer createOnDeviceSpeechRecognizer(@NonNull android.content.Context);
     method @MainThread public static android.speech.SpeechRecognizer createSpeechRecognizer(android.content.Context);
     method @MainThread public static android.speech.SpeechRecognizer createSpeechRecognizer(android.content.Context, android.content.ComponentName);
     method public void destroy();
     method public static boolean isOnDeviceRecognitionAvailable(@NonNull android.content.Context);
     method public static boolean isRecognitionAvailable(@NonNull android.content.Context);
-    method public void setModelDownloadListener(@NonNull android.content.Intent, @NonNull java.util.concurrent.Executor, @NonNull android.speech.ModelDownloadListener);
     method @MainThread public void setRecognitionListener(android.speech.RecognitionListener);
     method @MainThread public void startListening(android.content.Intent);
     method @MainThread public void stopListening();
     method public void triggerModelDownload(@NonNull android.content.Intent);
+    method public void triggerModelDownload(@NonNull android.content.Intent, @NonNull java.util.concurrent.Executor, @NonNull android.speech.ModelDownloadListener);
     field public static final String CONFIDENCE_SCORES = "confidence_scores";
     field public static final String DETECTED_LANGUAGE = "detected_language";
     field public static final int ERROR_AUDIO = 3; // 0x3
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 765a17f..d573e33 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -161,7 +161,7 @@
     field public static final String LOCAL_MAC_ADDRESS = "android.permission.LOCAL_MAC_ADDRESS";
     field public static final String LOCATION_BYPASS = "android.permission.LOCATION_BYPASS";
     field public static final String LOCK_DEVICE = "android.permission.LOCK_DEVICE";
-    field public static final String LOG_PROCESS_ACTIVITIES = "android.permission.LOG_PROCESS_ACTIVITIES";
+    field public static final String LOG_FOREGROUND_RESOURCE_USE = "android.permission.LOG_FOREGROUND_RESOURCE_USE";
     field public static final String LOOP_RADIO = "android.permission.LOOP_RADIO";
     field public static final String MANAGE_ACCESSIBILITY = "android.permission.MANAGE_ACCESSIBILITY";
     field @Deprecated public static final String MANAGE_ACTIVITY_STACKS = "android.permission.MANAGE_ACTIVITY_STACKS";
@@ -538,15 +538,15 @@
     method @RequiresPermission(android.Manifest.permission.PACKAGE_USAGE_STATS) public int getUidImportance(int);
     method @RequiresPermission(android.Manifest.permission.FORCE_STOP_PACKAGES) public void killProcessesWhenImperceptible(@NonNull int[], @NonNull String);
     method @RequiresPermission(android.Manifest.permission.KILL_UID) public void killUid(int, String);
-    method @RequiresPermission(android.Manifest.permission.LOG_PROCESS_ACTIVITIES) public void noteForegroundResourceUse(int, int, int, int);
+    method @RequiresPermission(android.Manifest.permission.LOG_FOREGROUND_RESOURCE_USE) public void noteForegroundResourceUseBegin(int, int, int) throws java.lang.SecurityException;
+    method @RequiresPermission(android.Manifest.permission.LOG_FOREGROUND_RESOURCE_USE) public void noteForegroundResourceUseEnd(int, int, int) throws java.lang.SecurityException;
     method @RequiresPermission(android.Manifest.permission.PACKAGE_USAGE_STATS) public void removeOnUidImportanceListener(android.app.ActivityManager.OnUidImportanceListener);
     method public void setDeviceLocales(@NonNull android.os.LocaleList);
     method @RequiresPermission(android.Manifest.permission.RESTRICTED_VR_ACCESS) public static void setPersistentVrThread(int);
     method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.INTERACT_ACROSS_USERS_FULL}) public boolean startProfile(@NonNull android.os.UserHandle);
     method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.INTERACT_ACROSS_USERS_FULL}) public boolean stopProfile(@NonNull android.os.UserHandle);
     method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public boolean switchUser(@NonNull android.os.UserHandle);
-    field public static final int FOREGROUND_SERVICE_API_EVENT_BEGIN = 1; // 0x1
-    field public static final int FOREGROUND_SERVICE_API_EVENT_END = 2; // 0x2
+    field public static final int FOREGROUND_SERVICE_API_TYPE_AUDIO = 5; // 0x5
     field public static final int FOREGROUND_SERVICE_API_TYPE_BLUETOOTH = 2; // 0x2
     field public static final int FOREGROUND_SERVICE_API_TYPE_CAMERA = 1; // 0x1
     field public static final int FOREGROUND_SERVICE_API_TYPE_CDM = 9; // 0x9
@@ -838,11 +838,9 @@
   public class BroadcastOptions {
     method public void clearRequireCompatChange();
     method public int getPendingIntentBackgroundActivityStartMode();
-    method @Deprecated public boolean isDeferUntilActive();
     method @Deprecated public boolean isPendingIntentBackgroundActivityLaunchAllowed();
     method @RequiresPermission(android.Manifest.permission.ACCESS_BROADCAST_RESPONSE_STATS) public void recordResponseEventWhileInBackground(@IntRange(from=0) long);
     method @RequiresPermission(android.Manifest.permission.START_ACTIVITIES_FROM_BACKGROUND) public void setBackgroundActivityStartsAllowed(boolean);
-    method @Deprecated @NonNull public android.app.BroadcastOptions setDeferUntilActive(boolean);
     method public void setDontSendToRestrictedApps(boolean);
     method @Deprecated public void setPendingIntentBackgroundActivityLaunchAllowed(boolean);
     method @NonNull public android.app.BroadcastOptions setPendingIntentBackgroundActivityStartMode(int);
@@ -1799,25 +1797,19 @@
     field public final long bytesTransferred;
   }
 
-  public class BackupRestoreEventLogger {
-    method public void logBackupMetaData(@android.app.backup.BackupRestoreEventLogger.BackupRestoreDataType @NonNull String, @NonNull String);
-    method public void logItemsBackedUp(@android.app.backup.BackupRestoreEventLogger.BackupRestoreDataType @NonNull String, int);
-    method public void logItemsBackupFailed(@android.app.backup.BackupRestoreEventLogger.BackupRestoreDataType @NonNull String, int, @android.app.backup.BackupRestoreEventLogger.BackupRestoreError @Nullable String);
-    method public void logItemsRestoreFailed(@android.app.backup.BackupRestoreEventLogger.BackupRestoreDataType @NonNull String, int, @android.app.backup.BackupRestoreEventLogger.BackupRestoreError @Nullable String);
-    method public void logItemsRestored(@android.app.backup.BackupRestoreEventLogger.BackupRestoreDataType @NonNull String, int);
-    method public void logRestoreMetadata(@android.app.backup.BackupRestoreEventLogger.BackupRestoreDataType @NonNull String, @NonNull String);
-  }
-
-  @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface BackupRestoreEventLogger.BackupRestoreDataType {
-  }
-
-  @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface BackupRestoreEventLogger.BackupRestoreError {
+  public final class BackupRestoreEventLogger {
+    method public void logBackupMetadata(@NonNull String, @NonNull String);
+    method public void logItemsBackedUp(@NonNull String, int);
+    method public void logItemsBackupFailed(@NonNull String, int, @Nullable String);
+    method public void logItemsRestoreFailed(@NonNull String, int, @Nullable String);
+    method public void logItemsRestored(@NonNull String, int);
+    method public void logRestoreMetadata(@NonNull String, @NonNull String);
   }
 
   public static final class BackupRestoreEventLogger.DataTypeResult implements android.os.Parcelable {
     ctor public BackupRestoreEventLogger.DataTypeResult(@NonNull String);
     method public int describeContents();
-    method @android.app.backup.BackupRestoreEventLogger.BackupRestoreDataType @NonNull public String getDataType();
+    method @NonNull public String getDataType();
     method @NonNull public java.util.Map<java.lang.String,java.lang.Integer> getErrors();
     method public int getFailCount();
     method @Nullable public byte[] getMetadataHash();
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index 574ed6f..445b957 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -2027,7 +2027,9 @@
     method public static boolean is64BitAbi(String);
     method public static boolean isDebuggable();
     field @Nullable public static final String BRAND_FOR_ATTESTATION;
+    field @Nullable public static final String DEVICE_FOR_ATTESTATION;
     field public static final boolean IS_EMULATOR;
+    field @Nullable public static final String MANUFACTURER_FOR_ATTESTATION;
     field @Nullable public static final String MODEL_FOR_ATTESTATION;
     field @Nullable public static final String PRODUCT_FOR_ATTESTATION;
   }
@@ -3986,6 +3988,7 @@
 
   public static class WindowInfosListenerForTest.WindowInfo {
     field @NonNull public final android.graphics.Rect bounds;
+    field public final boolean isTrustedOverlay;
     field @NonNull public final String name;
     field @NonNull public final android.os.IBinder windowToken;
   }
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index b4068db..ff75098 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -666,6 +666,7 @@
      * Used to log FGS API events from Audio API
      * @hide
      */
+    @SystemApi
     public static final int FOREGROUND_SERVICE_API_TYPE_AUDIO = 5;
     /**
      * Used to log FGS API events from microphone API
@@ -715,13 +716,11 @@
      * Used to log a start event for an FGS API
      * @hide
      */
-    @SystemApi
     public static final int FOREGROUND_SERVICE_API_EVENT_BEGIN = 1;
     /**
      * Used to log a stop event for an FGS API
      * @hide
      */
-    @SystemApi
     public static final int FOREGROUND_SERVICE_API_EVENT_END = 2;
     /**
      * Constants used to denote API state
@@ -5617,23 +5616,32 @@
      *
      */
     @SystemApi
-    @RequiresPermission(android.Manifest.permission.LOG_PROCESS_ACTIVITIES)
-    public void noteForegroundResourceUse(@ForegroundServiceApiType int apiType,
-            @ForegroundServiceApiEvent int apiEvent, int uid, int pid) {
+    @RequiresPermission(android.Manifest.permission.LOG_FOREGROUND_RESOURCE_USE)
+    public void noteForegroundResourceUseBegin(@ForegroundServiceApiType int apiType,
+            int uid, int pid) throws SecurityException {
         try {
-            switch (apiEvent) {
-                case FOREGROUND_SERVICE_API_EVENT_BEGIN:
-                    getService().logFgsApiBegin(apiType, uid, pid);
-                    break;
-                case FOREGROUND_SERVICE_API_EVENT_END:
-                    getService().logFgsApiEnd(apiType, uid, pid);
-                    break;
-                default:
-                    throw new IllegalArgumentException("Argument of "
-                            + apiType + " not supported for foreground resource use logging");
-            }
+            getService().logFgsApiBegin(apiType, uid, pid);
         } catch (RemoteException e) {
-            e.rethrowFromSystemServer();
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Internal method for logging API end. Used with
+     * FGS metrics logging. Is called by APIs that are
+     * used with FGS to log an API event (eg when
+     * the camera starts).
+     * @hide
+     *
+     */
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.LOG_FOREGROUND_RESOURCE_USE)
+    public void noteForegroundResourceUseEnd(@ForegroundServiceApiType int apiType,
+            int uid, int pid) throws SecurityException {
+        try {
+            getService().logFgsApiEnd(apiType, uid, pid);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
         }
     }
 
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index a3ada76..28d4433 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -849,8 +849,10 @@
         @UnsupportedAppUsage
         Intent intent;
         boolean rebind;
+        long bindSeq;
         public String toString() {
-            return "BindServiceData{token=" + token + " intent=" + intent + "}";
+            return "BindServiceData{token=" + token + " intent=" + intent
+                    + " bindSeq=" + bindSeq + "}";
         }
     }
 
@@ -1107,12 +1109,13 @@
         }
 
         public final void scheduleBindService(IBinder token, Intent intent,
-                boolean rebind, int processState) {
+                boolean rebind, int processState, long bindSeq) {
             updateProcessState(processState, false);
             BindServiceData s = new BindServiceData();
             s.token = token;
             s.intent = intent;
             s.rebind = rebind;
+            s.bindSeq = bindSeq;
 
             if (DEBUG_SERVICE)
                 Slog.v(TAG, "scheduleBindService token=" + token + " intent=" + intent + " uid="
@@ -1124,6 +1127,7 @@
             BindServiceData s = new BindServiceData();
             s.token = token;
             s.intent = intent;
+            s.bindSeq = -1;
 
             sendMessage(H.UNBIND_SERVICE, s);
         }
@@ -3557,8 +3561,13 @@
             if (mLastProcessState == processState) {
                 return;
             }
+            // Do not issue a transitional GC if we are transitioning between 2 cached states.
+            // Only update if the state flips between cached and uncached or vice versa
+            if (ActivityManager.isProcStateCached(mLastProcessState)
+                    != ActivityManager.isProcStateCached(processState)) {
+                updateVmProcessState(processState);
+            }
             mLastProcessState = processState;
-            updateVmProcessState(processState);
             if (localLOGV) {
                 Slog.i(TAG, "******************* PROCESS STATE CHANGED TO: " + processState
                         + (fromIpc ? " (from ipc" : ""));
@@ -3567,12 +3576,16 @@
     }
 
     /** Update VM state based on ActivityManager.PROCESS_STATE_* constants. */
+    // Currently ART VM only uses state updates for Transitional GC, and thus
+    // this function initiates a Transitional GC for transitions into Cached apps states.
     private void updateVmProcessState(int processState) {
-        // TODO: Tune this since things like gmail sync are important background but not jank
-        // perceptible.
-        final int state = processState <= ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND
-                ? VM_PROCESS_STATE_JANK_PERCEPTIBLE
-                : VM_PROCESS_STATE_JANK_IMPERCEPTIBLE;
+        // Only a transition into Cached state should result in a Transitional GC request
+        // to the ART runtime. Update VM state to JANK_IMPERCEPTIBLE in that case.
+        // Note that there are 4 possible cached states currently, all of which are
+        // JANK_IMPERCEPTIBLE from GC point of view.
+        final int state = ActivityManager.isProcStateCached(processState)
+                ? VM_PROCESS_STATE_JANK_IMPERCEPTIBLE
+                : VM_PROCESS_STATE_JANK_PERCEPTIBLE;
         VMRuntime.getRuntime().updateProcessState(state);
     }
 
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index 7c32c9c..265b564 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -2217,8 +2217,7 @@
     /** Whether noting for an appop should be collected */
     private static final @ShouldCollectNoteOp byte[] sAppOpsToNote = new byte[_NUM_OP];
 
-    private static final int[] RUNTIME_AND_APPOP_PERMISSIONS_OPS = {
-            // RUNTIME PERMISSIONS
+    private static final int[] RUNTIME_PERMISSION_OPS = {
             // Contacts
             OP_READ_CONTACTS,
             OP_WRITE_CONTACTS,
@@ -2275,8 +2274,13 @@
             OP_NEARBY_WIFI_DEVICES,
             // Notifications
             OP_POST_NOTIFICATION,
+    };
 
-            // APPOP PERMISSIONS
+    /**
+     * Ops for app op permissions that are setting the per-package mode for certain reasons. Most
+     * app op permissions should set the per-UID mode instead.
+     */
+    private static final int[] APP_OP_PERMISSION_PACKAGE_OPS = {
             OP_ACCESS_NOTIFICATIONS,
             OP_SYSTEM_ALERT_WINDOW,
             OP_WRITE_SETTINGS,
@@ -2285,9 +2289,16 @@
             OP_SMS_FINANCIAL_TRANSACTIONS,
             OP_MANAGE_IPSEC_TUNNELS,
             OP_INSTANT_APP_START_FOREGROUND,
+            OP_LOADER_USAGE_STATS
+    };
+
+    /**
+     * Ops for app op permissions that are setting the per-UID mode for certain reasons. This should
+     * be preferred over the per-package mode for new app op permissions.
+     */
+    private static final int[] APP_OP_PERMISSION_UID_OPS = {
             OP_MANAGE_EXTERNAL_STORAGE,
             OP_INTERACT_ACROSS_PROFILES,
-            OP_LOADER_USAGE_STATS,
             OP_MANAGE_ONGOING_CALLS,
             OP_USE_ICC_AUTH_WITH_DEVICE_IDENTIFIER,
             OP_SCHEDULE_EXACT_ALARM,
@@ -2777,7 +2788,17 @@
                 sOpStrToOp.put(sAppOpInfos[i].name, i);
             }
         }
-        for (int op : RUNTIME_AND_APPOP_PERMISSIONS_OPS) {
+        for (int op : RUNTIME_PERMISSION_OPS) {
+            if (sAppOpInfos[op].permission != null) {
+                sPermToOp.put(sAppOpInfos[op].permission, op);
+            }
+        }
+        for (int op : APP_OP_PERMISSION_PACKAGE_OPS) {
+            if (sAppOpInfos[op].permission != null) {
+                sPermToOp.put(sAppOpInfos[op].permission, op);
+            }
+        }
+        for (int op : APP_OP_PERMISSION_UID_OPS) {
             if (sAppOpInfos[op].permission != null) {
                 sPermToOp.put(sAppOpInfos[op].permission, op);
             }
@@ -2947,6 +2968,22 @@
     }
 
     /**
+     * Retrieve whether the op is a per-package op for an app op permission.
+     * @hide
+     */
+    public static boolean opIsPackageAppOpPermission(int op) {
+        return ArrayUtils.contains(APP_OP_PERMISSION_PACKAGE_OPS, op);
+    }
+
+    /**
+     * Retrieve whether the op is a per-package op for an app op permission.
+     * @hide
+     */
+    public static boolean opIsUidAppOpPermission(int op) {
+        return ArrayUtils.contains(APP_OP_PERMISSION_UID_OPS, op);
+    }
+
+    /**
      * Returns a listenerId suitable for use with {@link #noteOp(int, int, String, String, String)}.
      *
      * This is intended for use client side, when the receiver id must be created before the
@@ -8371,9 +8408,9 @@
     public int noteProxyOp(int op, @Nullable String proxiedPackageName, int proxiedUid,
             @Nullable String proxiedAttributionTag, @Nullable String message) {
         return noteProxyOp(op, new AttributionSource(mContext.getAttributionSource(),
-                new AttributionSource(proxiedUid, proxiedPackageName, proxiedAttributionTag,
-                        mContext.getAttributionSource().getToken())), message,
-                        /*skipProxyOperation*/ false);
+                new AttributionSource(proxiedUid, Process.INVALID_PID, proxiedPackageName,
+                        proxiedAttributionTag, mContext.getAttributionSource().getToken())),
+                        message, /*skipProxyOperation*/ false);
     }
 
     /**
@@ -8458,8 +8495,9 @@
             int proxiedUid, @Nullable String proxiedAttributionTag, @Nullable String message) {
         return noteProxyOpNoThrow(strOpToOp(op), new AttributionSource(
                 mContext.getAttributionSource(), new AttributionSource(proxiedUid,
-                        proxiedPackageName, proxiedAttributionTag, mContext.getAttributionSource()
-                        .getToken())), message,/*skipProxyOperation*/ false);
+                        Process.INVALID_PID, proxiedPackageName, proxiedAttributionTag,
+                        mContext.getAttributionSource().getToken())), message,
+                        /*skipProxyOperation*/ false);
     }
 
     /**
@@ -8869,9 +8907,9 @@
     public int startProxyOp(@NonNull String op, int proxiedUid, @NonNull String proxiedPackageName,
             @Nullable String proxiedAttributionTag, @Nullable String message) {
         return startProxyOp(op, new AttributionSource(mContext.getAttributionSource(),
-                new AttributionSource(proxiedUid, proxiedPackageName, proxiedAttributionTag,
-                        mContext.getAttributionSource().getToken())), message,
-                        /*skipProxyOperation*/ false);
+                new AttributionSource(proxiedUid, Process.INVALID_PID, proxiedPackageName,
+                        proxiedAttributionTag, mContext.getAttributionSource().getToken())),
+                        message, /*skipProxyOperation*/ false);
     }
 
     /**
@@ -8917,7 +8955,7 @@
             @Nullable String message) {
         return startProxyOpNoThrow(AppOpsManager.strOpToOp(op), new AttributionSource(
                 mContext.getAttributionSource(), new AttributionSource(proxiedUid,
-                        proxiedPackageName, proxiedAttributionTag,
+                        Process.INVALID_PID, proxiedPackageName, proxiedAttributionTag,
                         mContext.getAttributionSource().getToken())), message,
                         /*skipProxyOperation*/ false);
     }
@@ -9066,8 +9104,8 @@
             @NonNull String proxiedPackageName, @Nullable String proxiedAttributionTag) {
         IBinder token = mContext.getAttributionSource().getToken();
         finishProxyOp(token, op, new AttributionSource(mContext.getAttributionSource(),
-                new AttributionSource(proxiedUid, proxiedPackageName,  proxiedAttributionTag,
-                        token)), /*skipProxyOperation*/ false);
+                new AttributionSource(proxiedUid, Process.INVALID_PID, proxiedPackageName,
+                        proxiedAttributionTag, token)), /*skipProxyOperation*/ false);
     }
 
     /**
diff --git a/core/java/android/app/BroadcastOptions.java b/core/java/android/app/BroadcastOptions.java
index 481f671..a5095b2 100644
--- a/core/java/android/app/BroadcastOptions.java
+++ b/core/java/android/app/BroadcastOptions.java
@@ -769,9 +769,7 @@
     }
 
     /** {@hide} */
-    @SystemApi
     @Deprecated
-    // STOPSHIP: remove entirely after this API change lands in AOSP
     public @NonNull BroadcastOptions setDeferUntilActive(boolean shouldDefer) {
         if (shouldDefer) {
             setDeferralPolicy(DEFERRAL_POLICY_UNTIL_ACTIVE);
@@ -782,9 +780,7 @@
     }
 
     /** {@hide} */
-    @SystemApi
     @Deprecated
-    // STOPSHIP: remove entirely after this API change lands in AOSP
     public boolean isDeferUntilActive() {
         return (mDeferralPolicy == DEFERRAL_POLICY_UNTIL_ACTIVE);
     }
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java
index 4713a31..0cd42a3 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -1299,7 +1299,7 @@
 
     @Override
     public void sendBroadcastMultiplePermissions(Intent intent, String[] receiverPermissions,
-            String[] excludedPermissions, String[] excludedPackages) {
+            String[] excludedPermissions, String[] excludedPackages, BroadcastOptions options) {
         warnIfCallingFromSystemProcess();
         String resolvedType = intent.resolveTypeIfNeeded(getContentResolver());
         try {
@@ -1307,7 +1307,8 @@
             ActivityManager.getService().broadcastIntentWithFeature(
                     mMainThread.getApplicationThread(), getAttributionTag(), intent, resolvedType,
                     null, Activity.RESULT_OK, null, null, receiverPermissions, excludedPermissions,
-                    excludedPackages, AppOpsManager.OP_NONE, null, false, false, getUserId());
+                    excludedPackages, AppOpsManager.OP_NONE,
+                    options == null ? null : options.toBundle(), false, false, getUserId());
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -3460,7 +3461,9 @@
             @Nullable AttributionSource nextAttributionSource,
             @Nullable Set<String> renouncedPermissions) {
         AttributionSource attributionSource = new AttributionSource(Process.myUid(),
-                mOpPackageName, attributionTag, renouncedPermissions, nextAttributionSource);
+                Process.myPid(), mOpPackageName, attributionTag,
+                (renouncedPermissions != null) ? renouncedPermissions.toArray(new String[0]) : null,
+                nextAttributionSource);
         // If we want to access protected data on behalf of another app we need to
         // tell the OS that we opt in to participate in the attribution chain.
         if (nextAttributionSource != null) {
diff --git a/core/java/android/app/IActivityManager.aidl b/core/java/android/app/IActivityManager.aidl
index 5c38c42..62298a5 100644
--- a/core/java/android/app/IActivityManager.aidl
+++ b/core/java/android/app/IActivityManager.aidl
@@ -870,11 +870,11 @@
     boolean shouldServiceTimeOut(in ComponentName className, in IBinder token);
 
     /** Logs start of an API call to associate with an FGS, used for FGS Type Metrics */
-    oneway void logFgsApiBegin(int apiType, int appUid, int appPid);
+    void logFgsApiBegin(int apiType, int appUid, int appPid);
 
     /** Logs stop of an API call to associate with an FGS, used for FGS Type Metrics */
-    oneway void logFgsApiEnd(int apiType, int appUid, int appPid);
+    void logFgsApiEnd(int apiType, int appUid, int appPid);
 
     /** Logs API state change to associate with an FGS, used for FGS Type Metrics */
-    oneway void logFgsApiStateChanged(int apiType, int state, int appUid, int appPid);
+    void logFgsApiStateChanged(int apiType, int state, int appUid, int appPid);
 }
diff --git a/core/java/android/app/IApplicationThread.aidl b/core/java/android/app/IApplicationThread.aidl
index 4f77203..6b5f6b0 100644
--- a/core/java/android/app/IApplicationThread.aidl
+++ b/core/java/android/app/IApplicationThread.aidl
@@ -95,7 +95,7 @@
     void processInBackground();
     @UnsupportedAppUsage
     void scheduleBindService(IBinder token,
-            in Intent intent, boolean rebind, int processState);
+            in Intent intent, boolean rebind, int processState, long bindSeq);
     @UnsupportedAppUsage
     void scheduleUnbindService(IBinder token,
             in Intent intent);
diff --git a/core/java/android/app/WindowConfiguration.java b/core/java/android/app/WindowConfiguration.java
index 39d77c4..5b95503 100644
--- a/core/java/android/app/WindowConfiguration.java
+++ b/core/java/android/app/WindowConfiguration.java
@@ -875,15 +875,6 @@
     }
 
     /**
-     * Returns true if any visible windows belonging to apps with this window configuration should
-     * be kept on screen when the app is killed due to something like the low memory killer.
-     * @hide
-     */
-    public boolean keepVisibleDeadAppWindowOnScreen() {
-        return mWindowingMode != WINDOWING_MODE_PINNED;
-    }
-
-    /**
      * Returns true if the backdrop on the client side should match the frame of the window.
      * Returns false, if the backdrop should be fullscreen.
      * @hide
diff --git a/core/java/android/app/admin/PolicyUpdateReceiver.java b/core/java/android/app/admin/PolicyUpdateReceiver.java
index b5d9286..be13988 100644
--- a/core/java/android/app/admin/PolicyUpdateReceiver.java
+++ b/core/java/android/app/admin/PolicyUpdateReceiver.java
@@ -238,7 +238,7 @@
      *                               needed.
      * @param targetUser The {@link TargetUser} which this policy relates to.
      * @param policyUpdateResult Indicates whether the policy has been set successfully
-     *                           ({@link PolicyUpdateResult#RESULT_SUCCESS}) or the reason it
+     *                           ({@link PolicyUpdateResult#RESULT_POLICY_SET}) or the reason it
      *                           failed to apply (e.g.
      *                           {@link PolicyUpdateResult#RESULT_FAILURE_CONFLICTING_ADMIN_POLICY},
      *                           etc).
@@ -271,8 +271,8 @@
      *                               needed.
      * @param targetUser The {@link TargetUser} which this policy relates to.
      * @param policyUpdateResult Indicates the reason the policy value has changed
-     *                           (e.g. {@link PolicyUpdateResult#RESULT_SUCCESS} if the policy has
-     *                           changed to the value set by the admin,
+     *                           (e.g. {@link PolicyUpdateResult#RESULT_POLICY_SET} if the policy
+     *                           has changed to the value set by the admin,
      *                           {@link PolicyUpdateResult#RESULT_FAILURE_CONFLICTING_ADMIN_POLICY}
      *                           if the policy has changed because another admin has set a
      *                           conflicting policy, etc)
diff --git a/core/java/android/app/admin/PolicyUpdateResult.java b/core/java/android/app/admin/PolicyUpdateResult.java
index 7d7d91a..414c6ed 100644
--- a/core/java/android/app/admin/PolicyUpdateResult.java
+++ b/core/java/android/app/admin/PolicyUpdateResult.java
@@ -39,7 +39,7 @@
      * Result code to indicate that the policy has been changed to the desired value set by
      * the admin.
      */
-    public static final int RESULT_SUCCESS = 0;
+    public static final int RESULT_POLICY_SET = 0;
 
     /**
      * Result code to indicate that the policy has not been enforced or has changed because another
@@ -82,7 +82,7 @@
     @Retention(RetentionPolicy.SOURCE)
     @IntDef(prefix = { "RESULT_" }, value = {
             RESULT_FAILURE_UNKNOWN,
-            RESULT_SUCCESS,
+            RESULT_POLICY_SET,
             RESULT_FAILURE_CONFLICTING_ADMIN_POLICY,
             RESULT_POLICY_CLEARED,
             RESULT_FAILURE_STORAGE_LIMIT_REACHED,
diff --git a/core/java/android/app/backup/BackupRestoreEventLogger.java b/core/java/android/app/backup/BackupRestoreEventLogger.java
index c4ff892..ea31ef3 100644
--- a/core/java/android/app/backup/BackupRestoreEventLogger.java
+++ b/core/java/android/app/backup/BackupRestoreEventLogger.java
@@ -47,7 +47,7 @@
  * @hide
  */
 @SystemApi
-public class BackupRestoreEventLogger {
+public final class BackupRestoreEventLogger {
     private static final String TAG = "BackupRestoreEventLogger";
 
     /**
@@ -61,6 +61,8 @@
     /**
      * Denotes that the annotated element identifies a data type as required by the logging methods
      * of {@code BackupRestoreEventLogger}
+     *
+     * @hide
      */
     @Retention(RetentionPolicy.SOURCE)
     public @interface BackupRestoreDataType {}
@@ -68,6 +70,8 @@
     /**
      * Denotes that the annotated element identifies an error type as required by the logging
      * methods of {@code BackupRestoreEventLogger}
+     *
+     * @hide
      */
     @Retention(RetentionPolicy.SOURCE)
     public @interface BackupRestoreError {}
@@ -144,7 +148,7 @@
      * @param dataType the type of data being backed up.
      * @param metaData the metadata associated with the data type.
      */
-    public void logBackupMetaData(@NonNull @BackupRestoreDataType String dataType,
+    public void logBackupMetadata(@NonNull @BackupRestoreDataType String dataType,
             @NonNull String metaData) {
         logMetaData(OperationType.BACKUP, dataType, metaData);
     }
diff --git a/core/java/android/content/AttributionSource.java b/core/java/android/content/AttributionSource.java
index 2b400c1f..cd45f4d 100644
--- a/core/java/android/content/AttributionSource.java
+++ b/core/java/android/content/AttributionSource.java
@@ -101,22 +101,28 @@
     @TestApi
     public AttributionSource(int uid, @Nullable String packageName,
             @Nullable String attributionTag) {
-        this(uid, packageName, attributionTag, sDefaultToken);
+        this(uid, Process.INVALID_PID, packageName, attributionTag, sDefaultToken);
+    }
+
+    /** @hide */
+    public AttributionSource(int uid, int pid, @Nullable String packageName,
+            @Nullable String attributionTag) {
+        this(uid, pid, packageName, attributionTag, sDefaultToken);
     }
 
     /** @hide */
     @TestApi
     public AttributionSource(int uid, @Nullable String packageName,
             @Nullable String attributionTag, @NonNull IBinder token) {
-        this(uid, packageName, attributionTag, token, /*renouncedPermissions*/ null,
-                /*next*/ null);
+        this(uid, Process.INVALID_PID, packageName, attributionTag, token,
+                /*renouncedPermissions*/ null, /*next*/ null);
     }
 
     /** @hide */
-    public AttributionSource(int uid, @Nullable String packageName,
-            @Nullable String attributionTag, @NonNull IBinder token,
-            @Nullable AttributionSource next) {
-        this(uid, packageName, attributionTag, token, /*renouncedPermissions*/ null, next);
+    public AttributionSource(int uid, int pid, @Nullable String packageName,
+            @Nullable String attributionTag, @NonNull IBinder token) {
+        this(uid, pid, packageName, attributionTag, token, /*renouncedPermissions*/ null,
+                /*next*/ null);
     }
 
     /** @hide */
@@ -124,26 +130,33 @@
     public AttributionSource(int uid, @Nullable String packageName,
             @Nullable String attributionTag, @Nullable Set<String> renouncedPermissions,
             @Nullable AttributionSource next) {
-        this(uid, packageName, attributionTag, (renouncedPermissions != null)
-                ? renouncedPermissions.toArray(new String[0]) : null, next);
+        this(uid, Process.INVALID_PID, packageName, attributionTag, sDefaultToken,
+                (renouncedPermissions != null)
+                ? renouncedPermissions.toArray(new String[0]) : null, /*next*/ next);
     }
 
     /** @hide */
     public AttributionSource(@NonNull AttributionSource current, @Nullable AttributionSource next) {
-        this(current.getUid(), current.getPackageName(), current.getAttributionTag(),
-                current.getToken(), current.mAttributionSourceState.renouncedPermissions, next);
+        this(current.getUid(), current.getPid(), current.getPackageName(),
+                current.getAttributionTag(), current.getToken(),
+                current.mAttributionSourceState.renouncedPermissions, next);
     }
 
-    AttributionSource(int uid, @Nullable String packageName, @Nullable String attributionTag,
-            @Nullable String[] renouncedPermissions, @Nullable AttributionSource next) {
-        this(uid, packageName, attributionTag, sDefaultToken, renouncedPermissions, next);
+    /** @hide */
+    public AttributionSource(int uid, int pid, @Nullable String packageName,
+            @Nullable String attributionTag, @Nullable String[] renouncedPermissions,
+            @Nullable AttributionSource next) {
+        this(uid, pid, packageName, attributionTag, sDefaultToken, renouncedPermissions, next);
     }
 
-    AttributionSource(int uid, @Nullable String packageName, @Nullable String attributionTag,
-            @NonNull IBinder token, @Nullable String[] renouncedPermissions,
+    /** @hide */
+    public AttributionSource(int uid, int pid, @Nullable String packageName,
+            @Nullable String attributionTag, @NonNull IBinder token,
+            @Nullable String[] renouncedPermissions,
             @Nullable AttributionSource next) {
         mAttributionSourceState = new AttributionSourceState();
         mAttributionSourceState.uid = uid;
+        mAttributionSourceState.pid = pid;
         mAttributionSourceState.token = token;
         mAttributionSourceState.packageName = packageName;
         mAttributionSourceState.attributionTag = attributionTag;
@@ -162,7 +175,17 @@
 
         // Since we just unpacked this object as part of it transiting a Binder
         // call, this is the perfect time to enforce that its UID and PID can be trusted
-        enforceCallingUidAndPid();
+        enforceCallingUid();
+
+        // If this object is being constructed as part of a oneway Binder call, getCallingPid will
+        // return 0 instead of the true PID. In that case, invalidate the PID by setting it to
+        // INVALID_PID (-1).
+        final int callingPid = Binder.getCallingPid();
+        if (callingPid == 0) {
+            mAttributionSourceState.pid = Process.INVALID_PID;
+        }
+
+        enforceCallingPid();
     }
 
     /** @hide */
@@ -172,23 +195,29 @@
 
     /** @hide */
     public AttributionSource withNextAttributionSource(@Nullable AttributionSource next) {
-        return new AttributionSource(getUid(), getPackageName(), getAttributionTag(),
-                mAttributionSourceState.renouncedPermissions, next);
+        return new AttributionSource(getUid(), getPid(), getPackageName(), getAttributionTag(),
+                getToken(), mAttributionSourceState.renouncedPermissions, next);
     }
 
     /** @hide */
     public AttributionSource withPackageName(@Nullable String packageName) {
-        return new AttributionSource(getUid(), packageName, getAttributionTag(),
-                mAttributionSourceState.renouncedPermissions, getNext());
+        return new AttributionSource(getUid(), getPid(), packageName, getAttributionTag(),
+               getToken(), mAttributionSourceState.renouncedPermissions, getNext());
     }
 
     /** @hide */
     public AttributionSource withToken(@NonNull Binder token) {
-        return new AttributionSource(getUid(), getPackageName(), getAttributionTag(),
+        return new AttributionSource(getUid(), getPid(), getPackageName(), getAttributionTag(),
                 token, mAttributionSourceState.renouncedPermissions, getNext());
     }
 
     /** @hide */
+    public AttributionSource withPid(int pid) {
+        return new AttributionSource(getUid(), pid, getPackageName(), getAttributionTag(),
+                getToken(), mAttributionSourceState.renouncedPermissions, getNext());
+    }
+
+    /** @hide */
     public @NonNull AttributionSourceState asState() {
         return mAttributionSourceState;
     }
@@ -228,6 +257,7 @@
         }
         try {
             return new AttributionSource.Builder(uid)
+                .setPid(Process.myPid())
                 .setPackageName(AppGlobals.getPackageManager().getPackagesForUid(uid)[0])
                 .build();
         } catch (Exception ignored) {
@@ -265,18 +295,6 @@
     }
 
     /**
-     * If you are handling an IPC and you don't trust the caller you need to validate whether the
-     * attribution source is one for the calling app to prevent the caller to pass you a source from
-     * another app without including themselves in the attribution chain.
-     *
-     * @throws SecurityException if the attribution source cannot be trusted to be from the caller.
-     */
-    private void enforceCallingUidAndPid() {
-        enforceCallingUid();
-        enforceCallingPid();
-    }
-
-    /**
      * If you are handling an IPC and you don't trust the caller you need to validate
      * whether the attribution source is one for the calling app to prevent the caller
      * to pass you a source from another app without including themselves in the
@@ -312,7 +330,10 @@
     }
 
     /**
-     * Validate that the pid being claimed for the calling app is not spoofed
+     * Validate that the pid being claimed for the calling app is not spoofed.
+     *
+     * Note that the PID may be unavailable, for example if we're in a oneway Binder call. In this
+     * case, calling enforceCallingPid is guaranteed to fail. The caller should anticipate this.
      *
      * @throws SecurityException if the attribution source cannot be trusted to be from the caller.
      * @hide
@@ -320,8 +341,12 @@
     @TestApi
     public void enforceCallingPid() {
         if (!checkCallingPid()) {
-            throw new SecurityException("Calling pid: " + Binder.getCallingPid()
-                    + " doesn't match source pid: " + mAttributionSourceState.pid);
+            if (Binder.getCallingPid() == 0) {
+                throw new SecurityException("Calling pid unavailable due to oneway Binder call.");
+            } else {
+                throw new SecurityException("Calling pid: " + Binder.getCallingPid()
+                        + " doesn't match source pid: " + mAttributionSourceState.pid);
+            }
         }
     }
 
@@ -332,7 +357,8 @@
      */
     private boolean checkCallingPid() {
         final int callingPid = Binder.getCallingPid();
-        if (mAttributionSourceState.pid != -1 && callingPid != mAttributionSourceState.pid) {
+        if (mAttributionSourceState.pid != Process.INVALID_PID
+                && callingPid != mAttributionSourceState.pid) {
             return false;
         }
         return true;
@@ -449,6 +475,13 @@
     }
 
     /**
+     * The PID that is accessing the permission protected data.
+     */
+    public int getPid() {
+        return mAttributionSourceState.pid;
+    }
+
+    /**
      * The package that is accessing the permission protected data.
      */
     public @Nullable String getPackageName() {
@@ -551,6 +584,7 @@
                 throw new IllegalArgumentException("current AttributionSource can not be null");
             }
             mAttributionSourceState.uid = current.getUid();
+            mAttributionSourceState.pid = current.getPid();
             mAttributionSourceState.packageName = current.getPackageName();
             mAttributionSourceState.attributionTag = current.getAttributionTag();
             mAttributionSourceState.token = current.getToken();
@@ -559,11 +593,25 @@
         }
 
         /**
+         * The PID of the process that is accessing the permission protected data.
+         *
+         * If not called, pid will default to {@link Process@INVALID_PID} (-1). This indicates that
+         * the PID data is missing. Supplying a PID is not required, but recommended when
+         * accessible.
+         */
+        public @NonNull Builder setPid(int value) {
+            checkNotUsed();
+            mBuilderFieldsSet |= 0x2;
+            mAttributionSourceState.pid = value;
+            return this;
+        }
+
+        /**
          * The package that is accessing the permission protected data.
          */
         public @NonNull Builder setPackageName(@Nullable String value) {
             checkNotUsed();
-            mBuilderFieldsSet |= 0x2;
+            mBuilderFieldsSet |= 0x4;
             mAttributionSourceState.packageName = value;
             return this;
         }
@@ -573,7 +621,7 @@
          */
         public @NonNull Builder setAttributionTag(@Nullable String value) {
             checkNotUsed();
-            mBuilderFieldsSet |= 0x4;
+            mBuilderFieldsSet |= 0x8;
             mAttributionSourceState.attributionTag = value;
             return this;
         }
@@ -606,7 +654,7 @@
         @RequiresPermission(android.Manifest.permission.RENOUNCE_PERMISSIONS)
         public @NonNull Builder setRenouncedPermissions(@Nullable Set<String> value) {
             checkNotUsed();
-            mBuilderFieldsSet |= 0x8;
+            mBuilderFieldsSet |= 0x10;
             mAttributionSourceState.renouncedPermissions = (value != null)
                     ? value.toArray(new String[0]) : null;
             return this;
@@ -617,7 +665,7 @@
          */
         public @NonNull Builder setNext(@Nullable AttributionSource value) {
             checkNotUsed();
-            mBuilderFieldsSet |= 0x10;
+            mBuilderFieldsSet |= 0x20;
             mAttributionSourceState.next = (value != null) ? new AttributionSourceState[]
                     {value.mAttributionSourceState} : mAttributionSourceState.next;
             return this;
@@ -629,15 +677,18 @@
             mBuilderFieldsSet |= 0x40; // Mark builder used
 
             if ((mBuilderFieldsSet & 0x2) == 0) {
-                mAttributionSourceState.packageName = null;
+                mAttributionSourceState.pid = Process.INVALID_PID;
             }
             if ((mBuilderFieldsSet & 0x4) == 0) {
-                mAttributionSourceState.attributionTag = null;
+                mAttributionSourceState.packageName = null;
             }
             if ((mBuilderFieldsSet & 0x8) == 0) {
-                mAttributionSourceState.renouncedPermissions = null;
+                mAttributionSourceState.attributionTag = null;
             }
             if ((mBuilderFieldsSet & 0x10) == 0) {
+                mAttributionSourceState.renouncedPermissions = null;
+            }
+            if ((mBuilderFieldsSet & 0x20) == 0) {
                 mAttributionSourceState.next = null;
             }
 
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index 36f7ff5..fc75323 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -2399,7 +2399,6 @@
         sendBroadcastMultiplePermissions(intent, receiverPermissions, excludedPermissions, null);
     }
 
-
     /**
      * Like {@link #sendBroadcastMultiplePermissions(Intent, String[], String[])}, but also allows
      * specification of a list of excluded packages.
@@ -2409,6 +2408,19 @@
     public void sendBroadcastMultiplePermissions(@NonNull Intent intent,
             @NonNull String[] receiverPermissions, @Nullable String[] excludedPermissions,
             @Nullable String[] excludedPackages) {
+        sendBroadcastMultiplePermissions(intent, receiverPermissions, excludedPermissions,
+                excludedPackages, null);
+    }
+
+    /**
+     * Like {@link #sendBroadcastMultiplePermissions(Intent, String[], String[], String[])}, but
+     * also allows specification of options generated from {@link android.app.BroadcastOptions}.
+     *
+     * @hide
+     */
+    public void sendBroadcastMultiplePermissions(@NonNull Intent intent,
+            @NonNull String[] receiverPermissions, @Nullable String[] excludedPermissions,
+            @Nullable String[] excludedPackages, @Nullable BroadcastOptions options) {
         throw new RuntimeException("Not implemented. Must override in a subclass.");
     }
 
diff --git a/core/java/android/content/ContextWrapper.java b/core/java/android/content/ContextWrapper.java
index 1775bf7..21de5cf 100644
--- a/core/java/android/content/ContextWrapper.java
+++ b/core/java/android/content/ContextWrapper.java
@@ -24,6 +24,7 @@
 import android.annotation.SystemApi;
 import android.annotation.TestApi;
 import android.annotation.UiContext;
+import android.app.BroadcastOptions;
 import android.app.IApplicationThread;
 import android.app.IServiceConnection;
 import android.app.compat.CompatChanges;
@@ -519,9 +520,9 @@
     @Override
     public void sendBroadcastMultiplePermissions(@NonNull Intent intent,
             @NonNull String[] receiverPermissions, @Nullable String[] excludedPermissions,
-            @Nullable String[] excludedPackages) {
+            @Nullable String[] excludedPackages, @Nullable BroadcastOptions options) {
         mBase.sendBroadcastMultiplePermissions(intent, receiverPermissions, excludedPermissions,
-                excludedPackages);
+                excludedPackages, options);
     }
 
     /** @hide */
diff --git a/core/java/android/content/PermissionChecker.java b/core/java/android/content/PermissionChecker.java
index 8d3452e..0e3217d 100644
--- a/core/java/android/content/PermissionChecker.java
+++ b/core/java/android/content/PermissionChecker.java
@@ -152,7 +152,7 @@
             @NonNull String permission, int pid, int uid, @Nullable String packageName,
             @Nullable String attributionTag, @Nullable String message, boolean startDataDelivery) {
         return checkPermissionForDataDelivery(context, permission, pid, new AttributionSource(uid,
-                packageName, attributionTag), message, startDataDelivery);
+                pid, packageName, attributionTag), message, startDataDelivery);
     }
 
     /**
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index 6486278..328b0ae 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -204,6 +204,36 @@
             "android.app.PROPERTY_SPECIAL_USE_FGS_SUBTYPE";
 
     /**
+     * Application level {@link android.content.pm.PackageManager.Property PackageManager
+     * .Property} for an app to inform the system that the app can be opted-in or opted-out
+     * from the compatibility treatment that rotates camera output by 90 degrees on landscape
+     * sensors on devices known to have compatibility issues.
+     *
+     * <p>The treatment is disabled by default but device manufacturers can enable the treatment
+     * using their discretion to improve camera compatibility. With this property set to
+     * {@code false}, the rotation will not be applied. A value of {@code true}
+     * will ensure that rotation is applied, provided it is enabled for the device. In most cases,
+     * if rotation is the desired behavior this property need not be set. However, if your app
+     * experiences stretching or incorrect rotation on these devices, explicitly setting this to
+     * {@code true} may resolve that behavior. Apps should set this to {@code false} if there
+     * is confidence that the app handles
+     * {@link android.hardware.camera2.CameraCharacteristics#SENSOR_ORIENTATION} correctly.
+     * See <a href="https://developer.android.com/training/camera2/camera-preview"> the
+     * documentation for best practice.</a>
+     *
+     * <p><b>Syntax:</b>
+     * <pre>
+     * &lt;application&gt;
+     *   &lt;property
+     *     android:name="android.camera.PROPERTY_COMPAT_OVERRIDE_LANDSCAPE_TO_PORTRAIT"
+     *     android:value="true|false"/&gt;
+     * &lt;/application&gt;
+     * </pre>
+     */
+    public static final String PROPERTY_COMPAT_OVERRIDE_LANDSCAPE_TO_PORTRAIT =
+            "android.camera.PROPERTY_COMPAT_OVERRIDE_LANDSCAPE_TO_PORTRAIT";
+
+    /**
      * A property value set within the manifest.
      * <p>
      * The value of a property will only have a single type, as defined by
diff --git a/core/java/android/credentials/CredentialManager.java b/core/java/android/credentials/CredentialManager.java
index 0806f1d..493a4ff 100644
--- a/core/java/android/credentials/CredentialManager.java
+++ b/core/java/android/credentials/CredentialManager.java
@@ -167,6 +167,48 @@
     }
 
     /**
+     * Gets a {@link GetPendingCredentialResponse} that can launch the credential retrieval UI flow
+     * to request a user credential for your app.
+     *
+     * @param request            the request specifying type(s) of credentials to get from the user
+     * @param cancellationSignal an optional signal that allows for cancelling this call
+     * @param executor           the callback will take place on this {@link Executor}
+     * @param callback           the callback invoked when the request succeeds or fails
+     *
+     * @hide
+     */
+    public void getPendingCredential(
+            @NonNull GetCredentialRequest request,
+            @Nullable CancellationSignal cancellationSignal,
+            @CallbackExecutor @NonNull Executor executor,
+            @NonNull OutcomeReceiver<
+                    GetPendingCredentialResponse, GetCredentialException> callback) {
+        requireNonNull(request, "request must not be null");
+        requireNonNull(executor, "executor must not be null");
+        requireNonNull(callback, "callback must not be null");
+
+        if (cancellationSignal != null && cancellationSignal.isCanceled()) {
+            Log.w(TAG, "getPendingCredential already canceled");
+            return;
+        }
+
+        ICancellationSignal cancelRemote = null;
+        try {
+            cancelRemote =
+                    mService.executeGetPendingCredential(
+                            request,
+                            new GetPendingCredentialTransport(executor, callback),
+                            mContext.getOpPackageName());
+        } catch (RemoteException e) {
+            e.rethrowFromSystemServer();
+        }
+
+        if (cancellationSignal != null && cancelRemote != null) {
+            cancellationSignal.setRemote(cancelRemote);
+        }
+    }
+
+    /**
      * Launches the necessary flows to register an app credential for the user.
      *
      * <p>The execution can potentially launch UI flows to collect user consent to creating or
@@ -442,6 +484,32 @@
         }
     }
 
+    private static class GetPendingCredentialTransport extends IGetPendingCredentialCallback.Stub {
+        // TODO: listen for cancellation to release callback.
+
+        private final Executor mExecutor;
+        private final OutcomeReceiver<
+                GetPendingCredentialResponse, GetCredentialException> mCallback;
+
+        private GetPendingCredentialTransport(
+                Executor executor,
+                OutcomeReceiver<GetPendingCredentialResponse, GetCredentialException> callback) {
+            mExecutor = executor;
+            mCallback = callback;
+        }
+
+        @Override
+        public void onResponse(GetPendingCredentialResponse response) {
+            mExecutor.execute(() -> mCallback.onResult(response));
+        }
+
+        @Override
+        public void onError(String errorType, String message) {
+            mExecutor.execute(
+                    () -> mCallback.onError(new GetCredentialException(errorType, message)));
+        }
+    }
+
     private static class GetCredentialTransport extends IGetCredentialCallback.Stub {
         // TODO: listen for cancellation to release callback.
 
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardBouncerModel.kt b/core/java/android/credentials/GetPendingCredentialResponse.aidl
similarity index 60%
copy from packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardBouncerModel.kt
copy to core/java/android/credentials/GetPendingCredentialResponse.aidl
index ad783da..1cdd637 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardBouncerModel.kt
+++ b/core/java/android/credentials/GetPendingCredentialResponse.aidl
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2022 The Android Open Source Project
+ * Copyright 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.
@@ -11,14 +11,9 @@
  * 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
+ * limitations under the License.
  */
 
-package com.android.systemui.keyguard.shared.model
+package android.credentials;
 
-/** Models the state of the lock-screen bouncer */
-data class KeyguardBouncerModel(
-    val promptReason: Int = 0,
-    val errorMessage: CharSequence? = null,
-    val expansionAmount: Float = 0f,
-)
+parcelable GetPendingCredentialResponse;
\ No newline at end of file
diff --git a/core/java/android/credentials/GetPendingCredentialResponse.java b/core/java/android/credentials/GetPendingCredentialResponse.java
new file mode 100644
index 0000000..9005d9d
--- /dev/null
+++ b/core/java/android/credentials/GetPendingCredentialResponse.java
@@ -0,0 +1,127 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.credentials;
+
+import android.annotation.CallbackExecutor;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.Activity;
+import android.os.CancellationSignal;
+import android.os.OutcomeReceiver;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.concurrent.Executor;
+
+
+/**
+ * A response object that prefetches user app credentials and provides metadata about them. It can
+ * then be used to issue the full credential retrieval flow via the
+ * {@link #show(Activity, CancellationSignal, Executor, OutcomeReceiver)} method to perform the
+ * necessary flows such as consent collection and officially retrieve a credential.
+ *
+ * @hide
+ */
+public final class GetPendingCredentialResponse implements Parcelable {
+    private final boolean mHasCredentialResults;
+    private final boolean mHasAuthenticationResults;
+    private final boolean mHasRemoteResults;
+
+    /** Returns true if the user has any candidate credentials, and false otherwise. */
+    public boolean hasCredentialResults() {
+        return mHasCredentialResults;
+    }
+
+    /**
+     * Returns true if the user has any candidate authentication actions (locked credential
+     * supplier), and false otherwise.
+     */
+    public boolean hasAuthenticationResults() {
+        return mHasAuthenticationResults;
+    }
+
+    /**
+     * Returns true if the user has any candidate remote credential results, and false otherwise.
+     */
+    public boolean hasRemoteResults() {
+        return mHasRemoteResults;
+    }
+
+    /**
+     * Launches the necessary flows such as consent collection and credential selection to
+     * officially retrieve a credential among the pending credential candidates.
+     *
+     * @param activity           the activity used to launch any UI needed
+     * @param cancellationSignal an optional signal that allows for cancelling this call
+     * @param executor           the callback will take place on this {@link Executor}
+     * @param callback           the callback invoked when the request succeeds or fails
+     */
+    public void show(@NonNull Activity activity, @Nullable CancellationSignal cancellationSignal,
+            @CallbackExecutor @NonNull Executor executor,
+            @NonNull OutcomeReceiver<GetCredentialResponse, GetCredentialException> callback) {
+        // TODO(b/273308895): implement
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeBoolean(mHasCredentialResults);
+        dest.writeBoolean(mHasAuthenticationResults);
+        dest.writeBoolean(mHasRemoteResults);
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public String toString() {
+        return "GetCredentialResponse {" + "credential=" + mHasCredentialResults + "}";
+    }
+
+    /**
+     * Constructs a {@link GetPendingCredentialResponse}.
+     *
+     * @param hasCredentialResults whether the user has any candidate credentials
+     * @param hasAuthenticationResults whether the user has any candidate authentication actions
+     * @param hasRemoteResults whether the user has any candidate remote options
+     */
+    public GetPendingCredentialResponse(boolean hasCredentialResults,
+            boolean hasAuthenticationResults, boolean hasRemoteResults) {
+        mHasCredentialResults = hasCredentialResults;
+        mHasAuthenticationResults = hasAuthenticationResults;
+        mHasRemoteResults = hasRemoteResults;
+    }
+
+    private GetPendingCredentialResponse(@NonNull Parcel in) {
+        mHasCredentialResults = in.readBoolean();
+        mHasAuthenticationResults = in.readBoolean();
+        mHasRemoteResults = in.readBoolean();
+    }
+
+    public static final @NonNull Creator<GetPendingCredentialResponse> CREATOR = new Creator<>() {
+        @Override
+        public GetPendingCredentialResponse[] newArray(int size) {
+            return new GetPendingCredentialResponse[size];
+        }
+
+        @Override
+        public GetPendingCredentialResponse createFromParcel(@NonNull Parcel in) {
+            return new GetPendingCredentialResponse(in);
+        }
+    };
+}
diff --git a/core/java/android/credentials/ICredentialManager.aidl b/core/java/android/credentials/ICredentialManager.aidl
index 8c2cb5a..af8e7b4 100644
--- a/core/java/android/credentials/ICredentialManager.aidl
+++ b/core/java/android/credentials/ICredentialManager.aidl
@@ -27,6 +27,7 @@
 import android.credentials.IClearCredentialStateCallback;
 import android.credentials.ICreateCredentialCallback;
 import android.credentials.IGetCredentialCallback;
+import android.credentials.IGetPendingCredentialCallback;
 import android.credentials.ISetEnabledProvidersCallback;
 import android.content.ComponentName;
 import android.os.ICancellationSignal;
@@ -40,6 +41,8 @@
 
     @nullable ICancellationSignal executeGetCredential(in GetCredentialRequest request, in IGetCredentialCallback callback, String callingPackage);
 
+    @nullable ICancellationSignal executeGetPendingCredential(in GetCredentialRequest request, in IGetPendingCredentialCallback callback, String callingPackage);
+
     @nullable ICancellationSignal executeCreateCredential(in CreateCredentialRequest request, in ICreateCredentialCallback callback, String callingPackage);
 
     @nullable ICancellationSignal clearCredentialState(in ClearCredentialStateRequest request, in IClearCredentialStateCallback callback, String callingPackage);
diff --git a/core/java/android/credentials/IGetPendingCredentialCallback.aidl b/core/java/android/credentials/IGetPendingCredentialCallback.aidl
new file mode 100644
index 0000000..4ab0f99
--- /dev/null
+++ b/core/java/android/credentials/IGetPendingCredentialCallback.aidl
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.credentials;
+
+import android.app.PendingIntent;
+import android.credentials.GetPendingCredentialResponse;
+
+/**
+ * Listener for a executeGetPendingCredential request.
+ *
+ * @hide
+ */
+interface IGetPendingCredentialCallback {
+    oneway void onResponse(in GetPendingCredentialResponse response);
+    oneway void onError(String errorType, String message);
+}
\ No newline at end of file
diff --git a/core/java/android/hardware/camera2/CameraManager.java b/core/java/android/hardware/camera2/CameraManager.java
index 6cd4a2e..722dd08 100644
--- a/core/java/android/hardware/camera2/CameraManager.java
+++ b/core/java/android/hardware/camera2/CameraManager.java
@@ -118,13 +118,6 @@
     public static final long OVERRIDE_CAMERA_LANDSCAPE_TO_PORTRAIT = 250678880L;
 
     /**
-     * Package-level opt in/out for the above.
-     * @hide
-     */
-    public static final String PROPERTY_COMPAT_OVERRIDE_LANDSCAPE_TO_PORTRAIT =
-            "android.camera.PROPERTY_COMPAT_OVERRIDE_LANDSCAPE_TO_PORTRAIT";
-
-    /**
      * System property for allowing the above
      * @hide
      */
@@ -1189,7 +1182,8 @@
             PackageManager packageManager = context.getPackageManager();
 
             try {
-                return packageManager.getProperty(PROPERTY_COMPAT_OVERRIDE_LANDSCAPE_TO_PORTRAIT,
+                return packageManager.getProperty(
+                        PackageManager.PROPERTY_COMPAT_OVERRIDE_LANDSCAPE_TO_PORTRAIT,
                         context.getOpPackageName()).getBoolean();
             } catch (PackageManager.NameNotFoundException e) {
                 // No such property
diff --git a/core/java/android/hardware/camera2/params/DynamicRangeProfiles.java b/core/java/android/hardware/camera2/params/DynamicRangeProfiles.java
index 34c8336..7cd627d 100644
--- a/core/java/android/hardware/camera2/params/DynamicRangeProfiles.java
+++ b/core/java/android/hardware/camera2/params/DynamicRangeProfiles.java
@@ -181,13 +181,15 @@
      * for a given camera id in order to retrieve the device capabilities.</p>
      *
      * @param elements
-     *          An array of elements describing the map. It contains two elements per entry which
-     *          describe the supported dynamic range profile value in the first element and in the
-     *          second element a bitmap of concurrently supported dynamic range profiles within the
-     *          same capture request. Bitmap values of 0 indicate that there are no constraints.
+     *          An array of elements describing the map. It contains three elements per entry. The
+     *          first element describes the supported dynamic range profile value. The
+     *          second element contains a bitmap of concurrently supported dynamic range profiles
+     *          within the same capture request. The third element contains a hint about
+     *          extra latency associated with the corresponding dynamic range. Bitmap values of 0
+     *          indicate that there are no constraints.
      *
      * @throws IllegalArgumentException
-     *            if the {@code elements} array length is invalid, not divisible by 2 or contains
+     *            if the {@code elements} array length is invalid, not divisible by 3 or contains
      *            invalid element values
      * @throws NullPointerException
      *            if {@code elements} is {@code null}
diff --git a/core/java/android/hardware/input/InputManager.java b/core/java/android/hardware/input/InputManager.java
index 490589f..9662be3 100644
--- a/core/java/android/hardware/input/InputManager.java
+++ b/core/java/android/hardware/input/InputManager.java
@@ -44,8 +44,6 @@
 import android.os.IBinder;
 import android.os.IVibratorStateListener;
 import android.os.InputEventInjectionSync;
-import android.os.Looper;
-import android.os.Message;
 import android.os.Process;
 import android.os.RemoteException;
 import android.os.SystemClock;
@@ -68,7 +66,6 @@
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.os.SomeArgs;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -88,10 +85,6 @@
     // To enable these logs, run: 'adb shell setprop log.tag.InputManager DEBUG' (requires restart)
     private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
 
-    private static final int MSG_DEVICE_ADDED = 1;
-    private static final int MSG_DEVICE_REMOVED = 2;
-    private static final int MSG_DEVICE_CHANGED = 3;
-
     private static InputManager sInstance;
 
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
@@ -112,18 +105,6 @@
     @Nullable
     private Boolean mIsStylusPointerIconEnabled = null;
 
-    // Guarded by mInputDevicesLock
-    private final Object mInputDevicesLock = new Object();
-    private SparseArray<InputDevice> mInputDevices;
-    private InputDevicesChangedListener mInputDevicesChangedListener;
-    private final ArrayList<InputDeviceListenerDelegate> mInputDeviceListeners = new ArrayList<>();
-
-    // Guarded by mTabletModeLock
-    private final Object mTabletModeLock = new Object();
-    @SuppressWarnings({"FieldCanBeLocal", "UnusedDeclaration"})
-    private TabletModeChangedListener mTabletModeChangedListener;
-    private ArrayList<OnTabletModeChangedListenerDelegate> mOnTabletModeChangedListeners;
-
     private final Object mBatteryListenersLock = new Object();
     // Maps a deviceId whose battery is currently being monitored to an entry containing the
     // registered listeners for that device.
@@ -403,27 +384,7 @@
      */
     @Nullable
     public InputDevice getInputDevice(int id) {
-        synchronized (mInputDevicesLock) {
-            populateInputDevicesLocked();
-
-            int index = mInputDevices.indexOfKey(id);
-            if (index < 0) {
-                return null;
-            }
-
-            InputDevice inputDevice = mInputDevices.valueAt(index);
-            if (inputDevice == null) {
-                try {
-                    inputDevice = mIm.getInputDevice(id);
-                } catch (RemoteException ex) {
-                    throw ex.rethrowFromSystemServer();
-                }
-                if (inputDevice != null) {
-                    mInputDevices.setValueAt(index, inputDevice);
-                }
-            }
-            return inputDevice;
-        }
+        return mGlobal.getInputDevice(id);
     }
 
     /**
@@ -433,34 +394,7 @@
      * @hide
      */
     public InputDevice getInputDeviceByDescriptor(String descriptor) {
-        if (descriptor == null) {
-            throw new IllegalArgumentException("descriptor must not be null.");
-        }
-
-        synchronized (mInputDevicesLock) {
-            populateInputDevicesLocked();
-
-            int numDevices = mInputDevices.size();
-            for (int i = 0; i < numDevices; i++) {
-                InputDevice inputDevice = mInputDevices.valueAt(i);
-                if (inputDevice == null) {
-                    int id = mInputDevices.keyAt(i);
-                    try {
-                        inputDevice = mIm.getInputDevice(id);
-                    } catch (RemoteException ex) {
-                        throw ex.rethrowFromSystemServer();
-                    }
-                    if (inputDevice == null) {
-                        continue;
-                    }
-                    mInputDevices.setValueAt(i, inputDevice);
-                }
-                if (descriptor.equals(inputDevice.getDescriptor())) {
-                    return inputDevice;
-                }
-            }
-            return null;
-        }
+        return mGlobal.getInputDeviceByDescriptor(descriptor);
     }
 
     /**
@@ -468,16 +402,7 @@
      * @return The input device ids.
      */
     public int[] getInputDeviceIds() {
-        synchronized (mInputDevicesLock) {
-            populateInputDevicesLocked();
-
-            final int count = mInputDevices.size();
-            final int[] ids = new int[count];
-            for (int i = 0; i < count; i++) {
-                ids[i] = mInputDevices.keyAt(i);
-            }
-            return ids;
-        }
+        return mGlobal.getInputDeviceIds();
     }
 
     /**
@@ -547,17 +472,7 @@
      * @see #unregisterInputDeviceListener
      */
     public void registerInputDeviceListener(InputDeviceListener listener, Handler handler) {
-        if (listener == null) {
-            throw new IllegalArgumentException("listener must not be null");
-        }
-
-        synchronized (mInputDevicesLock) {
-            populateInputDevicesLocked();
-            int index = findInputDeviceListenerLocked(listener);
-            if (index < 0) {
-                mInputDeviceListeners.add(new InputDeviceListenerDelegate(listener, handler));
-            }
-        }
+        mGlobal.registerInputDeviceListener(listener, handler);
     }
 
     /**
@@ -568,28 +483,7 @@
      * @see #registerInputDeviceListener
      */
     public void unregisterInputDeviceListener(InputDeviceListener listener) {
-        if (listener == null) {
-            throw new IllegalArgumentException("listener must not be null");
-        }
-
-        synchronized (mInputDevicesLock) {
-            int index = findInputDeviceListenerLocked(listener);
-            if (index >= 0) {
-                InputDeviceListenerDelegate d = mInputDeviceListeners.get(index);
-                d.removeCallbacksAndMessages(null);
-                mInputDeviceListeners.remove(index);
-            }
-        }
-    }
-
-    private int findInputDeviceListenerLocked(InputDeviceListener listener) {
-        final int numListeners = mInputDeviceListeners.size();
-        for (int i = 0; i < numListeners; i++) {
-            if (mInputDeviceListeners.get(i).mListener == listener) {
-                return i;
-            }
-        }
-        return -1;
+        mGlobal.unregisterInputDeviceListener(listener);
     }
 
     /**
@@ -618,20 +512,7 @@
      */
     public void registerOnTabletModeChangedListener(
             OnTabletModeChangedListener listener, Handler handler) {
-        if (listener == null) {
-            throw new IllegalArgumentException("listener must not be null");
-        }
-        synchronized (mTabletModeLock) {
-            if (mOnTabletModeChangedListeners == null) {
-                initializeTabletModeListenerLocked();
-            }
-            int idx = findOnTabletModeChangedListenerLocked(listener);
-            if (idx < 0) {
-                OnTabletModeChangedListenerDelegate d =
-                    new OnTabletModeChangedListenerDelegate(listener, handler);
-                mOnTabletModeChangedListeners.add(d);
-            }
-        }
+        mGlobal.registerOnTabletModeChangedListener(listener, handler);
     }
 
     /**
@@ -641,37 +522,7 @@
      * @hide
      */
     public void unregisterOnTabletModeChangedListener(OnTabletModeChangedListener listener) {
-        if (listener == null) {
-            throw new IllegalArgumentException("listener must not be null");
-        }
-        synchronized (mTabletModeLock) {
-            int idx = findOnTabletModeChangedListenerLocked(listener);
-            if (idx >= 0) {
-                OnTabletModeChangedListenerDelegate d = mOnTabletModeChangedListeners.remove(idx);
-                d.removeCallbacksAndMessages(null);
-            }
-        }
-    }
-
-    private void initializeTabletModeListenerLocked() {
-        final TabletModeChangedListener listener = new TabletModeChangedListener();
-        try {
-            mIm.registerTabletModeChangedListener(listener);
-        } catch (RemoteException ex) {
-            throw ex.rethrowFromSystemServer();
-        }
-        mTabletModeChangedListener = listener;
-        mOnTabletModeChangedListeners = new ArrayList<>();
-    }
-
-    private int findOnTabletModeChangedListenerLocked(OnTabletModeChangedListener listener) {
-        final int N = mOnTabletModeChangedListeners.size();
-        for (int i = 0; i < N; i++) {
-            if (mOnTabletModeChangedListeners.get(i).mListener == listener) {
-                return i;
-            }
-        }
-        return -1;
+        mGlobal.unregisterOnTabletModeChangedListener(listener);
     }
 
     /**
@@ -1543,133 +1394,7 @@
      */
     @Nullable
     public HostUsiVersion getHostUsiVersion(@NonNull Display display) {
-        Objects.requireNonNull(display, "display should not be null");
-
-        // Return the first valid USI version reported by any input device associated with
-        // the display.
-        synchronized (mInputDevicesLock) {
-            populateInputDevicesLocked();
-
-            for (int i = 0; i < mInputDevices.size(); i++) {
-                final InputDevice device = getInputDevice(mInputDevices.keyAt(i));
-                if (device != null && device.getAssociatedDisplayId() == display.getDisplayId()) {
-                    if (device.getHostUsiVersion() != null) {
-                        return device.getHostUsiVersion();
-                    }
-                }
-            }
-        }
-
-        // If there are no input devices that report a valid USI version, see if there is a config
-        // that specifies the USI version for the display. This is to handle cases where the USI
-        // input device is not registered by the kernel/driver all the time.
-        try {
-            return mIm.getHostUsiVersionFromDisplayConfig(display.getDisplayId());
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
-    }
-
-    private void populateInputDevicesLocked() {
-        if (mInputDevicesChangedListener == null) {
-            final InputDevicesChangedListener listener = new InputDevicesChangedListener();
-            try {
-                mIm.registerInputDevicesChangedListener(listener);
-            } catch (RemoteException ex) {
-                throw ex.rethrowFromSystemServer();
-            }
-            mInputDevicesChangedListener = listener;
-        }
-
-        if (mInputDevices == null) {
-            final int[] ids;
-            try {
-                ids = mIm.getInputDeviceIds();
-            } catch (RemoteException ex) {
-                throw ex.rethrowFromSystemServer();
-            }
-
-            mInputDevices = new SparseArray<>();
-            for (int id : ids) {
-                mInputDevices.put(id, null);
-            }
-        }
-    }
-
-    private void onInputDevicesChanged(int[] deviceIdAndGeneration) {
-        if (DEBUG) {
-            Log.d(TAG, "Received input devices changed.");
-        }
-
-        synchronized (mInputDevicesLock) {
-            for (int i = mInputDevices.size(); --i > 0; ) {
-                final int deviceId = mInputDevices.keyAt(i);
-                if (!containsDeviceId(deviceIdAndGeneration, deviceId)) {
-                    if (DEBUG) {
-                        Log.d(TAG, "Device removed: " + deviceId);
-                    }
-                    mInputDevices.removeAt(i);
-                    sendMessageToInputDeviceListenersLocked(MSG_DEVICE_REMOVED, deviceId);
-                }
-            }
-
-            for (int i = 0; i < deviceIdAndGeneration.length; i += 2) {
-                final int deviceId = deviceIdAndGeneration[i];
-                int index = mInputDevices.indexOfKey(deviceId);
-                if (index >= 0) {
-                    final InputDevice device = mInputDevices.valueAt(index);
-                    if (device != null) {
-                        final int generation = deviceIdAndGeneration[i + 1];
-                        if (device.getGeneration() != generation) {
-                            if (DEBUG) {
-                                Log.d(TAG, "Device changed: " + deviceId);
-                            }
-                            mInputDevices.setValueAt(index, null);
-                            sendMessageToInputDeviceListenersLocked(MSG_DEVICE_CHANGED, deviceId);
-                        }
-                    }
-                } else {
-                    if (DEBUG) {
-                        Log.d(TAG, "Device added: " + deviceId);
-                    }
-                    mInputDevices.put(deviceId, null);
-                    sendMessageToInputDeviceListenersLocked(MSG_DEVICE_ADDED, deviceId);
-                }
-            }
-        }
-    }
-
-    private void sendMessageToInputDeviceListenersLocked(int what, int deviceId) {
-        final int numListeners = mInputDeviceListeners.size();
-        for (int i = 0; i < numListeners; i++) {
-            InputDeviceListenerDelegate listener = mInputDeviceListeners.get(i);
-            listener.sendMessage(listener.obtainMessage(what, deviceId, 0));
-        }
-    }
-
-    private static boolean containsDeviceId(int[] deviceIdAndGeneration, int deviceId) {
-        for (int i = 0; i < deviceIdAndGeneration.length; i += 2) {
-            if (deviceIdAndGeneration[i] == deviceId) {
-                return true;
-            }
-        }
-        return false;
-    }
-
-
-    private void onTabletModeChanged(long whenNanos, boolean inTabletMode) {
-        if (DEBUG) {
-            Log.d(TAG, "Received tablet mode changed: "
-                    + "whenNanos=" + whenNanos + ", inTabletMode=" + inTabletMode);
-        }
-        synchronized (mTabletModeLock) {
-            final int numListeners = mOnTabletModeChangedListeners.size();
-            for (int i = 0; i < numListeners; i++) {
-                OnTabletModeChangedListenerDelegate listener =
-                        mOnTabletModeChangedListeners.get(i);
-                listener.sendTabletModeChanged(whenNanos, inTabletMode);
-            }
-        }
+        return mGlobal.getHostUsiVersion(display);
     }
 
     /**
@@ -2149,7 +1874,7 @@
     public interface InputDeviceListener {
         /**
          * Called whenever an input device has been added to the system.
-         * Use {@link InputManager#getInputDevice} to get more information about the device.
+         * Use {@link InputManagerGlobal#getInputDevice} to get more information about the device.
          *
          * @param deviceId The id of the input device that was added.
          */
@@ -2172,37 +1897,6 @@
         void onInputDeviceChanged(int deviceId);
     }
 
-    private final class InputDevicesChangedListener extends IInputDevicesChangedListener.Stub {
-        @Override
-        public void onInputDevicesChanged(int[] deviceIdAndGeneration) throws RemoteException {
-            InputManager.this.onInputDevicesChanged(deviceIdAndGeneration);
-        }
-    }
-
-    private static final class InputDeviceListenerDelegate extends Handler {
-        public final InputDeviceListener mListener;
-
-        public InputDeviceListenerDelegate(InputDeviceListener listener, Handler handler) {
-            super(handler != null ? handler.getLooper() : Looper.myLooper());
-            mListener = listener;
-        }
-
-        @Override
-        public void handleMessage(Message msg) {
-            switch (msg.what) {
-                case MSG_DEVICE_ADDED:
-                    mListener.onInputDeviceAdded(msg.arg1);
-                    break;
-                case MSG_DEVICE_REMOVED:
-                    mListener.onInputDeviceRemoved(msg.arg1);
-                    break;
-                case MSG_DEVICE_CHANGED:
-                    mListener.onInputDeviceChanged(msg.arg1);
-                    break;
-            }
-        }
-    }
-
     /** @hide */
     public interface OnTabletModeChangedListener {
         /**
@@ -2236,43 +1930,6 @@
                 int deviceId, @NonNull KeyboardBacklightState state, boolean isTriggeredByKeyPress);
     }
 
-    private final class TabletModeChangedListener extends ITabletModeChangedListener.Stub {
-        @Override
-        public void onTabletModeChanged(long whenNanos, boolean inTabletMode) {
-            InputManager.this.onTabletModeChanged(whenNanos, inTabletMode);
-        }
-    }
-
-    private static final class OnTabletModeChangedListenerDelegate extends Handler {
-        private static final int MSG_TABLET_MODE_CHANGED = 0;
-
-        public final OnTabletModeChangedListener mListener;
-
-        public OnTabletModeChangedListenerDelegate(
-                OnTabletModeChangedListener listener, Handler handler) {
-            super(handler != null ? handler.getLooper() : Looper.myLooper());
-            mListener = listener;
-        }
-
-        public void sendTabletModeChanged(long whenNanos, boolean inTabletMode) {
-            SomeArgs args = SomeArgs.obtain();
-            args.argi1 = (int) whenNanos;
-            args.argi2 = (int) (whenNanos >> 32);
-            args.arg1 = inTabletMode;
-            obtainMessage(MSG_TABLET_MODE_CHANGED, args).sendToTarget();
-        }
-
-        @Override
-        public void handleMessage(Message msg) {
-            if (msg.what == MSG_TABLET_MODE_CHANGED) {
-                SomeArgs args = (SomeArgs) msg.obj;
-                long whenNanos = (args.argi1 & 0xFFFFFFFFL) | ((long) args.argi2 << 32);
-                boolean inTabletMode = (boolean) args.arg1;
-                mListener.onTabletModeChanged(whenNanos, inTabletMode);
-            }
-        }
-    }
-
     // Implementation of the android.hardware.BatteryState interface used to report the battery
     // state via the InputDevice#getBatteryState() and InputDeviceBatteryListener interfaces.
     private static final class LocalBatteryState extends BatteryState {
diff --git a/core/java/android/hardware/input/InputManagerGlobal.java b/core/java/android/hardware/input/InputManagerGlobal.java
index 82dddfc..28699e0 100644
--- a/core/java/android/hardware/input/InputManagerGlobal.java
+++ b/core/java/android/hardware/input/InputManagerGlobal.java
@@ -16,9 +16,27 @@
 
 package android.hardware.input;
 
+import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.content.Context;
+import android.hardware.input.InputManager.InputDeviceListener;
+import android.hardware.input.InputManager.OnTabletModeChangedListener;
+import android.os.Handler;
 import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
+import android.os.RemoteException;
 import android.os.ServiceManager;
+import android.util.Log;
+import android.util.SparseArray;
+import android.view.Display;
+import android.view.InputDevice;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.os.SomeArgs;
+
+import java.util.ArrayList;
+import java.util.Objects;
 
 /**
  * Manages communication with the input manager service on behalf of
@@ -28,6 +46,20 @@
  */
 public final class InputManagerGlobal {
     private static final String TAG = "InputManagerGlobal";
+    // To enable these logs, run: 'adb shell setprop log.tag.InputManagerGlobal DEBUG'
+    // (requires restart)
+    private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+
+    @GuardedBy("mInputDeviceListeners")
+    @Nullable private SparseArray<InputDevice> mInputDevices;
+    @GuardedBy("mInputDeviceListeners")
+    @Nullable private InputDevicesChangedListener mInputDevicesChangedListener;
+    @GuardedBy("mInputDeviceListeners")
+    private final ArrayList<InputDeviceListenerDelegate> mInputDeviceListeners = new ArrayList<>();
+
+    @GuardedBy("mOnTabletModeChangedListeners")
+    private final ArrayList<OnTabletModeChangedListenerDelegate> mOnTabletModeChangedListeners =
+            new ArrayList<>();
 
     private static InputManagerGlobal sInstance;
 
@@ -79,4 +111,396 @@
             sInstance = null;
         }
     }
+
+    /**
+     * @see InputManager#getInputDevice(int)
+     */
+    @Nullable
+    public InputDevice getInputDevice(int id) {
+        synchronized (mInputDeviceListeners) {
+            populateInputDevicesLocked();
+
+            int index = mInputDevices.indexOfKey(id);
+            if (index < 0) {
+                return null;
+            }
+
+            InputDevice inputDevice = mInputDevices.valueAt(index);
+            if (inputDevice == null) {
+                try {
+                    inputDevice = mIm.getInputDevice(id);
+                } catch (RemoteException ex) {
+                    throw ex.rethrowFromSystemServer();
+                }
+                if (inputDevice != null) {
+                    mInputDevices.setValueAt(index, inputDevice);
+                }
+            }
+            return inputDevice;
+        }
+    }
+
+    @GuardedBy("mInputDeviceListeners")
+    private void populateInputDevicesLocked() {
+        if (mInputDevicesChangedListener == null) {
+            final InputDevicesChangedListener
+                    listener = new InputDevicesChangedListener();
+            try {
+                mIm.registerInputDevicesChangedListener(listener);
+            } catch (RemoteException ex) {
+                throw ex.rethrowFromSystemServer();
+            }
+            mInputDevicesChangedListener = listener;
+        }
+
+        if (mInputDevices == null) {
+            final int[] ids;
+            try {
+                ids = mIm.getInputDeviceIds();
+            } catch (RemoteException ex) {
+                throw ex.rethrowFromSystemServer();
+            }
+
+            mInputDevices = new SparseArray<>();
+            for (int id : ids) {
+                mInputDevices.put(id, null);
+            }
+        }
+    }
+
+    private final class InputDevicesChangedListener extends IInputDevicesChangedListener.Stub {
+        @Override
+        public void onInputDevicesChanged(int[] deviceIdAndGeneration) throws RemoteException {
+            InputManagerGlobal.this.onInputDevicesChanged(deviceIdAndGeneration);
+        }
+    }
+
+    private void onInputDevicesChanged(int[] deviceIdAndGeneration) {
+        if (DEBUG) {
+            Log.d(TAG, "Received input devices changed.");
+        }
+
+        synchronized (mInputDeviceListeners) {
+            for (int i = mInputDevices.size(); --i > 0; ) {
+                final int deviceId = mInputDevices.keyAt(i);
+                if (!containsDeviceId(deviceIdAndGeneration, deviceId)) {
+                    if (DEBUG) {
+                        Log.d(TAG, "Device removed: " + deviceId);
+                    }
+                    mInputDevices.removeAt(i);
+                    sendMessageToInputDeviceListenersLocked(
+                            InputDeviceListenerDelegate.MSG_DEVICE_REMOVED, deviceId);
+                }
+            }
+
+            for (int i = 0; i < deviceIdAndGeneration.length; i += 2) {
+                final int deviceId = deviceIdAndGeneration[i];
+                int index = mInputDevices.indexOfKey(deviceId);
+                if (index >= 0) {
+                    final InputDevice device = mInputDevices.valueAt(index);
+                    if (device != null) {
+                        final int generation = deviceIdAndGeneration[i + 1];
+                        if (device.getGeneration() != generation) {
+                            if (DEBUG) {
+                                Log.d(TAG, "Device changed: " + deviceId);
+                            }
+                            mInputDevices.setValueAt(index, null);
+                            sendMessageToInputDeviceListenersLocked(
+                                    InputDeviceListenerDelegate.MSG_DEVICE_CHANGED, deviceId);
+                        }
+                    }
+                } else {
+                    if (DEBUG) {
+                        Log.d(TAG, "Device added: " + deviceId);
+                    }
+                    mInputDevices.put(deviceId, null);
+                    sendMessageToInputDeviceListenersLocked(
+                            InputDeviceListenerDelegate.MSG_DEVICE_ADDED, deviceId);
+                }
+            }
+        }
+    }
+
+    private static final class InputDeviceListenerDelegate extends Handler {
+        public final InputDeviceListener mListener;
+        static final int MSG_DEVICE_ADDED = 1;
+        static final int MSG_DEVICE_REMOVED = 2;
+        static final int MSG_DEVICE_CHANGED = 3;
+
+        InputDeviceListenerDelegate(InputDeviceListener listener, Handler handler) {
+            super(handler != null ? handler.getLooper() : Looper.myLooper());
+            mListener = listener;
+        }
+
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+                case MSG_DEVICE_ADDED:
+                    mListener.onInputDeviceAdded(msg.arg1);
+                    break;
+                case MSG_DEVICE_REMOVED:
+                    mListener.onInputDeviceRemoved(msg.arg1);
+                    break;
+                case MSG_DEVICE_CHANGED:
+                    mListener.onInputDeviceChanged(msg.arg1);
+                    break;
+            }
+        }
+    }
+
+    private static boolean containsDeviceId(int[] deviceIdAndGeneration, int deviceId) {
+        for (int i = 0; i < deviceIdAndGeneration.length; i += 2) {
+            if (deviceIdAndGeneration[i] == deviceId) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    @GuardedBy("mInputDeviceListeners")
+    private void sendMessageToInputDeviceListenersLocked(int what, int deviceId) {
+        final int numListeners = mInputDeviceListeners.size();
+        for (int i = 0; i < numListeners; i++) {
+            InputDeviceListenerDelegate listener = mInputDeviceListeners.get(i);
+            listener.sendMessage(listener.obtainMessage(what, deviceId, 0));
+        }
+    }
+
+    /**
+     * @see InputManager#registerInputDeviceListener
+     */
+    void registerInputDeviceListener(InputDeviceListener listener, Handler handler) {
+        if (listener == null) {
+            throw new IllegalArgumentException("listener must not be null");
+        }
+
+        synchronized (mInputDeviceListeners) {
+            populateInputDevicesLocked();
+            int index = findInputDeviceListenerLocked(listener);
+            if (index < 0) {
+                mInputDeviceListeners.add(new InputDeviceListenerDelegate(listener, handler));
+            }
+        }
+    }
+
+    /**
+     * @see InputManager#unregisterInputDeviceListener
+     */
+    void unregisterInputDeviceListener(InputDeviceListener listener) {
+        if (listener == null) {
+            throw new IllegalArgumentException("listener must not be null");
+        }
+
+        synchronized (mInputDeviceListeners) {
+            int index = findInputDeviceListenerLocked(listener);
+            if (index >= 0) {
+                InputDeviceListenerDelegate d = mInputDeviceListeners.get(index);
+                d.removeCallbacksAndMessages(null);
+                mInputDeviceListeners.remove(index);
+            }
+        }
+    }
+
+    @GuardedBy("mInputDeviceListeners")
+    private int findInputDeviceListenerLocked(InputDeviceListener listener) {
+        final int numListeners = mInputDeviceListeners.size();
+        for (int i = 0; i < numListeners; i++) {
+            if (mInputDeviceListeners.get(i).mListener == listener) {
+                return i;
+            }
+        }
+        return -1;
+    }
+
+    /**
+     * @see InputManager#getInputDeviceIds
+     */
+    public int[] getInputDeviceIds() {
+        synchronized (mInputDeviceListeners) {
+            populateInputDevicesLocked();
+
+            final int count = mInputDevices.size();
+            final int[] ids = new int[count];
+            for (int i = 0; i < count; i++) {
+                ids[i] = mInputDevices.keyAt(i);
+            }
+            return ids;
+        }
+    }
+
+    /**
+     * @see InputManager#getInputDeviceByDescriptor
+     */
+    InputDevice getInputDeviceByDescriptor(String descriptor) {
+        if (descriptor == null) {
+            throw new IllegalArgumentException("descriptor must not be null.");
+        }
+
+        synchronized (mInputDeviceListeners) {
+            populateInputDevicesLocked();
+
+            int numDevices = mInputDevices.size();
+            for (int i = 0; i < numDevices; i++) {
+                InputDevice inputDevice = mInputDevices.valueAt(i);
+                if (inputDevice == null) {
+                    int id = mInputDevices.keyAt(i);
+                    try {
+                        inputDevice = mIm.getInputDevice(id);
+                    } catch (RemoteException ex) {
+                        throw ex.rethrowFromSystemServer();
+                    }
+                    if (inputDevice == null) {
+                        continue;
+                    }
+                    mInputDevices.setValueAt(i, inputDevice);
+                }
+                if (descriptor.equals(inputDevice.getDescriptor())) {
+                    return inputDevice;
+                }
+            }
+            return null;
+        }
+    }
+
+    /**
+     * @see InputManager#getHostUsiVersion
+     */
+    @Nullable
+    HostUsiVersion getHostUsiVersion(@NonNull Display display) {
+        Objects.requireNonNull(display, "display should not be null");
+
+        // Return the first valid USI version reported by any input device associated with
+        // the display.
+        synchronized (mInputDeviceListeners) {
+            populateInputDevicesLocked();
+
+            for (int i = 0; i < mInputDevices.size(); i++) {
+                final InputDevice device = getInputDevice(mInputDevices.keyAt(i));
+                if (device != null && device.getAssociatedDisplayId() == display.getDisplayId()) {
+                    if (device.getHostUsiVersion() != null) {
+                        return device.getHostUsiVersion();
+                    }
+                }
+            }
+        }
+
+        // If there are no input devices that report a valid USI version, see if there is a config
+        // that specifies the USI version for the display. This is to handle cases where the USI
+        // input device is not registered by the kernel/driver all the time.
+        try {
+            return mIm.getHostUsiVersionFromDisplayConfig(display.getDisplayId());
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    private void onTabletModeChanged(long whenNanos, boolean inTabletMode) {
+        if (DEBUG) {
+            Log.d(TAG, "Received tablet mode changed: "
+                    + "whenNanos=" + whenNanos + ", inTabletMode=" + inTabletMode);
+        }
+        synchronized (mOnTabletModeChangedListeners) {
+            final int numListeners = mOnTabletModeChangedListeners.size();
+            for (int i = 0; i < numListeners; i++) {
+                OnTabletModeChangedListenerDelegate listener =
+                        mOnTabletModeChangedListeners.get(i);
+                listener.sendTabletModeChanged(whenNanos, inTabletMode);
+            }
+        }
+    }
+
+    private final class TabletModeChangedListener extends ITabletModeChangedListener.Stub {
+        @Override
+        public void onTabletModeChanged(long whenNanos, boolean inTabletMode) {
+            InputManagerGlobal.this.onTabletModeChanged(whenNanos, inTabletMode);
+        }
+    }
+
+    private static final class OnTabletModeChangedListenerDelegate extends Handler {
+        private static final int MSG_TABLET_MODE_CHANGED = 0;
+
+        public final OnTabletModeChangedListener mListener;
+
+        OnTabletModeChangedListenerDelegate(
+                OnTabletModeChangedListener listener, Handler handler) {
+            super(handler != null ? handler.getLooper() : Looper.myLooper());
+            mListener = listener;
+        }
+
+        public void sendTabletModeChanged(long whenNanos, boolean inTabletMode) {
+            SomeArgs args = SomeArgs.obtain();
+            args.argi1 = (int) whenNanos;
+            args.argi2 = (int) (whenNanos >> 32);
+            args.arg1 = inTabletMode;
+            obtainMessage(MSG_TABLET_MODE_CHANGED, args).sendToTarget();
+        }
+
+        @Override
+        public void handleMessage(Message msg) {
+            if (msg.what == MSG_TABLET_MODE_CHANGED) {
+                SomeArgs args = (SomeArgs) msg.obj;
+                long whenNanos = (args.argi1 & 0xFFFFFFFFL) | ((long) args.argi2 << 32);
+                boolean inTabletMode = (boolean) args.arg1;
+                mListener.onTabletModeChanged(whenNanos, inTabletMode);
+            }
+        }
+    }
+
+    /**
+     * @see InputManager#registerInputDeviceListener(InputDeviceListener, Handler)
+     */
+    void registerOnTabletModeChangedListener(
+            OnTabletModeChangedListener listener, Handler handler) {
+        if (listener == null) {
+            throw new IllegalArgumentException("listener must not be null");
+        }
+        synchronized (mOnTabletModeChangedListeners) {
+            if (mOnTabletModeChangedListeners == null) {
+                initializeTabletModeListenerLocked();
+            }
+            int idx = findOnTabletModeChangedListenerLocked(listener);
+            if (idx < 0) {
+                OnTabletModeChangedListenerDelegate d =
+                        new OnTabletModeChangedListenerDelegate(listener, handler);
+                mOnTabletModeChangedListeners.add(d);
+            }
+        }
+    }
+
+    /**
+     * @see InputManager#unregisterOnTabletModeChangedListener(OnTabletModeChangedListener)
+     */
+    void unregisterOnTabletModeChangedListener(OnTabletModeChangedListener listener) {
+        if (listener == null) {
+            throw new IllegalArgumentException("listener must not be null");
+        }
+        synchronized (mOnTabletModeChangedListeners) {
+            int idx = findOnTabletModeChangedListenerLocked(listener);
+            if (idx >= 0) {
+                OnTabletModeChangedListenerDelegate d = mOnTabletModeChangedListeners.remove(idx);
+                d.removeCallbacksAndMessages(null);
+            }
+        }
+    }
+
+    @GuardedBy("mOnTabletModeChangedListeners")
+    private void initializeTabletModeListenerLocked() {
+        final TabletModeChangedListener listener = new TabletModeChangedListener();
+        try {
+            mIm.registerTabletModeChangedListener(listener);
+        } catch (RemoteException ex) {
+            throw ex.rethrowFromSystemServer();
+        }
+    }
+
+    @GuardedBy("mOnTabletModeChangedListeners")
+    private int findOnTabletModeChangedListenerLocked(OnTabletModeChangedListener listener) {
+        final int n = mOnTabletModeChangedListeners.size();
+        for (int i = 0; i < n; i++) {
+            if (mOnTabletModeChangedListeners.get(i).mListener == listener) {
+                return i;
+            }
+        }
+        return -1;
+    }
 }
diff --git a/core/java/android/hardware/soundtrigger/ConversionUtil.java b/core/java/android/hardware/soundtrigger/ConversionUtil.java
index 9a2cd06..58e7f01 100644
--- a/core/java/android/hardware/soundtrigger/ConversionUtil.java
+++ b/core/java/android/hardware/soundtrigger/ConversionUtil.java
@@ -34,14 +34,16 @@
 import android.media.soundtrigger.SoundModel;
 import android.media.soundtrigger_middleware.SoundTriggerModuleDescriptor;
 import android.os.ParcelFileDescriptor;
+import android.system.ErrnoException;
 import android.os.SharedMemory;
 
 import java.nio.ByteBuffer;
 import java.util.Arrays;
+import java.util.Locale;
 import java.util.UUID;
 
 /** @hide */
-class ConversionUtil {
+public class ConversionUtil {
     public static SoundTrigger.ModuleProperties aidl2apiModuleDescriptor(
             SoundTriggerModuleDescriptor aidlDesc) {
         Properties properties = aidlDesc.properties;
@@ -140,6 +142,14 @@
         return aidlPhrase;
     }
 
+    public static SoundTrigger.Keyphrase aidl2apiPhrase(Phrase aidlPhrase) {
+        return new SoundTrigger.Keyphrase(aidlPhrase.id,
+                aidl2apiRecognitionModes(aidlPhrase.recognitionModes),
+                new Locale.Builder().setLanguageTag(aidlPhrase.locale).build(),
+                aidlPhrase.text,
+                Arrays.copyOf(aidlPhrase.users, aidlPhrase.users.length));
+    }
+
     public static RecognitionConfig api2aidlRecognitionConfig(
             SoundTrigger.RecognitionConfig apiConfig) {
         RecognitionConfig aidlConfig = new RecognitionConfig();
@@ -156,6 +166,21 @@
         return aidlConfig;
     }
 
+    public static SoundTrigger.RecognitionConfig aidl2apiRecognitionConfig(
+            RecognitionConfig aidlConfig) {
+        var keyphrases =
+            new SoundTrigger.KeyphraseRecognitionExtra[aidlConfig.phraseRecognitionExtras.length];
+        int i = 0;
+        for (var extras : aidlConfig.phraseRecognitionExtras) {
+            keyphrases[i++] = aidl2apiPhraseRecognitionExtra(extras);
+        }
+        return new SoundTrigger.RecognitionConfig(aidlConfig.captureRequested,
+                false /** allowMultipleTriggers **/,
+                keyphrases,
+                Arrays.copyOf(aidlConfig.data, aidlConfig.data.length),
+                aidl2apiAudioCapabilities(aidlConfig.audioCapabilities));
+    }
+
     public static PhraseRecognitionExtra api2aidlPhraseRecognitionExtra(
             SoundTrigger.KeyphraseRecognitionExtra apiExtra) {
         PhraseRecognitionExtra aidlExtra = new PhraseRecognitionExtra();
@@ -233,7 +258,11 @@
         if (audioConfig != null) {
             return AidlConversion.aidl2api_AudioConfig_AudioFormat(audioConfig, isInput);
         }
-        return new AudioFormat.Builder().build();
+        return new AudioFormat.Builder()
+            .setSampleRate(48000)
+            .setEncoding(AudioFormat.ENCODING_PCM_16BIT)
+            .setChannelMask(AudioFormat.CHANNEL_IN_MONO)
+            .build();
     }
 
     public static int api2aidlModelParameter(int apiParam) {
@@ -277,7 +306,7 @@
         return result;
     }
 
-    private static @Nullable ParcelFileDescriptor byteArrayToSharedMemory(byte[] data, String name) {
+    public static @Nullable ParcelFileDescriptor byteArrayToSharedMemory(byte[] data, String name) {
         if (data.length == 0) {
             return null;
         }
@@ -294,4 +323,19 @@
             throw new RuntimeException(e);
         }
     }
+
+    public static byte[] sharedMemoryToByteArray(@Nullable ParcelFileDescriptor pfd, int size) {
+        if (pfd == null || size == 0) {
+            return new byte[0];
+        }
+        try (SharedMemory mem = SharedMemory.fromFileDescriptor(pfd)) {
+            ByteBuffer buffer = mem.mapReadOnly();
+            byte[] data = new byte[(size > mem.getSize()) ? mem.getSize() : size];
+            buffer.get(data);
+            mem.unmap(buffer);
+            return data;
+        } catch (ErrnoException e) {
+            throw new RuntimeException(e);
+        }
+    }
 }
diff --git a/core/java/android/hardware/soundtrigger/SoundTrigger.java b/core/java/android/hardware/soundtrigger/SoundTrigger.java
index b7a694c..5593989 100644
--- a/core/java/android/hardware/soundtrigger/SoundTrigger.java
+++ b/core/java/android/hardware/soundtrigger/SoundTrigger.java
@@ -79,6 +79,11 @@
     }
 
     /**
+     * @hide
+     */
+    public static final String FAKE_HAL_ARCH = "injection";
+
+    /**
      * Status code used when the operation succeeded
      */
     public static final int STATUS_OK = 0;
@@ -1493,6 +1498,45 @@
                     + Arrays.toString(keyphrases) + ", data=" + Arrays.toString(data)
                     + ", audioCapabilities=" + Integer.toHexString(audioCapabilities) + "]";
         }
+
+        @Override
+        public final boolean equals(Object obj) {
+            if (this == obj)
+                return true;
+            if (obj == null)
+                return false;
+            if (!(obj instanceof RecognitionConfig))
+                return false;
+            RecognitionConfig other = (RecognitionConfig) obj;
+            if (captureRequested != other.captureRequested) {
+                return false;
+            }
+            if (allowMultipleTriggers != other.allowMultipleTriggers) {
+                return false;
+            }
+            if (!Arrays.equals(keyphrases, other.keyphrases)) {
+                return false;
+            }
+            if (!Arrays.equals(data, other.data)) {
+                return false;
+            }
+            if (audioCapabilities != other.audioCapabilities) {
+                return false;
+            }
+            return true;
+        }
+
+        @Override
+        public final int hashCode() {
+            final int prime = 31;
+            int result = 1;
+            result = prime * result + (captureRequested ? 1 : 0);
+            result = prime * result + (allowMultipleTriggers ? 1 : 0);
+            result = prime * result + Arrays.hashCode(keyphrases);
+            result = prime * result + Arrays.hashCode(data);
+            result = prime * result + audioCapabilities;
+            return result;
+        }
     }
 
     /**
diff --git a/core/java/android/os/Build.java b/core/java/android/os/Build.java
index 9689be2..244632a 100755
--- a/core/java/android/os/Build.java
+++ b/core/java/android/os/Build.java
@@ -64,17 +64,27 @@
     /**
      * The product name for attestation. In non-default builds (like the AOSP build) the value of
      * the 'PRODUCT' system property may be different to the one provisioned to KeyMint,
-     * and Keymint attestation would still attest to the product name, it's running on.
+     * and Keymint attestation would still attest to the product name which was provisioned.
      * @hide
      */
     @Nullable
     @TestApi
-    public static final String PRODUCT_FOR_ATTESTATION =
-            getString("ro.product.name_for_attestation");
+    public static final String PRODUCT_FOR_ATTESTATION = getVendorDeviceIdProperty("name");
 
     /** The name of the industrial design. */
     public static final String DEVICE = getString("ro.product.device");
 
+    /**
+     * The device name for attestation. In non-default builds (like the AOSP build) the value of
+     * the 'DEVICE' system property may be different to the one provisioned to KeyMint,
+     * and Keymint attestation would still attest to the device name which was provisioned.
+     * @hide
+     */
+    @Nullable
+    @TestApi
+    public static final String DEVICE_FOR_ATTESTATION =
+            getVendorDeviceIdProperty("device");
+
     /** The name of the underlying board, like "goldfish". */
     public static final String BOARD = getString("ro.product.board");
 
@@ -97,19 +107,29 @@
     /** The manufacturer of the product/hardware. */
     public static final String MANUFACTURER = getString("ro.product.manufacturer");
 
+    /**
+     * The manufacturer name for attestation. In non-default builds (like the AOSP build) the value
+     * of the 'MANUFACTURER' system property may be different to the one provisioned to KeyMint,
+     * and Keymint attestation would still attest to the manufacturer which was provisioned.
+     * @hide
+     */
+    @Nullable
+    @TestApi
+    public static final String MANUFACTURER_FOR_ATTESTATION =
+            getVendorDeviceIdProperty("manufacturer");
+
     /** The consumer-visible brand with which the product/hardware will be associated, if any. */
     public static final String BRAND = getString("ro.product.brand");
 
     /**
      * The product brand for attestation. In non-default builds (like the AOSP build) the value of
      * the 'BRAND' system property may be different to the one provisioned to KeyMint,
-     * and Keymint attestation would still attest to the product brand, it's running on.
+     * and Keymint attestation would still attest to the product brand which was provisioned.
      * @hide
      */
     @Nullable
     @TestApi
-    public static final String BRAND_FOR_ATTESTATION =
-                getString("ro.product.brand_for_attestation");
+    public static final String BRAND_FOR_ATTESTATION = getVendorDeviceIdProperty("brand");
 
     /** The end-user-visible name for the end product. */
     public static final String MODEL = getString("ro.product.model");
@@ -117,13 +137,12 @@
     /**
      * The product model for attestation. In non-default builds (like the AOSP build) the value of
      * the 'MODEL' system property may be different to the one provisioned to KeyMint,
-     * and Keymint attestation would still attest to the product model, it's running on.
+     * and Keymint attestation would still attest to the product model which was provisioned.
      * @hide
      */
     @Nullable
     @TestApi
-    public static final String MODEL_FOR_ATTESTATION =
-                getString("ro.product.model_for_attestation");
+    public static final String MODEL_FOR_ATTESTATION = getVendorDeviceIdProperty("model");
 
     /** The manufacturer of the device's primary system-on-chip. */
     @NonNull
@@ -1527,6 +1546,17 @@
     private static String getString(String property) {
         return SystemProperties.get(property, UNKNOWN);
     }
+    /**
+     * Return attestation specific proerties.
+     * @param property model, name, brand, device or manufacturer.
+     * @return property value or UNKNOWN
+     */
+    private static String getVendorDeviceIdProperty(String property) {
+        String attestProp = getString(
+                TextUtils.formatSimple("ro.product.%s_for_attestation", property));
+        return attestProp.equals(UNKNOWN)
+                ? getString(TextUtils.formatSimple("ro.product.vendor.%s", property)) : UNKNOWN;
+    }
 
     private static String[] getStringList(String property, String separator) {
         String value = SystemProperties.get(property);
diff --git a/core/java/android/os/Parcelable.java b/core/java/android/os/Parcelable.java
index a2b0486..f2b60a4 100644
--- a/core/java/android/os/Parcelable.java
+++ b/core/java/android/os/Parcelable.java
@@ -16,8 +16,8 @@
 
 package android.os;
 
-import android.annotation.NonNull;
 import android.annotation.IntDef;
+import android.annotation.NonNull;
 import android.annotation.SystemApi;
 
 import java.lang.annotation.Retention;
@@ -26,8 +26,9 @@
 /**
  * Interface for classes whose instances can be written to
  * and restored from a {@link Parcel}.  Classes implementing the Parcelable
- * interface must also have a non-null static field called <code>CREATOR</code>
- * of a type that implements the {@link Parcelable.Creator} interface.
+ * interface must also have a non-null public static field called
+ * <code>CREATOR</code> of a type that implements the {@link Parcelable.Creator}
+ * interface.
  *
  * <p>A typical implementation of Parcelable is:</p>
  *
diff --git a/core/java/android/os/Process.java b/core/java/android/os/Process.java
index ac1583a..b2208d1 100644
--- a/core/java/android/os/Process.java
+++ b/core/java/android/os/Process.java
@@ -365,6 +365,11 @@
     public static final int LAST_APPLICATION_CACHE_GID = 29999;
 
     /**
+     * An invalid PID value.
+     */
+    public static final int INVALID_PID = -1;
+
+    /**
      * Standard priority of application threads.
      * Use with {@link #setThreadPriority(int)} and
      * {@link #setThreadPriority(int, int)}, <b>not</b> with the normal
diff --git a/core/java/android/service/dreams/DreamOverlayService.java b/core/java/android/service/dreams/DreamOverlayService.java
index 6e4535b..5469916 100644
--- a/core/java/android/service/dreams/DreamOverlayService.java
+++ b/core/java/android/service/dreams/DreamOverlayService.java
@@ -27,6 +27,8 @@
 import android.util.Log;
 import android.view.WindowManager;
 
+import java.util.concurrent.Executor;
+
 
 /**
  * Basic implementation of for {@link IDreamOverlay} for testing.
@@ -40,6 +42,12 @@
     // The last client that started dreaming and hasn't ended
     private OverlayClient mCurrentClient;
 
+    /**
+     * Executor used to run callbacks that subclasses will implement. Any calls coming over Binder
+     * from {@link OverlayClient} should perform the work they need to do on this executor.
+     */
+    private Executor mExecutor;
+
     // An {@link IDreamOverlayClient} implementation that identifies itself when forwarding
     // requests to the {@link DreamOverlayService}
     private static class OverlayClient extends IDreamOverlayClient.Stub {
@@ -61,8 +69,6 @@
             mService.startDream(this, params);
         }
 
-
-
         @Override
         public void wakeUp() {
             mService.wakeUp(this, () -> {
@@ -97,12 +103,20 @@
     }
 
     private void startDream(OverlayClient client, WindowManager.LayoutParams params) {
-        endDream(mCurrentClient);
-        mCurrentClient = client;
-        onStartDream(params);
+        // Run on executor as this is a binder call from OverlayClient.
+        mExecutor.execute(() -> {
+            endDreamInternal(mCurrentClient);
+            mCurrentClient = client;
+            onStartDream(params);
+        });
     }
 
     private void endDream(OverlayClient client) {
+        // Run on executor as this is a binder call from OverlayClient.
+        mExecutor.execute(() -> endDreamInternal(client));
+    }
+
+    private void endDreamInternal(OverlayClient client) {
         if (client == null || client != mCurrentClient) {
             return;
         }
@@ -112,11 +126,14 @@
     }
 
     private void wakeUp(OverlayClient client, Runnable callback) {
-        if (mCurrentClient != client) {
-            return;
-        }
+        // Run on executor as this is a binder call from OverlayClient.
+        mExecutor.execute(() -> {
+            if (mCurrentClient != client) {
+                return;
+            }
 
-        onWakeUp(callback);
+            onWakeUp(callback);
+        });
     }
 
     private IDreamOverlay mDreamOverlay = new IDreamOverlay.Stub() {
@@ -134,6 +151,25 @@
     public DreamOverlayService() {
     }
 
+    /**
+     * This constructor allows providing an executor to run callbacks on.
+     *
+     * @hide
+     */
+    public DreamOverlayService(@NonNull Executor executor) {
+        mExecutor = executor;
+    }
+
+    @Override
+    public void onCreate() {
+        super.onCreate();
+        if (mExecutor == null) {
+            // If no executor was provided, use the main executor. onCreate is the earliest time
+            // getMainExecutor is available.
+            mExecutor = getMainExecutor();
+        }
+    }
+
     @Nullable
     @Override
     public final IBinder onBind(@NonNull Intent intent) {
@@ -143,6 +179,10 @@
     /**
      * This method is overridden by implementations to handle when the dream has started and the
      * window is ready to be interacted with.
+     *
+     * This callback will be run on the {@link Executor} provided in the constructor if provided, or
+     * on the main executor if none was provided.
+     *
      * @param layoutParams The {@link android.view.WindowManager.LayoutParams} associated with the
      *                     dream window.
      */
@@ -153,6 +193,9 @@
      * to wakeup. This allows any overlay animations to run. By default, the method will invoke
      * the callback immediately.
      *
+     * This callback will be run on the {@link Executor} provided in the constructor if provided, or
+     * on the main executor if none was provided.
+     *
      * @param onCompleteCallback The callback to trigger to notify the dream service that the
      *                           overlay has completed waking up.
      * @hide
@@ -164,6 +207,9 @@
     /**
      * This method is overridden by implementations to handle when the dream has ended. There may
      * be earlier signals leading up to this step, such as @{@link #onWakeUp(Runnable)}.
+     *
+     * This callback will be run on the {@link Executor} provided in the constructor if provided, or
+     * on the main executor if none was provided.
      */
     public void onEndDream() {
     }
diff --git a/core/java/android/service/voice/AlwaysOnHotwordDetector.java b/core/java/android/service/voice/AlwaysOnHotwordDetector.java
index b1dc686..2830fb7 100644
--- a/core/java/android/service/voice/AlwaysOnHotwordDetector.java
+++ b/core/java/android/service/voice/AlwaysOnHotwordDetector.java
@@ -805,11 +805,13 @@
             Identity identity = new Identity();
             identity.packageName = ActivityThread.currentOpPackageName();
             if (moduleProperties == null) {
-                List<SoundTrigger.ModuleProperties> modulePropList =
-                        mModelManagementService.listModuleProperties(identity);
-                if (modulePropList.size() > 0) {
-                    moduleProperties = modulePropList.get(0);
-                }
+                moduleProperties = mModelManagementService
+                        .listModuleProperties(identity)
+                        .stream()
+                        .filter(prop -> !prop.getSupportedModelArch()
+                                .equals(SoundTrigger.FAKE_HAL_ARCH))
+                        .findFirst()
+                        .orElse(null);
                 // (@atneya) intentionally let a null moduleProperties through until
                 // all CTS tests are fixed
             }
diff --git a/core/java/android/speech/IRecognitionService.aidl b/core/java/android/speech/IRecognitionService.aidl
index 3134dcd..1148fe3 100644
--- a/core/java/android/speech/IRecognitionService.aidl
+++ b/core/java/android/speech/IRecognitionService.aidl
@@ -78,23 +78,10 @@
      * information see {@link #checkRecognitionSupport},  {@link #startListening} and
      * {@link RecognizerIntent}.
      *
-     * Progress can be monitord by calling {@link #setModelDownloadListener} before a trigger.
+     * Progress updates can be received via {@link #IModelDownloadListener}.
      */
-    void triggerModelDownload(in Intent recognizerIntent, in AttributionSource attributionSource);
-
-    /**
-     * Sets listener to received download progress updates. Clients still have to call
-     * {@link #triggerModelDownload} to trigger a model download.
-     */
-    void setModelDownloadListener(
+    void triggerModelDownload(
         in Intent recognizerIntent,
         in AttributionSource attributionSource,
         in IModelDownloadListener listener);
-
-    /**
-     * Clears the listener for model download events attached to a recognitionIntent if any.
-     */
-    void clearModelDownloadListener(
-        in Intent recognizerIntent,
-        in AttributionSource attributionSource);
 }
diff --git a/core/java/android/speech/ModelDownloadListener.java b/core/java/android/speech/ModelDownloadListener.java
index 6c24399..a58ec90c 100644
--- a/core/java/android/speech/ModelDownloadListener.java
+++ b/core/java/android/speech/ModelDownloadListener.java
@@ -22,20 +22,27 @@
  */
 public interface ModelDownloadListener {
     /**
-     * Called by {@link RecognitionService} when there's an update on the download progress.
+     * Called by {@link RecognitionService} only if the download has started after the request.
      *
-     * <p>RecognitionService will call this zero or more times during the download.</p>
+     * <p> The number of calls to this method varies depending of the {@link RecognitionService}
+     * implementation. If the download finished quickly enough, {@link #onSuccess()} may be called
+     * directly. In other cases, this method may be called any number of times during the download.
+     *
+     * @param completedPercent the percentage of download that is completed
      */
     void onProgress(int completedPercent);
 
     /**
-     * Called when {@link RecognitionService} completed the download and it can now be used to
-     * satisfy recognition requests.
+     * This method is called:
+     * <li> if the model is already available;
+     * <li> if the {@link RecognitionService} has started and completed the download.
+     *
+     * <p> Once this method is called, the model can be safely used to satisfy recognition requests.
      */
     void onSuccess();
 
     /**
-     * Called when {@link RecognitionService} scheduled the download but won't satisfy it
+     * Called when {@link RecognitionService} scheduled the download, but won't satisfy it
      * immediately. There will be no further updates on this listener.
      */
     void onScheduled();
diff --git a/core/java/android/speech/RecognitionService.java b/core/java/android/speech/RecognitionService.java
index 4ecec8f..9656f36 100644
--- a/core/java/android/speech/RecognitionService.java
+++ b/core/java/android/speech/RecognitionService.java
@@ -36,9 +36,7 @@
 import android.os.Looper;
 import android.os.Message;
 import android.os.RemoteException;
-import android.text.TextUtils;
 import android.util.Log;
-import android.util.Pair;
 
 import com.android.internal.util.function.pooled.PooledLambda;
 
@@ -93,10 +91,6 @@
 
     private static final int MSG_TRIGGER_MODEL_DOWNLOAD = 6;
 
-    private static final int MSG_SET_MODEL_DOWNLOAD_LISTENER = 7;
-
-    private static final int MSG_CLEAR_MODEL_DOWNLOAD_LISTENER = 8;
-
     private final Handler mHandler = new Handler() {
         @Override
         public void handleMessage(Message msg) {
@@ -120,21 +114,11 @@
                             checkArgs.mIntent, checkArgs.callback, checkArgs.mAttributionSource);
                     break;
                 case MSG_TRIGGER_MODEL_DOWNLOAD:
-                    Pair<Intent, AttributionSource> params =
-                            (Pair<Intent, AttributionSource>) msg.obj;
-                    dispatchTriggerModelDownload(params.first, params.second);
-                    break;
-                case MSG_SET_MODEL_DOWNLOAD_LISTENER:
-                    ModelDownloadListenerArgs dListenerArgs = (ModelDownloadListenerArgs) msg.obj;
-                    dispatchSetModelDownloadListener(
-                            dListenerArgs.mIntent,
-                            dListenerArgs.mListener,
-                            dListenerArgs.mAttributionSource);
-                    break;
-                case MSG_CLEAR_MODEL_DOWNLOAD_LISTENER:
-                    Pair<Intent, AttributionSource> clearDlPair =
-                            (Pair<Intent, AttributionSource>) msg.obj;
-                    dispatchClearModelDownloadListener(clearDlPair.first, clearDlPair.second);
+                    ModelDownloadArgs modelDownloadArgs = (ModelDownloadArgs) msg.obj;
+                    dispatchTriggerModelDownload(
+                            modelDownloadArgs.mIntent,
+                            modelDownloadArgs.mAttributionSource,
+                            modelDownloadArgs.mListener);
                     break;
             }
         }
@@ -239,59 +223,52 @@
 
     private void dispatchTriggerModelDownload(
             Intent intent,
-            AttributionSource attributionSource) {
-        RecognitionService.this.onTriggerModelDownload(intent, attributionSource);
-    }
-
-    private void dispatchSetModelDownloadListener(
-            Intent intent,
-            IModelDownloadListener listener,
-            AttributionSource attributionSource) {
-        RecognitionService.this.setModelDownloadListener(
-                intent,
-                attributionSource,
-                new ModelDownloadListener() {
-                    @Override
-                    public void onProgress(int completedPercent) {
-                        try {
-                            listener.onProgress(completedPercent);
-                        } catch (RemoteException e) {
-                            throw e.rethrowFromSystemServer();
+            AttributionSource attributionSource,
+            IModelDownloadListener listener) {
+        if (listener == null) {
+            RecognitionService.this.onTriggerModelDownload(intent, attributionSource);
+        } else {
+            RecognitionService.this.onTriggerModelDownload(
+                    intent,
+                    attributionSource,
+                    new ModelDownloadListener() {
+                        @Override
+                        public void onProgress(int completedPercent) {
+                            try {
+                                listener.onProgress(completedPercent);
+                            } catch (RemoteException e) {
+                                throw e.rethrowFromSystemServer();
+                            }
                         }
-                    }
 
-                    @Override
-                    public void onSuccess() {
-                        try {
-                            listener.onSuccess();
-                        } catch (RemoteException e) {
-                            throw e.rethrowFromSystemServer();
+                        @Override
+                        public void onSuccess() {
+                            try {
+                                listener.onSuccess();
+                            } catch (RemoteException e) {
+                                throw e.rethrowFromSystemServer();
+                            }
                         }
-                    }
 
-                    @Override
-                    public void onScheduled() {
-                        try {
-                            listener.onScheduled();
-                        } catch (RemoteException e) {
-                            throw e.rethrowFromSystemServer();
+                        @Override
+                        public void onScheduled() {
+                            try {
+                                listener.onScheduled();
+                            } catch (RemoteException e) {
+                                throw e.rethrowFromSystemServer();
+                            }
                         }
-                    }
 
-                    @Override
-                    public void onError(int error) {
-                        try {
-                            listener.onError(error);
-                        } catch (RemoteException e) {
-                            throw e.rethrowFromSystemServer();
+                        @Override
+                        public void onError(int error) {
+                            try {
+                                listener.onError(error);
+                            } catch (RemoteException e) {
+                                throw e.rethrowFromSystemServer();
+                            }
                         }
-                    }
-                });
-    }
-
-    private void dispatchClearModelDownloadListener(
-            Intent intent, AttributionSource attributionSource) {
-        RecognitionService.this.clearModelDownloadListener(intent, attributionSource);
+                    });
+        }
     }
 
     private static class StartListeningArgs {
@@ -323,17 +300,18 @@
         }
     }
 
-    private static class ModelDownloadListenerArgs {
+    private static class ModelDownloadArgs {
         final Intent mIntent;
-        final IModelDownloadListener mListener;
         final AttributionSource mAttributionSource;
+        @Nullable final IModelDownloadListener mListener;
 
-        private ModelDownloadListenerArgs(Intent intent,
-                IModelDownloadListener listener,
-                AttributionSource attributionSource) {
-            mIntent = intent;
+        private ModelDownloadArgs(
+                Intent intent,
+                AttributionSource attributionSource,
+                @Nullable IModelDownloadListener listener) {
+            this.mIntent = intent;
+            this.mAttributionSource = attributionSource;
             this.mListener = listener;
-            mAttributionSource = attributionSource;
         }
     }
 
@@ -443,38 +421,39 @@
     }
 
     /**
-     * Sets a {@link ModelDownloadListener} to receive progress updates after
-     * {@link #onTriggerModelDownload} calls.
+     * Requests the download of the recognizer support for {@code recognizerIntent}.
      *
-     * @param recognizerIntent the request to monitor model download progress for.
-     * @param modelDownloadListener the listener to keep updated.
+     * <p> Provides the calling {@link AttributionSource} to the service implementation so that
+     * permissions and bandwidth could be correctly blamed.
+     *
+     * <p> Client will receive the progress updates via the given {@link ModelDownloadListener}:
+     *
+     * <li> If the model is already available, {@link ModelDownloadListener#onSuccess()} will be
+     * called directly. The model can be safely used afterwards.
+     *
+     * <li> If the {@link RecognitionService} has started the download,
+     * {@link ModelDownloadListener#onProgress(int)} will be called an unspecified (zero or more)
+     * number of times until the download is complete.
+     * When the download finishes, {@link ModelDownloadListener#onSuccess()} will be called.
+     * The model can be safely used afterwards.
+     *
+     * <li> If the {@link RecognitionService} has only scheduled the download, but won't satisfy it
+     * immediately, {@link ModelDownloadListener#onScheduled()} will be called.
+     * There will be no further updates on this listener.
+     *
+     * <li> If the request fails at any time due to a network or scheduling error,
+     * {@link ModelDownloadListener#onError(int)} will be called.
+     *
+     * @param recognizerIntent contains parameters for the recognition to be performed. The intent
+     *        may also contain optional extras, see {@link RecognizerIntent}.
+     * @param attributionSource the attribution source of the caller.
+     * @param listener on which to receive updates about the model download request.
      */
-    public void setModelDownloadListener(
+    public void onTriggerModelDownload(
             @NonNull Intent recognizerIntent,
             @NonNull AttributionSource attributionSource,
-            @NonNull ModelDownloadListener modelDownloadListener) {
-        if (DBG) {
-            Log.i(TAG, TextUtils.formatSimple(
-                    "#setModelDownloadListener [%s] [%s]",
-                    recognizerIntent,
-                    modelDownloadListener));
-        }
-        modelDownloadListener.onError(SpeechRecognizer.ERROR_CANNOT_LISTEN_TO_DOWNLOAD_EVENTS);
-    }
-
-    /**
-     * Clears the {@link ModelDownloadListener} set to receive progress updates for the given
-     * {@code recognizerIntent}, if any.
-     *
-     * @param recognizerIntent the request to monitor model download progress for.
-     */
-    public void clearModelDownloadListener(
-            @NonNull Intent recognizerIntent,
-            @NonNull AttributionSource attributionSource) {
-        if (DBG) {
-            Log.i(TAG, TextUtils.formatSimple(
-                    "#clearModelDownloadListener [%s]", recognizerIntent));
-        }
+            @NonNull ModelDownloadListener listener) {
+        listener.onError(SpeechRecognizer.ERROR_CANNOT_LISTEN_TO_DOWNLOAD_EVENTS);
     }
 
     @Override
@@ -815,41 +794,18 @@
 
         @Override
         public void triggerModelDownload(
-                Intent recognizerIntent, @NonNull AttributionSource attributionSource) {
+                Intent recognizerIntent,
+                @NonNull AttributionSource attributionSource,
+                IModelDownloadListener listener) {
             final RecognitionService service = mServiceRef.get();
             if (service != null) {
                 service.mHandler.sendMessage(
                         Message.obtain(
                                 service.mHandler, MSG_TRIGGER_MODEL_DOWNLOAD,
-                                Pair.create(recognizerIntent, attributionSource)));
-            }
-        }
-
-        @Override
-        public void setModelDownloadListener(
-                Intent recognizerIntent,
-                AttributionSource attributionSource,
-                IModelDownloadListener listener) throws RemoteException {
-            final RecognitionService service = mServiceRef.get();
-            if (service != null) {
-                service.mHandler.sendMessage(
-                        Message.obtain(service.mHandler, MSG_SET_MODEL_DOWNLOAD_LISTENER,
-                                new ModelDownloadListenerArgs(
+                                new ModelDownloadArgs(
                                         recognizerIntent,
-                                        listener,
-                                        attributionSource)));
-            }
-        }
-
-        @Override
-        public void clearModelDownloadListener(
-                Intent recognizerIntent,
-                AttributionSource attributionSource) throws RemoteException {
-            final RecognitionService service = mServiceRef.get();
-            if (service != null) {
-                service.mHandler.sendMessage(
-                        Message.obtain(service.mHandler, MSG_CLEAR_MODEL_DOWNLOAD_LISTENER,
-                                Pair.create(recognizerIntent, attributionSource)));
+                                        attributionSource,
+                                        listener)));
             }
         }
 
diff --git a/core/java/android/speech/SpeechRecognizer.java b/core/java/android/speech/SpeechRecognizer.java
index 76eb09e..dacb25c 100644
--- a/core/java/android/speech/SpeechRecognizer.java
+++ b/core/java/android/speech/SpeechRecognizer.java
@@ -297,8 +297,6 @@
     private static final int MSG_SET_TEMPORARY_ON_DEVICE_COMPONENT = 5;
     private static final int MSG_CHECK_RECOGNITION_SUPPORT = 6;
     private static final int MSG_TRIGGER_MODEL_DOWNLOAD = 7;
-    private static final int MSG_SET_MODEL_DOWNLOAD_LISTENER = 8;
-    private static final int MSG_CLEAR_MODEL_DOWNLOAD_LISTENER = 9;
 
     /** The actual RecognitionService endpoint */
     private IRecognitionService mService;
@@ -341,19 +339,13 @@
                             args.mIntent, args.mCallbackExecutor, args.mCallback);
                     break;
                 case MSG_TRIGGER_MODEL_DOWNLOAD:
-                    handleTriggerModelDownload((Intent) msg.obj);
-                    break;
-                case MSG_SET_MODEL_DOWNLOAD_LISTENER:
                     ModelDownloadListenerArgs modelDownloadListenerArgs =
                             (ModelDownloadListenerArgs) msg.obj;
-                    handleSetModelDownloadListener(
+                    handleTriggerModelDownload(
                             modelDownloadListenerArgs.mIntent,
                             modelDownloadListenerArgs.mExecutor,
                             modelDownloadListenerArgs.mModelDownloadListener);
                     break;
-                case MSG_CLEAR_MODEL_DOWNLOAD_LISTENER:
-                    handleClearModelDownloadListener((Intent) msg.obj);
-                    break;
             }
         }
     };
@@ -657,17 +649,13 @@
      * user interaction to approve the download. Callers can verify the status of the request via
      * {@link #checkRecognitionSupport(Intent, Executor, RecognitionSupportCallback)}.
      *
-     * <p>Listeners set via
-     * {@link #setModelDownloadListener(Intent, Executor, ModelDownloadListener)} will receive
-     * updates about this download request.</p>
-     *
      * @param recognizerIntent contains parameters for the recognition to be performed. The intent
      *        may also contain optional extras, see {@link RecognizerIntent}.
      */
     public void triggerModelDownload(@NonNull Intent recognizerIntent) {
         Objects.requireNonNull(recognizerIntent, "intent must not be null");
         if (DBG) {
-            Slog.i(TAG, "#triggerModelDownload called");
+            Slog.i(TAG, "#triggerModelDownload without a listener called");
             if (mService == null) {
                 Slog.i(TAG, "Connection is not established yet");
             }
@@ -676,23 +664,47 @@
             // First time connection: first establish a connection, then dispatch.
             connectToSystemService();
         }
-        putMessage(Message.obtain(mHandler, MSG_TRIGGER_MODEL_DOWNLOAD, recognizerIntent));
+        putMessage(Message.obtain(
+                mHandler, MSG_TRIGGER_MODEL_DOWNLOAD,
+                new ModelDownloadListenerArgs(recognizerIntent, null, null)));
     }
 
     /**
-     * Sets a listener to model download updates. Clients will have to call this method before
-     * {@link #triggerModelDownload(Intent)}.
+     * Attempts to download the support for the given {@code recognizerIntent}. This might trigger
+     * user interaction to approve the download. Callers can verify the status of the request via
+     * {@link #checkRecognitionSupport(Intent, Executor, RecognitionSupportCallback)}.
      *
-     * @param recognizerIntent the request to monitor support for.
+     * <p> The updates about the model download request are received via the given
+     * {@link ModelDownloadListener}:
+     *
+     * <li> If the model is already available, {@link ModelDownloadListener#onSuccess()} will be
+     * called directly. The model can be safely used afterwards.
+     *
+     * <li> If the {@link RecognitionService} has started the download,
+     * {@link ModelDownloadListener#onProgress(int)} will be called an unspecified (zero or more)
+     * number of times until the download is complete.
+     * When the download finishes, {@link ModelDownloadListener#onSuccess()} will be called.
+     * The model can be safely used afterwards.
+     *
+     * <li> If the {@link RecognitionService} has only scheduled the download, but won't satisfy it
+     * immediately, {@link ModelDownloadListener#onScheduled()} will be called.
+     * There will be no further updates on this listener.
+     *
+     * <li> If the request fails at any time due to a network or scheduling error,
+     * {@link ModelDownloadListener#onError(int)} will be called.
+     *
+     * @param recognizerIntent contains parameters for the recognition to be performed. The intent
+     *        may also contain optional extras, see {@link RecognizerIntent}.
+     * @param executor for dispatching listener callbacks
      * @param listener on which to receive updates about the model download request.
      */
-    public void setModelDownloadListener(
+    public void triggerModelDownload(
             @NonNull Intent recognizerIntent,
             @NonNull @CallbackExecutor Executor executor,
             @NonNull ModelDownloadListener listener) {
         Objects.requireNonNull(recognizerIntent, "intent must not be null");
         if (DBG) {
-            Slog.i(TAG, "#setModelDownloadListener called");
+            Slog.i(TAG, "#triggerModelDownload with a listener called");
             if (mService == null) {
                 Slog.i(TAG, "Connection is not established yet");
             }
@@ -702,32 +714,11 @@
             connectToSystemService();
         }
         putMessage(Message.obtain(
-                mHandler, MSG_SET_MODEL_DOWNLOAD_LISTENER,
+                mHandler, MSG_TRIGGER_MODEL_DOWNLOAD,
                 new ModelDownloadListenerArgs(recognizerIntent, executor, listener)));
     }
 
     /**
-     * Clears the listener for model download updates if any.
-     *
-     * @param recognizerIntent the request to monitor support for.
-     */
-    public void clearModelDownloadListener(@NonNull Intent recognizerIntent) {
-        Objects.requireNonNull(recognizerIntent, "intent must not be null");
-        if (DBG) {
-            Slog.i(TAG, "#clearModelDownloadListener called");
-            if (mService == null) {
-                Slog.i(TAG, "Connection is not established yet");
-            }
-        }
-        if (mService == null) {
-            // First time connection: first establish a connection, then dispatch.
-            connectToSystemService();
-        }
-        putMessage(Message.obtain(
-                mHandler, MSG_CLEAR_MODEL_DOWNLOAD_LISTENER, recognizerIntent));
-    }
-
-    /**
      * Sets a temporary component to power on-device speech recognizer.
      *
      * <p>This is only expected to be called in tests, system would reject calls from client apps.
@@ -836,51 +827,36 @@
         }
     }
 
-    private void handleTriggerModelDownload(Intent recognizerIntent) {
-        if (!maybeInitializeManagerService()) {
-            return;
-        }
-        try {
-            mService.triggerModelDownload(recognizerIntent, mContext.getAttributionSource());
-        } catch (final RemoteException e) {
-            Log.e(TAG, "downloadModel() failed", e);
-            mListener.onError(ERROR_CLIENT);
-        }
-    }
-
-    private void handleSetModelDownloadListener(
+    private void handleTriggerModelDownload(
             Intent recognizerIntent,
-            Executor callbackExecutor,
+            @Nullable Executor callbackExecutor,
             @Nullable ModelDownloadListener modelDownloadListener) {
         if (!maybeInitializeManagerService()) {
             return;
         }
-        try {
-            InternalModelDownloadListener listener =
-                    modelDownloadListener == null
-                            ? null
-                            : new InternalModelDownloadListener(
-                                    callbackExecutor,
-                                    modelDownloadListener);
-            mService.setModelDownloadListener(
-                    recognizerIntent, mContext.getAttributionSource(), listener);
-            if (DBG) Log.d(TAG, "setModelDownloadListener()");
-        } catch (final RemoteException e) {
-            Log.e(TAG, "setModelDownloadListener() failed", e);
-            callbackExecutor.execute(() -> modelDownloadListener.onError(ERROR_CLIENT));
-        }
-    }
 
-    private void handleClearModelDownloadListener(Intent recognizerIntent) {
-        if (!maybeInitializeManagerService()) {
-            return;
+        // Trigger model download without a listener.
+        if (modelDownloadListener == null) {
+            try {
+                mService.triggerModelDownload(
+                        recognizerIntent, mContext.getAttributionSource(), null);
+                if (DBG) Log.d(TAG, "triggerModelDownload() without a listener");
+            } catch (final RemoteException e) {
+                Log.e(TAG, "triggerModelDownload() without a listener failed", e);
+                mListener.onError(ERROR_CLIENT);
+            }
         }
-        try {
-            mService.clearModelDownloadListener(
-                    recognizerIntent, mContext.getAttributionSource());
-            if (DBG) Log.d(TAG, "clearModelDownloadListener()");
-        } catch (final RemoteException e) {
-            Log.e(TAG, "clearModelDownloadListener() failed", e);
+        // Trigger model download with a listener.
+        else {
+            try {
+                mService.triggerModelDownload(
+                        recognizerIntent, mContext.getAttributionSource(),
+                        new InternalModelDownloadListener(callbackExecutor, modelDownloadListener));
+                if (DBG) Log.d(TAG, "triggerModelDownload() with a listener");
+            } catch (final RemoteException e) {
+                Log.e(TAG, "triggerModelDownload() with a listener failed", e);
+                callbackExecutor.execute(() -> modelDownloadListener.onError(ERROR_CLIENT));
+            }
         }
     }
 
diff --git a/core/java/android/text/method/QwertyKeyListener.java b/core/java/android/text/method/QwertyKeyListener.java
index b4a1e8c..c43864d 100644
--- a/core/java/android/text/method/QwertyKeyListener.java
+++ b/core/java/android/text/method/QwertyKeyListener.java
@@ -362,6 +362,15 @@
 
                 return true;
             }
+        } else if (keyCode == KeyEvent.KEYCODE_ESCAPE && event.hasNoModifiers()) {
+            // If user is in the process of composing with a dead key, and
+            // presses Escape, cancel it. We need special handling because
+            // the Escape key will not produce a Unicode character
+            if (activeStart == selStart && activeEnd == selEnd) {
+                Selection.setSelection(content, selEnd);
+                content.removeSpan(TextKeyListener.ACTIVE);
+                return true;
+            }
         }
 
         return super.onKeyDown(view, content, keyCode, event);
diff --git a/core/java/android/view/HandwritingInitiator.java b/core/java/android/view/HandwritingInitiator.java
index fa92612..a47783c 100644
--- a/core/java/android/view/HandwritingInitiator.java
+++ b/core/java/android/view/HandwritingInitiator.java
@@ -170,6 +170,9 @@
                             findBestCandidateView(mState.mStylusDownX, mState.mStylusDownY);
                     if (candidateView != null) {
                         if (candidateView == getConnectedView()) {
+                            if (!candidateView.hasFocus()) {
+                                requestFocusWithoutReveal(candidateView);
+                            }
                             startHandwriting(candidateView);
                         } else if (candidateView.getHandwritingDelegatorCallback() != null) {
                             String delegatePackageName =
@@ -181,13 +184,7 @@
                                     candidateView, delegatePackageName);
                             candidateView.getHandwritingDelegatorCallback().run();
                         } else {
-                            if (candidateView.getRevealOnFocusHint()) {
-                                candidateView.setRevealOnFocusHint(false);
-                                candidateView.requestFocus();
-                                candidateView.setRevealOnFocusHint(true);
-                            } else {
-                                candidateView.requestFocus();
-                            }
+                            requestFocusWithoutReveal(candidateView);
                         }
                     }
                 }
@@ -380,6 +377,16 @@
         return false;
     }
 
+    private static void requestFocusWithoutReveal(View view) {
+        if (view.getRevealOnFocusHint()) {
+            view.setRevealOnFocusHint(false);
+            view.requestFocus();
+            view.setRevealOnFocusHint(true);
+        } else {
+            view.requestFocus();
+        }
+    }
+
     /**
      * Given the location of the stylus event, return the best candidate view to initialize
      * handwriting mode.
@@ -389,9 +396,6 @@
      */
     @Nullable
     private View findBestCandidateView(float x, float y) {
-        float minDistance = Float.MAX_VALUE;
-        View bestCandidate = null;
-
         // If the connectedView is not null and do not set any handwriting area, it will check
         // whether the connectedView's boundary contains the initial stylus position. If true,
         // directly return the connectedView.
@@ -400,14 +404,12 @@
             Rect handwritingArea = getViewHandwritingArea(connectedView);
             if (isInHandwritingArea(handwritingArea, x, y, connectedView)
                     && shouldTriggerStylusHandwritingForView(connectedView)) {
-                final float distance = distance(handwritingArea, x, y);
-                if (distance == 0f) return connectedView;
-
-                bestCandidate = connectedView;
-                minDistance = distance;
+                return connectedView;
             }
         }
 
+        float minDistance = Float.MAX_VALUE;
+        View bestCandidate = null;
         // Check the registered handwriting areas.
         final List<HandwritableViewInfo> handwritableViewInfos =
                 mHandwritingAreasTracker.computeViewInfos();
diff --git a/core/java/android/view/InputDevice.java b/core/java/android/view/InputDevice.java
index 720f569..9225cd9 100644
--- a/core/java/android/view/InputDevice.java
+++ b/core/java/android/view/InputDevice.java
@@ -29,6 +29,7 @@
 import android.hardware.input.HostUsiVersion;
 import android.hardware.input.InputDeviceIdentifier;
 import android.hardware.input.InputManager;
+import android.hardware.input.InputManagerGlobal;
 import android.hardware.lights.LightsManager;
 import android.icu.util.ULocale;
 import android.os.Build;
@@ -742,7 +743,7 @@
      */
     @Nullable
     public static InputDevice getDevice(int id) {
-        return InputManager.getInstance().getInputDevice(id);
+        return InputManagerGlobal.getInstance().getInputDevice(id);
     }
 
     /**
@@ -750,7 +751,7 @@
      * @return The input device ids.
      */
     public static int[] getDeviceIds() {
-        return InputManager.getInstance().getInputDeviceIds();
+        return InputManagerGlobal.getInstance().getInputDeviceIds();
     }
 
     /**
diff --git a/core/java/android/view/InsetsState.java b/core/java/android/view/InsetsState.java
index 761d504..16bc155 100644
--- a/core/java/android/view/InsetsState.java
+++ b/core/java/android/view/InsetsState.java
@@ -70,23 +70,11 @@
      */
     @Retention(RetentionPolicy.SOURCE)
     @IntDef(prefix = "ITYPE", value = {
-            ITYPE_STATUS_BAR,
-            ITYPE_NAVIGATION_BAR,
             ITYPE_CAPTION_BAR,
-            ITYPE_TOP_GESTURES,
-            ITYPE_BOTTOM_GESTURES,
-            ITYPE_LEFT_GESTURES,
-            ITYPE_RIGHT_GESTURES,
-            ITYPE_TOP_MANDATORY_GESTURES,
-            ITYPE_BOTTOM_MANDATORY_GESTURES,
-            ITYPE_LEFT_MANDATORY_GESTURES,
-            ITYPE_RIGHT_MANDATORY_GESTURES,
             ITYPE_LEFT_TAPPABLE_ELEMENT,
             ITYPE_TOP_TAPPABLE_ELEMENT,
             ITYPE_RIGHT_TAPPABLE_ELEMENT,
             ITYPE_BOTTOM_TAPPABLE_ELEMENT,
-            ITYPE_CLIMATE_BAR,
-            ITYPE_EXTRA_NAVIGATION_BAR,
             ITYPE_LEFT_GENERIC_OVERLAY,
             ITYPE_TOP_GENERIC_OVERLAY,
             ITYPE_RIGHT_GENERIC_OVERLAY,
@@ -94,34 +82,17 @@
     })
     public @interface InternalInsetsType {}
 
-    public static final int ITYPE_STATUS_BAR = 0;
-    public static final int ITYPE_NAVIGATION_BAR = 1;
-    public static final int ITYPE_CAPTION_BAR = 2;
+    public static final int ITYPE_CAPTION_BAR = 0;
 
-    public static final int ITYPE_TOP_GESTURES = 3;
-    public static final int ITYPE_BOTTOM_GESTURES = 4;
-    public static final int ITYPE_LEFT_GESTURES = 5;
-    public static final int ITYPE_RIGHT_GESTURES = 6;
+    public static final int ITYPE_LEFT_TAPPABLE_ELEMENT = 1;
+    public static final int ITYPE_TOP_TAPPABLE_ELEMENT = 2;
+    public static final int ITYPE_RIGHT_TAPPABLE_ELEMENT = 3;
+    public static final int ITYPE_BOTTOM_TAPPABLE_ELEMENT = 4;
 
-    public static final int ITYPE_TOP_MANDATORY_GESTURES = 7;
-    public static final int ITYPE_BOTTOM_MANDATORY_GESTURES = 8;
-    public static final int ITYPE_LEFT_MANDATORY_GESTURES = 9;
-    public static final int ITYPE_RIGHT_MANDATORY_GESTURES = 10;
-
-    public static final int ITYPE_LEFT_TAPPABLE_ELEMENT = 15;
-    public static final int ITYPE_TOP_TAPPABLE_ELEMENT = 16;
-    public static final int ITYPE_RIGHT_TAPPABLE_ELEMENT = 17;
-    public static final int ITYPE_BOTTOM_TAPPABLE_ELEMENT = 18;
-
-    /** Additional system decorations inset type. */
-    public static final int ITYPE_CLIMATE_BAR = 20;
-    public static final int ITYPE_EXTRA_NAVIGATION_BAR = 21;
-
-    /** Additional types for local insets. **/
-    public static final int ITYPE_LEFT_GENERIC_OVERLAY = 22;
-    public static final int ITYPE_TOP_GENERIC_OVERLAY = 23;
-    public static final int ITYPE_RIGHT_GENERIC_OVERLAY = 24;
-    public static final int ITYPE_BOTTOM_GENERIC_OVERLAY = 25;
+    public static final int ITYPE_LEFT_GENERIC_OVERLAY = 5;
+    public static final int ITYPE_TOP_GENERIC_OVERLAY = 6;
+    public static final int ITYPE_RIGHT_GENERIC_OVERLAY = 7;
+    public static final int ITYPE_BOTTOM_GENERIC_OVERLAY = 8;
 
     @Retention(RetentionPolicy.SOURCE)
     @IntDef(prefix = "ISIDE", value = {
@@ -713,12 +684,6 @@
      */
     public static @Type.InsetsType int toPublicType(@InternalInsetsType int type) {
         switch (type) {
-            case ITYPE_STATUS_BAR:
-            case ITYPE_CLIMATE_BAR:
-                return Type.STATUS_BARS;
-            case ITYPE_NAVIGATION_BAR:
-            case ITYPE_EXTRA_NAVIGATION_BAR:
-                return Type.NAVIGATION_BARS;
             case ITYPE_LEFT_GENERIC_OVERLAY:
             case ITYPE_TOP_GENERIC_OVERLAY:
             case ITYPE_RIGHT_GENERIC_OVERLAY:
@@ -726,16 +691,6 @@
                 return Type.SYSTEM_OVERLAYS;
             case ITYPE_CAPTION_BAR:
                 return Type.CAPTION_BAR;
-            case ITYPE_TOP_MANDATORY_GESTURES:
-            case ITYPE_BOTTOM_MANDATORY_GESTURES:
-            case ITYPE_LEFT_MANDATORY_GESTURES:
-            case ITYPE_RIGHT_MANDATORY_GESTURES:
-                return Type.MANDATORY_SYSTEM_GESTURES;
-            case ITYPE_TOP_GESTURES:
-            case ITYPE_BOTTOM_GESTURES:
-            case ITYPE_LEFT_GESTURES:
-            case ITYPE_RIGHT_GESTURES:
-                return Type.SYSTEM_GESTURES;
             case ITYPE_LEFT_TAPPABLE_ELEMENT:
             case ITYPE_TOP_TAPPABLE_ELEMENT:
             case ITYPE_RIGHT_TAPPABLE_ELEMENT:
@@ -771,55 +726,6 @@
         proto.end(token);
     }
 
-    public static String typeToString(@InternalInsetsType int type) {
-        switch (type) {
-            case ITYPE_STATUS_BAR:
-                return "ITYPE_STATUS_BAR";
-            case ITYPE_NAVIGATION_BAR:
-                return "ITYPE_NAVIGATION_BAR";
-            case ITYPE_CAPTION_BAR:
-                return "ITYPE_CAPTION_BAR";
-            case ITYPE_TOP_GESTURES:
-                return "ITYPE_TOP_GESTURES";
-            case ITYPE_BOTTOM_GESTURES:
-                return "ITYPE_BOTTOM_GESTURES";
-            case ITYPE_LEFT_GESTURES:
-                return "ITYPE_LEFT_GESTURES";
-            case ITYPE_RIGHT_GESTURES:
-                return "ITYPE_RIGHT_GESTURES";
-            case ITYPE_TOP_MANDATORY_GESTURES:
-                return "ITYPE_TOP_MANDATORY_GESTURES";
-            case ITYPE_BOTTOM_MANDATORY_GESTURES:
-                return "ITYPE_BOTTOM_MANDATORY_GESTURES";
-            case ITYPE_LEFT_MANDATORY_GESTURES:
-                return "ITYPE_LEFT_MANDATORY_GESTURES";
-            case ITYPE_RIGHT_MANDATORY_GESTURES:
-                return "ITYPE_RIGHT_MANDATORY_GESTURES";
-            case ITYPE_LEFT_TAPPABLE_ELEMENT:
-                return "ITYPE_LEFT_TAPPABLE_ELEMENT";
-            case ITYPE_TOP_TAPPABLE_ELEMENT:
-                return "ITYPE_TOP_TAPPABLE_ELEMENT";
-            case ITYPE_RIGHT_TAPPABLE_ELEMENT:
-                return "ITYPE_RIGHT_TAPPABLE_ELEMENT";
-            case ITYPE_BOTTOM_TAPPABLE_ELEMENT:
-                return "ITYPE_BOTTOM_TAPPABLE_ELEMENT";
-            case ITYPE_CLIMATE_BAR:
-                return "ITYPE_CLIMATE_BAR";
-            case ITYPE_EXTRA_NAVIGATION_BAR:
-                return "ITYPE_EXTRA_NAVIGATION_BAR";
-            case ITYPE_LEFT_GENERIC_OVERLAY:
-                return "ITYPE_LEFT_GENERIC_OVERLAY";
-            case ITYPE_TOP_GENERIC_OVERLAY:
-                return "ITYPE_TOP_GENERIC_OVERLAY";
-            case ITYPE_RIGHT_GENERIC_OVERLAY:
-                return "ITYPE_RIGHT_GENERIC_OVERLAY";
-            case ITYPE_BOTTOM_GENERIC_OVERLAY:
-                return "ITYPE_BOTTOM_GENERIC_OVERLAY";
-            default:
-                return "ITYPE_UNKNOWN_" + type;
-        }
-    }
-
     @Override
     public boolean equals(@Nullable Object o) {
         return equals(o, false, false);
diff --git a/core/java/android/view/MotionPredictor.java b/core/java/android/view/MotionPredictor.java
index 7d452f9..27af300 100644
--- a/core/java/android/view/MotionPredictor.java
+++ b/core/java/android/view/MotionPredictor.java
@@ -49,15 +49,17 @@
 
     // Pointer to the native object.
     private final long mPtr;
-    private final Context mContext;
+    // Device-specific override to enable/disable motion prediction.
+    private final boolean mIsPredictionEnabled;
 
     /**
      * Create a new MotionPredictor for the provided {@link Context}.
      * @param context The context for the predictions
      */
     public MotionPredictor(@NonNull Context context) {
-        mContext = context;
-        final int offsetNanos = mContext.getResources().getInteger(
+        mIsPredictionEnabled = context.getResources().getBoolean(
+                com.android.internal.R.bool.config_enableMotionPrediction);
+        final int offsetNanos = context.getResources().getInteger(
                 com.android.internal.R.integer.config_motionPredictionOffsetNanos);
         mPtr = nativeInitialize(offsetNanos);
         RegistryHolder.REGISTRY.registerNativeAllocation(this, mPtr);
@@ -73,7 +75,7 @@
      * @throws IllegalArgumentException if an inconsistent MotionEvent stream is sent.
      */
     public void record(@NonNull MotionEvent event) {
-        if (!isPredictionEnabled()) {
+        if (!mIsPredictionEnabled) {
             return;
         }
         nativeRecord(mPtr, event);
@@ -94,21 +96,12 @@
      */
     @Nullable
     public MotionEvent predict(long predictionTimeNanos) {
-        if (!isPredictionEnabled()) {
+        if (!mIsPredictionEnabled) {
             return null;
         }
         return nativePredict(mPtr, predictionTimeNanos);
     }
 
-    private boolean isPredictionEnabled() {
-        // Device-specific override
-        if (!mContext.getResources().getBoolean(
-                com.android.internal.R.bool.config_enableMotionPrediction)) {
-            return false;
-        }
-        return true;
-    }
-
     /**
      * Check whether a device supports motion predictions for a given source type.
      *
@@ -120,7 +113,7 @@
      * @see MotionEvent#getSource
      */
     public boolean isPredictionAvailable(int deviceId, int source) {
-        return isPredictionEnabled() && nativeIsPredictionAvailable(mPtr, deviceId, source);
+        return mIsPredictionEnabled && nativeIsPredictionAvailable(mPtr, deviceId, source);
     }
 
     private static native long nativeInitialize(int offsetNanos);
diff --git a/core/java/android/view/SurfaceControlHdrLayerInfoListener.java b/core/java/android/view/SurfaceControlHdrLayerInfoListener.java
index 13d68d0..b79fd2d 100644
--- a/core/java/android/view/SurfaceControlHdrLayerInfoListener.java
+++ b/core/java/android/view/SurfaceControlHdrLayerInfoListener.java
@@ -42,10 +42,13 @@
      * @param maxH The height of the HDR layer with the largest area
      * @param flags Additional metadata flags, currently always 0
      *              TODO(b/182312559): Add some flags
+     * @param maxDesiredHdrSdrRatio The max desired HDR/SDR ratio. Unbounded if the ratio is
+     *                              positive infinity.
      *
-     * @hide */
+     * @hide
+     */
     public abstract void onHdrInfoChanged(IBinder displayToken, int numberOfHdrLayers,
-            int maxW, int maxH, int flags);
+            int maxW, int maxH, int flags, float maxDesiredHdrSdrRatio);
 
     /**
      * Registers this as an HDR info listener on the provided display
diff --git a/core/java/android/view/ViewConfiguration.java b/core/java/android/view/ViewConfiguration.java
index c96d298..d80819f 100644
--- a/core/java/android/view/ViewConfiguration.java
+++ b/core/java/android/view/ViewConfiguration.java
@@ -28,6 +28,7 @@
 import android.content.res.Resources;
 import android.graphics.Rect;
 import android.hardware.input.InputManager;
+import android.hardware.input.InputManagerGlobal;
 import android.os.Build;
 import android.os.Bundle;
 import android.os.RemoteException;
@@ -1188,7 +1189,7 @@
     }
 
     private static boolean isInputDeviceInfoValid(int id, int axis, int source) {
-        InputDevice device = InputManager.getInstance().getInputDevice(id);
+        InputDevice device = InputManagerGlobal.getInstance().getInputDevice(id);
         return device != null && device.getMotionRange(axis, source) != null;
     }
 
diff --git a/core/java/android/view/accessibility/AccessibilityManager.java b/core/java/android/view/accessibility/AccessibilityManager.java
index 5ad2476..efa2a01 100644
--- a/core/java/android/view/accessibility/AccessibilityManager.java
+++ b/core/java/android/view/accessibility/AccessibilityManager.java
@@ -2116,6 +2116,58 @@
         }
     }
 
+    /**
+     * Determines if the accessibility target is allowed.
+     *
+     * @param packageName The name of the application attempting to perform the operation.
+     * @param uid The user id of the application attempting to perform the operation.
+     * @param userId The id of the user for whom to perform the operation.
+     * @return {@code true} the accessibility target is allowed.
+     * @hide
+     */
+    public boolean isAccessibilityTargetAllowed(String packageName, int uid, int userId) {
+        final IAccessibilityManager service;
+        synchronized (mLock) {
+            service = getServiceLocked();
+            if (service == null) {
+                return false;
+            }
+        }
+
+        try {
+            return service.isAccessibilityTargetAllowed(packageName, uid, userId);
+        } catch (RemoteException re) {
+            Log.e(LOG_TAG, "Error while check accessibility target status", re);
+            return false;
+        }
+    }
+
+    /**
+     * Sends restricted dialog intent if the accessibility target is disallowed.
+     *
+     * @param packageName The name of the application attempting to perform the operation.
+     * @param uid The user id of the application attempting to perform the operation.
+     * @param userId The id of the user for whom to perform the operation.
+     * @return {@code true} if the restricted dialog is shown.
+     * @hide
+     */
+    public boolean sendRestrictedDialogIntent(String packageName, int uid, int userId) {
+        final IAccessibilityManager service;
+        synchronized (mLock) {
+            service = getServiceLocked();
+            if (service == null) {
+                return false;
+            }
+        }
+
+        try {
+            return service.sendRestrictedDialogIntent(packageName, uid, userId);
+        } catch (RemoteException re) {
+            Log.e(LOG_TAG, "Error while show restricted dialog", re);
+            return false;
+        }
+    }
+
     private IAccessibilityManager getServiceLocked() {
         if (mService == null) {
             tryConnectToServiceLocked(null);
diff --git a/core/java/android/view/accessibility/IAccessibilityManager.aidl b/core/java/android/view/accessibility/IAccessibilityManager.aidl
index c828058..1fac142 100644
--- a/core/java/android/view/accessibility/IAccessibilityManager.aidl
+++ b/core/java/android/view/accessibility/IAccessibilityManager.aidl
@@ -124,6 +124,9 @@
     boolean stopFlashNotificationSequence(String opPkg);
     boolean startFlashNotificationEvent(String opPkg, int reason, String reasonPkg);
 
+    boolean isAccessibilityTargetAllowed(String packageName, int uid, int userId);
+    boolean sendRestrictedDialogIntent(String packageName, int uid, int userId);
+
     parcelable WindowTransformationSpec {
         float[] transformationMatrix;
         MagnificationSpec magnificationSpec;
diff --git a/core/java/android/window/SnapshotDrawerUtils.java b/core/java/android/window/SnapshotDrawerUtils.java
index 071c20f..52e17ca 100644
--- a/core/java/android/window/SnapshotDrawerUtils.java
+++ b/core/java/android/window/SnapshotDrawerUtils.java
@@ -34,6 +34,7 @@
 import static android.view.WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION;
 import static android.view.WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS;
 import static android.view.WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH;
+import static android.view.WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL;
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_FORCE_DRAW_BAR_BACKGROUNDS;
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY;
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_USE_BLAST;
@@ -223,6 +224,11 @@
                         PixelFormat.RGBA_8888,
                         GraphicBuffer.USAGE_HW_TEXTURE | GraphicBuffer.USAGE_HW_COMPOSER
                                 | GraphicBuffer.USAGE_SW_WRITE_RARELY);
+                if (background == null) {
+                    Log.e(TAG, "Unable to draw snapshot: failed to allocate graphic buffer for "
+                            + mTitle);
+                    return;
+                }
                 // TODO: Support this on HardwareBuffer
                 final Canvas c = background.lockCanvas();
                 drawBackgroundAndBars(c, frame);
@@ -410,6 +416,7 @@
         layoutParams.setFitInsetsIgnoringVisibility(attrs.isFitInsetsIgnoringVisibility());
 
         layoutParams.setTitle(title);
+        layoutParams.inputFeatures |= INPUT_FEATURE_NO_INPUT_CHANNEL;
         return layoutParams;
     }
 
diff --git a/core/java/android/window/TransitionInfo.java b/core/java/android/window/TransitionInfo.java
index 0b43eb5..4c482460 100644
--- a/core/java/android/window/TransitionInfo.java
+++ b/core/java/android/window/TransitionInfo.java
@@ -24,6 +24,7 @@
 import static android.app.ActivityOptions.ANIM_THUMBNAIL_SCALE_DOWN;
 import static android.app.ActivityOptions.ANIM_THUMBNAIL_SCALE_UP;
 import static android.app.WindowConfiguration.ROTATION_UNDEFINED;
+import static android.view.Display.INVALID_DISPLAY;
 import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_UNSPECIFIED;
 import static android.view.WindowManager.TRANSIT_CHANGE;
 import static android.view.WindowManager.TRANSIT_CLOSE;
@@ -58,6 +59,7 @@
  * @hide
  */
 public final class TransitionInfo implements Parcelable {
+    private static final String TAG = "TransitionInfo";
 
     /**
      * Modes are only a sub-set of all the transit-types since they are per-container
@@ -144,8 +146,11 @@
     /** The window should have no animation (by policy). */
     public static final int FLAG_NO_ANIMATION = 1 << 18;
 
+    /** The task is launching behind home. */
+    public static final int FLAG_TASK_LAUNCHING_BEHIND = 1 << 19;
+
     /** The first unused bit. This can be used by remotes to attach custom flags to this change. */
-    public static final int FLAG_FIRST_CUSTOM = 1 << 19;
+    public static final int FLAG_FIRST_CUSTOM = 1 << 20;
 
     /** The change belongs to a window that won't contain activities. */
     public static final int FLAGS_IS_NON_APP_WINDOW =
@@ -173,6 +178,7 @@
             FLAG_IS_SYSTEM_WINDOW,
             FLAG_BACK_GESTURE_ANIMATED,
             FLAG_NO_ANIMATION,
+            FLAG_TASK_LAUNCHING_BEHIND,
             FLAG_FIRST_CUSTOM
     })
     public @interface ChangeFlags {}
@@ -180,9 +186,7 @@
     private final @TransitionType int mType;
     private final @TransitionFlags int mFlags;
     private final ArrayList<Change> mChanges = new ArrayList<>();
-
-    private SurfaceControl mRootLeash;
-    private final Point mRootOffset = new Point();
+    private final ArrayList<Root> mRoots = new ArrayList<>();
 
     private AnimationOptions mOptions;
 
@@ -196,10 +200,7 @@
         mType = in.readInt();
         mFlags = in.readInt();
         in.readTypedList(mChanges, Change.CREATOR);
-        mRootLeash = new SurfaceControl();
-        mRootLeash.readFromParcel(in);
-        mRootLeash.setUnreleasedWarningCallSite("TransitionInfo");
-        mRootOffset.readFromParcel(in);
+        in.readTypedList(mRoots, Root.CREATOR);
         mOptions = in.readTypedObject(AnimationOptions.CREATOR);
     }
 
@@ -209,8 +210,7 @@
         dest.writeInt(mType);
         dest.writeInt(mFlags);
         dest.writeTypedList(mChanges);
-        mRootLeash.writeToParcel(dest, flags);
-        mRootOffset.writeToParcel(dest, flags);
+        dest.writeTypedList(mRoots, flags);
         dest.writeTypedObject(mOptions, flags);
     }
 
@@ -234,10 +234,15 @@
         return 0;
     }
 
-    /** @see #getRootLeash() */
-    public void setRootLeash(@NonNull SurfaceControl leash, int offsetLeft, int offsetTop) {
-        mRootLeash = leash;
-        mRootOffset.set(offsetLeft, offsetTop);
+    /** @see #getRoot */
+    public void addRootLeash(int displayId, @NonNull SurfaceControl leash,
+            int offsetLeft, int offsetTop) {
+        mRoots.add(new Root(displayId, leash, offsetLeft, offsetTop));
+    }
+
+    /** @see #getRoot */
+    public void addRoot(Root other) {
+        mRoots.add(other);
     }
 
     public void setAnimationOptions(AnimationOptions options) {
@@ -253,23 +258,52 @@
     }
 
     /**
+     * @return The number of animation roots. Most transitions should have 1, but there may be more
+     *         in some cases (such as a transition spanning multiple displays).
+     */
+    public int getRootCount() {
+        return mRoots.size();
+    }
+
+    /**
+     * @return the transition-root at a specific index.
+     */
+    @NonNull
+    public Root getRoot(int idx) {
+        return mRoots.get(idx);
+    }
+
+    /**
+     * @return the index of the transition-root associated with `displayId` or -1 if not found.
+     */
+    public int findRootIndex(int displayId) {
+        for (int i = 0; i < mRoots.size(); ++i) {
+            if (mRoots.get(i).mDisplayId == displayId) {
+                return i;
+            }
+        }
+        return -1;
+    }
+
+    /**
      * @return a surfacecontrol that can serve as a parent surfacecontrol for all the changing
      * participants to animate within. This will generally be placed at the highest-z-order
      * shared ancestor of all participants. While this is non-null, it's possible for the rootleash
      * to be invalid if the transition is a no-op.
+     *
+     * @deprecated Use {@link #getRoot} instead. This call assumes there is only one root.
      */
+    @Deprecated
     @NonNull
     public SurfaceControl getRootLeash() {
-        if (mRootLeash == null) {
-            throw new IllegalStateException("Trying to get a leash which wasn't set");
+        if (mRoots.isEmpty()) {
+            throw new IllegalStateException("Trying to get a root leash from a no-op transition.");
         }
-        return mRootLeash;
-    }
-
-    /** @return the offset (relative to the screen) of the root leash. */
-    @NonNull
-    public Point getRootOffset() {
-        return mRootOffset;
+        if (mRoots.size() > 1) {
+            android.util.Log.e(TAG, "Assuming one animation root when there are more.",
+                    new Throwable());
+        }
+        return mRoots.get(0).mLeash;
     }
 
     public AnimationOptions getAnimationOptions() {
@@ -316,8 +350,15 @@
     @Override
     public String toString() {
         StringBuilder sb = new StringBuilder();
-        sb.append("{t=" + transitTypeToString(mType) + " f=0x" + Integer.toHexString(mFlags)
-                + " ro=" + mRootOffset + " c=[");
+        sb.append("{t=").append(transitTypeToString(mType)).append(" f=0x")
+                .append(Integer.toHexString(mFlags)).append(" r=[");
+        for (int i = 0; i < mRoots.size(); ++i) {
+            if (i > 0) {
+                sb.append(',');
+            }
+            sb.append(mRoots.get(i).mDisplayId).append("@").append(mRoots.get(i).mOffset);
+        }
+        sb.append("] c=[");
         for (int i = 0; i < mChanges.size(); ++i) {
             if (i > 0) {
                 sb.append(',');
@@ -395,6 +436,9 @@
         if ((flags & FLAG_NO_ANIMATION) != 0) {
             sb.append(sb.length() == 0 ? "" : "|").append("NO_ANIMATION");
         }
+        if ((flags & FLAG_TASK_LAUNCHING_BEHIND) != 0) {
+            sb.append((sb.length() == 0 ? "" : "|") + "TASK_LAUNCHING_BEHIND");
+        }
         if ((flags & FLAG_FIRST_CUSTOM) != 0) {
             sb.append(sb.length() == 0 ? "" : "|").append("FIRST_CUSTOM");
         }
@@ -441,8 +485,8 @@
                 c.mSnapshot = null;
             }
         }
-        if (mRootLeash != null) {
-            mRootLeash.release();
+        for (int i = 0; i < mRoots.size(); ++i) {
+            mRoots.get(i).mLeash.release();
         }
     }
 
@@ -469,10 +513,11 @@
         for (int i = 0; i < mChanges.size(); ++i) {
             out.mChanges.add(mChanges.get(i).localRemoteCopy());
         }
-        out.mRootLeash = mRootLeash != null ? new SurfaceControl(mRootLeash, "localRemote") : null;
+        for (int i = 0; i < mRoots.size(); ++i) {
+            out.mRoots.add(mRoots.get(i).localRemoteCopy());
+        }
         // Doesn't have any native stuff, so no need for actual copy
         out.mOptions = mOptions;
-        out.mRootOffset.set(mRootOffset);
         return out;
     }
 
@@ -489,6 +534,8 @@
         private final Point mEndRelOffset = new Point();
         private ActivityManager.RunningTaskInfo mTaskInfo = null;
         private boolean mAllowEnterPip;
+        private int mStartDisplayId = INVALID_DISPLAY;
+        private int mEndDisplayId = INVALID_DISPLAY;
         private @Surface.Rotation int mStartRotation = ROTATION_UNDEFINED;
         private @Surface.Rotation int mEndRotation = ROTATION_UNDEFINED;
         /**
@@ -519,6 +566,8 @@
             mEndRelOffset.readFromParcel(in);
             mTaskInfo = in.readTypedObject(ActivityManager.RunningTaskInfo.CREATOR);
             mAllowEnterPip = in.readBoolean();
+            mStartDisplayId = in.readInt();
+            mEndDisplayId = in.readInt();
             mStartRotation = in.readInt();
             mEndRotation = in.readInt();
             mEndFixedRotation = in.readInt();
@@ -539,6 +588,8 @@
             out.mEndRelOffset.set(mEndRelOffset);
             out.mTaskInfo = mTaskInfo;
             out.mAllowEnterPip = mAllowEnterPip;
+            out.mStartDisplayId = mStartDisplayId;
+            out.mEndDisplayId = mEndDisplayId;
             out.mStartRotation = mStartRotation;
             out.mEndRotation = mEndRotation;
             out.mEndFixedRotation = mEndFixedRotation;
@@ -601,6 +652,12 @@
         }
 
         /** Sets the start and end rotation of this container. */
+        public void setDisplayId(int start, int end) {
+            mStartDisplayId = start;
+            mEndDisplayId = end;
+        }
+
+        /** Sets the start and end rotation of this container. */
         public void setRotation(@Surface.Rotation int start, @Surface.Rotation int end) {
             mStartRotation = start;
             mEndRotation = end;
@@ -718,6 +775,14 @@
             return mAllowEnterPip;
         }
 
+        public int getStartDisplayId() {
+            return mStartDisplayId;
+        }
+
+        public int getEndDisplayId() {
+            return mEndDisplayId;
+        }
+
         @Surface.Rotation
         public int getStartRotation() {
             return mStartRotation;
@@ -769,6 +834,8 @@
             mEndRelOffset.writeToParcel(dest, flags);
             dest.writeTypedObject(mTaskInfo, flags);
             dest.writeBoolean(mAllowEnterPip);
+            dest.writeInt(mStartDisplayId);
+            dest.writeInt(mEndDisplayId);
             dest.writeInt(mStartRotation);
             dest.writeInt(mEndRotation);
             dest.writeInt(mEndFixedRotation);
@@ -815,6 +882,11 @@
             if (mEndRelOffset.x != 0 || mEndRelOffset.y != 0) {
                 sb.append(" eo="); sb.append(mEndRelOffset);
             }
+            sb.append(" d=");
+            if (mStartDisplayId != mEndDisplayId) {
+                sb.append(mStartDisplayId).append("->");
+            }
+            sb.append(mEndDisplayId);
             if (mStartRotation != mEndRotation) {
                 sb.append(" r="); sb.append(mStartRotation);
                 sb.append("->"); sb.append(mEndRotation);
@@ -1101,4 +1173,86 @@
                     };
         }
     }
+
+    /**
+     * An animation root in a transition. There is one of these for each display that contains
+     * participants. It will be placed, in z-order, right above the top-most participant and at the
+     * same position in the hierarchy. As a result, if all participants are animating within a
+     * part of the screen, the root-leash will only be in that part of the screen. In these cases,
+     * it's relative position (from the screen) is stored in {@link Root#getOffset}.
+     */
+    public static final class Root implements Parcelable {
+        private final int mDisplayId;
+        private final SurfaceControl mLeash;
+        private final Point mOffset = new Point();
+
+        public Root(int displayId, @NonNull SurfaceControl leash, int offsetLeft, int offsetTop) {
+            mDisplayId = displayId;
+            mLeash = leash;
+            mOffset.set(offsetLeft, offsetTop);
+        }
+
+        private Root(Parcel in) {
+            mDisplayId = in.readInt();
+            mLeash = new SurfaceControl();
+            mLeash.readFromParcel(in);
+            mLeash.setUnreleasedWarningCallSite("TransitionInfo.Root");
+            mOffset.readFromParcel(in);
+        }
+
+        private Root localRemoteCopy() {
+            return new Root(mDisplayId, new SurfaceControl(mLeash, "localRemote"),
+                    mOffset.x, mOffset.y);
+        }
+
+        /** @return the id of the display this root is on. */
+        public int getDisplayId() {
+            return mDisplayId;
+        }
+
+        /** @return the root's leash. Surfaces should be parented to this while animating. */
+        @NonNull
+        public SurfaceControl getLeash() {
+            return mLeash;
+        }
+
+        /** @return the offset (relative to its screen) of the root leash. */
+        @NonNull
+        public Point getOffset() {
+            return mOffset;
+        }
+
+        /** @hide */
+        @Override
+        public void writeToParcel(@NonNull Parcel dest, int flags) {
+            dest.writeInt(mDisplayId);
+            mLeash.writeToParcel(dest, flags);
+            mOffset.writeToParcel(dest, flags);
+        }
+
+        @NonNull
+        public static final Creator<Root> CREATOR =
+                new Creator<Root>() {
+                    @Override
+                    public Root createFromParcel(Parcel in) {
+                        return new Root(in);
+                    }
+
+                    @Override
+                    public Root[] newArray(int size) {
+                        return new Root[size];
+                    }
+                };
+
+        /** @hide */
+        @Override
+        public int describeContents() {
+            return 0;
+        }
+
+        @Override
+        public String toString() {
+            return mDisplayId + "@" + mOffset + ":" + mLeash;
+        }
+    }
 }
diff --git a/core/java/android/window/WindowInfosListenerForTest.java b/core/java/android/window/WindowInfosListenerForTest.java
index 429156f..01e577f 100644
--- a/core/java/android/window/WindowInfosListenerForTest.java
+++ b/core/java/android/window/WindowInfosListenerForTest.java
@@ -63,10 +63,17 @@
         @NonNull
         public final Rect bounds;
 
-        WindowInfo(@NonNull IBinder windowToken, @NonNull String name, @NonNull Rect bounds) {
+        /**
+         * True if the window is a trusted overlay.
+         */
+        public final boolean isTrustedOverlay;
+
+        WindowInfo(@NonNull IBinder windowToken, @NonNull String name, @NonNull Rect bounds,
+                int inputConfig) {
             this.windowToken = windowToken;
             this.name = name;
             this.bounds = bounds;
+            this.isTrustedOverlay = (inputConfig & InputConfig.TRUSTED_OVERLAY) != 0;
         }
     }
 
@@ -129,7 +136,8 @@
             }
             var bounds = new Rect(handle.frameLeft, handle.frameTop, handle.frameRight,
                     handle.frameBottom);
-            windowInfos.add(new WindowInfo(handle.getWindowToken(), handle.name, bounds));
+            windowInfos.add(new WindowInfo(handle.getWindowToken(), handle.name, bounds,
+                    handle.inputConfig));
         }
         return windowInfos;
     }
diff --git a/core/java/com/android/internal/accessibility/dialog/AccessibilityActivityTarget.java b/core/java/com/android/internal/accessibility/dialog/AccessibilityActivityTarget.java
index 4c7d93b..063154d 100644
--- a/core/java/com/android/internal/accessibility/dialog/AccessibilityActivityTarget.java
+++ b/core/java/com/android/internal/accessibility/dialog/AccessibilityActivityTarget.java
@@ -26,6 +26,7 @@
 import android.view.accessibility.AccessibilityManager.ShortcutType;
 
 import com.android.internal.accessibility.common.ShortcutConstants.AccessibilityFragmentType;
+import com.android.internal.accessibility.common.ShortcutConstants.ShortcutMenuMode;
 
 /**
  * Base class for creating accessibility activity target.
@@ -40,8 +41,25 @@
                 isShortcutContained(context, shortcutType,
                         shortcutInfo.getComponentName().flattenToString()),
                 shortcutInfo.getComponentName().flattenToString(),
+                shortcutInfo.getActivityInfo().applicationInfo.uid,
                 shortcutInfo.getActivityInfo().loadLabel(context.getPackageManager()),
                 shortcutInfo.getActivityInfo().loadIcon(context.getPackageManager()),
                 convertToKey(convertToUserType(shortcutType)));
     }
+
+    @Override
+    public void updateActionItem(@NonNull TargetAdapter.ViewHolder holder,
+            @ShortcutMenuMode int shortcutMenuMode) {
+        super.updateActionItem(holder, shortcutMenuMode);
+
+        final boolean isAllowed = AccessibilityTargetHelper.isAccessibilityTargetAllowed(
+                getContext(), getComponentName().getPackageName(), getUid());
+        final boolean isEditMenuMode =
+                shortcutMenuMode == ShortcutMenuMode.EDIT;
+        final boolean enabled = isAllowed || (isEditMenuMode && isShortcutEnabled());
+        holder.mCheckBoxView.setEnabled(enabled);
+        holder.mIconView.setEnabled(enabled);
+        holder.mLabelView.setEnabled(enabled);
+        holder.mStatusView.setEnabled(enabled);
+    }
 }
diff --git a/core/java/com/android/internal/accessibility/dialog/AccessibilityServiceTarget.java b/core/java/com/android/internal/accessibility/dialog/AccessibilityServiceTarget.java
index e64f78a..6497409 100644
--- a/core/java/com/android/internal/accessibility/dialog/AccessibilityServiceTarget.java
+++ b/core/java/com/android/internal/accessibility/dialog/AccessibilityServiceTarget.java
@@ -26,6 +26,7 @@
 import android.view.accessibility.AccessibilityManager.ShortcutType;
 
 import com.android.internal.accessibility.common.ShortcutConstants.AccessibilityFragmentType;
+import com.android.internal.accessibility.common.ShortcutConstants.ShortcutMenuMode;
 
 /**
  * Base class for creating accessibility service target with various fragment types related to
@@ -42,8 +43,25 @@
                 isShortcutContained(context, shortcutType,
                         serviceInfo.getComponentName().flattenToString()),
                 serviceInfo.getComponentName().flattenToString(),
+                serviceInfo.getResolveInfo().serviceInfo.applicationInfo.uid,
                 serviceInfo.getResolveInfo().loadLabel(context.getPackageManager()),
                 serviceInfo.getResolveInfo().loadIcon(context.getPackageManager()),
                 convertToKey(convertToUserType(shortcutType)));
     }
+
+    @Override
+    public void updateActionItem(@NonNull TargetAdapter.ViewHolder holder,
+            @ShortcutMenuMode int shortcutMenuMode) {
+        super.updateActionItem(holder, shortcutMenuMode);
+
+        final boolean isAllowed = AccessibilityTargetHelper.isAccessibilityTargetAllowed(
+                getContext(), getComponentName().getPackageName(), getUid());
+        final boolean isEditMenuMode =
+                shortcutMenuMode == ShortcutMenuMode.EDIT;
+        final boolean enabled = isAllowed || (isEditMenuMode && isShortcutEnabled());
+        holder.mCheckBoxView.setEnabled(enabled);
+        holder.mIconView.setEnabled(enabled);
+        holder.mLabelView.setEnabled(enabled);
+        holder.mStatusView.setEnabled(enabled);
+    }
 }
diff --git a/core/java/com/android/internal/accessibility/dialog/AccessibilityShortcutChooserActivity.java b/core/java/com/android/internal/accessibility/dialog/AccessibilityShortcutChooserActivity.java
index 50afb3e..5dd558a 100644
--- a/core/java/com/android/internal/accessibility/dialog/AccessibilityShortcutChooserActivity.java
+++ b/core/java/com/android/internal/accessibility/dialog/AccessibilityShortcutChooserActivity.java
@@ -95,6 +95,13 @@
 
     private void onTargetSelected(AdapterView<?> parent, View view, int position, long id) {
         final AccessibilityTarget target = mTargets.get(position);
+        if (target instanceof AccessibilityServiceTarget
+                || target instanceof AccessibilityActivityTarget) {
+            if (sendRestrictedDialogIntentIfNeeded(target)) {
+                return;
+            }
+        }
+
         target.onSelected();
         mMenuDialog.dismiss();
     }
@@ -102,15 +109,41 @@
     private void onTargetChecked(AdapterView<?> parent, View view, int position, long id) {
         final AccessibilityTarget target = mTargets.get(position);
 
-        if ((target instanceof AccessibilityServiceTarget) && !target.isShortcutEnabled()) {
-            showPermissionDialogIfNeeded(this, (AccessibilityServiceTarget) target, mTargetAdapter);
-            return;
+        if (!target.isShortcutEnabled()) {
+            if (target instanceof AccessibilityServiceTarget
+                    || target instanceof AccessibilityActivityTarget) {
+                if (sendRestrictedDialogIntentIfNeeded(target)) {
+                    return;
+                }
+            }
+
+            if (target instanceof AccessibilityServiceTarget) {
+                showPermissionDialogIfNeeded(this, (AccessibilityServiceTarget) target,
+                        mTargetAdapter);
+                return;
+            }
         }
 
         target.onCheckedChanged(!target.isShortcutEnabled());
         mTargetAdapter.notifyDataSetChanged();
     }
 
+    /**
+     * Sends restricted dialog intent if the accessibility target is disallowed.
+     *
+     * @return true if sends restricted dialog intent, otherwise false.
+     */
+    private boolean sendRestrictedDialogIntentIfNeeded(AccessibilityTarget target) {
+        if (AccessibilityTargetHelper.isAccessibilityTargetAllowed(this,
+                target.getComponentName().getPackageName(), target.getUid())) {
+            return false;
+        }
+
+        AccessibilityTargetHelper.sendRestrictedDialogIntent(this,
+                target.getComponentName().getPackageName(), target.getUid());
+        return true;
+    }
+
     private void showPermissionDialogIfNeeded(Context context,
             AccessibilityServiceTarget serviceTarget, ShortcutTargetAdapter targetAdapter) {
         if (mPermissionDialog != null) {
diff --git a/core/java/com/android/internal/accessibility/dialog/AccessibilityTarget.java b/core/java/com/android/internal/accessibility/dialog/AccessibilityTarget.java
index b8446da..652cb52 100644
--- a/core/java/com/android/internal/accessibility/dialog/AccessibilityTarget.java
+++ b/core/java/com/android/internal/accessibility/dialog/AccessibilityTarget.java
@@ -25,6 +25,7 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.content.ComponentName;
 import android.content.Context;
 import android.graphics.drawable.Drawable;
 import android.view.View;
@@ -37,8 +38,11 @@
 import com.android.internal.annotations.VisibleForTesting;
 
 /**
- * Abstract base class for creating various target related to accessibility service,
- * accessibility activity, and allowlisting feature.
+ * Abstract base class for creating various target related to accessibility service, accessibility
+ * activity, and allowlisting features.
+ *
+ * <p> Disables accessibility features that are not permitted in adding a restricted padlock icon
+ * and showing admin support message dialog.
  */
 public abstract class AccessibilityTarget implements TargetOperations, OnTargetSelectedListener,
         OnTargetCheckedChangeListener {
@@ -49,6 +53,8 @@
     private int mFragmentType;
     private boolean mShortcutEnabled;
     private String mId;
+    private int mUid;
+    private ComponentName mComponentName;
     private CharSequence mLabel;
     private Drawable mIcon;
     private String mKey;
@@ -57,12 +63,14 @@
     @VisibleForTesting
     public AccessibilityTarget(Context context, @ShortcutType int shortcutType,
             @AccessibilityFragmentType int fragmentType, boolean isShortcutSwitched, String id,
-            CharSequence label, Drawable icon, String key) {
+            int uid, CharSequence label, Drawable icon, String key) {
         mContext = context;
         mShortcutType = shortcutType;
         mFragmentType = fragmentType;
         mShortcutEnabled = isShortcutSwitched;
         mId = id;
+        mUid = uid;
+        mComponentName = ComponentName.unflattenFromString(id);
         mLabel = label;
         mIcon = icon;
         mKey = key;
@@ -71,9 +79,14 @@
     @Override
     public void updateActionItem(@NonNull ViewHolder holder,
             @ShortcutConstants.ShortcutMenuMode int shortcutMenuMode) {
+        // Resetting the enable state of the item to avoid the previous wrong state of RecyclerView.
+        holder.mCheckBoxView.setEnabled(true);
+        holder.mIconView.setEnabled(true);
+        holder.mLabelView.setEnabled(true);
+        holder.mStatusView.setEnabled(true);
+
         final boolean isEditMenuMode =
                 shortcutMenuMode == ShortcutConstants.ShortcutMenuMode.EDIT;
-
         holder.mCheckBoxView.setChecked(isEditMenuMode && isShortcutEnabled());
         holder.mCheckBoxView.setVisibility(isEditMenuMode ? View.VISIBLE : View.GONE);
         holder.mIconView.setImageDrawable(getIcon());
@@ -145,6 +158,14 @@
         return mId;
     }
 
+    public int getUid() {
+        return mUid;
+    }
+
+    public ComponentName getComponentName() {
+        return mComponentName;
+    }
+
     public CharSequence getLabel() {
         return mLabel;
     }
diff --git a/core/java/com/android/internal/accessibility/dialog/AccessibilityTargetHelper.java b/core/java/com/android/internal/accessibility/dialog/AccessibilityTargetHelper.java
index a47a97c..0f85075 100644
--- a/core/java/com/android/internal/accessibility/dialog/AccessibilityTargetHelper.java
+++ b/core/java/com/android/internal/accessibility/dialog/AccessibilityTargetHelper.java
@@ -35,6 +35,7 @@
 import android.content.ComponentName;
 import android.content.Context;
 import android.os.Build;
+import android.os.UserHandle;
 import android.provider.Settings;
 import android.text.BidiFormatter;
 import android.view.LayoutInflater;
@@ -202,12 +203,14 @@
     private static List<AccessibilityTarget> getAllowListingFeatureTargets(Context context,
             @ShortcutType int shortcutType) {
         final List<AccessibilityTarget> targets = new ArrayList<>();
+        final int uid = context.getApplicationInfo().uid;
 
         final InvisibleToggleAllowListingFeatureTarget magnification =
                 new InvisibleToggleAllowListingFeatureTarget(context,
                         shortcutType,
                         isShortcutContained(context, shortcutType, MAGNIFICATION_CONTROLLER_NAME),
                         MAGNIFICATION_CONTROLLER_NAME,
+                        uid,
                         context.getString(R.string.accessibility_magnification_chooser_text),
                         context.getDrawable(R.drawable.ic_accessibility_magnification),
                         Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_NAVBAR_ENABLED);
@@ -219,6 +222,7 @@
                         isShortcutContained(context, shortcutType,
                                 DALTONIZER_COMPONENT_NAME.flattenToString()),
                         DALTONIZER_COMPONENT_NAME.flattenToString(),
+                        uid,
                         context.getString(R.string.color_correction_feature_name),
                         context.getDrawable(R.drawable.ic_accessibility_color_correction),
                         Settings.Secure.ACCESSIBILITY_DISPLAY_DALTONIZER_ENABLED);
@@ -230,6 +234,7 @@
                         isShortcutContained(context, shortcutType,
                                 COLOR_INVERSION_COMPONENT_NAME.flattenToString()),
                         COLOR_INVERSION_COMPONENT_NAME.flattenToString(),
+                        uid,
                         context.getString(R.string.color_inversion_feature_name),
                         context.getDrawable(R.drawable.ic_accessibility_color_inversion),
                         Settings.Secure.ACCESSIBILITY_DISPLAY_INVERSION_ENABLED);
@@ -242,6 +247,7 @@
                             isShortcutContained(context, shortcutType,
                                     ONE_HANDED_COMPONENT_NAME.flattenToString()),
                             ONE_HANDED_COMPONENT_NAME.flattenToString(),
+                            uid,
                             context.getString(R.string.one_handed_mode_feature_name),
                             context.getDrawable(R.drawable.ic_accessibility_one_handed),
                             Settings.Secure.ONE_HANDED_MODE_ACTIVATED);
@@ -254,6 +260,7 @@
                         isShortcutContained(context, shortcutType,
                                 REDUCE_BRIGHT_COLORS_COMPONENT_NAME.flattenToString()),
                         REDUCE_BRIGHT_COLORS_COMPONENT_NAME.flattenToString(),
+                        uid,
                         context.getString(R.string.reduce_bright_colors_feature_name),
                         context.getDrawable(R.drawable.ic_accessibility_reduce_bright_colors),
                         Settings.Secure.REDUCE_BRIGHT_COLORS_ACTIVATED);
@@ -265,6 +272,7 @@
                         isShortcutContained(context, shortcutType,
                                 ACCESSIBILITY_HEARING_AIDS_COMPONENT_NAME.flattenToString()),
                         ACCESSIBILITY_HEARING_AIDS_COMPONENT_NAME.flattenToString(),
+                        uid,
                         context.getString(R.string.hearing_aids_feature_name),
                         context.getDrawable(R.drawable.ic_accessibility_hearing_aid),
                         /* key= */ null);
@@ -327,4 +335,21 @@
         final Locale locale = context.getResources().getConfiguration().getLocales().get(0);
         return BidiFormatter.getInstance(locale).unicodeWrap(label);
     }
+
+    /**
+     * Determines if the{@link AccessibilityTarget} is allowed.
+     */
+    public static boolean isAccessibilityTargetAllowed(Context context, String packageName,
+            int uid) {
+        final AccessibilityManager am = context.getSystemService(AccessibilityManager.class);
+        return am.isAccessibilityTargetAllowed(packageName, uid, UserHandle.myUserId());
+    }
+
+    /**
+     * Sends restricted dialog intent if the accessibility target is disallowed.
+     */
+    public static boolean sendRestrictedDialogIntent(Context context, String packageName, int uid) {
+        final AccessibilityManager am = context.getSystemService(AccessibilityManager.class);
+        return am.sendRestrictedDialogIntent(packageName, uid, UserHandle.myUserId());
+    }
 }
diff --git a/core/java/com/android/internal/accessibility/dialog/InvisibleToggleAllowListingFeatureTarget.java b/core/java/com/android/internal/accessibility/dialog/InvisibleToggleAllowListingFeatureTarget.java
index e78036d..c22f17d 100644
--- a/core/java/com/android/internal/accessibility/dialog/InvisibleToggleAllowListingFeatureTarget.java
+++ b/core/java/com/android/internal/accessibility/dialog/InvisibleToggleAllowListingFeatureTarget.java
@@ -29,8 +29,9 @@
 class InvisibleToggleAllowListingFeatureTarget extends AccessibilityTarget {
 
     InvisibleToggleAllowListingFeatureTarget(Context context, @ShortcutType int shortcutType,
-            boolean isShortcutSwitched, String id, CharSequence label, Drawable icon, String key) {
-        super(context, shortcutType, AccessibilityFragmentType.INVISIBLE_TOGGLE,
-                isShortcutSwitched, id, label, icon, key);
+            boolean isShortcutSwitched, String id, int uid, CharSequence label, Drawable icon,
+            String key) {
+        super(context, shortcutType, AccessibilityFragmentType.INVISIBLE_TOGGLE, isShortcutSwitched,
+                id, uid, label, icon, key);
     }
 }
diff --git a/core/java/com/android/internal/accessibility/dialog/ToggleAccessibilityServiceTarget.java b/core/java/com/android/internal/accessibility/dialog/ToggleAccessibilityServiceTarget.java
index 41a0ba2..a4ffef6 100644
--- a/core/java/com/android/internal/accessibility/dialog/ToggleAccessibilityServiceTarget.java
+++ b/core/java/com/android/internal/accessibility/dialog/ToggleAccessibilityServiceTarget.java
@@ -29,12 +29,22 @@
 import com.android.internal.accessibility.common.ShortcutConstants.ShortcutMenuMode;
 import com.android.internal.accessibility.dialog.TargetAdapter.ViewHolder;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
 /**
  * Extension for {@link AccessibilityServiceTarget} with {@link AccessibilityFragmentType#TOGGLE}
  * type.
  */
 class ToggleAccessibilityServiceTarget extends AccessibilityServiceTarget {
 
+    /** Float enum for view alpha setting. */
+    @Retention(RetentionPolicy.SOURCE)
+    @interface StatusViewAlphaScale {
+        float OPAQUE = 1.0f;
+        float DISABLED = 0.5f;
+    }
+
     ToggleAccessibilityServiceTarget(Context context, @ShortcutType int shortcutType,
             @NonNull AccessibilityServiceInfo serviceInfo) {
         super(context,
@@ -53,9 +63,13 @@
             @ShortcutMenuMode int shortcutMenuMode) {
         super.updateActionItem(holder, shortcutMenuMode);
 
+        final boolean isAllowed = AccessibilityTargetHelper.isAccessibilityTargetAllowed(
+                getContext(), getComponentName().getPackageName(), getUid());
         final boolean isEditMenuMode =
                 shortcutMenuMode == ShortcutMenuMode.EDIT;
         holder.mStatusView.setVisibility(isEditMenuMode ? View.GONE : View.VISIBLE);
         holder.mStatusView.setText(getStateDescription());
+        holder.mStatusView.setAlpha(isAllowed
+                ? StatusViewAlphaScale.OPAQUE : StatusViewAlphaScale.DISABLED);
     }
 }
diff --git a/core/java/com/android/internal/accessibility/dialog/ToggleAllowListingFeatureTarget.java b/core/java/com/android/internal/accessibility/dialog/ToggleAllowListingFeatureTarget.java
index d2124a0..11e668f 100644
--- a/core/java/com/android/internal/accessibility/dialog/ToggleAllowListingFeatureTarget.java
+++ b/core/java/com/android/internal/accessibility/dialog/ToggleAllowListingFeatureTarget.java
@@ -35,9 +35,10 @@
 class ToggleAllowListingFeatureTarget extends AccessibilityTarget {
 
     ToggleAllowListingFeatureTarget(Context context, @ShortcutType int shortcutType,
-            boolean isShortcutSwitched, String id, CharSequence label, Drawable icon, String key) {
-        super(context, shortcutType, AccessibilityFragmentType.TOGGLE,
-                isShortcutSwitched, id, label, icon, key);
+            boolean isShortcutSwitched, String id, int uid, CharSequence label, Drawable icon,
+            String key) {
+        super(context, shortcutType, AccessibilityFragmentType.TOGGLE, isShortcutSwitched, id,
+                uid, label, icon, key);
 
         final int statusResId = isFeatureEnabled()
                 ? R.string.accessibility_shortcut_menu_item_status_on
diff --git a/core/java/com/android/internal/app/LocaleHelper.java b/core/java/com/android/internal/app/LocaleHelper.java
index 57bd3f9..d521866 100644
--- a/core/java/com/android/internal/app/LocaleHelper.java
+++ b/core/java/com/android/internal/app/LocaleHelper.java
@@ -20,6 +20,7 @@
 import android.compat.annotation.UnsupportedAppUsage;
 import android.icu.text.CaseMap;
 import android.icu.text.ListFormatter;
+import android.icu.text.NumberingSystem;
 import android.icu.util.ULocale;
 import android.os.LocaleList;
 import android.text.TextUtils;
@@ -173,6 +174,21 @@
     }
 
     /**
+     * Returns numbering system value of a locale for display in the provided locale.
+     *
+     * @param locale The locale whose key value is displayed.
+     * @param displayLocale The locale in which to display the key value.
+     * @return The string of numbering system.
+     */
+    public static String getDisplayNumberingSystemKeyValue(
+            Locale locale, Locale displayLocale) {
+        ULocale uLocale = new ULocale.Builder()
+                .setUnicodeLocaleKeyword("nu", NumberingSystem.getInstance(locale).getName())
+                .build();
+        return uLocale.getDisplayKeywordValue("numbers", ULocale.forLocale(displayLocale));
+    }
+
+    /**
      * Adds the likely subtags for a provided locale ID.
      *
      * @param locale the locale to maximize.
diff --git a/core/java/com/android/internal/app/LocalePickerWithRegion.java b/core/java/com/android/internal/app/LocalePickerWithRegion.java
index 685bd9a..5dfc0ea 100644
--- a/core/java/com/android/internal/app/LocalePickerWithRegion.java
+++ b/core/java/com/android/internal/app/LocalePickerWithRegion.java
@@ -61,6 +61,7 @@
     private int mTopDistance = 0;
     private CharSequence mTitle = null;
     private OnActionExpandListener mOnActionExpandListener;
+    private boolean mIsNumberingSystem = false;
 
     /**
      * Other classes can register to be notified when a locale was selected.
@@ -90,6 +91,18 @@
         boolean hasSpecificPackageName();
     }
 
+    private static LocalePickerWithRegion createNumberingSystemPicker(
+            LocaleSelectedListener listener, LocaleStore.LocaleInfo parent,
+            boolean translatedOnly, OnActionExpandListener onActionExpandListener,
+            LocaleCollectorBase localePickerCollector) {
+        LocalePickerWithRegion localePicker = new LocalePickerWithRegion();
+        localePicker.setOnActionExpandListener(onActionExpandListener);
+        localePicker.setIsNumberingSystem(true);
+        boolean shouldShowTheList = localePicker.setListener(listener, parent,
+                translatedOnly, localePickerCollector);
+        return shouldShowTheList ? localePicker : null;
+    }
+
     private static LocalePickerWithRegion createCountryPicker(
             LocaleSelectedListener listener, LocaleStore.LocaleInfo parent,
             boolean translatedOnly, OnActionExpandListener onActionExpandListener,
@@ -128,6 +141,10 @@
         return localePicker;
     }
 
+    private void setIsNumberingSystem(boolean isNumberingSystem) {
+        mIsNumberingSystem = isNumberingSystem;
+    }
+
     /**
      * Sets the listener and initializes the locale list.
      *
@@ -184,6 +201,7 @@
         final boolean hasSpecificPackageName =
                 mLocalePickerCollector != null && mLocalePickerCollector.hasSpecificPackageName();
         mAdapter = new SuggestedLocaleAdapter(mLocaleList, countryMode, hasSpecificPackageName);
+        mAdapter.setNumberingSystemMode(mIsNumberingSystem);
         final LocaleHelper.LocaleInfoComparator comp =
                 new LocaleHelper.LocaleInfoComparator(sortingLocale, countryMode);
         mAdapter.sort(comp);
@@ -213,7 +231,6 @@
     @Override
     public void onResume() {
         super.onResume();
-
         if (mParentLocale != null) {
             getActivity().setTitle(mParentLocale.getFullNameNative());
         } else {
@@ -250,16 +267,28 @@
         // Special case for resetting the app locale to equal the system locale.
         boolean isSystemLocale = locale.isSystemLocale();
         boolean isRegionLocale = locale.getParent() != null;
+        boolean mayHaveDifferentNumberingSystem = locale.hasNumberingSystems();
 
-        if (isSystemLocale || isRegionLocale) {
+        if (isSystemLocale
+                || (isRegionLocale && !mayHaveDifferentNumberingSystem)
+                || mIsNumberingSystem) {
             if (mListener != null) {
                 mListener.onLocaleSelected(locale);
             }
             returnToParentFrame();
         } else {
-            LocalePickerWithRegion selector = LocalePickerWithRegion.createCountryPicker(
-                    mListener, locale, mTranslatedOnly /* translate only */,
-                    mOnActionExpandListener, this.mLocalePickerCollector);
+            LocalePickerWithRegion selector;
+            if (mayHaveDifferentNumberingSystem) {
+                selector =
+                        LocalePickerWithRegion.createNumberingSystemPicker(
+                        mListener, locale, mTranslatedOnly /* translate only */,
+                        mOnActionExpandListener, this.mLocalePickerCollector);
+            } else {
+                selector = LocalePickerWithRegion.createCountryPicker(
+                        mListener, locale, mTranslatedOnly /* translate only */,
+                        mOnActionExpandListener, this.mLocalePickerCollector);
+            }
+
             if (selector != null) {
                 getFragmentManager().beginTransaction()
                         .setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN)
diff --git a/core/java/com/android/internal/app/LocaleStore.java b/core/java/com/android/internal/app/LocaleStore.java
index 8b41829..bcbfdc9 100644
--- a/core/java/com/android/internal/app/LocaleStore.java
+++ b/core/java/com/android/internal/app/LocaleStore.java
@@ -39,6 +39,9 @@
 import java.util.Set;
 
 public class LocaleStore {
+    private static final int TIER_LANGUAGE = 1;
+    private static final int TIER_REGION = 2;
+    private static final int TIER_NUMBERING = 3;
     private static final HashMap<String, LocaleInfo> sLocaleCache = new HashMap<>();
     private static final String TAG = LocaleStore.class.getSimpleName();
     private static boolean sFullyInitialized = false;
@@ -68,10 +71,13 @@
         private String mFullCountryNameNative;
         private String mLangScriptKey;
 
+        private boolean mHasNumberingSystems;
+
         private LocaleInfo(Locale locale) {
             this.mLocale = locale;
             this.mId = locale.toLanguageTag();
             this.mParent = getParent(locale);
+            this.mHasNumberingSystems = false;
             this.mIsChecked = false;
             this.mSuggestionFlags = SUGGESTION_TYPE_NONE;
             this.mIsTranslated = false;
@@ -93,6 +99,11 @@
                     .build();
         }
 
+        /** Return true if there are any same locales with different numbering system. */
+        public boolean hasNumberingSystems() {
+            return mHasNumberingSystems;
+        }
+
         @Override
         public String toString() {
             return mId;
@@ -195,6 +206,10 @@
             }
         }
 
+        String getNumberingSystem() {
+            return LocaleHelper.getDisplayNumberingSystemKeyValue(mLocale, mLocale);
+        }
+
         String getContentDescription(boolean countryMode) {
             if (countryMode) {
                 return getFullCountryNameInUiLanguage();
@@ -383,6 +398,12 @@
 
         final boolean isInDeveloperMode = Settings.Global.getInt(context.getContentResolver(),
                 Settings.Global.DEVELOPMENT_SETTINGS_ENABLED, 0) != 0;
+        Set<Locale> numberSystemLocaleList = new HashSet<>();
+        for (String localeId : LocalePicker.getSupportedLocales(context)) {
+            if (Locale.forLanguageTag(localeId).getUnicodeLocaleType("nu") != null) {
+                numberSystemLocaleList.add(Locale.forLanguageTag(localeId));
+            }
+        }
         for (String localeId : LocalePicker.getSupportedLocales(context)) {
             if (localeId.isEmpty()) {
                 throw new IllformedLocaleException("Bad locale entry in locale_config.xml");
@@ -403,6 +424,12 @@
             if (simCountries.contains(li.getLocale().getCountry())) {
                 li.mSuggestionFlags |= LocaleInfo.SUGGESTION_TYPE_SIM;
             }
+            numberSystemLocaleList.forEach(l -> {
+                if (li.getLocale().stripExtensions().equals(l.stripExtensions())) {
+                    li.mHasNumberingSystems = true;
+                }
+            });
+
             sLocaleCache.put(li.getId(), li);
             final Locale parent = li.getParent();
             if (parent != null) {
@@ -445,20 +472,43 @@
         sFullyInitialized = true;
     }
 
-    private static int getLevel(Set<String> ignorables, LocaleInfo li, boolean translatedOnly) {
-        if (ignorables.contains(li.getId())) return 0;
-        if (li.mIsPseudo) return 2;
-        if (translatedOnly && !li.isTranslated()) return 0;
-        if (li.getParent() != null) return 2;
-        return 0;
+    private static boolean isShallIgnore(
+            Set<String> ignorables, LocaleInfo li, boolean translatedOnly) {
+        if (ignorables.stream().anyMatch(tag ->
+                Locale.forLanguageTag(tag).stripExtensions()
+                        .equals(li.getLocale().stripExtensions()))) {
+            return true;
+        }
+        if (li.mIsPseudo) return false;
+        if (translatedOnly && !li.isTranslated()) return true;
+        if (li.getParent() != null) return false;
+        return true;
+    }
+
+    private static int getLocaleTier(LocaleInfo parent) {
+        if (parent == null) {
+            return TIER_LANGUAGE;
+        } else if (parent.getLocale().getCountry().isEmpty()) {
+            return TIER_REGION;
+        } else {
+            return TIER_NUMBERING;
+        }
     }
 
     /**
      * Returns a list of locales for language or region selection.
+     *
      * If the parent is null, then it is the language list.
+     *
      * If it is not null, then the list will contain all the locales that belong to that parent.
      * Example: if the parent is "ar", then the region list will contain all Arabic locales.
-     * (this is not language based, but language-script, so that it works for zh-Hant and so on.
+     * (this is not language based, but language-script, so that it works for zh-Hant and so on.)
+     *
+     * If it is not null and has country, then the list will contain all locales with that parent's
+     * language and country, i.e. containing alternate numbering systems.
+     *
+     * Example: if the parent is "ff-Adlm-BF", then the numbering list will contain all
+     * Fula (Adlam, Burkina Faso) i.e. "ff-Adlm-BF" and "ff-Adlm-BF-u-nu-latn"
      */
     @UnsupportedAppUsage
     public static Set<LocaleInfo> getLevelLocales(Context context, Set<String> ignorables,
@@ -478,28 +528,49 @@
      */
     public static Set<LocaleInfo> getLevelLocales(Context context, Set<String> ignorables,
             LocaleInfo parent, boolean translatedOnly, LocaleList explicitLocales) {
-        fillCache(context);
-        String parentId = parent == null ? null : parent.getId();
-        HashSet<LocaleInfo> result = new HashSet<>();
+        if (context != null) {
+            fillCache(context);
+        }
         HashMap<String, LocaleInfo> supportedLcoaleInfos =
                 explicitLocales == null
                         ? sLocaleCache
                         : convertExplicitLocales(explicitLocales, sLocaleCache.values());
+        return getTierLocales(ignorables, parent, translatedOnly, supportedLcoaleInfos);
+    }
 
+    private static Set<LocaleInfo> getTierLocales(
+            Set<String> ignorables,
+            LocaleInfo parent,
+            boolean translatedOnly,
+            HashMap<String, LocaleInfo> supportedLcoaleInfos) {
+
+        boolean hasTargetParent = parent != null;
+        String parentId = hasTargetParent ? parent.getId() : null;
+        HashSet<LocaleInfo> result = new HashSet<>();
         for (LocaleStore.LocaleInfo li : supportedLcoaleInfos.values()) {
-            int level = getLevel(ignorables, li, translatedOnly);
-            if (level == 2) {
-                if (parent != null) { // region selection
-                    if (parentId.equals(li.getParent().toLanguageTag())) {
-                        result.add(li);
-                    }
-                } else { // language selection
+            if (isShallIgnore(ignorables, li, translatedOnly)) {
+                continue;
+            }
+            switch(getLocaleTier(parent)) {
+                case TIER_LANGUAGE:
                     if (li.isSuggestionOfType(LocaleInfo.SUGGESTION_TYPE_SIM)) {
                         result.add(li);
                     } else {
-                        result.add(getLocaleInfo(li.getParent()));
+                        result.add(getLocaleInfo(li.getParent(), supportedLcoaleInfos));
                     }
-                }
+                    break;
+                case TIER_REGION:
+                    if (parentId.equals(li.getParent().toLanguageTag())) {
+                        result.add(getLocaleInfo(
+                                li.getLocale().stripExtensions(), supportedLcoaleInfos));
+                    }
+                    break;
+                case TIER_NUMBERING:
+                    if (parent.getLocale().stripExtensions()
+                            .equals(li.getLocale().stripExtensions())) {
+                        result.add(li);
+                    }
+                    break;
             }
         }
         return result;
@@ -538,18 +609,21 @@
     }
 
     private static LocaleList matchLocaleFromSupportedLocaleList(
-            LocaleList explicitLocales, Collection<LocaleInfo> localeinfo) {
+            LocaleList explicitLocales, Collection<LocaleInfo> localeInfos) {
+        if (localeInfos == null) {
+            return explicitLocales;
+        }
         //TODO: Adds a function for unicode extension if needed.
         Locale[] resultLocales = new Locale[explicitLocales.size()];
         for (int i = 0; i < explicitLocales.size(); i++) {
-            Locale locale = explicitLocales.get(i).stripExtensions();
+            Locale locale = explicitLocales.get(i);
             if (!TextUtils.isEmpty(locale.getCountry())) {
-                for (LocaleInfo localeInfo :localeinfo) {
+                for (LocaleInfo localeInfo :localeInfos) {
                     if (LocaleList.matchesLanguageAndScript(locale, localeInfo.getLocale())
                             && TextUtils.equals(locale.getCountry(),
                             localeInfo.getLocale().getCountry())) {
                         resultLocales[i] = localeInfo.getLocale();
-                        continue;
+                        break;
                     }
                 }
             }
@@ -562,18 +636,23 @@
 
     @UnsupportedAppUsage
     public static LocaleInfo getLocaleInfo(Locale locale) {
+        return getLocaleInfo(locale, sLocaleCache);
+    }
+
+    private static LocaleInfo getLocaleInfo(
+            Locale locale, HashMap<String, LocaleInfo> localeInfos) {
         String id = locale.toLanguageTag();
         LocaleInfo result;
-        if (!sLocaleCache.containsKey(id)) {
+        if (!localeInfos.containsKey(id)) {
             // Locale preferences can modify the language tag to current system languages, so we
             // need to check the input locale without extra u extension except numbering system.
             Locale filteredLocale = new Locale.Builder()
                     .setLocale(locale.stripExtensions())
                     .setUnicodeLocaleKeyword("nu", locale.getUnicodeLocaleType("nu"))
                     .build();
-            if (sLocaleCache.containsKey(filteredLocale.toLanguageTag())) {
+            if (localeInfos.containsKey(filteredLocale.toLanguageTag())) {
                 result = new LocaleInfo(locale);
-                LocaleInfo localeInfo = sLocaleCache.get(filteredLocale.toLanguageTag());
+                LocaleInfo localeInfo = localeInfos.get(filteredLocale.toLanguageTag());
                 // This locale is included in supported locales, so follow the settings
                 // of supported locales.
                 result.mIsPseudo = localeInfo.mIsPseudo;
@@ -582,9 +661,9 @@
                 return result;
             }
             result = new LocaleInfo(locale);
-            sLocaleCache.put(id, result);
+            localeInfos.put(id, result);
         } else {
-            result = sLocaleCache.get(id);
+            result = localeInfos.get(id);
         }
         return result;
     }
diff --git a/core/java/com/android/internal/app/SuggestedLocaleAdapter.java b/core/java/com/android/internal/app/SuggestedLocaleAdapter.java
index a61a6d7..08de4dfb 100644
--- a/core/java/com/android/internal/app/SuggestedLocaleAdapter.java
+++ b/core/java/com/android/internal/app/SuggestedLocaleAdapter.java
@@ -64,6 +64,7 @@
     protected ArrayList<LocaleStore.LocaleInfo> mOriginalLocaleOptions;
     protected int mSuggestionCount;
     protected final boolean mCountryMode;
+    protected boolean mIsNumberingMode;
     protected LayoutInflater mInflater;
 
     protected Locale mDisplayLocale = null;
@@ -89,6 +90,14 @@
         }
     }
 
+    public void setNumberingSystemMode(boolean isNumberSystemMode) {
+        mIsNumberingMode = isNumberSystemMode;
+    }
+
+    public boolean getIsForNumberingSystem() {
+        return mIsNumberingMode;
+    }
+
     @Override
     public boolean areAllItemsEnabled() {
         return false;
@@ -209,7 +218,6 @@
         if (convertView == null && mInflater == null) {
             mInflater = LayoutInflater.from(parent.getContext());
         }
-
         int itemType = getItemViewType(position);
         View itemView = getNewViewIfNeeded(convertView, parent, itemType, position);
         switch (itemType) {
@@ -217,13 +225,13 @@
             case TYPE_HEADER_ALL_OTHERS:
                 TextView textView = (TextView) itemView;
                 if (itemType == TYPE_HEADER_SUGGESTED) {
-                   if (mCountryMode) {
+                    if (mCountryMode && !mIsNumberingMode) {
                         setTextTo(textView, R.string.language_picker_regions_section_suggested);
                     } else {
                         setTextTo(textView, R.string.language_picker_section_suggested);
                     }
                 } else {
-                    if (mCountryMode) {
+                    if (mCountryMode && !mIsNumberingMode) {
                         setTextTo(textView, R.string.region_picker_section_all);
                     } else {
                         setTextTo(textView, R.string.language_picker_section_all);
@@ -419,9 +427,11 @@
 
     private void updateTextView(View convertView, TextView text, int position) {
         LocaleStore.LocaleInfo item = (LocaleStore.LocaleInfo) getItem(position);
-        text.setText(item.getLabel(mCountryMode));
+        text.setText(mIsNumberingMode
+                ? item.getNumberingSystem() : item.getLabel(mCountryMode));
         text.setTextLocale(item.getLocale());
-        text.setContentDescription(item.getContentDescription(mCountryMode));
+        text.setContentDescription(mIsNumberingMode
+                        ? item.getNumberingSystem() : item.getContentDescription(mCountryMode));
         if (mCountryMode) {
             int layoutDir = TextUtils.getLayoutDirectionFromLocale(item.getParent());
             //noinspection ResourceType
diff --git a/core/java/com/android/internal/inputmethod/ImeTracing.java b/core/java/com/android/internal/inputmethod/ImeTracing.java
index e6a9b54..db95012 100644
--- a/core/java/com/android/internal/inputmethod/ImeTracing.java
+++ b/core/java/com/android/internal/inputmethod/ImeTracing.java
@@ -62,11 +62,7 @@
             if (isSystemProcess()) {
                 sInstance = new ImeTracingServerImpl();
             } else {
-                try {
-                    sInstance = new ImeTracingClientImpl();
-                } catch (RuntimeException e) {
-                    Log.e(TAG, "Exception while creating ImeTracingClientImpl instance", e);
-                }
+                sInstance = new ImeTracingClientImpl();
             }
         }
         return sInstance;
diff --git a/core/jni/android_view_SurfaceControlHdrLayerInfoListener.cpp b/core/jni/android_view_SurfaceControlHdrLayerInfoListener.cpp
index adbd791..443f99a 100644
--- a/core/jni/android_view_SurfaceControlHdrLayerInfoListener.cpp
+++ b/core/jni/android_view_SurfaceControlHdrLayerInfoListener.cpp
@@ -42,12 +42,12 @@
         LOG_ALWAYS_FATAL_IF(env->GetJavaVM(&mVm) != JNI_OK, "Failed to GetJavaVm");
     }
 
-    binder::Status onHdrLayerInfoChanged(int numberOfHdrLayers, int maxW, int maxH,
-                                         int flags) override {
+    binder::Status onHdrLayerInfoChanged(int numberOfHdrLayers, int maxW, int maxH, int flags,
+                                         float maxDesiredHdrSdrRatio) override {
         JNIEnv* env = requireEnv();
 
         env->CallVoidMethod(mListener, gListenerClassInfo.mOnHdrInfoChanged, mDisplayToken,
-                            numberOfHdrLayers, maxW, maxH, flags);
+                            numberOfHdrLayers, maxW, maxH, flags, maxDesiredHdrSdrRatio);
 
         if (env->ExceptionCheck()) {
             ALOGE("SurfaceControlHdrLayerInfoListener.onHdrInfoChanged() failed.");
@@ -129,7 +129,7 @@
     jclass clazz = env->FindClass("android/view/SurfaceControlHdrLayerInfoListener");
     gListenerClassInfo.mClass = MakeGlobalRefOrDie(env, clazz);
     gListenerClassInfo.mOnHdrInfoChanged =
-            env->GetMethodID(clazz, "onHdrInfoChanged", "(Landroid/os/IBinder;IIII)V");
+            env->GetMethodID(clazz, "onHdrInfoChanged", "(Landroid/os/IBinder;IIIIF)V");
     return 0;
 }
 
diff --git a/core/jni/com_android_internal_os_Zygote.cpp b/core/jni/com_android_internal_os_Zygote.cpp
index 1b812d1..d62f1cf 100644
--- a/core/jni/com_android_internal_os_Zygote.cpp
+++ b/core/jni/com_android_internal_os_Zygote.cpp
@@ -1805,10 +1805,15 @@
     if (!is_system_server && getuid() == 0) {
         const int rc = createProcessGroup(uid, getpid());
         if (rc != 0) {
-            fail_fn(rc == -EROFS ? CREATE_ERROR("createProcessGroup failed, kernel missing "
-                                                "CONFIG_CGROUP_CPUACCT?")
-                                 : CREATE_ERROR("createProcessGroup(%d, %d) failed: %s", uid,
-                                                /* pid= */ 0, strerror(-rc)));
+            if (rc == -ESRCH) {
+                // If process is dead, treat this as a non-fatal error
+                ALOGE("createProcessGroup(%d, %d) failed: %s", uid, /* pid= */ 0, strerror(-rc));
+            } else {
+                fail_fn(rc == -EROFS ? CREATE_ERROR("createProcessGroup failed, kernel missing "
+                                                    "CONFIG_CGROUP_CPUACCT?")
+                                     : CREATE_ERROR("createProcessGroup(%d, %d) failed: %s", uid,
+                                                    /* pid= */ 0, strerror(-rc)));
+            }
         }
     }
 
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 5be18fb4..611035e 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -3161,7 +3161,7 @@
     <!-- @SystemApi @hide Allows an application to call APIs that allow it to query users on the
          device. -->
     <permission android:name="android.permission.QUERY_USERS"
-                android:protectionLevel="signature|role" />
+                android:protectionLevel="signature|privileged|role" />
 
     <!-- Allows an application to access data blobs across users. -->
     <permission android:name="android.permission.ACCESS_BLOBS_ACROSS_USERS"
@@ -7620,8 +7620,8 @@
 
     <!-- @SystemApi Allows to call APIs that log process lifecycle events
          @hide -->
-    <permission android:name="android.permission.LOG_PROCESS_ACTIVITIES"
-                android:protectionLevel="signature|privileged" />
+    <permission android:name="android.permission.LOG_FOREGROUND_RESOURCE_USE"
+                android:protectionLevel="signature|module" />
 
     <!-- @hide Allows an application to get type of any provider uri.
          <p>Not for use by third-party applications.
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 4d2747a..2ed1817 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -711,6 +711,9 @@
          mode. -->
     <integer name="config_unfoldTransitionHalfFoldedTimeout">1000</integer>
 
+    <!-- Timeout for receiving the keyguard drawn event from System UI.  -->
+    <integer name="config_keyguardDrawnTimeout">1000</integer>
+
     <!-- Indicates that the device supports having more than one internal display on at the same
          time. Only applicable to devices with more than one internal display. If this option is
          set to false, DisplayManager will make additional effort to ensure no more than 1 internal
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index a15833d..c10612e 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -1939,6 +1939,7 @@
   <java-symbol type="bool" name="config_allowTheaterModeWakeFromDock" />
   <java-symbol type="bool" name="config_allowTheaterModeWakeFromWindowLayout" />
   <java-symbol type="bool" name="config_keepDreamingWhenUndocking" />
+  <java-symbol type="integer" name="config_keyguardDrawnTimeout" />
   <java-symbol type="bool" name="config_goToSleepOnButtonPressTheaterMode" />
   <java-symbol type="bool" name="config_supportLongPressPowerWhenNonInteractive" />
   <java-symbol type="bool" name="config_wimaxEnabled" />
diff --git a/core/tests/coretests/src/android/app/backup/BackupRestoreEventLoggerTest.java b/core/tests/coretests/src/android/app/backup/BackupRestoreEventLoggerTest.java
index b176307..6e1c580 100644
--- a/core/tests/coretests/src/android/app/backup/BackupRestoreEventLoggerTest.java
+++ b/core/tests/coretests/src/android/app/backup/BackupRestoreEventLoggerTest.java
@@ -23,7 +23,6 @@
 
 import static junit.framework.Assert.fail;
 
-import android.app.backup.BackupRestoreEventLogger.BackupRestoreDataType;
 import android.app.backup.BackupRestoreEventLogger.DataTypeResult;
 import android.os.Parcel;
 import android.platform.test.annotations.Presubmit;
@@ -78,7 +77,7 @@
 
         mLogger.logItemsBackedUp(DATA_TYPE_1, /* count */ 5);
         mLogger.logItemsBackupFailed(DATA_TYPE_1, /* count */ 5, ERROR_1);
-        mLogger.logBackupMetaData(DATA_TYPE_1, /* metadata */ "metadata");
+        mLogger.logBackupMetadata(DATA_TYPE_1, /* metadata */ "metadata");
 
         assertThat(getResultForDataTypeIfPresent(mLogger, DATA_TYPE_1)).isEqualTo(Optional.empty());
     }
@@ -91,7 +90,7 @@
             String dataType = DATA_TYPE_1 + i;
             mLogger.logItemsBackedUp(dataType, /* count */ 5);
             mLogger.logItemsBackupFailed(dataType, /* count */ 5, /* error */ null);
-            mLogger.logBackupMetaData(dataType, METADATA_1);
+            mLogger.logBackupMetadata(dataType, METADATA_1);
 
             assertThat(getResultForDataTypeIfPresent(mLogger, dataType)).isNotEqualTo(
                     Optional.empty());
@@ -127,8 +126,8 @@
     public void testLogBackupMetadata_repeatedCalls_recordsLatestMetadataHash() {
         mLogger = new BackupRestoreEventLogger(BACKUP);
 
-        mLogger.logBackupMetaData(DATA_TYPE_1, METADATA_1);
-        mLogger.logBackupMetaData(DATA_TYPE_1, METADATA_2);
+        mLogger.logBackupMetadata(DATA_TYPE_1, METADATA_1);
+        mLogger.logBackupMetadata(DATA_TYPE_1, METADATA_2);
 
         byte[] recordedHash = getResultForDataType(mLogger, DATA_TYPE_1).getMetadataHash();
         byte[] expectedHash = getMetaDataHash(METADATA_2);
@@ -315,7 +314,7 @@
     }
 
     private static DataTypeResult getResultForDataType(
-            BackupRestoreEventLogger logger, @BackupRestoreDataType String dataType) {
+            BackupRestoreEventLogger logger, String dataType) {
         Optional<DataTypeResult> result = getResultForDataTypeIfPresent(logger, dataType);
         if (result.isEmpty()) {
             fail("Failed to find result for data type: " + dataType);
@@ -324,7 +323,7 @@
     }
 
     private static Optional<DataTypeResult> getResultForDataTypeIfPresent(
-            BackupRestoreEventLogger logger, @BackupRestoreDataType String dataType) {
+            BackupRestoreEventLogger logger, String dataType) {
         List<DataTypeResult> resultList = logger.getLoggingResults();
         return resultList.stream()
                 .filter(dataTypeResult -> dataTypeResult.getDataType().equals(dataType))
diff --git a/core/tests/coretests/src/android/view/stylus/HandwritingInitiatorTest.java b/core/tests/coretests/src/android/view/stylus/HandwritingInitiatorTest.java
index 76f5277..1ec2613 100644
--- a/core/tests/coretests/src/android/view/stylus/HandwritingInitiatorTest.java
+++ b/core/tests/coretests/src/android/view/stylus/HandwritingInitiatorTest.java
@@ -64,10 +64,19 @@
     private static final int HW_BOUNDS_OFFSETS_BOTTOM_PX = 40;
     private int mHandwritingSlop = 4;
 
-    private static final Rect sHwArea = new Rect(100, 200, 500, 500);
+    private static final Rect sHwArea1;
+    private static final Rect sHwArea2;
+
+    static {
+        sHwArea1 = new Rect(100, 200, 500, 500);
+        // The extended handwriting area bounds of the two views are overlapping.
+        int hwArea2Top = sHwArea1.bottom + HW_BOUNDS_OFFSETS_TOP_PX / 2;
+        sHwArea2 = new Rect(sHwArea1.left, hwArea2Top, sHwArea1.right, hwArea2Top + 300);
+    }
 
     private HandwritingInitiator mHandwritingInitiator;
-    private View mTestView;
+    private View mTestView1;
+    private View mTestView2;
     private Context mContext;
 
     @Before
@@ -82,20 +91,27 @@
         mHandwritingInitiator =
                 spy(new HandwritingInitiator(viewConfiguration, inputMethodManager));
 
-        mTestView = createView(sHwArea, true /* autoHandwritingEnabled */,
-                true /* isStylusHandwritingAvailable */,
+        mTestView1 = createView(sHwArea1, /* autoHandwritingEnabled= */ true,
+                /* isStylusHandwritingAvailable= */ true,
                 HW_BOUNDS_OFFSETS_LEFT_PX,
                 HW_BOUNDS_OFFSETS_TOP_PX,
                 HW_BOUNDS_OFFSETS_RIGHT_PX,
                 HW_BOUNDS_OFFSETS_BOTTOM_PX);
-        mHandwritingInitiator.updateHandwritingAreasForView(mTestView);
+        mTestView2 = createView(sHwArea2, /* autoHandwritingEnabled= */ true,
+                /* isStylusHandwritingAvailable= */ true,
+                HW_BOUNDS_OFFSETS_LEFT_PX,
+                HW_BOUNDS_OFFSETS_TOP_PX,
+                HW_BOUNDS_OFFSETS_RIGHT_PX,
+                HW_BOUNDS_OFFSETS_BOTTOM_PX);
+        mHandwritingInitiator.updateHandwritingAreasForView(mTestView1);
+        mHandwritingInitiator.updateHandwritingAreasForView(mTestView2);
     }
 
     @Test
     public void onTouchEvent_startHandwriting_when_stylusMoveOnce_withinHWArea() {
-        mHandwritingInitiator.onInputConnectionCreated(mTestView);
-        final int x1 = (sHwArea.left + sHwArea.right) / 2;
-        final int y1 = (sHwArea.top + sHwArea.bottom) / 2;
+        mHandwritingInitiator.onInputConnectionCreated(mTestView1);
+        final int x1 = (sHwArea1.left + sHwArea1.right) / 2;
+        final int y1 = (sHwArea1.top + sHwArea1.bottom) / 2;
         MotionEvent stylusEvent1 = createStylusEvent(ACTION_DOWN, x1, y1, 0);
         boolean onTouchEventResult1 = mHandwritingInitiator.onTouchEvent(stylusEvent1);
 
@@ -106,7 +122,7 @@
         boolean onTouchEventResult2 = mHandwritingInitiator.onTouchEvent(stylusEvent2);
 
         // Stylus movement within HandwritingArea should trigger IMM.startHandwriting once.
-        verify(mHandwritingInitiator, times(1)).startHandwriting(mTestView);
+        verify(mHandwritingInitiator, times(1)).startHandwriting(mTestView1);
         assertThat(onTouchEventResult1).isFalse();
         // After IMM.startHandwriting is triggered, onTouchEvent should return true for ACTION_MOVE
         // events so that the events are not dispatched to the view tree.
@@ -115,9 +131,9 @@
 
     @Test
     public void onTouchEvent_startHandwritingOnce_when_stylusMoveMultiTimes_withinHWArea() {
-        mHandwritingInitiator.onInputConnectionCreated(mTestView);
-        final int x1 = (sHwArea.left + sHwArea.right) / 2;
-        final int y1 = (sHwArea.top + sHwArea.bottom) / 2;
+        mHandwritingInitiator.onInputConnectionCreated(mTestView1);
+        final int x1 = (sHwArea1.left + sHwArea1.right) / 2;
+        final int y1 = (sHwArea1.top + sHwArea1.bottom) / 2;
         MotionEvent stylusEvent1 = createStylusEvent(ACTION_DOWN, x1, y1, 0);
         boolean onTouchEventResult1 = mHandwritingInitiator.onTouchEvent(stylusEvent1);
 
@@ -140,7 +156,7 @@
         boolean onTouchEventResult5 = mHandwritingInitiator.onTouchEvent(stylusEvent5);
 
         // It only calls startHandwriting once for each ACTION_DOWN.
-        verify(mHandwritingInitiator, times(1)).startHandwriting(mTestView);
+        verify(mHandwritingInitiator, times(1)).startHandwriting(mTestView1);
         assertThat(onTouchEventResult1).isFalse();
         // stylusEvent2 does not trigger IMM.startHandwriting since the touch slop distance has not
         // been exceeded. onTouchEvent should return false so that the event is dispatched to the
@@ -155,9 +171,9 @@
 
     @Test
     public void onTouchEvent_startHandwriting_when_stylusMove_withinExtendedHWArea() {
-        mHandwritingInitiator.onInputConnectionCreated(mTestView);
-        final int x1 = sHwArea.left - HW_BOUNDS_OFFSETS_LEFT_PX / 2;
-        final int y1 = sHwArea.top - HW_BOUNDS_OFFSETS_TOP_PX / 2;
+        mHandwritingInitiator.onInputConnectionCreated(mTestView1);
+        final int x1 = sHwArea1.left - HW_BOUNDS_OFFSETS_LEFT_PX / 2;
+        final int y1 = sHwArea1.top - HW_BOUNDS_OFFSETS_TOP_PX / 2;
         MotionEvent stylusEvent1 = createStylusEvent(ACTION_DOWN, x1, y1, 0);
         mHandwritingInitiator.onTouchEvent(stylusEvent1);
 
@@ -168,13 +184,13 @@
         mHandwritingInitiator.onTouchEvent(stylusEvent2);
 
         // Stylus movement within extended HandwritingArea should trigger IMM.startHandwriting once.
-        verify(mHandwritingInitiator, times(1)).startHandwriting(mTestView);
+        verify(mHandwritingInitiator, times(1)).startHandwriting(mTestView1);
     }
 
     @Test
     public void onTouchEvent_startHandwriting_inputConnectionBuiltAfterStylusMove() {
-        final int x1 = (sHwArea.left + sHwArea.right) / 2;
-        final int y1 = (sHwArea.top + sHwArea.bottom) / 2;
+        final int x1 = (sHwArea1.left + sHwArea1.right) / 2;
+        final int y1 = (sHwArea1.top + sHwArea1.bottom) / 2;
         MotionEvent stylusEvent1 = createStylusEvent(ACTION_DOWN, x1, y1, 0);
         mHandwritingInitiator.onTouchEvent(stylusEvent1);
 
@@ -184,15 +200,15 @@
         mHandwritingInitiator.onTouchEvent(stylusEvent2);
 
         // InputConnection is created after stylus movement.
-        mHandwritingInitiator.onInputConnectionCreated(mTestView);
+        mHandwritingInitiator.onInputConnectionCreated(mTestView1);
 
-        verify(mHandwritingInitiator, times(1)).startHandwriting(mTestView);
+        verify(mHandwritingInitiator, times(1)).startHandwriting(mTestView1);
     }
 
     @Test
     public void onTouchEvent_startHandwriting_inputConnectionBuilt_stylusMoveInExtendedHWArea() {
-        final int x1 = sHwArea.right + HW_BOUNDS_OFFSETS_RIGHT_PX / 2;
-        final int y1 = sHwArea.bottom + HW_BOUNDS_OFFSETS_BOTTOM_PX / 2;
+        final int x1 = sHwArea1.right + HW_BOUNDS_OFFSETS_RIGHT_PX / 2;
+        final int y1 = sHwArea1.bottom + HW_BOUNDS_OFFSETS_BOTTOM_PX / 2;
         MotionEvent stylusEvent1 = createStylusEvent(ACTION_DOWN, x1, y1, 0);
         mHandwritingInitiator.onTouchEvent(stylusEvent1);
 
@@ -202,9 +218,9 @@
         mHandwritingInitiator.onTouchEvent(stylusEvent2);
 
         // InputConnection is created after stylus movement.
-        mHandwritingInitiator.onInputConnectionCreated(mTestView);
+        mHandwritingInitiator.onInputConnectionCreated(mTestView1);
 
-        verify(mHandwritingInitiator, times(1)).startHandwriting(mTestView);
+        verify(mHandwritingInitiator, times(1)).startHandwriting(mTestView1);
     }
 
     @Test
@@ -212,11 +228,11 @@
         View delegateView = new View(mContext);
         delegateView.setIsHandwritingDelegate(true);
 
-        mTestView.setHandwritingDelegatorCallback(
+        mTestView1.setHandwritingDelegatorCallback(
                 () -> mHandwritingInitiator.onInputConnectionCreated(delegateView));
 
-        final int x1 = (sHwArea.left + sHwArea.right) / 2;
-        final int y1 = (sHwArea.top + sHwArea.bottom) / 2;
+        final int x1 = (sHwArea1.left + sHwArea1.right) / 2;
+        final int y1 = (sHwArea1.top + sHwArea1.bottom) / 2;
         MotionEvent stylusEvent1 = createStylusEvent(ACTION_DOWN, x1, y1, 0);
         mHandwritingInitiator.onTouchEvent(stylusEvent1);
 
@@ -249,12 +265,12 @@
 
         // Stylus movement within HandwritingArea should not trigger IMM.startHandwriting since
         // the current IME doesn't support handwriting.
-        verify(mHandwritingInitiator, never()).startHandwriting(mTestView);
+        verify(mHandwritingInitiator, never()).startHandwriting(mTestView1);
     }
 
     @Test
     public void onTouchEvent_notStartHandwriting_when_stylusTap_withinHWArea() {
-        mHandwritingInitiator.onInputConnectionCreated(mTestView);
+        mHandwritingInitiator.onInputConnectionCreated(mTestView1);
         final int x1 = 200;
         final int y1 = 200;
         MotionEvent stylusEvent1 = createStylusEvent(ACTION_DOWN, x1, y1, 0);
@@ -265,12 +281,12 @@
         MotionEvent stylusEvent2 = createStylusEvent(ACTION_UP, x2, y2, 0);
         mHandwritingInitiator.onTouchEvent(stylusEvent2);
 
-        verify(mHandwritingInitiator, never()).startHandwriting(mTestView);
+        verify(mHandwritingInitiator, never()).startHandwriting(mTestView1);
     }
 
     @Test
     public void onTouchEvent_notStartHandwriting_when_stylusMove_outOfHWArea() {
-        mHandwritingInitiator.onInputConnectionCreated(mTestView);
+        mHandwritingInitiator.onInputConnectionCreated(mTestView1);
         final int x1 = 10;
         final int y1 = 10;
         MotionEvent stylusEvent1 = createStylusEvent(ACTION_DOWN, x1, y1, 0);
@@ -281,12 +297,12 @@
         MotionEvent stylusEvent2 = createStylusEvent(ACTION_MOVE, x2, y2, 0);
         mHandwritingInitiator.onTouchEvent(stylusEvent2);
 
-        verify(mHandwritingInitiator, never()).startHandwriting(mTestView);
+        verify(mHandwritingInitiator, never()).startHandwriting(mTestView1);
     }
 
     @Test
     public void onTouchEvent_notStartHandwriting_when_stylusMove_afterTimeOut() {
-        mHandwritingInitiator.onInputConnectionCreated(mTestView);
+        mHandwritingInitiator.onInputConnectionCreated(mTestView1);
         final int x1 = 10;
         final int y1 = 10;
         final long time1 = 10L;
@@ -300,13 +316,13 @@
         mHandwritingInitiator.onTouchEvent(stylusEvent2);
 
         // stylus movement is after TAP_TIMEOUT it shouldn't call startHandwriting.
-        verify(mHandwritingInitiator, never()).startHandwriting(mTestView);
+        verify(mHandwritingInitiator, never()).startHandwriting(mTestView1);
     }
 
     @Test
     public void onTouchEvent_focusView_stylusMoveOnce_withinHWArea() {
-        final int x1 = (sHwArea.left + sHwArea.right) / 2;
-        final int y1 = (sHwArea.top + sHwArea.bottom) / 2;
+        final int x1 = (sHwArea1.left + sHwArea1.right) / 2;
+        final int y1 = (sHwArea1.top + sHwArea1.bottom) / 2;
         MotionEvent stylusEvent1 = createStylusEvent(ACTION_DOWN, x1, y1, 0);
         mHandwritingInitiator.onTouchEvent(stylusEvent1);
 
@@ -317,13 +333,34 @@
         mHandwritingInitiator.onTouchEvent(stylusEvent2);
 
         // HandwritingInitiator will request focus for the registered view.
-        verify(mTestView, times(1)).requestFocus();
+        verify(mTestView1, times(1)).requestFocus();
+    }
+
+    @Test
+    public void onTouchEvent_focusView_inputConnectionAlreadyBuilt_stylusMoveOnce_withinHWArea() {
+        mHandwritingInitiator.onInputConnectionCreated(mTestView1);
+
+        final int x1 = (sHwArea1.left + sHwArea1.right) / 2;
+        final int y1 = (sHwArea1.top + sHwArea1.bottom) / 2;
+        MotionEvent stylusEvent1 = createStylusEvent(ACTION_DOWN, x1, y1, 0);
+        mHandwritingInitiator.onTouchEvent(stylusEvent1);
+
+        final int x2 = x1 + mHandwritingSlop * 2;
+        final int y2 = y1;
+
+        MotionEvent stylusEvent2 = createStylusEvent(ACTION_MOVE, x2, y2, 0);
+        mHandwritingInitiator.onTouchEvent(stylusEvent2);
+
+        // View has input connection but not focus, so HandwritingInitiator will request focus
+        // before starting handwriting.
+        verify(mTestView1).requestFocus();
+        verify(mHandwritingInitiator).startHandwriting(mTestView1);
     }
 
     @Test
     public void onTouchEvent_focusView_stylusMoveOnce_withinExtendedHWArea() {
-        final int x1 = sHwArea.left - HW_BOUNDS_OFFSETS_LEFT_PX / 2;
-        final int y1 = sHwArea.top - HW_BOUNDS_OFFSETS_TOP_PX / 2;
+        final int x1 = sHwArea1.left - HW_BOUNDS_OFFSETS_LEFT_PX / 2;
+        final int y1 = sHwArea1.top - HW_BOUNDS_OFFSETS_TOP_PX / 2;
         MotionEvent stylusEvent1 = createStylusEvent(ACTION_DOWN, x1, y1, 0);
         mHandwritingInitiator.onTouchEvent(stylusEvent1);
 
@@ -334,16 +371,16 @@
         mHandwritingInitiator.onTouchEvent(stylusEvent2);
 
         // HandwritingInitiator will request focus for the registered view.
-        verify(mTestView, times(1)).requestFocus();
+        verify(mTestView1, times(1)).requestFocus();
     }
 
     @Test
-    public void autoHandwriting_whenDisabled_wontStartHW() {
-        View mockView = createView(sHwArea, false /* autoHandwritingEnabled */,
-                true /* isStylusHandwritingAvailable */);
-        mHandwritingInitiator.onInputConnectionCreated(mockView);
-        final int x1 = (sHwArea.left + sHwArea.right) / 2;
-        final int y1 = (sHwArea.top + sHwArea.bottom) / 2;
+    public void onTouchEvent_handwritingAreaOverlapped_initiateForCloserView() {
+        // The ACTION_DOWN location is within the handwriting bounds of both mTestView1 and
+        // mTestView2. Because it's closer to mTestView2's handwriting bounds, handwriting is
+        // initiated for mTestView2.
+        final int x1 = (sHwArea1.left + sHwArea1.right) / 2;
+        final int y1 = sHwArea1.bottom + HW_BOUNDS_OFFSETS_BOTTOM_PX - 1;
         MotionEvent stylusEvent1 = createStylusEvent(ACTION_DOWN, x1, y1, 0);
         mHandwritingInitiator.onTouchEvent(stylusEvent1);
 
@@ -353,14 +390,57 @@
         MotionEvent stylusEvent2 = createStylusEvent(ACTION_MOVE, x2, y2, 0);
         mHandwritingInitiator.onTouchEvent(stylusEvent2);
 
-        verify(mHandwritingInitiator, never()).startHandwriting(mTestView);
+        verify(mTestView2, times(1)).requestFocus();
+
+        mHandwritingInitiator.onInputConnectionCreated(mTestView2);
+        verify(mHandwritingInitiator, times(1)).startHandwriting(mTestView2);
+    }
+
+    @Test
+    public void onTouchEvent_handwritingAreaOverlapped_focusedViewHasPriority() {
+        // Simulate the case where mTestView1 is focused.
+        mHandwritingInitiator.onInputConnectionCreated(mTestView1);
+        // The ACTION_DOWN location is within the handwriting bounds of both mTestView1 and
+        // mTestView2. Although it's closer to mTestView2's handwriting bounds, handwriting is
+        // initiated for mTestView1 because it's focused.
+        final int x1 = (sHwArea1.left + sHwArea1.right) / 2;
+        final int y1 = sHwArea1.bottom + HW_BOUNDS_OFFSETS_BOTTOM_PX - 1;
+        MotionEvent stylusEvent1 = createStylusEvent(ACTION_DOWN, x1, y1, 0);
+        mHandwritingInitiator.onTouchEvent(stylusEvent1);
+
+        final int x2 = x1 + mHandwritingSlop * 2;
+        final int y2 = y1;
+
+        MotionEvent stylusEvent2 = createStylusEvent(ACTION_MOVE, x2, y2, 0);
+        mHandwritingInitiator.onTouchEvent(stylusEvent2);
+
+        verify(mHandwritingInitiator, times(1)).startHandwriting(mTestView1);
+    }
+
+    @Test
+    public void autoHandwriting_whenDisabled_wontStartHW() {
+        View mockView = createView(sHwArea1, false /* autoHandwritingEnabled */,
+                true /* isStylusHandwritingAvailable */);
+        mHandwritingInitiator.onInputConnectionCreated(mockView);
+        final int x1 = (sHwArea1.left + sHwArea1.right) / 2;
+        final int y1 = (sHwArea1.top + sHwArea1.bottom) / 2;
+        MotionEvent stylusEvent1 = createStylusEvent(ACTION_DOWN, x1, y1, 0);
+        mHandwritingInitiator.onTouchEvent(stylusEvent1);
+
+        final int x2 = x1 + mHandwritingSlop * 2;
+        final int y2 = y1;
+
+        MotionEvent stylusEvent2 = createStylusEvent(ACTION_MOVE, x2, y2, 0);
+        mHandwritingInitiator.onTouchEvent(stylusEvent2);
+
+        verify(mHandwritingInitiator, never()).startHandwriting(mTestView1);
     }
 
     @Test
     public void onInputConnectionCreated() {
-        mHandwritingInitiator.onInputConnectionCreated(mTestView);
+        mHandwritingInitiator.onInputConnectionCreated(mTestView1);
         assertThat(mHandwritingInitiator.mConnectedView).isNotNull();
-        assertThat(mHandwritingInitiator.mConnectedView.get()).isEqualTo(mTestView);
+        assertThat(mHandwritingInitiator.mConnectedView.get()).isEqualTo(mTestView1);
     }
 
     @Test
@@ -375,8 +455,8 @@
 
     @Test
     public void onInputConnectionClosed() {
-        mHandwritingInitiator.onInputConnectionCreated(mTestView);
-        mHandwritingInitiator.onInputConnectionClosed(mTestView);
+        mHandwritingInitiator.onInputConnectionCreated(mTestView1);
+        mHandwritingInitiator.onInputConnectionClosed(mTestView1);
 
         assertThat(mHandwritingInitiator.mConnectedView).isNull();
     }
@@ -396,12 +476,12 @@
         // When IMM restarts input connection, View#onInputConnectionCreatedInternal might be
         // called before View#onInputConnectionClosedInternal. As a result, we need to handle the
         // case where "one view "2 InputConnections".
-        mHandwritingInitiator.onInputConnectionCreated(mTestView);
-        mHandwritingInitiator.onInputConnectionCreated(mTestView);
-        mHandwritingInitiator.onInputConnectionClosed(mTestView);
+        mHandwritingInitiator.onInputConnectionCreated(mTestView1);
+        mHandwritingInitiator.onInputConnectionCreated(mTestView1);
+        mHandwritingInitiator.onInputConnectionClosed(mTestView1);
 
         assertThat(mHandwritingInitiator.mConnectedView).isNotNull();
-        assertThat(mHandwritingInitiator.mConnectedView.get()).isEqualTo(mTestView);
+        assertThat(mHandwritingInitiator.mConnectedView.get()).isEqualTo(mTestView1);
     }
 
     private MotionEvent createStylusEvent(int action, int x, int y, long eventTime) {
diff --git a/core/tests/coretests/src/com/android/internal/accessibility/AccessibilityShortcutChooserActivityTest.java b/core/tests/coretests/src/com/android/internal/accessibility/AccessibilityShortcutChooserActivityTest.java
index 973b904..03d366e6 100644
--- a/core/tests/coretests/src/com/android/internal/accessibility/AccessibilityShortcutChooserActivityTest.java
+++ b/core/tests/coretests/src/com/android/internal/accessibility/AccessibilityShortcutChooserActivityTest.java
@@ -33,6 +33,9 @@
 import static org.hamcrest.Matchers.endsWith;
 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.Mockito.verify;
 import static org.mockito.Mockito.when;
 
 import android.accessibilityservice.AccessibilityServiceInfo;
@@ -54,6 +57,7 @@
 import com.android.internal.R;
 import com.android.internal.accessibility.dialog.AccessibilityShortcutChooserActivity;
 
+import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -71,6 +75,7 @@
     private static final String ONE_HANDED_MODE = "One-Handed mode";
     private static final String TEST_LABEL = "TEST_LABEL";
     private static final ComponentName TEST_COMPONENT_NAME = new ComponentName("package", "class");
+    private TestAccessibilityShortcutChooserActivity mActivity;
 
     @Rule
     public final MockitoRule mMockitoRule = MockitoJUnit.rule();
@@ -85,10 +90,22 @@
     @Mock
     private IAccessibilityManager mAccessibilityManagerService;
 
+    @Before
+    public void setUp() throws Exception {
+        when(mAccessibilityServiceInfo.getResolveInfo()).thenReturn(mResolveInfo);
+        mResolveInfo.serviceInfo = mServiceInfo;
+        mServiceInfo.applicationInfo = mApplicationInfo;
+        when(mResolveInfo.loadLabel(any(PackageManager.class))).thenReturn(TEST_LABEL);
+        when(mAccessibilityServiceInfo.getComponentName()).thenReturn(TEST_COMPONENT_NAME);
+        when(mAccessibilityManagerService.getInstalledAccessibilityServiceList(
+                anyInt())).thenReturn(Collections.singletonList(mAccessibilityServiceInfo));
+        when(mAccessibilityManagerService.isAccessibilityTargetAllowed(
+                anyString(), anyInt(), anyInt())).thenReturn(true);
+        TestAccessibilityShortcutChooserActivity.setupForTesting(mAccessibilityManagerService);
+    }
+
     @Test
-    public void doubleClickTestServiceAndClickDenyButton_permissionDialogDoesNotExist()
-            throws Exception {
-        configureTestService();
+    public void doubleClickTestServiceAndClickDenyButton_permissionDialogDoesNotExist() {
         final ActivityScenario<TestAccessibilityShortcutChooserActivity> scenario =
                 ActivityScenario.launch(TestAccessibilityShortcutChooserActivity.class);
         scenario.moveToState(Lifecycle.State.CREATED);
@@ -101,6 +118,7 @@
         onView(withText(TEST_LABEL)).perform(scrollTo(), doubleClick());
         onView(withId(R.id.accessibility_permission_enable_deny_button)).perform(scrollTo(),
                 click());
+        InstrumentationRegistry.getInstrumentation().waitForIdleSync();
 
         onView(withId(R.id.accessibility_permissionDialog_title)).inRoot(isDialog()).check(
                 doesNotExist());
@@ -108,6 +126,29 @@
     }
 
     @Test
+    public void clickServiceTarget_notPermittedByAdmin_sendRestrictedDialogIntent()
+            throws Exception {
+        when(mAccessibilityManagerService.isAccessibilityTargetAllowed(
+                eq(TEST_COMPONENT_NAME.getPackageName()), anyInt(), anyInt())).thenReturn(false);
+        final ActivityScenario<TestAccessibilityShortcutChooserActivity> scenario =
+                ActivityScenario.launch(TestAccessibilityShortcutChooserActivity.class);
+        scenario.onActivity(activity -> mActivity = activity);
+        scenario.moveToState(Lifecycle.State.CREATED);
+        scenario.moveToState(Lifecycle.State.STARTED);
+        scenario.moveToState(Lifecycle.State.RESUMED);
+
+        onView(withText(R.string.accessibility_select_shortcut_menu_title)).inRoot(
+                isDialog()).check(matches(isDisplayed()));
+        onView(withText(R.string.edit_accessibility_shortcut_menu_button)).perform(click());
+        InstrumentationRegistry.getInstrumentation().waitForIdleSync();
+
+        onView(withText(TEST_LABEL)).perform(scrollTo(), click());
+        verify(mAccessibilityManagerService).sendRestrictedDialogIntent(
+                eq(TEST_COMPONENT_NAME.getPackageName()), anyInt(), anyInt());
+        scenario.moveToState(Lifecycle.State.DESTROYED);
+    }
+
+    @Test
     public void popEditShortcutMenuList_oneHandedModeEnabled_shouldBeInListView() {
         TestUtils.setOneHandedModeEnabled(this, /* enabled= */ true);
         final ActivityScenario<TestAccessibilityShortcutChooserActivity> scenario =
@@ -145,18 +186,6 @@
         scenario.moveToState(Lifecycle.State.DESTROYED);
     }
 
-    private void configureTestService() throws Exception {
-        when(mAccessibilityServiceInfo.getResolveInfo()).thenReturn(mResolveInfo);
-        mResolveInfo.serviceInfo = mServiceInfo;
-        mServiceInfo.applicationInfo = mApplicationInfo;
-        when(mResolveInfo.loadLabel(any(PackageManager.class))).thenReturn(TEST_LABEL);
-        when(mAccessibilityServiceInfo.getComponentName()).thenReturn(TEST_COMPONENT_NAME);
-        when(mAccessibilityManagerService.getInstalledAccessibilityServiceList(
-                anyInt())).thenReturn(Collections.singletonList(mAccessibilityServiceInfo));
-
-        TestAccessibilityShortcutChooserActivity.setupForTesting(mAccessibilityManagerService);
-    }
-
     /**
      * Used for testing.
      */
diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml
index 4a98c4d..5d303cf 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -275,6 +275,7 @@
         <!-- Permission required to test onPermissionsChangedListener -->
         <permission name="android.permission.OBSERVE_GRANT_REVOKE_PERMISSIONS"/>
         <permission name="android.permission.INTERACT_ACROSS_USERS"/>
+        <permission name="android.permission.QUERY_USERS"/>
         <permission name="android.permission.LOCAL_MAC_ADDRESS"/>
         <permission name="android.permission.MANAGE_ACCESSIBILITY"/>
         <permission name="android.permission.MANAGE_DEVICE_ADMINS"/>
@@ -511,6 +512,8 @@
         <permission name="android.permission.WRITE_APN_SETTINGS"/>
         <!-- Permission required for GTS test - GtsStatsdHostTestCases -->
         <permission name="android.permission.READ_RESTRICTED_STATS"/>
+        <!-- Permission required for CTS test -->
+        <permission name="android.permission.LOG_FOREGROUND_RESOURCE_USE"/>
     </privapp-permissions>
 
     <privapp-permissions package="com.android.statementservice">
diff --git a/data/keyboards/Vendor_004c_Product_0265.idc b/data/keyboards/Vendor_004c_Product_0265.idc
new file mode 120000
index 0000000..707dfcf
--- /dev/null
+++ b/data/keyboards/Vendor_004c_Product_0265.idc
@@ -0,0 +1 @@
+Vendor_05ac_Product_0265.idc
\ No newline at end of file
diff --git a/data/keyboards/Vendor_03f6_Product_a001.idc b/data/keyboards/Vendor_03f6_Product_a001.idc
new file mode 100644
index 0000000..bcb4ee3
--- /dev/null
+++ b/data/keyboards/Vendor_03f6_Product_a001.idc
@@ -0,0 +1,22 @@
+# Copyright 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.
+
+#
+# Brydge Touchpad
+#
+
+# Reports from this touchpad sometimes get bunched together due to Bluetooth
+# batching, leading to bad timestamps that mess up finger velocity calculations.
+# To fix this, set a fake delta using the touchpad's known report rate.
+gestureProp.Fake_Timestamp_Delta = 0.010
diff --git a/data/keyboards/Vendor_046d_Product_4011.idc b/data/keyboards/Vendor_046d_Product_4011.idc
new file mode 100644
index 0000000..3a23830
--- /dev/null
+++ b/data/keyboards/Vendor_046d_Product_4011.idc
@@ -0,0 +1,32 @@
+# Copyright 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.
+
+#
+# Logitech Wireless Touchpad
+#
+
+gestureProp.Touchpad_Stack_Version = 1
+gestureProp.IIR_b0 = 1
+gestureProp.IIR_b1 = 0
+gestureProp.IIR_b2 = 0
+gestureProp.IIR_b3 = 0
+gestureProp.IIR_a1 = 0
+gestureProp.IIR_a2 = 0
+gestureProp.Pressure_Calibration_Offset = -313.240741792594
+gestureProp.Pressure_Calibration_Slope = 4.39678062436752
+gestureProp.Max_Allowed_Pressure_Change_Per_Sec = 100000.0
+gestureProp.Max_Hysteresis_Pressure_Per_Sec = 100000.0
+gestureProp.Palm_Pressure = 100000.0
+gestureProp.Two_Finger_Vertical_Close_Distance_Thresh = 35.0
+gestureProp.Fling_Buffer_Suppress_Zero_Length_Scrolls = 0
diff --git a/data/keyboards/Vendor_046d_Product_4101.idc b/data/keyboards/Vendor_046d_Product_4101.idc
new file mode 100644
index 0000000..47e2530
--- /dev/null
+++ b/data/keyboards/Vendor_046d_Product_4101.idc
@@ -0,0 +1,31 @@
+# Copyright 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.
+
+#
+# Logitech T650
+#
+
+gestureProp.Touchpad_Stack_Version = 1
+gestureProp.IIR_b0 = 1
+gestureProp.IIR_b1 = 0
+gestureProp.IIR_b2 = 0
+gestureProp.IIR_b3 = 0
+gestureProp.IIR_a1 = 0
+gestureProp.IIR_a2 = 0
+gestureProp.Pressure_Calibration_Offset = -0.439288351750068
+gestureProp.Pressure_Calibration_Slope = 3.05998553523335
+gestureProp.Max_Allowed_Pressure_Change_Per_Sec = 100000.0
+gestureProp.Max_Hysteresis_Pressure_Per_Sec = 100000.0
+gestureProp.Two_Finger_Vertical_Close_Distance_Thresh = 35.0
+gestureProp.Fling_Buffer_Suppress_Zero_Length_Scrolls = 0
diff --git a/data/keyboards/Vendor_046d_Product_4102.idc b/data/keyboards/Vendor_046d_Product_4102.idc
new file mode 100644
index 0000000..e33a28a
--- /dev/null
+++ b/data/keyboards/Vendor_046d_Product_4102.idc
@@ -0,0 +1,24 @@
+# Copyright 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.
+
+#
+# Logitech TK820
+#
+
+gestureProp.Touchpad_Stack_Version = 2
+# Pressure jumps around a lot on this touchpad, so allow that:
+gestureProp.Max_Allowed_Pressure_Change_Per_Sec = 100000.0
+gestureProp.Max_Hysteresis_Pressure_Per_Sec = 100000.0
+gestureProp.Pressure_Calibration_Offset = -18.8078435
+gestureProp.Pressure_Calibration_Slope = 2.466208137
diff --git a/data/keyboards/Vendor_046d_Product_b00c.idc b/data/keyboards/Vendor_046d_Product_b00c.idc
new file mode 100644
index 0000000..a49970c
--- /dev/null
+++ b/data/keyboards/Vendor_046d_Product_b00c.idc
@@ -0,0 +1,31 @@
+# Copyright 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.
+
+#
+# Logitech T651
+#
+
+gestureProp.Touchpad_Stack_Version = 1
+gestureProp.IIR_b0 = 1
+gestureProp.IIR_b1 = 0
+gestureProp.IIR_b2 = 0
+gestureProp.IIR_b3 = 0
+gestureProp.IIR_a1 = 0
+gestureProp.IIR_a2 = 0
+gestureProp.Pressure_Calibration_Offset = -4.46520447177073
+gestureProp.Pressure_Calibration_Slope = 3.21071719332644
+gestureProp.Max_Allowed_Pressure_Change_Per_Sec = 100000.0
+gestureProp.Max_Hysteresis_Pressure_Per_Sec = 100000.0
+gestureProp.Two_Finger_Vertical_Close_Distance_Thresh = 35.0
+gestureProp.Fling_Buffer_Suppress_Zero_Length_Scrolls = 0
diff --git a/data/keyboards/Vendor_05ac_Product_0265.idc b/data/keyboards/Vendor_05ac_Product_0265.idc
new file mode 100644
index 0000000..d72de64
--- /dev/null
+++ b/data/keyboards/Vendor_05ac_Product_0265.idc
@@ -0,0 +1,34 @@
+# Copyright 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.
+
+#
+# Apple Magic Trackpad 2 configuration file
+#   Bluetooth vendor ID 004c
+#   USB vendor ID 05ac
+#
+
+gestureProp.Pressure_Calibration_Offset = 30
+gestureProp.Palm_Pressure = 250.0
+gestureProp.Palm_Width = 20.0
+gestureProp.Multiple_Palm_Width = 20.0
+
+# Enable Stationary Wiggle Filter
+gestureProp.Stationary_Wiggle_Filter_Enabled = 1
+gestureProp.Finger_Moving_Energy = 0.0008
+gestureProp.Finger_Moving_Hysteresis = 0.0004
+
+# Avoid accidental scroll/move on finger lift
+gestureProp.Max_Stationary_Move_Speed = 47
+gestureProp.Max_Stationary_Move_Speed_Hysteresis = 1
+gestureProp.Max_Stationary_Move_Suppress_Distance = 0.2
diff --git a/data/keyboards/Vendor_05ac_Product_030e.idc b/data/keyboards/Vendor_05ac_Product_030e.idc
new file mode 100644
index 0000000..23a2e18
--- /dev/null
+++ b/data/keyboards/Vendor_05ac_Product_030e.idc
@@ -0,0 +1,38 @@
+# Copyright 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.
+
+#
+# Apple Magic Trackpad
+#
+
+gestureProp.Touchpad_Stack_Version = 1
+# We are using raw touch major value as pressure value, so set the Palm
+# pressure threshold high.
+gestureProp.Palm_Pressure = 1000
+gestureProp.Compute_Surface_Area_from_Pressure = 0
+gestureProp.IIR_b0 = 1
+gestureProp.IIR_b1 = 0
+gestureProp.IIR_b2 = 0
+gestureProp.IIR_b3 = 0
+gestureProp.IIR_a1 = 0
+gestureProp.IIR_a2 = 0
+# NOTE: bias on X-axis is uncalibrated
+gestureProp.Touchpad_Device_Output_Bias_on_X-Axis = -283.3226025266607
+gestureProp.Touchpad_Device_Output_Bias_on_Y-Axis = -283.3226025266607
+gestureProp.Max_Allowed_Pressure_Change_Per_Sec = 100000.0
+gestureProp.Max_Hysteresis_Pressure_Per_Sec = 100000.0
+# Drumroll suppression causes janky movement on this touchpad.
+gestureProp.Drumroll_Suppression_Enable = 0
+gestureProp.Two_Finger_Vertical_Close_Distance_Thresh = 35.0
+gestureProp.Fling_Buffer_Suppress_Zero_Length_Scrolls = 0
diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java b/keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java
index c1f6c29..c3b0f9b 100644
--- a/keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java
+++ b/keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java
@@ -808,9 +808,12 @@
                         KeymasterDefs.KM_TAG_ATTESTATION_ID_BRAND,
                         platformReportedBrand.getBytes(StandardCharsets.UTF_8)
                 ));
+                final String platformReportedDevice =
+                        isPropertyEmptyOrUnknown(Build.DEVICE_FOR_ATTESTATION)
+                                ? Build.DEVICE : Build.DEVICE_FOR_ATTESTATION;
                 params.add(KeyStore2ParameterUtils.makeBytes(
                         KeymasterDefs.KM_TAG_ATTESTATION_ID_DEVICE,
-                        Build.DEVICE.getBytes(StandardCharsets.UTF_8)
+                        platformReportedDevice.getBytes(StandardCharsets.UTF_8)
                 ));
                 final String platformReportedProduct =
                         isPropertyEmptyOrUnknown(Build.PRODUCT_FOR_ATTESTATION)
@@ -819,9 +822,12 @@
                         KeymasterDefs.KM_TAG_ATTESTATION_ID_PRODUCT,
                         platformReportedProduct.getBytes(StandardCharsets.UTF_8)
                 ));
+                final String platformReportedManufacturer =
+                        isPropertyEmptyOrUnknown(Build.MANUFACTURER_FOR_ATTESTATION)
+                                ? Build.MANUFACTURER : Build.MANUFACTURER_FOR_ATTESTATION;
                 params.add(KeyStore2ParameterUtils.makeBytes(
                         KeymasterDefs.KM_TAG_ATTESTATION_ID_MANUFACTURER,
-                        Build.MANUFACTURER.getBytes(StandardCharsets.UTF_8)
+                        platformReportedManufacturer.getBytes(StandardCharsets.UTF_8)
                 ));
                 final String platformReportedModel =
                         isPropertyEmptyOrUnknown(Build.MODEL_FOR_ATTESTATION)
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/area/WindowAreaComponentImpl.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/area/WindowAreaComponentImpl.java
index 274dcae..575b0ce 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/area/WindowAreaComponentImpl.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/area/WindowAreaComponentImpl.java
@@ -263,8 +263,10 @@
                 return;
             }
             @WindowAreaStatus int currentStatus = getCurrentRearDisplayPresentationModeStatus();
+            DisplayMetrics metrics =
+                    currentStatus == STATUS_UNSUPPORTED ? null : getRearDisplayMetrics();
             consumer.accept(
-                    new RearDisplayPresentationStatus(currentStatus, getRearDisplayMetrics()));
+                    new RearDisplayPresentationStatus(currentStatus, metrics));
         }
     }
 
@@ -408,6 +410,10 @@
 
     @GuardedBy("mLock")
     private int getCurrentRearDisplayModeStatus() {
+        if (mRearDisplayState == INVALID_DEVICE_STATE) {
+            return WindowAreaComponent.STATUS_UNSUPPORTED;
+        }
+
         if (mRearDisplaySessionStatus == WindowAreaComponent.SESSION_STATE_ACTIVE
                 || !ArrayUtils.contains(mCurrentSupportedDeviceStates, mRearDisplayState)
                 || isRearDisplayActive()) {
@@ -441,6 +447,10 @@
 
     @GuardedBy("mLock")
     private int getCurrentRearDisplayPresentationModeStatus() {
+        if (mConcurrentDisplayState == INVALID_DEVICE_STATE) {
+            return WindowAreaComponent.STATUS_UNSUPPORTED;
+        }
+
         if (mCurrentDeviceState == mConcurrentDisplayState
                 || !ArrayUtils.contains(mCurrentSupportedDeviceStates, mConcurrentDisplayState)
                 || isDeviceFolded()) {
diff --git a/libs/WindowManager/Shell/OWNERS b/libs/WindowManager/Shell/OWNERS
index 4b12590..852edef 100644
--- a/libs/WindowManager/Shell/OWNERS
+++ b/libs/WindowManager/Shell/OWNERS
@@ -1,4 +1,4 @@
 xutan@google.com
 
 # Give submodule owners in shell resource approval
-per-file res*/*/*.xml = hwwang@google.com, lbill@google.com, madym@google.com
+per-file res*/*/*.xml = hwwang@google.com, jorgegil@google.com, lbill@google.com, madym@google.com
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/TabletopModeController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/TabletopModeController.java
index bf226283..cb1a6e7 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/TabletopModeController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/TabletopModeController.java
@@ -22,10 +22,12 @@
 import static com.android.wm.shell.common.DevicePostureController.DEVICE_POSTURE_UNKNOWN;
 import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_FOLDABLE;
 
+import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.app.WindowConfiguration;
 import android.content.Context;
 import android.content.res.Configuration;
+import android.os.SystemProperties;
 import android.util.ArraySet;
 import android.view.Surface;
 
@@ -34,6 +36,8 @@
 import com.android.wm.shell.common.annotations.ShellMainThread;
 import com.android.wm.shell.sysui.ShellInit;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Set;
@@ -49,8 +53,34 @@
 public class TabletopModeController implements
         DevicePostureController.OnDevicePostureChangedListener,
         DisplayController.OnDisplaysChangedListener {
+    /**
+     * When {@code true}, floating windows like PiP would auto move to the position
+     * specified by {@link #PREFER_TOP_HALF_IN_TABLETOP} when in tabletop mode.
+     */
+    private static final boolean ENABLE_MOVE_FLOATING_WINDOW_IN_TABLETOP =
+            SystemProperties.getBoolean(
+                    "persist.wm.debug.enable_move_floating_window_in_tabletop", false);
+
+    /**
+     * Prefer the {@link #PREFERRED_TABLETOP_HALF_TOP} if this flag is enabled,
+     * {@link #PREFERRED_TABLETOP_HALF_BOTTOM} otherwise.
+     * See also {@link #getPreferredHalfInTabletopMode()}.
+     */
+    private static final boolean PREFER_TOP_HALF_IN_TABLETOP =
+            SystemProperties.getBoolean("persist.wm.debug.prefer_top_half_in_tabletop", true);
+
     private static final long TABLETOP_MODE_DELAY_MILLIS = 1_000;
 
+    @IntDef(prefix = {"PREFERRED_TABLETOP_HALF_"}, value = {
+            PREFERRED_TABLETOP_HALF_TOP,
+            PREFERRED_TABLETOP_HALF_BOTTOM
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface PreferredTabletopHalf {}
+
+    public static final int PREFERRED_TABLETOP_HALF_TOP = 0;
+    public static final int PREFERRED_TABLETOP_HALF_BOTTOM = 1;
+
     private final Context mContext;
 
     private final DevicePostureController mDevicePostureController;
@@ -132,6 +162,22 @@
         }
     }
 
+    /**
+     * @return {@code true} if floating windows like PiP would auto move to the position
+     * specified by {@link #getPreferredHalfInTabletopMode()} when in tabletop mode.
+     */
+    public boolean enableMoveFloatingWindowInTabletop() {
+        return ENABLE_MOVE_FLOATING_WINDOW_IN_TABLETOP;
+    }
+
+    /** @return Preferred half for floating windows like PiP when in tabletop mode. */
+    @PreferredTabletopHalf
+    public int getPreferredHalfInTabletopMode() {
+        return PREFER_TOP_HALF_IN_TABLETOP
+                ? PREFERRED_TABLETOP_HALF_TOP
+                : PREFERRED_TABLETOP_HALF_BOTTOM;
+    }
+
     /** Register {@link OnTabletopModeChangedListener} to listen for tabletop mode change. */
     public void registerOnTabletopModeChangedListener(
             @NonNull OnTabletopModeChangedListener listener) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
index ba0f073..7a83d10 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
@@ -45,6 +45,7 @@
 import com.android.wm.shell.common.ShellExecutor;
 import com.android.wm.shell.common.SyncTransactionQueue;
 import com.android.wm.shell.common.SystemWindows;
+import com.android.wm.shell.common.TabletopModeController;
 import com.android.wm.shell.common.TaskStackListenerImpl;
 import com.android.wm.shell.common.TransactionPool;
 import com.android.wm.shell.common.annotations.ShellBackgroundThread;
@@ -357,6 +358,7 @@
             TaskStackListenerImpl taskStackListener,
             PipParamsChangedForwarder pipParamsChangedForwarder,
             DisplayInsetsController displayInsetsController,
+            TabletopModeController pipTabletopController,
             Optional<OneHandedController> oneHandedController,
             @ShellMainThread ShellExecutor mainExecutor) {
         return Optional.ofNullable(PipController.create(
@@ -366,7 +368,7 @@
                 pipMotionHelper, pipMediaController, phonePipMenuController, pipTaskOrganizer,
                 pipTransitionState, pipTouchHandler, pipTransitionController,
                 windowManagerShellWrapper, taskStackListener, pipParamsChangedForwarder,
-                displayInsetsController, oneHandedController, mainExecutor));
+                displayInsetsController, pipTabletopController, oneHandedController, mainExecutor));
     }
 
     @WMSingleton
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/fullscreen/FullscreenTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/fullscreen/FullscreenTaskListener.java
index d094c22..998728d6 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/fullscreen/FullscreenTaskListener.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/fullscreen/FullscreenTaskListener.java
@@ -129,6 +129,7 @@
     public void onTaskInfoChanged(RunningTaskInfo taskInfo) {
         final State state = mTasks.get(taskInfo.taskId);
         final Point oldPositionInParent = state.mTaskInfo.positionInParent;
+        boolean oldVisible = state.mTaskInfo.isVisible;
 
         if (mWindowDecorViewModelOptional.isPresent()) {
             mWindowDecorViewModelOptional.get().onTaskInfoChanged(taskInfo);
@@ -138,12 +139,18 @@
         updateRecentsForVisibleFullscreenTask(taskInfo);
 
         final Point positionInParent = state.mTaskInfo.positionInParent;
-        if (!oldPositionInParent.equals(state.mTaskInfo.positionInParent)) {
+        boolean positionInParentChanged = !oldPositionInParent.equals(positionInParent);
+        boolean becameVisible = !oldVisible && state.mTaskInfo.isVisible;
+
+        if (becameVisible || positionInParentChanged) {
             mSyncQueue.runInSync(t -> {
                 if (!state.mLeash.isValid()) {
                     // Task vanished before sync completion
                     return;
                 }
+                if (becameVisible) {
+                    t.show(state.mLeash);
+                }
                 t.setPosition(state.mLeash, positionInParent.x, positionInParent.y);
             });
         }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsState.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsState.java
index f664808..f08742d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsState.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsState.java
@@ -43,7 +43,9 @@
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
 import java.util.Objects;
 import java.util.Set;
 import java.util.function.Consumer;
@@ -113,6 +115,12 @@
      * @see android.view.View#setPreferKeepClearRects
      */
     private final Set<Rect> mUnrestrictedKeepClearAreas = new ArraySet<>();
+    /**
+     * Additional to {@link #mUnrestrictedKeepClearAreas}, allow the caller to append named bounds
+     * as unrestricted keep clear area. Values in this map would be appended to
+     * {@link #getUnrestrictedKeepClearAreas()} and this is meant for internal usage only.
+     */
+    private final Map<String, Rect> mNamedUnrestrictedKeepClearAreas = new HashMap<>();
 
     private @Nullable Runnable mOnMinimalSizeChangeCallback;
     private @Nullable TriConsumer<Boolean, Integer, Boolean> mOnShelfVisibilityChangeCallback;
@@ -378,6 +386,16 @@
         mUnrestrictedKeepClearAreas.addAll(unrestrictedAreas);
     }
 
+    /** Add a named unrestricted keep clear area. */
+    public void addNamedUnrestrictedKeepClearArea(@NonNull String name, Rect unrestrictedArea) {
+        mNamedUnrestrictedKeepClearAreas.put(name, unrestrictedArea);
+    }
+
+    /** Remove a named unrestricted keep clear area. */
+    public void removeNamedUnrestrictedKeepClearArea(@NonNull String name) {
+        mNamedUnrestrictedKeepClearAreas.remove(name);
+    }
+
     @NonNull
     public Set<Rect> getRestrictedKeepClearAreas() {
         return mRestrictedKeepClearAreas;
@@ -385,7 +403,10 @@
 
     @NonNull
     public Set<Rect> getUnrestrictedKeepClearAreas() {
-        return mUnrestrictedKeepClearAreas;
+        if (mNamedUnrestrictedKeepClearAreas.isEmpty()) return mUnrestrictedKeepClearAreas;
+        final Set<Rect> unrestrictedAreas = new ArraySet<>(mUnrestrictedKeepClearAreas);
+        unrestrictedAreas.addAll(mNamedUnrestrictedKeepClearAreas.values());
+        return unrestrictedAreas;
     }
 
     /**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
index 2848f34..45bb73b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
@@ -489,10 +489,11 @@
         // Reparent the pip leash to the root with max layer so that we can animate it outside of
         // parent crop, and make sure it is not covered by other windows.
         final SurfaceControl pipLeash = pipChange.getLeash();
-        startTransaction.reparent(pipLeash, info.getRootLeash());
+        final int rootIdx = TransitionUtil.rootIndexFor(pipChange, info);
+        startTransaction.reparent(pipLeash, info.getRoot(rootIdx).getLeash());
         startTransaction.setLayer(pipLeash, Integer.MAX_VALUE);
         // Note: because of this, the bounds to animate should be translated to the root coordinate.
-        final Point offset = info.getRootOffset();
+        final Point offset = info.getRoot(rootIdx).getOffset();
         final Rect currentBounds = mPipBoundsState.getBounds();
         currentBounds.offset(-offset.x, -offset.y);
         startTransaction.setPosition(pipLeash, currentBounds.left, currentBounds.top);
@@ -797,8 +798,15 @@
             if (sourceHintRect == null) {
                 // We use content overlay when there is no source rect hint to enter PiP use bounds
                 // animation.
+                // TODO(b/272819817): cleanup the null-check and extra logging.
+                final boolean hasTopActivityInfo = taskInfo.topActivityInfo != null;
+                if (!hasTopActivityInfo) {
+                    ProtoLog.w(ShellProtoLogGroup.WM_SHELL_TRANSITIONS,
+                            "%s: TaskInfo.topActivityInfo is null", TAG);
+                }
                 if (SystemProperties.getBoolean(
-                        "persist.wm.debug.enable_pip_app_icon_overlay", true)) {
+                        "persist.wm.debug.enable_pip_app_icon_overlay", true)
+                        && hasTopActivityInfo) {
                     animator.setAppIconContentOverlay(
                             mContext, currentBounds, taskInfo.topActivityInfo);
                 } else {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
index db6ef1d..be9b529 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
@@ -43,6 +43,7 @@
 import android.content.Context;
 import android.content.pm.ActivityInfo;
 import android.content.res.Configuration;
+import android.graphics.Point;
 import android.graphics.Rect;
 import android.os.RemoteException;
 import android.os.SystemProperties;
@@ -71,6 +72,7 @@
 import com.android.wm.shell.common.RemoteCallable;
 import com.android.wm.shell.common.ShellExecutor;
 import com.android.wm.shell.common.SingleInstanceRemoteListener;
+import com.android.wm.shell.common.TabletopModeController;
 import com.android.wm.shell.common.TaskStackListenerCallback;
 import com.android.wm.shell.common.TaskStackListenerImpl;
 import com.android.wm.shell.onehanded.OneHandedController;
@@ -147,6 +149,7 @@
     private TaskStackListenerImpl mTaskStackListener;
     private PipParamsChangedForwarder mPipParamsChangedForwarder;
     private DisplayInsetsController mDisplayInsetsController;
+    private TabletopModeController mTabletopModeController;
     private Optional<OneHandedController> mOneHandedController;
     private final ShellCommandHandler mShellCommandHandler;
     private final ShellController mShellController;
@@ -403,6 +406,7 @@
             TaskStackListenerImpl taskStackListener,
             PipParamsChangedForwarder pipParamsChangedForwarder,
             DisplayInsetsController displayInsetsController,
+            TabletopModeController pipTabletopController,
             Optional<OneHandedController> oneHandedController,
             ShellExecutor mainExecutor) {
         if (!context.getPackageManager().hasSystemFeature(FEATURE_PICTURE_IN_PICTURE)) {
@@ -417,7 +421,7 @@
                 pipDisplayLayoutState, pipMotionHelper, pipMediaController, phonePipMenuController,
                 pipTaskOrganizer, pipTransitionState, pipTouchHandler, pipTransitionController,
                 windowManagerShellWrapper, taskStackListener, pipParamsChangedForwarder,
-                displayInsetsController, oneHandedController, mainExecutor)
+                displayInsetsController, pipTabletopController, oneHandedController, mainExecutor)
                 .mImpl;
     }
 
@@ -444,6 +448,7 @@
             TaskStackListenerImpl taskStackListener,
             PipParamsChangedForwarder pipParamsChangedForwarder,
             DisplayInsetsController displayInsetsController,
+            TabletopModeController tabletopModeController,
             Optional<OneHandedController> oneHandedController,
             ShellExecutor mainExecutor
     ) {
@@ -477,6 +482,7 @@
                 .getInteger(R.integer.config_pipEnterAnimationDuration);
         mPipParamsChangedForwarder = pipParamsChangedForwarder;
         mDisplayInsetsController = displayInsetsController;
+        mTabletopModeController = tabletopModeController;
 
         shellInit.addInitCallback(this::onInit, this);
     }
@@ -659,6 +665,42 @@
                     }
                 });
 
+        mTabletopModeController.registerOnTabletopModeChangedListener((isInTabletopMode) -> {
+            if (!mTabletopModeController.enableMoveFloatingWindowInTabletop()) return;
+            final String tag = "tabletop-mode";
+            if (!isInTabletopMode) {
+                mPipBoundsState.removeNamedUnrestrictedKeepClearArea(tag);
+                return;
+            }
+
+            // To prepare for the entry bounds.
+            final Rect displayBounds = mPipBoundsState.getDisplayBounds();
+            if (mTabletopModeController.getPreferredHalfInTabletopMode()
+                    == TabletopModeController.PREFERRED_TABLETOP_HALF_TOP) {
+                // Prefer top, avoid the bottom half of the display.
+                mPipBoundsState.addNamedUnrestrictedKeepClearArea(tag, new Rect(
+                        displayBounds.left, displayBounds.centerY(),
+                        displayBounds.right, displayBounds.bottom));
+            } else {
+                // Prefer bottom, avoid the top half of the display.
+                mPipBoundsState.addNamedUnrestrictedKeepClearArea(tag, new Rect(
+                        displayBounds.left, displayBounds.top,
+                        displayBounds.right, displayBounds.centerY()));
+            }
+
+            // Try to move the PiP window if we have entered PiP mode.
+            if (mPipTransitionState.hasEnteredPip()) {
+                final Rect pipBounds = mPipBoundsState.getBounds();
+                final Point edgeInsets = mPipSizeSpecHandler.getScreenEdgeInsets();
+                if ((pipBounds.height() + 2 * edgeInsets.y) > (displayBounds.height() / 2)) {
+                    // PiP bounds is too big to fit either half, bail early.
+                    return;
+                }
+                mMainExecutor.removeCallbacks(mMovePipInResponseToKeepClearAreasChangeCallback);
+                mMainExecutor.execute(mMovePipInResponseToKeepClearAreasChangeCallback);
+            }
+        });
+
         mOneHandedController.ifPresent(controller -> {
             controller.registerTransitionCallback(
                     new OneHandedTransitionCallback() {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java
index e1c0895..4b5ad1d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java
@@ -37,6 +37,7 @@
 import android.animation.ValueAnimator;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.graphics.Point;
 import android.graphics.Rect;
 import android.os.IBinder;
 import android.view.SurfaceControl;
@@ -67,7 +68,6 @@
 
     DismissTransition mPendingDismiss = null;
     TransitSession mPendingEnter = null;
-    TransitSession mPendingRecent = null;
     TransitSession mPendingResize = null;
 
     private IBinder mAnimatingTransition = null;
@@ -128,6 +128,7 @@
             final int mode = info.getChanges().get(i).getMode();
 
             if (mode == TRANSIT_CHANGE) {
+                final int rootIdx = TransitionUtil.rootIndexFor(change, info);
                 if (change.getParent() != null) {
                     // This is probably reparented, so we want the parent to be immediately visible
                     final TransitionInfo.Change parentChange = info.getChange(change.getParent());
@@ -135,7 +136,7 @@
                     t.setAlpha(parentChange.getLeash(), 1.f);
                     // and then animate this layer outside the parent (since, for example, this is
                     // the home task animating from fullscreen to part-screen).
-                    t.reparent(leash, info.getRootLeash());
+                    t.reparent(leash, info.getRoot(rootIdx).getLeash());
                     t.setLayer(leash, info.getChanges().size() - i);
                     // build the finish reparent/reposition
                     mFinishTransaction.reparent(leash, parentChange.getLeash());
@@ -145,8 +146,9 @@
                 // TODO(shell-transitions): screenshot here
                 final Rect startBounds = new Rect(change.getStartAbsBounds());
                 final Rect endBounds = new Rect(change.getEndAbsBounds());
-                startBounds.offset(-info.getRootOffset().x, -info.getRootOffset().y);
-                endBounds.offset(-info.getRootOffset().x, -info.getRootOffset().y);
+                final Point rootOffset = info.getRoot(rootIdx).getOffset();
+                startBounds.offset(-rootOffset.x, -rootOffset.y);
+                endBounds.offset(-rootOffset.x, -rootOffset.y);
                 startExampleResizeAnimation(leash, startBounds, endBounds);
             }
             boolean isRootOrSplitSideRoot = change.getParent() == null
@@ -257,10 +259,6 @@
         return mPendingEnter != null && mPendingEnter.mTransition == transition;
     }
 
-    boolean isPendingRecent(IBinder transition) {
-        return mPendingRecent != null && mPendingRecent.mTransition == transition;
-    }
-
     boolean isPendingDismiss(IBinder transition) {
         return mPendingDismiss != null && mPendingDismiss.mTransition == transition;
     }
@@ -273,8 +271,6 @@
     private TransitSession getPendingTransition(IBinder transition) {
         if (isPendingEnter(transition)) {
             return mPendingEnter;
-        } else if (isPendingRecent(transition)) {
-            return mPendingRecent;
         } else if (isPendingDismiss(transition)) {
             return mPendingDismiss;
         } else if (isPendingResize(transition)) {
@@ -358,32 +354,10 @@
                 + " deduced Resize split screen");
     }
 
-    void setRecentTransition(@NonNull IBinder transition,
-            @Nullable RemoteTransition remoteTransition,
-            @Nullable TransitionFinishedCallback finishCallback) {
-        mPendingRecent = new TransitSession(transition, null /* consumedCb */, finishCallback);
-
-        if (remoteTransition != null) {
-            // Wrapping it for ease-of-use (OneShot handles all the binder linking/death stuff)
-            mPendingRemoteHandler = new OneShotRemoteHandler(
-                    mTransitions.getMainExecutor(), remoteTransition);
-            mPendingRemoteHandler.setTransition(transition);
-        }
-
-        ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "  splitTransition "
-                + " deduced Enter recent panel");
-    }
-
     void mergeAnimation(IBinder transition, TransitionInfo info, SurfaceControl.Transaction t,
             IBinder mergeTarget, Transitions.TransitionFinishCallback finishCallback) {
         if (mergeTarget != mAnimatingTransition) return;
 
-        if (isPendingEnter(transition) && isPendingRecent(mergeTarget)) {
-            // Since there's an entering transition merged, recent transition no longer
-            // need to handle entering split screen after the transition finished.
-            mPendingRecent.setFinishedCallback(null);
-        }
-
         if (mActiveRemoteHandler != null) {
             mActiveRemoteHandler.mergeAnimation(transition, info, t, mergeTarget, finishCallback);
         } else {
@@ -420,10 +394,6 @@
         } else if (isPendingDismiss(transition)) {
             mPendingDismiss.onConsumed(aborted);
             mPendingDismiss = null;
-        } else if (isPendingRecent(transition)) {
-            mPendingRecent.onConsumed(aborted);
-            mPendingRecent = null;
-            mPendingRemoteHandler = null;
         } else if (isPendingResize(transition)) {
             mPendingResize.onConsumed(aborted);
             mPendingResize = null;
@@ -437,9 +407,6 @@
         if (isPendingEnter(mAnimatingTransition)) {
             mPendingEnter.onFinished(wct, mFinishTransaction);
             mPendingEnter = null;
-        } else if (isPendingRecent(mAnimatingTransition)) {
-            mPendingRecent.onFinished(wct, mFinishTransaction);
-            mPendingRecent = null;
         } else if (isPendingDismiss(mAnimatingTransition)) {
             mPendingDismiss.onFinished(wct, mFinishTransaction);
             mPendingDismiss = null;
@@ -549,7 +516,7 @@
     }
 
     /** Calls when the transition finished. */
-    interface TransitionFinishedCallback {
+    public interface TransitionFinishedCallback {
         void onFinished(WindowContainerTransaction wct, SurfaceControl.Transaction t);
     }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
index a5546e5..9d1a7ea 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
@@ -259,37 +259,6 @@
                 }
             };
 
-    private final SplitScreenTransitions.TransitionFinishedCallback
-            mRecentTransitionFinishedCallback =
-            new SplitScreenTransitions.TransitionFinishedCallback() {
-                @Override
-                public void onFinished(WindowContainerTransaction finishWct,
-                        SurfaceControl.Transaction finishT) {
-                    // Check if the recent transition is finished by returning to the current
-                    // split, so we
-                    // can restore the divider bar.
-                    for (int i = 0; i < finishWct.getHierarchyOps().size(); ++i) {
-                        final WindowContainerTransaction.HierarchyOp op =
-                                finishWct.getHierarchyOps().get(i);
-                        final IBinder container = op.getContainer();
-                        if (op.getType() == HIERARCHY_OP_TYPE_REORDER && op.getToTop()
-                                && (mMainStage.containsContainer(container)
-                                || mSideStage.containsContainer(container))) {
-                            updateSurfaceBounds(mSplitLayout, finishT,
-                                    false /* applyResizingOffset */);
-                            setDividerVisibility(true, finishT);
-                            return;
-                        }
-                    }
-
-                    // Dismiss the split screen if it's not returning to split.
-                    prepareExitSplitScreen(STAGE_TYPE_UNDEFINED, finishWct);
-                    setSplitsVisible(false);
-                    setDividerVisibility(false, finishT);
-                    logExit(EXIT_REASON_UNKNOWN);
-                }
-            };
-
     protected StageCoordinator(Context context, int displayId, SyncTransactionQueue syncQueue,
             ShellTaskOrganizer taskOrganizer, DisplayController displayController,
             DisplayImeController displayImeController,
@@ -388,6 +357,11 @@
         return mMainStage.isActive();
     }
 
+    /** Checks if `transition` is a pending enter-split transition. */
+    public boolean isPendingEnter(IBinder transition) {
+        return mSplitTransitions.isPendingEnter(transition);
+    }
+
     @StageType
     int getStageOfTask(int taskId) {
         if (mMainStage.containsTask(taskId)) {
@@ -2266,9 +2240,8 @@
                 final int activityType = triggerTask.getActivityType();
                 if (activityType == ACTIVITY_TYPE_HOME
                         || activityType == ACTIVITY_TYPE_RECENTS) {
-                    // Enter overview panel, so start recent transition.
-                    mSplitTransitions.setRecentTransition(transition, request.getRemoteTransition(),
-                            mRecentTransitionFinishedCallback);
+                    // starting recents, so don't handle this.
+                    return null;
                 }
             }
         } else {
@@ -2397,8 +2370,6 @@
         if (mSplitTransitions.isPendingEnter(transition)) {
             shouldAnimate = startPendingEnterAnimation(
                     transition, info, startTransaction, finishTransaction);
-        } else if (mSplitTransitions.isPendingRecent(transition)) {
-            shouldAnimate = startPendingRecentAnimation(transition, info, startTransaction);
         } else if (mSplitTransitions.isPendingDismiss(transition)) {
             shouldAnimate = startPendingDismissAnimation(
                     mSplitTransitions.mPendingDismiss, info, startTransaction, finishTransaction);
@@ -2653,10 +2624,35 @@
         return true;
     }
 
-    private boolean startPendingRecentAnimation(@NonNull IBinder transition,
-            @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction t) {
+    /** Call this when starting the open-recents animation while split-screen is active. */
+    public void onRecentsInSplitAnimationStart(@NonNull SurfaceControl.Transaction t) {
         setDividerVisibility(false, t);
-        return true;
+    }
+
+    /** Call this when the recents animation during split-screen finishes. */
+    public void onRecentsInSplitAnimationFinish(WindowContainerTransaction finishWct,
+            SurfaceControl.Transaction finishT) {
+        // Check if the recent transition is finished by returning to the current
+        // split, so we can restore the divider bar.
+        for (int i = 0; i < finishWct.getHierarchyOps().size(); ++i) {
+            final WindowContainerTransaction.HierarchyOp op =
+                    finishWct.getHierarchyOps().get(i);
+            final IBinder container = op.getContainer();
+            if (op.getType() == HIERARCHY_OP_TYPE_REORDER && op.getToTop()
+                    && (mMainStage.containsContainer(container)
+                    || mSideStage.containsContainer(container))) {
+                updateSurfaceBounds(mSplitLayout, finishT,
+                        false /* applyResizingOffset */);
+                setDividerVisibility(true, finishT);
+                return;
+            }
+        }
+
+        // Dismiss the split screen if it's not returning to split.
+        prepareExitSplitScreen(STAGE_TYPE_UNDEFINED, finishWct);
+        setSplitsVisible(false);
+        setDividerVisibility(false, finishT);
+        logExit(EXIT_REASON_UNKNOWN);
     }
 
     private void addDividerBarToTransition(@NonNull TransitionInfo info,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java
index 75112b6..b9f6a01 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java
@@ -18,6 +18,7 @@
 
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
 import static android.view.WindowManager.TRANSIT_CHANGE;
 import static android.view.WindowManager.TRANSIT_TO_BACK;
 import static android.window.TransitionInfo.FLAG_IS_WALLPAPER;
@@ -25,6 +26,7 @@
 import static com.android.wm.shell.common.split.SplitScreenConstants.FLAG_IS_DIVIDER_BAR;
 import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_UNDEFINED;
 import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_CHILD_TASK_ENTER_PIP;
+import static com.android.wm.shell.util.TransitionUtil.isOpeningType;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -68,14 +70,20 @@
         /** Pip was entered while handling an intent with its own remoteTransition. */
         static final int TYPE_OPTIONS_REMOTE_AND_PIP_CHANGE = 3;
 
+        /** Recents transition while split-screen active. */
+        static final int TYPE_RECENTS_DURING_SPLIT = 4;
+
         /** The default animation for this mixed transition. */
         static final int ANIM_TYPE_DEFAULT = 0;
 
         /** For ENTER_PIP_FROM_SPLIT, indicates that this is a to-home animation. */
         static final int ANIM_TYPE_GOING_HOME = 1;
 
+        /** For RECENTS_DURING_SPLIT, is set when this turns into a pair->pair task switch. */
+        static final int ANIM_TYPE_PAIR_TO_PAIR = 1;
+
         final int mType;
-        int mAnimType = 0;
+        int mAnimType = ANIM_TYPE_DEFAULT;
         final IBinder mTransition;
 
         Transitions.TransitionHandler mLeftoversHandler = null;
@@ -167,6 +175,25 @@
             mixed.mLeftoversHandler = handler.first;
             mActiveTransitions.add(mixed);
             return handler.second;
+        } else if (mSplitHandler.isSplitActive()
+                && isOpeningType(request.getType())
+                && request.getTriggerTask() != null
+                && request.getTriggerTask().getWindowingMode() == WINDOWING_MODE_FULLSCREEN
+                && (request.getTriggerTask().getActivityType() == ACTIVITY_TYPE_HOME
+                        || request.getTriggerTask().getActivityType() == ACTIVITY_TYPE_RECENTS)) {
+            ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Got a recents request while "
+                    + "Split-Screen is active, so treat it as Mixed.");
+            Pair<Transitions.TransitionHandler, WindowContainerTransaction> handler =
+                    mPlayer.dispatchRequest(transition, request, this);
+            if (handler == null) {
+                // fall through -- it will probably be picked-up by normal split handler.
+                return null;
+            }
+            final MixedTransition mixed = new MixedTransition(
+                    MixedTransition.TYPE_RECENTS_DURING_SPLIT, transition);
+            mixed.mLeftoversHandler = handler.first;
+            mActiveTransitions.add(mixed);
+            return handler.second;
         }
         return null;
     }
@@ -179,7 +206,9 @@
                 out.getChanges().add(info.getChanges().get(i));
             }
         }
-        out.setRootLeash(info.getRootLeash(), info.getRootOffset().x, info.getRootOffset().y);
+        for (int i = 0; i < info.getRootCount(); ++i) {
+            out.addRoot(info.getRoot(i));
+        }
         out.setAnimationOptions(info.getAnimationOptions());
         return out;
     }
@@ -214,6 +243,9 @@
         } else if (mixed.mType == MixedTransition.TYPE_OPTIONS_REMOTE_AND_PIP_CHANGE) {
             return animateOpenIntentWithRemoteAndPip(mixed, info, startTransaction,
                     finishTransaction, finishCallback);
+        } else if (mixed.mType == MixedTransition.TYPE_RECENTS_DURING_SPLIT) {
+            return animateRecentsDuringSplit(mixed, info, startTransaction, finishTransaction,
+                    finishCallback);
         } else {
             mActiveTransitions.remove(mixed);
             throw new IllegalStateException("Starting mixed animation without a known mixed type? "
@@ -439,12 +471,40 @@
         return true;
     }
 
+    private boolean animateRecentsDuringSplit(@NonNull final MixedTransition mixed,
+            @NonNull TransitionInfo info,
+            @NonNull SurfaceControl.Transaction startTransaction,
+            @NonNull SurfaceControl.Transaction finishTransaction,
+            @NonNull Transitions.TransitionFinishCallback finishCallback) {
+        // Split-screen is only interested in the recents transition finishing (and merging), so
+        // just wrap finish and start recents animation directly.
+        Transitions.TransitionFinishCallback finishCB = (wct, wctCB) -> {
+            mixed.mInFlightSubAnimations = 0;
+            mActiveTransitions.remove(mixed);
+            // If pair-to-pair switching, the post-recents clean-up isn't needed.
+            if (mixed.mAnimType != MixedTransition.ANIM_TYPE_PAIR_TO_PAIR) {
+                wct = wct != null ? wct : new WindowContainerTransaction();
+                mSplitHandler.onRecentsInSplitAnimationFinish(wct, finishTransaction);
+            }
+            mSplitHandler.onTransitionAnimationComplete();
+            finishCallback.onTransitionFinished(wct, wctCB);
+        };
+        mixed.mInFlightSubAnimations = 1;
+        mSplitHandler.onRecentsInSplitAnimationStart(startTransaction);
+        final boolean handled = mixed.mLeftoversHandler.startAnimation(mixed.mTransition, info,
+                startTransaction, finishTransaction, finishCB);
+        if (!handled) {
+            mActiveTransitions.remove(mixed);
+        }
+        return handled;
+    }
+
     @Override
     public void mergeAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
             @NonNull SurfaceControl.Transaction t, @NonNull IBinder mergeTarget,
             @NonNull Transitions.TransitionFinishCallback finishCallback) {
         for (int i = 0; i < mActiveTransitions.size(); ++i) {
-            if (mActiveTransitions.get(i) != mergeTarget) continue;
+            if (mActiveTransitions.get(i).mTransition != mergeTarget) continue;
             MixedTransition mixed = mActiveTransitions.get(i);
             if (mixed.mInFlightSubAnimations <= 0) {
                 // Already done, so no need to end it.
@@ -472,6 +532,14 @@
                     mixed.mLeftoversHandler.mergeAnimation(transition, info, t, mergeTarget,
                             finishCallback);
                 }
+            } else if (mixed.mType == MixedTransition.TYPE_RECENTS_DURING_SPLIT) {
+                if (mSplitHandler.isPendingEnter(transition)) {
+                    // Recents -> enter-split means that we are switching from one pair to
+                    // another pair.
+                    mixed.mAnimType = MixedTransition.ANIM_TYPE_PAIR_TO_PAIR;
+                }
+                mixed.mLeftoversHandler.mergeAnimation(transition, info, t, mergeTarget,
+                        finishCallback);
             } else {
                 throw new IllegalStateException("Playing a mixed transition with unknown type? "
                         + mixed.mType);
@@ -491,6 +559,8 @@
         if (mixed == null) return;
         if (mixed.mType == MixedTransition.TYPE_ENTER_PIP_FROM_SPLIT) {
             mPipHandler.onTransitionConsumed(transition, aborted, finishT);
+        } else if (mixed.mType == MixedTransition.TYPE_RECENTS_DURING_SPLIT) {
+            mixed.mLeftoversHandler.onTransitionConsumed(transition, aborted, finishT);
         }
     }
 }
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 f66c26b..63c7969 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
@@ -383,9 +383,10 @@
                     continue;
                 }
                 // No default animation for this, so just update bounds/position.
+                final int rootIdx = TransitionUtil.rootIndexFor(change, info);
                 startTransaction.setPosition(change.getLeash(),
-                        change.getEndAbsBounds().left - info.getRootOffset().x,
-                        change.getEndAbsBounds().top - info.getRootOffset().y);
+                        change.getEndAbsBounds().left - info.getRoot(rootIdx).getOffset().x,
+                        change.getEndAbsBounds().top - info.getRoot(rootIdx).getOffset().y);
                 // Seamless display transition doesn't need to animate.
                 if (isSeamlessDisplayChange) continue;
                 if (isTask || (change.hasFlags(FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY)
@@ -474,8 +475,10 @@
         }
 
         if (backgroundColorForTransition != 0) {
-            addBackgroundToTransition(info.getRootLeash(), backgroundColorForTransition,
-                    startTransaction, finishTransaction);
+            for (int i = 0; i < info.getRootCount(); ++i) {
+                addBackgroundToTransition(info.getRoot(i).getLeash(), backgroundColorForTransition,
+                        startTransaction, finishTransaction);
+            }
         }
 
         if (postStartTransactionCallbacks.size() > 0) {
@@ -520,8 +523,10 @@
     private void startRotationAnimation(SurfaceControl.Transaction startTransaction,
             TransitionInfo.Change change, TransitionInfo info, int animHint,
             ArrayList<Animator> animations, Runnable onAnimFinish) {
+        final int rootIdx = TransitionUtil.rootIndexFor(change, info);
         final ScreenRotationAnimation anim = new ScreenRotationAnimation(mContext, mSurfaceSession,
-                mTransactionPool, startTransaction, change, info.getRootLeash(), animHint);
+                mTransactionPool, startTransaction, change, info.getRoot(rootIdx).getLeash(),
+                animHint);
         // The rotation animation may consist of 3 animations: fade-out screenshot, fade-in real
         // content, and background color. The item of "animGroup" will be removed if the sub
         // animation is finished. Then if the list becomes empty, the rotation animation is done.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
index 27b82c0..039bde9 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
@@ -75,6 +75,7 @@
 import com.android.wm.shell.protolog.ShellProtoLogGroup;
 import com.android.wm.shell.sysui.ShellController;
 import com.android.wm.shell.sysui.ShellInit;
+import com.android.wm.shell.util.TransitionUtil;
 
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -415,8 +416,8 @@
     private static void setupAnimHierarchy(@NonNull TransitionInfo info,
             @NonNull SurfaceControl.Transaction t, @NonNull SurfaceControl.Transaction finishT) {
         boolean isOpening = isOpeningType(info.getType());
-        if (info.getRootLeash().isValid()) {
-            t.show(info.getRootLeash());
+        for (int i = 0; i < info.getRootCount(); ++i) {
+            t.show(info.getRoot(i).getLeash());
         }
         final int numChanges = info.getChanges().size();
         // Put animating stuff above this line and put static stuff below it.
@@ -434,10 +435,12 @@
 
             boolean hasParent = change.getParent() != null;
 
+            final int rootIdx = TransitionUtil.rootIndexFor(change, info);
             if (!hasParent) {
-                t.reparent(leash, info.getRootLeash());
-                t.setPosition(leash, change.getStartAbsBounds().left - info.getRootOffset().x,
-                        change.getStartAbsBounds().top - info.getRootOffset().y);
+                t.reparent(leash, info.getRoot(rootIdx).getLeash());
+                t.setPosition(leash,
+                        change.getStartAbsBounds().left - info.getRoot(rootIdx).getOffset().x,
+                        change.getStartAbsBounds().top - info.getRoot(rootIdx).getOffset().y);
             }
             final int layer;
             // Put all the OPEN/SHOW on top
@@ -532,12 +535,6 @@
 
         if (info.getType() == TRANSIT_SLEEP) {
             if (activeIdx > 0) {
-                if (!info.getRootLeash().isValid()) {
-                    // Shell has some debug settings which makes calling binders with invalid
-                    // surfaces crash, so replace it with a "real" one.
-                    info.setRootLeash(new SurfaceControl.Builder().setName("Invalid")
-                            .setContainerLayer().build(), 0, 0);
-                }
                 // Sleep starts a process of forcing all prior transitions to finish immediately
                 finishForSleep(null /* forceFinish */);
                 return;
@@ -546,10 +543,10 @@
 
         // Allow to notify keyguard un-occluding state to KeyguardService, which can happen while
         // screen-off, so there might no visibility change involved.
-        if (!info.getRootLeash().isValid() && info.getType() != TRANSIT_KEYGUARD_UNOCCLUDE) {
-            // Invalid root-leash implies that the transition is empty/no-op, so just do
+        if (info.getRootCount() == 0 && info.getType() != TRANSIT_KEYGUARD_UNOCCLUDE) {
+            // No root-leashes implies that the transition is empty/no-op, so just do
             // housekeeping and return.
-            ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Invalid root leash (%s): %s",
+            ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "No transition roots (%s): %s",
                     transitionToken, info);
             onAbort(active);
             return;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/util/TransitionUtil.java b/libs/WindowManager/Shell/src/com/android/wm/shell/util/TransitionUtil.java
index 8c6e1e7..7595c96 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/util/TransitionUtil.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/util/TransitionUtil.java
@@ -139,11 +139,12 @@
         // changes should be ordered top-to-bottom in z
         final int mode = change.getMode();
 
-        t.reparent(leash, info.getRootLeash());
+        final int rootIdx = TransitionUtil.rootIndexFor(change, info);
+        t.reparent(leash, info.getRoot(rootIdx).getLeash());
         final Rect absBounds =
                 (mode == TRANSIT_OPEN) ? change.getEndAbsBounds() : change.getStartAbsBounds();
-        t.setPosition(leash, absBounds.left - info.getRootOffset().x,
-                absBounds.top - info.getRootOffset().y);
+        t.setPosition(leash, absBounds.left - info.getRoot(rootIdx).getOffset().x,
+                absBounds.top - info.getRoot(rootIdx).getOffset().y);
 
         // Put all the OPEN/SHOW on top
         if (TransitionUtil.isOpeningType(mode)) {
@@ -179,12 +180,13 @@
             // making leashes means we have to handle them specially.
             return change.getLeash();
         }
+        final int rootIdx = TransitionUtil.rootIndexFor(change, info);
         SurfaceControl leashSurface = new SurfaceControl.Builder()
                 .setName(change.getLeash().toString() + "_transition-leash")
                 .setContainerLayer()
                 // Initial the surface visible to respect the visibility of the original surface.
                 .setHidden(false)
-                .setParent(info.getRootLeash())
+                .setParent(info.getRoot(rootIdx).getLeash())
                 .build();
         // Copied Transitions setup code (which expects bottom-to-top order, so we swap here)
         setupLeash(leashSurface, change, info.getChanges().size() - order, info, t);
@@ -261,4 +263,18 @@
         target.setRotationChange(change.getEndRotation() - change.getStartRotation());
         return target;
     }
+
+    /**
+     * Finds the "correct" root idx for a change. The change's end display is prioritized, then
+     * the start display. If there is no display, it will fallback on the 0th root in the
+     * transition. There MUST be at-least 1 root in the transition (ie. it's not a no-op).
+     */
+    public static int rootIndexFor(@NonNull TransitionInfo.Change change,
+            @NonNull TransitionInfo info) {
+        int rootIdx = info.findRootIndex(change.getEndDisplayId());
+        if (rootIdx >= 0) return rootIdx;
+        rootIdx = info.findRootIndex(change.getStartDisplayId());
+        if (rootIdx >= 0) return rootIdx;
+        return 0;
+    }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/OWNERS b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/OWNERS
new file mode 100644
index 0000000..4417209
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/OWNERS
@@ -0,0 +1 @@
+jorgegil@google.com
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/TransitionInfoBuilder.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/TransitionInfoBuilder.java
index 35c374d..26b787f 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/TransitionInfoBuilder.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/TransitionInfoBuilder.java
@@ -30,6 +30,7 @@
  */
 public class TransitionInfoBuilder {
     final TransitionInfo mInfo;
+    static final int DISPLAY_ID = 0;
 
     public TransitionInfoBuilder(@WindowManager.TransitionType int type) {
         this(type, 0 /* flags */);
@@ -38,7 +39,7 @@
     public TransitionInfoBuilder(@WindowManager.TransitionType int type,
             @WindowManager.TransitionFlags int flags) {
         mInfo = new TransitionInfo(type, flags);
-        mInfo.setRootLeash(createMockSurface(true /* valid */), 0, 0);
+        mInfo.addRootLeash(DISPLAY_ID, createMockSurface(true /* valid */), 0, 0);
     }
 
     public TransitionInfoBuilder addChange(@WindowManager.TransitionType int mode,
@@ -61,6 +62,7 @@
     }
 
     public TransitionInfoBuilder addChange(TransitionInfo.Change change) {
+        change.setDisplayId(DISPLAY_ID, DISPLAY_ID);
         mInfo.addChange(change);
         return this;
     }
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java
index 0e14c69..108e273 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java
@@ -53,6 +53,7 @@
 import com.android.wm.shell.common.DisplayInsetsController;
 import com.android.wm.shell.common.DisplayLayout;
 import com.android.wm.shell.common.ShellExecutor;
+import com.android.wm.shell.common.TabletopModeController;
 import com.android.wm.shell.common.TaskStackListenerImpl;
 import com.android.wm.shell.onehanded.OneHandedController;
 import com.android.wm.shell.pip.PipAnimationController;
@@ -115,6 +116,7 @@
     @Mock private Optional<OneHandedController> mMockOneHandedController;
     @Mock private PipParamsChangedForwarder mMockPipParamsChangedForwarder;
     @Mock private DisplayInsetsController mMockDisplayInsetsController;
+    @Mock private TabletopModeController mMockTabletopModeController;
 
     @Mock private DisplayLayout mMockDisplayLayout1;
     @Mock private DisplayLayout mMockDisplayLayout2;
@@ -137,7 +139,8 @@
                 mMockPipTaskOrganizer, mMockPipTransitionState, mMockPipTouchHandler,
                 mMockPipTransitionController, mMockWindowManagerShellWrapper,
                 mMockTaskStackListener, mMockPipParamsChangedForwarder,
-                mMockDisplayInsetsController, mMockOneHandedController, mMockExecutor);
+                mMockDisplayInsetsController, mMockTabletopModeController,
+                mMockOneHandedController, mMockExecutor);
         mShellInit.init();
         when(mMockPipBoundsAlgorithm.getSnapAlgorithm()).thenReturn(mMockPipSnapAlgorithm);
         when(mMockPipTouchHandler.getMotionHelper()).thenReturn(mMockPipMotionHelper);
@@ -228,7 +231,8 @@
                 mMockPipTaskOrganizer, mMockPipTransitionState, mMockPipTouchHandler,
                 mMockPipTransitionController, mMockWindowManagerShellWrapper,
                 mMockTaskStackListener, mMockPipParamsChangedForwarder,
-                mMockDisplayInsetsController, mMockOneHandedController, mMockExecutor));
+                mMockDisplayInsetsController, mMockTabletopModeController,
+                mMockOneHandedController, mMockExecutor));
     }
 
     @Test
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java
index 3901dab..2edec7d 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java
@@ -35,6 +35,7 @@
 
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyInt;
@@ -249,7 +250,7 @@
 
     @Test
     @UiThreadTest
-    public void testEnterRecents() {
+    public void testEnterRecentsAndCommit() {
         enterSplit();
 
         ActivityManager.RunningTaskInfo homeTask = new TestRunningTaskInfoBuilder()
@@ -261,24 +262,60 @@
         TransitionRequestInfo request = new TransitionRequestInfo(TRANSIT_TO_FRONT, homeTask, null);
         IBinder transition = mock(IBinder.class);
         WindowContainerTransaction result = mStageCoordinator.handleRequest(transition, request);
-
-        assertTrue(result.isEmpty());
+        // Don't handle recents opening
+        assertNull(result);
 
         // make sure we haven't made any local changes yet (need to wait until transition is ready)
         assertTrue(mStageCoordinator.isSplitScreenVisible());
 
-        // simulate the transition
-        TransitionInfo info = new TransitionInfoBuilder(TRANSIT_TO_FRONT, 0)
-                .addChange(TRANSIT_TO_FRONT, homeTask)
-                .addChange(TRANSIT_TO_BACK, mMainChild)
-                .addChange(TRANSIT_TO_BACK, mSideChild)
-                .build();
+        // simulate the start of recents transition
         mMainStage.onTaskVanished(mMainChild);
         mSideStage.onTaskVanished(mSideChild);
-        mStageCoordinator.startAnimation(transition, info,
-                mock(SurfaceControl.Transaction.class),
-                mock(SurfaceControl.Transaction.class),
-                mock(Transitions.TransitionFinishCallback.class));
+        mStageCoordinator.onRecentsInSplitAnimationStart(mock(SurfaceControl.Transaction.class));
+        assertTrue(mStageCoordinator.isSplitScreenVisible());
+
+        // Make sure it cleans-up if recents doesn't restore
+        WindowContainerTransaction commitWCT = new WindowContainerTransaction();
+        mStageCoordinator.onRecentsInSplitAnimationFinish(commitWCT,
+                mock(SurfaceControl.Transaction.class));
+        assertFalse(mStageCoordinator.isSplitScreenVisible());
+    }
+
+    @Test
+    @UiThreadTest
+    public void testEnterRecentsAndRestore() {
+        enterSplit();
+
+        ActivityManager.RunningTaskInfo homeTask = new TestRunningTaskInfoBuilder()
+                .setWindowingMode(WINDOWING_MODE_FULLSCREEN)
+                .setActivityType(ACTIVITY_TYPE_HOME)
+                .build();
+
+        // Create a request to bring home forward
+        TransitionRequestInfo request = new TransitionRequestInfo(TRANSIT_TO_FRONT, homeTask, null);
+        IBinder transition = mock(IBinder.class);
+        WindowContainerTransaction result = mStageCoordinator.handleRequest(transition, request);
+        // Don't handle recents opening
+        assertNull(result);
+
+        // make sure we haven't made any local changes yet (need to wait until transition is ready)
+        assertTrue(mStageCoordinator.isSplitScreenVisible());
+
+        // simulate the start of recents transition
+        mMainStage.onTaskVanished(mMainChild);
+        mSideStage.onTaskVanished(mSideChild);
+        mStageCoordinator.onRecentsInSplitAnimationStart(mock(SurfaceControl.Transaction.class));
+        assertTrue(mStageCoordinator.isSplitScreenVisible());
+
+        // Make sure we remain in split after recents restores.
+        WindowContainerTransaction restoreWCT = new WindowContainerTransaction();
+        restoreWCT.reorder(mMainChild.token, true /* toTop */);
+        restoreWCT.reorder(mSideChild.token, true /* toTop */);
+        // simulate the restoreWCT being applied:
+        mMainStage.onTaskAppeared(mMainChild, mock(SurfaceControl.class));
+        mSideStage.onTaskAppeared(mSideChild, mock(SurfaceControl.class));
+        mStageCoordinator.onRecentsInSplitAnimationFinish(restoreWCT,
+                mock(SurfaceControl.Transaction.class));
         assertTrue(mStageCoordinator.isSplitScreenVisible());
     }
 
diff --git a/libs/hwui/renderthread/RenderThread.cpp b/libs/hwui/renderthread/RenderThread.cpp
index 7a7f1ab..0afd949 100644
--- a/libs/hwui/renderthread/RenderThread.cpp
+++ b/libs/hwui/renderthread/RenderThread.cpp
@@ -135,7 +135,9 @@
         !mFrameCallbackTaskPending) {
         ATRACE_NAME("queue mFrameCallbackTask");
         mFrameCallbackTaskPending = true;
-        nsecs_t runAt = (frameTimeNanos + mDispatchFrameDelay);
+
+        nsecs_t timeUntilDeadline = frameDeadline - frameTimeNanos;
+        nsecs_t runAt = (frameTimeNanos + (timeUntilDeadline * 0.25f));
         queue().postAt(runAt, [=]() { dispatchFrameCallbacks(); });
     }
 }
@@ -257,7 +259,6 @@
 void RenderThread::setupFrameInterval() {
     nsecs_t frameIntervalNanos = DeviceInfo::getVsyncPeriod();
     mTimeLord.setFrameInterval(frameIntervalNanos);
-    mDispatchFrameDelay = static_cast<nsecs_t>(frameIntervalNanos * .25f);
 }
 
 void RenderThread::requireGlContext() {
diff --git a/libs/hwui/renderthread/RenderThread.h b/libs/hwui/renderthread/RenderThread.h
index 0a89e5e..c77cd41 100644
--- a/libs/hwui/renderthread/RenderThread.h
+++ b/libs/hwui/renderthread/RenderThread.h
@@ -235,7 +235,6 @@
     bool mFrameCallbackTaskPending;
 
     TimeLord mTimeLord;
-    nsecs_t mDispatchFrameDelay = 4_ms;
     RenderState* mRenderState;
     EglManager* mEglManager;
     WebViewFunctorManager& mFunctorManager;
diff --git a/location/java/android/location/GnssMeasurement.java b/location/java/android/location/GnssMeasurement.java
index ab3dafe..b0769ab 100644
--- a/location/java/android/location/GnssMeasurement.java
+++ b/location/java/android/location/GnssMeasurement.java
@@ -497,24 +497,29 @@
      *   <thead>
      *     <tr>
      *       <td />
-     *       <td colspan="3"><strong>GPS/QZSS</strong></td>
+     *       <td colspan="4"><strong>GPS/QZSS</strong></td>
      *       <td><strong>GLNS</strong></td>
-     *       <td colspan="2"><strong>BDS</strong></td>
+     *       <td colspan="4"><strong>BDS</strong></td>
      *       <td colspan="3"><strong>GAL</strong></td>
      *       <td><strong>SBAS</strong></td>
+     *       <td><strong>IRNSS</strong></td>
      *     </tr>
      *     <tr>
      *       <td><strong>State Flag</strong></td>
      *       <td><strong>L1 C/A</strong></td>
+     *       <td><strong>L1 C(P)</strong></td>
      *       <td><strong>L5I</strong></td>
      *       <td><strong>L5Q</strong></td>
      *       <td><strong>L1OF</strong></td>
      *       <td><strong>B1I (D1)</strong></td>
-     *       <td><strong>B1I &nbsp;(D2)</strong></td>
+     *       <td><strong>B1I (D2)</strong></td>
+     *       <td><strong>B1C (P)</strong></td>
+     *       <td><strong>B2AQ </strong></td>
      *       <td><strong>E1B</strong></td>
      *       <td><strong>E1C</strong></td>
      *       <td><strong>E5AQ</strong></td>
      *       <td><strong>L1 C/A</strong></td>
+     *       <td><strong>L5C</strong></td>
      *     </tr>
      *   </thead>
      *   <tbody>
@@ -532,87 +537,123 @@
      *       <td>0</td>
      *       <td>0</td>
      *       <td>0</td>
+     *       <td>0</td>
+     *       <td>0</td>
+     *       <td>0</td>
+     *       <td>0</td>
      *     </tr>
      *     <tr>
      *       <td>
      *         <strong>STATE_CODE_LOCK</strong>
      *       </td>
      *       <td>1 ms</td>
+     *       <td>10 ms</td>
      *       <td>1 ms</td>
      *       <td>1 ms</td>
      *       <td>1 ms</td>
      *       <td>1 ms</td>
      *       <td>1 ms</td>
+     *       <td>10 ms</td>
+     *       <td>1 ms</td>
      *       <td>-</td>
      *       <td>-</td>
      *       <td>1 ms</td>
      *       <td>1 ms</td>
+     *       <td>1 ms</td>
      *     </tr>
      *     <tr>
      *       <td>
      *         <strong>STATE_SYMBOL_SYNC</strong>
      *       </td>
-     *       <td>20 ms (optional)</td>
+     *       <td>-</td>
+     *       <td>-</td>
      *       <td>10 ms</td>
-     *       <td>1 ms (optional)</td>
+     *       <td>-</td>
      *       <td>10 ms</td>
-     *       <td>20 ms (optional)</td>
+     *       <td>-</td>
      *       <td>2 ms</td>
-     *       <td>4 ms (optional)</td>
-     *       <td>4 ms (optional)</td>
-     *       <td>1 ms (optional)</td>
+     *       <td>-</td>
+     *       <td>-</td>
+     *       <td>-</td>
+     *       <td>-</td>
+     *       <td>-</td>
      *       <td>2 ms</td>
+     *       <td>-</td>
      *     </tr>
      *     <tr>
      *       <td>
      *         <strong>STATE_BIT_SYNC</strong>
      *       </td>
      *       <td>20 ms</td>
+     *       <td>-</td>
      *       <td>20 ms</td>
-     *       <td>1 ms (optional)</td>
+     *       <td>-</td>
      *       <td>20 ms</td>
      *       <td>20 ms</td>
      *       <td>-</td>
+     *       <td>-</td>
+     *       <td>-</td>
      *       <td>8 ms</td>
      *       <td>-</td>
-     *       <td>1 ms (optional)</td>
+     *       <td>-</td>
      *       <td>4 ms</td>
+     *       <td>20 ms</td>
      *     </tr>
      *     <tr>
      *       <td>
      *         <strong>STATE_SUBFRAME_SYNC</strong>
      *       </td>
-     *       <td>6s</td>
-     *       <td>6s</td>
-     *       <td>-</td>
-     *       <td>2 s</td>
      *       <td>6 s</td>
      *       <td>-</td>
+     *       <td>6 s</td>
+     *       <td>-</td>
+     *       <td>-</td>
+     *       <td>6 s</td>
      *       <td>-</td>
      *       <td>-</td>
      *       <td>100 ms</td>
      *       <td>-</td>
+     *       <td>-</td>
+     *       <td>100 ms</td>
+     *       <td>-</td>
+     *       <td>6 s</td>
      *     </tr>
      *     <tr>
      *       <td>
      *         <strong>STATE_TOW_DECODED</strong>
      *       </td>
-     *       <td colspan="2">1 week</td>
+     *       <td>1 week</td>
      *       <td>-</td>
-     *       <td>1 day</td>
-     *       <td colspan="2">1 week</td>
-     *       <td colspan="2">1 week</td>
+     *       <td>1 week</td>
      *       <td>-</td>
+     *       <td>-</td>
+     *       <td>1 week</td>
+     *       <td>1 week</td>
+     *       <td>-</td>
+     *       <td>-</td>
+     *       <td>1 week</td>
+     *       <td>1 week</td>
+     *       <td>-</td>
+     *       <td>1 week</td>
      *       <td>1 week</td>
      *     </tr>
      *     <tr>
      *       <td>
      *         <strong>STATE_TOW_KNOWN</strong>
      *       </td>
-     *       <td colspan="3">1 week</td>
-     *       <td>1 day</td>
-     *       <td colspan="2">1 week</td>
-     *       <td colspan="3">1 week</td>
+     *       <td>1 week</td>
+     *       <td>1 week</td>
+     *       <td>1 week</td>
+     *       <td>1 week</td>
+     *       <td>-</td>
+     *       <td>1 week</td>
+     *       <td>1 week</td>
+     *       <td>1 week</td>
+     *       <td>1 week</td>
+     *       <td>1 week</td>
+     *       <td>1 week</td>
+     *       <td>1 week</td>
+     *       <td>1 week</td>
      *       <td>1 week</td>
      *     </tr>
      *     <tr>
@@ -622,6 +663,7 @@
      *       <td>-</td>
      *       <td>-</td>
      *       <td>-</td>
+     *       <td>-</td>
      *       <td>2 s</td>
      *       <td>-</td>
      *       <td>-</td>
@@ -629,6 +671,9 @@
      *       <td>-</td>
      *       <td>-</td>
      *       <td>-</td>
+     *       <td>-</td>
+     *       <td>-</td>
+     *       <td>-</td>
      *     </tr>
      *     <tr>
      *       <td>
@@ -637,6 +682,7 @@
      *       <td>-</td>
      *       <td>-</td>
      *       <td>-</td>
+     *       <td>-</td>
      *       <td>1 day</td>
      *       <td>-</td>
      *       <td>-</td>
@@ -644,6 +690,9 @@
      *       <td>-</td>
      *       <td>-</td>
      *       <td>-</td>
+     *       <td>-</td>
+     *       <td>-</td>
+     *       <td>-</td>
      *     </tr>
      *     <tr>
      *       <td>
@@ -652,6 +701,7 @@
      *       <td>-</td>
      *       <td>-</td>
      *       <td>-</td>
+     *       <td>-</td>
      *       <td>1 day</td>
      *       <td>-</td>
      *       <td>-</td>
@@ -659,6 +709,9 @@
      *       <td>-</td>
      *       <td>-</td>
      *       <td>-</td>
+     *       <td>-</td>
+     *       <td>-</td>
+     *       <td>-</td>
      *     </tr>
      *     <tr>
      *       <td>
@@ -669,11 +722,15 @@
      *       <td>-</td>
      *       <td>-</td>
      *       <td>-</td>
+     *       <td>-</td>
      *       <td>2 ms</td>
      *       <td>-</td>
      *       <td>-</td>
      *       <td>-</td>
      *       <td>-</td>
+     *       <td>-</td>
+     *       <td>-</td>
+     *       <td>-</td>
      *     </tr>
      *     <tr>
      *       <td>
@@ -684,11 +741,15 @@
      *       <td>-</td>
      *       <td>-</td>
      *       <td>-</td>
+     *       <td>-</td>
      *       <td>600 ms</td>
      *       <td>-</td>
      *       <td>-</td>
      *       <td>-</td>
      *       <td>-</td>
+     *       <td>-</td>
+     *       <td>-</td>
+     *       <td>-</td>
      *     </tr>
      *     <tr>
      *       <td>
@@ -700,10 +761,14 @@
      *       <td>-</td>
      *       <td>-</td>
      *       <td>-</td>
+     *       <td>-</td>
+     *       <td>-</td>
+     *       <td>-</td>
      *       <td>4 ms</td>
      *       <td>4 ms</td>
      *       <td>-</td>
      *       <td>-</td>
+     *       <td>-</td>
      *     </tr>
      *     <tr>
      *       <td>
@@ -716,24 +781,32 @@
      *       <td>-</td>
      *       <td>-</td>
      *       <td>-</td>
+     *       <td>-</td>
+     *       <td>-</td>
+     *       <td>-</td>
      *       <td>100 ms</td>
      *       <td>-</td>
      *       <td>-</td>
+     *       <td>-</td>
      *     </tr>
      *     <tr>
      *       <td>
      *         <strong>STATE_2ND_CODE_LOCK</strong>
      *       </td>
      *       <td>-</td>
-     *       <td>10 ms (optional)</td>
+     *       <td>18000 ms</td>
+     *       <td>10 ms</td>
      *       <td>20 ms</td>
      *       <td>-</td>
      *       <td>-</td>
      *       <td>-</td>
-     *       <td>-</td>
-     *       <td>100 ms (optional)</td>
+     *       <td>18000 ms</td>
      *       <td>100 ms</td>
      *       <td>-</td>
+     *       <td>-</td>
+     *       <td>100 ms</td>
+     *       <td>-</td>
+     *       <td>-</td>
      *     </tr>
      *     <tr>
      *       <td>
@@ -745,10 +818,14 @@
      *       <td>-</td>
      *       <td>-</td>
      *       <td>-</td>
+     *       <td>-</td>
+     *       <td>-</td>
+     *       <td>-</td>
      *       <td>2 s</td>
      *       <td>-</td>
      *       <td>-</td>
      *       <td>-</td>
+     *       <td>-</td>
      *     </tr>
      *     <tr>
      *       <td>
@@ -763,7 +840,11 @@
      *       <td>-</td>
      *       <td>-</td>
      *       <td>-</td>
+     *       <td>-</td>
+     *       <td>-</td>
+     *       <td>-</td>
      *       <td>1 s</td>
+     *       <td>-</td>
      *     </tr>
      *   </tbody>
      * </table>
diff --git a/media/java/android/media/MediaCas.java b/media/java/android/media/MediaCas.java
index 5bc8c04..0a0a626 100644
--- a/media/java/android/media/MediaCas.java
+++ b/media/java/android/media/MediaCas.java
@@ -436,7 +436,7 @@
                     if (mEventHandler != null) {
                         mEventHandler.sendMessage(
                                 mEventHandler.obtainMessage(
-                                        EventHandler.MSG_CAS_EVENT, event, arg, data));
+                                        EventHandler.MSG_CAS_EVENT, event, arg, toBytes(data)));
                     }
                 }
 
diff --git a/media/java/android/media/MediaFormat.java b/media/java/android/media/MediaFormat.java
index 9c629bb..b1b7d40 100644
--- a/media/java/android/media/MediaFormat.java
+++ b/media/java/android/media/MediaFormat.java
@@ -1210,7 +1210,7 @@
 
     /**
      * A key describing the desired bitrate mode to be used by an encoder.
-     * Constants are declared in {@link MediaCodecInfo.CodecCapabilities}.
+     * Constants are declared in {@link MediaCodecInfo.EncoderCapabilities}.
      *
      * @see MediaCodecInfo.EncoderCapabilities#isBitrateModeSupported(int)
      */
diff --git a/media/java/android/media/soundtrigger/SoundTriggerManager.java b/media/java/android/media/soundtrigger/SoundTriggerManager.java
index 0e9ef4c..ae8121a 100644
--- a/media/java/android/media/soundtrigger/SoundTriggerManager.java
+++ b/media/java/android/media/soundtrigger/SoundTriggerManager.java
@@ -94,12 +94,17 @@
             originatorIdentity.packageName = ActivityThread.currentOpPackageName();
 
             try (SafeCloseable ignored = ClearCallingIdentityContext.create()) {
-                List<ModuleProperties> modulePropertiesList = soundTriggerService
-                        .listModuleProperties(originatorIdentity);
-                if (!modulePropertiesList.isEmpty()) {
+                ModuleProperties moduleProperties = soundTriggerService
+                        .listModuleProperties(originatorIdentity)
+                        .stream()
+                        .filter(prop -> !prop.getSupportedModelArch()
+                                .equals(SoundTrigger.FAKE_HAL_ARCH))
+                        .findFirst()
+                        .orElse(null);
+                if (moduleProperties != null) {
                     mSoundTriggerSession = soundTriggerService.attachAsOriginator(
                                                 originatorIdentity,
-                                                modulePropertiesList.get(0),
+                                                moduleProperties,
                                                 mBinderToken);
                 } else {
                     mSoundTriggerSession = null;
diff --git a/media/java/android/media/tv/TvInputManager.java b/media/java/android/media/tv/TvInputManager.java
index 7d6a8b4..ef2b5a5 100644
--- a/media/java/android/media/tv/TvInputManager.java
+++ b/media/java/android/media/tv/TvInputManager.java
@@ -149,10 +149,45 @@
     /**
      * This constant is used as a {@link Bundle} key for TV messages. The value of the key
      * identifies the stream on the TV input source for which the watermark event is relevant to.
+     *
+     * <p> Type: String
      */
     public static final String TV_MESSAGE_KEY_STREAM_ID =
             "android.media.tv.TvInputManager.stream_id";
 
+    /**
+     * This constant is used as a {@link Bundle} key for TV messages. The value of the key
+     * identifies the subtype of the data, such as the format of the CC data. The format
+     * found at this key can then be used to identify how to parse the data at
+     * {@link #TV_MESSAGE_KEY_RAW_DATA}.
+     *
+     * To parse the raw data bsed on the subtype, please refer to the official documentation of the
+     * concerning subtype. For example, for the subtype "ATSC A/335" for watermarking, the
+     * document for A/335 from the ATSC standard details how this data is formatted.
+     *
+     * Some other examples of common formats include:
+     * <ul>
+     *     <li>Watermarking - ATSC A/336</li>
+     *     <li>Closed Captioning - CTA 608-E</li>
+     * </ul>
+     *
+     * <p> Type: String
+     */
+    public static final String TV_MESSAGE_KEY_SUBTYPE =
+            "android.media.tv.TvInputManager.subtype";
+
+    /**
+     * This constant is used as a {@link Bundle} key for TV messages. The value of the key
+     * stores the raw data contained in this TV Message. The format of this data is determined
+     * by the format defined by the subtype, found using the key at
+     * {@link #TV_MESSAGE_KEY_SUBTYPE}. See {@link #TV_MESSAGE_KEY_SUBTYPE} for more
+     * information on how to parse this data.
+     *
+     * <p> Type: byte[]
+     */
+    public static final String TV_MESSAGE_KEY_RAW_DATA =
+            "android.media.tv.TvInputManager.raw_data";
+
     static final int VIDEO_UNAVAILABLE_REASON_START = 0;
     static final int VIDEO_UNAVAILABLE_REASON_END = 18;
 
@@ -802,7 +837,13 @@
          *
          * @param session A {@link TvInputManager.Session} associated with this callback.
          * @param type The type of message received, such as {@link #TV_MESSAGE_TYPE_WATERMARK}
-         * @param data The raw data of the message
+         * @param data The raw data of the message. The bundle keys are:
+         *             {@link TvInputManager#TV_MESSAGE_KEY_STREAM_ID},
+         *             {@link TvInputManager#TV_MESSAGE_KEY_SUBTYPE},
+         *             {@link TvInputManager#TV_MESSAGE_KEY_RAW_DATA}.
+         *             See {@link TvInputManager#TV_MESSAGE_KEY_SUBTYPE} for more information on
+         *             how to parse this data.
+         *
          */
         public void onTvMessage(Session session, @TvInputManager.TvMessageType int type,
                 Bundle data) {
diff --git a/media/java/android/media/tv/TvInputService.java b/media/java/android/media/tv/TvInputService.java
index 3c6ed91..4e380c4 100644
--- a/media/java/android/media/tv/TvInputService.java
+++ b/media/java/android/media/tv/TvInputService.java
@@ -1030,7 +1030,12 @@
          *
          * @param type The of message that was sent, such as
          * {@link TvInputManager#TV_MESSAGE_TYPE_WATERMARK}
-         * @param data The data sent with the message.
+         * @param data The raw data of the message. The bundle keys are:
+         *             {@link TvInputManager#TV_MESSAGE_KEY_STREAM_ID},
+         *             {@link TvInputManager#TV_MESSAGE_KEY_SUBTYPE},
+         *             {@link TvInputManager#TV_MESSAGE_KEY_RAW_DATA}.
+         *             See {@link TvInputManager#TV_MESSAGE_KEY_SUBTYPE} for more information on
+         *             how to parse this data.
          */
         public void notifyTvMessage(@TvInputManager.TvMessageType int type,
                 @NonNull Bundle data) {
@@ -1500,7 +1505,12 @@
          *
          * @param type The type of message received, such as
          * {@link TvInputManager#TV_MESSAGE_TYPE_WATERMARK}
-         * @param data The raw data of the message
+         * @param data The raw data of the message. The bundle keys are:
+         *             {@link TvInputManager#TV_MESSAGE_KEY_STREAM_ID},
+         *             {@link TvInputManager#TV_MESSAGE_KEY_SUBTYPE},
+         *             {@link TvInputManager#TV_MESSAGE_KEY_RAW_DATA}.
+         *             See {@link TvInputManager#TV_MESSAGE_KEY_SUBTYPE} for more information on
+         *             how to parse this data.
          */
         public void onTvMessage(@NonNull @TvInputManager.TvMessageType String type,
                 @NonNull Bundle data) {
diff --git a/media/java/android/media/tv/TvView.java b/media/java/android/media/tv/TvView.java
index 19a2e5d..8a886832 100644
--- a/media/java/android/media/tv/TvView.java
+++ b/media/java/android/media/tv/TvView.java
@@ -1254,7 +1254,12 @@
          * @param inputId The ID of the TV input bound to this view.
          * @param type The type of message received, such as
          *             {@link TvInputManager#TV_MESSAGE_TYPE_WATERMARK}
-         * @param data The raw data of the message
+         * @param data The raw data of the message. The bundle keys are:
+         *             {@link TvInputManager#TV_MESSAGE_KEY_STREAM_ID},
+         *             {@link TvInputManager#TV_MESSAGE_KEY_SUBTYPE},
+         *             {@link TvInputManager#TV_MESSAGE_KEY_RAW_DATA}.
+         *             See {@link TvInputManager#TV_MESSAGE_KEY_SUBTYPE} for more information on
+         *             how to parse this data.
          */
         public void onTvMessage(@NonNull String inputId,
                 @TvInputManager.TvMessageType int type, @NonNull Bundle data) {
diff --git a/media/java/android/media/tv/interactive/TvInteractiveAppService.java b/media/java/android/media/tv/interactive/TvInteractiveAppService.java
index 7dfe16a..69ff9c6 100755
--- a/media/java/android/media/tv/interactive/TvInteractiveAppService.java
+++ b/media/java/android/media/tv/interactive/TvInteractiveAppService.java
@@ -919,7 +919,12 @@
          *
          * @param type The type of message received, such as
          * {@link TvInputManager#TV_MESSAGE_TYPE_WATERMARK}
-         * @param data The raw data of the message
+         * @param data The raw data of the message. The bundle keys are:
+         *             {@link TvInputManager#TV_MESSAGE_KEY_STREAM_ID},
+         *             {@link TvInputManager#TV_MESSAGE_KEY_SUBTYPE},
+         *             {@link TvInputManager#TV_MESSAGE_KEY_RAW_DATA}.
+         *             See {@link TvInputManager#TV_MESSAGE_KEY_SUBTYPE} for more information on
+         *             how to parse this data.
          */
         public void onTvMessage(@TvInputManager.TvMessageType int type,
                 @NonNull Bundle data) {
diff --git a/media/java/android/media/tv/interactive/TvInteractiveAppView.java b/media/java/android/media/tv/interactive/TvInteractiveAppView.java
index 1a0319b..80a1435 100755
--- a/media/java/android/media/tv/interactive/TvInteractiveAppView.java
+++ b/media/java/android/media/tv/interactive/TvInteractiveAppView.java
@@ -944,7 +944,12 @@
      *
      * @param type The type of message received, such as
      * {@link TvInputManager#TV_MESSAGE_TYPE_WATERMARK}
-     * @param data The raw data of the message
+     * @param data The raw data of the message. The bundle keys are:
+     *             {@link TvInputManager#TV_MESSAGE_KEY_STREAM_ID},
+     *             {@link TvInputManager#TV_MESSAGE_KEY_SUBTYPE},
+     *             {@link TvInputManager#TV_MESSAGE_KEY_RAW_DATA}.
+     *             See {@link TvInputManager#TV_MESSAGE_KEY_SUBTYPE} for more information on
+     *             how to parse this data.
      */
     public void notifyTvMessage(@NonNull @TvInputManager.TvMessageType int type,
             @NonNull Bundle data) {
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SearchScaffold.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SearchScaffold.kt
index 4218489b..f6ca0c4 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SearchScaffold.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SearchScaffold.kt
@@ -41,6 +41,7 @@
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.remember
+import androidx.compose.runtime.saveable.rememberSaveable
 import androidx.compose.runtime.setValue
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.draw.alpha
@@ -72,6 +73,7 @@
     actions: @Composable RowScope.() -> Unit = {},
     content: @Composable (bottomPadding: Dp, searchQuery: State<String>) -> Unit,
 ) {
+    var isSearchMode by rememberSaveable { mutableStateOf(false) }
     val viewModel: SearchScaffoldViewModel = viewModel()
 
     val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior()
@@ -82,8 +84,11 @@
                 title = title,
                 actions = actions,
                 scrollBehavior = scrollBehavior,
+                isSearchMode = isSearchMode,
+                onSearchModeChange = { isSearchMode = it },
                 searchQuery = viewModel.searchQuery,
-            ) { viewModel.searchQuery = it }
+                onSearchQueryChange = { viewModel.searchQuery = it },
+            )
         },
     ) { paddingValues ->
         Box(
@@ -95,7 +100,7 @@
             content(
                 bottomPadding = paddingValues.calculateBottomPadding(),
                 searchQuery = remember {
-                    derivedStateOf { viewModel.searchQuery?.text ?: "" }
+                    derivedStateOf { if (isSearchMode) viewModel.searchQuery.text else "" }
                 },
             )
         }
@@ -103,7 +108,8 @@
 }
 
 internal class SearchScaffoldViewModel : ViewModel() {
-    var searchQuery: TextFieldValue? by mutableStateOf(null)
+    // Put in view model because TextFieldValue has not default Saver for rememberSaveable.
+    var searchQuery by mutableStateOf(TextFieldValue())
 }
 
 @OptIn(ExperimentalMaterial3Api::class)
@@ -112,14 +118,16 @@
     title: String,
     actions: @Composable RowScope.() -> Unit,
     scrollBehavior: TopAppBarScrollBehavior,
-    searchQuery: TextFieldValue?,
-    onSearchQueryChange: (TextFieldValue?) -> Unit,
+    isSearchMode: Boolean,
+    onSearchModeChange: (Boolean) -> Unit,
+    searchQuery: TextFieldValue,
+    onSearchQueryChange: (TextFieldValue) -> Unit,
 ) {
-    if (searchQuery != null) {
+    if (isSearchMode) {
         SearchTopAppBar(
             query = searchQuery,
             onQueryChange = onSearchQueryChange,
-            onClose = { onSearchQueryChange(null) },
+            onClose = { onSearchModeChange(false) },
             actions = actions,
         )
     } else {
@@ -127,6 +135,7 @@
             SearchAction {
                 scrollBehavior.collapse()
                 onSearchQueryChange(TextFieldValue())
+                onSearchModeChange(true)
             }
             actions()
         }
diff --git a/packages/SettingsLib/res/values/arrays.xml b/packages/SettingsLib/res/values/arrays.xml
index 5022960..3e2b800 100644
--- a/packages/SettingsLib/res/values/arrays.xml
+++ b/packages/SettingsLib/res/values/arrays.xml
@@ -572,7 +572,7 @@
 
     <!-- BatteryMeterView parameters -->
     <array name="batterymeter_color_levels">
-        <item>15</item>
+        <item>20</item>
         <item>100</item>
     </array>
     <array name="batterymeter_color_values">
diff --git a/packages/SettingsLib/res/values/colors.xml b/packages/SettingsLib/res/values/colors.xml
index 6b7e918..67139b5 100644
--- a/packages/SettingsLib/res/values/colors.xml
+++ b/packages/SettingsLib/res/values/colors.xml
@@ -43,4 +43,6 @@
     <color name="qr_focused_corner_line_color">#ff1a73e8</color>
     <color name="qr_background_color">#b3ffffff</color> <!-- 70% white transparency -->
     <!-- End of QR code scanner colors -->
+
+    <color name="batterymeter_saver_color">#FBBC04</color>
 </resources>
diff --git a/packages/SettingsLib/src/com/android/settingslib/applications/ServiceListing.java b/packages/SettingsLib/src/com/android/settingslib/applications/ServiceListing.java
index bd9e760..c8bcabf 100644
--- a/packages/SettingsLib/src/com/android/settingslib/applications/ServiceListing.java
+++ b/packages/SettingsLib/src/com/android/settingslib/applications/ServiceListing.java
@@ -35,6 +35,7 @@
 import java.util.ArrayList;
 import java.util.HashSet;
 import java.util.List;
+import java.util.function.Predicate;
 
 /**
  * Class for managing services matching a given intent and requesting a given permission.
@@ -51,12 +52,13 @@
     private final HashSet<ComponentName> mEnabledServices = new HashSet<>();
     private final List<ServiceInfo> mServices = new ArrayList<>();
     private final List<Callback> mCallbacks = new ArrayList<>();
+    private final Predicate mValidator;
 
     private boolean mListening;
 
     private ServiceListing(Context context, String tag,
             String setting, String intentAction, String permission, String noun,
-            boolean addDeviceLockedFlags) {
+            boolean addDeviceLockedFlags, Predicate validator) {
         mContentResolver = context.getContentResolver();
         mContext = context;
         mTag = tag;
@@ -65,6 +67,7 @@
         mPermission = permission;
         mNoun = noun;
         mAddDeviceLockedFlags = addDeviceLockedFlags;
+        mValidator = validator;
     }
 
     public void addCallback(Callback callback) {
@@ -137,7 +140,6 @@
         final PackageManager pmWrapper = mContext.getPackageManager();
         List<ResolveInfo> installedServices = pmWrapper.queryIntentServicesAsUser(
                 new Intent(mIntentAction), flags, user);
-
         for (ResolveInfo resolveInfo : installedServices) {
             ServiceInfo info = resolveInfo.serviceInfo;
 
@@ -148,6 +150,9 @@
                         + mPermission);
                 continue;
             }
+            if (mValidator != null && !mValidator.test(info)) {
+                continue;
+            }
             mServices.add(info);
         }
         for (Callback callback : mCallbacks) {
@@ -194,6 +199,7 @@
         private String mPermission;
         private String mNoun;
         private boolean mAddDeviceLockedFlags = false;
+        private Predicate mValidator;
 
         public Builder(Context context) {
             mContext = context;
@@ -224,6 +230,11 @@
             return this;
         }
 
+        public Builder setValidator(Predicate<ServiceInfo> validator) {
+            mValidator = validator;
+            return this;
+        }
+
         /**
          * Set to true to add support for both MATCH_DIRECT_BOOT_AWARE and
          * MATCH_DIRECT_BOOT_UNAWARE flags when querying PackageManager. Required to get results
@@ -236,7 +247,7 @@
 
         public ServiceListing build() {
             return new ServiceListing(mContext, mTag, mSetting, mIntentAction, mPermission, mNoun,
-                    mAddDeviceLockedFlags);
+                    mAddDeviceLockedFlags, mValidator);
         }
     }
 }
diff --git a/packages/SettingsLib/src/com/android/settingslib/graph/ThemedBatteryDrawable.kt b/packages/SettingsLib/src/com/android/settingslib/graph/ThemedBatteryDrawable.kt
index faea5b2..a03acc3 100644
--- a/packages/SettingsLib/src/com/android/settingslib/graph/ThemedBatteryDrawable.kt
+++ b/packages/SettingsLib/src/com/android/settingslib/graph/ThemedBatteryDrawable.kt
@@ -135,7 +135,7 @@
     }
 
     private val errorPaint = Paint(Paint.ANTI_ALIAS_FLAG).also { p ->
-        p.color = Utils.getColorStateListDefaultColor(context, R.color.batterymeter_plus_color)
+        p.color = Utils.getColorStateListDefaultColor(context, R.color.batterymeter_saver_color)
         p.alpha = 255
         p.isDither = true
         p.strokeWidth = 0f
@@ -244,10 +244,11 @@
                 c.drawPath(scaledBolt, fillColorStrokeProtection)
             }
         } else if (powerSaveEnabled) {
-            // If power save is enabled draw the perimeter path with colorError
-            c.drawPath(scaledErrorPerimeter, errorPaint)
+            // If power save is enabled draw the level path with colorError
+            c.drawPath(levelPath, errorPaint)
             // And draw the plus sign on top of the fill
-            c.drawPath(scaledPlus, errorPaint)
+            fillPaint.color = fillColor
+            c.drawPath(scaledPlus, fillPaint)
         }
         c.restore()
     }
@@ -414,7 +415,7 @@
     companion object {
         const val WIDTH = 12f
         const val HEIGHT = 20f
-        private const val CRITICAL_LEVEL = 15
+        private const val CRITICAL_LEVEL = 20
         // On a 12x20 grid, how wide to make the fill protection stroke.
         // Scales when our size changes
         private const val PROTECTION_STROKE_WIDTH = 3f
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/ServiceListingTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/ServiceListingTest.java
index f7fd25b..7ff0988 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/ServiceListingTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/ServiceListingTest.java
@@ -18,20 +18,35 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.anyList;
 import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
 
 import android.content.ComponentName;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.content.pm.ServiceInfo;
 import android.provider.Settings;
 
+import androidx.test.core.app.ApplicationProvider;
+
+import com.google.common.collect.ImmutableList;
+
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
 import org.robolectric.RobolectricTestRunner;
 import org.robolectric.RuntimeEnvironment;
 
+import java.util.List;
+
 @RunWith(RobolectricTestRunner.class)
 public class ServiceListingTest {
 
@@ -39,10 +54,16 @@
     private static final String TEST_INTENT = "com.example.intent";
 
     private ServiceListing mServiceListing;
+    private Context mContext;
+    private PackageManager mPm;
 
     @Before
     public void setUp() {
-        mServiceListing = new ServiceListing.Builder(RuntimeEnvironment.application)
+        mPm = mock(PackageManager.class);
+        mContext = spy(ApplicationProvider.getApplicationContext());
+        when(mContext.getPackageManager()).thenReturn(mPm);
+
+        mServiceListing = new ServiceListing.Builder(mContext)
                 .setTag("testTag")
                 .setSetting(TEST_SETTING)
                 .setNoun("testNoun")
@@ -52,6 +73,81 @@
     }
 
     @Test
+    public void testValidator() {
+        ServiceInfo s1 = new ServiceInfo();
+        s1.permission = "testPermission";
+        s1.packageName = "pkg";
+        ServiceInfo s2 = new ServiceInfo();
+        s2.permission = "testPermission";
+        s2.packageName = "pkg2";
+        ResolveInfo r1 = new ResolveInfo();
+        r1.serviceInfo = s1;
+        ResolveInfo r2 = new ResolveInfo();
+        r2.serviceInfo = s2;
+
+        when(mPm.queryIntentServicesAsUser(any(), anyInt(), anyInt())).thenReturn(
+                ImmutableList.of(r1, r2));
+
+        mServiceListing = new ServiceListing.Builder(mContext)
+                .setTag("testTag")
+                .setSetting(TEST_SETTING)
+                .setNoun("testNoun")
+                .setIntentAction(TEST_INTENT)
+                .setValidator(info -> {
+                    if (info.packageName.equals("pkg")) {
+                        return true;
+                    }
+                    return false;
+                })
+                .setPermission("testPermission")
+                .build();
+        ServiceListing.Callback callback = mock(ServiceListing.Callback.class);
+        mServiceListing.addCallback(callback);
+        mServiceListing.reload();
+
+        verify(mPm).queryIntentServicesAsUser(any(), anyInt(), anyInt());
+        ArgumentCaptor<List<ServiceInfo>> captor = ArgumentCaptor.forClass(List.class);
+        verify(callback, times(1)).onServicesReloaded(captor.capture());
+
+        assertThat(captor.getValue().size()).isEqualTo(1);
+        assertThat(captor.getValue().get(0)).isEqualTo(s1);
+    }
+
+    @Test
+    public void testNoValidator() {
+        ServiceInfo s1 = new ServiceInfo();
+        s1.permission = "testPermission";
+        s1.packageName = "pkg";
+        ServiceInfo s2 = new ServiceInfo();
+        s2.permission = "testPermission";
+        s2.packageName = "pkg2";
+        ResolveInfo r1 = new ResolveInfo();
+        r1.serviceInfo = s1;
+        ResolveInfo r2 = new ResolveInfo();
+        r2.serviceInfo = s2;
+
+        when(mPm.queryIntentServicesAsUser(any(), anyInt(), anyInt())).thenReturn(
+                ImmutableList.of(r1, r2));
+
+        mServiceListing = new ServiceListing.Builder(mContext)
+                .setTag("testTag")
+                .setSetting(TEST_SETTING)
+                .setNoun("testNoun")
+                .setIntentAction(TEST_INTENT)
+                .setPermission("testPermission")
+                .build();
+        ServiceListing.Callback callback = mock(ServiceListing.Callback.class);
+        mServiceListing.addCallback(callback);
+        mServiceListing.reload();
+
+        verify(mPm).queryIntentServicesAsUser(any(), anyInt(), anyInt());
+        ArgumentCaptor<List<ServiceInfo>> captor = ArgumentCaptor.forClass(List.class);
+        verify(callback, times(1)).onServicesReloaded(captor.capture());
+
+        assertThat(captor.getValue().size()).isEqualTo(2);
+    }
+
+    @Test
     public void testCallback() {
         ServiceListing.Callback callback = mock(ServiceListing.Callback.class);
         mServiceListing.addCallback(callback);
diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/GlobalSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/GlobalSettings.java
index 09a1ba2..e50f522 100644
--- a/packages/SettingsProvider/src/android/provider/settings/backup/GlobalSettings.java
+++ b/packages/SettingsProvider/src/android/provider/settings/backup/GlobalSettings.java
@@ -49,6 +49,7 @@
         Settings.Global.CHARGING_SOUNDS_ENABLED,
         Settings.Global.USB_MASS_STORAGE_ENABLED,
         Settings.Global.NETWORK_RECOMMENDATIONS_ENABLED,
+        Settings.Global.NETWORK_AVOID_BAD_WIFI,
         Settings.Global.WIFI_WAKEUP_ENABLED,
         Settings.Global.WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON,
         Settings.Global.USE_OPEN_WIFI_PACKAGE,
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java
index e57cf3b..8d07fb6 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java
@@ -20,6 +20,9 @@
 import static android.hardware.display.HdrConversionMode.HDR_CONVERSION_PASSTHROUGH;
 import static android.hardware.display.HdrConversionMode.HDR_CONVERSION_SYSTEM;
 import static android.media.AudioFormat.SURROUND_SOUND_ENCODING;
+import static android.net.ConnectivitySettingsManager.NETWORK_AVOID_BAD_WIFI_AVOID;
+import static android.net.ConnectivitySettingsManager.NETWORK_AVOID_BAD_WIFI_IGNORE;
+import static android.net.ConnectivitySettingsManager.NETWORK_AVOID_BAD_WIFI_PROMPT;
 import static android.provider.settings.validators.SettingsValidators.ANY_INTEGER_VALIDATOR;
 import static android.provider.settings.validators.SettingsValidators.ANY_STRING_VALIDATOR;
 import static android.provider.settings.validators.SettingsValidators.BOOLEAN_VALIDATOR;
@@ -103,6 +106,14 @@
         VALIDATORS.put(
                 Global.NETWORK_RECOMMENDATIONS_ENABLED,
                 new DiscreteValueValidator(new String[] {"-1", "0", "1"}));
+        VALIDATORS.put(
+                Global.NETWORK_AVOID_BAD_WIFI,
+                new DiscreteValueValidator(
+                        new String[] {
+                                String.valueOf(NETWORK_AVOID_BAD_WIFI_IGNORE),
+                                String.valueOf(NETWORK_AVOID_BAD_WIFI_PROMPT),
+                                String.valueOf(NETWORK_AVOID_BAD_WIFI_AVOID),
+                        }));
         VALIDATORS.put(Global.WIFI_WAKEUP_ENABLED, BOOLEAN_VALIDATOR);
         VALIDATORS.put(Global.WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON, BOOLEAN_VALIDATOR);
         VALIDATORS.put(
diff --git a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
index 278ceb9..1f14723 100644
--- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
+++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
@@ -372,7 +372,6 @@
                     Settings.Global.NETPOLICY_QUOTA_FRAC_JOBS,
                     Settings.Global.NETPOLICY_QUOTA_FRAC_MULTIPATH,
                     Settings.Global.NETPOLICY_OVERRIDE_ENABLED,
-                    Settings.Global.NETWORK_AVOID_BAD_WIFI,
                     Settings.Global.NETWORK_DEFAULT_DAILY_MULTIPATH_QUOTA_BYTES,
                     Settings.Global.NETWORK_METERED_MULTIPATH_PREFERENCE,
                     Settings.Global.NETWORK_WATCHLIST_LAST_REPORT_TIME,
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index 8cbf5f8..4c48f0e 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -828,6 +828,8 @@
     <!-- Permission required for CTS test - CtsTelephonyProviderTestCases -->
     <uses-permission android:name="android.permission.WRITE_APN_SETTINGS" />
 
+    <uses-permission android:name="android.permission.LOG_FOREGROUND_RESOURCE_USE"/>
+
     <application android:label="@string/app_label"
                 android:theme="@android:style/Theme.DeviceDefault.DayNight"
                 android:defaultToDeviceProtectedStorage="true"
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/AndroidManifest.xml b/packages/SystemUI/accessibility/accessibilitymenu/AndroidManifest.xml
index ca84265..440c6e5 100644
--- a/packages/SystemUI/accessibility/accessibilitymenu/AndroidManifest.xml
+++ b/packages/SystemUI/accessibility/accessibilitymenu/AndroidManifest.xml
@@ -58,5 +58,8 @@
         <intent>
             <action android:name="android.intent.action.VOICE_COMMAND" />
         </intent>
+        <!--intent>
+            <action android:name="android.settings.ACCESSIBILITY_SETTINGS" />
+        </intent-->
     </queries>
 </manifest>
\ No newline at end of file
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/res/drawable-night/a11ymenu_intro.xml b/packages/SystemUI/accessibility/accessibilitymenu/res/drawable-night/a11ymenu_intro.xml
new file mode 100644
index 0000000..b2a0b32
--- /dev/null
+++ b/packages/SystemUI/accessibility/accessibilitymenu/res/drawable-night/a11ymenu_intro.xml
@@ -0,0 +1,90 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="412dp"
+    android:height="300dp"
+    android:viewportWidth="412"
+    android:viewportHeight="300">
+  <path
+      android:fillColor="#FF000000"
+      android:pathData="M383.9,300H28.1c-15.5,0 -28.1,-12.6 -28.1,-28.1V28.1C0,12.6 12.6,0 28.1,0H383.9c15.5,0 28.1,12.6 28.1,28.1v243.8c0,15.5 -12.6,28.1 -28.1,28.1Z"/>
+  <path
+      android:pathData="M106.4,0h195.3c2,0 3.6,0.2 3.6,0.4V31.2c0,0.2 -1.6,0.4 -3.6,0.4H106.4c-2,0 -3.6,-0.2 -3.6,-0.4V0.4c0,-0.2 1.6,-0.4 3.6,-0.4Z"
+      android:fillColor="#3b4043"/>
+  <path
+      android:pathData="M303.7,238.9H104.5v1h98.4v29.5h1v-29.5h99.8v-1Z"
+      android:fillColor="#3b4043"/>
+  <path
+      android:pathData="M153.7,258.3l0.7,-0.7 -2.7,-2.7h5.9v-1h-5.9l2.7,-2.7 -0.7,-0.7 -3.9,3.9 3.9,3.9Z"
+      android:fillColor="#3b4043"
+      android:fillType="evenOdd"/>
+  <path
+      android:pathData="M253.5,250.4l-0.7,0.7 2.7,2.7h-5.9v1h5.9l-2.7,2.7 0.7,0.7 3.9,-3.9 -3.9,-3.9Z"
+      android:fillColor="#7f868c"
+      android:fillType="evenOdd"/>
+  <path
+      android:pathData="M119.3,273h169.8c10.2,0 18.5,-8.3 18.5,-18.5V73.7c2,0 3.7,-1.7 3.7,-3.7V33.1c0,-2 -1.7,-3.7 -3.7,-3.7V0h-3.7V254.5c0,8.1 -6.6,14.8 -14.8,14.8H119.3c-8.1,0 -14.8,-6.6 -14.8,-14.8V0h-3.7V254.5c0,10.2 8.3,18.5 18.5,18.5Z"
+      android:fillColor="#80868b"/>
+  <path
+      android:pathData="M141.86,52.23h0c9.77,0 17.7,7.92 17.7,17.7h0c0,9.77 -7.92,17.7 -17.7,17.7h0c-9.77,0 -17.7,-7.92 -17.7,-17.7h0c0,-9.77 7.92,-17.7 17.7,-17.7Z"
+      android:fillColor="#2197f3"/>
+  <path
+      android:fillColor="#FF000000"
+      android:pathData="M141.86,81.15l-2.93,-2.93h-4.39c-0.39,0 -0.73,-0.15 -1.02,-0.44 -0.29,-0.29 -0.44,-0.63 -0.44,-1.02v-14.63c0,-0.39 0.15,-0.73 0.44,-1.02 0.29,-0.29 0.63,-0.44 1.02,-0.44h14.63c0.39,0 0.73,0.15 1.02,0.44 0.29,0.29 0.44,0.63 0.44,1.02v14.63c0,0.39 -0.15,0.73 -0.44,1.02 -0.29,0.29 -0.63,0.44 -1.02,0.44h-4.39l-2.93,2.93ZM141.88,74l1.37,-3.12 3.12,-1.37 -3.12,-1.37 -1.37,-3.12 -1.39,3.12 -3.1,1.37 3.1,1.37 1.39,3.12Z"/>
+  <path
+      android:pathData="M270.14,52.23h0c9.77,0 17.7,7.92 17.7,17.7h0c0,9.77 -7.92,17.7 -17.7,17.7h0c-9.77,0 -17.7,-7.92 -17.7,-17.7h0c0,-9.77 7.92,-17.7 17.7,-17.7Z"
+      android:fillColor="#dbdce0"/>
+  <path
+      android:fillColor="#FF000000"
+      android:pathData="M269.25,62.01h1.77v8.74h-1.77v-8.74ZM274.01,65.22l1.22,-1.22c1.66,1.44 2.76,3.54 2.76,5.97 0,4.31 -3.54,7.85 -7.85,7.85s-7.85,-3.54 -7.85,-7.85c0,-2.43 1.11,-4.53 2.76,-5.97l1.22,1.22c-1.33,1.11 -2.21,2.88 -2.21,4.76 0,3.43 2.76,6.19 6.19,6.19 3.43,0 6.19,-2.76 6.19,-6.19 -0.11,-1.99 -1.11,-3.65 -2.43,-4.76Z"
+      android:fillType="evenOdd"/>
+  <path
+      android:pathData="M207.03,52.23h0c9.77,0 17.7,7.92 17.7,17.7h0c0,9.77 -7.92,17.7 -17.7,17.7h0c-9.77,0 -17.7,-7.92 -17.7,-17.7h0c0,-9.77 7.92,-17.7 17.7,-17.7Z"
+      android:fillColor="#d9affd"/>
+  <path
+      android:fillColor="#FF000000"
+      android:pathData="M207.03,62.6c-0.44,0 -0.81,-0.16 -1.13,-0.47 -0.31,-0.31 -0.47,-0.69 -0.47,-1.13s0.16,-0.81 0.47,-1.13c0.31,-0.31 0.69,-0.47 1.13,-0.47s0.81,0.16 1.13,0.47c0.31,0.31 0.47,0.69 0.47,1.13s-0.16,0.81 -0.47,1.13 -0.69,0.47 -1.13,0.47ZM204.75,76.06v-10.83c-0.92,-0.07 -1.85,-0.18 -2.78,-0.32 -0.94,-0.14 -1.8,-0.31 -2.61,-0.52l0.33,-1.31c1.17,0.29 2.37,0.5 3.61,0.64 1.23,0.13 2.48,0.2 3.74,0.2s2.5,-0.07 3.74,-0.2c1.23,-0.13 2.44,-0.34 3.61,-0.64l0.33,1.31c-0.8,0.2 -1.67,0.38 -2.61,0.52 -0.94,0.14 -1.86,0.24 -2.78,0.32v10.83h-1.31v-5.35h-1.93v5.35h-1.31ZM203.45,80.44c-0.25,0 -0.45,-0.08 -0.6,-0.23 -0.15,-0.15 -0.23,-0.35 -0.23,-0.6 0,-0.25 0.08,-0.45 0.23,-0.6 0.15,-0.15 0.35,-0.23 0.6,-0.23s0.45,0.08 0.6,0.23c0.15,0.15 0.23,0.35 0.23,0.6 0,0.25 -0.08,0.45 -0.23,0.6 -0.15,0.15 -0.35,0.23 -0.6,0.23ZM207.05,80.44c-0.25,0 -0.45,-0.08 -0.6,-0.23 -0.15,-0.15 -0.23,-0.35 -0.23,-0.6 0,-0.25 0.08,-0.45 0.23,-0.6 0.15,-0.15 0.35,-0.23 0.6,-0.23s0.45,0.08 0.6,0.23c0.15,0.15 0.23,0.35 0.23,0.6 0,0.25 -0.08,0.45 -0.23,0.6 -0.15,0.15 -0.35,0.23 -0.6,0.23ZM210.64,80.44c-0.25,0 -0.45,-0.08 -0.6,-0.23 -0.15,-0.15 -0.23,-0.35 -0.23,-0.6 0,-0.25 0.08,-0.45 0.23,-0.6 0.15,-0.15 0.35,-0.23 0.6,-0.23s0.45,0.08 0.6,0.23c0.15,0.15 0.23,0.35 0.23,0.6 0,0.25 -0.08,0.45 -0.23,0.6 -0.15,0.15 -0.35,0.23 -0.6,0.23Z"/>
+  <path
+      android:pathData="M141.86,180.81h0c9.77,0 17.7,7.92 17.7,17.7h0c0,9.77 -7.92,17.7 -17.7,17.7h0c-9.77,0 -17.7,-7.92 -17.7,-17.7h0c0,-9.77 7.92,-17.7 17.7,-17.7Z"
+      android:fillColor="#fdd663"/>
+  <path
+      android:fillColor="#FF000000"
+      android:pathData="M141.88,209.09l-3.16,-3.06h-4.35v-4.35l-3.13,-3.13 3.13,-3.13v-4.35h4.35l3.16,-3.13 3.11,3.13h4.35v4.35l3.13,3.13 -3.13,3.13v4.35h-4.35l-3.11,3.06ZM141.88,203.08c-1.26,0 -2.34,-0.44 -3.23,-1.33 -0.89,-0.89 -1.33,-1.96 -1.33,-3.23s0.44,-2.34 1.33,-3.23c0.89,-0.89 1.96,-1.33 3.23,-1.33 1.26,0 2.34,0.44 3.23,1.33 0.89,0.89 1.33,1.96 1.33,3.23s-0.44,2.34 -1.33,3.23c-0.89,0.89 -1.96,1.33 -3.23,1.33ZM141.88,201.68c0.89,0 1.64,-0.3 2.24,-0.91 0.61,-0.61 0.91,-1.36 0.91,-2.24 -0,-0.89 -0.3,-1.64 -0.91,-2.24 -0.61,-0.61 -1.36,-0.91 -2.24,-0.91 -0.89,0 -1.64,0.3 -2.24,0.91 -0.61,0.61 -0.91,1.36 -0.91,2.24s0.3,1.64 0.91,2.24c0.61,0.61 1.36,0.91 2.24,0.91ZM141.88,207.12l2.53,-2.5h3.53v-3.53l2.55,-2.55 -2.55,-2.55v-3.53h-3.53l-2.53,-2.55 -2.57,2.55h-3.53v3.53l-2.55,2.55 2.55,2.55v3.53h3.51l2.6,2.5Z"/>
+  <path
+      android:pathData="M207.03,180.82h0c9.77,0 17.7,7.92 17.7,17.7h0c0,9.77 -7.92,17.7 -17.7,17.7h0c-9.77,0 -17.7,-7.92 -17.7,-17.7h0c0,-9.77 7.92,-17.7 17.7,-17.7Z"
+      android:fillColor="#fdd663"/>
+  <path
+      android:fillColor="#FF000000"
+      android:pathData="M207.05,209.09l-3.16,-3.06h-4.35v-4.35l-3.13,-3.13 3.13,-3.13v-4.35h4.35l3.16,-3.13 3.11,3.13h4.35v4.35l3.13,3.13 -3.13,3.13v4.35h-4.35l-3.11,3.06ZM207.05,203.08c1.26,0 2.34,-0.44 3.23,-1.33 0.89,-0.89 1.33,-1.96 1.33,-3.23s-0.44,-2.34 -1.33,-3.23c-0.89,-0.89 -1.96,-1.33 -3.23,-1.33 -1.26,0 -2.34,0.44 -3.23,1.33 -0.89,0.89 -1.33,1.96 -1.33,3.23s0.44,2.34 1.33,3.23c0.89,0.89 1.96,1.33 3.23,1.33ZM207.05,201.68c0.89,0 1.64,-0.3 2.24,-0.91 0.61,-0.61 0.91,-1.36 0.91,-2.24 -0,-0.89 -0.3,-1.64 -0.91,-2.24 -0.61,-0.61 -1.36,-0.91 -2.24,-0.91 -0.89,0 -1.64,0.3 -2.24,0.91 -0.61,0.61 -0.91,1.36 -0.91,2.24s0.3,1.64 0.91,2.24c0.61,0.61 1.36,0.91 2.24,0.91ZM207.05,207.13l2.53,-2.5h3.53v-3.53l2.55,-2.55 -2.55,-2.55v-3.53h-3.53l-2.53,-2.55 -2.57,2.55h-3.53v3.53l-2.55,2.55 2.55,2.55v3.53h3.51l2.6,2.5ZM207.05,201.68c0.89,0 1.64,-0.3 2.24,-0.91 0.61,-0.61 0.91,-1.36 0.91,-2.24 -0,-0.89 -0.3,-1.64 -0.91,-2.24 -0.61,-0.61 -1.36,-0.91 -2.24,-0.91 -0.89,0 -1.64,0.3 -2.24,0.91 -0.61,0.61 -0.91,1.36 -0.91,2.24s0.3,1.64 0.91,2.24c0.61,0.61 1.36,0.91 2.24,0.91Z"/>
+  <path
+      android:pathData="M270.14,180.81h0c9.77,0 17.7,7.92 17.7,17.7h0c0,9.77 -7.92,17.7 -17.7,17.7h0c-9.77,0 -17.7,-7.92 -17.7,-17.7h0c0,-9.77 7.92,-17.7 17.7,-17.7Z"
+      android:fillColor="#84e39f"/>
+  <path
+      android:fillColor="#FF000000"
+      android:pathData="M263.92,207.44c-0.4,0 -0.74,-0.14 -1.02,-0.42s-0.42,-0.62 -0.42,-1.02v-10.37c0,-0.4 0.14,-0.74 0.42,-1.02s0.62,-0.42 1.02,-0.42h1.67v-2.29c0,-1.26 0.44,-2.33 1.33,-3.22 0.88,-0.88 1.96,-1.33 3.22,-1.33s2.33,0.44 3.22,1.33c0.88,0.88 1.33,1.96 1.33,3.22v2.29h1.67c0.4,0 0.74,0.14 1.02,0.42s0.42,0.62 0.42,1.02v10.37c0,0.4 -0.14,0.74 -0.42,1.02s-0.62,0.42 -1.02,0.42h-12.43ZM263.92,206.01h12.43v-10.37h-12.43v10.37ZM270.14,202.66c0.51,0 0.94,-0.18 1.3,-0.53s0.54,-0.77 0.54,-1.27c0,-0.48 -0.18,-0.91 -0.54,-1.3s-0.79,-0.59 -1.3,-0.59 -0.94,0.2 -1.3,0.59c-0.36,0.39 -0.54,0.82 -0.54,1.3 0,0.49 0.18,0.92 0.54,1.27s0.79,0.53 1.3,0.53ZM267.03,194.2h6.22v-2.29c0,-0.86 -0.3,-1.59 -0.91,-2.2 -0.61,-0.61 -1.34,-0.91 -2.2,-0.91s-1.59,0.3 -2.2,0.91 -0.91,1.34 -0.91,2.2v2.29ZM263.92,206.01v0Z"/>
+  <path
+      android:pathData="M207.03,116.5h0c9.77,0 17.7,7.92 17.7,17.7h0c0,9.77 -7.92,17.7 -17.7,17.7h0c-9.77,0 -17.7,-7.92 -17.7,-17.7h0c0,-9.77 7.92,-17.7 17.7,-17.7Z"
+      android:fillColor="#7ae2d4"/>
+  <path
+      android:fillColor="#FF000000"
+      android:pathData="M209.17,143.6v-1.66c1.73,-0.5 3.15,-1.46 4.25,-2.88 1.1,-1.42 1.65,-3.03 1.65,-4.84 0,-1.8 -0.54,-3.42 -1.63,-4.85s-2.51,-2.38 -4.26,-2.87v-1.66c2.22,0.5 4.02,1.62 5.41,3.36 1.39,1.74 2.09,3.75 2.09,6.02s-0.7,4.27 -2.09,6.02c-1.39,1.74 -3.2,2.86 -5.41,3.36ZM197.38,137.46v-6.43h4.29l5.36,-5.36v17.15l-5.36,-5.36h-4.29ZM208.63,138.75v-9.03c0.98,0.3 1.76,0.88 2.34,1.71 0.58,0.84 0.87,1.78 0.87,2.81 0,1.02 -0.29,1.95 -0.88,2.79s-1.37,1.41 -2.33,1.71ZM205.42,129.75l-3.03,2.89h-3.4v3.22h3.4l3.03,2.92v-9.03Z"/>
+  <path
+      android:pathData="M270.14,116.54h0c9.77,0 17.7,7.92 17.7,17.7h0c0,9.77 -7.92,17.7 -17.7,17.7h0c-9.77,0 -17.7,-7.92 -17.7,-17.7h0c0,-9.77 7.92,-17.7 17.7,-17.7Z"
+      android:fillColor="#efa5de"/>
+  <path
+      android:fillColor="#FF000000"
+      android:pathData="M275.08,127.12h-9.89v14.23h9.89v-14.23Z"/>
+  <path
+      android:fillColor="#FF000000"
+      android:pathData="M263.88,129.91h-3.56v8.76h3.56v-8.76Z"/>
+  <path
+      android:fillColor="#FF000000"
+      android:pathData="M279.96,129.91h-3.56v8.76h3.56v-8.76Z"/>
+  <path
+      android:pathData="M267.04,128.82h6.21v10.83h-6.21z"
+      android:fillColor="#efa5de"/>
+  <path
+      android:pathData="M141.86,116.5h0c9.77,0 17.7,7.92 17.7,17.7h0c0,9.77 -7.92,17.7 -17.7,17.7h0c-9.77,0 -17.7,-7.92 -17.7,-17.7h0c0,-9.77 7.92,-17.7 17.7,-17.7Z"
+      android:fillColor="#7ae2d4"/>
+  <path
+      android:fillColor="#FF000000"
+      android:pathData="M134.62,137.44v-6.43h4.29l5.36,-5.36v17.16l-5.36,-5.36h-4.29ZM145.88,138.73v-9.03c0.97,0.3 1.74,0.88 2.33,1.72 0.59,0.84 0.88,1.78 0.88,2.81 0,1.05 -0.29,1.99 -0.88,2.81s-1.37,1.39 -2.33,1.69ZM142.66,129.72l-3.03,2.9h-3.4v3.22h3.4l3.03,2.92v-9.03Z"/>
+</vector>
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/res/drawable-sw600dp-night/a11ymenu_intro.xml b/packages/SystemUI/accessibility/accessibilitymenu/res/drawable-sw600dp-night/a11ymenu_intro.xml
index 7a4fbb2..cb2e974 100644
--- a/packages/SystemUI/accessibility/accessibilitymenu/res/drawable-sw600dp-night/a11ymenu_intro.xml
+++ b/packages/SystemUI/accessibility/accessibilitymenu/res/drawable-sw600dp-night/a11ymenu_intro.xml
@@ -3,274 +3,104 @@
     android:height="300dp"
     android:viewportWidth="412"
     android:viewportHeight="300">
-  <group>
-    <clip-path
-        android:pathData="M62.23,50.19L349.77,50.19A16,16 0,0 1,365.77 66.19L365.77,236.14A16,16 0,0 1,349.77 252.14L62.23,252.14A16,16 0,0 1,46.23 236.14L46.23,66.19A16,16 0,0 1,62.23 50.19z"/>
-    <path
-        android:pathData="M62.23,50.19L349.77,50.19A16,16 0,0 1,365.77 66.19L365.77,236.14A16,16 0,0 1,349.77 252.14L62.23,252.14A16,16 0,0 1,46.23 236.14L46.23,66.19A16,16 0,0 1,62.23 50.19z"
-        android:fillColor="#3B4043"/>
-    <path
-        android:pathData="M46.23,52.14h160v200h-160z"
-        android:fillColor="#000000"/>
-    <path
-        android:pathData="M86.21,240.04L86.91,239.34L84.21,236.64H90.11V235.64H84.21L86.91,232.94L86.21,232.24L82.31,236.14L86.21,240.04Z"
-        android:fillColor="#3B4043"
-        android:fillType="evenOdd"/>
-    <path
-        android:pathData="M125.71,220.14V252.14H127.71V220.14H125.71Z"
-        android:fillColor="#3B4043"/>
-    <path
-        android:pathData="M166.21,232.24L165.51,232.94L168.21,235.64H162.31V236.64H168.21L165.51,239.34L166.21,240.04L170.11,236.14L166.21,232.24Z"
-        android:fillColor="#7F868C"
-        android:fillType="evenOdd"/>
-    <path
-        android:pathData="M46.71,221.14H206.71V219.14H46.71V221.14Z"
-        android:fillColor="#3B4043"/>
-    <path
-        android:pathData="M78.71,120.5L78.71,120.5A16,16 0,0 1,94.71 136.5L94.71,136.5A16,16 0,0 1,78.71 152.5L78.71,152.5A16,16 0,0 1,62.71 136.5L62.71,136.5A16,16 0,0 1,78.71 120.5z"
-        android:fillColor="#327969"/>
-    <group>
-      <clip-path
-          android:pathData="M73.36,138.95V134.15H76.56L80.56,130.15V142.85L76.56,138.85H73.36V138.95ZM82.06,133.35C83.26,133.95 84.06,135.15 84.06,136.55C84.06,137.95 83.26,139.15 82.06,139.75V133.35Z"
-          android:fillType="evenOdd"/>
-      <path
-          android:pathData="M88.36,127.05H69.36V146.05H88.36V127.05Z"
-          android:fillColor="#000000"/>
-    </group>
-    <path
-        android:pathData="M78.71,172.5L78.71,172.5A16,16 0,0 1,94.71 188.5L94.71,188.5A16,16 0,0 1,78.71 204.5L78.71,204.5A16,16 0,0 1,62.71 188.5L62.71,188.5A16,16 0,0 1,78.71 172.5z"
-        android:fillColor="#DE9834"/>
-    <path
-        android:pathData="M87.66,188.5L85.06,191.1V194.8H81.36L78.76,197.4L76.16,194.8H72.36V191.1L69.76,188.5L72.36,185.9V182.2H76.06L78.66,179.6L81.26,182.2H84.96V185.9L87.66,188.5ZM73.96,188.5C73.96,191.1 76.06,193.3 78.76,193.3C81.36,193.3 83.56,191.2 83.56,188.5C83.56,185.8 81.46,183.7 78.76,183.7C76.06,183.7 73.96,185.8 73.96,188.5Z"
-        android:fillColor="#000000"
-        android:fillType="evenOdd"/>
-    <path
-        android:pathData="M80.66,187.9H76.66V189.4H80.66V187.9Z"
-        android:fillColor="#000000"/>
-    <path
-        android:pathData="M126.71,120.5L126.71,120.5A16,16 0,0 1,142.71 136.5L142.71,136.5A16,16 0,0 1,126.71 152.5L126.71,152.5A16,16 0,0 1,110.71 136.5L110.71,136.5A16,16 0,0 1,126.71 120.5z"
-        android:fillColor="#327969"/>
-    <path
-        android:pathData="M128.31,131.1V129.5C131.51,130.2 133.91,133.1 133.91,136.5C133.91,139.9 131.51,142.7 128.31,143.5V141.9C130.61,141.2 132.31,139.1 132.31,136.6C132.31,134.1 130.61,131.8 128.31,131.1ZM130.31,136.4C130.31,135 129.51,133.8 128.31,133.2V139.6C129.41,139 130.31,137.8 130.31,136.4Z"
-        android:fillColor="#8B3737"
-        android:fillType="evenOdd"/>
-    <path
-        android:pathData="M126.72,142.79V130.13L122.71,134.08H119.5V138.78H122.71L126.72,142.79Z"
-        android:fillColor="#8B3737"/>
-    <group>
-      <clip-path
-          android:pathData="M126.72,142.79V130.13L122.71,134.08H119.5V138.78H122.71L126.72,142.79Z"/>
-      <clip-path
-          android:pathData="M128.31,131.1V129.5C131.51,130.2 133.91,133.1 133.91,136.5C133.91,139.9 131.51,142.7 128.31,143.5V141.9C130.61,141.2 132.31,139.1 132.31,136.6C132.31,134.1 130.61,131.8 128.31,131.1ZM130.31,136.4C130.31,135 129.51,133.8 128.31,133.2V139.6C129.41,139 130.31,137.8 130.31,136.4Z"
-          android:fillType="evenOdd"/>
-      <path
-          android:pathData="M128.31,131.1H78.31V168.15L113.75,178.94L128.31,131.1ZM128.31,129.5L138.99,80.66L78.31,67.38V129.5H128.31ZM128.31,143.5H78.31V207.54L140.44,192.01L128.31,143.5ZM128.31,141.9L113.75,94.07L78.31,104.86V141.9H128.31ZM128.31,133.2L150.67,88.48L78.31,52.3V133.2H128.31ZM128.31,139.6H78.31V223.83L152.25,183.5L128.31,139.6ZM126.72,130.13H176.73V10.79L91.66,94.5L126.72,130.13ZM126.72,142.79L91.37,178.15L176.73,263.5V142.79H126.72ZM122.71,138.78L158.07,103.43L143.43,88.78H122.71V138.78ZM119.5,138.78H69.5V188.78H119.5V138.78ZM119.5,134.08V84.08H69.5V134.08H119.5ZM122.71,134.08V184.08H143.19L157.78,169.72L122.71,134.08ZM178.31,131.1V129.5H78.31V131.1H178.31ZM117.63,178.35C98.03,174.06 83.91,156.76 83.91,136.5H183.91C183.91,109.44 164.99,86.34 138.99,80.66L117.63,178.35ZM83.91,136.5C83.91,115.15 98.93,99.31 116.18,94.99L140.44,192.01C164.09,186.1 183.91,164.66 183.91,136.5H83.91ZM178.31,143.5V141.9H78.31V143.5H178.31ZM142.87,189.74C164.72,183.09 182.31,162.64 182.31,136.6H82.31C82.31,115.57 96.5,99.32 113.75,94.07L142.87,189.74ZM182.31,136.6C182.31,112.64 166.74,90.53 142.87,83.27L113.75,178.94C94.48,173.07 82.31,155.56 82.31,136.6H182.31ZM180.31,136.4C180.31,115.04 167.88,97.08 150.67,88.48L105.95,177.92C91.14,170.52 80.31,154.96 80.31,136.4H180.31ZM78.31,133.2V139.6H178.31V133.2H78.31ZM152.25,183.5C166.09,175.95 180.31,159.6 180.31,136.4H80.31C80.31,116 92.73,102.06 104.37,95.71L152.25,183.5ZM76.72,130.13V142.79H176.73V130.13H76.72ZM162.08,107.44L158.07,103.43L87.36,174.14L91.37,178.15L162.08,107.44ZM122.71,88.78H119.5V188.78H122.71V88.78ZM169.5,138.78V134.08H69.5V138.78H169.5ZM119.5,184.08H122.71V84.08H119.5V184.08ZM157.78,169.72L161.79,165.77L91.66,94.5L87.65,98.44L157.78,169.72Z"
-          android:fillColor="#000000"/>
-    </group>
-    <path
-        android:pathData="M78.71,68.5L78.71,68.5A16,16 0,0 1,94.71 84.5L94.71,84.5A16,16 0,0 1,78.71 100.5L78.71,100.5A16,16 0,0 1,62.71 84.5L62.71,84.5A16,16 0,0 1,78.71 68.5z"
-        android:fillColor="#3B4043"/>
-    <path
-        android:pathData="M86.26,82.15C86.92,82.15 87.46,81.61 87.46,80.95C87.46,80.29 86.92,79.75 86.26,79.75C85.6,79.75 85.06,80.29 85.06,80.95C85.06,81.61 85.6,82.15 86.26,82.15Z"
-        android:fillColor="#34A853"/>
-    <path
-        android:pathData="M82.66,86.15C83.99,86.15 85.06,85.08 85.06,83.75C85.06,82.43 83.99,81.35 82.66,81.35C81.34,81.35 80.26,82.43 80.26,83.75C80.26,85.08 81.34,86.15 82.66,86.15Z"
-        android:fillColor="#EA4335"/>
-    <path
-        android:pathData="M82.66,92.45C84.21,92.45 85.46,91.2 85.46,89.65C85.46,88.11 84.21,86.85 82.66,86.85C81.11,86.85 79.86,88.11 79.86,89.65C79.86,91.2 81.11,92.45 82.66,92.45Z"
-        android:fillColor="#FBBC04"/>
-    <path
-        android:pathData="M74.76,86.15C77.41,86.15 79.56,84 79.56,81.35C79.56,78.7 77.41,76.55 74.76,76.55C72.11,76.55 69.96,78.7 69.96,81.35C69.96,84 72.11,86.15 74.76,86.15Z"
-        android:fillColor="#4285F4"/>
-    <path
-        android:pathData="M174.71,120.5L174.71,120.5A16,16 0,0 1,190.71 136.5L190.71,136.5A16,16 0,0 1,174.71 152.5L174.71,152.5A16,16 0,0 1,158.71 136.5L158.71,136.5A16,16 0,0 1,174.71 120.5z"
-        android:fillColor="#9F3EBF"/>
-    <path
-        android:pathData="M178.53,130.5H171.03V142.2H178.53V130.5Z"
-        android:fillColor="#000000"/>
-    <path
-        android:pathData="M170.23,132.8H167.53V140H170.23V132.8Z"
-        android:fillColor="#000000"/>
-    <path
-        android:pathData="M182.23,132.8H179.53V140H182.23V132.8Z"
-        android:fillColor="#000000"/>
-    <path
-        android:pathData="M126.71,172.5L126.71,172.5A16,16 0,0 1,142.71 188.5L142.71,188.5A16,16 0,0 1,126.71 204.5L126.71,204.5A16,16 0,0 1,110.71 188.5L110.71,188.5A16,16 0,0 1,126.71 172.5z"
-        android:fillColor="#DE9834"/>
-    <path
-        android:pathData="M135.71,188.5L133.11,191.1V194.8H129.31L126.71,197.4L124.11,194.8H120.31V191.1L117.71,188.5L120.31,185.9V182.2H124.01L126.61,179.6L129.21,182.2H132.91V185.9L135.71,188.5ZM121.91,188.5C121.91,191.1 124.01,193.3 126.71,193.3C129.31,193.3 131.51,191.2 131.51,188.5C131.51,185.8 129.41,183.7 126.71,183.7C124.11,183.7 121.91,185.8 121.91,188.5Z"
-        android:fillColor="#000000"
-        android:fillType="evenOdd"/>
-    <path
-        android:pathData="M127.21,187.9V186.4H126.21V187.9H124.71V188.9H126.21V190.4H127.21V188.9H128.71V187.9H127.21Z"
-        android:fillColor="#000000"
-        android:fillType="evenOdd"/>
-    <path
-        android:pathData="M126.71,68.5L126.71,68.5A16,16 0,0 1,142.71 84.5L142.71,84.5A16,16 0,0 1,126.71 100.5L126.71,100.5A16,16 0,0 1,110.71 84.5L110.71,84.5A16,16 0,0 1,126.71 68.5z"
-        android:fillColor="#521BBF"/>
-    <path
-        android:pathData="M128.36,78.51C128.36,79.31 127.66,80.01 126.76,80.01C125.86,80.01 125.16,79.31 125.16,78.51C125.16,77.71 125.86,77.01 126.76,77.01C127.66,76.91 128.36,77.61 128.36,78.51ZM126.76,80.71C128.96,80.71 131.46,80.51 133.46,79.91L133.86,81.41C132.36,81.81 130.66,82.01 129.06,82.21V92.01H127.46V87.51H125.86V92.01H124.36V82.21C122.76,82.11 121.06,81.81 119.56,81.41L119.96,79.91C122.06,80.51 124.46,80.71 126.76,80.71Z"
-        android:fillColor="#000000"
-        android:fillType="evenOdd"/>
-    <path
-        android:pathData="M174.71,172.5L174.71,172.5A16,16 0,0 1,190.71 188.5L190.71,188.5A16,16 0,0 1,174.71 204.5L174.71,204.5A16,16 0,0 1,158.71 188.5L158.71,188.5A16,16 0,0 1,174.71 172.5z"
-        android:fillColor="#438947"/>
-    <path
-        android:pathData="M179.11,185.95H178.41V184.45C178.41,182.45 176.81,180.75 174.71,180.75C172.61,180.75 171.01,182.35 171.01,184.45V185.95H170.31C169.51,185.95 168.81,186.65 168.81,187.45V194.75C168.81,195.55 169.51,196.25 170.31,196.25H179.11C179.91,196.25 180.61,195.55 180.61,194.75V187.45C180.61,186.65 179.91,185.95 179.11,185.95ZM174.71,192.55C173.91,192.55 173.21,191.85 173.21,191.05C173.21,190.25 173.91,189.55 174.71,189.55C175.51,189.55 176.21,190.25 176.21,191.05C176.21,191.95 175.51,192.55 174.71,192.55ZM172.41,185.95H176.91V184.45C176.91,183.15 175.91,182.15 174.61,182.15C173.31,182.15 172.31,183.15 172.31,184.45V185.95H172.41Z"
-        android:fillColor="#000000"
-        android:fillType="evenOdd"/>
-    <path
-        android:pathData="M174.71,68.5L174.71,68.5A16,16 0,0 1,190.71 84.5L190.71,84.5A16,16 0,0 1,174.71 100.5L174.71,100.5A16,16 0,0 1,158.71 84.5L158.71,84.5A16,16 0,0 1,174.71 68.5z"
-        android:fillColor="#80868B"/>
-    <path
-        android:pathData="M173.91,77.35H175.51V85.25H173.91V77.35ZM178.21,80.25L179.31,79.15C180.81,80.45 181.81,82.35 181.81,84.55C181.81,88.45 178.61,91.65 174.71,91.65C170.81,91.65 167.61,88.45 167.61,84.55C167.61,82.35 168.61,80.45 170.11,79.15L171.21,80.25C170.01,81.25 169.21,82.85 169.21,84.55C169.21,87.65 171.71,90.15 174.81,90.15C177.91,90.15 180.41,87.65 180.41,84.55C180.31,82.75 179.41,81.25 178.21,80.25Z"
-        android:fillColor="#000000"
-        android:fillType="evenOdd"/>
-  </group>
   <path
-      android:pathData="M62.23,51.69L349.77,51.69A14.5,14.5 0,0 1,364.27 66.19L364.27,236.14A14.5,14.5 0,0 1,349.77 250.64L62.23,250.64A14.5,14.5 0,0 1,47.73 236.14L47.73,66.19A14.5,14.5 0,0 1,62.23 51.69z"
-      android:strokeWidth="3"
+      android:fillColor="#FF000000"
+      android:pathData="M199.88,53.03l-2.73,-2.73h-4.09c-0.36,0 -0.68,-0.14 -0.95,-0.41 -0.27,-0.27 -0.41,-0.59 -0.41,-0.95v-13.64c0,-0.36 0.14,-0.68 0.41,-0.95 0.27,-0.27 0.59,-0.41 0.95,-0.41h13.64c0.36,0 0.68,0.14 0.95,0.41 0.27,0.27 0.41,0.59 0.41,0.95v13.64c0,0.36 -0.14,0.68 -0.41,0.95 -0.27,0.27 -0.59,0.41 -0.95,0.41h-4.09l-2.73,2.73ZM199.9,46.37l1.27,-2.91 2.91,-1.27 -2.91,-1.27 -1.27,-2.91 -1.3,2.91 -2.89,1.27 2.89,1.27 1.3,2.91Z"/>
+  <path
+      android:fillColor="#FF000000"
+      android:pathData="M384.18,300H27.82c-15.29,0 -27.82,-12.83 -27.82,-28.48V28.48C0,12.83 12.53,0 27.82,0H384.29c15.18,0 27.71,12.83 27.71,28.48v243.15c0,15.54 -12.53,28.37 -27.82,28.37Z"/>
+  <path
+      android:pathData="M207.19,52.65h151.18c4.14,0 7.51,3.36 7.51,7.51V243.15c0,4.14 -3.36,7.51 -7.51,7.51H207.19V52.65h0Z"
+      android:fillColor="#5f6368"/>
+  <path
+      android:pathData="M368.24,143.47L368.24,60.55c0,-5.67 -4.59,-10.26 -10.26,-10.26L54.02,50.29c-5.67,0 -10.26,4.59 -10.26,10.26L43.76,242.76c0,5.67 4.59,10.26 10.26,10.26L357.98,253.02c5.67,0 10.26,-4.59 10.26,-10.26v-99.29ZM365.88,243.14c0,4.15 -3.75,7.52 -7.9,7.52L54.02,250.66c-4.15,0 -7.9,-3.37 -7.9,-7.52L46.12,60.55c0,-4.15 3.75,-7.9 7.9,-7.9L357.98,52.65c4.15,0 7.9,3.75 7.9,7.9L365.88,243.14Z"
+      android:fillColor="#80868b"/>
+  <path
+      android:pathData="M319.83,50.29c-0,-1.28 -1.04,-2.31 -2.31,-2.31h-23.11c-1.28,0 -2.31,1.03 -2.31,2.31h27.74Z"
+      android:fillColor="#80868b"/>
+  <path
+      android:pathData="M344.42,50.29c-0,-1.28 -1.03,-2.31 -2.31,-2.31h-9.25c-1.28,0 -2.31,1.03 -2.31,2.31h13.87Z"
+      android:fillColor="#80868b"/>
+  <path
+      android:pathData="M86.06,240.43l0.7,-0.7 -2.7,-2.7h5.9v-1h-5.9l2.7,-2.7 -0.7,-0.7 -3.9,3.9 3.9,3.9Z"
+      android:fillColor="#5f6368"
+      android:fillType="evenOdd"/>
+  <path
+      android:pathData="M166.93,232.89l-0.7,0.7 2.7,2.7h-5.9v1h5.9l-2.7,2.7 0.7,0.7 3.9,-3.9 -3.9,-3.9Z"
+      android:fillColor="#7f868c"
+      android:fillType="evenOdd"/>
+  <path
+      android:strokeWidth="1"
+      android:pathData="M46.12,222.93L207.19,222.93"
       android:fillColor="#00000000"
-      android:strokeColor="#7F868C"/>
+      android:strokeColor="#5f6368"/>
   <path
-      android:pathData="M311.45,50.35C311.45,48.98 312.56,47.87 313.92,47.87L322.84,47.87C324.2,47.87 325.32,48.98 325.32,50.35L319.37,51.34L311.45,50.35Z"
-      android:fillColor="#7F868C"/>
-  <path
-      android:pathData="M263.59,50.35C263.59,48.98 264.7,47.87 266.06,47.87L287.85,47.87C289.22,47.87 290.33,48.98 290.33,50.35L277.45,51.34L263.59,50.35Z"
-      android:fillColor="#7F868C"/>
-  <group>
-    <clip-path
-        android:pathData="M62,50.32L349.54,50.32A16,16 0,0 1,365.54 66.32L365.54,236.27A16,16 0,0 1,349.54 252.27L62,252.27A16,16 0,0 1,46 236.27L46,66.32A16,16 0,0 1,62 50.32z"/>
-    <path
-        android:pathData="M62,50.32L349.54,50.32A16,16 0,0 1,365.54 66.32L365.54,236.27A16,16 0,0 1,349.54 252.27L62,252.27A16,16 0,0 1,46 236.27L46,66.32A16,16 0,0 1,62 50.32z"
-        android:fillColor="#3B4043"/>
-    <path
-        android:pathData="M46,52.27h160v200h-160z"
-        android:fillColor="#000000"/>
-    <path
-        android:pathData="M85.98,240.17L86.68,239.47L83.98,236.77H89.88V235.77H83.98L86.68,233.07L85.98,232.37L82.08,236.27L85.98,240.17Z"
-        android:fillColor="#3B4043"
-        android:fillType="evenOdd"/>
-    <path
-        android:pathData="M125.48,220.27V252.27H127.48V220.27H125.48Z"
-        android:fillColor="#3B4043"/>
-    <path
-        android:pathData="M165.98,232.37L165.28,233.07L167.98,235.77H162.08V236.77H167.98L165.28,239.47L165.98,240.17L169.88,236.27L165.98,232.37Z"
-        android:fillColor="#7F868C"
-        android:fillType="evenOdd"/>
-    <path
-        android:pathData="M46.48,221.27H206.48V219.27H46.48V221.27Z"
-        android:fillColor="#3B4043"/>
-    <path
-        android:pathData="M78.48,120.64L78.48,120.64A16,16 0,0 1,94.48 136.64L94.48,136.64A16,16 0,0 1,78.48 152.64L78.48,152.64A16,16 0,0 1,62.48 136.64L62.48,136.64A16,16 0,0 1,78.48 120.64z"
-        android:fillColor="#7AE2D4"/>
-    <group>
-      <clip-path
-          android:pathData="M73.13,139.09V134.29H76.33L80.33,130.29V142.99L76.33,138.99H73.13V139.09ZM81.83,133.49C83.03,134.09 83.83,135.29 83.83,136.69C83.83,138.09 83.03,139.29 81.83,139.89V133.49Z"
-          android:fillType="evenOdd"/>
-      <path
-          android:pathData="M88.13,127.19H69.13V146.19H88.13V127.19Z"
-          android:fillColor="#000000"/>
-    </group>
-    <path
-        android:pathData="M78.48,172.64L78.48,172.64A16,16 0,0 1,94.48 188.64L94.48,188.64A16,16 0,0 1,78.48 204.64L78.48,204.64A16,16 0,0 1,62.48 188.64L62.48,188.64A16,16 0,0 1,78.48 172.64z"
-        android:fillColor="#FDD663"/>
-    <path
-        android:pathData="M87.43,188.64L84.83,191.24V194.94H81.13L78.53,197.54L75.93,194.94H72.13V191.24L69.53,188.64L72.13,186.04V182.34H75.83L78.43,179.74L81.03,182.34H84.73V186.04L87.43,188.64ZM73.73,188.64C73.73,191.24 75.83,193.44 78.53,193.44C81.13,193.44 83.33,191.34 83.33,188.64C83.33,185.94 81.23,183.84 78.53,183.84C75.83,183.84 73.73,185.94 73.73,188.64Z"
-        android:fillColor="#000000"
-        android:fillType="evenOdd"/>
-    <path
-        android:pathData="M80.43,188.04H76.43V189.54H80.43V188.04Z"
-        android:fillColor="#000000"/>
-    <path
-        android:pathData="M126.48,120.64L126.48,120.64A16,16 0,0 1,142.48 136.64L142.48,136.64A16,16 0,0 1,126.48 152.64L126.48,152.64A16,16 0,0 1,110.48 136.64L110.48,136.64A16,16 0,0 1,126.48 120.64z"
-        android:fillColor="#7AE2D4"/>
-    <path
-        android:pathData="M128.08,131.24V129.64C131.28,130.34 133.68,133.24 133.68,136.64C133.68,140.04 131.28,142.84 128.08,143.64V142.04C130.38,141.34 132.08,139.24 132.08,136.74C132.08,134.24 130.38,131.94 128.08,131.24ZM130.08,136.54C130.08,135.14 129.28,133.94 128.08,133.34V139.74C129.18,139.14 130.08,137.94 130.08,136.54Z"
-        android:fillColor="#000000"
-        android:fillType="evenOdd"/>
-    <path
-        android:pathData="M126.49,142.93V130.27L122.49,134.21H119.27V138.92H122.49L126.49,142.93Z"
-        android:fillColor="#000000"/>
-    <group>
-      <clip-path
-          android:pathData="M126.49,142.93V130.27L122.49,134.21H119.27V138.92H122.49L126.49,142.93Z"/>
-      <clip-path
-          android:pathData="M128.08,131.24V129.64C131.28,130.34 133.68,133.24 133.68,136.64C133.68,140.04 131.28,142.84 128.08,143.64V142.04C130.38,141.34 132.08,139.24 132.08,136.74C132.08,134.24 130.38,131.94 128.08,131.24ZM130.08,136.54C130.08,135.14 129.28,133.94 128.08,133.34V139.74C129.18,139.14 130.08,137.94 130.08,136.54Z"
-          android:fillType="evenOdd"/>
-      <path
-          android:pathData="M128.08,131.24H78.08V168.28L113.52,179.07L128.08,131.24ZM128.08,129.64L138.76,80.79L78.08,67.52V129.64H128.08ZM128.08,143.64H78.08V207.68L140.21,192.14L128.08,143.64ZM128.08,142.04L113.52,94.2L78.08,104.99V142.04H128.08ZM128.08,133.34L150.44,88.62L78.08,52.44V133.34H128.08ZM128.08,139.74H78.08V223.96L152.02,183.63L128.08,139.74ZM126.49,130.27H176.49V10.92L91.43,94.63L126.49,130.27ZM126.49,142.93L91.14,178.28L176.49,263.64V142.93H126.49ZM122.49,138.92L157.84,103.56L143.2,88.92H122.49V138.92ZM119.27,138.92H69.27V188.92H119.27V138.92ZM119.27,134.21V84.22H69.27V134.21H119.27ZM122.49,134.21V184.21H142.96L157.55,169.85L122.49,134.21ZM178.08,131.24V129.64H78.08V131.24H178.08ZM117.39,178.48C97.8,174.2 83.68,156.9 83.68,136.64H183.68C183.68,109.57 164.76,86.48 138.76,80.79L117.39,178.48ZM83.68,136.64C83.68,115.28 98.7,99.44 115.95,95.13L140.21,192.14C163.86,186.23 183.68,164.79 183.68,136.64H83.68ZM178.08,143.64V142.04H78.08V143.64H178.08ZM142.64,189.87C164.49,183.22 182.08,162.77 182.08,136.74H82.08C82.08,115.7 96.27,99.45 113.52,94.2L142.64,189.87ZM182.08,136.74C182.08,112.78 166.51,90.67 142.64,83.4L113.52,179.07C94.25,173.2 82.08,155.69 82.08,136.74H182.08ZM180.08,136.54C180.08,115.18 167.65,97.22 150.44,88.62L105.72,178.06C90.91,170.65 80.08,155.1 80.08,136.54H180.08ZM78.08,133.34V139.74H178.08V133.34H78.08ZM152.02,183.63C165.86,176.08 180.08,159.73 180.08,136.54H80.08C80.08,116.14 92.5,102.19 104.14,95.84L152.02,183.63ZM76.49,130.27V142.93H176.49V130.27H76.49ZM161.85,107.57L157.84,103.56L87.13,174.27L91.14,178.28L161.85,107.57ZM122.49,88.92H119.27V188.92H122.49V88.92ZM169.27,138.92V134.21H69.27V138.92H169.27ZM119.27,184.21H122.49V84.22H119.27V184.21ZM157.55,169.85L161.56,165.91L91.43,94.63L87.42,98.58L157.55,169.85Z"
-          android:fillColor="#000000"/>
-    </group>
-    <path
-        android:pathData="M78.48,68.64L78.48,68.64A16,16 0,0 1,94.48 84.64L94.48,84.64A16,16 0,0 1,78.48 100.64L78.48,100.64A16,16 0,0 1,62.48 84.64L62.48,84.64A16,16 0,0 1,78.48 68.64z"
-        android:fillColor="#3B4043"/>
-    <path
-        android:pathData="M86.03,82.29C86.69,82.29 87.23,81.75 87.23,81.09C87.23,80.42 86.69,79.89 86.03,79.89C85.37,79.89 84.83,80.42 84.83,81.09C84.83,81.75 85.37,82.29 86.03,82.29Z"
-        android:fillColor="#34A853"/>
-    <path
-        android:pathData="M82.43,86.29C83.76,86.29 84.83,85.21 84.83,83.89C84.83,82.56 83.76,81.49 82.43,81.49C81.11,81.49 80.03,82.56 80.03,83.89C80.03,85.21 81.11,86.29 82.43,86.29Z"
-        android:fillColor="#EA4335"/>
-    <path
-        android:pathData="M82.43,92.59C83.98,92.59 85.23,91.33 85.23,89.79C85.23,88.24 83.98,86.99 82.43,86.99C80.88,86.99 79.63,88.24 79.63,89.79C79.63,91.33 80.88,92.59 82.43,92.59Z"
-        android:fillColor="#FBBC04"/>
-    <path
-        android:pathData="M74.53,86.29C77.18,86.29 79.33,84.14 79.33,81.49C79.33,78.84 77.18,76.69 74.53,76.69C71.88,76.69 69.73,78.84 69.73,81.49C69.73,84.14 71.88,86.29 74.53,86.29Z"
-        android:fillColor="#4285F4"/>
-    <path
-        android:pathData="M174.48,120.64L174.48,120.64A16,16 0,0 1,190.48 136.64L190.48,136.64A16,16 0,0 1,174.48 152.64L174.48,152.64A16,16 0,0 1,158.48 136.64L158.48,136.64A16,16 0,0 1,174.48 120.64z"
-        android:fillColor="#EFA5DE"/>
-    <path
-        android:pathData="M178.3,130.64H170.8V142.34H178.3V130.64Z"
-        android:fillColor="#000000"/>
-    <path
-        android:pathData="M170,132.94H167.3V140.14H170V132.94Z"
-        android:fillColor="#000000"/>
-    <path
-        android:pathData="M182,132.94H179.3V140.14H182V132.94Z"
-        android:fillColor="#000000"/>
-    <path
-        android:pathData="M126.48,172.64L126.48,172.64A16,16 0,0 1,142.48 188.64L142.48,188.64A16,16 0,0 1,126.48 204.64L126.48,204.64A16,16 0,0 1,110.48 188.64L110.48,188.64A16,16 0,0 1,126.48 172.64z"
-        android:fillColor="#FDD663"/>
-    <path
-        android:pathData="M135.48,188.64L132.88,191.24V194.94H129.08L126.48,197.54L123.88,194.94H120.08V191.24L117.48,188.64L120.08,186.04V182.34H123.78L126.38,179.74L128.98,182.34H132.68V186.04L135.48,188.64ZM121.68,188.64C121.68,191.24 123.78,193.44 126.48,193.44C129.08,193.44 131.28,191.34 131.28,188.64C131.28,185.94 129.18,183.84 126.48,183.84C123.88,183.84 121.68,185.94 121.68,188.64Z"
-        android:fillColor="#000000"
-        android:fillType="evenOdd"/>
-    <path
-        android:pathData="M126.98,188.04V186.54H125.98V188.04H124.48V189.04H125.98V190.54H126.98V189.04H128.48V188.04H126.98Z"
-        android:fillColor="#000000"
-        android:fillType="evenOdd"/>
-    <path
-        android:pathData="M126.48,68.64L126.48,68.64A16,16 0,0 1,142.48 84.64L142.48,84.64A16,16 0,0 1,126.48 100.64L126.48,100.64A16,16 0,0 1,110.48 84.64L110.48,84.64A16,16 0,0 1,126.48 68.64z"
-        android:fillColor="#D9AFFD"/>
-    <path
-        android:pathData="M128.13,78.64C128.13,79.44 127.43,80.14 126.53,80.14C125.63,80.14 124.93,79.44 124.93,78.64C124.93,77.84 125.63,77.14 126.53,77.14C127.43,77.04 128.13,77.74 128.13,78.64ZM126.53,80.84C128.73,80.84 131.23,80.64 133.23,80.04L133.63,81.54C132.13,81.94 130.43,82.14 128.83,82.34V92.14H127.23V87.64H125.63V92.14H124.13V82.34C122.53,82.24 120.83,81.94 119.33,81.54L119.73,80.04C121.83,80.64 124.23,80.84 126.53,80.84Z"
-        android:fillColor="#000000"
-        android:fillType="evenOdd"/>
-    <path
-        android:pathData="M174.48,172.64L174.48,172.64A16,16 0,0 1,190.48 188.64L190.48,188.64A16,16 0,0 1,174.48 204.64L174.48,204.64A16,16 0,0 1,158.48 188.64L158.48,188.64A16,16 0,0 1,174.48 172.64z"
-        android:fillColor="#84E39F"/>
-    <path
-        android:pathData="M178.88,186.09H178.18V184.59C178.18,182.59 176.58,180.89 174.48,180.89C172.38,180.89 170.78,182.49 170.78,184.59V186.09H170.08C169.28,186.09 168.58,186.79 168.58,187.59V194.89C168.58,195.69 169.28,196.39 170.08,196.39H178.88C179.68,196.39 180.38,195.69 180.38,194.89V187.59C180.38,186.79 179.68,186.09 178.88,186.09ZM174.48,192.69C173.68,192.69 172.98,191.99 172.98,191.19C172.98,190.39 173.68,189.69 174.48,189.69C175.28,189.69 175.98,190.39 175.98,191.19C175.98,192.09 175.28,192.69 174.48,192.69ZM172.18,186.09H176.68V184.59C176.68,183.29 175.68,182.29 174.38,182.29C173.08,182.29 172.08,183.29 172.08,184.59V186.09H172.18Z"
-        android:fillColor="#000000"
-        android:fillType="evenOdd"/>
-    <path
-        android:pathData="M174.48,68.64L174.48,68.64A16,16 0,0 1,190.48 84.64L190.48,84.64A16,16 0,0 1,174.48 100.64L174.48,100.64A16,16 0,0 1,158.48 84.64L158.48,84.64A16,16 0,0 1,174.48 68.64z"
-        android:fillColor="#DBDCE0"/>
-    <path
-        android:pathData="M173.68,77.49H175.28V85.39H173.68V77.49ZM177.98,80.39L179.08,79.29C180.58,80.59 181.58,82.49 181.58,84.69C181.58,88.59 178.38,91.79 174.48,91.79C170.58,91.79 167.38,88.59 167.38,84.69C167.38,82.49 168.38,80.59 169.88,79.29L170.98,80.39C169.78,81.39 168.98,82.99 168.98,84.69C168.98,87.79 171.48,90.29 174.58,90.29C177.68,90.29 180.18,87.79 180.18,84.69C180.08,82.89 179.18,81.39 177.98,80.39Z"
-        android:fillColor="#000000"
-        android:fillType="evenOdd"/>
-  </group>
-  <path
-      android:pathData="M62,51.82L349.54,51.82A14.5,14.5 0,0 1,364.04 66.32L364.04,236.27A14.5,14.5 0,0 1,349.54 250.77L62,250.77A14.5,14.5 0,0 1,47.5 236.27L47.5,66.32A14.5,14.5 0,0 1,62 51.82z"
-      android:strokeWidth="3"
+      android:strokeWidth="1"
+      android:pathData="M126.66,222.93L126.66,250.66"
       android:fillColor="#00000000"
-      android:strokeColor="#7F868C"/>
+      android:strokeColor="#5f6368"/>
   <path
-      android:pathData="M311.22,50.49C311.22,49.11 312.33,48 313.7,48L322.61,48C323.98,48 325.08,49.11 325.08,50.49L319.14,51.48L311.22,50.49Z"
-      android:fillColor="#7F868C"/>
+      android:pathData="M78.55,70.3h0c8.84,0 16,7.16 16,16h0c0,8.84 -7.16,16 -16,16h0c-8.84,0 -16,-7.16 -16,-16h0c0,-8.84 7.16,-16 16,-16Z"
+      android:fillColor="#2197f3"/>
   <path
-      android:pathData="M263.36,50.49C263.36,49.11 264.47,48 265.83,48L287.62,48C288.99,48 290.1,49.11 290.1,50.49L277.22,51.48L263.36,50.49Z"
-      android:fillColor="#7F868C"/>
+      android:pathData="M78.55,174.3h0c8.83,0 16,7.16 16,16h0c0,8.83 -7.16,16 -16,16h0c-8.83,0 -16,-7.16 -16,-16h0c0,-8.83 7.16,-16 16,-16Z"
+      android:fillColor="#fdd663"/>
+  <path
+      android:fillColor="#FF000000"
+      android:pathData="M78.57,199.86l-2.85,-2.77h-3.93v-3.93l-2.83,-2.83 2.83,-2.83v-3.93h3.93l2.85,-2.83 2.81,2.83h3.93v3.93l2.83,2.83 -2.83,2.83v3.93h-3.93l-2.81,2.77ZM78.57,194.43c-1.14,0 -2.11,-0.4 -2.92,-1.2 -0.8,-0.8 -1.2,-1.78 -1.2,-2.92s0.4,-2.11 1.2,-2.92c0.8,-0.8 1.78,-1.2 2.92,-1.2 1.14,0 2.11,0.4 2.92,1.2 0.8,0.8 1.2,1.78 1.2,2.92s-0.4,2.11 -1.2,2.92c-0.8,0.8 -1.78,1.2 -2.92,1.2ZM78.57,193.16c0.8,0 1.48,-0.27 2.03,-0.82 0.55,-0.55 0.82,-1.23 0.82,-2.03 -0,-0.8 -0.27,-1.48 -0.82,-2.03 -0.55,-0.55 -1.23,-0.82 -2.03,-0.82 -0.8,0 -1.48,0.27 -2.03,0.82 -0.55,0.55 -0.82,1.23 -0.82,2.03s0.27,1.48 0.82,2.03c0.55,0.55 1.23,0.82 2.03,0.82ZM78.57,198.09l2.28,-2.26h3.19v-3.19l2.3,-2.3 -2.3,-2.3v-3.19h-3.19l-2.28,-2.3 -2.32,2.3h-3.19v3.19l-2.3,2.3 2.3,2.3v3.19h3.17l2.35,2.26Z"/>
+  <path
+      android:pathData="M126.55,174.31h0c8.83,0 16,7.16 16,16h0c0,8.83 -7.16,16 -16,16h0c-8.83,0 -16,-7.16 -16,-16h0c0,-8.83 7.16,-16 16,-16Z"
+      android:fillColor="#fdd663"/>
+  <path
+      android:fillColor="#FF000000"
+      android:pathData="M126.58,199.87l-2.85,-2.77h-3.93v-3.93l-2.83,-2.83 2.83,-2.83v-3.93h3.93l2.85,-2.83 2.81,2.83h3.93v3.93l2.83,2.83 -2.83,2.83v3.93h-3.93l-2.81,2.77ZM126.58,194.44c1.14,0 2.11,-0.4 2.92,-1.2 0.8,-0.8 1.2,-1.78 1.2,-2.92s-0.4,-2.11 -1.2,-2.92c-0.8,-0.8 -1.78,-1.2 -2.92,-1.2 -1.14,0 -2.11,0.4 -2.92,1.2 -0.8,0.8 -1.2,1.78 -1.2,2.92s0.4,2.11 1.2,2.92c0.8,0.8 1.78,1.2 2.92,1.2ZM126.58,193.17c0.8,0 1.48,-0.27 2.03,-0.82 0.55,-0.55 0.82,-1.23 0.82,-2.03 -0,-0.8 -0.27,-1.48 -0.82,-2.03 -0.55,-0.55 -1.23,-0.82 -2.03,-0.82 -0.8,0 -1.48,0.27 -2.03,0.82 -0.55,0.55 -0.82,1.23 -0.82,2.03s0.27,1.48 0.82,2.03c0.55,0.55 1.23,0.82 2.03,0.82ZM126.58,198.09l2.28,-2.26h3.19v-3.19l2.3,-2.3 -2.3,-2.3v-3.19h-3.19l-2.28,-2.3 -2.32,2.3h-3.19v3.19l-2.3,2.3 2.3,2.3v3.19h3.17l2.35,2.26ZM126.58,193.17c0.8,0 1.48,-0.27 2.03,-0.82 0.55,-0.55 0.82,-1.23 0.82,-2.03 -0,-0.8 -0.27,-1.48 -0.82,-2.03 -0.55,-0.55 -1.23,-0.82 -2.03,-0.82 -0.8,0 -1.48,0.27 -2.03,0.82 -0.55,0.55 -0.82,1.23 -0.82,2.03s0.27,1.48 0.82,2.03c0.55,0.55 1.23,0.82 2.03,0.82Z"/>
+  <path
+      android:pathData="M174.56,174.3h0c8.83,0 16,7.16 16,16h0c0,8.83 -7.16,16 -16,16h0c-8.83,0 -16,-7.16 -16,-16h0c0,-8.83 7.16,-16 16,-16Z"
+      android:fillColor="#84e39f"/>
+  <path
+      android:fillColor="#FF000000"
+      android:pathData="M168.94,198.37c-0.36,0 -0.67,-0.13 -0.92,-0.38s-0.38,-0.56 -0.38,-0.92v-9.38c0,-0.36 0.13,-0.67 0.38,-0.92s0.56,-0.38 0.92,-0.38h1.51v-2.07c0,-1.14 0.4,-2.11 1.2,-2.91 0.8,-0.8 1.77,-1.2 2.91,-1.2s2.11,0.4 2.91,1.2c0.8,0.8 1.2,1.77 1.2,2.91v2.07h1.51c0.36,0 0.67,0.13 0.92,0.38s0.38,0.56 0.38,0.92v9.38c0,0.36 -0.13,0.67 -0.38,0.92s-0.56,0.38 -0.92,0.38h-11.24ZM168.94,197.08h11.24v-9.38h-11.24v9.38ZM174.56,194.05c0.46,0 0.85,-0.16 1.18,-0.48s0.49,-0.7 0.49,-1.15c0,-0.43 -0.16,-0.82 -0.49,-1.18s-0.72,-0.53 -1.18,-0.53 -0.85,0.18 -1.18,0.53c-0.32,0.35 -0.49,0.75 -0.49,1.18 0,0.45 0.16,0.83 0.49,1.15s0.72,0.48 1.18,0.48ZM171.75,186.4h5.62v-2.07c0,-0.78 -0.27,-1.44 -0.82,-1.99 -0.55,-0.55 -1.21,-0.82 -1.99,-0.82s-1.44,0.27 -1.99,0.82 -0.82,1.21 -0.82,1.99v2.07ZM168.94,197.08v0Z"/>
+  <path
+      android:pathData="M174.56,70.24h0c8.87,0 16.06,7.19 16.06,16.06h0c0,8.87 -7.19,16.06 -16.06,16.06h0c-8.87,0 -16.06,-7.19 -16.06,-16.06h0c0,-8.87 7.19,-16.06 16.06,-16.06Z"
+      android:fillColor="#dbdce0"/>
+  <path
+      android:fillColor="#FF000000"
+      android:pathData="M173.75,79.12h1.61v7.93h-1.61v-7.93ZM178.07,82.03l1.1,-1.1c1.51,1.31 2.51,3.21 2.51,5.42 0,3.92 -3.21,7.13 -7.13,7.13s-7.13,-3.21 -7.13,-7.13c0,-2.21 1,-4.12 2.51,-5.42l1.1,1.1c-1.2,1 -2.01,2.61 -2.01,4.32 0,3.11 2.51,5.62 5.62,5.62 3.11,0 5.62,-2.51 5.62,-5.62 -0.1,-1.81 -1,-3.31 -2.21,-4.32Z"
+      android:fillType="evenOdd"/>
+  <path
+      android:pathData="M126.55,70.24h0c8.87,0 16.06,7.19 16.06,16.06h0c0,8.87 -7.19,16.06 -16.06,16.06h0c-8.87,0 -16.06,-7.19 -16.06,-16.06h0c0,-8.87 7.19,-16.06 16.06,-16.06Z"
+      android:fillColor="#d9affd"/>
+  <path
+      android:fillColor="#FF000000"
+      android:pathData="M126.55,79.66c-0.4,0 -0.74,-0.14 -1.02,-0.43 -0.29,-0.29 -0.43,-0.63 -0.43,-1.02s0.14,-0.74 0.43,-1.02c0.29,-0.29 0.63,-0.43 1.02,-0.43s0.74,0.14 1.02,0.43c0.29,0.29 0.43,0.63 0.43,1.02s-0.14,0.74 -0.43,1.02 -0.63,0.43 -1.02,0.43ZM124.49,91.87v-9.83c-0.84,-0.07 -1.68,-0.16 -2.53,-0.29 -0.85,-0.13 -1.64,-0.28 -2.37,-0.47l0.3,-1.19c1.06,0.27 2.15,0.46 3.27,0.58 1.12,0.12 2.25,0.18 3.39,0.18s2.27,-0.06 3.39,-0.18c1.12,-0.12 2.21,-0.31 3.27,-0.58l0.3,1.19c-0.73,0.19 -1.52,0.34 -2.37,0.47 -0.85,0.13 -1.69,0.22 -2.53,0.29v9.83h-1.19v-4.85h-1.75v4.85h-1.19ZM123.31,95.85c-0.23,0 -0.41,-0.07 -0.55,-0.21 -0.14,-0.14 -0.21,-0.32 -0.21,-0.55 0,-0.23 0.07,-0.41 0.21,-0.55 0.14,-0.14 0.32,-0.21 0.55,-0.21s0.41,0.07 0.55,0.21c0.14,0.14 0.21,0.32 0.21,0.55 0,0.23 -0.07,0.41 -0.21,0.55 -0.14,0.14 -0.32,0.21 -0.55,0.21ZM126.57,95.85c-0.23,0 -0.41,-0.07 -0.55,-0.21 -0.14,-0.14 -0.21,-0.32 -0.21,-0.55 0,-0.23 0.07,-0.41 0.21,-0.55 0.14,-0.14 0.32,-0.21 0.55,-0.21s0.41,0.07 0.55,0.21c0.14,0.14 0.21,0.32 0.21,0.55 0,0.23 -0.07,0.41 -0.21,0.55 -0.14,0.14 -0.32,0.21 -0.55,0.21ZM129.84,95.85c-0.23,0 -0.41,-0.07 -0.55,-0.21 -0.14,-0.14 -0.21,-0.32 -0.21,-0.55 0,-0.23 0.07,-0.41 0.21,-0.55 0.14,-0.14 0.32,-0.21 0.55,-0.21s0.41,0.07 0.55,0.21c0.14,0.14 0.21,0.32 0.21,0.55 0,0.23 -0.07,0.41 -0.21,0.55 -0.14,0.14 -0.32,0.21 -0.55,0.21Z"/>
+  <path
+      android:pathData="M126.55,122.3h0c8.83,0 15.99,7.16 15.99,15.99h0c0,8.83 -7.16,15.99 -15.99,15.99h0c-8.83,0 -15.99,-7.16 -15.99,-15.99h0c0,-8.83 7.16,-15.99 15.99,-15.99Z"
+      android:fillColor="#7ae2d4"/>
+  <path
+      android:fillColor="#FF000000"
+      android:pathData="M128.49,146.78v-1.5c1.57,-0.45 2.84,-1.32 3.84,-2.6 0.99,-1.28 1.49,-2.74 1.49,-4.37 0,-1.63 -0.49,-3.09 -1.48,-4.38s-2.27,-2.15 -3.85,-2.59v-1.5c2,0.45 3.63,1.46 4.89,3.04 1.26,1.57 1.89,3.39 1.89,5.43s-0.63,3.86 -1.89,5.43c-1.26,1.57 -2.89,2.59 -4.89,3.04ZM117.84,141.24v-5.81h3.87l4.84,-4.84v15.49l-4.84,-4.84h-3.87ZM128.01,142.4v-8.16c0.89,0.27 1.59,0.79 2.12,1.55 0.52,0.76 0.79,1.61 0.79,2.54 0,0.92 -0.27,1.76 -0.8,2.52s-1.23,1.28 -2.11,1.55ZM125.1,134.26l-2.74,2.61h-3.07v2.91h3.07l2.74,2.64v-8.16Z"/>
+  <path
+      android:pathData="M174.56,122.33h0c8.83,0 15.99,7.16 15.99,15.99h0c0,8.83 -7.16,15.99 -15.99,15.99h0c-8.83,0 -15.99,-7.16 -15.99,-15.99h0c0,-8.83 7.16,-15.99 15.99,-15.99Z"
+      android:fillColor="#efa5de"/>
+  <path
+      android:fillColor="#FF000000"
+      android:pathData="M179.02,131.89h-8.93v12.86h8.93v-12.86Z"/>
+  <path
+      android:fillColor="#FF000000"
+      android:pathData="M168.9,134.41h-3.22v7.91h3.22v-7.91Z"/>
+  <path
+      android:fillColor="#FF000000"
+      android:pathData="M183.43,134.41h-3.22v7.91h3.22v-7.91Z"/>
+  <path
+      android:pathData="M171.75,133.42h5.61v9.78h-5.61z"
+      android:fillColor="#efa5de"/>
+  <path
+      android:pathData="M78.55,122.3h0c8.83,0 15.99,7.16 15.99,15.99h0c0,8.83 -7.16,15.99 -15.99,15.99h0c-8.83,0 -15.99,-7.16 -15.99,-15.99h0c0,-8.83 7.16,-15.99 15.99,-15.99Z"
+      android:fillColor="#7ae2d4"/>
+  <path
+      android:fillColor="#FF000000"
+      android:pathData="M72.01,141.21v-5.81h3.88l4.84,-4.84v15.5l-4.84,-4.84h-3.88ZM82.18,142.38v-8.16c0.87,0.27 1.57,0.79 2.11,1.55 0.53,0.76 0.8,1.61 0.8,2.54 0,0.95 -0.27,1.8 -0.8,2.54 -0.53,0.74 -1.24,1.25 -2.11,1.53ZM79.28,134.24l-2.74,2.62h-3.08v2.91h3.08l2.74,2.64v-8.16Z"/>
+  <path
+      android:fillColor="#FF000000"
+      android:pathData="M78.73,96.45l-2.73,-2.73h-4.09c-0.36,0 -0.68,-0.14 -0.95,-0.41 -0.27,-0.27 -0.41,-0.59 -0.41,-0.95v-13.64c0,-0.36 0.14,-0.68 0.41,-0.95 0.27,-0.27 0.59,-0.41 0.95,-0.41h13.64c0.36,0 0.68,0.14 0.95,0.41 0.27,0.27 0.41,0.59 0.41,0.95v13.64c0,0.36 -0.14,0.68 -0.41,0.95 -0.27,0.27 -0.59,0.41 -0.95,0.41h-4.09l-2.73,2.73ZM78.75,89.79l1.27,-2.91 2.91,-1.27 -2.91,-1.27 -1.27,-2.91 -1.3,2.91 -2.89,1.27 2.89,1.27 1.3,2.91Z"/>
 </vector>
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/res/drawable-sw600dp/a11ymenu_intro.xml b/packages/SystemUI/accessibility/accessibilitymenu/res/drawable-sw600dp/a11ymenu_intro.xml
index f0fbc48..aba9581 100644
--- a/packages/SystemUI/accessibility/accessibilitymenu/res/drawable-sw600dp/a11ymenu_intro.xml
+++ b/packages/SystemUI/accessibility/accessibilitymenu/res/drawable-sw600dp/a11ymenu_intro.xml
@@ -3,139 +3,101 @@
     android:height="300dp"
     android:viewportWidth="412"
     android:viewportHeight="300">
-  <group>
-    <clip-path
-        android:pathData="M62.23,50.19L349.77,50.19A16,16 0,0 1,365.77 66.19L365.77,236.14A16,16 0,0 1,349.77 252.14L62.23,252.14A16,16 0,0 1,46.23 236.14L46.23,66.19A16,16 0,0 1,62.23 50.19z"/>
-    <path
-        android:pathData="M62.23,50.19L349.77,50.19A16,16 0,0 1,365.77 66.19L365.77,236.14A16,16 0,0 1,349.77 252.14L62.23,252.14A16,16 0,0 1,46.23 236.14L46.23,66.19A16,16 0,0 1,62.23 50.19z"
-        android:fillColor="#E8EAED"/>
-    <path
-        android:pathData="M46.23,52.14h160v200h-160z"
-        android:fillColor="#ffffff"/>
-    <path
-        android:pathData="M86.21,240.04L86.91,239.34L84.21,236.64H90.11V235.64H84.21L86.91,232.94L86.21,232.24L82.31,236.14L86.21,240.04Z"
-        android:fillColor="#E8EAED"
-        android:fillType="evenOdd"/>
-    <path
-        android:pathData="M125.71,220.14V252.14H127.71V220.14H125.71Z"
-        android:fillColor="#E8EAED"/>
-    <path
-        android:pathData="M166.21,232.24L165.51,232.94L168.21,235.64H162.31V236.64H168.21L165.51,239.34L166.21,240.04L170.11,236.14L166.21,232.24Z"
-        android:fillColor="#7F868C"
-        android:fillType="evenOdd"/>
-    <path
-        android:pathData="M46.71,221.14H206.71V219.14H46.71V221.14Z"
-        android:fillColor="#E8EAED"/>
-    <path
-        android:pathData="M78.71,120.5L78.71,120.5A16,16 0,0 1,94.71 136.5L94.71,136.5A16,16 0,0 1,78.71 152.5L78.71,152.5A16,16 0,0 1,62.71 136.5L62.71,136.5A16,16 0,0 1,78.71 120.5z"
-        android:fillColor="#327969"/>
-    <group>
-      <clip-path
-          android:pathData="M73.36,138.95V134.15H76.56L80.56,130.15V142.85L76.56,138.85H73.36V138.95ZM82.06,133.35C83.26,133.95 84.06,135.15 84.06,136.55C84.06,137.95 83.26,139.15 82.06,139.75V133.35Z"
-          android:fillType="evenOdd"/>
-      <path
-          android:pathData="M88.36,127.05H69.36V146.05H88.36V127.05Z"
-          android:fillColor="#ffffff"/>
-    </group>
-    <path
-        android:pathData="M78.71,172.5L78.71,172.5A16,16 0,0 1,94.71 188.5L94.71,188.5A16,16 0,0 1,78.71 204.5L78.71,204.5A16,16 0,0 1,62.71 188.5L62.71,188.5A16,16 0,0 1,78.71 172.5z"
-        android:fillColor="#DE9834"/>
-    <path
-        android:pathData="M87.66,188.5L85.06,191.1V194.8H81.36L78.76,197.4L76.16,194.8H72.36V191.1L69.76,188.5L72.36,185.9V182.2H76.06L78.66,179.6L81.26,182.2H84.96V185.9L87.66,188.5ZM73.96,188.5C73.96,191.1 76.06,193.3 78.76,193.3C81.36,193.3 83.56,191.2 83.56,188.5C83.56,185.8 81.46,183.7 78.76,183.7C76.06,183.7 73.96,185.8 73.96,188.5Z"
-        android:fillColor="#ffffff"
-        android:fillType="evenOdd"/>
-    <path
-        android:pathData="M80.66,187.9H76.66V189.4H80.66V187.9Z"
-        android:fillColor="#ffffff"/>
-    <path
-        android:pathData="M126.71,120.5L126.71,120.5A16,16 0,0 1,142.71 136.5L142.71,136.5A16,16 0,0 1,126.71 152.5L126.71,152.5A16,16 0,0 1,110.71 136.5L110.71,136.5A16,16 0,0 1,126.71 120.5z"
-        android:fillColor="#327969"/>
-    <path
-        android:pathData="M128.31,131.1V129.5C131.51,130.2 133.91,133.1 133.91,136.5C133.91,139.9 131.51,142.7 128.31,143.5V141.9C130.61,141.2 132.31,139.1 132.31,136.6C132.31,134.1 130.61,131.8 128.31,131.1ZM130.31,136.4C130.31,135 129.51,133.8 128.31,133.2V139.6C129.41,139 130.31,137.8 130.31,136.4Z"
-        android:fillColor="#ffffff"
-        android:fillType="evenOdd"/>
-    <path
-        android:pathData="M126.72,142.79V130.13L122.71,134.08H119.5V138.78H122.71L126.72,142.79Z"
-        android:fillColor="#ffffff"/>
-    <group>
-      <clip-path
-          android:pathData="M126.72,142.79V130.13L122.71,134.08H119.5V138.78H122.71L126.72,142.79Z"/>
-      <clip-path
-          android:pathData="M128.31,131.1V129.5C131.51,130.2 133.91,133.1 133.91,136.5C133.91,139.9 131.51,142.7 128.31,143.5V141.9C130.61,141.2 132.31,139.1 132.31,136.6C132.31,134.1 130.61,131.8 128.31,131.1ZM130.31,136.4C130.31,135 129.51,133.8 128.31,133.2V139.6C129.41,139 130.31,137.8 130.31,136.4Z"
-          android:fillType="evenOdd"/>
-      <path
-          android:pathData="M128.31,131.1H78.31V168.15L113.75,178.94L128.31,131.1ZM128.31,129.5L138.99,80.66L78.31,67.38V129.5H128.31ZM128.31,143.5H78.31V207.54L140.44,192.01L128.31,143.5ZM128.31,141.9L113.75,94.07L78.31,104.86V141.9H128.31ZM128.31,133.2L150.67,88.48L78.31,52.3V133.2H128.31ZM128.31,139.6H78.31V223.83L152.25,183.5L128.31,139.6ZM126.72,130.13H176.73V10.79L91.66,94.5L126.72,130.13ZM126.72,142.79L91.37,178.15L176.73,263.5V142.79H126.72ZM122.71,138.78L158.07,103.43L143.43,88.78H122.71V138.78ZM119.5,138.78H69.5V188.78H119.5V138.78ZM119.5,134.08V84.08H69.5V134.08H119.5ZM122.71,134.08V184.08H143.19L157.78,169.72L122.71,134.08ZM178.31,131.1V129.5H78.31V131.1H178.31ZM117.63,178.35C98.03,174.06 83.91,156.76 83.91,136.5H183.91C183.91,109.44 164.99,86.34 138.99,80.66L117.63,178.35ZM83.91,136.5C83.91,115.15 98.93,99.31 116.18,94.99L140.44,192.01C164.09,186.1 183.91,164.66 183.91,136.5H83.91ZM178.31,143.5V141.9H78.31V143.5H178.31ZM142.87,189.74C164.72,183.09 182.31,162.64 182.31,136.6H82.31C82.31,115.57 96.5,99.32 113.75,94.07L142.87,189.74ZM182.31,136.6C182.31,112.64 166.74,90.53 142.87,83.27L113.75,178.94C94.48,173.07 82.31,155.56 82.31,136.6H182.31ZM180.31,136.4C180.31,115.04 167.88,97.08 150.67,88.48L105.95,177.92C91.14,170.52 80.31,154.96 80.31,136.4H180.31ZM78.31,133.2V139.6H178.31V133.2H78.31ZM152.25,183.5C166.09,175.95 180.31,159.6 180.31,136.4H80.31C80.31,116 92.73,102.06 104.37,95.71L152.25,183.5ZM76.72,130.13V142.79H176.73V130.13H76.72ZM162.08,107.44L158.07,103.43L87.36,174.14L91.37,178.15L162.08,107.44ZM122.71,88.78H119.5V188.78H122.71V88.78ZM169.5,138.78V134.08H69.5V138.78H169.5ZM119.5,184.08H122.71V84.08H119.5V184.08ZM157.78,169.72L161.79,165.77L91.66,94.5L87.65,98.44L157.78,169.72Z"
-          android:fillColor="#ffffff"/>
-    </group>
-    <path
-        android:pathData="M78.71,68.5L78.71,68.5A16,16 0,0 1,94.71 84.5L94.71,84.5A16,16 0,0 1,78.71 100.5L78.71,100.5A16,16 0,0 1,62.71 84.5L62.71,84.5A16,16 0,0 1,78.71 68.5z"
-        android:fillColor="#E8EAED"/>
-    <path
-        android:pathData="M86.26,82.15C86.92,82.15 87.46,81.61 87.46,80.95C87.46,80.29 86.92,79.75 86.26,79.75C85.6,79.75 85.06,80.29 85.06,80.95C85.06,81.61 85.6,82.15 86.26,82.15Z"
-        android:fillColor="#34A853"/>
-    <path
-        android:pathData="M82.66,86.15C83.99,86.15 85.06,85.08 85.06,83.75C85.06,82.43 83.99,81.35 82.66,81.35C81.34,81.35 80.26,82.43 80.26,83.75C80.26,85.08 81.34,86.15 82.66,86.15Z"
-        android:fillColor="#EA4335"/>
-    <path
-        android:pathData="M82.66,92.45C84.21,92.45 85.46,91.2 85.46,89.65C85.46,88.11 84.21,86.85 82.66,86.85C81.11,86.85 79.86,88.11 79.86,89.65C79.86,91.2 81.11,92.45 82.66,92.45Z"
-        android:fillColor="#FBBC04"/>
-    <path
-        android:pathData="M74.76,86.15C77.41,86.15 79.56,84 79.56,81.35C79.56,78.7 77.41,76.55 74.76,76.55C72.11,76.55 69.96,78.7 69.96,81.35C69.96,84 72.11,86.15 74.76,86.15Z"
-        android:fillColor="#4285F4"/>
-    <path
-        android:pathData="M174.71,120.5L174.71,120.5A16,16 0,0 1,190.71 136.5L190.71,136.5A16,16 0,0 1,174.71 152.5L174.71,152.5A16,16 0,0 1,158.71 136.5L158.71,136.5A16,16 0,0 1,174.71 120.5z"
-        android:fillColor="#9F3EBF"/>
-    <path
-        android:pathData="M178.53,130.5H171.03V142.2H178.53V130.5Z"
-        android:fillColor="#ffffff"/>
-    <path
-        android:pathData="M170.23,132.8H167.53V140H170.23V132.8Z"
-        android:fillColor="#ffffff"/>
-    <path
-        android:pathData="M182.23,132.8H179.53V140H182.23V132.8Z"
-        android:fillColor="#ffffff"/>
-    <path
-        android:pathData="M126.71,172.5L126.71,172.5A16,16 0,0 1,142.71 188.5L142.71,188.5A16,16 0,0 1,126.71 204.5L126.71,204.5A16,16 0,0 1,110.71 188.5L110.71,188.5A16,16 0,0 1,126.71 172.5z"
-        android:fillColor="#DE9834"/>
-    <path
-        android:pathData="M135.71,188.5L133.11,191.1V194.8H129.31L126.71,197.4L124.11,194.8H120.31V191.1L117.71,188.5L120.31,185.9V182.2H124.01L126.61,179.6L129.21,182.2H132.91V185.9L135.71,188.5ZM121.91,188.5C121.91,191.1 124.01,193.3 126.71,193.3C129.31,193.3 131.51,191.2 131.51,188.5C131.51,185.8 129.41,183.7 126.71,183.7C124.11,183.7 121.91,185.8 121.91,188.5Z"
-        android:fillColor="#ffffff"
-        android:fillType="evenOdd"/>
-    <path
-        android:pathData="M127.21,187.9V186.4H126.21V187.9H124.71V188.9H126.21V190.4H127.21V188.9H128.71V187.9H127.21Z"
-        android:fillColor="#ffffff"
-        android:fillType="evenOdd"/>
-    <path
-        android:pathData="M126.71,68.5L126.71,68.5A16,16 0,0 1,142.71 84.5L142.71,84.5A16,16 0,0 1,126.71 100.5L126.71,100.5A16,16 0,0 1,110.71 84.5L110.71,84.5A16,16 0,0 1,126.71 68.5z"
-        android:fillColor="#521BBF"/>
-    <path
-        android:pathData="M128.36,78.51C128.36,79.31 127.66,80.01 126.76,80.01C125.86,80.01 125.16,79.31 125.16,78.51C125.16,77.71 125.86,77.01 126.76,77.01C127.66,76.91 128.36,77.61 128.36,78.51ZM126.76,80.71C128.96,80.71 131.46,80.51 133.46,79.91L133.86,81.41C132.36,81.81 130.66,82.01 129.06,82.21V92.01H127.46V87.51H125.86V92.01H124.36V82.21C122.76,82.11 121.06,81.81 119.56,81.41L119.96,79.91C122.06,80.51 124.46,80.71 126.76,80.71Z"
-        android:fillColor="#ffffff"
-        android:fillType="evenOdd"/>
-    <path
-        android:pathData="M174.71,172.5L174.71,172.5A16,16 0,0 1,190.71 188.5L190.71,188.5A16,16 0,0 1,174.71 204.5L174.71,204.5A16,16 0,0 1,158.71 188.5L158.71,188.5A16,16 0,0 1,174.71 172.5z"
-        android:fillColor="#438947"/>
-    <path
-        android:pathData="M179.11,185.95H178.41V184.45C178.41,182.45 176.81,180.75 174.71,180.75C172.61,180.75 171.01,182.35 171.01,184.45V185.95H170.31C169.51,185.95 168.81,186.65 168.81,187.45V194.75C168.81,195.55 169.51,196.25 170.31,196.25H179.11C179.91,196.25 180.61,195.55 180.61,194.75V187.45C180.61,186.65 179.91,185.95 179.11,185.95ZM174.71,192.55C173.91,192.55 173.21,191.85 173.21,191.05C173.21,190.25 173.91,189.55 174.71,189.55C175.51,189.55 176.21,190.25 176.21,191.05C176.21,191.95 175.51,192.55 174.71,192.55ZM172.41,185.95H176.91V184.45C176.91,183.15 175.91,182.15 174.61,182.15C173.31,182.15 172.31,183.15 172.31,184.45V185.95H172.41Z"
-        android:fillColor="#ffffff"
-        android:fillType="evenOdd"/>
-    <path
-        android:pathData="M174.71,68.5L174.71,68.5A16,16 0,0 1,190.71 84.5L190.71,84.5A16,16 0,0 1,174.71 100.5L174.71,100.5A16,16 0,0 1,158.71 84.5L158.71,84.5A16,16 0,0 1,174.71 68.5z"
-        android:fillColor="#80868B"/>
-    <path
-        android:pathData="M173.91,77.35H175.51V85.25H173.91V77.35ZM178.21,80.25L179.31,79.15C180.81,80.45 181.81,82.35 181.81,84.55C181.81,88.45 178.61,91.65 174.71,91.65C170.81,91.65 167.61,88.45 167.61,84.55C167.61,82.35 168.61,80.45 170.11,79.15L171.21,80.25C170.01,81.25 169.21,82.85 169.21,84.55C169.21,87.65 171.71,90.15 174.81,90.15C177.91,90.15 180.41,87.65 180.41,84.55C180.31,82.75 179.41,81.25 178.21,80.25Z"
-        android:fillColor="#ffffff"
-        android:fillType="evenOdd"/>
-  </group>
   <path
-      android:pathData="M62.23,51.69L349.77,51.69A14.5,14.5 0,0 1,364.27 66.19L364.27,236.14A14.5,14.5 0,0 1,349.77 250.64L62.23,250.64A14.5,14.5 0,0 1,47.73 236.14L47.73,66.19A14.5,14.5 0,0 1,62.23 51.69z"
-      android:strokeWidth="3"
+      android:pathData="M384.18,300H27.82c-15.29,0 -27.82,-12.83 -27.82,-28.48V28.48C0,12.83 12.53,0 27.82,0H384.29c15.18,0 27.71,12.83 27.71,28.48v243.15c0,15.54 -12.53,28.37 -27.82,28.37Z"
+      android:fillColor="#fff"/>
+  <path
+      android:pathData="M78.71,69.91h0c8.84,0 16,7.16 16,16h0c0,8.84 -7.16,16 -16,16h0c-8.84,0 -16,-7.16 -16,-16h0c0,-8.84 7.16,-16 16,-16Z"
+      android:fillColor="#2197f3"/>
+  <path
+      android:pathData="M207.35,52.26h151.18c4.14,0 7.51,3.36 7.51,7.51V242.76c0,4.14 -3.36,7.51 -7.51,7.51H207.35V52.26h0Z"
+      android:fillColor="#e8eaed"/>
+  <path
+      android:pathData="M368.4,143.08L368.4,60.16c0,-5.67 -4.59,-10.26 -10.26,-10.26L54.17,49.9c-5.67,0 -10.26,4.59 -10.26,10.26L43.91,242.37c0,5.67 4.59,10.26 10.26,10.26L358.14,252.63c5.67,0 10.26,-4.59 10.26,-10.26v-99.29ZM366.04,242.75c0,4.15 -3.75,7.52 -7.9,7.52L54.17,250.27c-4.15,0 -7.9,-3.37 -7.9,-7.52L46.27,60.16c0,-4.15 3.75,-7.9 7.9,-7.9L358.14,52.26c4.15,0 7.9,3.75 7.9,7.9L366.04,242.75Z"
+      android:fillColor="#dadce0"/>
+  <path
+      android:pathData="M319.98,49.9c-0,-1.28 -1.04,-2.31 -2.31,-2.31h-23.11c-1.28,0 -2.31,1.03 -2.31,2.31h27.74Z"
+      android:fillColor="#dadce0"/>
+  <path
+      android:pathData="M344.57,49.9c-0,-1.28 -1.03,-2.31 -2.31,-2.31h-9.25c-1.28,0 -2.31,1.03 -2.31,2.31h13.87Z"
+      android:fillColor="#dadce0"/>
+  <path
+      android:pathData="M86.21,240.04l0.7,-0.7 -2.7,-2.7h5.9v-1h-5.9l2.7,-2.7 -0.7,-0.7 -3.9,3.9 3.9,3.9Z"
+      android:fillColor="#e8eaed"
+      android:fillType="evenOdd"/>
+  <path
+      android:pathData="M167.08,232.5l-0.7,0.7 2.7,2.7h-5.9v1h5.9l-2.7,2.7 0.7,0.7 3.9,-3.9 -3.9,-3.9Z"
+      android:fillColor="#7f868c"
+      android:fillType="evenOdd"/>
+  <path
+      android:strokeWidth="1"
+      android:pathData="M46.27,222.54L207.35,222.54"
       android:fillColor="#00000000"
-      android:strokeColor="#DADCE0"/>
+      android:strokeColor="#e8eaed"/>
   <path
-      android:pathData="M311.45,50.35C311.45,48.98 312.56,47.87 313.92,47.87L322.84,47.87C324.2,47.87 325.32,48.98 325.32,50.35L319.37,51.34L311.45,50.35Z"
-      android:fillColor="#DADCE0"/>
+      android:strokeWidth="1"
+      android:pathData="M126.81,222.54L126.81,250.27"
+      android:fillColor="#00000000"
+      android:strokeColor="#e8eaed"/>
   <path
-      android:pathData="M263.59,50.35C263.59,48.98 264.7,47.87 266.06,47.87L287.85,47.87C289.22,47.87 290.33,48.98 290.33,50.35L277.45,51.34L263.59,50.35Z"
-      android:fillColor="#DADCE0"/>
+      android:pathData="M78.71,173.91h0c8.83,0 16,7.16 16,16h0c0,8.83 -7.16,16 -16,16h0c-8.83,0 -16,-7.16 -16,-16h0c0,-8.83 7.16,-16 16,-16Z"
+      android:fillColor="#de9834"/>
+  <path
+      android:pathData="M78.73,199.47l-2.85,-2.77h-3.93v-3.93l-2.83,-2.83 2.83,-2.83v-3.93h3.93l2.85,-2.83 2.81,2.83h3.93v3.93l2.83,2.83 -2.83,2.83v3.93h-3.93l-2.81,2.77ZM78.73,194.04c-1.14,0 -2.11,-0.4 -2.92,-1.2 -0.8,-0.8 -1.2,-1.78 -1.2,-2.92s0.4,-2.11 1.2,-2.92c0.8,-0.8 1.78,-1.2 2.92,-1.2 1.14,0 2.11,0.4 2.92,1.2 0.8,0.8 1.2,1.78 1.2,2.92s-0.4,2.11 -1.2,2.92c-0.8,0.8 -1.78,1.2 -2.92,1.2ZM78.73,192.77c0.8,0 1.48,-0.27 2.03,-0.82 0.55,-0.55 0.82,-1.23 0.82,-2.03 -0,-0.8 -0.27,-1.48 -0.82,-2.03 -0.55,-0.55 -1.23,-0.82 -2.03,-0.82 -0.8,0 -1.48,0.27 -2.03,0.82 -0.55,0.55 -0.82,1.23 -0.82,2.03s0.27,1.48 0.82,2.03c0.55,0.55 1.23,0.82 2.03,0.82ZM78.73,197.7l2.28,-2.26h3.19v-3.19l2.3,-2.3 -2.3,-2.3v-3.19h-3.19l-2.28,-2.3 -2.32,2.3h-3.19v3.19l-2.3,2.3 2.3,2.3v3.19h3.17l2.35,2.26Z"
+      android:fillColor="#fff"/>
+  <path
+      android:pathData="M126.71,173.92h0c8.83,0 16,7.16 16,16h0c0,8.83 -7.16,16 -16,16h0c-8.83,0 -16,-7.16 -16,-16h0c0,-8.83 7.16,-16 16,-16Z"
+      android:fillColor="#de9834"/>
+  <path
+      android:pathData="M126.73,199.48l-2.85,-2.77h-3.93v-3.93l-2.83,-2.83 2.83,-2.83v-3.93h3.93l2.85,-2.83 2.81,2.83h3.93v3.93l2.83,2.83 -2.83,2.83v3.93h-3.93l-2.81,2.77ZM126.73,194.04c1.14,0 2.11,-0.4 2.92,-1.2 0.8,-0.8 1.2,-1.78 1.2,-2.92s-0.4,-2.11 -1.2,-2.92c-0.8,-0.8 -1.78,-1.2 -2.92,-1.2 -1.14,0 -2.11,0.4 -2.92,1.2 -0.8,0.8 -1.2,1.78 -1.2,2.92s0.4,2.11 1.2,2.92c0.8,0.8 1.78,1.2 2.92,1.2ZM126.73,192.78c0.8,0 1.48,-0.27 2.03,-0.82 0.55,-0.55 0.82,-1.23 0.82,-2.03 -0,-0.8 -0.27,-1.48 -0.82,-2.03 -0.55,-0.55 -1.23,-0.82 -2.03,-0.82 -0.8,0 -1.48,0.27 -2.03,0.82 -0.55,0.55 -0.82,1.23 -0.82,2.03s0.27,1.48 0.82,2.03c0.55,0.55 1.23,0.82 2.03,0.82ZM126.73,197.7l2.28,-2.26h3.19v-3.19l2.3,-2.3 -2.3,-2.3v-3.19h-3.19l-2.28,-2.3 -2.32,2.3h-3.19v3.19l-2.3,2.3 2.3,2.3v3.19h3.17l2.35,2.26ZM126.73,192.78c0.8,0 1.48,-0.27 2.03,-0.82 0.55,-0.55 0.82,-1.23 0.82,-2.03 -0,-0.8 -0.27,-1.48 -0.82,-2.03 -0.55,-0.55 -1.23,-0.82 -2.03,-0.82 -0.8,0 -1.48,0.27 -2.03,0.82 -0.55,0.55 -0.82,1.23 -0.82,2.03s0.27,1.48 0.82,2.03c0.55,0.55 1.23,0.82 2.03,0.82Z"
+      android:fillColor="#fff"/>
+  <path
+      android:pathData="M174.71,173.91h0c8.83,0 16,7.16 16,16h0c0,8.83 -7.16,16 -16,16h0c-8.83,0 -16,-7.16 -16,-16h0c0,-8.83 7.16,-16 16,-16Z"
+      android:fillColor="#438947"/>
+  <path
+      android:pathData="M169.09,197.98c-0.36,0 -0.67,-0.13 -0.92,-0.38s-0.38,-0.56 -0.38,-0.92v-9.38c0,-0.36 0.13,-0.67 0.38,-0.92s0.56,-0.38 0.92,-0.38h1.51v-2.07c0,-1.14 0.4,-2.11 1.2,-2.91 0.8,-0.8 1.77,-1.2 2.91,-1.2s2.11,0.4 2.91,1.2c0.8,0.8 1.2,1.77 1.2,2.91v2.07h1.51c0.36,0 0.67,0.13 0.92,0.38s0.38,0.56 0.38,0.92v9.38c0,0.36 -0.13,0.67 -0.38,0.92s-0.56,0.38 -0.92,0.38h-11.24ZM169.09,196.68h11.24v-9.38h-11.24v9.38ZM174.71,193.66c0.46,0 0.85,-0.16 1.18,-0.48s0.49,-0.7 0.49,-1.15c0,-0.43 -0.16,-0.82 -0.49,-1.18s-0.72,-0.53 -1.18,-0.53 -0.85,0.18 -1.18,0.53c-0.32,0.35 -0.49,0.75 -0.49,1.18 0,0.45 0.16,0.83 0.49,1.15s0.72,0.48 1.18,0.48ZM171.9,186.01h5.62v-2.07c0,-0.78 -0.27,-1.44 -0.82,-1.99 -0.55,-0.55 -1.21,-0.82 -1.99,-0.82s-1.44,0.27 -1.99,0.82 -0.82,1.21 -0.82,1.99v2.07ZM169.09,196.68v0Z"
+      android:fillColor="#fff"/>
+  <path
+      android:pathData="M174.71,69.85h0c8.87,0 16.06,7.19 16.06,16.06h0c0,8.87 -7.19,16.06 -16.06,16.06h0c-8.87,0 -16.06,-7.19 -16.06,-16.06h0c0,-8.87 7.19,-16.06 16.06,-16.06Z"
+      android:fillColor="#80868b"/>
+  <path
+      android:pathData="M173.91,78.73h1.61v7.93h-1.61v-7.93ZM178.22,81.64l1.1,-1.1c1.51,1.31 2.51,3.21 2.51,5.42 0,3.92 -3.21,7.13 -7.13,7.13s-7.13,-3.21 -7.13,-7.13c0,-2.21 1,-4.12 2.51,-5.42l1.1,1.1c-1.2,1 -2.01,2.61 -2.01,4.32 0,3.11 2.51,5.62 5.62,5.62 3.11,0 5.62,-2.51 5.62,-5.62 -0.1,-1.81 -1,-3.31 -2.21,-4.32Z"
+      android:fillColor="#fff"
+      android:fillType="evenOdd"/>
+  <path
+      android:pathData="M126.71,69.85h0c8.87,0 16.06,7.19 16.06,16.06h0c0,8.87 -7.19,16.06 -16.06,16.06h0c-8.87,0 -16.06,-7.19 -16.06,-16.06h0c0,-8.87 7.19,-16.06 16.06,-16.06Z"
+      android:fillColor="#521bbf"/>
+  <path
+      android:pathData="M126.71,79.26c-0.4,0 -0.74,-0.14 -1.02,-0.43 -0.29,-0.29 -0.43,-0.63 -0.43,-1.02s0.14,-0.74 0.43,-1.02c0.29,-0.29 0.63,-0.43 1.02,-0.43s0.74,0.14 1.02,0.43c0.29,0.29 0.43,0.63 0.43,1.02s-0.14,0.74 -0.43,1.02 -0.63,0.43 -1.02,0.43ZM124.64,91.48v-9.83c-0.84,-0.07 -1.68,-0.16 -2.53,-0.29 -0.85,-0.13 -1.64,-0.28 -2.37,-0.47l0.3,-1.19c1.06,0.27 2.15,0.46 3.27,0.58 1.12,0.12 2.25,0.18 3.39,0.18s2.27,-0.06 3.39,-0.18c1.12,-0.12 2.21,-0.31 3.27,-0.58l0.3,1.19c-0.73,0.19 -1.52,0.34 -2.37,0.47 -0.85,0.13 -1.69,0.22 -2.53,0.29v9.83h-1.19v-4.85h-1.75v4.85h-1.19ZM123.47,95.46c-0.23,0 -0.41,-0.07 -0.55,-0.21 -0.14,-0.14 -0.21,-0.32 -0.21,-0.55 0,-0.23 0.07,-0.41 0.21,-0.55 0.14,-0.14 0.32,-0.21 0.55,-0.21s0.41,0.07 0.55,0.21c0.14,0.14 0.21,0.32 0.21,0.55 0,0.23 -0.07,0.41 -0.21,0.55 -0.14,0.14 -0.32,0.21 -0.55,0.21ZM126.73,95.46c-0.23,0 -0.41,-0.07 -0.55,-0.21 -0.14,-0.14 -0.21,-0.32 -0.21,-0.55 0,-0.23 0.07,-0.41 0.21,-0.55 0.14,-0.14 0.32,-0.21 0.55,-0.21s0.41,0.07 0.55,0.21c0.14,0.14 0.21,0.32 0.21,0.55 0,0.23 -0.07,0.41 -0.21,0.55 -0.14,0.14 -0.32,0.21 -0.55,0.21ZM129.99,95.46c-0.23,0 -0.41,-0.07 -0.55,-0.21 -0.14,-0.14 -0.21,-0.32 -0.21,-0.55 0,-0.23 0.07,-0.41 0.21,-0.55 0.14,-0.14 0.32,-0.21 0.55,-0.21s0.41,0.07 0.55,0.21c0.14,0.14 0.21,0.32 0.21,0.55 0,0.23 -0.07,0.41 -0.21,0.55 -0.14,0.14 -0.32,0.21 -0.55,0.21Z"
+      android:fillColor="#fff"/>
+  <path
+      android:pathData="M126.71,121.91h0c8.83,0 15.99,7.16 15.99,15.99h0c0,8.83 -7.16,15.99 -15.99,15.99h0c-8.83,0 -15.99,-7.16 -15.99,-15.99h0c0,-8.83 7.16,-15.99 15.99,-15.99Z"
+      android:fillColor="#327969"/>
+  <path
+      android:pathData="M128.65,146.39v-1.5c1.57,-0.45 2.84,-1.32 3.84,-2.6 0.99,-1.28 1.49,-2.74 1.49,-4.37 0,-1.63 -0.49,-3.09 -1.48,-4.38s-2.27,-2.15 -3.85,-2.59v-1.5c2,0.45 3.63,1.46 4.89,3.04 1.26,1.57 1.89,3.39 1.89,5.43s-0.63,3.86 -1.89,5.43c-1.26,1.57 -2.89,2.59 -4.89,3.04ZM117.99,140.84v-5.81h3.87l4.84,-4.84v15.49l-4.84,-4.84h-3.87ZM128.16,142.01v-8.16c0.89,0.27 1.59,0.79 2.12,1.55 0.52,0.76 0.79,1.61 0.79,2.54 0,0.92 -0.27,1.76 -0.8,2.52s-1.23,1.28 -2.11,1.55ZM125.26,133.87l-2.74,2.61h-3.07v2.91h3.07l2.74,2.64v-8.16Z"
+      android:fillColor="#fff"/>
+  <path
+      android:pathData="M174.71,121.94h0c8.83,0 15.99,7.16 15.99,15.99h0c0,8.83 -7.16,15.99 -15.99,15.99h0c-8.83,0 -15.99,-7.16 -15.99,-15.99h0c0,-8.83 7.16,-15.99 15.99,-15.99Z"
+      android:fillColor="#9f3ebf"/>
+  <path
+      android:pathData="M179.18,131.5h-8.93v12.86h8.93v-12.86Z"
+      android:fillColor="#fff"/>
+  <path
+      android:pathData="M169.05,134.02h-3.22v7.91h3.22v-7.91Z"
+      android:fillColor="#fff"/>
+  <path
+      android:pathData="M183.58,134.02h-3.22v7.91h3.22v-7.91Z"
+      android:fillColor="#fff"/>
+  <path
+      android:pathData="M171.91,133.03h5.61v9.78h-5.61z"
+      android:fillColor="#9f3ebf"/>
+  <path
+      android:pathData="M78.71,121.91h0c8.83,0 15.99,7.16 15.99,15.99h0c0,8.83 -7.16,15.99 -15.99,15.99h0c-8.83,0 -15.99,-7.16 -15.99,-15.99h0c0,-8.83 7.16,-15.99 15.99,-15.99Z"
+      android:fillColor="#327969"/>
+  <path
+      android:pathData="M72.17,140.82v-5.81h3.88l4.84,-4.84v15.5l-4.84,-4.84h-3.88ZM82.34,141.98v-8.16c0.87,0.27 1.57,0.79 2.11,1.55 0.53,0.76 0.8,1.61 0.8,2.54 0,0.95 -0.27,1.8 -0.8,2.54 -0.53,0.74 -1.24,1.25 -2.11,1.53ZM79.43,133.85l-2.74,2.62h-3.08v2.91h3.08l2.74,2.64v-8.16Z"
+      android:fillColor="#fff"/>
+  <path
+      android:pathData="M78.73,96.45l-2.73,-2.73h-4.09c-0.36,0 -0.68,-0.14 -0.95,-0.41 -0.27,-0.27 -0.41,-0.59 -0.41,-0.95v-13.64c0,-0.36 0.14,-0.68 0.41,-0.95 0.27,-0.27 0.59,-0.41 0.95,-0.41h13.64c0.36,0 0.68,0.14 0.95,0.41 0.27,0.27 0.41,0.59 0.41,0.95v13.64c0,0.36 -0.14,0.68 -0.41,0.95 -0.27,0.27 -0.59,0.41 -0.95,0.41h-4.09l-2.73,2.73ZM78.75,89.79l1.27,-2.91 2.91,-1.27 -2.91,-1.27 -1.27,-2.91 -1.3,2.91 -2.89,1.27 2.89,1.27 1.3,2.91Z"
+      android:fillColor="#fff"/>
 </vector>
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/res/drawable/a11ymenu_intro.png b/packages/SystemUI/accessibility/accessibilitymenu/res/drawable/a11ymenu_intro.png
deleted file mode 100644
index 6149ee4..0000000
--- a/packages/SystemUI/accessibility/accessibilitymenu/res/drawable/a11ymenu_intro.png
+++ /dev/null
Binary files differ
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/res/drawable/a11ymenu_intro.xml b/packages/SystemUI/accessibility/accessibilitymenu/res/drawable/a11ymenu_intro.xml
new file mode 100644
index 0000000..a5a0535
--- /dev/null
+++ b/packages/SystemUI/accessibility/accessibilitymenu/res/drawable/a11ymenu_intro.xml
@@ -0,0 +1,90 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="412dp"
+    android:height="299.24dp"
+    android:viewportWidth="412"
+    android:viewportHeight="299.24">
+  <path
+      android:pathData="M383.9,299.24H28.1c-15.5,0 -28.1,-12.57 -28.1,-28.03V28.03C0,12.57 12.6,0 28.1,0H383.9c15.5,0 28.1,12.57 28.1,28.03v243.18c0,15.46 -12.6,28.03 -28.1,28.03Z"
+      android:fillColor="#fff"/>
+  <path
+      android:pathData="M106.4,0h195.3c2,0 3.6,0.2 3.6,0.4V31.2c0,0.2 -1.6,0.4 -3.6,0.4H106.4c-2,0 -3.6,-0.2 -3.6,-0.4V0.4c0,-0.2 1.6,-0.4 3.6,-0.4Z"
+      android:fillColor="#e8eaed"/>
+  <path
+      android:pathData="M303.7,238.9H104.5v1h98.4v29.5h1v-29.5h99.8v-1Z"
+      android:fillColor="#e8eaed"/>
+  <path
+      android:pathData="M153.7,258.3l0.7,-0.7 -2.7,-2.7h5.9v-1h-5.9l2.7,-2.7 -0.7,-0.7 -3.9,3.9 3.9,3.9Z"
+      android:fillColor="#e8eaed"
+      android:fillType="evenOdd"/>
+  <path
+      android:pathData="M253.5,250.4l-0.7,0.7 2.7,2.7h-5.9v1h5.9l-2.7,2.7 0.7,0.7 3.9,-3.9 -3.9,-3.9Z"
+      android:fillColor="#7f868c"
+      android:fillType="evenOdd"/>
+  <path
+      android:pathData="M119.3,273h169.8c10.2,0 18.5,-8.3 18.5,-18.5V73.7c2,0 3.7,-1.7 3.7,-3.7V33.1c0,-2 -1.7,-3.7 -3.7,-3.7V0h-3.7V254.5c0,8.1 -6.6,14.8 -14.8,14.8H119.3c-8.1,0 -14.8,-6.6 -14.8,-14.8V0h-3.7V254.5c0,10.2 8.3,18.5 18.5,18.5Z"
+      android:fillColor="#dadce0"/>
+  <path
+      android:pathData="M141.86,52.23h0c9.77,0 17.7,7.92 17.7,17.7h0c0,9.77 -7.92,17.7 -17.7,17.7h0c-9.77,0 -17.7,-7.92 -17.7,-17.7h0c0,-9.77 7.92,-17.7 17.7,-17.7Z"
+      android:fillColor="#2197f3"/>
+  <path
+      android:pathData="M270.14,52.23h0c9.77,0 17.7,7.92 17.7,17.7h0c0,9.77 -7.92,17.7 -17.7,17.7h0c-9.77,0 -17.7,-7.92 -17.7,-17.7h0c0,-9.77 7.92,-17.7 17.7,-17.7Z"
+      android:fillColor="#80868b"/>
+  <path
+      android:pathData="M269.25,62.01h1.77v8.74h-1.77v-8.74ZM274.01,65.22l1.22,-1.22c1.66,1.44 2.76,3.54 2.76,5.97 0,4.31 -3.54,7.85 -7.85,7.85s-7.85,-3.54 -7.85,-7.85c0,-2.43 1.11,-4.53 2.76,-5.97l1.22,1.22c-1.33,1.11 -2.21,2.88 -2.21,4.76 0,3.43 2.76,6.19 6.19,6.19 3.43,0 6.19,-2.76 6.19,-6.19 -0.11,-1.99 -1.11,-3.65 -2.43,-4.76Z"
+      android:fillColor="#fff"
+      android:fillType="evenOdd"/>
+  <path
+      android:pathData="M207.03,52.23h0c9.77,0 17.7,7.92 17.7,17.7h0c0,9.77 -7.92,17.7 -17.7,17.7h0c-9.77,0 -17.7,-7.92 -17.7,-17.7h0c0,-9.77 7.92,-17.7 17.7,-17.7Z"
+      android:fillColor="#521bbf"/>
+  <path
+      android:pathData="M207.03,62.6c-0.44,0 -0.81,-0.16 -1.13,-0.47 -0.31,-0.31 -0.47,-0.69 -0.47,-1.13s0.16,-0.81 0.47,-1.13c0.31,-0.31 0.69,-0.47 1.13,-0.47s0.81,0.16 1.13,0.47c0.31,0.31 0.47,0.69 0.47,1.13s-0.16,0.81 -0.47,1.13 -0.69,0.47 -1.13,0.47ZM204.75,76.06v-10.83c-0.92,-0.07 -1.85,-0.18 -2.78,-0.32 -0.94,-0.14 -1.8,-0.31 -2.61,-0.52l0.33,-1.31c1.17,0.29 2.37,0.5 3.61,0.64 1.23,0.13 2.48,0.2 3.74,0.2s2.5,-0.07 3.74,-0.2c1.23,-0.13 2.44,-0.34 3.61,-0.64l0.33,1.31c-0.8,0.2 -1.67,0.38 -2.61,0.52 -0.94,0.14 -1.86,0.24 -2.78,0.32v10.83h-1.31v-5.35h-1.93v5.35h-1.31ZM203.45,80.44c-0.25,0 -0.45,-0.08 -0.6,-0.23 -0.15,-0.15 -0.23,-0.35 -0.23,-0.6 0,-0.25 0.08,-0.45 0.23,-0.6 0.15,-0.15 0.35,-0.23 0.6,-0.23s0.45,0.08 0.6,0.23c0.15,0.15 0.23,0.35 0.23,0.6 0,0.25 -0.08,0.45 -0.23,0.6 -0.15,0.15 -0.35,0.23 -0.6,0.23ZM207.05,80.44c-0.25,0 -0.45,-0.08 -0.6,-0.23 -0.15,-0.15 -0.23,-0.35 -0.23,-0.6 0,-0.25 0.08,-0.45 0.23,-0.6 0.15,-0.15 0.35,-0.23 0.6,-0.23s0.45,0.08 0.6,0.23c0.15,0.15 0.23,0.35 0.23,0.6 0,0.25 -0.08,0.45 -0.23,0.6 -0.15,0.15 -0.35,0.23 -0.6,0.23ZM210.64,80.44c-0.25,0 -0.45,-0.08 -0.6,-0.23 -0.15,-0.15 -0.23,-0.35 -0.23,-0.6 0,-0.25 0.08,-0.45 0.23,-0.6 0.15,-0.15 0.35,-0.23 0.6,-0.23s0.45,0.08 0.6,0.23c0.15,0.15 0.23,0.35 0.23,0.6 0,0.25 -0.08,0.45 -0.23,0.6 -0.15,0.15 -0.35,0.23 -0.6,0.23Z"
+      android:fillColor="#fff"/>
+  <path
+      android:pathData="M141.86,180.81h0c9.77,0 17.7,7.92 17.7,17.7h0c0,9.77 -7.92,17.7 -17.7,17.7h0c-9.77,0 -17.7,-7.92 -17.7,-17.7h0c0,-9.77 7.92,-17.7 17.7,-17.7Z"
+      android:fillColor="#de9834"/>
+  <path
+      android:pathData="M141.88,209.09l-3.16,-3.06h-4.35v-4.35l-3.13,-3.13 3.13,-3.13v-4.35h4.35l3.16,-3.13 3.11,3.13h4.35v4.35l3.13,3.13 -3.13,3.13v4.35h-4.35l-3.11,3.06ZM141.88,203.08c-1.26,0 -2.34,-0.44 -3.23,-1.33 -0.89,-0.89 -1.33,-1.96 -1.33,-3.23s0.44,-2.34 1.33,-3.23c0.89,-0.89 1.96,-1.33 3.23,-1.33 1.26,0 2.34,0.44 3.23,1.33 0.89,0.89 1.33,1.96 1.33,3.23s-0.44,2.34 -1.33,3.23c-0.89,0.89 -1.96,1.33 -3.23,1.33ZM141.88,201.68c0.89,0 1.64,-0.3 2.24,-0.91 0.61,-0.61 0.91,-1.36 0.91,-2.24 -0,-0.89 -0.3,-1.64 -0.91,-2.24 -0.61,-0.61 -1.36,-0.91 -2.24,-0.91 -0.89,0 -1.64,0.3 -2.24,0.91 -0.61,0.61 -0.91,1.36 -0.91,2.24s0.3,1.64 0.91,2.24c0.61,0.61 1.36,0.91 2.24,0.91ZM141.88,207.12l2.53,-2.5h3.53v-3.53l2.55,-2.55 -2.55,-2.55v-3.53h-3.53l-2.53,-2.55 -2.57,2.55h-3.53v3.53l-2.55,2.55 2.55,2.55v3.53h3.51l2.6,2.5Z"
+      android:fillColor="#fff"/>
+  <path
+      android:pathData="M207.03,180.82h0c9.77,0 17.7,7.92 17.7,17.7h0c0,9.77 -7.92,17.7 -17.7,17.7h0c-9.77,0 -17.7,-7.92 -17.7,-17.7h0c0,-9.77 7.92,-17.7 17.7,-17.7Z"
+      android:fillColor="#de9834"/>
+  <path
+      android:pathData="M207.05,209.09l-3.16,-3.06h-4.35v-4.35l-3.13,-3.13 3.13,-3.13v-4.35h4.35l3.16,-3.13 3.11,3.13h4.35v4.35l3.13,3.13 -3.13,3.13v4.35h-4.35l-3.11,3.06ZM207.05,203.08c1.26,0 2.34,-0.44 3.23,-1.33 0.89,-0.89 1.33,-1.96 1.33,-3.23s-0.44,-2.34 -1.33,-3.23c-0.89,-0.89 -1.96,-1.33 -3.23,-1.33 -1.26,0 -2.34,0.44 -3.23,1.33 -0.89,0.89 -1.33,1.96 -1.33,3.23s0.44,2.34 1.33,3.23c0.89,0.89 1.96,1.33 3.23,1.33ZM207.05,201.68c0.89,0 1.64,-0.3 2.24,-0.91 0.61,-0.61 0.91,-1.36 0.91,-2.24 -0,-0.89 -0.3,-1.64 -0.91,-2.24 -0.61,-0.61 -1.36,-0.91 -2.24,-0.91 -0.89,0 -1.64,0.3 -2.24,0.91 -0.61,0.61 -0.91,1.36 -0.91,2.24s0.3,1.64 0.91,2.24c0.61,0.61 1.36,0.91 2.24,0.91ZM207.05,207.13l2.53,-2.5h3.53v-3.53l2.55,-2.55 -2.55,-2.55v-3.53h-3.53l-2.53,-2.55 -2.57,2.55h-3.53v3.53l-2.55,2.55 2.55,2.55v3.53h3.51l2.6,2.5ZM207.05,201.68c0.89,0 1.64,-0.3 2.24,-0.91 0.61,-0.61 0.91,-1.36 0.91,-2.24 -0,-0.89 -0.3,-1.64 -0.91,-2.24 -0.61,-0.61 -1.36,-0.91 -2.24,-0.91 -0.89,0 -1.64,0.3 -2.24,0.91 -0.61,0.61 -0.91,1.36 -0.91,2.24s0.3,1.64 0.91,2.24c0.61,0.61 1.36,0.91 2.24,0.91Z"
+      android:fillColor="#fff"/>
+  <path
+      android:pathData="M270.14,180.81h0c9.77,0 17.7,7.92 17.7,17.7h0c0,9.77 -7.92,17.7 -17.7,17.7h0c-9.77,0 -17.7,-7.92 -17.7,-17.7h0c0,-9.77 7.92,-17.7 17.7,-17.7Z"
+      android:fillColor="#438947"/>
+  <path
+      android:pathData="M263.92,207.44c-0.4,0 -0.74,-0.14 -1.02,-0.42s-0.42,-0.62 -0.42,-1.02v-10.37c0,-0.4 0.14,-0.74 0.42,-1.02s0.62,-0.42 1.02,-0.42h1.67v-2.29c0,-1.26 0.44,-2.33 1.33,-3.22 0.88,-0.88 1.96,-1.33 3.22,-1.33s2.33,0.44 3.22,1.33c0.88,0.88 1.33,1.96 1.33,3.22v2.29h1.67c0.4,0 0.74,0.14 1.02,0.42s0.42,0.62 0.42,1.02v10.37c0,0.4 -0.14,0.74 -0.42,1.02s-0.62,0.42 -1.02,0.42h-12.43ZM263.92,206.01h12.43v-10.37h-12.43v10.37ZM270.14,202.66c0.51,0 0.94,-0.18 1.3,-0.53s0.54,-0.77 0.54,-1.27c0,-0.48 -0.18,-0.91 -0.54,-1.3s-0.79,-0.59 -1.3,-0.59 -0.94,0.2 -1.3,0.59c-0.36,0.39 -0.54,0.82 -0.54,1.3 0,0.49 0.18,0.92 0.54,1.27s0.79,0.53 1.3,0.53ZM267.03,194.2h6.22v-2.29c0,-0.86 -0.3,-1.59 -0.91,-2.2 -0.61,-0.61 -1.34,-0.91 -2.2,-0.91s-1.59,0.3 -2.2,0.91 -0.91,1.34 -0.91,2.2v2.29ZM263.92,206.01v0Z"
+      android:fillColor="#fff"/>
+  <path
+      android:pathData="M207.03,116.5h0c9.77,0 17.7,7.92 17.7,17.7h0c0,9.77 -7.92,17.7 -17.7,17.7h0c-9.77,0 -17.7,-7.92 -17.7,-17.7h0c0,-9.77 7.92,-17.7 17.7,-17.7Z"
+      android:fillColor="#327969"/>
+  <path
+      android:pathData="M209.17,143.6v-1.66c1.73,-0.5 3.15,-1.46 4.25,-2.88 1.1,-1.42 1.65,-3.03 1.65,-4.84 0,-1.8 -0.54,-3.42 -1.63,-4.85s-2.51,-2.38 -4.26,-2.87v-1.66c2.22,0.5 4.02,1.62 5.41,3.36 1.39,1.74 2.09,3.75 2.09,6.02s-0.7,4.27 -2.09,6.02c-1.39,1.74 -3.2,2.86 -5.41,3.36ZM197.38,137.46v-6.43h4.29l5.36,-5.36v17.15l-5.36,-5.36h-4.29ZM208.63,138.75v-9.03c0.98,0.3 1.76,0.88 2.34,1.71 0.58,0.84 0.87,1.78 0.87,2.81 0,1.02 -0.29,1.95 -0.88,2.79s-1.37,1.41 -2.33,1.71ZM205.42,129.75l-3.03,2.89h-3.4v3.22h3.4l3.03,2.92v-9.03Z"
+      android:fillColor="#fff"/>
+  <path
+      android:pathData="M270.14,116.54h0c9.77,0 17.7,7.92 17.7,17.7h0c0,9.77 -7.92,17.7 -17.7,17.7h0c-9.77,0 -17.7,-7.92 -17.7,-17.7h0c0,-9.77 7.92,-17.7 17.7,-17.7Z"
+      android:fillColor="#9f3ebf"/>
+  <path
+      android:pathData="M275.08,127.12h-9.89v14.23h9.89v-14.23Z"
+      android:fillColor="#fff"/>
+  <path
+      android:pathData="M263.88,129.91h-3.56v8.76h3.56v-8.76Z"
+      android:fillColor="#fff"/>
+  <path
+      android:pathData="M279.96,129.91h-3.56v8.76h3.56v-8.76Z"
+      android:fillColor="#fff"/>
+  <path
+      android:pathData="M267.04,128.82h6.21v10.83h-6.21z"
+      android:fillColor="#9f3ebf"/>
+  <path
+      android:pathData="M141.86,116.5h0c9.77,0 17.7,7.92 17.7,17.7h0c0,9.77 -7.92,17.7 -17.7,17.7h0c-9.77,0 -17.7,-7.92 -17.7,-17.7h0c0,-9.77 7.92,-17.7 17.7,-17.7Z"
+      android:fillColor="#327969"/>
+  <path
+      android:pathData="M134.62,137.44v-6.43h4.29l5.36,-5.36v17.16l-5.36,-5.36h-4.29ZM145.88,138.73v-9.03c0.97,0.3 1.74,0.88 2.33,1.72 0.59,0.84 0.88,1.78 0.88,2.81 0,1.05 -0.29,1.99 -0.88,2.81s-1.37,1.39 -2.33,1.69ZM142.66,129.72l-3.03,2.9h-3.4v3.22h3.4l3.03,2.92v-9.03Z"
+      android:fillColor="#fff"/>
+  <path
+      android:pathData="M141.86,81.15l-2.93,-2.93h-4.39c-0.39,0 -0.73,-0.15 -1.02,-0.44 -0.29,-0.29 -0.44,-0.63 -0.44,-1.02v-14.63c0,-0.39 0.15,-0.73 0.44,-1.02 0.29,-0.29 0.63,-0.44 1.02,-0.44h14.63c0.39,0 0.73,0.15 1.02,0.44 0.29,0.29 0.44,0.63 0.44,1.02v14.63c0,0.39 -0.15,0.73 -0.44,1.02 -0.29,0.29 -0.63,0.44 -1.02,0.44h-4.39l-2.93,2.93ZM141.88,74l1.37,-3.12 3.12,-1.37 -3.12,-1.37 -1.37,-3.12 -1.39,3.12 -3.1,1.37 3.1,1.37 1.39,3.12Z"
+      android:fillColor="#fff"/>
+</vector>
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/res/drawable/ic_logo_a11y_assistant_24dp.xml b/packages/SystemUI/accessibility/accessibilitymenu/res/drawable/ic_logo_a11y_assistant_24dp.xml
new file mode 100644
index 0000000..9c3417f
--- /dev/null
+++ b/packages/SystemUI/accessibility/accessibilitymenu/res/drawable/ic_logo_a11y_assistant_24dp.xml
@@ -0,0 +1,10 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="24.0"
+    android:viewportHeight="24.0"
+    android:tint="@color/colorControlNormal">
+    <path
+        android:fillColor="@android:color/white"
+        android:pathData="M19,2L5,2c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h4l3,3 3,-3h4c1.1,0 2,-0.9 2,-2L21,4c0,-1.1 -0.9,-2 -2,-2zM13.88,12.88L12,17l-1.88,-4.12L6,11l4.12,-1.88L12,5l1.88,4.12L18,11l-4.12,1.88z"/>
+</vector>
\ No newline at end of file
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/res/drawable/ic_logo_assistant_32dp.xml b/packages/SystemUI/accessibility/accessibilitymenu/res/drawable/ic_logo_assistant_32dp.xml
deleted file mode 100644
index ebeebf8..0000000
--- a/packages/SystemUI/accessibility/accessibilitymenu/res/drawable/ic_logo_assistant_32dp.xml
+++ /dev/null
@@ -1,8 +0,0 @@
-<vector android:height="32dp"
-    android:viewportHeight="192.0" android:viewportWidth="192.0"
-    android:width="32dp" xmlns:android="http://schemas.android.com/apk/res/android">
-    <path android:fillColor="#34A853" android:pathData="M172,60m-12,0a12,12 0,1 1,24 0a12,12 0,1 1,-24 0"/>
-    <path android:fillColor="#EA4335" android:pathData="M136,88m-24,0a24,24 0,1 1,48 0a24,24 0,1 1,-48 0"/>
-    <path android:fillColor="#FBBC05" android:pathData="M136,148m-28,0a28,28 0,1 1,56 0a28,28 0,1 1,-56 0"/>
-    <path android:fillColor="#4285F4" android:pathData="M56,64m-48,0a48,48 0,1 1,96 0a48,48 0,1 1,-96 0"/>
-</vector>
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/res/values-night/colors.xml b/packages/SystemUI/accessibility/accessibilitymenu/res/values-night/colors.xml
index a600ec6..fbf2b07 100644
--- a/packages/SystemUI/accessibility/accessibilitymenu/res/values-night/colors.xml
+++ b/packages/SystemUI/accessibility/accessibilitymenu/res/values-night/colors.xml
@@ -9,7 +9,7 @@
   <color name="volume_color">#7ae3d4</color>
   <color name="notifications_color">#f496ac</color>
   <color name="screenshot_color">#adcbff</color>
-  <color name="assistant_color">#F1F3F4</color>
+  <color name="assistant_color">#adcbff</color>
   <color name="brightness_color">#fdd663</color>
 
   <color name="ripple_material_color">#10FFFFFF</color>
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/res/values/colors.xml b/packages/SystemUI/accessibility/accessibilitymenu/res/values/colors.xml
index c1494f9..d81d0d5 100644
--- a/packages/SystemUI/accessibility/accessibilitymenu/res/values/colors.xml
+++ b/packages/SystemUI/accessibility/accessibilitymenu/res/values/colors.xml
@@ -9,7 +9,7 @@
   <color name="volume_color">#01A2A0</color>
   <color name="notifications_color">#F15B8D</color>
   <color name="screenshot_color">#26459C</color>
-  <color name="assistant_color">#F1F3F4</color>
+  <color name="assistant_color">#2196F3</color>
   <color name="brightness_color">#E59810</color>
   <color name="colorAccent">#1a73e8</color>
 
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/res/values/strings.xml b/packages/SystemUI/accessibility/accessibilitymenu/res/values/strings.xml
index 30fd017..0747ef0 100644
--- a/packages/SystemUI/accessibility/accessibilitymenu/res/values/strings.xml
+++ b/packages/SystemUI/accessibility/accessibilitymenu/res/values/strings.xml
@@ -10,7 +10,7 @@
   <!-- String defining the label for the assistant button -->
   <string name="assistant_label">Assistant</string>
   <!-- String defining utterance for the assistant button for screen readers -->
-  <string name="assistant_utterance">Google Assistant</string>
+  <string name="assistant_utterance">Assistant</string>
   <!-- String defining the label for the accessibility settings button -->
   <string name="a11y_settings_label">Accessibility Settings</string>
   <!-- String defining the label for the volume button -->
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/AccessibilityMenuService.java b/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/AccessibilityMenuService.java
index 8ca64d2..96ea5b4 100644
--- a/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/AccessibilityMenuService.java
+++ b/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/AccessibilityMenuService.java
@@ -35,6 +35,7 @@
 import android.os.Looper;
 import android.os.SystemClock;
 import android.provider.Settings;
+import android.util.Log;
 import android.view.Display;
 import android.view.KeyEvent;
 import android.view.MotionEvent;
@@ -56,6 +57,8 @@
     public static final String PACKAGE_NAME = AccessibilityMenuService.class.getPackageName();
     public static final String INTENT_TOGGLE_MENU = ".toggle_menu";
     public static final String INTENT_HIDE_MENU = ".hide_menu";
+    public static final String INTENT_GLOBAL_ACTION = ".global_action";
+    public static final String INTENT_GLOBAL_ACTION_EXTRA = "GLOBAL_ACTION";
 
     private static final String TAG = "A11yMenuService";
     private static final long BUFFER_MILLISECONDS_TO_PREVENT_UPDATE_FAILURE = 100L;
@@ -231,6 +234,22 @@
     }
 
     /**
+     * Performs global action and broadcasts an intent indicating the action was performed.
+     * This is unnecessary for any current functionality, but is used for testing.
+     * Refer to {@code performGlobalAction()}.
+     *
+     * @param globalAction Global action to be performed.
+     * @return {@code true} if successful, {@code false} otherwise.
+     */
+    private boolean performGlobalActionInternal(int globalAction) {
+        Intent intent = new Intent(PACKAGE_NAME + INTENT_GLOBAL_ACTION);
+        intent.putExtra(INTENT_GLOBAL_ACTION_EXTRA, globalAction);
+        sendBroadcast(intent);
+        Log.i("A11yMenuService", "Broadcasting global action " + globalAction);
+        return performGlobalAction(globalAction);
+    }
+
+    /**
      * Handles click events of shortcuts.
      *
      * @param view the shortcut button being clicked.
@@ -249,17 +268,17 @@
                     new Intent(Settings.ACTION_ACCESSIBILITY_SETTINGS),
                     Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
         } else if (viewTag == ShortcutId.ID_POWER_VALUE.ordinal()) {
-            performGlobalAction(GLOBAL_ACTION_POWER_DIALOG);
+            performGlobalActionInternal(GLOBAL_ACTION_POWER_DIALOG);
         } else if (viewTag == ShortcutId.ID_RECENT_VALUE.ordinal()) {
-            performGlobalAction(GLOBAL_ACTION_RECENTS);
+            performGlobalActionInternal(GLOBAL_ACTION_RECENTS);
         } else if (viewTag == ShortcutId.ID_LOCKSCREEN_VALUE.ordinal()) {
-            performGlobalAction(GLOBAL_ACTION_LOCK_SCREEN);
+            performGlobalActionInternal(GLOBAL_ACTION_LOCK_SCREEN);
         } else if (viewTag == ShortcutId.ID_QUICKSETTING_VALUE.ordinal()) {
-            performGlobalAction(GLOBAL_ACTION_QUICK_SETTINGS);
+            performGlobalActionInternal(GLOBAL_ACTION_QUICK_SETTINGS);
         } else if (viewTag == ShortcutId.ID_NOTIFICATION_VALUE.ordinal()) {
-            performGlobalAction(GLOBAL_ACTION_NOTIFICATIONS);
+            performGlobalActionInternal(GLOBAL_ACTION_NOTIFICATIONS);
         } else if (viewTag == ShortcutId.ID_SCREENSHOT_VALUE.ordinal()) {
-            performGlobalAction(GLOBAL_ACTION_TAKE_SCREENSHOT);
+            performGlobalActionInternal(GLOBAL_ACTION_TAKE_SCREENSHOT);
         } else if (viewTag == ShortcutId.ID_BRIGHTNESS_UP_VALUE.ordinal()) {
             adjustBrightness(BRIGHTNESS_UP_INCREMENT_GAMMA);
             return;
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/model/A11yMenuShortcut.java b/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/model/A11yMenuShortcut.java
index b6328f0..66a2fae 100644
--- a/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/model/A11yMenuShortcut.java
+++ b/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/model/A11yMenuShortcut.java
@@ -54,7 +54,7 @@
     /** Map stores all shortcut resource IDs that is in matching order of defined shortcut. */
     private static final Map<ShortcutId, int[]> sShortcutResource = new HashMap<>() {{
             put(ShortcutId.ID_ASSISTANT_VALUE, new int[] {
-                    R.drawable.ic_logo_assistant_32dp,
+                    R.drawable.ic_logo_a11y_assistant_24dp,
                     R.color.assistant_color,
                     R.string.assistant_utterance,
                     R.string.assistant_label,
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/tests/AndroidManifest.xml b/packages/SystemUI/accessibility/accessibilitymenu/tests/AndroidManifest.xml
index 7be6ca7..2be9245 100644
--- a/packages/SystemUI/accessibility/accessibilitymenu/tests/AndroidManifest.xml
+++ b/packages/SystemUI/accessibility/accessibilitymenu/tests/AndroidManifest.xml
@@ -29,4 +29,15 @@
         android:targetPackage="com.android.systemui.accessibility.accessibilitymenu.tests"
         android:label="AccessibilityMenu Test Cases">
     </instrumentation>
+
+    <queries>
+        <intent>
+            <action android:name="android.intent.action.VIEW" />
+            <category android:name="android.intent.category.BROWSABLE" />
+            <data android:scheme="https" />
+        </intent>
+        <intent>
+            <action android:name="android.intent.action.VOICE_COMMAND" />
+        </intent>
+    </queries>
 </manifest>
\ No newline at end of file
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/tests/src/com/android/systemui/accessibility/accessibilitymenu/tests/AccessibilityMenuServiceTest.java b/packages/SystemUI/accessibility/accessibilitymenu/tests/src/com/android/systemui/accessibility/accessibilitymenu/tests/AccessibilityMenuServiceTest.java
index 529a70c..0e89dcd 100644
--- a/packages/SystemUI/accessibility/accessibilitymenu/tests/src/com/android/systemui/accessibility/accessibilitymenu/tests/AccessibilityMenuServiceTest.java
+++ b/packages/SystemUI/accessibility/accessibilitymenu/tests/src/com/android/systemui/accessibility/accessibilitymenu/tests/AccessibilityMenuServiceTest.java
@@ -16,6 +16,15 @@
 
 package com.android.systemui.accessibility.accessibilitymenu.tests;
 
+import static android.accessibilityservice.AccessibilityService.GLOBAL_ACTION_LOCK_SCREEN;
+import static android.accessibilityservice.AccessibilityService.GLOBAL_ACTION_NOTIFICATIONS;
+import static android.accessibilityservice.AccessibilityService.GLOBAL_ACTION_POWER_DIALOG;
+import static android.accessibilityservice.AccessibilityService.GLOBAL_ACTION_QUICK_SETTINGS;
+import static android.accessibilityservice.AccessibilityService.GLOBAL_ACTION_RECENTS;
+import static android.accessibilityservice.AccessibilityService.GLOBAL_ACTION_TAKE_SCREENSHOT;
+
+import static com.android.systemui.accessibility.accessibilitymenu.AccessibilityMenuService.INTENT_GLOBAL_ACTION;
+import static com.android.systemui.accessibility.accessibilitymenu.AccessibilityMenuService.INTENT_GLOBAL_ACTION_EXTRA;
 import static com.android.systemui.accessibility.accessibilitymenu.AccessibilityMenuService.INTENT_HIDE_MENU;
 import static com.android.systemui.accessibility.accessibilitymenu.AccessibilityMenuService.INTENT_TOGGLE_MENU;
 import static com.android.systemui.accessibility.accessibilitymenu.AccessibilityMenuService.PACKAGE_NAME;
@@ -23,11 +32,15 @@
 import android.accessibilityservice.AccessibilityServiceInfo;
 import android.app.Instrumentation;
 import android.app.UiAutomation;
+import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
+import android.content.IntentFilter;
 import android.hardware.display.BrightnessInfo;
 import android.hardware.display.DisplayManager;
+import android.media.AudioManager;
 import android.provider.Settings;
+import android.util.Log;
 import android.view.accessibility.AccessibilityManager;
 import android.view.accessibility.AccessibilityNodeInfo;
 
@@ -37,22 +50,29 @@
 import com.android.compatibility.common.util.TestUtils;
 import com.android.systemui.accessibility.accessibilitymenu.model.A11yMenuShortcut.ShortcutId;
 
+import org.junit.After;
 import org.junit.AfterClass;
+import org.junit.Before;
 import org.junit.BeforeClass;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
 import java.util.List;
+import java.util.concurrent.atomic.AtomicInteger;
 
 @RunWith(AndroidJUnit4.class)
 public class AccessibilityMenuServiceTest {
     private static final String TAG = "A11yMenuServiceTest";
+    private static final int CLICK_ID = AccessibilityNodeInfo.ACTION_CLICK;
 
     private static final int TIMEOUT_SERVICE_STATUS_CHANGE_S = 5;
-    private static final int TIMEOUT_UI_CHANGE_S = 5;
+    private static final int TIMEOUT_UI_CHANGE_S = 10;
+    private static final int NO_GLOBAL_ACTION = -1;
+    private static final String INPUT_KEYEVENT_KEYCODE_BACK = "input keyevent KEYCODE_BACK";
 
     private static Instrumentation sInstrumentation;
     private static UiAutomation sUiAutomation;
+    private static AtomicInteger sLastGlobalAction;
 
     private static AccessibilityManager sAccessibilityManager;
 
@@ -62,7 +82,7 @@
         sInstrumentation = InstrumentationRegistry.getInstrumentation();
         sUiAutomation = sInstrumentation.getUiAutomation(
                 UiAutomation.FLAG_DONT_SUPPRESS_ACCESSIBILITY_SERVICES);
-        final Context context = sInstrumentation.getContext();
+        final Context context = sInstrumentation.getTargetContext();
         sAccessibilityManager = context.getSystemService(AccessibilityManager.class);
 
         // Disable all a11yServices if any are active.
@@ -85,6 +105,17 @@
                 () -> sAccessibilityManager.getEnabledAccessibilityServiceList(
                         AccessibilityServiceInfo.FEEDBACK_ALL_MASK).stream().filter(
                                 info -> info.getId().contains(serviceName)).count() == 1);
+
+        sLastGlobalAction = new AtomicInteger(NO_GLOBAL_ACTION);
+        context.registerReceiver(new BroadcastReceiver() {
+            @Override
+            public void onReceive(Context context, Intent intent) {
+                Log.i(TAG, "Received global action intent.");
+                sLastGlobalAction.set(
+                        intent.getIntExtra(INTENT_GLOBAL_ACTION_EXTRA, NO_GLOBAL_ACTION));
+            }},
+                new IntentFilter(PACKAGE_NAME + INTENT_GLOBAL_ACTION),
+                null, null, Context.RECEIVER_EXPORTED);
     }
 
     @AfterClass
@@ -93,23 +124,39 @@
                 Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES, "");
     }
 
-    private boolean isMenuVisible() {
-        return sUiAutomation.getRootInActiveWindow() != null
-                && sUiAutomation.getRootInActiveWindow().getPackageName().toString().equals(
-                PACKAGE_NAME);
+    @Before
+    public void setup() throws Throwable {
+        openMenu();
     }
 
-    private void openMenu() throws Throwable {
-        if (isMenuVisible()) {
-            return;
-        }
+    @After
+    public void tearDown() throws Throwable {
+        closeMenu();
+        sLastGlobalAction.set(NO_GLOBAL_ACTION);
+    }
+
+    private static boolean isMenuVisible() {
+        AccessibilityNodeInfo root = sUiAutomation.getRootInActiveWindow();
+        return root != null && root.getPackageName().toString().equals(PACKAGE_NAME);
+    }
+
+    private static void openMenu() throws Throwable {
         Intent intent = new Intent(PACKAGE_NAME + INTENT_TOGGLE_MENU);
         sInstrumentation.getContext().sendBroadcast(intent);
+
         TestUtils.waitUntil("Timed out before menu could appear.",
-                TIMEOUT_UI_CHANGE_S, () -> isMenuVisible());
+                TIMEOUT_UI_CHANGE_S,
+                () -> {
+                    if (isMenuVisible()) {
+                        return true;
+                    } else {
+                        sInstrumentation.getContext().sendBroadcast(intent);
+                        return false;
+                    }
+                });
     }
 
-    private void closeMenu() throws Throwable {
+    private static void closeMenu() throws Throwable {
         if (!isMenuVisible()) {
             return;
         }
@@ -119,11 +166,21 @@
                 TIMEOUT_UI_CHANGE_S, () -> !isMenuVisible());
     }
 
+    /**
+     * Provides list of all present shortcut buttons.
+     * @return List of shortcut buttons.
+     */
     private List<AccessibilityNodeInfo> getGridButtonList() {
         return sUiAutomation.getRootInActiveWindow()
                         .findAccessibilityNodeInfosByViewId(PACKAGE_NAME + ":id/shortcutIconBtn");
     }
 
+    /**
+     * Returns the first button whose uniqueID matches the provided text.
+     * @param buttons List of buttons.
+     * @param text Text to match button's uniqueID to.
+     * @return Button whose uniqueID matches text, {@code null} otherwise.
+     */
     private AccessibilityNodeInfo findGridButtonInfo(
             List<AccessibilityNodeInfo> buttons, String text) {
         for (AccessibilityNodeInfo button: buttons) {
@@ -136,8 +193,6 @@
 
     @Test
     public void testAdjustBrightness() throws Throwable {
-        openMenu();
-
         Context context = sInstrumentation.getTargetContext();
         DisplayManager displayManager = context.getSystemService(
                 DisplayManager.class);
@@ -149,7 +204,6 @@
         AccessibilityNodeInfo brightnessDownButton = findGridButtonInfo(buttons,
                 String.valueOf(ShortcutId.ID_BRIGHTNESS_DOWN_VALUE.ordinal()));
 
-        int clickId = AccessibilityNodeInfo.AccessibilityAction.ACTION_CLICK.getId();
         BrightnessInfo brightnessInfo = displayManager.getDisplay(
                 context.getDisplayId()).getBrightnessInfo();
 
@@ -159,7 +213,7 @@
                     TIMEOUT_UI_CHANGE_S,
                     () -> displayManager.getBrightness(context.getDisplayId())
                             == brightnessInfo.brightnessMinimum);
-            brightnessUpButton.performAction(clickId);
+            brightnessUpButton.performAction(CLICK_ID);
             TestUtils.waitUntil("Did not detect an increase in brightness.",
                     TIMEOUT_UI_CHANGE_S,
                     () -> displayManager.getBrightness(context.getDisplayId())
@@ -170,14 +224,155 @@
                     TIMEOUT_UI_CHANGE_S,
                     () -> displayManager.getBrightness(context.getDisplayId())
                             == brightnessInfo.brightnessMaximum);
-            brightnessDownButton.performAction(clickId);
+            brightnessDownButton.performAction(CLICK_ID);
             TestUtils.waitUntil("Did not detect a decrease in brightness.",
                     TIMEOUT_UI_CHANGE_S,
                     () -> displayManager.getBrightness(context.getDisplayId())
                             < brightnessInfo.brightnessMaximum);
         } finally {
             displayManager.setBrightness(context.getDisplayId(), resetBrightness);
-            closeMenu();
         }
     }
+
+    @Test
+    public void testAdjustVolume() throws Throwable {
+        Context context = sInstrumentation.getTargetContext();
+        AudioManager audioManager = context.getSystemService(AudioManager.class);
+        int resetVolume = audioManager.getStreamVolume(AudioManager.STREAM_MUSIC);
+
+        List<AccessibilityNodeInfo> buttons = getGridButtonList();
+        AccessibilityNodeInfo volumeUpButton = findGridButtonInfo(buttons,
+                String.valueOf(ShortcutId.ID_VOLUME_UP_VALUE.ordinal()));
+        AccessibilityNodeInfo volumeDownButton = findGridButtonInfo(buttons,
+                String.valueOf(ShortcutId.ID_VOLUME_DOWN_VALUE.ordinal()));
+
+        try {
+            int min = audioManager.getStreamMinVolume(AudioManager.STREAM_MUSIC);
+            audioManager.setStreamVolume(AudioManager.STREAM_MUSIC, min,
+                    AudioManager.FLAG_REMOVE_SOUND_AND_VIBRATE);
+            TestUtils.waitUntil("Could not change audio stream to minimum volume.",
+                    TIMEOUT_UI_CHANGE_S,
+                    () -> audioManager.getStreamVolume(AudioManager.STREAM_MUSIC) == min);
+            volumeUpButton.performAction(CLICK_ID);
+            TestUtils.waitUntil("Did not detect an increase in volume.",
+                    TIMEOUT_UI_CHANGE_S,
+                    () -> audioManager.getStreamVolume(AudioManager.STREAM_MUSIC) > min);
+
+            int max = audioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC);
+            audioManager.setStreamVolume(AudioManager.STREAM_MUSIC, max,
+                    AudioManager.FLAG_REMOVE_SOUND_AND_VIBRATE);
+            TestUtils.waitUntil("Could not change audio stream to maximum volume.",
+                    TIMEOUT_UI_CHANGE_S,
+                    () -> audioManager.getStreamVolume(AudioManager.STREAM_MUSIC) == max);
+            volumeDownButton.performAction(CLICK_ID);
+            TestUtils.waitUntil("Did not detect a decrease in volume.",
+                    TIMEOUT_UI_CHANGE_S,
+                    () -> audioManager.getStreamVolume(AudioManager.STREAM_MUSIC) < max);
+        } finally {
+            audioManager.setStreamVolume(AudioManager.STREAM_MUSIC,
+                    resetVolume, AudioManager.FLAG_REMOVE_SOUND_AND_VIBRATE);
+        }
+    }
+
+    @Test
+    public void testAssistantButton_opensVoiceAssistant() throws Throwable {
+        AccessibilityNodeInfo assistantButton = findGridButtonInfo(getGridButtonList(),
+                String.valueOf(ShortcutId.ID_ASSISTANT_VALUE.ordinal()));
+        Intent expectedIntent = new Intent(Intent.ACTION_VOICE_COMMAND);
+        String expectedPackage = expectedIntent.resolveActivity(
+                sInstrumentation.getContext().getPackageManager()).getPackageName();
+
+        sUiAutomation.executeAndWaitForEvent(
+                () -> assistantButton.performAction(CLICK_ID),
+                (event) -> expectedPackage.contains(event.getPackageName()),
+                TIMEOUT_UI_CHANGE_S * 1000
+        );
+    }
+
+    @Test
+    public void testAccessibilitySettingsButton_opensAccessibilitySettings() throws Throwable {
+        AccessibilityNodeInfo settingsButton = findGridButtonInfo(getGridButtonList(),
+                String.valueOf(ShortcutId.ID_A11YSETTING_VALUE.ordinal()));
+        Intent expectedIntent = new Intent(Settings.ACTION_ACCESSIBILITY_SETTINGS);
+        String expectedPackage = expectedIntent.resolveActivity(
+                sInstrumentation.getContext().getPackageManager()).getPackageName();
+
+        sUiAutomation.executeAndWaitForEvent(
+                () -> settingsButton.performAction(CLICK_ID),
+                (event) -> expectedPackage.contains(event.getPackageName()),
+                TIMEOUT_UI_CHANGE_S * 1000
+        );
+    }
+
+    @Test
+    public void testPowerButton_performsGlobalAction() throws Throwable {
+        AccessibilityNodeInfo button = findGridButtonInfo(getGridButtonList(),
+                String.valueOf(ShortcutId.ID_POWER_VALUE.ordinal()));
+
+        button.performAction(CLICK_ID);
+        TestUtils.waitUntil("Did not detect Power action being performed.",
+                TIMEOUT_UI_CHANGE_S,
+                () -> sLastGlobalAction.compareAndSet(
+                        GLOBAL_ACTION_POWER_DIALOG, NO_GLOBAL_ACTION));
+    }
+
+    @Test
+    public void testRecentButton_performsGlobalAction() throws Throwable {
+        AccessibilityNodeInfo button = findGridButtonInfo(getGridButtonList(),
+                String.valueOf(ShortcutId.ID_RECENT_VALUE.ordinal()));
+
+        button.performAction(CLICK_ID);
+        TestUtils.waitUntil("Did not detect Recents action being performed.",
+                TIMEOUT_UI_CHANGE_S,
+                () -> sLastGlobalAction.compareAndSet(
+                        GLOBAL_ACTION_RECENTS, NO_GLOBAL_ACTION));
+    }
+
+    @Test
+    public void testLockButton_performsGlobalAction() throws Throwable {
+        AccessibilityNodeInfo button = findGridButtonInfo(getGridButtonList(),
+                String.valueOf(ShortcutId.ID_LOCKSCREEN_VALUE.ordinal()));
+
+        button.performAction(CLICK_ID);
+        TestUtils.waitUntil("Did not detect Lock action being performed.",
+                TIMEOUT_UI_CHANGE_S,
+                () -> sLastGlobalAction.compareAndSet(
+                        GLOBAL_ACTION_LOCK_SCREEN, NO_GLOBAL_ACTION));
+    }
+
+    @Test
+    public void testQuickSettingsButton_performsGlobalAction() throws Throwable {
+        AccessibilityNodeInfo button = findGridButtonInfo(getGridButtonList(),
+                String.valueOf(ShortcutId.ID_QUICKSETTING_VALUE.ordinal()));
+
+        button.performAction(CLICK_ID);
+        TestUtils.waitUntil("Did not detect Quick Settings action being performed.",
+                TIMEOUT_UI_CHANGE_S,
+                () -> sLastGlobalAction.compareAndSet(
+                        GLOBAL_ACTION_QUICK_SETTINGS, NO_GLOBAL_ACTION));
+    }
+
+    @Test
+    public void testNotificationsButton_performsGlobalAction() throws Throwable {
+        AccessibilityNodeInfo button = findGridButtonInfo(getGridButtonList(),
+                String.valueOf(ShortcutId.ID_NOTIFICATION_VALUE.ordinal()));
+
+        button.performAction(CLICK_ID);
+        TestUtils.waitUntil("Did not detect Notifications action being performed.",
+                TIMEOUT_UI_CHANGE_S,
+                () -> sLastGlobalAction.compareAndSet(
+                        GLOBAL_ACTION_NOTIFICATIONS, NO_GLOBAL_ACTION));
+    }
+
+    @Test
+    public void testScreenshotButton_performsGlobalAction() throws Throwable {
+        AccessibilityNodeInfo button = findGridButtonInfo(getGridButtonList(),
+                String.valueOf(ShortcutId.ID_SCREENSHOT_VALUE.ordinal()));
+
+        button.performAction(CLICK_ID);
+        TestUtils.waitUntil("Did not detect Screenshot action being performed.",
+                TIMEOUT_UI_CHANGE_S,
+                () -> sLastGlobalAction.compareAndSet(
+                        GLOBAL_ACTION_TAKE_SCREENSHOT, NO_GLOBAL_ACTION));
+    }
 }
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/RemoteTransitionAdapter.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/RemoteTransitionAdapter.kt
index 6946e6b..03e1e66 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/RemoteTransitionAdapter.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/RemoteTransitionAdapter.kt
@@ -59,12 +59,14 @@
             // changes should be ordered top-to-bottom in z
             val mode = change.mode
 
+            var rootIdx = info.findRootIndex(change.endDisplayId)
+            if (rootIdx < 0) rootIdx = 0
             // Launcher animates leaf tasks directly, so always reparent all task leashes to root.
-            t.reparent(leash, info.rootLeash)
+            t.reparent(leash, info.getRoot(rootIdx).leash)
             t.setPosition(
                 leash,
-                (change.startAbsBounds.left - info.rootOffset.x).toFloat(),
-                (change.startAbsBounds.top - info.rootOffset.y).toFloat()
+                (change.startAbsBounds.left - info.getRoot(rootIdx).offset.x).toFloat(),
+                (change.startAbsBounds.top - info.getRoot(rootIdx).offset.y).toFloat()
             )
             t.show(leash)
             // Put all the OPEN/SHOW on top
@@ -114,8 +116,11 @@
                     .setName(change.leash.toString() + "_transition-leash")
                     .setContainerLayer()
                     .setParent(
-                        if (change.parent == null) info.rootLeash
-                        else info.getChange(change.parent!!)!!.leash
+                        if (change.parent == null) {
+                            var rootIdx = info.findRootIndex(change.endDisplayId)
+                            if (rootIdx < 0) rootIdx = 0
+                            info.getRoot(rootIdx).leash
+                        } else info.getChange(change.parent!!)!!.leash
                     )
                     .build()
             // Copied Transitions setup code (which expects bottom-to-top order, so we swap here)
diff --git a/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/shaderutil/ShaderUtilLibrary.kt b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/shaderutil/ShaderUtilLibrary.kt
index d78e0c1..280e7ed9 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/shaderutil/ShaderUtilLibrary.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/shaderutil/ShaderUtilLibrary.kt
@@ -53,6 +53,19 @@
                                 cos(p.y * 0.01 + time * 0.005931)) * distort_amount_xy;
             }
 
+            // Perceived luminosity (L′), not absolute luminosity.
+            half getLuminosity(vec3 c) {
+                return 0.3 * c.r + 0.59 * c.g + 0.11 * c.b;
+            }
+
+            // Creates a luminosity mask and clamp to the legal range.
+            vec3 maskLuminosity(vec3 dest, float lum) {
+                dest.rgb *= vec3(lum);
+                // Clip back into the legal range
+                dest = clamp(dest, vec3(0.), vec3(1.0));
+                return dest;
+            }
+
             // Return range [-1, 1].
             vec3 hash(vec3 p) {
                 p = fract(p * vec3(.3456, .1234, .9876));
@@ -62,14 +75,14 @@
             }
 
             // Skew factors (non-uniform).
-            const float SKEW = 0.3333333;  // 1/3
-            const float UNSKEW = 0.1666667;  // 1/6
+            const half SKEW = 0.3333333;  // 1/3
+            const half UNSKEW = 0.1666667;  // 1/6
 
             // Return range roughly [-1,1].
             // It's because the hash function (that returns a random gradient vector) returns
             // different magnitude of vectors. Noise doesn't have to be in the precise range thus
             // skipped normalize.
-            float simplex3d(vec3 p) {
+            half simplex3d(vec3 p) {
                 // Skew the input coordinate, so that we get squashed cubical grid
                 vec3 s = floor(p + (p.x + p.y + p.z) * SKEW);
 
@@ -143,6 +156,22 @@
                 // Should multiply by the possible max contribution to adjust the range in [-1,1].
                 return dot(vec4(32.), nc);
             }
+
+            // Random rotations.
+            // The way you create fractal noise is layering simplex noise with some rotation.
+            // To make random cloud looking noise, the rotations should not align. (Otherwise it
+            // creates patterned noise).
+            // Below rotations only rotate in one axis.
+            const mat3 rot1 = mat3(1.0, 0. ,0., 0., 0.15, -0.98, 0., 0.98, 0.15);
+            const mat3 rot2 = mat3(-0.95, 0. ,-0.3, 0., 1., 0., 0.3, 0., -0.95);
+            const mat3 rot3 = mat3(1.0, 0. ,0., 0., -0.44, -0.89, 0., 0.89, -0.44);
+
+            // Octave = 4
+            // Divide each coefficient by 3 to produce more grainy noise.
+            half simplex3d_fractal(vec3 mat) {
+                return 0.675 * simplex3d(mat * rot1) + 0.225 * simplex3d(2.0 * mat * rot2)
+                        + 0.075 * simplex3d(4.0 * mat * rot3) + 0.025 * simplex3d(8.0 * mat);
+            }
             """
     }
 }
diff --git a/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseShader.kt b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseShader.kt
index 7456c43..0e22667 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseShader.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseShader.kt
@@ -19,8 +19,13 @@
 import com.android.systemui.surfaceeffects.shaderutil.ShaderUtilLibrary
 import java.lang.Float.max
 
-/** Shader that renders turbulence simplex noise, with no octave. */
-class TurbulenceNoiseShader : RuntimeShader(TURBULENCE_NOISE_SHADER) {
+/**
+ * Shader that renders turbulence simplex noise, by default no octave.
+ *
+ * @param useFractal whether to use fractal noise (4 octaves).
+ */
+class TurbulenceNoiseShader(useFractal: Boolean = false) :
+    RuntimeShader(if (useFractal) FRACTAL_NOISE_SHADER else SIMPLEX_NOISE_SHADER) {
     // language=AGSL
     companion object {
         private const val UNIFORMS =
@@ -35,21 +40,7 @@
             layout(color) uniform vec4 in_backgroundColor;
         """
 
-        private const val SHADER_LIB =
-            """
-            float getLuminosity(vec3 c) {
-                return 0.3*c.r + 0.59*c.g + 0.11*c.b;
-            }
-
-            vec3 maskLuminosity(vec3 dest, float lum) {
-                dest.rgb *= vec3(lum);
-                // Clip back into the legal range
-                dest = clamp(dest, vec3(0.), vec3(1.0));
-                return dest;
-            }
-        """
-
-        private const val MAIN_SHADER =
+        private const val SIMPLEX_SHADER =
             """
             vec4 main(vec2 p) {
                 vec2 uv = p / in_size.xy;
@@ -71,8 +62,26 @@
             }
         """
 
-        private const val TURBULENCE_NOISE_SHADER =
-            ShaderUtilLibrary.SHADER_LIB + UNIFORMS + SHADER_LIB + MAIN_SHADER
+        private const val FRACTAL_SHADER =
+            """
+            vec4 main(vec2 p) {
+                vec2 uv = p / in_size.xy;
+                uv.x *= in_aspectRatio;
+
+                vec3 noiseP = vec3(uv + in_noiseMove.xy, in_noiseMove.z) * in_gridNum;
+                float luma = simplex3d_fractal(noiseP) * in_opacity;
+                vec3 mask = maskLuminosity(in_color.rgb, luma);
+                vec3 color = in_backgroundColor.rgb + mask * 0.6;
+
+                // Skip dithering.
+                return vec4(color * in_color.a, in_color.a);
+            }
+        """
+
+        private const val SIMPLEX_NOISE_SHADER =
+            ShaderUtilLibrary.SHADER_LIB + UNIFORMS + SIMPLEX_SHADER
+        private const val FRACTAL_NOISE_SHADER =
+            ShaderUtilLibrary.SHADER_LIB + UNIFORMS + FRACTAL_SHADER
     }
 
     /** Sets the number of grid for generating noise. */
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/swipeable/Swipeable.kt b/packages/SystemUI/compose/core/src/com/android/compose/swipeable/Swipeable.kt
new file mode 100644
index 0000000..946e779
--- /dev/null
+++ b/packages/SystemUI/compose/core/src/com/android/compose/swipeable/Swipeable.kt
@@ -0,0 +1,849 @@
+/*
+ * 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.compose.swipeable
+
+import androidx.compose.animation.core.Animatable
+import androidx.compose.animation.core.AnimationSpec
+import androidx.compose.animation.core.SpringSpec
+import androidx.compose.foundation.gestures.DraggableState
+import androidx.compose.foundation.gestures.Orientation
+import androidx.compose.foundation.gestures.draggable
+import androidx.compose.foundation.interaction.MutableInteractionSource
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.DisposableEffect
+import androidx.compose.runtime.Immutable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.Stable
+import androidx.compose.runtime.State
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.saveable.Saver
+import androidx.compose.runtime.saveable.rememberSaveable
+import androidx.compose.runtime.setValue
+import androidx.compose.runtime.snapshotFlow
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.composed
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
+import androidx.compose.ui.input.nestedscroll.NestedScrollSource
+import androidx.compose.ui.platform.LocalDensity
+import androidx.compose.ui.platform.debugInspectorInfo
+import androidx.compose.ui.unit.Density
+import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.Velocity
+import androidx.compose.ui.unit.dp
+import com.android.compose.swipeable.SwipeableDefaults.AnimationSpec
+import com.android.compose.swipeable.SwipeableDefaults.StandardResistanceFactor
+import com.android.compose.swipeable.SwipeableDefaults.VelocityThreshold
+import com.android.compose.swipeable.SwipeableDefaults.resistanceConfig
+import com.android.compose.ui.util.lerp
+import kotlin.math.PI
+import kotlin.math.abs
+import kotlin.math.sign
+import kotlin.math.sin
+import kotlinx.coroutines.CancellationException
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.filter
+import kotlinx.coroutines.flow.take
+import kotlinx.coroutines.launch
+
+/**
+ * State of the [swipeable] modifier.
+ *
+ * This contains necessary information about any ongoing swipe or animation and provides methods to
+ * change the state either immediately or by starting an animation. To create and remember a
+ * [SwipeableState] with the default animation clock, use [rememberSwipeableState].
+ *
+ * @param initialValue The initial value of the state.
+ * @param animationSpec The default animation that will be used to animate to a new state.
+ * @param confirmStateChange Optional callback invoked to confirm or veto a pending state change.
+ *
+ * TODO(b/272311106): this is a fork from material. Unfork it when Swipeable.kt reaches material3.
+ */
+@Stable
+open class SwipeableState<T>(
+    initialValue: T,
+    internal val animationSpec: AnimationSpec<Float> = AnimationSpec,
+    internal val confirmStateChange: (newValue: T) -> Boolean = { true }
+) {
+    /**
+     * The current value of the state.
+     *
+     * If no swipe or animation is in progress, this corresponds to the anchor at which the
+     * [swipeable] is currently settled. If a swipe or animation is in progress, this corresponds
+     * the last anchor at which the [swipeable] was settled before the swipe or animation started.
+     */
+    var currentValue: T by mutableStateOf(initialValue)
+        private set
+
+    /** Whether the state is currently animating. */
+    var isAnimationRunning: Boolean by mutableStateOf(false)
+        private set
+
+    /**
+     * The current position (in pixels) of the [swipeable].
+     *
+     * You should use this state to offset your content accordingly. The recommended way is to use
+     * `Modifier.offsetPx`. This includes the resistance by default, if resistance is enabled.
+     */
+    val offset: State<Float>
+        get() = offsetState
+
+    /** The amount by which the [swipeable] has been swiped past its bounds. */
+    val overflow: State<Float>
+        get() = overflowState
+
+    // Use `Float.NaN` as a placeholder while the state is uninitialised.
+    private val offsetState = mutableStateOf(0f)
+    private val overflowState = mutableStateOf(0f)
+
+    // the source of truth for the "real"(non ui) position
+    // basically position in bounds + overflow
+    private val absoluteOffset = mutableStateOf(0f)
+
+    // current animation target, if animating, otherwise null
+    private val animationTarget = mutableStateOf<Float?>(null)
+
+    internal var anchors by mutableStateOf(emptyMap<Float, T>())
+
+    private val latestNonEmptyAnchorsFlow: Flow<Map<Float, T>> =
+        snapshotFlow { anchors }.filter { it.isNotEmpty() }.take(1)
+
+    internal var minBound = Float.NEGATIVE_INFINITY
+    internal var maxBound = Float.POSITIVE_INFINITY
+
+    internal fun ensureInit(newAnchors: Map<Float, T>) {
+        if (anchors.isEmpty()) {
+            // need to do initial synchronization synchronously :(
+            val initialOffset = newAnchors.getOffset(currentValue)
+            requireNotNull(initialOffset) { "The initial value must have an associated anchor." }
+            offsetState.value = initialOffset
+            absoluteOffset.value = initialOffset
+        }
+    }
+
+    internal suspend fun processNewAnchors(oldAnchors: Map<Float, T>, newAnchors: Map<Float, T>) {
+        if (oldAnchors.isEmpty()) {
+            // If this is the first time that we receive anchors, then we need to initialise
+            // the state so we snap to the offset associated to the initial value.
+            minBound = newAnchors.keys.minOrNull()!!
+            maxBound = newAnchors.keys.maxOrNull()!!
+            val initialOffset = newAnchors.getOffset(currentValue)
+            requireNotNull(initialOffset) { "The initial value must have an associated anchor." }
+            snapInternalToOffset(initialOffset)
+        } else if (newAnchors != oldAnchors) {
+            // If we have received new anchors, then the offset of the current value might
+            // have changed, so we need to animate to the new offset. If the current value
+            // has been removed from the anchors then we animate to the closest anchor
+            // instead. Note that this stops any ongoing animation.
+            minBound = Float.NEGATIVE_INFINITY
+            maxBound = Float.POSITIVE_INFINITY
+            val animationTargetValue = animationTarget.value
+            // if we're in the animation already, let's find it a new home
+            val targetOffset =
+                if (animationTargetValue != null) {
+                    // first, try to map old state to the new state
+                    val oldState = oldAnchors[animationTargetValue]
+                    val newState = newAnchors.getOffset(oldState)
+                    // return new state if exists, or find the closes one among new anchors
+                    newState ?: newAnchors.keys.minByOrNull { abs(it - animationTargetValue) }!!
+                } else {
+                    // we're not animating, proceed by finding the new anchors for an old value
+                    val actualOldValue = oldAnchors[offset.value]
+                    val value = if (actualOldValue == currentValue) currentValue else actualOldValue
+                    newAnchors.getOffset(value)
+                        ?: newAnchors.keys.minByOrNull { abs(it - offset.value) }!!
+                }
+            try {
+                animateInternalToOffset(targetOffset, animationSpec)
+            } catch (c: CancellationException) {
+                // If the animation was interrupted for any reason, snap as a last resort.
+                snapInternalToOffset(targetOffset)
+            } finally {
+                currentValue = newAnchors.getValue(targetOffset)
+                minBound = newAnchors.keys.minOrNull()!!
+                maxBound = newAnchors.keys.maxOrNull()!!
+            }
+        }
+    }
+
+    internal var thresholds: (Float, Float) -> Float by mutableStateOf({ _, _ -> 0f })
+
+    internal var velocityThreshold by mutableStateOf(0f)
+
+    internal var resistance: ResistanceConfig? by mutableStateOf(null)
+
+    internal val draggableState = DraggableState {
+        val newAbsolute = absoluteOffset.value + it
+        val clamped = newAbsolute.coerceIn(minBound, maxBound)
+        val overflow = newAbsolute - clamped
+        val resistanceDelta = resistance?.computeResistance(overflow) ?: 0f
+        offsetState.value = clamped + resistanceDelta
+        overflowState.value = overflow
+        absoluteOffset.value = newAbsolute
+    }
+
+    private suspend fun snapInternalToOffset(target: Float) {
+        draggableState.drag { dragBy(target - absoluteOffset.value) }
+    }
+
+    private suspend fun animateInternalToOffset(target: Float, spec: AnimationSpec<Float>) {
+        draggableState.drag {
+            var prevValue = absoluteOffset.value
+            animationTarget.value = target
+            isAnimationRunning = true
+            try {
+                Animatable(prevValue).animateTo(target, spec) {
+                    dragBy(this.value - prevValue)
+                    prevValue = this.value
+                }
+            } finally {
+                animationTarget.value = null
+                isAnimationRunning = false
+            }
+        }
+    }
+
+    /**
+     * The target value of the state.
+     *
+     * If a swipe is in progress, this is the value that the [swipeable] would animate to if the
+     * swipe finished. If an animation is running, this is the target value of that animation.
+     * Finally, if no swipe or animation is in progress, this is the same as the [currentValue].
+     */
+    val targetValue: T
+        get() {
+            // TODO(calintat): Track current velocity (b/149549482) and use that here.
+            val target =
+                animationTarget.value
+                    ?: computeTarget(
+                        offset = offset.value,
+                        lastValue = anchors.getOffset(currentValue) ?: offset.value,
+                        anchors = anchors.keys,
+                        thresholds = thresholds,
+                        velocity = 0f,
+                        velocityThreshold = Float.POSITIVE_INFINITY
+                    )
+            return anchors[target] ?: currentValue
+        }
+
+    /**
+     * Information about the ongoing swipe or animation, if any. See [SwipeProgress] for details.
+     *
+     * If no swipe or animation is in progress, this returns `SwipeProgress(value, value, 1f)`.
+     */
+    val progress: SwipeProgress<T>
+        get() {
+            val bounds = findBounds(offset.value, anchors.keys)
+            val from: T
+            val to: T
+            val fraction: Float
+            when (bounds.size) {
+                0 -> {
+                    from = currentValue
+                    to = currentValue
+                    fraction = 1f
+                }
+                1 -> {
+                    from = anchors.getValue(bounds[0])
+                    to = anchors.getValue(bounds[0])
+                    fraction = 1f
+                }
+                else -> {
+                    val (a, b) =
+                        if (direction > 0f) {
+                            bounds[0] to bounds[1]
+                        } else {
+                            bounds[1] to bounds[0]
+                        }
+                    from = anchors.getValue(a)
+                    to = anchors.getValue(b)
+                    fraction = (offset.value - a) / (b - a)
+                }
+            }
+            return SwipeProgress(from, to, fraction)
+        }
+
+    /**
+     * The direction in which the [swipeable] is moving, relative to the current [currentValue].
+     *
+     * This will be either 1f if it is is moving from left to right or top to bottom, -1f if it is
+     * moving from right to left or bottom to top, or 0f if no swipe or animation is in progress.
+     */
+    val direction: Float
+        get() = anchors.getOffset(currentValue)?.let { sign(offset.value - it) } ?: 0f
+
+    /**
+     * Set the state without any animation and suspend until it's set
+     *
+     * @param targetValue The new target value to set [currentValue] to.
+     */
+    suspend fun snapTo(targetValue: T) {
+        latestNonEmptyAnchorsFlow.collect { anchors ->
+            val targetOffset = anchors.getOffset(targetValue)
+            requireNotNull(targetOffset) { "The target value must have an associated anchor." }
+            snapInternalToOffset(targetOffset)
+            currentValue = targetValue
+        }
+    }
+
+    /**
+     * Set the state to the target value by starting an animation.
+     *
+     * @param targetValue The new value to animate to.
+     * @param anim The animation that will be used to animate to the new value.
+     */
+    suspend fun animateTo(targetValue: T, anim: AnimationSpec<Float> = animationSpec) {
+        latestNonEmptyAnchorsFlow.collect { anchors ->
+            try {
+                val targetOffset = anchors.getOffset(targetValue)
+                requireNotNull(targetOffset) { "The target value must have an associated anchor." }
+                animateInternalToOffset(targetOffset, anim)
+            } finally {
+                val endOffset = absoluteOffset.value
+                val endValue =
+                    anchors
+                        // fighting rounding error once again, anchor should be as close as 0.5
+                        // pixels
+                        .filterKeys { anchorOffset -> abs(anchorOffset - endOffset) < 0.5f }
+                        .values
+                        .firstOrNull()
+                        ?: currentValue
+                currentValue = endValue
+            }
+        }
+    }
+
+    /**
+     * Perform fling with settling to one of the anchors which is determined by the given
+     * [velocity]. Fling with settling [swipeable] will always consume all the velocity provided
+     * since it will settle at the anchor.
+     *
+     * In general cases, [swipeable] flings by itself when being swiped. This method is to be used
+     * for nested scroll logic that wraps the [swipeable]. In nested scroll developer may want to
+     * trigger settling fling when the child scroll container reaches the bound.
+     *
+     * @param velocity velocity to fling and settle with
+     * @return the reason fling ended
+     */
+    suspend fun performFling(velocity: Float) {
+        latestNonEmptyAnchorsFlow.collect { anchors ->
+            val lastAnchor = anchors.getOffset(currentValue)!!
+            val targetValue =
+                computeTarget(
+                    offset = offset.value,
+                    lastValue = lastAnchor,
+                    anchors = anchors.keys,
+                    thresholds = thresholds,
+                    velocity = velocity,
+                    velocityThreshold = velocityThreshold
+                )
+            val targetState = anchors[targetValue]
+            if (targetState != null && confirmStateChange(targetState)) animateTo(targetState)
+            // If the user vetoed the state change, rollback to the previous state.
+            else animateInternalToOffset(lastAnchor, animationSpec)
+        }
+    }
+
+    /**
+     * Force [swipeable] to consume drag delta provided from outside of the regular [swipeable]
+     * gesture flow.
+     *
+     * Note: This method performs generic drag and it won't settle to any particular anchor, *
+     * leaving swipeable in between anchors. When done dragging, [performFling] must be called as
+     * well to ensure swipeable will settle at the anchor.
+     *
+     * In general cases, [swipeable] drags by itself when being swiped. This method is to be used
+     * for nested scroll logic that wraps the [swipeable]. In nested scroll developer may want to
+     * force drag when the child scroll container reaches the bound.
+     *
+     * @param delta delta in pixels to drag by
+     * @return the amount of [delta] consumed
+     */
+    fun performDrag(delta: Float): Float {
+        val potentiallyConsumed = absoluteOffset.value + delta
+        val clamped = potentiallyConsumed.coerceIn(minBound, maxBound)
+        val deltaToConsume = clamped - absoluteOffset.value
+        if (abs(deltaToConsume) > 0) {
+            draggableState.dispatchRawDelta(deltaToConsume)
+        }
+        return deltaToConsume
+    }
+
+    companion object {
+        /** The default [Saver] implementation for [SwipeableState]. */
+        fun <T : Any> Saver(
+            animationSpec: AnimationSpec<Float>,
+            confirmStateChange: (T) -> Boolean
+        ) =
+            Saver<SwipeableState<T>, T>(
+                save = { it.currentValue },
+                restore = { SwipeableState(it, animationSpec, confirmStateChange) }
+            )
+    }
+}
+
+/**
+ * Collects information about the ongoing swipe or animation in [swipeable].
+ *
+ * To access this information, use [SwipeableState.progress].
+ *
+ * @param from The state corresponding to the anchor we are moving away from.
+ * @param to The state corresponding to the anchor we are moving towards.
+ * @param fraction The fraction that the current position represents between [from] and [to]. Must
+ *   be between `0` and `1`.
+ */
+@Immutable
+class SwipeProgress<T>(
+    val from: T,
+    val to: T,
+    /*@FloatRange(from = 0.0, to = 1.0)*/
+    val fraction: Float
+) {
+    override fun equals(other: Any?): Boolean {
+        if (this === other) return true
+        if (other !is SwipeProgress<*>) return false
+
+        if (from != other.from) return false
+        if (to != other.to) return false
+        if (fraction != other.fraction) return false
+
+        return true
+    }
+
+    override fun hashCode(): Int {
+        var result = from?.hashCode() ?: 0
+        result = 31 * result + (to?.hashCode() ?: 0)
+        result = 31 * result + fraction.hashCode()
+        return result
+    }
+
+    override fun toString(): String {
+        return "SwipeProgress(from=$from, to=$to, fraction=$fraction)"
+    }
+}
+
+/**
+ * Create and [remember] a [SwipeableState] with the default animation clock.
+ *
+ * @param initialValue The initial value of the state.
+ * @param animationSpec The default animation that will be used to animate to a new state.
+ * @param confirmStateChange Optional callback invoked to confirm or veto a pending state change.
+ */
+@Composable
+fun <T : Any> rememberSwipeableState(
+    initialValue: T,
+    animationSpec: AnimationSpec<Float> = AnimationSpec,
+    confirmStateChange: (newValue: T) -> Boolean = { true }
+): SwipeableState<T> {
+    return rememberSaveable(
+        saver =
+            SwipeableState.Saver(
+                animationSpec = animationSpec,
+                confirmStateChange = confirmStateChange
+            )
+    ) {
+        SwipeableState(
+            initialValue = initialValue,
+            animationSpec = animationSpec,
+            confirmStateChange = confirmStateChange
+        )
+    }
+}
+
+/**
+ * Create and [remember] a [SwipeableState] which is kept in sync with another state, i.e.:
+ * 1. Whenever the [value] changes, the [SwipeableState] will be animated to that new value.
+ * 2. Whenever the value of the [SwipeableState] changes (e.g. after a swipe), the owner of the
+ *    [value] will be notified to update their state to the new value of the [SwipeableState] by
+ *    invoking [onValueChange]. If the owner does not update their state to the provided value for
+ *    some reason, then the [SwipeableState] will perform a rollback to the previous, correct value.
+ */
+@Composable
+internal fun <T : Any> rememberSwipeableStateFor(
+    value: T,
+    onValueChange: (T) -> Unit,
+    animationSpec: AnimationSpec<Float> = AnimationSpec
+): SwipeableState<T> {
+    val swipeableState = remember {
+        SwipeableState(
+            initialValue = value,
+            animationSpec = animationSpec,
+            confirmStateChange = { true }
+        )
+    }
+    val forceAnimationCheck = remember { mutableStateOf(false) }
+    LaunchedEffect(value, forceAnimationCheck.value) {
+        if (value != swipeableState.currentValue) {
+            swipeableState.animateTo(value)
+        }
+    }
+    DisposableEffect(swipeableState.currentValue) {
+        if (value != swipeableState.currentValue) {
+            onValueChange(swipeableState.currentValue)
+            forceAnimationCheck.value = !forceAnimationCheck.value
+        }
+        onDispose {}
+    }
+    return swipeableState
+}
+
+/**
+ * Enable swipe gestures between a set of predefined states.
+ *
+ * To use this, you must provide a map of anchors (in pixels) to states (of type [T]). Note that
+ * this map cannot be empty and cannot have two anchors mapped to the same state.
+ *
+ * When a swipe is detected, the offset of the [SwipeableState] will be updated with the swipe
+ * delta. You should use this offset to move your content accordingly (see `Modifier.offsetPx`).
+ * When the swipe ends, the offset will be animated to one of the anchors and when that anchor is
+ * reached, the value of the [SwipeableState] will also be updated to the state corresponding to the
+ * new anchor. The target anchor is calculated based on the provided positional [thresholds].
+ *
+ * Swiping is constrained between the minimum and maximum anchors. If the user attempts to swipe
+ * past these bounds, a resistance effect will be applied by default. The amount of resistance at
+ * each edge is specified by the [resistance] config. To disable all resistance, set it to `null`.
+ *
+ * For an example of a [swipeable] with three states, see:
+ *
+ * @param T The type of the state.
+ * @param state The state of the [swipeable].
+ * @param anchors Pairs of anchors and states, used to map anchors to states and vice versa.
+ * @param thresholds Specifies where the thresholds between the states are. The thresholds will be
+ *   used to determine which state to animate to when swiping stops. This is represented as a lambda
+ *   that takes two states and returns the threshold between them in the form of a
+ *   [ThresholdConfig]. Note that the order of the states corresponds to the swipe direction.
+ * @param orientation The orientation in which the [swipeable] can be swiped.
+ * @param enabled Whether this [swipeable] is enabled and should react to the user's input.
+ * @param reverseDirection Whether to reverse the direction of the swipe, so a top to bottom swipe
+ *   will behave like bottom to top, and a left to right swipe will behave like right to left.
+ * @param interactionSource Optional [MutableInteractionSource] that will passed on to the internal
+ *   [Modifier.draggable].
+ * @param resistance Controls how much resistance will be applied when swiping past the bounds.
+ * @param velocityThreshold The threshold (in dp per second) that the end velocity has to exceed in
+ *   order to animate to the next state, even if the positional [thresholds] have not been reached.
+ * @sample androidx.compose.material.samples.SwipeableSample
+ */
+fun <T> Modifier.swipeable(
+    state: SwipeableState<T>,
+    anchors: Map<Float, T>,
+    orientation: Orientation,
+    enabled: Boolean = true,
+    reverseDirection: Boolean = false,
+    interactionSource: MutableInteractionSource? = null,
+    thresholds: (from: T, to: T) -> ThresholdConfig = { _, _ -> FixedThreshold(56.dp) },
+    resistance: ResistanceConfig? = resistanceConfig(anchors.keys),
+    velocityThreshold: Dp = VelocityThreshold
+) =
+    composed(
+        inspectorInfo =
+            debugInspectorInfo {
+                name = "swipeable"
+                properties["state"] = state
+                properties["anchors"] = anchors
+                properties["orientation"] = orientation
+                properties["enabled"] = enabled
+                properties["reverseDirection"] = reverseDirection
+                properties["interactionSource"] = interactionSource
+                properties["thresholds"] = thresholds
+                properties["resistance"] = resistance
+                properties["velocityThreshold"] = velocityThreshold
+            }
+    ) {
+        require(anchors.isNotEmpty()) { "You must have at least one anchor." }
+        require(anchors.values.distinct().count() == anchors.size) {
+            "You cannot have two anchors mapped to the same state."
+        }
+        val density = LocalDensity.current
+        state.ensureInit(anchors)
+        LaunchedEffect(anchors, state) {
+            val oldAnchors = state.anchors
+            state.anchors = anchors
+            state.resistance = resistance
+            state.thresholds = { a, b ->
+                val from = anchors.getValue(a)
+                val to = anchors.getValue(b)
+                with(thresholds(from, to)) { density.computeThreshold(a, b) }
+            }
+            with(density) { state.velocityThreshold = velocityThreshold.toPx() }
+            state.processNewAnchors(oldAnchors, anchors)
+        }
+
+        Modifier.draggable(
+            orientation = orientation,
+            enabled = enabled,
+            reverseDirection = reverseDirection,
+            interactionSource = interactionSource,
+            startDragImmediately = state.isAnimationRunning,
+            onDragStopped = { velocity -> launch { state.performFling(velocity) } },
+            state = state.draggableState
+        )
+    }
+
+/**
+ * Interface to compute a threshold between two anchors/states in a [swipeable].
+ *
+ * To define a [ThresholdConfig], consider using [FixedThreshold] and [FractionalThreshold].
+ */
+@Stable
+interface ThresholdConfig {
+    /** Compute the value of the threshold (in pixels), once the values of the anchors are known. */
+    fun Density.computeThreshold(fromValue: Float, toValue: Float): Float
+}
+
+/**
+ * A fixed threshold will be at an [offset] away from the first anchor.
+ *
+ * @param offset The offset (in dp) that the threshold will be at.
+ */
+@Immutable
+data class FixedThreshold(private val offset: Dp) : ThresholdConfig {
+    override fun Density.computeThreshold(fromValue: Float, toValue: Float): Float {
+        return fromValue + offset.toPx() * sign(toValue - fromValue)
+    }
+}
+
+/**
+ * A fractional threshold will be at a [fraction] of the way between the two anchors.
+ *
+ * @param fraction The fraction (between 0 and 1) that the threshold will be at.
+ */
+@Immutable
+data class FractionalThreshold(
+    /*@FloatRange(from = 0.0, to = 1.0)*/
+    private val fraction: Float
+) : ThresholdConfig {
+    override fun Density.computeThreshold(fromValue: Float, toValue: Float): Float {
+        return lerp(fromValue, toValue, fraction)
+    }
+}
+
+/**
+ * Specifies how resistance is calculated in [swipeable].
+ *
+ * There are two things needed to calculate resistance: the resistance basis determines how much
+ * overflow will be consumed to achieve maximum resistance, and the resistance factor determines the
+ * amount of resistance (the larger the resistance factor, the stronger the resistance).
+ *
+ * The resistance basis is usually either the size of the component which [swipeable] is applied to,
+ * or the distance between the minimum and maximum anchors. For a constructor in which the
+ * resistance basis defaults to the latter, consider using [resistanceConfig].
+ *
+ * You may specify different resistance factors for each bound. Consider using one of the default
+ * resistance factors in [SwipeableDefaults]: `StandardResistanceFactor` to convey that the user has
+ * run out of things to see, and `StiffResistanceFactor` to convey that the user cannot swipe this
+ * right now. Also, you can set either factor to 0 to disable resistance at that bound.
+ *
+ * @param basis Specifies the maximum amount of overflow that will be consumed. Must be positive.
+ * @param factorAtMin The factor by which to scale the resistance at the minimum bound. Must not be
+ *   negative.
+ * @param factorAtMax The factor by which to scale the resistance at the maximum bound. Must not be
+ *   negative.
+ */
+@Immutable
+class ResistanceConfig(
+    /*@FloatRange(from = 0.0, fromInclusive = false)*/
+    val basis: Float,
+    /*@FloatRange(from = 0.0)*/
+    val factorAtMin: Float = StandardResistanceFactor,
+    /*@FloatRange(from = 0.0)*/
+    val factorAtMax: Float = StandardResistanceFactor
+) {
+    fun computeResistance(overflow: Float): Float {
+        val factor = if (overflow < 0) factorAtMin else factorAtMax
+        if (factor == 0f) return 0f
+        val progress = (overflow / basis).coerceIn(-1f, 1f)
+        return basis / factor * sin(progress * PI.toFloat() / 2)
+    }
+
+    override fun equals(other: Any?): Boolean {
+        if (this === other) return true
+        if (other !is ResistanceConfig) return false
+
+        if (basis != other.basis) return false
+        if (factorAtMin != other.factorAtMin) return false
+        if (factorAtMax != other.factorAtMax) return false
+
+        return true
+    }
+
+    override fun hashCode(): Int {
+        var result = basis.hashCode()
+        result = 31 * result + factorAtMin.hashCode()
+        result = 31 * result + factorAtMax.hashCode()
+        return result
+    }
+
+    override fun toString(): String {
+        return "ResistanceConfig(basis=$basis, factorAtMin=$factorAtMin, factorAtMax=$factorAtMax)"
+    }
+}
+
+/**
+ * Given an offset x and a set of anchors, return a list of anchors:
+ * 1. [ ] if the set of anchors is empty,
+ * 2. [ x' ] if x is equal to one of the anchors, accounting for a small rounding error, where x' is
+ *    x rounded to the exact value of the matching anchor,
+ * 3. [ min ] if min is the minimum anchor and x < min,
+ * 4. [ max ] if max is the maximum anchor and x > max, or
+ * 5. [ a , b ] if a and b are anchors such that a < x < b and b - a is minimal.
+ */
+private fun findBounds(offset: Float, anchors: Set<Float>): List<Float> {
+    // Find the anchors the target lies between with a little bit of rounding error.
+    val a = anchors.filter { it <= offset + 0.001 }.maxOrNull()
+    val b = anchors.filter { it >= offset - 0.001 }.minOrNull()
+
+    return when {
+        a == null ->
+            // case 1 or 3
+            listOfNotNull(b)
+        b == null ->
+            // case 4
+            listOf(a)
+        a == b ->
+            // case 2
+            // Can't return offset itself here since it might not be exactly equal
+            // to the anchor, despite being considered an exact match.
+            listOf(a)
+        else ->
+            // case 5
+            listOf(a, b)
+    }
+}
+
+private fun computeTarget(
+    offset: Float,
+    lastValue: Float,
+    anchors: Set<Float>,
+    thresholds: (Float, Float) -> Float,
+    velocity: Float,
+    velocityThreshold: Float
+): Float {
+    val bounds = findBounds(offset, anchors)
+    return when (bounds.size) {
+        0 -> lastValue
+        1 -> bounds[0]
+        else -> {
+            val lower = bounds[0]
+            val upper = bounds[1]
+            if (lastValue <= offset) {
+                // Swiping from lower to upper (positive).
+                if (velocity >= velocityThreshold) {
+                    return upper
+                } else {
+                    val threshold = thresholds(lower, upper)
+                    if (offset < threshold) lower else upper
+                }
+            } else {
+                // Swiping from upper to lower (negative).
+                if (velocity <= -velocityThreshold) {
+                    return lower
+                } else {
+                    val threshold = thresholds(upper, lower)
+                    if (offset > threshold) upper else lower
+                }
+            }
+        }
+    }
+}
+
+private fun <T> Map<Float, T>.getOffset(state: T): Float? {
+    return entries.firstOrNull { it.value == state }?.key
+}
+
+/** Contains useful defaults for [swipeable] and [SwipeableState]. */
+object SwipeableDefaults {
+    /** The default animation used by [SwipeableState]. */
+    val AnimationSpec = SpringSpec<Float>()
+
+    /** The default velocity threshold (1.8 dp per millisecond) used by [swipeable]. */
+    val VelocityThreshold = 125.dp
+
+    /** A stiff resistance factor which indicates that swiping isn't available right now. */
+    const val StiffResistanceFactor = 20f
+
+    /** A standard resistance factor which indicates that the user has run out of things to see. */
+    const val StandardResistanceFactor = 10f
+
+    /**
+     * The default resistance config used by [swipeable].
+     *
+     * This returns `null` if there is one anchor. If there are at least two anchors, it returns a
+     * [ResistanceConfig] with the resistance basis equal to the distance between the two bounds.
+     */
+    fun resistanceConfig(
+        anchors: Set<Float>,
+        factorAtMin: Float = StandardResistanceFactor,
+        factorAtMax: Float = StandardResistanceFactor
+    ): ResistanceConfig? {
+        return if (anchors.size <= 1) {
+            null
+        } else {
+            val basis = anchors.maxOrNull()!! - anchors.minOrNull()!!
+            ResistanceConfig(basis, factorAtMin, factorAtMax)
+        }
+    }
+}
+
+// temp default nested scroll connection for swipeables which desire as an opt in
+// revisit in b/174756744 as all types will have their own specific connection probably
+internal val <T> SwipeableState<T>.PreUpPostDownNestedScrollConnection: NestedScrollConnection
+    get() =
+        object : NestedScrollConnection {
+            override fun onPreScroll(available: Offset, source: NestedScrollSource): Offset {
+                val delta = available.toFloat()
+                return if (delta < 0 && source == NestedScrollSource.Drag) {
+                    performDrag(delta).toOffset()
+                } else {
+                    Offset.Zero
+                }
+            }
+
+            override fun onPostScroll(
+                consumed: Offset,
+                available: Offset,
+                source: NestedScrollSource
+            ): Offset {
+                return if (source == NestedScrollSource.Drag) {
+                    performDrag(available.toFloat()).toOffset()
+                } else {
+                    Offset.Zero
+                }
+            }
+
+            override suspend fun onPreFling(available: Velocity): Velocity {
+                val toFling = Offset(available.x, available.y).toFloat()
+                return if (toFling < 0 && offset.value > minBound) {
+                    performFling(velocity = toFling)
+                    // since we go to the anchor with tween settling, consume all for the best UX
+                    available
+                } else {
+                    Velocity.Zero
+                }
+            }
+
+            override suspend fun onPostFling(consumed: Velocity, available: Velocity): Velocity {
+                performFling(velocity = Offset(available.x, available.y).toFloat())
+                return available
+            }
+
+            private fun Float.toOffset(): Offset = Offset(0f, this)
+
+            private fun Offset.toFloat(): Float = this.y
+        }
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/ui/util/MathHelpers.kt b/packages/SystemUI/compose/core/src/com/android/compose/ui/util/MathHelpers.kt
new file mode 100644
index 0000000..c1defb7
--- /dev/null
+++ b/packages/SystemUI/compose/core/src/com/android/compose/ui/util/MathHelpers.kt
@@ -0,0 +1,38 @@
+/*
+ * 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.compose.ui.util
+
+import kotlin.math.roundToInt
+import kotlin.math.roundToLong
+
+// TODO(b/272311106): this is a fork from material. Unfork it when MathHelpers.kt reaches material3.
+
+/** Linearly interpolate between [start] and [stop] with [fraction] fraction between them. */
+fun lerp(start: Float, stop: Float, fraction: Float): Float {
+    return (1 - fraction) * start + fraction * stop
+}
+
+/** Linearly interpolate between [start] and [stop] with [fraction] fraction between them. */
+fun lerp(start: Int, stop: Int, fraction: Float): Int {
+    return start + ((stop - start) * fraction.toDouble()).roundToInt()
+}
+
+/** Linearly interpolate between [start] and [stop] with [fraction] fraction between them. */
+fun lerp(start: Long, stop: Long, fraction: Float): Long {
+    return start + ((stop - start) * fraction.toDouble()).roundToLong()
+}
diff --git a/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/compose/ComposeFacade.kt b/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/compose/ComposeFacade.kt
index e253fb9..cc33745 100644
--- a/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/compose/ComposeFacade.kt
+++ b/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/compose/ComposeFacade.kt
@@ -21,8 +21,10 @@
 import android.view.View
 import androidx.activity.ComponentActivity
 import androidx.lifecycle.LifecycleOwner
+import com.android.systemui.multishade.ui.viewmodel.MultiShadeViewModel
 import com.android.systemui.people.ui.viewmodel.PeopleViewModel
 import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel
+import com.android.systemui.util.time.SystemClock
 
 /** The Compose facade, when Compose is *not* available. */
 object ComposeFacade : BaseComposeFacade {
@@ -48,6 +50,14 @@
         throwComposeUnavailableError()
     }
 
+    override fun createMultiShadeView(
+        context: Context,
+        viewModel: MultiShadeViewModel,
+        clock: SystemClock,
+    ): View {
+        throwComposeUnavailableError()
+    }
+
     private fun throwComposeUnavailableError(): Nothing {
         error(
             "Compose is not available. Make sure to check isComposeAvailable() before calling any" +
diff --git a/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/compose/ComposeFacade.kt b/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/compose/ComposeFacade.kt
index 1ea18fe..0e79b18 100644
--- a/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/compose/ComposeFacade.kt
+++ b/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/compose/ComposeFacade.kt
@@ -23,10 +23,13 @@
 import androidx.compose.ui.platform.ComposeView
 import androidx.lifecycle.LifecycleOwner
 import com.android.compose.theme.PlatformTheme
+import com.android.systemui.multishade.ui.composable.MultiShade
+import com.android.systemui.multishade.ui.viewmodel.MultiShadeViewModel
 import com.android.systemui.people.ui.compose.PeopleScreen
 import com.android.systemui.people.ui.viewmodel.PeopleViewModel
 import com.android.systemui.qs.footer.ui.compose.FooterActions
 import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel
+import com.android.systemui.util.time.SystemClock
 
 /** The Compose facade, when Compose is available. */
 object ComposeFacade : BaseComposeFacade {
@@ -51,4 +54,21 @@
             setContent { PlatformTheme { FooterActions(viewModel, qsVisibilityLifecycleOwner) } }
         }
     }
+
+    override fun createMultiShadeView(
+        context: Context,
+        viewModel: MultiShadeViewModel,
+        clock: SystemClock,
+    ): View {
+        return ComposeView(context).apply {
+            setContent {
+                PlatformTheme {
+                    MultiShade(
+                        viewModel = viewModel,
+                        clock = clock,
+                    )
+                }
+            }
+        }
+    }
 }
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/multishade/ui/composable/MultiShade.kt b/packages/SystemUI/compose/features/src/com/android/systemui/multishade/ui/composable/MultiShade.kt
new file mode 100644
index 0000000..b9e38cf
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/multishade/ui/composable/MultiShade.kt
@@ -0,0 +1,144 @@
+/*
+ * 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.multishade.ui.composable
+
+import androidx.compose.foundation.background
+import androidx.compose.foundation.gestures.detectTapGestures
+import androidx.compose.foundation.gestures.detectVerticalDragGestures
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.BoxWithConstraints
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.collectAsState
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.graphicsLayer
+import androidx.compose.ui.input.pointer.pointerInput
+import androidx.compose.ui.layout.onSizeChanged
+import androidx.compose.ui.platform.LocalDensity
+import androidx.compose.ui.res.colorResource
+import androidx.compose.ui.unit.IntSize
+import com.android.systemui.R
+import com.android.systemui.multishade.shared.model.ProxiedInputModel
+import com.android.systemui.multishade.ui.viewmodel.MultiShadeViewModel
+import com.android.systemui.notifications.ui.composable.Notifications
+import com.android.systemui.qs.footer.ui.compose.QuickSettings
+import com.android.systemui.statusbar.ui.composable.StatusBar
+import com.android.systemui.util.time.SystemClock
+
+@Composable
+fun MultiShade(
+    viewModel: MultiShadeViewModel,
+    clock: SystemClock,
+    modifier: Modifier = Modifier,
+) {
+    val isScrimEnabled: Boolean by viewModel.isScrimEnabled.collectAsState()
+
+    // TODO(b/273298030): find a different way to get the height constraint from its parent.
+    BoxWithConstraints(modifier = modifier) {
+        val maxHeightPx = with(LocalDensity.current) { maxHeight.toPx() }
+
+        Scrim(
+            modifier = Modifier.fillMaxSize(),
+            remoteTouch = viewModel::onScrimTouched,
+            alpha = { viewModel.scrimAlpha.value },
+            isScrimEnabled = isScrimEnabled,
+        )
+        Shade(
+            viewModel = viewModel.leftShade,
+            currentTimeMillis = clock::elapsedRealtime,
+            containerHeightPx = maxHeightPx,
+            modifier = Modifier.align(Alignment.TopStart),
+        ) {
+            Column {
+                StatusBar()
+                Notifications()
+            }
+        }
+        Shade(
+            viewModel = viewModel.rightShade,
+            currentTimeMillis = clock::elapsedRealtime,
+            containerHeightPx = maxHeightPx,
+            modifier = Modifier.align(Alignment.TopEnd),
+        ) {
+            Column {
+                StatusBar()
+                QuickSettings()
+            }
+        }
+        Shade(
+            viewModel = viewModel.singleShade,
+            currentTimeMillis = clock::elapsedRealtime,
+            containerHeightPx = maxHeightPx,
+            modifier = Modifier,
+        ) {
+            Column {
+                StatusBar()
+                Notifications()
+                QuickSettings()
+            }
+        }
+    }
+}
+
+@Composable
+private fun Scrim(
+    remoteTouch: (ProxiedInputModel) -> Unit,
+    alpha: () -> Float,
+    isScrimEnabled: Boolean,
+    modifier: Modifier = Modifier,
+) {
+    var size by remember { mutableStateOf(IntSize.Zero) }
+
+    Box(
+        modifier =
+            modifier
+                .graphicsLayer { this.alpha = alpha() }
+                .background(colorResource(R.color.opaque_scrim))
+                .fillMaxSize()
+                .onSizeChanged { size = it }
+                .then(
+                    if (isScrimEnabled) {
+                        Modifier.pointerInput(Unit) {
+                                detectTapGestures(onTap = { remoteTouch(ProxiedInputModel.OnTap) })
+                            }
+                            .pointerInput(Unit) {
+                                detectVerticalDragGestures(
+                                    onVerticalDrag = { change, dragAmount ->
+                                        remoteTouch(
+                                            ProxiedInputModel.OnDrag(
+                                                xFraction = change.position.x / size.width,
+                                                yDragAmountPx = dragAmount,
+                                            )
+                                        )
+                                    },
+                                    onDragEnd = { remoteTouch(ProxiedInputModel.OnDragEnd) },
+                                    onDragCancel = { remoteTouch(ProxiedInputModel.OnDragCancel) }
+                                )
+                            }
+                    } else {
+                        Modifier
+                    }
+                )
+    )
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/multishade/ui/composable/Shade.kt b/packages/SystemUI/compose/features/src/com/android/systemui/multishade/ui/composable/Shade.kt
new file mode 100644
index 0000000..98ef57f9
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/multishade/ui/composable/Shade.kt
@@ -0,0 +1,317 @@
+/*
+ * 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.multishade.ui.composable
+
+import androidx.compose.foundation.gestures.Orientation
+import androidx.compose.foundation.interaction.DragInteraction
+import androidx.compose.foundation.interaction.InteractionSource
+import androidx.compose.foundation.interaction.MutableInteractionSource
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.width
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.material3.Surface
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.collectAsState
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.snapshotFlow
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.graphics.graphicsLayer
+import androidx.compose.ui.input.pointer.util.VelocityTracker
+import androidx.compose.ui.platform.LocalDensity
+import androidx.compose.ui.unit.Density
+import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.dp
+import com.android.compose.modifiers.height
+import com.android.compose.swipeable.FixedThreshold
+import com.android.compose.swipeable.SwipeableState
+import com.android.compose.swipeable.ThresholdConfig
+import com.android.compose.swipeable.rememberSwipeableState
+import com.android.compose.swipeable.swipeable
+import com.android.systemui.multishade.shared.model.ProxiedInputModel
+import com.android.systemui.multishade.ui.viewmodel.ShadeViewModel
+import kotlin.math.roundToInt
+import kotlinx.coroutines.launch
+
+/**
+ * Renders a shade (container and content).
+ *
+ * This should be allowed to grow to fill the width and height of its container.
+ *
+ * @param viewModel The view-model for this shade.
+ * @param currentTimeMillis A provider for the current time, in milliseconds.
+ * @param containerHeightPx The height of the container that this shade is being shown in, in
+ *   pixels.
+ * @param modifier The Modifier.
+ * @param content The content of the shade.
+ */
+@Composable
+fun Shade(
+    viewModel: ShadeViewModel,
+    currentTimeMillis: () -> Long,
+    containerHeightPx: Float,
+    modifier: Modifier = Modifier,
+    content: @Composable () -> Unit = {},
+) {
+    val isVisible: Boolean by viewModel.isVisible.collectAsState()
+    if (!isVisible) {
+        return
+    }
+
+    val interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }
+    ReportNonProxiedInput(viewModel, interactionSource)
+
+    val swipeableState = rememberSwipeableState(initialValue = ShadeState.FullyCollapsed)
+    HandleForcedCollapse(viewModel, swipeableState)
+    HandleProxiedInput(viewModel, swipeableState, currentTimeMillis)
+    ReportShadeExpansion(viewModel, swipeableState, containerHeightPx)
+
+    val isSwipingEnabled: Boolean by viewModel.isSwipingEnabled.collectAsState()
+    val collapseThreshold: Float by viewModel.swipeCollapseThreshold.collectAsState()
+    val expandThreshold: Float by viewModel.swipeExpandThreshold.collectAsState()
+
+    val width: ShadeViewModel.Size by viewModel.width.collectAsState()
+    val density = LocalDensity.current
+
+    val anchors: Map<Float, ShadeState> =
+        remember(containerHeightPx) { swipeableAnchors(containerHeightPx) }
+
+    ShadeContent(
+        shadeHeightPx = { swipeableState.offset.value },
+        overstretch = { swipeableState.overflow.value / containerHeightPx },
+        isSwipingEnabled = isSwipingEnabled,
+        swipeableState = swipeableState,
+        interactionSource = interactionSource,
+        anchors = anchors,
+        thresholds = { _, to ->
+            swipeableThresholds(
+                to = to,
+                swipeCollapseThreshold = collapseThreshold.fractionToDp(density, containerHeightPx),
+                swipeExpandThreshold = expandThreshold.fractionToDp(density, containerHeightPx),
+            )
+        },
+        modifier = modifier.shadeWidth(width, density),
+        content = content,
+    )
+}
+
+/**
+ * Draws the content of the shade.
+ *
+ * @param shadeHeightPx Provider for the current expansion of the shade, in pixels, where `0` is
+ *   fully collapsed.
+ * @param overstretch Provider for the current amount of vertical "overstretch" that the shade
+ *   should be rendered with. This is `0` or a positive number that is a percentage of the total
+ *   height of the shade when fully expanded. A value of `0` means that the shade is not stretched
+ *   at all.
+ * @param isSwipingEnabled Whether swiping inside the shade is enabled or not.
+ * @param swipeableState The state to use for the [swipeable] modifier, allowing external control in
+ *   addition to direct control (proxied user input in addition to non-proxied/direct user input).
+ * @param anchors A map of [ShadeState] keyed by the vertical position, in pixels, where that state
+ *   occurs; this is used to configure the [swipeable] modifier.
+ * @param thresholds Function that returns the [ThresholdConfig] for going from one [ShadeState] to
+ *   another. This controls how the [swipeable] decides which [ShadeState] to animate to once the
+ *   user lets go of the shade; e.g. does it animate to fully collapsed or fully expanded.
+ * @param content The content to render inside the shade.
+ * @param modifier The [Modifier].
+ */
+@Composable
+private fun ShadeContent(
+    shadeHeightPx: () -> Float,
+    overstretch: () -> Float,
+    isSwipingEnabled: Boolean,
+    swipeableState: SwipeableState<ShadeState>,
+    interactionSource: MutableInteractionSource,
+    anchors: Map<Float, ShadeState>,
+    thresholds: (from: ShadeState, to: ShadeState) -> ThresholdConfig,
+    modifier: Modifier = Modifier,
+    content: @Composable () -> Unit = {},
+) {
+    Surface(
+        shape = RoundedCornerShape(32.dp),
+        modifier =
+            modifier
+                .padding(12.dp)
+                .fillMaxWidth()
+                .height { shadeHeightPx().roundToInt() }
+                .graphicsLayer {
+                    // Applies the vertical over-stretching of the shade content that may happen if
+                    // the user keep dragging down when the shade is already fully-expanded.
+                    transformOrigin = transformOrigin.copy(pivotFractionY = 0f)
+                    this.scaleY = 1 + overstretch().coerceAtLeast(0f)
+                }
+                .swipeable(
+                    enabled = isSwipingEnabled,
+                    state = swipeableState,
+                    interactionSource = interactionSource,
+                    anchors = anchors,
+                    thresholds = thresholds,
+                    orientation = Orientation.Vertical,
+                ),
+        content = content,
+    )
+}
+
+/** Funnels current shade expansion values into the view-model. */
+@Composable
+private fun ReportShadeExpansion(
+    viewModel: ShadeViewModel,
+    swipeableState: SwipeableState<ShadeState>,
+    containerHeightPx: Float,
+) {
+    LaunchedEffect(swipeableState.offset, containerHeightPx) {
+        snapshotFlow { swipeableState.offset.value / containerHeightPx }
+            .collect { expansion -> viewModel.onExpansionChanged(expansion) }
+    }
+}
+
+/** Funnels drag gesture start and end events into the view-model. */
+@Composable
+private fun ReportNonProxiedInput(
+    viewModel: ShadeViewModel,
+    interactionSource: InteractionSource,
+) {
+    LaunchedEffect(interactionSource) {
+        interactionSource.interactions.collect {
+            when (it) {
+                is DragInteraction.Start -> {
+                    viewModel.onDragStarted()
+                }
+                is DragInteraction.Stop -> {
+                    viewModel.onDragEnded()
+                }
+            }
+        }
+    }
+}
+
+/** When told to force collapse, collapses the shade. */
+@Composable
+private fun HandleForcedCollapse(
+    viewModel: ShadeViewModel,
+    swipeableState: SwipeableState<ShadeState>,
+) {
+    LaunchedEffect(viewModel) {
+        viewModel.isForceCollapsed.collect {
+            launch { swipeableState.animateTo(ShadeState.FullyCollapsed) }
+        }
+    }
+}
+
+/**
+ * Handles proxied input (input originating outside of the UI of the shade) by driving the
+ * [SwipeableState] accordingly.
+ */
+@Composable
+private fun HandleProxiedInput(
+    viewModel: ShadeViewModel,
+    swipeableState: SwipeableState<ShadeState>,
+    currentTimeMillis: () -> Long,
+) {
+    val velocityTracker: VelocityTracker = remember { VelocityTracker() }
+    LaunchedEffect(viewModel) {
+        viewModel.proxiedInput.collect {
+            when (it) {
+                is ProxiedInputModel.OnDrag -> {
+                    velocityTracker.addPosition(
+                        timeMillis = currentTimeMillis.invoke(),
+                        position = Offset(0f, it.yDragAmountPx),
+                    )
+                    swipeableState.performDrag(it.yDragAmountPx)
+                }
+                is ProxiedInputModel.OnDragEnd -> {
+                    launch {
+                        val velocity = velocityTracker.calculateVelocity().y
+                        velocityTracker.resetTracking()
+                        // We use a VelocityTracker to keep a record of how fast the pointer was
+                        // moving such that we know how far to fling the shade when the gesture
+                        // ends. Flinging the SwipeableState using performFling is required after
+                        // one or more calls to performDrag such that the swipeable settles into one
+                        // of the states. Without doing that, the shade would remain unmoving in an
+                        // in-between state on the screen.
+                        swipeableState.performFling(velocity)
+                    }
+                }
+                is ProxiedInputModel.OnDragCancel -> {
+                    launch {
+                        velocityTracker.resetTracking()
+                        swipeableState.animateTo(swipeableState.progress.from)
+                    }
+                }
+                else -> Unit
+            }
+        }
+    }
+}
+
+/**
+ * Converts the [Float] (which is assumed to be a fraction between `0` and `1`) to a value in dp.
+ *
+ * @param density The [Density] of the display.
+ * @param wholePx The whole amount that the given [Float] is a fraction of.
+ * @return The dp size that's a fraction of the whole amount.
+ */
+private fun Float.fractionToDp(density: Density, wholePx: Float): Dp {
+    return with(density) { (this@fractionToDp * wholePx).toDp() }
+}
+
+private fun Modifier.shadeWidth(
+    size: ShadeViewModel.Size,
+    density: Density,
+): Modifier {
+    return then(
+        when (size) {
+            is ShadeViewModel.Size.Fraction -> Modifier.fillMaxWidth(size.fraction)
+            is ShadeViewModel.Size.Pixels -> Modifier.width(with(density) { size.pixels.toDp() })
+        }
+    )
+}
+
+/** Returns the pixel positions for each of the supported shade states. */
+private fun swipeableAnchors(containerHeightPx: Float): Map<Float, ShadeState> {
+    return mapOf(
+        0f to ShadeState.FullyCollapsed,
+        containerHeightPx to ShadeState.FullyExpanded,
+    )
+}
+
+/**
+ * Returns the [ThresholdConfig] for how far the shade should be expanded or collapsed such that it
+ * actually completes the expansion or collapse after the user lifts their pointer.
+ */
+private fun swipeableThresholds(
+    to: ShadeState,
+    swipeExpandThreshold: Dp,
+    swipeCollapseThreshold: Dp,
+): ThresholdConfig {
+    return FixedThreshold(
+        when (to) {
+            ShadeState.FullyExpanded -> swipeExpandThreshold
+            ShadeState.FullyCollapsed -> swipeCollapseThreshold
+        }
+    )
+}
+
+/** Enumerates the shade UI states for [SwipeableState]. */
+private enum class ShadeState {
+    FullyCollapsed,
+    FullyExpanded,
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt
new file mode 100644
index 0000000..ca91b8a
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.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.notifications.ui.composable
+
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.defaultMinSize
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.unit.dp
+
+@Composable
+fun Notifications(
+    modifier: Modifier = Modifier,
+) {
+    // TODO(b/272779828): implement.
+    Column(
+        modifier = modifier.fillMaxWidth().defaultMinSize(minHeight = 300.dp).padding(4.dp),
+    ) {
+        Text("Notifications", modifier = Modifier.align(Alignment.CenterHorizontally))
+        Spacer(modifier = Modifier.weight(1f))
+        Text("Shelf", modifier = Modifier.align(Alignment.CenterHorizontally))
+    }
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/qs/footer/ui/compose/QuickSettings.kt b/packages/SystemUI/compose/features/src/com/android/systemui/qs/footer/ui/compose/QuickSettings.kt
new file mode 100644
index 0000000..665d6dd
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/qs/footer/ui/compose/QuickSettings.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.qs.footer.ui.compose
+
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.defaultMinSize
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.unit.dp
+
+@Composable
+fun QuickSettings(
+    modifier: Modifier = Modifier,
+) {
+    // TODO(b/272780058): implement.
+    Column(
+        modifier = modifier.fillMaxWidth().defaultMinSize(minHeight = 300.dp).padding(4.dp),
+    ) {
+        Text("Quick settings", modifier = Modifier.align(Alignment.CenterHorizontally))
+        Spacer(modifier = Modifier.weight(1f))
+        Text("QS footer actions", modifier = Modifier.align(Alignment.CenterHorizontally))
+    }
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/statusbar/ui/composable/StatusBar.kt b/packages/SystemUI/compose/features/src/com/android/systemui/statusbar/ui/composable/StatusBar.kt
new file mode 100644
index 0000000..f514ab4
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/statusbar/ui/composable/StatusBar.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.statusbar.ui.composable
+
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.defaultMinSize
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.unit.dp
+
+@Composable
+fun StatusBar(
+    modifier: Modifier = Modifier,
+) {
+    // TODO(b/272780101): implement.
+    Row(
+        modifier = modifier.fillMaxWidth().defaultMinSize(minHeight = 48.dp).padding(4.dp),
+        horizontalArrangement = Arrangement.Center,
+        verticalAlignment = Alignment.CenterVertically,
+    ) {
+        Text("Status bar")
+    }
+}
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSTile.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSTile.java
index 1d28c63..c0b69c1 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSTile.java
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSTile.java
@@ -189,10 +189,12 @@
 
         /** Get the text for secondaryLabel. */
         public String getSecondaryLabel(String stateText) {
-            if (TextUtils.isEmpty(secondaryLabel)) {
+            // Use a local reference as the value might change from other threads
+            CharSequence localSecondaryLabel = secondaryLabel;
+            if (TextUtils.isEmpty(localSecondaryLabel)) {
                 return stateText;
             }
-            return secondaryLabel.toString();
+            return localSecondaryLabel.toString();
         }
 
         public boolean copyTo(State other) {
diff --git a/packages/SystemUI/res/drawable/media_ttt_chip_background.xml b/packages/SystemUI/res/drawable/chipbar_background.xml
similarity index 92%
rename from packages/SystemUI/res/drawable/media_ttt_chip_background.xml
rename to packages/SystemUI/res/drawable/chipbar_background.xml
index 3abf4d7..5722177 100644
--- a/packages/SystemUI/res/drawable/media_ttt_chip_background.xml
+++ b/packages/SystemUI/res/drawable/chipbar_background.xml
@@ -17,6 +17,6 @@
 <shape
     xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:androidprv="http://schemas.android.com/apk/prv/res/android">
-    <solid android:color="?androidprv:attr/colorSurface" />
+    <solid android:color="?androidprv:attr/colorAccentSecondary" />
     <corners android:radius="32dp" />
 </shape>
diff --git a/packages/SystemUI/res/drawable/media_ttt_undo_background.xml b/packages/SystemUI/res/drawable/chipbar_end_button_background.xml
similarity index 93%
rename from packages/SystemUI/res/drawable/media_ttt_undo_background.xml
rename to packages/SystemUI/res/drawable/chipbar_end_button_background.xml
index 3e2e4f0..80c7207 100644
--- a/packages/SystemUI/res/drawable/media_ttt_undo_background.xml
+++ b/packages/SystemUI/res/drawable/chipbar_end_button_background.xml
@@ -20,7 +20,7 @@
     android:color="?android:textColorPrimary">
     <item android:id="@android:id/background">
         <shape>
-            <solid android:color="?androidprv:attr/colorAccentPrimary"/>
+            <solid android:color="@android:color/system_accent1_200"/>
             <corners android:radius="24dp" />
         </shape>
     </item>
diff --git a/packages/SystemUI/res/drawable/dream_overlay_bottom_affordance_bg.xml b/packages/SystemUI/res/drawable/dream_overlay_bottom_affordance_bg.xml
new file mode 100644
index 0000000..3b67ddd
--- /dev/null
+++ b/packages/SystemUI/res/drawable/dream_overlay_bottom_affordance_bg.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+* Copyright 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.
+*/
+-->
+<shape
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+    android:shape="rectangle">
+  <solid android:color="?androidprv:attr/colorSurface"/>
+  <size
+      android:width="@dimen/dream_overlay_bottom_affordance_height"
+      android:height="@dimen/dream_overlay_bottom_affordance_width"/>
+  <corners android:radius="@dimen/dream_overlay_bottom_affordance_radius" />
+</shape>
diff --git a/packages/SystemUI/res/layout/chipbar.xml b/packages/SystemUI/res/layout/chipbar.xml
index 0ff944c..a317178 100644
--- a/packages/SystemUI/res/layout/chipbar.xml
+++ b/packages/SystemUI/res/layout/chipbar.xml
@@ -29,8 +29,8 @@
         android:orientation="horizontal"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
-        android:padding="@dimen/media_ttt_chip_outer_padding"
-        android:background="@drawable/media_ttt_chip_background"
+        android:padding="@dimen/chipbar_outer_padding"
+        android:background="@drawable/chipbar_background"
         android:layout_marginTop="20dp"
         android:layout_marginStart="@dimen/notification_side_paddings"
         android:layout_marginEnd="@dimen/notification_side_paddings"
@@ -43,8 +43,8 @@
 
         <com.android.internal.widget.CachingIconView
             android:id="@+id/start_icon"
-            android:layout_width="@dimen/media_ttt_app_icon_size"
-            android:layout_height="@dimen/media_ttt_app_icon_size"
+            android:layout_width="@dimen/chipbar_start_icon_size"
+            android:layout_height="@dimen/chipbar_start_icon_size"
             android:layout_marginEnd="12dp"
             android:alpha="0.0"
             />
@@ -54,47 +54,46 @@
             android:layout_width="0dp"
             android:layout_height="wrap_content"
             android:layout_weight="1"
-            android:textSize="@dimen/media_ttt_text_size"
-            android:textColor="?android:attr/textColorPrimary"
+            android:textSize="@dimen/chipbar_text_size"
+            android:textColor="@android:color/system_accent2_900"
             android:alpha="0.0"
             />
 
         <!-- At most one of [loading, failure_icon, undo] will be visible at a time. -->
         <ImageView
             android:id="@+id/loading"
-            android:layout_width="@dimen/media_ttt_status_icon_size"
-            android:layout_height="@dimen/media_ttt_status_icon_size"
-            android:layout_marginStart="@dimen/media_ttt_last_item_start_margin"
+            android:layout_width="@dimen/chipbar_end_icon_size"
+            android:layout_height="@dimen/chipbar_end_icon_size"
+            android:layout_marginStart="@dimen/chipbar_end_item_start_margin"
             android:src="@drawable/ic_progress_activity"
-            android:tint="?androidprv:attr/colorAccentPrimaryVariant"
+            android:tint="@android:color/system_accent2_700"
             android:alpha="0.0"
             />
 
         <ImageView
             android:id="@+id/error"
-            android:layout_width="@dimen/media_ttt_status_icon_size"
-            android:layout_height="@dimen/media_ttt_status_icon_size"
-            android:layout_marginStart="@dimen/media_ttt_last_item_start_margin"
+            android:layout_width="@dimen/chipbar_end_icon_size"
+            android:layout_height="@dimen/chipbar_end_icon_size"
+            android:layout_marginStart="@dimen/chipbar_end_item_start_margin"
             android:src="@drawable/ic_warning"
-            android:tint="@color/GM2_red_500"
+            android:tint="@color/GM2_red_600"
             android:alpha="0.0"
             />
 
-        <!-- TODO(b/245610654): Re-name all the media-specific dimens to chipbar dimens instead. -->
         <TextView
             android:id="@+id/end_button"
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
             android:textColor="?androidprv:attr/textColorOnAccent"
-            android:layout_marginStart="@dimen/media_ttt_last_item_start_margin"
-            android:textSize="@dimen/media_ttt_text_size"
-            android:paddingStart="@dimen/media_ttt_chip_outer_padding"
-            android:paddingEnd="@dimen/media_ttt_chip_outer_padding"
-            android:paddingTop="@dimen/media_ttt_undo_button_vertical_padding"
-            android:paddingBottom="@dimen/media_ttt_undo_button_vertical_padding"
-            android:layout_marginTop="@dimen/media_ttt_undo_button_vertical_negative_margin"
-            android:layout_marginBottom="@dimen/media_ttt_undo_button_vertical_negative_margin"
-            android:background="@drawable/media_ttt_undo_background"
+            android:layout_marginStart="@dimen/chipbar_end_item_start_margin"
+            android:textSize="@dimen/chipbar_text_size"
+            android:paddingStart="@dimen/chipbar_outer_padding"
+            android:paddingEnd="@dimen/chipbar_outer_padding"
+            android:paddingTop="@dimen/chipbar_end_button_vertical_padding"
+            android:paddingBottom="@dimen/chipbar_end_button_vertical_padding"
+            android:layout_marginTop="@dimen/chipbar_end_button_vertical_negative_margin"
+            android:layout_marginBottom="@dimen/chipbar_end_button_vertical_negative_margin"
+            android:background="@drawable/chipbar_end_button_background"
             android:alpha="0.0"
             />
 
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 fb78b49..5b2ec48 100644
--- a/packages/SystemUI/res/layout/dream_overlay_home_controls_chip.xml
+++ b/packages/SystemUI/res/layout/dream_overlay_home_controls_chip.xml
@@ -22,13 +22,14 @@
 
     <com.android.systemui.animation.view.LaunchableImageView
         android:id="@+id/home_controls_chip"
-        android:layout_height="@dimen/keyguard_affordance_fixed_height"
-        android:layout_width="@dimen/keyguard_affordance_fixed_width"
+        android:layout_height="@dimen/dream_overlay_bottom_affordance_height"
+        android:layout_width="@dimen/dream_overlay_bottom_affordance_width"
         android:layout_gravity="bottom|start"
-        android:scaleType="center"
+        android:padding="@dimen/dream_overlay_bottom_affordance_padding"
+        android:background="@drawable/dream_overlay_bottom_affordance_bg"
+        android:scaleType="fitCenter"
         android:tint="?android:attr/textColorPrimary"
         android:src="@drawable/controls_icon"
-        android:background="@drawable/keyguard_bottom_affordance_bg"
         android:contentDescription="@string/quick_controls_title" />
 
 </FrameLayout>
diff --git a/packages/SystemUI/res/values/colors.xml b/packages/SystemUI/res/values/colors.xml
index 5d188fe..62e8c5f 100644
--- a/packages/SystemUI/res/values/colors.xml
+++ b/packages/SystemUI/res/values/colors.xml
@@ -153,6 +153,7 @@
 
     <color name="GM2_red_300">#F28B82</color>
     <color name="GM2_red_500">#EA4335</color>
+    <color name="GM2_red_600">#B3261E</color>
     <color name="GM2_red_700">#C5221F</color>
 
     <color name="GM2_blue_300">#8AB4F8</color>
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index 311990c..e5cd0c5 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -844,4 +844,44 @@
 
     <!-- Configuration to set Learn more in device logs as URL link -->
     <bool name="log_access_confirmation_learn_more_as_link">true</bool>
+
+    <!-- [START] MULTI SHADE -->
+    <!-- Whether the device should use dual shade. If false, the device uses single shade. -->
+    <bool name="dual_shade_enabled">true</bool>
+    <!--
+    When in dual shade, where should the horizontal split be on the screen to help determine whether
+    the user is pulling down the left shade or the right shade. Must be between 0.0 and 1.0,
+    inclusive. In other words: how much of the left-hand side of the screen, when pulled down on,
+    would reveal the left-hand side shade.
+
+    More concretely:
+    A value of 0.67 means that the left two-thirds of the screen are dedicated to the left-hand side
+    shade and the remaining one-third of the screen on the right is dedicated to the right-hand side
+    shade.
+    -->
+    <dimen name="dual_shade_split_fraction">0.67</dimen>
+    <!-- Width of the left-hand side shade. -->
+    <dimen name="left_shade_width">436dp</dimen>
+    <!-- Width of the right-hand side shade. -->
+    <dimen name="right_shade_width">436dp</dimen>
+    <!--
+    Opaque version of the scrim that shows up behind dual shades. The alpha channel is driven
+    programmatically.
+    -->
+    <color name="opaque_scrim">#D9D9D9</color>
+    <!-- Maximum opacity when the scrim that shows up behind the dual shades is fully visible. -->
+    <dimen name="dual_shade_scrim_alpha">0.1</dimen>
+    <!--
+    The amount that the user must swipe down when the shade is fully collapsed to automatically
+    expand once the user lets go of the shade. If the user swipes less than this amount, the shade
+    will automatically revert back to fully collapsed once the user stops swiping.
+    -->
+    <dimen name="shade_swipe_expand_threshold">0.5</dimen>
+    <!--
+    The amount that the user must swipe up when the shade is fully expanded to automatically
+    collapse once the user lets go of the shade. If the user swipes less than this amount, the shade
+    will automatically revert back to fully expanded once the user stops swiping.
+    -->
+    <dimen name="shade_swipe_collapse_threshold">0.5</dimen>
+    <!-- [END] MULTI SHADE -->
 </resources>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index e78be4c..4d38541 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -1108,14 +1108,15 @@
     <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>
-    <dimen name="media_ttt_text_size">16sp</dimen>
-    <dimen name="media_ttt_app_icon_size">24dp</dimen>
-    <dimen name="media_ttt_status_icon_size">20dp</dimen>
-    <dimen name="media_ttt_undo_button_vertical_padding">8dp</dimen>
-    <dimen name="media_ttt_undo_button_vertical_negative_margin">-8dp</dimen>
-    <dimen name="media_ttt_last_item_start_margin">12dp</dimen>
+    <!-- Chipbar -->
+    <!-- (Used for media tap-to-transfer chip for sender device and active unlock) -->
+    <dimen name="chipbar_outer_padding">16dp</dimen>
+    <dimen name="chipbar_text_size">16sp</dimen>
+    <dimen name="chipbar_start_icon_size">24dp</dimen>
+    <dimen name="chipbar_end_icon_size">20dp</dimen>
+    <dimen name="chipbar_end_button_vertical_padding">8dp</dimen>
+    <dimen name="chipbar_end_button_vertical_negative_margin">-8dp</dimen>
+    <dimen name="chipbar_end_item_start_margin">12dp</dimen>
 
     <!-- Media tap-to-transfer chip for receiver device -->
     <dimen name="media_ttt_icon_size_receiver">112dp</dimen>
@@ -1629,6 +1630,10 @@
     <!-- Dream overlay complications related dimensions -->
     <!-- The blur radius applied to the dream overlay when entering and exiting dreams -->
     <dimen name="dream_overlay_anim_blur_radius">50dp</dimen>
+    <dimen name="dream_overlay_bottom_affordance_height">64dp</dimen>
+    <dimen name="dream_overlay_bottom_affordance_width">64dp</dimen>
+    <dimen name="dream_overlay_bottom_affordance_radius">32dp</dimen>
+    <dimen name="dream_overlay_bottom_affordance_padding">14dp</dimen>
     <dimen name="dream_overlay_complication_clock_time_text_size">86dp</dimen>
     <dimen name="dream_overlay_complication_clock_time_translation_y">28dp</dimen>
     <dimen name="dream_overlay_complication_home_controls_padding">28dp</dimen>
diff --git a/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ViewScreenshotTestRule.kt b/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ViewScreenshotTestRule.kt
index 738b37c..2d47356 100644
--- a/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ViewScreenshotTestRule.kt
+++ b/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ViewScreenshotTestRule.kt
@@ -39,14 +39,14 @@
 import platform.test.screenshot.matchers.BitmapMatcher
 
 /** A rule for View screenshot diff unit tests. */
-class ViewScreenshotTestRule(
+open class ViewScreenshotTestRule(
     emulationSpec: DeviceEmulationSpec,
     private val matcher: BitmapMatcher = UnitTestBitmapMatcher,
     assetsPathRelativeToBuildRoot: String
 ) : TestRule {
     private val colorsRule = MaterialYouColorsRule()
     private val deviceEmulationRule = DeviceEmulationRule(emulationSpec)
-    private val screenshotRule =
+    protected val screenshotRule =
         ScreenshotTestRule(
             SystemUIGoldenImagePathManager(
                 getEmulatedDevicePathConfig(emulationSpec),
@@ -64,15 +64,10 @@
         return delegateRule.apply(base, description)
     }
 
-    /**
-     * Compare the content of the view provided by [viewProvider] with the golden image identified
-     * by [goldenIdentifier] in the context of [emulationSpec].
-     */
-    fun screenshotTest(
-        goldenIdentifier: String,
+    protected fun takeScreenshot(
         mode: Mode = Mode.WrapContent,
         viewProvider: (ComponentActivity) -> View,
-    ) {
+    ): Bitmap {
         activityRule.scenario.onActivity { activity ->
             // Make sure that the activity draws full screen and fits the whole display instead of
             // the system bars.
@@ -99,7 +94,19 @@
             contentView = content.getChildAt(0)
         }
 
-        val bitmap = contentView?.toBitmap() ?: error("contentView is null")
+        return contentView?.toBitmap() ?: error("contentView is null")
+    }
+
+    /**
+     * Compare the content of the view provided by [viewProvider] with the golden image identified
+     * by [goldenIdentifier] in the context of [emulationSpec].
+     */
+    fun screenshotTest(
+        goldenIdentifier: String,
+        mode: Mode = Mode.WrapContent,
+        viewProvider: (ComponentActivity) -> View,
+    ) {
+        val bitmap = takeScreenshot(mode, viewProvider)
         screenshotRule.assertBitmapAgainstGolden(
             bitmap,
             goldenIdentifier,
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/RotationButtonController.java b/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/RotationButtonController.java
index 5b27c40..53fab69 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/RotationButtonController.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/RotationButtonController.java
@@ -96,9 +96,9 @@
     private boolean mHoveringRotationSuggestion;
     private final AccessibilityManager mAccessibilityManager;
     private final TaskStackListenerImpl mTaskStackListener;
-    private Consumer<Integer> mRotWatcherListener;
 
     private boolean mListenersRegistered = false;
+    private boolean mRotationWatcherRegistered = false;
     private boolean mIsNavigationBarShowing;
     @SuppressLint("InlinedApi")
     private @WindowInsetsController.Behavior
@@ -140,22 +140,7 @@
             // We need this to be scheduled as early as possible to beat the redrawing of
             // window in response to the orientation change.
             mMainThreadHandler.postAtFrontOfQueue(() -> {
-                // If the screen rotation changes while locked, potentially update lock to flow with
-                // new screen rotation and hide any showing suggestions.
-                boolean rotationLocked = isRotationLocked();
-                // The isVisible check makes the rotation button disappear when we are not locked
-                // (e.g. for tabletop auto-rotate).
-                if (rotationLocked || mRotationButton.isVisible()) {
-                    // Do not allow a change in rotation to set user rotation when docked.
-                    if (shouldOverrideUserLockPrefs(rotation) && rotationLocked && !mDocked) {
-                        setRotationLockedAtAngle(rotation);
-                    }
-                    setRotateSuggestionButtonState(false /* visible */, true /* forced */);
-                }
-
-                if (mRotWatcherListener != null) {
-                    mRotWatcherListener.accept(rotation);
-                }
+                onRotationWatcherChanged(rotation);
             });
         }
     };
@@ -206,8 +191,11 @@
         return mContext;
     }
 
+    /**
+     * Called during Taskbar initialization.
+     */
     public void init() {
-        registerListeners();
+        registerListeners(true /* registerRotationWatcher */);
         if (mContext.getDisplay().getDisplayId() != DEFAULT_DISPLAY) {
             // Currently there is no accelerometer sensor on non-default display, disable fixed
             // rotation for non-default display
@@ -215,11 +203,14 @@
         }
     }
 
+    /**
+     * Called during Taskbar uninitialization.
+     */
     public void onDestroy() {
         unregisterListeners();
     }
 
-    public void registerListeners() {
+    public void registerListeners(boolean registerRotationWatcher) {
         if (mListenersRegistered || getContext().getPackageManager().hasSystemFeature(FEATURE_PC)) {
             return;
         }
@@ -229,15 +220,18 @@
         updateDockedState(mContext.registerReceiver(mDockedReceiver,
                 new IntentFilter(Intent.ACTION_DOCK_EVENT)));
 
-        try {
-            WindowManagerGlobal.getWindowManagerService()
-                    .watchRotation(mRotationWatcher, DEFAULT_DISPLAY);
-        } catch (IllegalArgumentException e) {
-            mListenersRegistered = false;
-            Log.w(TAG, "RegisterListeners for the display failed");
-        } catch (RemoteException e) {
-            Log.e(TAG, "RegisterListeners caught a RemoteException", e);
-            return;
+        if (registerRotationWatcher) {
+            try {
+                WindowManagerGlobal.getWindowManagerService()
+                        .watchRotation(mRotationWatcher, DEFAULT_DISPLAY);
+                mRotationWatcherRegistered = true;
+            } catch (IllegalArgumentException e) {
+                mListenersRegistered = false;
+                Log.w(TAG, "RegisterListeners for the display failed", e);
+            } catch (RemoteException e) {
+                Log.e(TAG, "RegisterListeners caught a RemoteException", e);
+                return;
+            }
         }
 
         TaskStackChangeListeners.getInstance().registerTaskStackListener(mTaskStackListener);
@@ -251,20 +245,19 @@
         mListenersRegistered = false;
 
         mContext.unregisterReceiver(mDockedReceiver);
-        try {
-            WindowManagerGlobal.getWindowManagerService().removeRotationWatcher(mRotationWatcher);
-        } catch (RemoteException e) {
-            Log.e(TAG, "UnregisterListeners caught a RemoteException", e);
-            return;
+        if (mRotationWatcherRegistered) {
+            try {
+                WindowManagerGlobal.getWindowManagerService().removeRotationWatcher(
+                        mRotationWatcher);
+            } catch (RemoteException e) {
+                Log.e(TAG, "UnregisterListeners caught a RemoteException", e);
+                return;
+            }
         }
 
         TaskStackChangeListeners.getInstance().unregisterTaskStackListener(mTaskStackListener);
     }
 
-    public void setRotationCallback(Consumer<Integer> watcher) {
-        mRotWatcherListener = watcher;
-    }
-
     public void setRotationLockedAtAngle(int rotationSuggestion) {
         RotationPolicy.setRotationLockAtAngle(mContext, /* enabled= */ isRotationLocked(),
                 /* rotation= */ rotationSuggestion);
@@ -427,6 +420,30 @@
         }
     }
 
+    /**
+     * Called when the rotation watcher rotation changes, either from the watcher registered
+     * internally in this class, or a signal propagated from NavBarHelper.
+     */
+    public void onRotationWatcherChanged(int rotation) {
+        if (!mListenersRegistered) {
+            // Ignore if not registered
+            return;
+        }
+
+        // If the screen rotation changes while locked, potentially update lock to flow with
+        // new screen rotation and hide any showing suggestions.
+        boolean rotationLocked = isRotationLocked();
+        // The isVisible check makes the rotation button disappear when we are not locked
+        // (e.g. for tabletop auto-rotate).
+        if (rotationLocked || mRotationButton.isVisible()) {
+            // Do not allow a change in rotation to set user rotation when docked.
+            if (shouldOverrideUserLockPrefs(rotation) && rotationLocked && !mDocked) {
+                setRotationLockedAtAngle(rotation);
+            }
+            setRotateSuggestionButtonState(false /* visible */, true /* forced */);
+        }
+    }
+
     public void onDisable2FlagChanged(int state2) {
         final boolean rotateSuggestionsDisabled = hasDisable2RotateSuggestionFlag(state2);
         if (rotateSuggestionsDisabled) onRotationSuggestionsDisabled();
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.java
index 6f7d66d..58e7747 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.java
@@ -321,7 +321,9 @@
                     } else {
                         // We are receiving new opening tasks, so convert to onTasksAppeared.
                         targets[i] = TransitionUtil.newTarget(change, layer, info, t, mLeashMap);
-                        t.reparent(targets[i].leash, mInfo.getRootLeash());
+                        // reparent into the original `mInfo` since that's where we are animating.
+                        final int rootIdx = TransitionUtil.rootIndexFor(change, mInfo);
+                        t.reparent(targets[i].leash, mInfo.getRoot(rootIdx).getLeash());
                         t.setLayer(targets[i].leash, layer);
                         mOpeningTasks.add(new TaskState(change, targets[i].leash));
                     }
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index aaf6307..b3a641c 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -3408,8 +3408,8 @@
      */
     private void handleBatteryUpdate(BatteryStatus status) {
         Assert.isMainThread();
-        mLogger.d("handleBatteryUpdate");
         final boolean batteryUpdateInteresting = isBatteryUpdateInteresting(mBatteryStatus, status);
+        mLogger.logHandleBatteryUpdate(batteryUpdateInteresting);
         mBatteryStatus = status;
         if (batteryUpdateInteresting) {
             for (int i = 0; i < mCallbacks.size(); i++) {
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardViewController.java
index 6c3c246..7661b8d 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardViewController.java
@@ -168,7 +168,7 @@
     /**
      * Stop showing the alternate bouncer, if showing.
      */
-    void hideAlternateBouncer(boolean forceUpdateScrim);
+    void hideAlternateBouncer(boolean updateScrim);
 
     // TODO: Deprecate registerStatusBar in KeyguardViewController interface. It is currently
     //  only used for testing purposes in StatusBarKeyguardViewManager, and it prevents us from
diff --git a/packages/SystemUI/src/com/android/keyguard/logging/KeyguardLogger.kt b/packages/SystemUI/src/com/android/keyguard/logging/KeyguardLogger.kt
index 51aca07..4d71a89 100644
--- a/packages/SystemUI/src/com/android/keyguard/logging/KeyguardLogger.kt
+++ b/packages/SystemUI/src/com/android/keyguard/logging/KeyguardLogger.kt
@@ -97,6 +97,21 @@
         )
     }
 
+    fun logUpdateBatteryIndication(
+        powerIndication: String,
+        pluggedIn: Boolean,
+    ) {
+        buffer.log(
+            KeyguardIndicationController.TAG,
+            LogLevel.DEBUG,
+            {
+                str1 = powerIndication
+                bool1 = pluggedIn
+            },
+            { "updateBatteryIndication powerIndication:$str1 pluggedIn:$bool1" }
+        )
+    }
+
     fun logKeyguardSwitchIndication(
         type: Int,
         message: String?,
@@ -112,6 +127,28 @@
         )
     }
 
+    fun logRefreshBatteryInfo(
+        isChargingOrFull: Boolean,
+        powerPluggedIn: Boolean,
+        batteryLevel: Int,
+        batteryOverheated: Boolean
+    ) {
+        buffer.log(
+            KeyguardIndicationController.TAG,
+            LogLevel.DEBUG,
+            {
+                bool1 = isChargingOrFull
+                bool2 = powerPluggedIn
+                bool3 = batteryOverheated
+                int1 = batteryLevel
+            },
+            {
+                "refreshBatteryInfo isChargingOrFull:$bool1 powerPluggedIn:$bool2" +
+                    " batteryOverheated:$bool3 batteryLevel:$int1"
+            }
+        )
+    }
+
     fun getKeyguardSwitchIndicationNonSensitiveLog(type: Int, message: String?): String {
         // only show the battery string. other strings may contain sensitive info
         return if (type == KeyguardIndicationRotateTextViewController.INDICATION_TYPE_BATTERY) {
diff --git a/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt b/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt
index 2403d11..5162807 100644
--- a/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt
+++ b/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt
@@ -657,4 +657,15 @@
             }
         )
     }
+
+    fun logHandleBatteryUpdate(isInteresting: Boolean) {
+        logBuffer.log(
+            TAG,
+            DEBUG,
+            {
+                bool1 = isInteresting
+            },
+            { "handleBatteryUpdate: $bool1" }
+        )
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
index c31d45f..4aa985b 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
@@ -198,32 +198,36 @@
     final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
         @Override
         public void onReceive(Context context, Intent intent) {
-            if (mCurrentDialog != null
-                    && Intent.ACTION_CLOSE_SYSTEM_DIALOGS.equals(intent.getAction())) {
+            if (Intent.ACTION_CLOSE_SYSTEM_DIALOGS.equals(intent.getAction())) {
                 String reason = intent.getStringExtra("reason");
                 reason = (reason != null) ? reason : "unknown";
-                Log.d(TAG, "ACTION_CLOSE_SYSTEM_DIALOGS received, reason: " + reason);
-
-                mCurrentDialog.dismissWithoutCallback(true /* animate */);
-                mCurrentDialog = null;
-
-                for (Callback cb : mCallbacks) {
-                    cb.onBiometricPromptDismissed();
-                }
-
-                try {
-                    if (mReceiver != null) {
-                        mReceiver.onDialogDismissed(BiometricPrompt.DISMISSED_REASON_USER_CANCEL,
-                                null /* credentialAttestation */);
-                        mReceiver = null;
-                    }
-                } catch (RemoteException e) {
-                    Log.e(TAG, "Remote exception", e);
-                }
+                closeDioalog(reason);
             }
         }
     };
 
+    private void closeDioalog(String reason) {
+        if (isShowing()) {
+            Log.i(TAG, "Close BP, reason :" + reason);
+            mCurrentDialog.dismissWithoutCallback(true /* animate */);
+            mCurrentDialog = null;
+
+            for (Callback cb : mCallbacks) {
+                cb.onBiometricPromptDismissed();
+            }
+
+            try {
+                if (mReceiver != null) {
+                    mReceiver.onDialogDismissed(BiometricPrompt.DISMISSED_REASON_USER_CANCEL,
+                            null /* credentialAttestation */);
+                    mReceiver = null;
+                }
+            } catch (RemoteException e) {
+                Log.e(TAG, "Remote exception", e);
+            }
+        }
+    }
+
     private void cancelIfOwnerIsNotInForeground() {
         mExecution.assertIsMainThread();
         if (mCurrentDialog != null) {
@@ -546,6 +550,11 @@
         }
     }
 
+    @Override
+    public void handleShowGlobalActionsMenu() {
+        closeDioalog("PowerMenu shown");
+    }
+
     /**
      * @return where the UDFPS exists on the screen in pixels in portrait mode.
      */
diff --git a/packages/SystemUI/src/com/android/systemui/compose/BaseComposeFacade.kt b/packages/SystemUI/src/com/android/systemui/compose/BaseComposeFacade.kt
index c0f8549..4173bdc 100644
--- a/packages/SystemUI/src/com/android/systemui/compose/BaseComposeFacade.kt
+++ b/packages/SystemUI/src/com/android/systemui/compose/BaseComposeFacade.kt
@@ -21,8 +21,10 @@
 import android.view.View
 import androidx.activity.ComponentActivity
 import androidx.lifecycle.LifecycleOwner
+import com.android.systemui.multishade.ui.viewmodel.MultiShadeViewModel
 import com.android.systemui.people.ui.viewmodel.PeopleViewModel
 import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel
+import com.android.systemui.util.time.SystemClock
 
 /**
  * A facade to interact with Compose, when it is available.
@@ -57,4 +59,11 @@
         viewModel: FooterActionsViewModel,
         qsVisibilityLifecycleOwner: LifecycleOwner,
     ): View
+
+    /** Create a [View] to represent [viewModel] on screen. */
+    fun createMultiShadeView(
+        context: Context,
+        viewModel: MultiShadeViewModel,
+        clock: SystemClock,
+    ): View
 }
diff --git a/packages/SystemUI/src/com/android/systemui/controls/settings/ControlsSettingsDialogManager.kt b/packages/SystemUI/src/com/android/systemui/controls/settings/ControlsSettingsDialogManager.kt
index 06d4a08..ce0f2e9 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/settings/ControlsSettingsDialogManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/settings/ControlsSettingsDialogManager.kt
@@ -155,17 +155,18 @@
         d.show()
     }
 
-    private fun turnOnSettingSecurely(settings: List<String>) {
+    private fun turnOnSettingSecurely(settings: List<String>, onComplete: () -> Unit) {
         val action =
             ActivityStarter.OnDismissAction {
                 settings.forEach { setting ->
                     secureSettings.putIntForUser(setting, 1, userTracker.userId)
                 }
+                onComplete()
                 true
             }
         activityStarter.dismissKeyguardThenExecute(
             action,
-            /* cancel */ null,
+            /* cancel */ onComplete,
             /* afterKeyguardGone */ true
         )
     }
@@ -186,7 +187,11 @@
                 if (!showDeviceControlsInLockscreen) {
                     settings.add(Settings.Secure.LOCKSCREEN_SHOW_CONTROLS)
                 }
-                turnOnSettingSecurely(settings)
+                // If we are toggling the flag, we want to call onComplete after the keyguard is
+                // dismissed (and the setting is turned on), to pass the correct value.
+                turnOnSettingSecurely(settings, onComplete)
+            } else {
+                onComplete()
             }
             if (attempts != MAX_NUMBER_ATTEMPTS_CONTROLS_DIALOG) {
                 prefs
@@ -194,7 +199,6 @@
                     .putInt(PREFS_SETTINGS_DIALOG_ATTEMPTS, MAX_NUMBER_ATTEMPTS_CONTROLS_DIALOG)
                     .apply()
             }
-            onComplete()
         }
 
         override fun onCancel(dialog: DialogInterface?) {
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java
index 9374ad9..471c445 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java
@@ -156,6 +156,7 @@
                     ComponentName lowLightDreamComponent,
             DreamOverlayCallbackController dreamOverlayCallbackController,
             @Named(DREAM_OVERLAY_WINDOW_TITLE) String windowTitle) {
+        super(executor);
         mContext = context;
         mExecutor = executor;
         mWindowManager = windowManager;
@@ -202,55 +203,50 @@
 
     @Override
     public void onStartDream(@NonNull WindowManager.LayoutParams layoutParams) {
-        mExecutor.execute(() -> {
-            setCurrentStateLocked(Lifecycle.State.STARTED);
+        setCurrentStateLocked(Lifecycle.State.STARTED);
 
-            mUiEventLogger.log(DreamOverlayEvent.DREAM_OVERLAY_ENTER_START);
+        mUiEventLogger.log(DreamOverlayEvent.DREAM_OVERLAY_ENTER_START);
 
-            if (mDestroyed) {
-                // The task could still be executed after the service has been destroyed. Bail if
-                // that is the case.
-                return;
-            }
+        if (mDestroyed) {
+            // The task could still be executed after the service has been destroyed. Bail if
+            // that is the case.
+            return;
+        }
 
-            if (mStarted) {
-                // Reset the current dream overlay before starting a new one. This can happen
-                // when two dreams overlap (briefly, for a smoother dream transition) and both
-                // dreams are bound to the dream overlay service.
-                resetCurrentDreamOverlayLocked();
-            }
+        if (mStarted) {
+            // Reset the current dream overlay before starting a new one. This can happen
+            // when two dreams overlap (briefly, for a smoother dream transition) and both
+            // dreams are bound to the dream overlay service.
+            resetCurrentDreamOverlayLocked();
+        }
 
-            mDreamOverlayContainerViewController =
-                    mDreamOverlayComponent.getDreamOverlayContainerViewController();
-            mDreamOverlayTouchMonitor = mDreamOverlayComponent.getDreamOverlayTouchMonitor();
-            mDreamOverlayTouchMonitor.init();
+        mDreamOverlayContainerViewController =
+                mDreamOverlayComponent.getDreamOverlayContainerViewController();
+        mDreamOverlayTouchMonitor = mDreamOverlayComponent.getDreamOverlayTouchMonitor();
+        mDreamOverlayTouchMonitor.init();
 
-            mStateController.setShouldShowComplications(shouldShowComplications());
+        mStateController.setShouldShowComplications(shouldShowComplications());
 
-            // If we are not able to add the overlay window, reset the overlay.
-            if (!addOverlayWindowLocked(layoutParams)) {
-                resetCurrentDreamOverlayLocked();
-                return;
-            }
+        // If we are not able to add the overlay window, reset the overlay.
+        if (!addOverlayWindowLocked(layoutParams)) {
+            resetCurrentDreamOverlayLocked();
+            return;
+        }
 
+        setCurrentStateLocked(Lifecycle.State.RESUMED);
+        mStateController.setOverlayActive(true);
+        final ComponentName dreamComponent = getDreamComponent();
+        mStateController.setLowLightActive(
+                dreamComponent != null && dreamComponent.equals(mLowLightDreamComponent));
+        mUiEventLogger.log(DreamOverlayEvent.DREAM_OVERLAY_COMPLETE_START);
 
-            setCurrentStateLocked(Lifecycle.State.RESUMED);
-            mStateController.setOverlayActive(true);
-            final ComponentName dreamComponent = getDreamComponent();
-            mStateController.setLowLightActive(
-                    dreamComponent != null && dreamComponent.equals(mLowLightDreamComponent));
-            mUiEventLogger.log(DreamOverlayEvent.DREAM_OVERLAY_COMPLETE_START);
-
-            mDreamOverlayCallbackController.onStartDream();
-            mStarted = true;
-        });
+        mDreamOverlayCallbackController.onStartDream();
+        mStarted = true;
     }
 
     @Override
     public void onEndDream() {
-        mExecutor.execute(() -> {
-            resetCurrentDreamOverlayLocked();
-        });
+        resetCurrentDreamOverlayLocked();
     }
 
     private Lifecycle.State getCurrentStateLocked() {
@@ -263,12 +259,10 @@
 
     @Override
     public void onWakeUp(@NonNull Runnable onCompletedCallback) {
-        mExecutor.execute(() -> {
-            if (mDreamOverlayContainerViewController != null) {
-                mDreamOverlayCallbackController.onWakeUp();
-                mDreamOverlayContainerViewController.wakeUp(onCompletedCallback, mExecutor);
-            }
-        });
+        if (mDreamOverlayContainerViewController != null) {
+            mDreamOverlayCallbackController.onWakeUp();
+            mDreamOverlayContainerViewController.wakeUp(onCompletedCallback, mExecutor);
+        }
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/RegisteredComplicationsModule.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/RegisteredComplicationsModule.java
index 9b954f5f..6808142 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/RegisteredComplicationsModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/RegisteredComplicationsModule.java
@@ -51,6 +51,7 @@
     int DREAM_MEDIA_COMPLICATION_WEIGHT = 0;
     int DREAM_HOME_CONTROLS_CHIP_COMPLICATION_WEIGHT = 4;
     int DREAM_MEDIA_ENTRY_COMPLICATION_WEIGHT = 3;
+    int DREAM_WEATHER_COMPLICATION_WEIGHT = 0;
 
     /**
      * Provides layout parameters for the clock time complication.
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/smartspace/DreamSmartspaceController.kt b/packages/SystemUI/src/com/android/systemui/dreams/smartspace/DreamSmartspaceController.kt
index 63f63a5..78e132f 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/smartspace/DreamSmartspaceController.kt
+++ b/packages/SystemUI/src/com/android/systemui/dreams/smartspace/DreamSmartspaceController.kt
@@ -30,14 +30,15 @@
 import com.android.systemui.plugins.BcSmartspaceDataPlugin
 import com.android.systemui.plugins.BcSmartspaceDataPlugin.SmartspaceTargetListener
 import com.android.systemui.plugins.BcSmartspaceDataPlugin.SmartspaceView
+import com.android.systemui.plugins.BcSmartspaceDataPlugin.UI_SURFACE_DREAM
 import com.android.systemui.smartspace.SmartspacePrecondition
 import com.android.systemui.smartspace.SmartspaceTargetFilter
+import com.android.systemui.smartspace.dagger.SmartspaceModule
 import com.android.systemui.smartspace.dagger.SmartspaceModule.Companion.DREAM_SMARTSPACE_DATA_PLUGIN
 import com.android.systemui.smartspace.dagger.SmartspaceModule.Companion.DREAM_SMARTSPACE_PRECONDITION
 import com.android.systemui.smartspace.dagger.SmartspaceModule.Companion.DREAM_SMARTSPACE_TARGET_FILTER
 import com.android.systemui.smartspace.dagger.SmartspaceViewComponent
 import com.android.systemui.util.concurrency.Execution
-import java.lang.RuntimeException
 import java.util.Optional
 import java.util.concurrent.Executor
 import javax.inject.Inject
@@ -56,13 +57,16 @@
     @Named(DREAM_SMARTSPACE_PRECONDITION) private val precondition: SmartspacePrecondition,
     @Named(DREAM_SMARTSPACE_TARGET_FILTER)
     private val optionalTargetFilter: Optional<SmartspaceTargetFilter>,
-    @Named(DREAM_SMARTSPACE_DATA_PLUGIN) optionalPlugin: Optional<BcSmartspaceDataPlugin>
+    @Named(DREAM_SMARTSPACE_DATA_PLUGIN) optionalPlugin: Optional<BcSmartspaceDataPlugin>,
+    @Named(SmartspaceModule.WEATHER_SMARTSPACE_DATA_PLUGIN)
+    optionalWeatherPlugin: Optional<BcSmartspaceDataPlugin>,
 ) {
     companion object {
         private const val TAG = "DreamSmartspaceCtrlr"
     }
 
     private var session: SmartspaceSession? = null
+    private val weatherPlugin: BcSmartspaceDataPlugin? = optionalWeatherPlugin.orElse(null)
     private val plugin: BcSmartspaceDataPlugin? = optionalPlugin.orElse(null)
     private var targetFilter: SmartspaceTargetFilter? = optionalTargetFilter.orElse(null)
 
@@ -116,31 +120,54 @@
     private val sessionListener = SmartspaceSession.OnTargetsAvailableListener { targets ->
         execution.assertIsMainThread()
 
+        // The weather data plugin takes unfiltered targets and performs the filtering internally.
+        weatherPlugin?.onTargetsAvailable(targets)
+
         onTargetsAvailableUnfiltered(targets)
         val filteredTargets = targets.filter { targetFilter?.filterSmartspaceTarget(it) ?: true }
         plugin?.onTargetsAvailable(filteredTargets)
     }
 
     /**
+     * Constructs the weather view with custom layout and connects it to the weather plugin.
+     */
+    fun buildAndConnectWeatherView(parent: ViewGroup, customView: View?): View? {
+        return buildAndConnectViewWithPlugin(parent, weatherPlugin, customView)
+    }
+
+    /**
      * Constructs the smartspace view and connects it to the smartspace service.
      */
     fun buildAndConnectView(parent: ViewGroup): View? {
+        return buildAndConnectViewWithPlugin(parent, plugin, null)
+    }
+
+    private fun buildAndConnectViewWithPlugin(
+        parent: ViewGroup,
+        smartspaceDataPlugin: BcSmartspaceDataPlugin?,
+        customView: View?
+    ): View? {
         execution.assertIsMainThread()
 
         if (!precondition.conditionsMet()) {
             throw RuntimeException("Cannot build view when not enabled")
         }
 
-        val view = buildView(parent)
+        val view = buildView(parent, smartspaceDataPlugin, customView)
 
         connectSession()
 
         return view
     }
 
-    private fun buildView(parent: ViewGroup): View? {
-        return if (plugin != null) {
-            var view = smartspaceViewComponentFactory.create(parent, plugin, stateChangeListener)
+    private fun buildView(
+        parent: ViewGroup,
+        smartspaceDataPlugin: BcSmartspaceDataPlugin?,
+        customView: View?
+    ): View? {
+        return if (smartspaceDataPlugin != null) {
+            val view = smartspaceViewComponentFactory.create(parent, smartspaceDataPlugin,
+                stateChangeListener, customView)
                 .getView()
             if (view !is View) {
                 return null
@@ -157,7 +184,10 @@
     }
 
     private fun connectSession() {
-        if (plugin == null || session != null || !hasActiveSessionListeners()) {
+        if (plugin == null && weatherPlugin == null) {
+            return
+        }
+        if (session != null || !hasActiveSessionListeners()) {
             return
         }
 
@@ -166,13 +196,14 @@
         }
 
         val newSession = smartspaceManager.createSmartspaceSession(
-            SmartspaceConfig.Builder(context, "dream").build()
+            SmartspaceConfig.Builder(context, UI_SURFACE_DREAM).build()
         )
         Log.d(TAG, "Starting smartspace session for dream")
         newSession.addOnTargetsAvailableListener(uiExecutor, sessionListener)
         this.session = newSession
 
-        plugin.registerSmartspaceEventNotifier {
+        weatherPlugin?.registerSmartspaceEventNotifier { e -> session?.notifySmartspaceEvent(e) }
+        plugin?.registerSmartspaceEventNotifier {
                 e ->
             session?.notifySmartspaceEvent(e)
         }
@@ -199,22 +230,47 @@
 
         session = null
 
+        weatherPlugin?.registerSmartspaceEventNotifier(null)
+        weatherPlugin?.onTargetsAvailable(emptyList())
+
         plugin?.registerSmartspaceEventNotifier(null)
         plugin?.onTargetsAvailable(emptyList())
         Log.d(TAG, "Ending smartspace session for dream")
     }
 
     fun addListener(listener: SmartspaceTargetListener) {
+        addAndRegisterListener(listener, plugin)
+    }
+
+    fun removeListener(listener: SmartspaceTargetListener) {
+        removeAndUnregisterListener(listener, plugin)
+    }
+
+    fun addListenerForWeatherPlugin(listener: SmartspaceTargetListener) {
+        addAndRegisterListener(listener, weatherPlugin)
+    }
+
+    fun removeListenerForWeatherPlugin(listener: SmartspaceTargetListener) {
+        removeAndUnregisterListener(listener, weatherPlugin)
+    }
+
+    private fun addAndRegisterListener(
+        listener: SmartspaceTargetListener,
+        smartspaceDataPlugin: BcSmartspaceDataPlugin?
+    ) {
         execution.assertIsMainThread()
-        plugin?.registerListener(listener)
+        smartspaceDataPlugin?.registerListener(listener)
         listeners.add(listener)
 
         connectSession()
     }
 
-    fun removeListener(listener: SmartspaceTargetListener) {
+    private fun removeAndUnregisterListener(
+        listener: SmartspaceTargetListener,
+        smartspaceDataPlugin: BcSmartspaceDataPlugin?
+    ) {
         execution.assertIsMainThread()
-        plugin?.unregisterListener(listener)
+        smartspaceDataPlugin?.unregisterListener(listener)
         listeners.remove(listener)
         disconnect()
     }
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index 265a9903..c202416 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -244,6 +244,11 @@
     @JvmField
     val HIDE_SMARTSPACE_ON_DREAM_OVERLAY = unreleasedFlag(404, "hide_smartspace_on_dream_overlay")
 
+    // TODO(b/271460958): Tracking Bug
+    @JvmField
+    val SHOW_WEATHER_COMPLICATION_ON_DREAM_OVERLAY = unreleasedFlag(405,
+        "show_weather_complication_on_dream_overlay")
+
     // 500 - quick settings
 
     val PEOPLE_TILE = resourceBooleanFlag(502, R.bool.flag_conversations, "people_tile")
@@ -308,7 +313,8 @@
         unreleasedFlag(611, "new_status_bar_icons_debug_coloring")
 
     // TODO(b/265892345): Tracking Bug
-    val PLUG_IN_STATUS_BAR_CHIP = unreleasedFlag(265892345, "plug_in_status_bar_chip")
+    val PLUG_IN_STATUS_BAR_CHIP =
+            unreleasedFlag(265892345, "plug_in_status_bar_chip", teamfood = true)
 
     // 700 - dialer/calls
     // TODO(b/254512734): Tracking Bug
@@ -668,7 +674,8 @@
 
     // TODO(b/259428678): Tracking Bug
     @JvmField
-    val KEYBOARD_BACKLIGHT_INDICATOR = unreleasedFlag(2601, "keyboard_backlight_indicator")
+    val KEYBOARD_BACKLIGHT_INDICATOR =
+            unreleasedFlag(2601, "keyboard_backlight_indicator", teamfood = true)
 
     // TODO(b/272036292): Tracking Bug
     @JvmField
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/BouncerView.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/BouncerView.kt
index a2589d3..871a3ff 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/BouncerView.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/BouncerView.kt
@@ -55,4 +55,5 @@
     fun willRunDismissFromKeyguard(): Boolean
     /** @return the {@link OnBackAnimationCallback} to animate Bouncer during a back gesture. */
     fun getBackCallback(): OnBackAnimationCallback
+    fun showPromptReason(reason: Int)
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardBouncerRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardBouncerRepository.kt
index 86e5cd7..ae5b799 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardBouncerRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardBouncerRepository.kt
@@ -22,7 +22,6 @@
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.keyguard.shared.constants.KeyguardBouncerConstants.EXPANSION_HIDDEN
 import com.android.systemui.keyguard.shared.model.BouncerShowMessageModel
-import com.android.systemui.keyguard.shared.model.KeyguardBouncerModel
 import com.android.systemui.log.dagger.BouncerLog
 import com.android.systemui.log.table.TableLogBuffer
 import com.android.systemui.log.table.logDiffsForTable
@@ -43,10 +42,8 @@
  */
 interface KeyguardBouncerRepository {
     /** Values associated with the PrimaryBouncer (pin/pattern/password) input. */
-    val primaryBouncerVisible: StateFlow<Boolean>
-    val primaryBouncerShow: StateFlow<KeyguardBouncerModel?>
+    val primaryBouncerShow: StateFlow<Boolean>
     val primaryBouncerShowingSoon: StateFlow<Boolean>
-    val primaryBouncerHide: StateFlow<Boolean>
     val primaryBouncerStartingToHide: StateFlow<Boolean>
     val primaryBouncerStartingDisappearAnimation: StateFlow<Runnable?>
     /** Determines if we want to instantaneously show the primary bouncer instead of translating. */
@@ -76,14 +73,10 @@
 
     fun setPrimaryScrimmed(isScrimmed: Boolean)
 
-    fun setPrimaryVisible(isVisible: Boolean)
-
-    fun setPrimaryShow(keyguardBouncerModel: KeyguardBouncerModel?)
+    fun setPrimaryShow(isShowing: Boolean)
 
     fun setPrimaryShowingSoon(showingSoon: Boolean)
 
-    fun setPrimaryHide(hide: Boolean)
-
     fun setPrimaryStartingToHide(startingToHide: Boolean)
 
     fun setPrimaryStartDisappearAnimation(runnable: Runnable?)
@@ -117,14 +110,10 @@
     @BouncerLog private val buffer: TableLogBuffer,
 ) : KeyguardBouncerRepository {
     /** Values associated with the PrimaryBouncer (pin/pattern/password) input. */
-    private val _primaryBouncerVisible = MutableStateFlow(false)
-    override val primaryBouncerVisible = _primaryBouncerVisible.asStateFlow()
-    private val _primaryBouncerShow = MutableStateFlow<KeyguardBouncerModel?>(null)
+    private val _primaryBouncerShow = MutableStateFlow(false)
     override val primaryBouncerShow = _primaryBouncerShow.asStateFlow()
     private val _primaryBouncerShowingSoon = MutableStateFlow(false)
     override val primaryBouncerShowingSoon = _primaryBouncerShowingSoon.asStateFlow()
-    private val _primaryBouncerHide = MutableStateFlow(false)
-    override val primaryBouncerHide = _primaryBouncerHide.asStateFlow()
     private val _primaryBouncerStartingToHide = MutableStateFlow(false)
     override val primaryBouncerStartingToHide = _primaryBouncerStartingToHide.asStateFlow()
     private val _primaryBouncerDisappearAnimation = MutableStateFlow<Runnable?>(null)
@@ -177,10 +166,6 @@
         _primaryBouncerScrimmed.value = isScrimmed
     }
 
-    override fun setPrimaryVisible(isVisible: Boolean) {
-        _primaryBouncerVisible.value = isVisible
-    }
-
     override fun setAlternateVisible(isVisible: Boolean) {
         if (isVisible && !_alternateBouncerVisible.value) {
             lastAlternateBouncerVisibleTime = clock.uptimeMillis()
@@ -194,18 +179,14 @@
         _alternateBouncerUIAvailable.value = isAvailable
     }
 
-    override fun setPrimaryShow(keyguardBouncerModel: KeyguardBouncerModel?) {
-        _primaryBouncerShow.value = keyguardBouncerModel
+    override fun setPrimaryShow(isShowing: Boolean) {
+        _primaryBouncerShow.value = isShowing
     }
 
     override fun setPrimaryShowingSoon(showingSoon: Boolean) {
         _primaryBouncerShowingSoon.value = showingSoon
     }
 
-    override fun setPrimaryHide(hide: Boolean) {
-        _primaryBouncerHide.value = hide
-    }
-
     override fun setPrimaryStartingToHide(startingToHide: Boolean) {
         _primaryBouncerStartingToHide.value = startingToHide
     }
@@ -248,19 +229,12 @@
             return
         }
 
-        primaryBouncerVisible
-            .logDiffsForTable(buffer, "", "PrimaryBouncerVisible", false)
-            .launchIn(applicationScope)
         primaryBouncerShow
-            .map { it != null }
             .logDiffsForTable(buffer, "", "PrimaryBouncerShow", false)
             .launchIn(applicationScope)
         primaryBouncerShowingSoon
             .logDiffsForTable(buffer, "", "PrimaryBouncerShowingSoon", false)
             .launchIn(applicationScope)
-        primaryBouncerHide
-            .logDiffsForTable(buffer, "", "PrimaryBouncerHide", false)
-            .launchIn(applicationScope)
         primaryBouncerStartingToHide
             .logDiffsForTable(buffer, "", "PrimaryBouncerStartingToHide", false)
             .launchIn(applicationScope)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
index c42e502..1ac0c52 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
@@ -137,7 +137,7 @@
     /** Whether the keyguard is going away. */
     val isKeyguardGoingAway: Flow<Boolean> = repository.isKeyguardGoingAway
     /** Whether the primary bouncer is showing or not. */
-    val primaryBouncerShowing: Flow<Boolean> = bouncerRepository.primaryBouncerVisible
+    val primaryBouncerShowing: Flow<Boolean> = bouncerRepository.primaryBouncerShow
     /** Whether the alternate bouncer is showing or not. */
     val alternateBouncerShowing: Flow<Boolean> = bouncerRepository.alternateBouncerVisible
     /** Observable for the [StatusBarState] */
@@ -159,7 +159,7 @@
         if (featureFlags.isEnabled(Flags.FACE_AUTH_REFACTOR)) {
             combine(
                     isKeyguardVisible,
-                    bouncerRepository.primaryBouncerVisible,
+                    primaryBouncerShowing,
                     onCameraLaunchDetected,
                 ) { isKeyguardVisible, isPrimaryBouncerShowing, cameraLaunchEvent ->
                     when {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractor.kt
index 3d2c472..b10aa90 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractor.kt
@@ -21,12 +21,15 @@
 import android.hardware.biometrics.BiometricSourceType
 import android.os.Handler
 import android.os.Trace
-import android.view.View
+import android.os.UserHandle
+import android.os.UserManager
 import android.util.Log
+import android.view.View
 import com.android.keyguard.KeyguardConstants
 import com.android.keyguard.KeyguardSecurityModel
 import com.android.keyguard.KeyguardUpdateMonitor
 import com.android.keyguard.KeyguardUpdateMonitorCallback
+import com.android.settingslib.Utils
 import com.android.systemui.DejankUtils
 import com.android.systemui.R
 import com.android.systemui.classifier.FalsingCollector
@@ -37,7 +40,6 @@
 import com.android.systemui.keyguard.data.repository.KeyguardBouncerRepository
 import com.android.systemui.keyguard.shared.constants.KeyguardBouncerConstants
 import com.android.systemui.keyguard.shared.model.BouncerShowMessageModel
-import com.android.systemui.keyguard.shared.model.KeyguardBouncerModel
 import com.android.systemui.plugins.ActivityStarter
 import com.android.systemui.shared.system.SysUiStatsLog
 import com.android.systemui.statusbar.phone.KeyguardBypassController
@@ -81,23 +83,21 @@
 
     /** Runnable to show the primary bouncer. */
     val showRunnable = Runnable {
-        repository.setPrimaryVisible(true)
-        repository.setPrimaryShow(
-            KeyguardBouncerModel(
-                promptReason = repository.bouncerPromptReason ?: 0,
-                errorMessage = repository.bouncerErrorMessage,
-                expansionAmount = repository.panelExpansionAmount.value
+        repository.setPrimaryShow(true)
+        primaryBouncerView.delegate?.showPromptReason(repository.bouncerPromptReason)
+        (repository.bouncerErrorMessage as? String)?.let {
+            repository.setShowMessage(
+                BouncerShowMessageModel(message = it, Utils.getColorError(context))
             )
-        )
+        }
         repository.setPrimaryShowingSoon(false)
         primaryBouncerCallbackInteractor.dispatchVisibilityChanged(View.VISIBLE)
     }
 
     val keyguardAuthenticated: Flow<Boolean> = repository.keyguardAuthenticated.filterNotNull()
-    val show: Flow<KeyguardBouncerModel> = repository.primaryBouncerShow.filterNotNull()
-    val hide: Flow<Unit> = repository.primaryBouncerHide.filter { it }.map {}
+    val show: Flow<Unit> = repository.primaryBouncerShow.filter { it }.map {}
+    val hide: Flow<Unit> = repository.primaryBouncerShow.filter { !it }.map {}
     val startingToHide: Flow<Unit> = repository.primaryBouncerStartingToHide.filter { it }.map {}
-    val isVisible: Flow<Boolean> = repository.primaryBouncerVisible
     val isBackButtonEnabled: Flow<Boolean> = repository.isBackButtonEnabled.filterNotNull()
     val showMessage: Flow<BouncerShowMessageModel> = repository.showMessage.filterNotNull()
     val startingDisappearAnimation: Flow<Runnable> =
@@ -107,10 +107,11 @@
     val panelExpansionAmount: Flow<Float> = repository.panelExpansionAmount
     /** 0f = bouncer fully hidden. 1f = bouncer fully visible. */
     val bouncerExpansion: Flow<Float> =
-        combine(repository.panelExpansionAmount, repository.primaryBouncerVisible) {
-            panelExpansion,
-            primaryBouncerVisible ->
-            if (primaryBouncerVisible) {
+        combine(
+            repository.panelExpansionAmount,
+            repository.primaryBouncerShow
+        ) { panelExpansion, primaryBouncerIsShowing ->
+            if (primaryBouncerIsShowing) {
                 1f - panelExpansion
             } else {
                 0f
@@ -120,21 +121,20 @@
     val isInteractable: Flow<Boolean> = bouncerExpansion.map { it > 0.9 }
     val sideFpsShowing: Flow<Boolean> = repository.sideFpsShowing
 
-    /**
-     * This callback needs to be a class field so it does not get garbage collected.
-     */
-    val keyguardUpdateMonitorCallback = object : KeyguardUpdateMonitorCallback() {
-        override fun onBiometricRunningStateChanged(
-            running: Boolean,
-            biometricSourceType: BiometricSourceType?
-        ) {
-            updateSideFpsVisibility()
-        }
+    /** This callback needs to be a class field so it does not get garbage collected. */
+    val keyguardUpdateMonitorCallback =
+        object : KeyguardUpdateMonitorCallback() {
+            override fun onBiometricRunningStateChanged(
+                running: Boolean,
+                biometricSourceType: BiometricSourceType?
+            ) {
+                updateSideFpsVisibility()
+            }
 
-        override fun onStrongAuthStateChanged(userId: Int) {
-            updateSideFpsVisibility()
+            override fun onStrongAuthStateChanged(userId: Int) {
+                updateSideFpsVisibility()
+            }
         }
-    }
 
     init {
         keyguardUpdateMonitor.registerCallback(keyguardUpdateMonitorCallback)
@@ -147,14 +147,13 @@
     fun show(isScrimmed: Boolean) {
         // Reset some states as we show the bouncer.
         repository.setKeyguardAuthenticated(null)
-        repository.setPrimaryHide(false)
         repository.setPrimaryStartingToHide(false)
 
         val resumeBouncer =
-            (repository.primaryBouncerVisible.value ||
-                repository.primaryBouncerShowingSoon.value) && needsFullscreenBouncer()
+            (isBouncerShowing() || repository.primaryBouncerShowingSoon.value) &&
+                needsFullscreenBouncer()
 
-        if (!resumeBouncer && repository.primaryBouncerShow.value != null) {
+        if (!resumeBouncer && isBouncerShowing()) {
             // If bouncer is visible, the bouncer is already showing.
             return
         }
@@ -201,9 +200,7 @@
         keyguardStateController.notifyPrimaryBouncerShowing(false /* showing */)
         cancelShowRunnable()
         repository.setPrimaryShowingSoon(false)
-        repository.setPrimaryVisible(false)
-        repository.setPrimaryHide(true)
-        repository.setPrimaryShow(null)
+        repository.setPrimaryShow(false)
         primaryBouncerCallbackInteractor.dispatchVisibilityChanged(View.INVISIBLE)
         Trace.endSection()
     }
@@ -320,9 +317,8 @@
         val fpsDetectionRunning: Boolean = keyguardUpdateMonitor.isFingerprintDetectionRunning
         val isUnlockingWithFpAllowed: Boolean =
             keyguardUpdateMonitor.isUnlockingWithFingerprintAllowed
-        val bouncerVisible = repository.primaryBouncerVisible.value
         val toShow =
-            (repository.primaryBouncerVisible.value &&
+            (isBouncerShowing() &&
                 sfpsEnabled &&
                 fpsDetectionRunning &&
                 isUnlockingWithFpAllowed &&
@@ -332,7 +328,7 @@
             Log.d(
                 TAG,
                 ("sideFpsToShow=$toShow\n" +
-                    "bouncerVisible=$bouncerVisible\n" +
+                    "isBouncerShowing=${isBouncerShowing()}\n" +
                     "configEnabled=$sfpsEnabled\n" +
                     "fpsDetectionRunning=$fpsDetectionRunning\n" +
                     "isUnlockingWithFpAllowed=$isUnlockingWithFpAllowed\n" +
@@ -344,8 +340,7 @@
 
     /** Returns whether bouncer is fully showing. */
     fun isFullyShowing(): Boolean {
-        return (repository.primaryBouncerShowingSoon.value ||
-            repository.primaryBouncerVisible.value) &&
+        return (repository.primaryBouncerShowingSoon.value || isBouncerShowing()) &&
             repository.panelExpansionAmount.value == KeyguardBouncerConstants.EXPANSION_VISIBLE &&
             repository.primaryBouncerStartingDisappearAnimation.value == null
     }
@@ -391,6 +386,10 @@
         mainHandler.removeCallbacks(showRunnable)
     }
 
+    private fun isBouncerShowing(): Boolean {
+        return repository.primaryBouncerShow.value
+    }
+
     companion object {
         private const val TAG = "PrimaryBouncerInteractor"
     }
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 bb617bd..d716784 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
@@ -27,7 +27,6 @@
 import com.android.keyguard.KeyguardSecurityView
 import com.android.keyguard.KeyguardUpdateMonitor
 import com.android.keyguard.dagger.KeyguardBouncerComponent
-import com.android.settingslib.Utils
 import com.android.systemui.keyguard.data.BouncerViewDelegate
 import com.android.systemui.keyguard.shared.constants.KeyguardBouncerConstants.EXPANSION_VISIBLE
 import com.android.systemui.keyguard.ui.viewmodel.KeyguardBouncerViewModel
@@ -101,6 +100,10 @@
                 override fun willRunDismissFromKeyguard(): Boolean {
                     return securityContainerController.willRunDismissFromKeyguard()
                 }
+
+                override fun showPromptReason(reason: Int) {
+                    securityContainerController.showPromptReason(reason)
+                }
             }
         view.repeatWhenAttached {
             repeatOnLifecycle(Lifecycle.State.CREATED) {
@@ -109,14 +112,11 @@
                     launch {
                         viewModel.show.collect {
                             // Reset Security Container entirely.
+                            view.visibility = View.VISIBLE
+                            securityContainerController.onBouncerVisibilityChanged(
+                                /* isVisible= */ true
+                            )
                             securityContainerController.reinflateViewFlipper()
-                            securityContainerController.showPromptReason(it.promptReason)
-                            it.errorMessage?.let { errorMessage ->
-                                securityContainerController.showMessage(
-                                    errorMessage,
-                                    Utils.getColorError(view.context)
-                                )
-                            }
                             securityContainerController.showPrimarySecurityScreen(
                                 /* turningOff= */ false
                             )
@@ -127,8 +127,13 @@
 
                     launch {
                         viewModel.hide.collect {
+                            view.visibility = View.INVISIBLE
+                            securityContainerController.onBouncerVisibilityChanged(
+                                /* isVisible= */ false
+                            )
                             securityContainerController.cancelDismissAction()
                             securityContainerController.reset()
+                            securityContainerController.onPause()
                         }
                     }
 
@@ -166,19 +171,6 @@
                     }
 
                     launch {
-                        viewModel.isBouncerVisible.collect { isVisible ->
-                            view.visibility = if (isVisible) View.VISIBLE else View.INVISIBLE
-                            securityContainerController.onBouncerVisibilityChanged(isVisible)
-                        }
-                    }
-
-                    launch {
-                        viewModel.isBouncerVisible
-                            .filter { !it }
-                            .collect { securityContainerController.onPause() }
-                    }
-
-                    launch {
                         viewModel.isInteractable.collect { isInteractable ->
                             securityContainerController.setInteractable(isInteractable)
                         }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardRemotePreviewManager.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardRemotePreviewManager.kt
index 50722d5..6d95882 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardRemotePreviewManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardRemotePreviewManager.kt
@@ -26,18 +26,21 @@
 import android.util.Log
 import androidx.annotation.VisibleForTesting
 import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.shared.quickaffordance.shared.model.KeyguardQuickAffordancePreviewConstants
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineDispatcher
-import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.launch
 
 @SysUISingleton
 class KeyguardRemotePreviewManager
 @Inject
 constructor(
     private val previewRendererFactory: KeyguardPreviewRendererFactory,
+    @Application private val applicationScope: CoroutineScope,
     @Main private val mainDispatcher: CoroutineDispatcher,
     @Background private val backgroundHandler: Handler,
 ) {
@@ -55,7 +58,13 @@
 
             // Destroy any previous renderer associated with this token.
             activePreviews[renderer.hostToken]?.let { destroyObserver(it) }
-            observer = PreviewLifecycleObserver(renderer, mainDispatcher, ::destroyObserver)
+            observer =
+                PreviewLifecycleObserver(
+                    renderer,
+                    applicationScope,
+                    mainDispatcher,
+                    ::destroyObserver,
+                )
             activePreviews[renderer.hostToken] = observer
             renderer.render()
             renderer.hostToken?.linkToDeath(observer, 0)
@@ -92,13 +101,18 @@
 
     private class PreviewLifecycleObserver(
         private val renderer: KeyguardPreviewRenderer,
+        private val scope: CoroutineScope,
         private val mainDispatcher: CoroutineDispatcher,
         private val requestDestruction: (PreviewLifecycleObserver) -> Unit,
     ) : Handler.Callback, IBinder.DeathRecipient {
 
-        private var isDestroyed = false
+        private var isDestroyedOrDestroying = false
 
         override fun handleMessage(message: Message): Boolean {
+            if (isDestroyedOrDestroying) {
+                return true
+            }
+
             when (message.what) {
                 KeyguardQuickAffordancePreviewConstants.MESSAGE_ID_SLOT_SELECTED -> {
                     message.data
@@ -118,14 +132,14 @@
         }
 
         fun onDestroy(): IBinder? {
-            if (isDestroyed) {
+            if (isDestroyedOrDestroying) {
                 return null
             }
 
-            isDestroyed = true
+            isDestroyedOrDestroying = true
             val hostToken = renderer.hostToken
             hostToken?.unlinkToDeath(this, 0)
-            runBlocking(mainDispatcher) { renderer.destroy() }
+            scope.launch(mainDispatcher) { renderer.destroy() }
             return hostToken
         }
     }
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 97e94d8f3..68910c6 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
@@ -21,7 +21,6 @@
 import com.android.systemui.keyguard.data.BouncerViewDelegate
 import com.android.systemui.keyguard.domain.interactor.PrimaryBouncerInteractor
 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.filterNotNull
@@ -38,14 +37,11 @@
     /** Observe on bouncer expansion amount. */
     val bouncerExpansionAmount: Flow<Float> = interactor.panelExpansionAmount
 
-    /** Observe on bouncer visibility. */
-    val isBouncerVisible: Flow<Boolean> = interactor.isVisible
-
     /** Can the user interact with the view? */
     val isInteractable: Flow<Boolean> = interactor.isInteractable
 
     /** Observe whether bouncer is showing. */
-    val show: Flow<KeyguardBouncerModel> = interactor.show
+    val show: Flow<Unit> = interactor.show
 
     /** Observe whether bouncer is hiding. */
     val hide: Flow<Unit> = interactor.hide
@@ -75,7 +71,7 @@
     val shouldUpdateSideFps: Flow<Unit> =
         merge(
             interactor.startingToHide,
-            interactor.isVisible.map {},
+            interactor.show,
             interactor.startingDisappearAnimation.filterNotNull().map {}
         )
 
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHierarchyManager.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHierarchyManager.kt
index 7fc7bdb..e10d74d 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHierarchyManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHierarchyManager.kt
@@ -639,7 +639,9 @@
     ) =
         traceSection("MediaHierarchyManager#updateDesiredLocation") {
             val desiredLocation = calculateLocation()
-            if (desiredLocation != this.desiredLocation || forceStateUpdate) {
+            if (
+                desiredLocation != this.desiredLocation || forceStateUpdate && !blockLocationChanges
+            ) {
                 if (this.desiredLocation >= 0 && desiredLocation != this.desiredLocation) {
                     // Only update previous location when it actually changes
                     previousLocation = this.desiredLocation
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/ChipStateSender.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/ChipStateSender.kt
index 537dbb9..d3efae4 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/ChipStateSender.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/ChipStateSender.kt
@@ -23,7 +23,6 @@
 import com.android.internal.logging.UiEventLogger
 import com.android.systemui.R
 import com.android.systemui.common.shared.model.Text
-import com.android.systemui.temporarydisplay.DEFAULT_TIMEOUT_MILLIS
 
 /**
  * A class enumerating all the possible states of the media tap-to-transfer chip on the sender
@@ -34,8 +33,8 @@
  *   state should not have the chip be displayed.
  * @property transferStatus the transfer status that the chip state represents.
  * @property endItem the item that should be displayed in the end section of the chip.
- * @property timeout the amount of time this chip should display on the screen before it times out
- *   and disappears.
+ * @property timeoutLength how long the chip should display on the screen before it times out and
+ *   disappears.
  */
 enum class ChipStateSender(
     @StatusBarManager.MediaTransferSenderState val stateInt: Int,
@@ -43,7 +42,7 @@
     @StringRes val stringResId: Int?,
     val transferStatus: TransferStatus,
     val endItem: SenderEndItem?,
-    val timeout: Int = DEFAULT_TIMEOUT_MILLIS,
+    val timeoutLength: TimeoutLength = TimeoutLength.DEFAULT,
 ) {
     /**
      * A state representing that the two devices are close but not close enough to *start* a cast to
@@ -56,6 +55,9 @@
         R.string.media_move_closer_to_start_cast,
         transferStatus = TransferStatus.NOT_STARTED,
         endItem = null,
+        // Give this view more time in case the loading view takes a bit to come in. (We don't want
+        // this view to disappear and then the loading view to appear quickly afterwards.)
+        timeoutLength = TimeoutLength.LONG,
     ) {
         override fun isValidNextState(nextState: ChipStateSender): Boolean {
             return nextState == FAR_FROM_RECEIVER ||
@@ -75,6 +77,7 @@
         R.string.media_move_closer_to_end_cast,
         transferStatus = TransferStatus.NOT_STARTED,
         endItem = null,
+        timeoutLength = TimeoutLength.LONG,
     ) {
         override fun isValidNextState(nextState: ChipStateSender): Boolean {
             return nextState == FAR_FROM_RECEIVER ||
@@ -92,7 +95,9 @@
         R.string.media_transfer_playing_different_device,
         transferStatus = TransferStatus.IN_PROGRESS,
         endItem = SenderEndItem.Loading,
-        timeout = TRANSFER_TRIGGERED_TIMEOUT_MILLIS
+        // Give this view more time in case the succeeded/failed view takes a bit to come in. (We
+        // don't want this view to disappear and then the next view to appear quickly afterwards.)
+        timeoutLength = TimeoutLength.LONG,
     ) {
         override fun isValidNextState(nextState: ChipStateSender): Boolean {
             return nextState == FAR_FROM_RECEIVER ||
@@ -111,7 +116,7 @@
         R.string.media_transfer_playing_this_device,
         transferStatus = TransferStatus.IN_PROGRESS,
         endItem = SenderEndItem.Loading,
-        timeout = TRANSFER_TRIGGERED_TIMEOUT_MILLIS
+        timeoutLength = TimeoutLength.LONG,
     ) {
         override fun isValidNextState(nextState: ChipStateSender): Boolean {
             return nextState == FAR_FROM_RECEIVER ||
@@ -325,9 +330,16 @@
     ) : SenderEndItem()
 }
 
-// Give the Transfer*Triggered states a longer timeout since those states represent an active
-// process and we should keep the user informed about it as long as possible (but don't allow it to
-// continue indefinitely).
-private const val TRANSFER_TRIGGERED_TIMEOUT_MILLIS = 30000
+/** Represents how long the chip should be visible before it times out. */
+enum class TimeoutLength {
+    /** A default timeout used for temporary displays at the top of the screen. */
+    DEFAULT,
+    /**
+     * A longer timeout. Should be used when the status is pending (e.g. loading), so that the user
+     * remains informed about the process for longer and so that the UI has more time to resolve the
+     * pending state before disappearing.
+     */
+    LONG,
+}
 
 private const val TAG = "ChipStateSender"
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinator.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinator.kt
index 6bb6906..c7c72a9 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinator.kt
@@ -56,6 +56,9 @@
     private val uiEventLogger: MediaTttSenderUiEventLogger,
 ) : CoreStartable, Dumpable {
 
+    // Since the media transfer display is similar to a heads-up notification, use the same timeout.
+    private val defaultTimeout = context.resources.getInteger(R.integer.heads_up_notification_decay)
+
     // A map to store current chip state per id.
     private var stateMap: MutableMap<String, ChipStateSender> = mutableMapOf()
 
@@ -165,6 +168,12 @@
                 logger.logPackageNotFound(packageName)
             }
 
+        val timeout =
+            when (chipStateSender.timeoutLength) {
+                TimeoutLength.DEFAULT -> defaultTimeout
+                TimeoutLength.LONG -> 2 * defaultTimeout
+            }
+
         return ChipbarInfo(
             // Display the app's icon as the start icon
             startIcon = icon.toTintedIcon(),
@@ -191,7 +200,7 @@
             allowSwipeToDismiss = true,
             windowTitle = MediaTttUtils.WINDOW_TITLE_SENDER,
             wakeReason = MediaTttUtils.WAKE_REASON_SENDER,
-            timeoutMs = chipStateSender.timeout,
+            timeoutMs = timeout,
             id = routeInfo.id,
             priority = ViewPriority.NORMAL,
         )
diff --git a/packages/SystemUI/src/com/android/systemui/multishade/data/model/MultiShadeInteractionModel.kt b/packages/SystemUI/src/com/android/systemui/multishade/data/model/MultiShadeInteractionModel.kt
new file mode 100644
index 0000000..c48028c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/multishade/data/model/MultiShadeInteractionModel.kt
@@ -0,0 +1,28 @@
+/*
+ * 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.multishade.data.model
+
+import com.android.systemui.multishade.shared.model.ShadeId
+
+/** Models the current interaction with one of the shades. */
+data class MultiShadeInteractionModel(
+    /** The ID of the shade that the user is currently interacting with. */
+    val shadeId: ShadeId,
+    /** Whether the interaction is proxied (as in: coming from an external app or different UI). */
+    val isProxied: Boolean,
+)
diff --git a/packages/SystemUI/src/com/android/systemui/multishade/data/remoteproxy/MultiShadeInputProxy.kt b/packages/SystemUI/src/com/android/systemui/multishade/data/remoteproxy/MultiShadeInputProxy.kt
new file mode 100644
index 0000000..86f0c0d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/multishade/data/remoteproxy/MultiShadeInputProxy.kt
@@ -0,0 +1,47 @@
+/*
+ * 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.multishade.data.remoteproxy
+
+import com.android.systemui.multishade.shared.model.ProxiedInputModel
+import javax.inject.Inject
+import javax.inject.Singleton
+import kotlinx.coroutines.channels.BufferOverflow
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableSharedFlow
+import kotlinx.coroutines.flow.asSharedFlow
+
+/**
+ * Acts as a hub for routing proxied user input into the multi shade system.
+ *
+ * "Proxied" user input is coming through a proxy; typically from an external app or different UI.
+ * In other words: it's not user input that's occurring directly on the shade UI itself. This class
+ * is that proxy.
+ */
+@Singleton
+class MultiShadeInputProxy @Inject constructor() {
+    private val _proxiedTouch =
+        MutableSharedFlow<ProxiedInputModel>(
+            replay = 1,
+            onBufferOverflow = BufferOverflow.DROP_OLDEST,
+        )
+    val proxiedInput: Flow<ProxiedInputModel> = _proxiedTouch.asSharedFlow()
+
+    fun onProxiedInput(proxiedInput: ProxiedInputModel) {
+        _proxiedTouch.tryEmit(proxiedInput)
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/multishade/data/repository/MultiShadeRepository.kt b/packages/SystemUI/src/com/android/systemui/multishade/data/repository/MultiShadeRepository.kt
new file mode 100644
index 0000000..1172030
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/multishade/data/repository/MultiShadeRepository.kt
@@ -0,0 +1,157 @@
+/*
+ * 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.multishade.data.repository
+
+import android.content.Context
+import androidx.annotation.FloatRange
+import com.android.systemui.R
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.multishade.data.model.MultiShadeInteractionModel
+import com.android.systemui.multishade.data.remoteproxy.MultiShadeInputProxy
+import com.android.systemui.multishade.shared.model.ProxiedInputModel
+import com.android.systemui.multishade.shared.model.ShadeConfig
+import com.android.systemui.multishade.shared.model.ShadeId
+import com.android.systemui.multishade.shared.model.ShadeModel
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
+
+/** Encapsulates application state for all shades. */
+@SysUISingleton
+class MultiShadeRepository
+@Inject
+constructor(
+    @Application private val applicationContext: Context,
+    inputProxy: MultiShadeInputProxy,
+) {
+    /**
+     * Remote input coming from sources outside of system UI (for example, swiping down on the
+     * Launcher or from the status bar).
+     */
+    val proxiedInput: Flow<ProxiedInputModel> = inputProxy.proxiedInput
+
+    /** Width of the left-hand side shade, in pixels. */
+    private val leftShadeWidthPx =
+        applicationContext.resources.getDimensionPixelSize(R.dimen.left_shade_width)
+
+    /** Width of the right-hand side shade, in pixels. */
+    private val rightShadeWidthPx =
+        applicationContext.resources.getDimensionPixelSize(R.dimen.right_shade_width)
+
+    /**
+     * The amount that the user must swipe up when the shade is fully expanded to automatically
+     * collapse once the user lets go of the shade. If the user swipes less than this amount, the
+     * shade will automatically revert back to fully expanded once the user stops swiping.
+     *
+     * This is a fraction between `0` and `1`.
+     */
+    private val swipeCollapseThreshold =
+        checkInBounds(applicationContext.resources.getFloat(R.dimen.shade_swipe_collapse_threshold))
+
+    /**
+     * The amount that the user must swipe down when the shade is fully collapsed to automatically
+     * expand once the user lets go of the shade. If the user swipes less than this amount, the
+     * shade will automatically revert back to fully collapsed once the user stops swiping.
+     *
+     * This is a fraction between `0` and `1`.
+     */
+    private val swipeExpandThreshold =
+        checkInBounds(applicationContext.resources.getFloat(R.dimen.shade_swipe_expand_threshold))
+
+    /**
+     * Maximum opacity when the scrim that shows up behind the dual shades is fully visible.
+     *
+     * This is a fraction between `0` and `1`.
+     */
+    private val dualShadeScrimAlpha =
+        checkInBounds(applicationContext.resources.getFloat(R.dimen.dual_shade_scrim_alpha))
+
+    /** The current configuration of the shade system. */
+    val shadeConfig: StateFlow<ShadeConfig> =
+        MutableStateFlow(
+                if (applicationContext.resources.getBoolean(R.bool.dual_shade_enabled)) {
+                    ShadeConfig.DualShadeConfig(
+                        leftShadeWidthPx = leftShadeWidthPx,
+                        rightShadeWidthPx = rightShadeWidthPx,
+                        swipeCollapseThreshold = swipeCollapseThreshold,
+                        swipeExpandThreshold = swipeExpandThreshold,
+                        splitFraction =
+                            applicationContext.resources.getFloat(
+                                R.dimen.dual_shade_split_fraction
+                            ),
+                        scrimAlpha = dualShadeScrimAlpha,
+                    )
+                } else {
+                    ShadeConfig.SingleShadeConfig(
+                        swipeCollapseThreshold = swipeCollapseThreshold,
+                        swipeExpandThreshold = swipeExpandThreshold,
+                    )
+                }
+            )
+            .asStateFlow()
+
+    private val _forceCollapseAll = MutableStateFlow(false)
+    /** Whether all shades should be collapsed. */
+    val forceCollapseAll: StateFlow<Boolean> = _forceCollapseAll.asStateFlow()
+
+    private val _shadeInteraction = MutableStateFlow<MultiShadeInteractionModel?>(null)
+    /** The current shade interaction or `null` if no shade is interacted with currently. */
+    val shadeInteraction: StateFlow<MultiShadeInteractionModel?> = _shadeInteraction.asStateFlow()
+
+    private val stateByShade = mutableMapOf<ShadeId, MutableStateFlow<ShadeModel>>()
+
+    /** The model for the shade with the given ID. */
+    fun getShade(
+        shadeId: ShadeId,
+    ): StateFlow<ShadeModel> {
+        return getMutableShade(shadeId).asStateFlow()
+    }
+
+    /** Sets the expansion amount for the shade with the given ID. */
+    fun setExpansion(
+        shadeId: ShadeId,
+        @FloatRange(from = 0.0, to = 1.0) expansion: Float,
+    ) {
+        getMutableShade(shadeId).let { mutableState ->
+            mutableState.value = mutableState.value.copy(expansion = expansion)
+        }
+    }
+
+    /** Sets whether all shades should be immediately forced to collapse. */
+    fun setForceCollapseAll(isForced: Boolean) {
+        _forceCollapseAll.value = isForced
+    }
+
+    /** Sets the current shade interaction; use `null` if no shade is interacted with currently. */
+    fun setShadeInteraction(shadeInteraction: MultiShadeInteractionModel?) {
+        _shadeInteraction.value = shadeInteraction
+    }
+
+    private fun getMutableShade(id: ShadeId): MutableStateFlow<ShadeModel> {
+        return stateByShade.getOrPut(id) { MutableStateFlow(ShadeModel(id)) }
+    }
+
+    /** Asserts that the given [Float] is in the range of `0` and `1`, inclusive. */
+    private fun checkInBounds(float: Float): Float {
+        check(float in 0f..1f) { "$float isn't between 0 and 1." }
+        return float
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/multishade/domain/interactor/MultiShadeInteractor.kt b/packages/SystemUI/src/com/android/systemui/multishade/domain/interactor/MultiShadeInteractor.kt
new file mode 100644
index 0000000..b9f6d83
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/multishade/domain/interactor/MultiShadeInteractor.kt
@@ -0,0 +1,322 @@
+/*
+ * 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.multishade.domain.interactor
+
+import androidx.annotation.FloatRange
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.multishade.data.model.MultiShadeInteractionModel
+import com.android.systemui.multishade.data.remoteproxy.MultiShadeInputProxy
+import com.android.systemui.multishade.data.repository.MultiShadeRepository
+import com.android.systemui.multishade.shared.model.ProxiedInputModel
+import com.android.systemui.multishade.shared.model.ShadeConfig
+import com.android.systemui.multishade.shared.model.ShadeId
+import com.android.systemui.multishade.shared.model.ShadeModel
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.SharedFlow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.flow.shareIn
+import kotlinx.coroutines.yield
+
+/** Encapsulates business logic related to interactions with the multi-shade system. */
+@OptIn(ExperimentalCoroutinesApi::class)
+@SysUISingleton
+class MultiShadeInteractor
+@Inject
+constructor(
+    @Application private val applicationScope: CoroutineScope,
+    private val repository: MultiShadeRepository,
+    private val inputProxy: MultiShadeInputProxy,
+) {
+    /** The current configuration of the shade system. */
+    val shadeConfig: StateFlow<ShadeConfig> = repository.shadeConfig
+
+    /** The expansion of the shade that's most expanded. */
+    val maxShadeExpansion: Flow<Float> =
+        repository.shadeConfig.flatMapLatest { shadeConfig ->
+            combine(allShades(shadeConfig)) { shadeModels ->
+                shadeModels.maxOfOrNull { it.expansion } ?: 0f
+            }
+        }
+
+    /**
+     * A _processed_ version of the proxied input flow.
+     *
+     * All internal dependencies on the proxied input flow *must* use this one for two reasons:
+     * 1. It's a [SharedFlow] so we only do the upstream work once, no matter how many usages we
+     *    actually have.
+     * 2. It actually does some preprocessing as the proxied input events stream through, handling
+     *    common things like recording the current state of the system based on incoming input
+     *    events.
+     */
+    private val processedProxiedInput: SharedFlow<ProxiedInputModel> =
+        combine(
+                repository.shadeConfig,
+                repository.proxiedInput.distinctUntilChanged(),
+                ::Pair,
+            )
+            .map { (shadeConfig, proxiedInput) ->
+                if (proxiedInput !is ProxiedInputModel.OnTap) {
+                    // If the user is interacting with any other gesture type (for instance,
+                    // dragging),
+                    // we no longer want to force collapse all shades.
+                    repository.setForceCollapseAll(false)
+                }
+
+                when (proxiedInput) {
+                    is ProxiedInputModel.OnDrag -> {
+                        val affectedShadeId = affectedShadeId(shadeConfig, proxiedInput.xFraction)
+                        // This might be the start of a new drag gesture, let's update our
+                        // application
+                        // state to record that fact.
+                        onUserInteractionStarted(
+                            shadeId = affectedShadeId,
+                            isProxied = true,
+                        )
+                    }
+                    is ProxiedInputModel.OnTap -> {
+                        // Tapping outside any shade collapses all shades. This code path is not hit
+                        // for
+                        // taps that happen _inside_ a shade as that input event is directly applied
+                        // through the UI and is, hence, not a proxied input.
+                        collapseAll()
+                    }
+                    else -> Unit
+                }
+
+                proxiedInput
+            }
+            .shareIn(
+                scope = applicationScope,
+                started = SharingStarted.Eagerly,
+                replay = 1,
+            )
+
+    /** Whether the shade with the given ID should be visible. */
+    fun isVisible(shadeId: ShadeId): Flow<Boolean> {
+        return repository.shadeConfig.map { shadeConfig -> shadeConfig.shadeIds.contains(shadeId) }
+    }
+
+    /** Whether direct user input is allowed on the shade with the given ID. */
+    fun isNonProxiedInputAllowed(shadeId: ShadeId): Flow<Boolean> {
+        return combine(
+                isForceCollapsed(shadeId),
+                repository.shadeInteraction,
+                ::Pair,
+            )
+            .map { (isForceCollapsed, shadeInteraction) ->
+                !isForceCollapsed && shadeInteraction?.isProxied != true
+            }
+    }
+
+    /** Whether the shade with the given ID is forced to collapse. */
+    fun isForceCollapsed(shadeId: ShadeId): Flow<Boolean> {
+        return combine(
+                repository.forceCollapseAll,
+                repository.shadeInteraction.map { it?.shadeId },
+                ::Pair,
+            )
+            .map { (collapseAll, userInteractedShadeIdOrNull) ->
+                val counterpartShadeIdOrNull =
+                    when (shadeId) {
+                        ShadeId.SINGLE -> null
+                        ShadeId.LEFT -> ShadeId.RIGHT
+                        ShadeId.RIGHT -> ShadeId.LEFT
+                    }
+
+                when {
+                    // If all shades have been told to collapse (by a tap outside, for example),
+                    // then this shade is collapsed.
+                    collapseAll -> true
+                    // A shade that doesn't have a counterpart shade cannot be force-collapsed by
+                    // interactions on the counterpart shade.
+                    counterpartShadeIdOrNull == null -> false
+                    // If the current user interaction is on the counterpart shade, then this shade
+                    // should be force-collapsed.
+                    else -> userInteractedShadeIdOrNull == counterpartShadeIdOrNull
+                }
+            }
+    }
+
+    /**
+     * Proxied input affecting the shade with the given ID. This is input coming from sources
+     * outside of system UI (for example, swiping down on the Launcher or from the status bar) or
+     * outside the UI of any shade (for example, the scrim that's shown behind the shades).
+     */
+    fun proxiedInput(shadeId: ShadeId): Flow<ProxiedInputModel?> {
+        return combine(
+                processedProxiedInput,
+                isForceCollapsed(shadeId).distinctUntilChanged(),
+                repository.shadeInteraction,
+                ::Triple,
+            )
+            .map { (proxiedInput, isForceCollapsed, shadeInteraction) ->
+                when {
+                    // If the shade is force-collapsed, we ignored proxied input on it.
+                    isForceCollapsed -> null
+                    // If the proxied input does not belong to this shade, ignore it.
+                    shadeInteraction?.shadeId != shadeId -> null
+                    // If there is ongoing non-proxied user input on any shade, ignore the
+                    // proxied input.
+                    !shadeInteraction.isProxied -> null
+                    // Otherwise, send the proxied input downstream.
+                    else -> proxiedInput
+                }
+            }
+            .onEach { proxiedInput ->
+                // We use yield() to make sure that the following block of code happens _after_
+                // downstream collectors had a chance to process the proxied input. Otherwise, we
+                // might change our state to clear the current UserInteraction _before_ those
+                // downstream collectors get a chance to process the proxied input, which will make
+                // them ignore it (since they ignore proxied input when the current user interaction
+                // doesn't match their shade).
+                yield()
+
+                if (
+                    proxiedInput is ProxiedInputModel.OnDragEnd ||
+                        proxiedInput is ProxiedInputModel.OnDragCancel
+                ) {
+                    onUserInteractionEnded(shadeId = shadeId, isProxied = true)
+                }
+            }
+    }
+
+    /** Sets the expansion amount for the shade with the given ID. */
+    fun setExpansion(
+        shadeId: ShadeId,
+        @FloatRange(from = 0.0, to = 1.0) expansion: Float,
+    ) {
+        repository.setExpansion(shadeId, expansion)
+    }
+
+    /** Collapses all shades. */
+    fun collapseAll() {
+        repository.setForceCollapseAll(true)
+    }
+
+    /**
+     * Notifies that a new non-proxied interaction may have started. Safe to call multiple times for
+     * the same interaction as it won't overwrite an existing interaction.
+     *
+     * Existing interactions can be cleared by calling [onUserInteractionEnded].
+     */
+    fun onUserInteractionStarted(shadeId: ShadeId) {
+        onUserInteractionStarted(
+            shadeId = shadeId,
+            isProxied = false,
+        )
+    }
+
+    /**
+     * Notifies that the current non-proxied interaction has ended.
+     *
+     * Safe to call multiple times, even if there's no current interaction or even if the current
+     * interaction doesn't belong to the given shade or is proxied as the code is a no-op unless
+     * there's a match between the parameters and the current interaction.
+     */
+    fun onUserInteractionEnded(
+        shadeId: ShadeId,
+    ) {
+        onUserInteractionEnded(
+            shadeId = shadeId,
+            isProxied = false,
+        )
+    }
+
+    fun sendProxiedInput(proxiedInput: ProxiedInputModel) {
+        inputProxy.onProxiedInput(proxiedInput)
+    }
+
+    /**
+     * Notifies that a new interaction may have started. Safe to call multiple times for the same
+     * interaction as it won't overwrite an existing interaction.
+     *
+     * Existing interactions can be cleared by calling [onUserInteractionEnded].
+     */
+    private fun onUserInteractionStarted(
+        shadeId: ShadeId,
+        isProxied: Boolean,
+    ) {
+        if (repository.shadeInteraction.value != null) {
+            return
+        }
+
+        repository.setShadeInteraction(
+            MultiShadeInteractionModel(
+                shadeId = shadeId,
+                isProxied = isProxied,
+            )
+        )
+    }
+
+    /**
+     * Notifies that the current interaction has ended.
+     *
+     * Safe to call multiple times, even if there's no current interaction or even if the current
+     * interaction doesn't belong to the given shade or [isProxied] value as the code is a no-op
+     * unless there's a match between the parameters and the current interaction.
+     */
+    private fun onUserInteractionEnded(
+        shadeId: ShadeId,
+        isProxied: Boolean,
+    ) {
+        repository.shadeInteraction.value?.let { (interactionShadeId, isInteractionProxied) ->
+            if (shadeId == interactionShadeId && isProxied == isInteractionProxied) {
+                repository.setShadeInteraction(null)
+            }
+        }
+    }
+
+    /**
+     * Returns the ID of the shade that's affected by user input at a given coordinate.
+     *
+     * @param config The shade configuration being used.
+     * @param xFraction The horizontal position of the user input as a fraction along the width of
+     *   its container where `0` is all the way to the left and `1` is all the way to the right.
+     */
+    private fun affectedShadeId(
+        config: ShadeConfig,
+        @FloatRange(from = 0.0, to = 1.0) xFraction: Float,
+    ): ShadeId {
+        return if (config is ShadeConfig.DualShadeConfig) {
+            if (xFraction <= config.splitFraction) {
+                ShadeId.LEFT
+            } else {
+                ShadeId.RIGHT
+            }
+        } else {
+            ShadeId.SINGLE
+        }
+    }
+
+    /** Returns the list of flows of all the shades in the given configuration. */
+    private fun allShades(
+        config: ShadeConfig,
+    ): List<Flow<ShadeModel>> {
+        return config.shadeIds.map { shadeId -> repository.getShade(shadeId) }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/multishade/shared/model/ProxiedInputModel.kt b/packages/SystemUI/src/com/android/systemui/multishade/shared/model/ProxiedInputModel.kt
new file mode 100644
index 0000000..ee1dd65
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/multishade/shared/model/ProxiedInputModel.kt
@@ -0,0 +1,50 @@
+/*
+ * 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.multishade.shared.model
+
+import androidx.annotation.FloatRange
+
+/**
+ * Models a part of an ongoing proxied user input gesture.
+ *
+ * "Proxied" user input is coming through a proxy; typically from an external app or different UI.
+ * In other words: it's not user input that's occurring directly on the shade UI itself.
+ */
+sealed class ProxiedInputModel {
+    /** The user is dragging their pointer. */
+    data class OnDrag(
+        /**
+         * The relative position of the pointer as a fraction of its container width where `0` is
+         * all the way to the left and `1` is all the way to the right.
+         */
+        @FloatRange(from = 0.0, to = 1.0) val xFraction: Float,
+        /** The amount that the pointer was dragged, in pixels. */
+        val yDragAmountPx: Float,
+    ) : ProxiedInputModel()
+
+    /** The user finished dragging by lifting up their pointer. */
+    object OnDragEnd : ProxiedInputModel()
+
+    /**
+     * The drag gesture has been canceled. Usually because the pointer exited the draggable area.
+     */
+    object OnDragCancel : ProxiedInputModel()
+
+    /** The user has tapped (clicked). */
+    object OnTap : ProxiedInputModel()
+}
diff --git a/packages/SystemUI/src/com/android/systemui/multishade/shared/model/ShadeConfig.kt b/packages/SystemUI/src/com/android/systemui/multishade/shared/model/ShadeConfig.kt
new file mode 100644
index 0000000..a4cd35c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/multishade/shared/model/ShadeConfig.kt
@@ -0,0 +1,79 @@
+/*
+ * 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.multishade.shared.model
+
+import androidx.annotation.FloatRange
+
+/** Enumerates the various possible configurations of the shade system. */
+sealed class ShadeConfig(
+
+    /** IDs of the shade(s) in this configuration. */
+    open val shadeIds: List<ShadeId>,
+
+    /**
+     * The amount that the user must swipe up when the shade is fully expanded to automatically
+     * collapse once the user lets go of the shade. If the user swipes less than this amount, the
+     * shade will automatically revert back to fully expanded once the user stops swiping.
+     */
+    @FloatRange(from = 0.0, to = 1.0) open val swipeCollapseThreshold: Float,
+
+    /**
+     * The amount that the user must swipe down when the shade is fully collapsed to automatically
+     * expand once the user lets go of the shade. If the user swipes less than this amount, the
+     * shade will automatically revert back to fully collapsed once the user stops swiping.
+     */
+    @FloatRange(from = 0.0, to = 1.0) open val swipeExpandThreshold: Float,
+) {
+
+    /** There is a single shade. */
+    data class SingleShadeConfig(
+        @FloatRange(from = 0.0, to = 1.0) override val swipeCollapseThreshold: Float,
+        @FloatRange(from = 0.0, to = 1.0) override val swipeExpandThreshold: Float,
+    ) :
+        ShadeConfig(
+            shadeIds = listOf(ShadeId.SINGLE),
+            swipeCollapseThreshold = swipeCollapseThreshold,
+            swipeExpandThreshold = swipeExpandThreshold,
+        )
+
+    /** There are two shades arranged side-by-side. */
+    data class DualShadeConfig(
+        /** Width of the left-hand side shade. */
+        val leftShadeWidthPx: Int,
+        /** Width of the right-hand side shade. */
+        val rightShadeWidthPx: Int,
+        @FloatRange(from = 0.0, to = 1.0) override val swipeCollapseThreshold: Float,
+        @FloatRange(from = 0.0, to = 1.0) override val swipeExpandThreshold: Float,
+        /**
+         * The position of the "split" between interaction areas for each of the shades, as a
+         * fraction of the width of the container.
+         *
+         * Interactions that occur on the start-side (left-hand side in left-to-right languages like
+         * English) affect the start-side shade. Interactions that occur on the end-side (right-hand
+         * side in left-to-right languages like English) affect the end-side shade.
+         */
+        @FloatRange(from = 0.0, to = 1.0) val splitFraction: Float,
+        /** Maximum opacity when the scrim that shows up behind the dual shades is fully visible. */
+        @FloatRange(from = 0.0, to = 1.0) val scrimAlpha: Float,
+    ) :
+        ShadeConfig(
+            shadeIds = listOf(ShadeId.LEFT, ShadeId.RIGHT),
+            swipeCollapseThreshold = swipeCollapseThreshold,
+            swipeExpandThreshold = swipeExpandThreshold,
+        )
+}
diff --git a/packages/SystemUI/src/com/android/systemui/multishade/shared/model/ShadeId.kt b/packages/SystemUI/src/com/android/systemui/multishade/shared/model/ShadeId.kt
new file mode 100644
index 0000000..9e02657
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/multishade/shared/model/ShadeId.kt
@@ -0,0 +1,28 @@
+/*
+ * 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.multishade.shared.model
+
+/** Enumerates all known shade IDs. */
+enum class ShadeId {
+    /** ID of the shade on the left in dual shade configurations. */
+    LEFT,
+    /** ID of the shade on the right in dual shade configurations. */
+    RIGHT,
+    /** ID of the single shade in single shade configurations. */
+    SINGLE,
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardBouncerModel.kt b/packages/SystemUI/src/com/android/systemui/multishade/shared/model/ShadeModel.kt
similarity index 61%
rename from packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardBouncerModel.kt
rename to packages/SystemUI/src/com/android/systemui/multishade/shared/model/ShadeModel.kt
index ad783da..49ac64c 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardBouncerModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/multishade/shared/model/ShadeModel.kt
@@ -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.
@@ -11,14 +11,16 @@
  * 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
+ * limitations under the License.
+ *
  */
 
-package com.android.systemui.keyguard.shared.model
+package com.android.systemui.multishade.shared.model
 
-/** Models the state of the lock-screen bouncer */
-data class KeyguardBouncerModel(
-    val promptReason: Int = 0,
-    val errorMessage: CharSequence? = null,
-    val expansionAmount: Float = 0f,
+import androidx.annotation.FloatRange
+
+/** Models the current state of a shade. */
+data class ShadeModel(
+    val id: ShadeId,
+    @FloatRange(from = 0.0, to = 1.0) val expansion: Float = 0f,
 )
diff --git a/packages/SystemUI/src/com/android/systemui/multishade/ui/viewmodel/MultiShadeViewModel.kt b/packages/SystemUI/src/com/android/systemui/multishade/ui/viewmodel/MultiShadeViewModel.kt
new file mode 100644
index 0000000..ce6ab97
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/multishade/ui/viewmodel/MultiShadeViewModel.kt
@@ -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 com.android.systemui.multishade.ui.viewmodel
+
+import com.android.systemui.multishade.domain.interactor.MultiShadeInteractor
+import com.android.systemui.multishade.shared.model.ProxiedInputModel
+import com.android.systemui.multishade.shared.model.ShadeConfig
+import com.android.systemui.multishade.shared.model.ShadeId
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.stateIn
+
+/** Models UI state for UI that supports multi (or single) shade. */
+@OptIn(ExperimentalCoroutinesApi::class)
+class MultiShadeViewModel(
+    viewModelScope: CoroutineScope,
+    private val interactor: MultiShadeInteractor,
+) {
+    /** Models UI state for the single shade. */
+    val singleShade =
+        ShadeViewModel(
+            viewModelScope,
+            ShadeId.SINGLE,
+            interactor,
+        )
+
+    /** Models UI state for the shade on the left-hand side. */
+    val leftShade =
+        ShadeViewModel(
+            viewModelScope,
+            ShadeId.LEFT,
+            interactor,
+        )
+
+    /** Models UI state for the shade on the right-hand side. */
+    val rightShade =
+        ShadeViewModel(
+            viewModelScope,
+            ShadeId.RIGHT,
+            interactor,
+        )
+
+    /** The amount of alpha that the scrim should have. This is a value between `0` and `1`. */
+    val scrimAlpha: StateFlow<Float> =
+        combine(
+                interactor.maxShadeExpansion,
+                interactor.shadeConfig
+                    .map { it as? ShadeConfig.DualShadeConfig }
+                    .map { dualShadeConfigOrNull -> dualShadeConfigOrNull?.scrimAlpha ?: 0f },
+                ::Pair,
+            )
+            .map { (anyShadeExpansion, scrimAlpha) ->
+                (anyShadeExpansion * scrimAlpha).coerceIn(0f, 1f)
+            }
+            .stateIn(
+                scope = viewModelScope,
+                started = SharingStarted.WhileSubscribed(),
+                initialValue = 0f,
+            )
+
+    /** Whether the scrim should accept touch events. */
+    val isScrimEnabled: StateFlow<Boolean> =
+        interactor.shadeConfig
+            .flatMapLatest { shadeConfig ->
+                when (shadeConfig) {
+                    // In the dual shade configuration, the scrim is enabled when the expansion is
+                    // greater than zero on any one of the shades.
+                    is ShadeConfig.DualShadeConfig ->
+                        interactor.maxShadeExpansion
+                            .map { expansion -> expansion > 0 }
+                            .distinctUntilChanged()
+                    // No scrim in the single shade configuration.
+                    is ShadeConfig.SingleShadeConfig -> flowOf(false)
+                }
+            }
+            .stateIn(
+                scope = viewModelScope,
+                started = SharingStarted.WhileSubscribed(),
+                initialValue = false,
+            )
+
+    /** Notifies that the scrim has been touched. */
+    fun onScrimTouched(proxiedInput: ProxiedInputModel) {
+        if (!isScrimEnabled.value) {
+            return
+        }
+
+        interactor.sendProxiedInput(proxiedInput)
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/multishade/ui/viewmodel/ShadeViewModel.kt b/packages/SystemUI/src/com/android/systemui/multishade/ui/viewmodel/ShadeViewModel.kt
new file mode 100644
index 0000000..e828dbd
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/multishade/ui/viewmodel/ShadeViewModel.kt
@@ -0,0 +1,150 @@
+/*
+ * 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.multishade.ui.viewmodel
+
+import androidx.annotation.FloatRange
+import com.android.systemui.multishade.domain.interactor.MultiShadeInteractor
+import com.android.systemui.multishade.shared.model.ProxiedInputModel
+import com.android.systemui.multishade.shared.model.ShadeConfig
+import com.android.systemui.multishade.shared.model.ShadeId
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.stateIn
+
+/** Models UI state for a single shade. */
+class ShadeViewModel(
+    viewModelScope: CoroutineScope,
+    private val shadeId: ShadeId,
+    private val interactor: MultiShadeInteractor,
+) {
+    /** Whether the shade is visible. */
+    val isVisible: StateFlow<Boolean> =
+        interactor
+            .isVisible(shadeId)
+            .stateIn(
+                scope = viewModelScope,
+                started = SharingStarted.WhileSubscribed(),
+                initialValue = false,
+            )
+
+    /** Whether swiping on the shade UI is currently enabled. */
+    val isSwipingEnabled: StateFlow<Boolean> =
+        interactor
+            .isNonProxiedInputAllowed(shadeId)
+            .stateIn(
+                scope = viewModelScope,
+                started = SharingStarted.WhileSubscribed(),
+                initialValue = false,
+            )
+
+    /** Whether the shade must be collapsed immediately. */
+    val isForceCollapsed: Flow<Boolean> =
+        interactor.isForceCollapsed(shadeId).distinctUntilChanged()
+
+    /** The width of the shade. */
+    val width: StateFlow<Size> =
+        interactor.shadeConfig
+            .map { shadeWidth(it) }
+            .stateIn(
+                scope = viewModelScope,
+                started = SharingStarted.WhileSubscribed(),
+                initialValue = shadeWidth(interactor.shadeConfig.value),
+            )
+
+    /**
+     * The amount that the user must swipe up when the shade is fully expanded to automatically
+     * collapse once the user lets go of the shade. If the user swipes less than this amount, the
+     * shade will automatically revert back to fully expanded once the user stops swiping.
+     */
+    val swipeCollapseThreshold: StateFlow<Float> =
+        interactor.shadeConfig
+            .map { it.swipeCollapseThreshold }
+            .stateIn(
+                scope = viewModelScope,
+                started = SharingStarted.WhileSubscribed(),
+                initialValue = interactor.shadeConfig.value.swipeCollapseThreshold,
+            )
+
+    /**
+     * The amount that the user must swipe down when the shade is fully collapsed to automatically
+     * expand once the user lets go of the shade. If the user swipes less than this amount, the
+     * shade will automatically revert back to fully collapsed once the user stops swiping.
+     */
+    val swipeExpandThreshold: StateFlow<Float> =
+        interactor.shadeConfig
+            .map { it.swipeExpandThreshold }
+            .stateIn(
+                scope = viewModelScope,
+                started = SharingStarted.WhileSubscribed(),
+                initialValue = interactor.shadeConfig.value.swipeExpandThreshold,
+            )
+
+    /**
+     * Proxied input affecting the shade. This is input coming from sources outside of system UI
+     * (for example, swiping down on the Launcher or from the status bar) or outside the UI of any
+     * shade (for example, the scrim that's shown behind the shades).
+     */
+    val proxiedInput: Flow<ProxiedInputModel?> =
+        interactor
+            .proxiedInput(shadeId)
+            .stateIn(
+                scope = viewModelScope,
+                started = SharingStarted.WhileSubscribed(),
+                initialValue = null,
+            )
+
+    /** Notifies that the expansion amount for the shade has changed. */
+    fun onExpansionChanged(
+        expansion: Float,
+    ) {
+        interactor.setExpansion(shadeId, expansion.coerceIn(0f, 1f))
+    }
+
+    /** Notifies that a drag gesture has started. */
+    fun onDragStarted() {
+        interactor.onUserInteractionStarted(shadeId)
+    }
+
+    /** Notifies that a drag gesture has ended. */
+    fun onDragEnded() {
+        interactor.onUserInteractionEnded(shadeId = shadeId)
+    }
+
+    private fun shadeWidth(shadeConfig: ShadeConfig): Size {
+        return when (shadeId) {
+            ShadeId.LEFT ->
+                Size.Pixels((shadeConfig as? ShadeConfig.DualShadeConfig)?.leftShadeWidthPx ?: 0)
+            ShadeId.RIGHT ->
+                Size.Pixels((shadeConfig as? ShadeConfig.DualShadeConfig)?.rightShadeWidthPx ?: 0)
+            ShadeId.SINGLE -> Size.Fraction(1f)
+        }
+    }
+
+    sealed class Size {
+        data class Fraction(
+            @FloatRange(from = 0.0, to = 1.0) val fraction: Float,
+        ) : Size()
+        data class Pixels(
+            val pixels: Int,
+        ) : Size()
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavBarHelper.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavBarHelper.java
index 1121e160..1da8718 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavBarHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavBarHelper.java
@@ -41,11 +41,17 @@
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.Looper;
+import android.os.RemoteException;
 import android.os.UserHandle;
 import android.provider.Settings;
 import android.provider.Settings.Secure;
+import android.util.Log;
+import android.view.IRotationWatcher;
+import android.view.IWallpaperVisibilityListener;
+import android.view.IWindowManager;
 import android.view.View;
 import android.view.WindowInsets;
+import android.view.WindowManagerGlobal;
 import android.view.accessibility.AccessibilityManager;
 
 import androidx.annotation.NonNull;
@@ -57,7 +63,9 @@
 import com.android.systemui.assist.AssistManager;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dump.DumpManager;
+import com.android.systemui.navigationbar.gestural.EdgeBackGestureHandler;
 import com.android.systemui.recents.OverviewProxyService;
+import com.android.systemui.settings.DisplayTracker;
 import com.android.systemui.settings.UserTracker;
 import com.android.systemui.shared.system.QuickStepContract;
 import com.android.systemui.statusbar.CommandQueue;
@@ -90,6 +98,9 @@
         AccessibilityButtonTargetsObserver.TargetsChangedListener,
         OverviewProxyService.OverviewProxyListener, NavigationModeController.ModeChangedListener,
         Dumpable, CommandQueue.Callbacks {
+    private static final String TAG = NavBarHelper.class.getSimpleName();
+
+    private final Handler mHandler = new Handler(Looper.getMainLooper());
     private final AccessibilityManager mAccessibilityManager;
     private final Lazy<AssistManager> mAssistManagerLazy;
     private final Lazy<Optional<CentralSurfaces>> mCentralSurfacesOptionalLazy;
@@ -98,28 +109,60 @@
     private final SystemActions mSystemActions;
     private final AccessibilityButtonModeObserver mAccessibilityButtonModeObserver;
     private final AccessibilityButtonTargetsObserver mAccessibilityButtonTargetsObserver;
-    private final List<NavbarTaskbarStateUpdater> mA11yEventListeners = new ArrayList<>();
+    private final List<NavbarTaskbarStateUpdater> mStateListeners = new ArrayList<>();
     private final Context mContext;
     private final CommandQueue mCommandQueue;
     private final ContentResolver mContentResolver;
+    private final EdgeBackGestureHandler mEdgeBackGestureHandler;
+    private final IWindowManager mWm;
+    private final int mDefaultDisplayId;
     private boolean mAssistantAvailable;
     private boolean mLongPressHomeEnabled;
     private boolean mAssistantTouchGestureEnabled;
     private int mNavBarMode;
     private int mA11yButtonState;
+    private int mRotationWatcherRotation;
+    private boolean mTogglingNavbarTaskbar;
+    private boolean mWallpaperVisible;
 
     // Attributes used in NavBarHelper.CurrentSysuiState
     private int mWindowStateDisplayId;
     private @WindowVisibleState int mWindowState;
 
-    private final ContentObserver mAssistContentObserver = new ContentObserver(
-            new Handler(Looper.getMainLooper())) {
+    // Listens for changes to the assistant
+    private final ContentObserver mAssistContentObserver = new ContentObserver(mHandler) {
         @Override
         public void onChange(boolean selfChange, Uri uri) {
             updateAssistantAvailability();
         }
     };
 
+    // Listens for changes to the wallpaper visibility
+    private final IWallpaperVisibilityListener mWallpaperVisibilityListener =
+            new IWallpaperVisibilityListener.Stub() {
+                @Override
+                public void onWallpaperVisibilityChanged(boolean visible,
+                        int displayId) throws RemoteException {
+                    mHandler.post(() -> {
+                        mWallpaperVisible = visible;
+                        dispatchWallpaperVisibilityChanged(visible, displayId);
+                    });
+                }
+            };
+
+    // Listens for changes to display rotation
+    private final IRotationWatcher mRotationWatcher = new IRotationWatcher.Stub() {
+        @Override
+        public void onRotationChanged(final int rotation) {
+            // We need this to be scheduled as early as possible to beat the redrawing of
+            // window in response to the orientation change.
+            mHandler.postAtFrontOfQueue(() -> {
+                mRotationWatcherRotation = rotation;
+                dispatchRotationChanged(rotation);
+            });
+        }
+    };
+
     /**
      * @param context This is not display specific, then again neither is any of the code in
      *                this class. Once there's display specific code, we may want to create an
@@ -135,7 +178,10 @@
             Lazy<Optional<CentralSurfaces>> centralSurfacesOptionalLazy,
             KeyguardStateController keyguardStateController,
             NavigationModeController navigationModeController,
+            EdgeBackGestureHandler.Factory edgeBackGestureHandlerFactory,
+            IWindowManager wm,
             UserTracker userTracker,
+            DisplayTracker displayTracker,
             DumpManager dumpManager,
             CommandQueue commandQueue) {
         mContext = context;
@@ -147,18 +193,36 @@
         mKeyguardStateController = keyguardStateController;
         mUserTracker = userTracker;
         mSystemActions = systemActions;
-        accessibilityManager.addAccessibilityServicesStateChangeListener(this);
         mAccessibilityButtonModeObserver = accessibilityButtonModeObserver;
         mAccessibilityButtonTargetsObserver = accessibilityButtonTargetsObserver;
+        mWm = wm;
+        mDefaultDisplayId = displayTracker.getDefaultDisplayId();
+        mEdgeBackGestureHandler = edgeBackGestureHandlerFactory.create(context);
 
-        mAccessibilityButtonModeObserver.addListener(this);
-        mAccessibilityButtonTargetsObserver.addListener(this);
         mNavBarMode = navigationModeController.addListener(this);
+        mCommandQueue.addCallback(this);
         overviewProxyService.addCallback(this);
         dumpManager.registerDumpable(this);
     }
 
-    public void init() {
+    /**
+     * Hints to the helper that bars are being replaced, which is a signal to potentially suppress
+     * normal setup/cleanup when no bars are present.
+     */
+    public void setTogglingNavbarTaskbar(boolean togglingNavbarTaskbar) {
+        mTogglingNavbarTaskbar = togglingNavbarTaskbar;
+    }
+
+    /**
+     * Called when the first (non-replacing) bar is registered.
+     */
+    private void setupOnFirstBar() {
+        // Setup accessibility listeners
+        mAccessibilityManager.addAccessibilityServicesStateChangeListener(this);
+        mAccessibilityButtonModeObserver.addListener(this);
+        mAccessibilityButtonTargetsObserver.addListener(this);
+
+        // Setup assistant listener
         mContentResolver.registerContentObserver(
                 Settings.Secure.getUriFor(Settings.Secure.ASSISTANT),
                 false /* notifyForDescendants */, mAssistContentObserver, UserHandle.USER_ALL);
@@ -168,59 +232,114 @@
         mContentResolver.registerContentObserver(
                 Settings.Secure.getUriFor(Settings.Secure.ASSIST_TOUCH_GESTURE_ENABLED),
                 false, mAssistContentObserver, UserHandle.USER_ALL);
-        updateAssistantAvailability();
-        updateA11yState();
-        mCommandQueue.addCallback(this);
 
-    }
+        // Setup display rotation watcher
+        try {
+            mWm.watchRotation(mRotationWatcher, mDefaultDisplayId);
+        } catch (Exception e) {
+            Log.w(TAG, "Failed to register rotation watcher", e);
+        }
 
-    public void destroy() {
-        mContentResolver.unregisterContentObserver(mAssistContentObserver);
-        mCommandQueue.removeCallback(this);
+        // Setup wallpaper visibility listener
+        try {
+            mWallpaperVisible = mWm.registerWallpaperVisibilityListener(
+                    mWallpaperVisibilityListener, mDefaultDisplayId);
+        } catch (Exception e) {
+            Log.w(TAG, "Failed to register wallpaper visibility listener", e);
+        }
+
+        // Attach the back handler only when the first bar is registered
+        mEdgeBackGestureHandler.onNavBarAttached();
     }
 
     /**
+     * Called after the last (non-replacing) bar is unregistered.
+     */
+    private void cleanupAfterLastBar() {
+        // Clean up accessibility listeners
+        mAccessibilityManager.removeAccessibilityServicesStateChangeListener(this);
+        mAccessibilityButtonModeObserver.removeListener(this);
+        mAccessibilityButtonTargetsObserver.removeListener(this);
+
+        // Clean up assistant listeners
+        mContentResolver.unregisterContentObserver(mAssistContentObserver);
+
+        // Clean up display rotation watcher
+        try {
+            mWm.removeRotationWatcher(mRotationWatcher);
+        } catch (Exception e) {
+            Log.w(TAG, "Failed to unregister rotation watcher", e);
+        }
+
+        // Clean up wallpaper visibility listener
+        try {
+            mWm.unregisterWallpaperVisibilityListener(mWallpaperVisibilityListener,
+                    mDefaultDisplayId);
+        } catch (Exception e) {
+            Log.w(TAG, "Failed to register wallpaper visibility listener", e);
+        }
+
+        // No more bars, detach the back handler for now
+        mEdgeBackGestureHandler.onNavBarDetached();
+    }
+
+    /**
+     * Registers a listener for future updates to the shared navbar/taskbar state.
      * @param listener Will immediately get callbacks based on current state
      */
     public void registerNavTaskStateUpdater(NavbarTaskbarStateUpdater listener) {
-        mA11yEventListeners.add(listener);
-        listener.updateAccessibilityServicesState();
-        listener.updateAssistantAvailable(mAssistantAvailable, mLongPressHomeEnabled);
+        mStateListeners.add(listener);
+        if (!mTogglingNavbarTaskbar && mStateListeners.size() == 1) {
+            setupOnFirstBar();
+
+            // Update the state once the first bar is registered
+            updateAssistantAvailability();
+            updateA11yState();
+            mCommandQueue.recomputeDisableFlags(mContext.getDisplayId(), false /* animate */);
+        } else {
+            listener.updateAccessibilityServicesState();
+            listener.updateAssistantAvailable(mAssistantAvailable, mLongPressHomeEnabled);
+        }
+        listener.updateWallpaperVisibility(mWallpaperVisible, mDefaultDisplayId);
+        listener.updateRotationWatcherState(mRotationWatcherRotation);
     }
 
+    /**
+     * Removes a previously registered listener.
+     */
     public void removeNavTaskStateUpdater(NavbarTaskbarStateUpdater listener) {
-        mA11yEventListeners.remove(listener);
+        mStateListeners.remove(listener);
+        if (!mTogglingNavbarTaskbar && mStateListeners.isEmpty()) {
+            cleanupAfterLastBar();
+        }
     }
 
     private void dispatchA11yEventUpdate() {
-        for (NavbarTaskbarStateUpdater listener : mA11yEventListeners) {
+        for (NavbarTaskbarStateUpdater listener : mStateListeners) {
             listener.updateAccessibilityServicesState();
         }
     }
 
     private void dispatchAssistantEventUpdate(boolean assistantAvailable,
             boolean longPressHomeEnabled) {
-        for (NavbarTaskbarStateUpdater listener : mA11yEventListeners) {
+        for (NavbarTaskbarStateUpdater listener : mStateListeners) {
             listener.updateAssistantAvailable(assistantAvailable, longPressHomeEnabled);
         }
     }
 
     @Override
     public void onAccessibilityServicesStateChanged(AccessibilityManager manager) {
-        dispatchA11yEventUpdate();
         updateA11yState();
     }
 
     @Override
     public void onAccessibilityButtonModeChanged(int mode) {
         updateA11yState();
-        dispatchA11yEventUpdate();
     }
 
     @Override
     public void onAccessibilityButtonTargetsChanged(String targets) {
         updateA11yState();
-        dispatchA11yEventUpdate();
     }
 
     /**
@@ -262,6 +381,8 @@
             updateSystemAction(clickable, SYSTEM_ACTION_ID_ACCESSIBILITY_BUTTON);
             updateSystemAction(longClickable, SYSTEM_ACTION_ID_ACCESSIBILITY_BUTTON_CHOOSER);
         }
+
+        dispatchA11yEventUpdate();
     }
 
     /**
@@ -319,6 +440,10 @@
         return mLongPressHomeEnabled;
     }
 
+    public EdgeBackGestureHandler getEdgeBackGestureHandler() {
+        return mEdgeBackGestureHandler;
+    }
+
     @Override
     public void startAssistant(Bundle bundle) {
         mAssistManagerLazy.get().startAssist(bundle);
@@ -357,6 +482,18 @@
         mWindowState = state;
     }
 
+    private void dispatchWallpaperVisibilityChanged(boolean visible, int displayId) {
+        for (NavbarTaskbarStateUpdater listener : mStateListeners) {
+            listener.updateWallpaperVisibility(visible, displayId);
+        }
+    }
+
+    private void dispatchRotationChanged(int rotation) {
+        for (NavbarTaskbarStateUpdater listener : mStateListeners) {
+            listener.updateRotationWatcherState(rotation);
+        }
+    }
+
     public CurrentSysuiState getCurrentSysuiState() {
         return new CurrentSysuiState();
     }
@@ -368,6 +505,8 @@
     public interface NavbarTaskbarStateUpdater {
         void updateAccessibilityServicesState();
         void updateAssistantAvailable(boolean available, boolean longPressHomeEnabled);
+        default void updateWallpaperVisibility(boolean visible, int displayId) {}
+        default void updateRotationWatcherState(int rotation) {}
     }
 
     /** Data class to help Taskbar/Navbar initiate state correctly when switching between the two.*/
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
index df70f6b..8a5bac8 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
@@ -164,7 +164,6 @@
 import java.util.Map;
 import java.util.Optional;
 import java.util.concurrent.Executor;
-import java.util.function.Consumer;
 
 import javax.inject.Inject;
 
@@ -209,7 +208,6 @@
     private final Optional<Recents> mRecentsOptional;
     private final DeviceConfigProxy mDeviceConfigProxy;
     private final NavigationBarTransitions mNavigationBarTransitions;
-    private final EdgeBackGestureHandler mEdgeBackGestureHandler;
     private final Optional<BackAnimation> mBackAnimation;
     private final Handler mHandler;
     private final UiEventLogger mUiEventLogger;
@@ -221,6 +219,7 @@
     private final DisplayTracker mDisplayTracker;
     private final RegionSamplingHelper mRegionSamplingHelper;
     private final int mNavColorSampleMargin;
+    private EdgeBackGestureHandler mEdgeBackGestureHandler;
     private NavigationBarFrame mFrame;
 
     private @WindowVisibleState int mNavigationBarWindowState = WINDOW_STATE_SHOWING;
@@ -357,6 +356,21 @@
                     mLongPressHomeEnabled = longPressHomeEnabled;
                     updateAssistantEntrypoints(available, longPressHomeEnabled);
                 }
+
+                @Override
+                public void updateWallpaperVisibility(boolean visible, int displayId) {
+                    mNavigationBarTransitions.setWallpaperVisibility(visible);
+                }
+
+                @Override
+                public void updateRotationWatcherState(int rotation) {
+                    if (mIsOnDefaultDisplay && mView != null) {
+                        mView.getRotationButtonController().onRotationWatcherChanged(rotation);
+                        if (mView.needsReorient(rotation)) {
+                            repositionNavigationBar(rotation);
+                        }
+                    }
+                }
             };
 
     private final OverviewProxyListener mOverviewProxyListener = new OverviewProxyListener() {
@@ -550,7 +564,6 @@
             DeadZone deadZone,
             DeviceConfigProxy deviceConfigProxy,
             NavigationBarTransitions navigationBarTransitions,
-            EdgeBackGestureHandler edgeBackGestureHandler,
             Optional<BackAnimation> backAnimation,
             UserContextProvider userContextProvider,
             WakefulnessLifecycle wakefulnessLifecycle,
@@ -580,7 +593,6 @@
         mDeadZone = deadZone;
         mDeviceConfigProxy = deviceConfigProxy;
         mNavigationBarTransitions = navigationBarTransitions;
-        mEdgeBackGestureHandler = edgeBackGestureHandler;
         mBackAnimation = backAnimation;
         mHandler = mainHandler;
         mUiEventLogger = uiEventLogger;
@@ -596,6 +608,7 @@
         mWakefulnessLifecycle = wakefulnessLifecycle;
         mTaskStackChangeListeners = taskStackChangeListeners;
         mDisplayTracker = displayTracker;
+        mEdgeBackGestureHandler = navBarHelper.getEdgeBackGestureHandler();
 
         mNavColorSampleMargin = getResources()
                 .getDimensionPixelSize(R.dimen.navigation_handle_sample_horizontal_margin);
@@ -688,13 +701,14 @@
         // start firing, since the latter is source of truth
         parseCurrentSysuiState();
         mCommandQueue.addCallback(this);
-        mLongPressHomeEnabled = mNavBarHelper.getLongPressHomeEnabled();
-        mNavBarHelper.init();
         mHomeButtonLongPressDurationMs = Optional.of(mDeviceConfigProxy.getLong(
                 DeviceConfig.NAMESPACE_SYSTEMUI,
                 HOME_BUTTON_LONG_PRESS_DURATION_MS,
                 /* defaultValue = */ 0
         )).filter(duration -> duration != 0);
+        // This currently MUST be called after mHomeButtonLongPressDurationMs is initialized since
+        // the registration callbacks will trigger code that uses it
+        mNavBarHelper.registerNavTaskStateUpdater(mNavbarTaskbarStateUpdater);
         mDeviceConfigProxy.addOnPropertiesChangedListener(
                 DeviceConfig.NAMESPACE_SYSTEMUI, mHandler::post, mOnPropertiesChangedListener);
 
@@ -718,9 +732,9 @@
         mCommandQueue.removeCallback(this);
         mWindowManager.removeViewImmediate(mView.getRootView());
         mNavigationModeController.removeListener(mModeChangedListener);
+        mEdgeBackGestureHandler.setStateChangeCallback(null);
 
         mNavBarHelper.removeNavTaskStateUpdater(mNavbarTaskbarStateUpdater);
-        mNavBarHelper.destroy();
         mNotificationShadeDepthController.removeListener(mDepthListener);
 
         mDeviceConfigProxy.removeOnPropertiesChangedListener(mOnPropertiesChangedListener);
@@ -758,8 +772,6 @@
         mView.getViewRootImpl().addSurfaceChangedCallback(mSurfaceChangedCallback);
         notifyNavigationBarSurface();
 
-        mNavBarHelper.registerNavTaskStateUpdater(mNavbarTaskbarStateUpdater);
-
         mPipOptional.ifPresent(mView::addPipExclusionBoundsChangeListener);
         mBackAnimation.ifPresent(mView::registerBackAnimation);
 
@@ -777,7 +789,6 @@
         if (mIsOnDefaultDisplay) {
             final RotationButtonController rotationButtonController =
                     mView.getRotationButtonController();
-            rotationButtonController.setRotationCallback(mRotationWatcher);
 
             // Reset user rotation pref to match that of the WindowManager if starting in locked
             // mode. This will automatically happen when switching from auto-rotate to locked mode.
@@ -811,9 +822,6 @@
 
     @Override
     public void onViewDetached() {
-        final RotationButtonController rotationButtonController =
-                mView.getRotationButtonController();
-        rotationButtonController.setRotationCallback(null);
         mView.setUpdateActiveTouchRegionsCallback(null);
         getBarTransitions().destroy();
         mOverviewProxyService.removeCallback(mOverviewProxyListener);
@@ -1503,6 +1511,7 @@
     }
 
     void updateAccessibilityStateFlags() {
+        mLongPressHomeEnabled = mNavBarHelper.getLongPressHomeEnabled();
         if (mView != null) {
             int a11yFlags = mNavBarHelper.getA11yButtonState();
             boolean clickable = (a11yFlags & SYSUI_STATE_A11Y_BUTTON_CLICKABLE) != 0;
@@ -1753,12 +1762,6 @@
         return mNavBarMode == NAV_BAR_MODE_GESTURAL && mOrientationHandle != null;
     }
 
-    private final Consumer<Integer> mRotationWatcher = rotation -> {
-        if (mView != null && mView.needsReorient(rotation)) {
-            repositionNavigationBar(rotation);
-        }
-    };
-
     private final UserTracker.Callback mUserChangedCallback =
             new UserTracker.Callback() {
                 @Override
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java
index 63d977e..5b0a4bb 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java
@@ -90,6 +90,7 @@
     private final DisplayTracker mDisplayTracker;
     private final DisplayManager mDisplayManager;
     private final TaskbarDelegate mTaskbarDelegate;
+    private final NavBarHelper mNavBarHelper;
     private int mNavMode;
     @VisibleForTesting boolean mIsLargeScreen;
 
@@ -133,6 +134,7 @@
         configurationController.addCallback(this);
         mConfigChanges.applyNewConfig(mContext.getResources());
         mNavMode = navigationModeController.addListener(this);
+        mNavBarHelper = navBarHelper;
         mTaskbarDelegate = taskbarDelegate;
         mTaskbarDelegate.setDependencies(commandQueue, overviewProxyService,
                 navBarHelper, navigationModeController, sysUiFlagsContainer,
@@ -241,10 +243,15 @@
 
         if (taskbarEnabled) {
             Trace.beginSection("NavigationBarController#initializeTaskbarIfNecessary");
+            final int displayId = mContext.getDisplayId();
+            // Hint to NavBarHelper if we are replacing an existing bar to skip extra work
+            mNavBarHelper.setTogglingNavbarTaskbar(mNavigationBars.contains(displayId));
             // Remove navigation bar when taskbar is showing
-            removeNavigationBar(mContext.getDisplayId());
-            mTaskbarDelegate.init(mContext.getDisplayId());
+            removeNavigationBar(displayId);
+            mTaskbarDelegate.init(displayId);
+            mNavBarHelper.setTogglingNavbarTaskbar(false);
             Trace.endSection();
+
         } else {
             mTaskbarDelegate.destroy();
         }
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarTransitions.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarTransitions.java
index 20b5032..1d73bc20 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarTransitions.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarTransitions.java
@@ -21,11 +21,7 @@
 import static com.android.systemui.util.Utils.isGesturalModeOnDefaultDisplay;
 
 import android.graphics.Rect;
-import android.os.Handler;
-import android.os.RemoteException;
 import android.util.SparseArray;
-import android.view.IWallpaperVisibilityListener;
-import android.view.IWindowManager;
 import android.view.View;
 
 import com.android.systemui.R;
@@ -36,7 +32,6 @@
 import com.android.systemui.statusbar.phone.LightBarTransitionsController;
 
 import java.io.PrintWriter;
-import java.lang.ref.WeakReference;
 import java.util.ArrayList;
 import java.util.List;
 
@@ -63,8 +58,6 @@
     }
 
     private final NavigationBarView mView;
-    @org.jetbrains.annotations.NotNull
-    private final IWindowManager mWindowManagerService;
     private final LightBarTransitionsController mLightTransitionsController;
     private final DisplayTracker mDisplayTracker;
     private final boolean mAllowAutoDimWallpaperNotVisible;
@@ -76,51 +69,20 @@
     private int mNavBarMode = NAV_BAR_MODE_3BUTTON;
     private List<DarkIntensityListener> mDarkIntensityListeners;
 
-    private final Handler mHandler = Handler.getMain();
-
-    static final class WallpaperVisibilityListener extends IWallpaperVisibilityListener.Stub {
-        private final WeakReference<NavigationBarTransitions> mSelf;
-
-        WallpaperVisibilityListener(NavigationBarTransitions self) {
-            mSelf = new WeakReference<>(self);
-        }
-
-        @Override
-        public void onWallpaperVisibilityChanged(boolean newVisibility,
-                int displayId) throws RemoteException {
-            NavigationBarTransitions self = mSelf.get();
-            if (self == null) {
-                return;
-            }
-            self.mWallpaperVisible = newVisibility;
-            self.mHandler.post(() -> self.applyLightsOut(true, false));
-        }
-    }
-
-    private final IWallpaperVisibilityListener mWallpaperVisibilityListener;
-
     @Inject
     public NavigationBarTransitions(
             NavigationBarView view,
-            IWindowManager windowManagerService,
             LightBarTransitionsController.Factory lightBarTransitionsControllerFactory,
             DisplayTracker displayTracker) {
         super(view, R.drawable.nav_background);
 
         mView = view;
-        mWindowManagerService = windowManagerService;
         mLightTransitionsController = lightBarTransitionsControllerFactory.create(this);
         mDisplayTracker = displayTracker;
         mAllowAutoDimWallpaperNotVisible = view.getContext().getResources()
                 .getBoolean(R.bool.config_navigation_bar_enable_auto_dim_no_visible_wallpaper);
         mDarkIntensityListeners = new ArrayList();
 
-        mWallpaperVisibilityListener = new WallpaperVisibilityListener(this);
-        try {
-            mWallpaperVisible = mWindowManagerService.registerWallpaperVisibilityListener(
-                    mWallpaperVisibilityListener, mDisplayTracker.getDefaultDisplayId());
-        } catch (RemoteException e) {
-        }
         mView.addOnLayoutChangeListener(
                 (v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> {
                     View currentView = mView.getCurrentView();
@@ -142,14 +104,14 @@
 
     @Override
     public void destroy() {
-        try {
-            mWindowManagerService.unregisterWallpaperVisibilityListener(mWallpaperVisibilityListener,
-                    mDisplayTracker.getDefaultDisplayId());
-        } catch (RemoteException e) {
-        }
         mLightTransitionsController.destroy();
     }
 
+    void setWallpaperVisibility(boolean visible) {
+        mWallpaperVisible = visible;
+        applyLightsOut(true, false);
+    }
+
     @Override
     public void setAutoDim(boolean autoDim) {
         // Ensure we aren't in gestural nav if we are triggering auto dim
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java
index 63fb499..5d598e8 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java
@@ -1093,13 +1093,10 @@
     @Override
     protected void onAttachedToWindow() {
         super.onAttachedToWindow();
-        // This needs to happen first as it can changed the enabled state which can affect whether
-        // the back button is visible
-        mEdgeBackGestureHandler.onNavBarAttached();
         requestApplyInsets();
         reorient();
         if (mRotationButtonController != null) {
-            mRotationButtonController.registerListeners();
+            mRotationButtonController.registerListeners(false /* registerRotationWatcher */);
         }
 
         updateNavButtonIcons();
@@ -1115,8 +1112,6 @@
             mFloatingRotationButton.hide();
             mRotationButtonController.unregisterListeners();
         }
-
-        mEdgeBackGestureHandler.onNavBarDetached();
     }
 
     void dump(PrintWriter pw) {
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java b/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java
index 27e99f7..b96ca7a 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java
@@ -92,7 +92,7 @@
         Dumpable {
     private static final String TAG = TaskbarDelegate.class.getSimpleName();
 
-    private final EdgeBackGestureHandler mEdgeBackGestureHandler;
+    private EdgeBackGestureHandler mEdgeBackGestureHandler;
     private final LightBarTransitionsController.Factory mLightBarTransitionsControllerFactory;
     private boolean mInitialized;
     private CommandQueue mCommandQueue;
@@ -170,15 +170,15 @@
     private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
     @Inject
     public TaskbarDelegate(Context context,
-            EdgeBackGestureHandler.Factory edgeBackGestureHandlerFactory,
             LightBarTransitionsController.Factory lightBarTransitionsControllerFactory,
             StatusBarKeyguardViewManager statusBarKeyguardViewManager) {
         mLightBarTransitionsControllerFactory = lightBarTransitionsControllerFactory;
-        mEdgeBackGestureHandler = edgeBackGestureHandlerFactory.create(context);
 
         mContext = context;
         mDisplayManager = mContext.getSystemService(DisplayManager.class);
-        mPipListener = mEdgeBackGestureHandler::setPipStashExclusionBounds;
+        mPipListener = (bounds) -> {
+            mEdgeBackGestureHandler.setPipStashExclusionBounds(bounds);
+        };
         mStatusBarKeyguardViewManager = statusBarKeyguardViewManager;
         mStatusBarKeyguardViewManager.setTaskbarDelegate(this);
     }
@@ -206,6 +206,7 @@
         mBackAnimation = backAnimation;
         mLightBarTransitionsController = createLightBarTransitionsController();
         mTaskStackChangeListeners = taskStackChangeListeners;
+        mEdgeBackGestureHandler = navBarHelper.getEdgeBackGestureHandler();
     }
 
     // Separated into a method to keep setDependencies() clean/readable.
@@ -238,8 +239,6 @@
         mOverviewProxyService.addCallback(this);
         onNavigationModeChanged(mNavigationModeController.addListener(this));
         mNavBarHelper.registerNavTaskStateUpdater(mNavbarTaskbarStateUpdater);
-        mNavBarHelper.init();
-        mEdgeBackGestureHandler.onNavBarAttached();
         // Initialize component callback
         Display display = mDisplayManager.getDisplay(displayId);
         mWindowContext = mContext.createWindowContext(display, TYPE_APPLICATION, null);
@@ -263,8 +262,6 @@
         mOverviewProxyService.removeCallback(this);
         mNavigationModeController.removeListener(this);
         mNavBarHelper.removeNavTaskStateUpdater(mNavbarTaskbarStateUpdater);
-        mNavBarHelper.destroy();
-        mEdgeBackGestureHandler.onNavBarDetached();
         mScreenPinningNotify = null;
         mWindowContext = null;
         mAutoHideController.setNavigationBar(null);
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
index f28c275..b454c23 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
@@ -477,7 +477,7 @@
     }
 
     /**
-     * @see NavigationBarView#onAttachedToWindow()
+     * Called when the nav/task bar is attached.
      */
     public void onNavBarAttached() {
         mIsAttached = true;
@@ -489,7 +489,7 @@
     }
 
     /**
-     * @see NavigationBarView#onDetachedFromWindow()
+     * Called when the nav/task bar is detached.
      */
     public void onNavBarDetached() {
         mIsAttached = false;
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index f1bf94a..06426b3 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -1219,6 +1219,12 @@
 
     private void onSplitShadeEnabledChanged() {
         mShadeLog.logSplitShadeChanged(mSplitShadeEnabled);
+        // Reset any left over overscroll state. It is a rare corner case but can happen.
+        mQsController.setOverScrollAmount(0);
+        mScrimController.setNotificationsOverScrollAmount(0);
+        mNotificationStackScrollLayoutController.setOverExpansion(0);
+        mNotificationStackScrollLayoutController.setOverScrollAmount(0);
+
         // when we switch between split shade and regular shade we want to enforce setting qs to
         // the default state: expanded for split shade and collapsed otherwise
         if (!isOnKeyguard() && mPanelExpanded) {
@@ -3575,12 +3581,15 @@
     }
 
     private void endMotionEvent(MotionEvent event, float x, float y, boolean forceCancel) {
+        // don't fling while in keyguard to avoid jump in shade expand animation
+        boolean fullyExpandedInKeyguard = mBarState == KEYGUARD && mExpandedFraction >= 1.0;
         mTrackingPointer = -1;
         mAmbientState.setSwipingUp(false);
-        if ((mTracking && mTouchSlopExceeded) || Math.abs(x - mInitialExpandX) > mTouchSlop
+        if (!fullyExpandedInKeyguard && ((mTracking && mTouchSlopExceeded)
+                || Math.abs(x - mInitialExpandX) > mTouchSlop
                 || Math.abs(y - mInitialExpandY) > mTouchSlop
                 || (!isFullyExpanded() && !isFullyCollapsed())
-                || event.getActionMasked() == MotionEvent.ACTION_CANCEL || forceCancel) {
+                || event.getActionMasked() == MotionEvent.ACTION_CANCEL || forceCancel)) {
             mVelocityTracker.computeCurrentVelocity(1000);
             float vel = mVelocityTracker.getYVelocity();
             float vectorVel = (float) Math.hypot(
@@ -3628,9 +3637,9 @@
             if (mUpdateFlingOnLayout) {
                 mUpdateFlingVelocity = vel;
             }
-        } else if (!mCentralSurfaces.isBouncerShowing()
+        } else if (fullyExpandedInKeyguard || (!mCentralSurfaces.isBouncerShowing()
                 && !mAlternateBouncerInteractor.isVisibleState()
-                && !mKeyguardStateController.isKeyguardGoingAway()) {
+                && !mKeyguardStateController.isKeyguardGoingAway())) {
             onEmptySpaceClick();
             onTrackingStopped(true);
         }
diff --git a/packages/SystemUI/src/com/android/systemui/smartspace/dagger/SmartspaceViewComponent.kt b/packages/SystemUI/src/com/android/systemui/smartspace/dagger/SmartspaceViewComponent.kt
index 5736a5c..26149321 100644
--- a/packages/SystemUI/src/com/android/systemui/smartspace/dagger/SmartspaceViewComponent.kt
+++ b/packages/SystemUI/src/com/android/systemui/smartspace/dagger/SmartspaceViewComponent.kt
@@ -37,7 +37,8 @@
         fun create(
             @BindsInstance parent: ViewGroup,
             @BindsInstance @Named(PLUGIN) plugin: BcSmartspaceDataPlugin,
-            @BindsInstance onAttachListener: View.OnAttachStateChangeListener
+            @BindsInstance onAttachListener: View.OnAttachStateChangeListener,
+            @BindsInstance viewWithCustomLayout: View? = null
         ): SmartspaceViewComponent
     }
 
@@ -53,10 +54,13 @@
             falsingManager: FalsingManager,
             parent: ViewGroup,
             @Named(PLUGIN) plugin: BcSmartspaceDataPlugin,
+            viewWithCustomLayout: View?,
             onAttachListener: View.OnAttachStateChangeListener
         ):
                 BcSmartspaceDataPlugin.SmartspaceView {
-            val ssView = plugin.getView(parent)
+            val ssView = viewWithCustomLayout
+                    as? BcSmartspaceDataPlugin.SmartspaceView
+                    ?: plugin.getView(parent)
             // Currently, this is only used to provide SmartspaceView on Dream surface.
             ssView.setUiSurface(UI_SURFACE_DREAM)
             ssView.registerDataProvider(plugin)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
index d3927a2..779be2b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
@@ -178,10 +178,12 @@
     private boolean mVisible;
     private boolean mOrganizationOwnedDevice;
 
+    // these all assume the device is plugged in (wired/wireless/docked) AND chargingOrFull:
     private boolean mPowerPluggedIn;
     private boolean mPowerPluggedInWired;
     private boolean mPowerPluggedInWireless;
     private boolean mPowerPluggedInDock;
+
     private boolean mPowerCharged;
     private boolean mBatteryOverheated;
     private boolean mEnableBatteryDefender;
@@ -512,6 +514,7 @@
                 powerIndication += ",  " + (mChargingWattage / 1000) + " mW";
             }
 
+            mKeyguardLogger.logUpdateBatteryIndication(powerIndication, mPowerPluggedIn);
             mRotateTextViewController.updateIndication(
                     INDICATION_TYPE_BATTERY,
                     new KeyguardIndication.Builder()
@@ -520,6 +523,7 @@
                             .build(),
                     animate);
         } else {
+            mKeyguardLogger.log(TAG, LogLevel.DEBUG, "hide battery indication");
             // don't show the charging information if device isn't plugged in
             mRotateTextViewController.hideIndication(INDICATION_TYPE_BATTERY);
         }
@@ -898,6 +902,9 @@
         updateLockScreenIndications(animate, getCurrentUser());
     }
 
+    /**
+     * Assumption: device is charging
+     */
     protected String computePowerIndication() {
         int chargingId;
         if (mBatteryOverheated) {
@@ -1052,6 +1059,12 @@
             }
         }
 
+        /**
+         * KeyguardUpdateMonitor only sends "interesting" battery updates
+         * {@link KeyguardUpdateMonitor#isBatteryUpdateInteresting}.
+         * Therefore, make sure to always check plugged in state along with any charging status
+         * change, or else we could end up with stale state.
+         */
         @Override
         public void onRefreshBatteryInfo(BatteryStatus status) {
             boolean isChargingOrFull = status.status == BatteryManager.BATTERY_STATUS_CHARGING
@@ -1067,6 +1080,7 @@
             mBatteryLevel = status.level;
             mBatteryPresent = status.present;
             mBatteryOverheated = status.isOverheated();
+            // when the battery is overheated, device doesn't charge so only guard on pluggedIn:
             mEnableBatteryDefender = mBatteryOverheated && status.isPluggedIn();
             mIncompatibleCharger = status.incompatibleCharger.orElse(false);
             try {
@@ -1076,14 +1090,10 @@
                 mKeyguardLogger.log(TAG, ERROR, "Error calling IBatteryStats", e);
                 mChargingTimeRemaining = -1;
             }
+
+            mKeyguardLogger.logRefreshBatteryInfo(isChargingOrFull, mPowerPluggedIn, mBatteryLevel,
+                    mBatteryOverheated);
             updateDeviceEntryIndication(!wasPluggedIn && mPowerPluggedInWired);
-            if (mDozing) {
-                if (!wasPluggedIn && mPowerPluggedIn) {
-                    showTransientIndication(computePowerIndication());
-                } else if (wasPluggedIn && !mPowerPluggedIn) {
-                    hideTransientIndication();
-                }
-            }
         }
 
         @Override
@@ -1335,6 +1345,10 @@
         }
     }
 
+    protected boolean isPluggedInAndCharging() {
+        return mPowerPluggedIn;
+    }
+
     private boolean isCurrentUser(int userId) {
         return getCurrentUser() == userId;
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.kt
index 1004ec1..4d0e746 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.kt
@@ -389,11 +389,11 @@
             // First check whether this notification should launch a full screen intent, and
             // launch it if needed.
             val fsiDecision = mNotificationInterruptStateProvider.getFullScreenIntentDecision(entry)
-            if (fsiDecision != null && fsiDecision.shouldLaunch) {
-                mNotificationInterruptStateProvider.logFullScreenIntentDecision(entry, fsiDecision)
+            mNotificationInterruptStateProvider.logFullScreenIntentDecision(entry, fsiDecision)
+            if (fsiDecision.shouldLaunch) {
                 mLaunchFullScreenIntentProvider.launchFullScreenIntent(entry)
             } else if (mFlags.fsiOnDNDUpdate() &&
-                fsiDecision.equals(FullScreenIntentDecision.NO_FSI_SUPPRESSED_ONLY_BY_DND)) {
+                fsiDecision == FullScreenIntentDecision.NO_FSI_SUPPRESSED_ONLY_BY_DND) {
                 // If DND was the only reason this entry was suppressed, note it for potential
                 // reconsideration on later ranking updates.
                 addForFSIReconsideration(entry, mSystemClock.currentTimeMillis())
@@ -514,14 +514,24 @@
                         mNotificationInterruptStateProvider.getFullScreenIntentDecision(entry)
                     if (decision.shouldLaunch) {
                         // Log both the launch of the full screen and also that this was via a
-                        // ranking update.
-                        mLogger.logEntryUpdatedToFullScreen(entry.key)
+                        // ranking update, and finally revoke candidacy for FSI reconsideration
+                        mLogger.logEntryUpdatedToFullScreen(entry.key, decision.name)
                         mNotificationInterruptStateProvider.logFullScreenIntentDecision(
                             entry, decision)
                         mLaunchFullScreenIntentProvider.launchFullScreenIntent(entry)
+                        mFSIUpdateCandidates.remove(entry.key)
 
                         // if we launch the FSI then this is no longer a candidate for HUN
                         continue
+                    } else if (decision == FullScreenIntentDecision.NO_FSI_SUPPRESSED_ONLY_BY_DND) {
+                        // decision has not changed; no need to log
+                    } else {
+                        // some other condition is now blocking FSI; log that and revoke candidacy
+                        // for FSI reconsideration
+                        mLogger.logEntryDisqualifiedFromFullScreen(entry.key, decision.name)
+                        mNotificationInterruptStateProvider.logFullScreenIntentDecision(
+                            entry, decision)
+                        mFSIUpdateCandidates.remove(entry.key)
                     }
                 }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorLogger.kt
index 2c6bf6b..e936559 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorLogger.kt
@@ -70,11 +70,21 @@
         })
     }
 
-    fun logEntryUpdatedToFullScreen(key: String) {
+    fun logEntryUpdatedToFullScreen(key: String, reason: String) {
         buffer.log(TAG, LogLevel.DEBUG, {
             str1 = key
+            str2 = reason
         }, {
-            "updating entry to launch full screen intent: $str1"
+            "updating entry to launch full screen intent: $str1 because $str2"
+        })
+    }
+
+    fun logEntryDisqualifiedFromFullScreen(key: String, reason: String) {
+        buffer.log(TAG, LogLevel.DEBUG, {
+            str1 = key
+            str2 = reason
+        }, {
+            "updated entry no longer qualifies for full screen intent: $str1 because $str2"
         })
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProvider.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProvider.java
index 9001470..5ba8801 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProvider.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProvider.java
@@ -16,6 +16,8 @@
 
 package com.android.systemui.statusbar.notification.interruption;
 
+import androidx.annotation.NonNull;
+
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 
 /**
@@ -153,7 +155,8 @@
      * @param entry the entry to evaluate
      * @return FullScreenIntentDecision representing the decision for whether to show the intent
      */
-    FullScreenIntentDecision getFullScreenIntentDecision(NotificationEntry entry);
+    @NonNull
+    FullScreenIntentDecision getFullScreenIntentDecision(@NonNull NotificationEntry entry);
 
     /**
      * Write the full screen launch decision for the given entry to logs.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java
index 9f45b9d..274377f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java
@@ -35,6 +35,8 @@
 import android.service.notification.StatusBarNotification;
 import android.util.Log;
 
+import androidx.annotation.NonNull;
+
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.logging.UiEvent;
 import com.android.internal.logging.UiEventLogger;
@@ -232,6 +234,7 @@
     // suppressor.
     //
     // If the entry was not suppressed by DND, just returns the given decision.
+    @NonNull
     private FullScreenIntentDecision getDecisionGivenSuppression(FullScreenIntentDecision decision,
             boolean suppressedByDND) {
         if (suppressedByDND) {
@@ -243,7 +246,7 @@
     }
 
     @Override
-    public FullScreenIntentDecision getFullScreenIntentDecision(NotificationEntry entry) {
+    public FullScreenIntentDecision getFullScreenIntentDecision(@NonNull NotificationEntry entry) {
         if (entry.getSbn().getNotification().fullScreenIntent == null) {
             if (entry.isStickyAndNotDemoted()) {
                 return FullScreenIntentDecision.NO_FSI_SHOW_STICKY_HUN;
@@ -336,52 +339,30 @@
         final int uid = entry.getSbn().getUid();
         final String packageName = entry.getSbn().getPackageName();
         switch (decision) {
-            case NO_FSI_SHOW_STICKY_HUN:
-                mLogger.logNoFullscreen(entry, "Permission denied, show sticky HUN");
-                return;
             case NO_FULL_SCREEN_INTENT:
-                return;
-            case NO_FSI_SUPPRESSED_BY_DND:
-            case NO_FSI_SUPPRESSED_ONLY_BY_DND:
-                mLogger.logNoFullscreen(entry, "Suppressed by DND");
-                return;
-            case NO_FSI_NOT_IMPORTANT_ENOUGH:
-                mLogger.logNoFullscreen(entry, "Not important enough");
+                // explicitly prevent logging for this (frequent) case
                 return;
             case NO_FSI_SUPPRESSIVE_GROUP_ALERT_BEHAVIOR:
                 android.util.EventLog.writeEvent(0x534e4554, "231322873", uid,
                         "groupAlertBehavior");
                 mUiEventLogger.log(FSI_SUPPRESSED_SUPPRESSIVE_GROUP_ALERT_BEHAVIOR, uid,
                         packageName);
-                mLogger.logNoFullscreenWarning(entry, "GroupAlertBehavior will prevent HUN");
-                return;
-            case FSI_DEVICE_NOT_INTERACTIVE:
-                mLogger.logFullscreen(entry, "Device is not interactive");
-                return;
-            case FSI_DEVICE_IS_DREAMING:
-                mLogger.logFullscreen(entry, "Device is dreaming");
-                return;
-            case FSI_KEYGUARD_SHOWING:
-                mLogger.logFullscreen(entry, "Keyguard is showing");
-                return;
-            case NO_FSI_EXPECTED_TO_HUN:
-                mLogger.logNoFullscreen(entry, "Expected to HUN");
-                return;
-            case FSI_KEYGUARD_OCCLUDED:
-                mLogger.logFullscreen(entry,
-                        "Expected not to HUN while keyguard occluded");
-                return;
-            case FSI_LOCKED_SHADE:
-                mLogger.logFullscreen(entry, "Keyguard is showing and not occluded");
+                mLogger.logNoFullscreenWarning(entry,
+                        decision + ": GroupAlertBehavior will prevent HUN");
                 return;
             case NO_FSI_NO_HUN_OR_KEYGUARD:
                 android.util.EventLog.writeEvent(0x534e4554, "231322873", uid,
                         "no hun or keyguard");
                 mUiEventLogger.log(FSI_SUPPRESSED_NO_HUN_OR_KEYGUARD, uid, packageName);
-                mLogger.logNoFullscreenWarning(entry, "Expected not to HUN while not on keyguard");
+                mLogger.logNoFullscreenWarning(entry,
+                        decision + ": Expected not to HUN while not on keyguard");
                 return;
-            case FSI_EXPECTED_NOT_TO_HUN:
-                mLogger.logFullscreen(entry, "Expected not to HUN");
+            default:
+                if (decision.shouldLaunch) {
+                    mLogger.logFullscreen(entry, decision.name());
+                } else {
+                    mLogger.logNoFullscreen(entry, decision.name());
+                }
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
index 3741f0c..33cbf06 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
@@ -26,10 +26,8 @@
 import android.animation.AnimatorListenerAdapter;
 import android.animation.ObjectAnimator;
 import android.animation.ValueAnimator.AnimatorUpdateListener;
-import android.app.INotificationManager;
 import android.app.Notification;
 import android.app.NotificationChannel;
-import android.app.role.RoleManager;
 import android.content.Context;
 import android.content.res.Configuration;
 import android.content.res.Resources;
@@ -38,12 +36,9 @@
 import android.graphics.Point;
 import android.graphics.drawable.AnimatedVectorDrawable;
 import android.graphics.drawable.AnimationDrawable;
-import android.graphics.drawable.ColorDrawable;
 import android.graphics.drawable.Drawable;
 import android.os.Build;
 import android.os.Bundle;
-import android.os.RemoteException;
-import android.os.ServiceManager;
 import android.os.Trace;
 import android.service.notification.StatusBarNotification;
 import android.util.ArraySet;
@@ -86,7 +81,6 @@
 import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
 import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin.MenuItem;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.statusbar.NotificationMediaManager;
 import com.android.systemui.statusbar.RemoteInputController;
 import com.android.systemui.statusbar.SmartReplyController;
 import com.android.systemui.statusbar.StatusBarIconView;
@@ -141,8 +135,6 @@
     private static final boolean DEBUG = Compile.IS_DEBUG && Log.isLoggable(TAG, Log.DEBUG);
     private static final boolean DEBUG_ONMEASURE =
             Compile.IS_DEBUG && Log.isLoggable(TAG, Log.VERBOSE);
-    private static final int DEFAULT_DIVIDER_ALPHA = 0x29;
-    private static final int COLORED_DIVIDER_ALPHA = 0x7B;
     private static final int MENU_VIEW_INDEX = 0;
     public static final float DEFAULT_HEADER_VISIBLE_AMOUNT = 1.0f;
     private static final long RECENTLY_ALERTED_THRESHOLD_MS = TimeUnit.SECONDS.toMillis(30);
@@ -192,7 +184,6 @@
     private int mMaxSmallHeight;
     private int mMaxSmallHeightLarge;
     private int mMaxExpandedHeight;
-    private int mIncreasedPaddingBetweenElements;
     private int mNotificationLaunchHeight;
     private boolean mMustStayOnScreen;
 
@@ -278,7 +269,6 @@
     private boolean mShowNoBackground;
     private ExpandableNotificationRow mNotificationParent;
     private OnExpandClickListener mOnExpandClickListener;
-    private View.OnClickListener mOnAppClickListener;
     private View.OnClickListener mOnFeedbackClickListener;
     private Path mExpandingClipPath;
     private boolean mIsInlineReplyAnimationFlagEnabled = false;
@@ -371,7 +361,6 @@
     private float mTranslationWhenRemoved;
     private boolean mWasChildInGroupWhenRemoved;
     private NotificationInlineImageResolver mImageResolver;
-    private NotificationMediaManager mMediaManager;
     @Nullable
     private OnExpansionChangedListener mExpansionChangedListener;
     @Nullable
@@ -381,32 +370,6 @@
     private float mBottomRoundnessDuringLaunchAnimation;
     private float mSmallRoundness;
 
-    /**
-     * Returns whether the given {@code statusBarNotification} is a system notification.
-     * <b>Note</b>, this should be run in the background thread if possible as it makes multiple IPC
-     * calls.
-     */
-    private static Boolean isSystemNotification(Context context, StatusBarNotification sbn) {
-        INotificationManager iNm = INotificationManager.Stub.asInterface(
-                ServiceManager.getService(Context.NOTIFICATION_SERVICE));
-
-        boolean isSystem = false;
-        try {
-            isSystem = iNm.isPermissionFixed(sbn.getPackageName(), sbn.getUserId());
-        } catch (RemoteException e) {
-            Log.e(TAG, "cannot reach NMS");
-        }
-        RoleManager rm = context.getSystemService(RoleManager.class);
-        List<String> fixedRoleHolders = new ArrayList<>();
-        fixedRoleHolders.addAll(rm.getRoleHolders(RoleManager.ROLE_DIALER));
-        fixedRoleHolders.addAll(rm.getRoleHolders(RoleManager.ROLE_EMERGENCY));
-        if (fixedRoleHolders.contains(sbn.getPackageName())) {
-            isSystem = true;
-        }
-
-        return isSystem;
-    }
-
     public NotificationContentView[] getLayouts() {
         return Arrays.copyOf(mLayouts, mLayouts.length);
     }
@@ -794,11 +757,6 @@
         mPrivateLayout.setRemoteInputController(r);
     }
 
-
-    String getAppName() {
-        return mAppName;
-    }
-
     public void addChildNotification(ExpandableNotificationRow row) {
         addChildNotification(row, -1);
     }
@@ -1272,10 +1230,6 @@
         }
     }
 
-    public HeadsUpManager getHeadsUpManager() {
-        return mHeadsUpManager;
-    }
-
     public void setGutsView(MenuItem item) {
         if (getGuts() != null && item.getGutsView() instanceof NotificationGuts.GutsContent) {
             getGuts().setGutsContent((NotificationGuts.GutsContent) item.getGutsView());
@@ -1705,7 +1659,6 @@
             HeadsUpManager headsUpManager,
             RowContentBindStage rowContentBindStage,
             OnExpandClickListener onExpandClickListener,
-            NotificationMediaManager notificationMediaManager,
             CoordinateOnClickListener onFeedbackClickListener,
             FalsingManager falsingManager,
             FalsingCollector falsingCollector,
@@ -1736,7 +1689,6 @@
         mHeadsUpManager = headsUpManager;
         mRowContentBindStage = rowContentBindStage;
         mOnExpandClickListener = onExpandClickListener;
-        mMediaManager = notificationMediaManager;
         setOnFeedbackClickListener(onFeedbackClickListener);
         mFalsingManager = falsingManager;
         mFalsingCollector = falsingCollector;
@@ -2925,22 +2877,6 @@
         updateClickAndFocus();
     }
 
-    public static void applyTint(View v, int color) {
-        int alpha;
-        if (color != 0) {
-            alpha = COLORED_DIVIDER_ALPHA;
-        } else {
-            color = 0xff000000;
-            alpha = DEFAULT_DIVIDER_ALPHA;
-        }
-        if (v.getBackground() instanceof ColorDrawable) {
-            ColorDrawable background = (ColorDrawable) v.getBackground();
-            background.mutate();
-            background.setColor(color);
-            background.setAlpha(alpha);
-        }
-    }
-
     public int getMaxExpandHeight() {
         return mPrivateLayout.getExpandHeight();
     }
@@ -3128,14 +3064,6 @@
         return showingLayout != null && showingLayout.requireRowToHaveOverlappingRendering();
     }
 
-    @Override
-    public int getExtraBottomPadding() {
-        if (mIsSummaryWithChildren && isGroupExpanded()) {
-            return mIncreasedPaddingBetweenElements;
-        }
-        return 0;
-    }
-
     public void setInlineReplyAnimationFlagEnabled(boolean isEnabled) {
         mIsInlineReplyAnimationFlagEnabled = isEnabled;
     }
@@ -3257,10 +3185,6 @@
         return shouldShowPublic() ? mPublicLayout : mPrivateLayout;
     }
 
-    public View getExpandedContentView() {
-        return getPrivateLayout().getExpandedChild();
-    }
-
     public void setLegacy(boolean legacy) {
         for (NotificationContentView l : mLayouts) {
             l.setLegacy(legacy);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java
index dfc80fd..e2a3111 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java
@@ -36,7 +36,6 @@
 import com.android.systemui.plugins.PluginManager;
 import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.statusbar.NotificationMediaManager;
 import com.android.systemui.statusbar.SmartReplyController;
 import com.android.systemui.statusbar.notification.FeedbackIcon;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
@@ -74,7 +73,6 @@
     private final NotificationListContainer mListContainer;
     private final RemoteInputViewSubcomponent.Factory mRemoteInputViewSubcomponentFactory;
     private final ActivatableNotificationViewController mActivatableNotificationViewController;
-    private final NotificationMediaManager mMediaManager;
     private final PluginManager mPluginManager;
     private final SystemClock mClock;
     private final String mAppName;
@@ -136,7 +134,6 @@
             MetricsLogger metricsLogger,
             NotificationRowLogger logBufferLogger,
             NotificationListContainer listContainer,
-            NotificationMediaManager mediaManager,
             SmartReplyConstants smartReplyConstants,
             SmartReplyController smartReplyController,
             PluginManager pluginManager,
@@ -165,7 +162,6 @@
         mListContainer = listContainer;
         mRemoteInputViewSubcomponentFactory = rivSubcomponentFactory;
         mActivatableNotificationViewController = activatableNotificationViewController;
-        mMediaManager = mediaManager;
         mPluginManager = pluginManager;
         mClock = clock;
         mAppName = appName;
@@ -212,7 +208,6 @@
                 mHeadsUpManager,
                 mRowContentBindStage,
                 mOnExpandClickListener,
-                mMediaManager,
                 mOnFeedbackClickListener,
                 mFalsingManager,
                 mFalsingCollector,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java
index 25c7264..9df6ba9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java
@@ -451,7 +451,7 @@
     protected void updateClipping() {
         if (mClipToActualHeight && shouldClipToActualHeight()) {
             int top = getClipTopAmount();
-            int bottom = Math.max(Math.max(getActualHeight() + getExtraBottomPadding()
+            int bottom = Math.max(Math.max(getActualHeight()
                     - mClipBottomAmount, top), mMinimumHeightForClipping);
             mClipRect.set(Integer.MIN_VALUE, top, Integer.MAX_VALUE, bottom);
             setClipBounds(mClipRect);
@@ -592,13 +592,6 @@
     }
 
     /**
-     * @return padding used to alter how much of the view is clipped.
-     */
-    public int getExtraBottomPadding() {
-        return 0;
-    }
-
-    /**
      * @return true if the group's expansion state is changing, false otherwise.
      */
     public boolean isGroupExpansionChanging() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
index e2e2a23..2c088fa 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
@@ -4499,7 +4499,7 @@
                 expandableView.setFakeShadowIntensity(0.0f, 0.0f, 0, 0);
             } else {
                 float yLocation = previous.getTranslationY() + previous.getActualHeight() -
-                        expandableView.getTranslationY() - previous.getExtraBottomPadding();
+                        expandableView.getTranslationY();
                 expandableView.setFakeShadowIntensity(
                         diff / FakeShadowView.SHADOW_SIBLING_TRESHOLD,
                         previous.getOutlineAlpha(), (int) yLocation,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
index 00519b9..5438a59 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
@@ -613,7 +613,6 @@
 
     private Runnable mLaunchTransitionEndRunnable;
     private Runnable mLaunchTransitionCancelRunnable;
-    private boolean mLaunchingAffordance;
     private boolean mLaunchCameraWhenFinishedWaking;
     private boolean mLaunchCameraOnFinishedGoingToSleep;
     private boolean mLaunchEmergencyActionWhenFinishedWaking;
@@ -3796,8 +3795,6 @@
 
         mScrimController.setExpansionAffectsAlpha(!unlocking);
 
-        boolean launchingAffordanceWithPreview = mLaunchingAffordance;
-        mScrimController.setLaunchingAffordanceWithPreview(launchingAffordanceWithPreview);
         if (mAlternateBouncerInteractor.isVisibleState()) {
             if ((!isOccluded() || isPanelExpanded())
                     && (mState == StatusBarState.SHADE || mState == StatusBarState.SHADE_LOCKED
@@ -3816,9 +3813,6 @@
             ScrimState state = mStatusBarKeyguardViewManager.primaryBouncerNeedsScrimming()
                     ? ScrimState.BOUNCER_SCRIMMED : ScrimState.BOUNCER;
             mScrimController.transitionTo(state);
-        } else if (launchingAffordanceWithPreview) {
-            // We want to avoid animating when launching with a preview.
-            mScrimController.transitionTo(ScrimState.UNLOCKED, mUnlockScrimCallback);
         } else if (mBrightnessMirrorVisible) {
             mScrimController.transitionTo(ScrimState.BRIGHTNESS_MIRROR);
         } else if (mState == StatusBarState.SHADE_LOCKED) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardIndicationTextView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardIndicationTextView.java
index b1553b0..9d30cb4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardIndicationTextView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardIndicationTextView.java
@@ -76,6 +76,7 @@
         if (mLastAnimator != null) {
             mLastAnimator.cancel();
         }
+        mMessage = "";
         setText("");
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
index aa71b51..69b683b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -85,6 +85,8 @@
 import com.android.systemui.unfold.FoldAodAnimationController;
 import com.android.systemui.unfold.SysUIUnfoldComponent;
 
+import dagger.Lazy;
+
 import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.HashSet;
@@ -94,8 +96,6 @@
 
 import javax.inject.Inject;
 
-import dagger.Lazy;
-
 /**
  * Manages creating, showing, hiding and resetting the keyguard within the status bar. Calls back
  * via {@link ViewMediatorCallback} to poke the wake lock and report that the keyguard is done,
@@ -405,14 +405,14 @@
     }
 
     /**
-     * Sets a new legacy alternate bouncer. Only used if mdoern alternate bouncer is NOT enable.
+     * Sets a new legacy alternate bouncer. Only used if modern alternate bouncer is NOT enabled.
      */
     public void setLegacyAlternateBouncer(@NonNull LegacyAlternateBouncer alternateBouncerLegacy) {
         if (!mIsModernAlternateBouncerEnabled) {
             if (!Objects.equals(mAlternateBouncerInteractor.getLegacyAlternateBouncer(),
                     alternateBouncerLegacy)) {
                 mAlternateBouncerInteractor.setLegacyAlternateBouncer(alternateBouncerLegacy);
-                hideAlternateBouncer(false);
+                hideAlternateBouncer(true);
             }
         }
 
@@ -640,8 +640,7 @@
      */
     public void showPrimaryBouncer(boolean scrimmed) {
         hideAlternateBouncer(false);
-
-        if (mKeyguardStateController.isShowing()  && !isBouncerShowing()) {
+        if (mKeyguardStateController.isShowing() && !isBouncerShowing()) {
             mPrimaryBouncerInteractor.show(scrimmed);
         }
         updateStates();
@@ -734,7 +733,7 @@
                 showBouncerOrKeyguard(hideBouncerWhenShowing);
             }
             if (hideBouncerWhenShowing) {
-                hideAlternateBouncer(false);
+                hideAlternateBouncer(true);
             }
             mKeyguardUpdateManager.sendKeyguardReset();
             updateStates();
@@ -742,8 +741,8 @@
     }
 
     @Override
-    public void hideAlternateBouncer(boolean forceUpdateScrim) {
-        updateAlternateBouncerShowing(mAlternateBouncerInteractor.hide() || forceUpdateScrim);
+    public void hideAlternateBouncer(boolean updateScrim) {
+        updateAlternateBouncerShowing(mAlternateBouncerInteractor.hide() && updateScrim);
     }
 
     private void updateAlternateBouncerShowing(boolean updateScrim) {
@@ -1447,16 +1446,21 @@
      * For any touches on the NPVC, show the primary bouncer if the alternate bouncer is currently
      * showing.
      */
-    public void onTouch(MotionEvent event) {
-        if (mAlternateBouncerInteractor.isVisibleState()
+    public boolean onTouch(MotionEvent event) {
+        boolean handledTouch = false;
+        if (event.getAction() == MotionEvent.ACTION_UP
+                && mAlternateBouncerInteractor.isVisibleState()
                 && mAlternateBouncerInteractor.hasAlternateBouncerShownWithMinTime()) {
             showPrimaryBouncer(true);
+            handledTouch = true;
         }
 
         // Forward NPVC touches to callbacks in case they want to respond to touches
         for (KeyguardViewManagerCallback callback: mCallbacks) {
             callback.onTouch(event);
         }
+
+        return handledTouch;
     }
 
     /** Update keyguard position based on a tapped X coordinate. */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt
index f1fc386..f866d65 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt
@@ -18,7 +18,6 @@
 
 import android.content.Context
 import android.content.IntentFilter
-import android.telephony.CellSignalStrength
 import android.telephony.CellSignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN
 import android.telephony.CellSignalStrengthCdma
 import android.telephony.ServiceState
@@ -28,12 +27,12 @@
 import android.telephony.TelephonyDisplayInfo
 import android.telephony.TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NONE
 import android.telephony.TelephonyManager
-import android.telephony.TelephonyManager.ERI_OFF
+import android.telephony.TelephonyManager.ERI_FLASH
+import android.telephony.TelephonyManager.ERI_ON
 import android.telephony.TelephonyManager.EXTRA_SUBSCRIPTION_ID
 import android.telephony.TelephonyManager.NETWORK_TYPE_UNKNOWN
 import com.android.settingslib.Utils
 import com.android.systemui.broadcast.BroadcastDispatcher
-import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.log.table.TableLogBuffer
@@ -59,16 +58,14 @@
 import kotlinx.coroutines.asExecutor
 import kotlinx.coroutines.channels.awaitClose
 import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.MutableSharedFlow
-import kotlinx.coroutines.flow.SharedFlow
 import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.callbackFlow
 import kotlinx.coroutines.flow.filter
-import kotlinx.coroutines.flow.filterIsInstance
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.mapLatest
 import kotlinx.coroutines.flow.mapNotNull
-import kotlinx.coroutines.flow.shareIn
+import kotlinx.coroutines.flow.scan
 import kotlinx.coroutines.flow.stateIn
 
 /**
@@ -100,8 +97,6 @@
         }
     }
 
-    private val telephonyCallbackEvent = MutableSharedFlow<Unit>(extraBufferCapacity = 1)
-
     /**
      * This flow defines the single shared connection to system_server via TelephonyCallback. Any
      * new callback should be added to this listener and funneled through callbackEvents via a data
@@ -109,9 +104,15 @@
      *
      * The reason we need to do this is because TelephonyManager limits the number of registered
      * listeners per-process, so we don't want to create a new listener for every callback.
+     *
+     * A note on the design for back pressure here: We use the [coalesce] operator here to change
+     * the backpressure strategy to store exactly the last callback event of _each type_ here, as
+     * opposed to the default strategy which is to drop the oldest event (regardless of type). This
+     * means that we should never miss any single event as long as the flow has been started.
      */
-    private val callbackEvents: SharedFlow<CallbackEvent> =
-        conflatedCallbackFlow {
+    private val callbackEvents: StateFlow<TelephonyCallbackState> = run {
+        val initial = TelephonyCallbackState()
+        callbackFlow {
                 val callback =
                     object :
                         TelephonyCallback(),
@@ -165,48 +166,50 @@
                 telephonyManager.registerTelephonyCallback(bgDispatcher.asExecutor(), callback)
                 awaitClose { telephonyManager.unregisterTelephonyCallback(callback) }
             }
-            .shareIn(scope, SharingStarted.WhileSubscribed())
+            .scan(initial = initial) { state, event -> state.applyEvent(event) }
+            .stateIn(scope = scope, started = SharingStarted.WhileSubscribed(), initial)
+    }
 
     override val isEmergencyOnly =
         callbackEvents
-            .filterIsInstance<CallbackEvent.OnServiceStateChanged>()
+            .mapNotNull { it.onServiceStateChanged }
             .map { it.serviceState.isEmergencyOnly }
             .stateIn(scope, SharingStarted.WhileSubscribed(), false)
 
     override val isRoaming =
         callbackEvents
-            .filterIsInstance<CallbackEvent.OnServiceStateChanged>()
+            .mapNotNull { it.onServiceStateChanged }
             .map { it.serviceState.roaming }
             .stateIn(scope, SharingStarted.WhileSubscribed(), false)
 
     override val operatorAlphaShort =
         callbackEvents
-            .filterIsInstance<CallbackEvent.OnServiceStateChanged>()
+            .mapNotNull { it.onServiceStateChanged }
             .map { it.serviceState.operatorAlphaShort }
             .stateIn(scope, SharingStarted.WhileSubscribed(), null)
 
     override val isInService =
         callbackEvents
-            .filterIsInstance<CallbackEvent.OnServiceStateChanged>()
+            .mapNotNull { it.onServiceStateChanged }
             .map { Utils.isInService(it.serviceState) }
             .stateIn(scope, SharingStarted.WhileSubscribed(), false)
 
     override val isGsm =
         callbackEvents
-            .filterIsInstance<CallbackEvent.OnSignalStrengthChanged>()
+            .mapNotNull { it.onSignalStrengthChanged }
             .map { it.signalStrength.isGsm }
             .stateIn(scope, SharingStarted.WhileSubscribed(), false)
 
     override val cdmaLevel =
         callbackEvents
-            .filterIsInstance<CallbackEvent.OnSignalStrengthChanged>()
+            .mapNotNull { it.onSignalStrengthChanged }
             .map {
                 it.signalStrength.getCellSignalStrengths(CellSignalStrengthCdma::class.java).let {
                     strengths ->
                     if (strengths.isNotEmpty()) {
                         strengths[0].level
                     } else {
-                        CellSignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN
+                        SIGNAL_STRENGTH_NONE_OR_UNKNOWN
                     }
                 }
             }
@@ -214,19 +217,19 @@
 
     override val primaryLevel =
         callbackEvents
-            .filterIsInstance<CallbackEvent.OnSignalStrengthChanged>()
+            .mapNotNull { it.onSignalStrengthChanged }
             .map { it.signalStrength.level }
             .stateIn(scope, SharingStarted.WhileSubscribed(), SIGNAL_STRENGTH_NONE_OR_UNKNOWN)
 
     override val dataConnectionState =
         callbackEvents
-            .filterIsInstance<CallbackEvent.OnDataConnectionStateChanged>()
+            .mapNotNull { it.onDataConnectionStateChanged }
             .map { it.dataState.toDataConnectionType() }
             .stateIn(scope, SharingStarted.WhileSubscribed(), Disconnected)
 
     override val dataActivityDirection =
         callbackEvents
-            .filterIsInstance<CallbackEvent.OnDataActivity>()
+            .mapNotNull { it.onDataActivity }
             .map { it.direction.toMobileDataActivityModel() }
             .stateIn(
                 scope,
@@ -236,28 +239,26 @@
 
     override val carrierNetworkChangeActive =
         callbackEvents
-            .filterIsInstance<CallbackEvent.OnCarrierNetworkChange>()
+            .mapNotNull { it.onCarrierNetworkChange }
             .map { it.active }
             .stateIn(scope, SharingStarted.WhileSubscribed(), false)
 
     override val resolvedNetworkType =
         callbackEvents
-            .filterIsInstance<CallbackEvent.OnDisplayInfoChanged>()
+            .mapNotNull { it.onDisplayInfoChanged }
             .map {
-                if (it.telephonyDisplayInfo.networkType == NETWORK_TYPE_UNKNOWN) {
-                    UnknownNetworkType
-                } else if (
-                    it.telephonyDisplayInfo.overrideNetworkType == OVERRIDE_NETWORK_TYPE_NONE
-                ) {
-                    DefaultNetworkType(
-                        mobileMappingsProxy.toIconKey(it.telephonyDisplayInfo.networkType)
-                    )
-                } else {
+                if (it.telephonyDisplayInfo.overrideNetworkType != OVERRIDE_NETWORK_TYPE_NONE) {
                     OverrideNetworkType(
                         mobileMappingsProxy.toIconKeyOverride(
                             it.telephonyDisplayInfo.overrideNetworkType
                         )
                     )
+                } else if (it.telephonyDisplayInfo.networkType != NETWORK_TYPE_UNKNOWN) {
+                    DefaultNetworkType(
+                        mobileMappingsProxy.toIconKey(it.telephonyDisplayInfo.networkType)
+                    )
+                } else {
+                    UnknownNetworkType
                 }
             }
             .stateIn(scope, SharingStarted.WhileSubscribed(), UnknownNetworkType)
@@ -282,7 +283,10 @@
 
     override val cdmaRoaming: StateFlow<Boolean> =
         telephonyPollingEvent
-            .mapLatest { telephonyManager.cdmaEnhancedRoamingIndicatorDisplayNumber != ERI_OFF }
+            .mapLatest {
+                val cdmaEri = telephonyManager.cdmaEnhancedRoamingIndicatorDisplayNumber
+                cdmaEri == ERI_ON || cdmaEri == ERI_FLASH
+            }
             .stateIn(scope, SharingStarted.WhileSubscribed(), false)
 
     override val networkName: StateFlow<NetworkNameModel> =
@@ -300,7 +304,8 @@
     override val dataEnabled = run {
         val initial = telephonyManager.isDataConnectionAllowed
         callbackEvents
-            .mapNotNull { (it as? CallbackEvent.OnDataEnabledChanged)?.enabled }
+            .mapNotNull { it.onDataEnabledChanged }
+            .map { it.enabled }
             .stateIn(scope, SharingStarted.WhileSubscribed(), initial)
     }
 
@@ -344,12 +349,41 @@
  * Wrap every [TelephonyCallback] we care about in a data class so we can accept them in a single
  * shared flow and then split them back out into other flows.
  */
-private sealed interface CallbackEvent {
+sealed interface CallbackEvent {
+    data class OnCarrierNetworkChange(val active: Boolean) : CallbackEvent
+    data class OnDataActivity(val direction: Int) : CallbackEvent
+    data class OnDataConnectionStateChanged(val dataState: Int) : CallbackEvent
+    data class OnDataEnabledChanged(val enabled: Boolean) : CallbackEvent
+    data class OnDisplayInfoChanged(val telephonyDisplayInfo: TelephonyDisplayInfo) : CallbackEvent
     data class OnServiceStateChanged(val serviceState: ServiceState) : CallbackEvent
     data class OnSignalStrengthChanged(val signalStrength: SignalStrength) : CallbackEvent
-    data class OnDataConnectionStateChanged(val dataState: Int) : CallbackEvent
-    data class OnDataActivity(val direction: Int) : CallbackEvent
-    data class OnCarrierNetworkChange(val active: Boolean) : CallbackEvent
-    data class OnDisplayInfoChanged(val telephonyDisplayInfo: TelephonyDisplayInfo) : CallbackEvent
-    data class OnDataEnabledChanged(val enabled: Boolean) : CallbackEvent
+}
+
+/**
+ * A simple box type for 1-to-1 mapping of [CallbackEvent] to the batched event. Used in conjunction
+ * with [scan] to make sure we don't drop important callbacks due to late subscribers
+ */
+data class TelephonyCallbackState(
+    val onDataActivity: CallbackEvent.OnDataActivity? = null,
+    val onCarrierNetworkChange: CallbackEvent.OnCarrierNetworkChange? = null,
+    val onDataConnectionStateChanged: CallbackEvent.OnDataConnectionStateChanged? = null,
+    val onDataEnabledChanged: CallbackEvent.OnDataEnabledChanged? = null,
+    val onDisplayInfoChanged: CallbackEvent.OnDisplayInfoChanged? = null,
+    val onServiceStateChanged: CallbackEvent.OnServiceStateChanged? = null,
+    val onSignalStrengthChanged: CallbackEvent.OnSignalStrengthChanged? = null,
+) {
+    fun applyEvent(event: CallbackEvent): TelephonyCallbackState {
+        return when (event) {
+            is CallbackEvent.OnCarrierNetworkChange -> copy(onCarrierNetworkChange = event)
+            is CallbackEvent.OnDataActivity -> copy(onDataActivity = event)
+            is CallbackEvent.OnDataConnectionStateChanged ->
+                copy(onDataConnectionStateChanged = event)
+            is CallbackEvent.OnDataEnabledChanged -> copy(onDataEnabledChanged = event)
+            is CallbackEvent.OnDisplayInfoChanged -> copy(onDisplayInfoChanged = event)
+            is CallbackEvent.OnServiceStateChanged -> {
+                copy(onServiceStateChanged = event)
+            }
+            is CallbackEvent.OnSignalStrengthChanged -> copy(onSignalStrengthChanged = event)
+        }
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModel.kt
index 1057231..4b24e7a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModel.kt
@@ -124,7 +124,8 @@
                     isDefault -> icon
                     wifiConstants.alwaysShowIconIfEnabled -> icon
                     !connectivityConstants.hasDataCapabilities -> icon
-                    wifiNetwork is WifiNetworkModel.Active && wifiNetwork.isValidated -> icon
+                    // See b/272509965: Even if we have an active and validated wifi network, we
+                    // don't want to show the icon if wifi isn't the default network.
                     else -> WifiIcon.Hidden
                 }
             }
diff --git a/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayController.kt b/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayController.kt
index 1065d33..59122af 100644
--- a/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayController.kt
+++ b/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayController.kt
@@ -274,6 +274,7 @@
             it.title = newInfo.windowTitle
         }
         newView.keepScreenOn = true
+        logger.logViewAddedToWindowManager(displayInfo.info, newView)
         windowManager.addView(newView, paramsWithTitle)
         animateViewIn(newView)
     }
@@ -286,6 +287,11 @@
         val view = checkNotNull(currentDisplayInfo.view) {
             "First item in activeViews list must have a valid view"
         }
+        logger.logViewRemovedFromWindowManager(
+            currentDisplayInfo.info,
+            view,
+            isReinflation = true,
+        )
         windowManager.removeView(view)
         inflateAndUpdateView(currentDisplayInfo)
     }
@@ -294,6 +300,10 @@
         override fun onDensityOrFontScaleChanged() {
             reinflateView()
         }
+
+        override fun onThemeChanged() {
+            reinflateView()
+        }
     }
 
     private fun addCallbacks() {
@@ -378,6 +388,7 @@
         }
         displayInfo.view = null // Need other places??
         animateViewOut(view, removalReason) {
+            logger.logViewRemovedFromWindowManager(displayInfo.info, view)
             windowManager.removeView(view)
             displayInfo.wakeLock?.release(displayInfo.info.wakeReason)
         }
diff --git a/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewLogger.kt b/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewLogger.kt
index 899b0c2..667e22a 100644
--- a/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewLogger.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.temporarydisplay
 
+import android.view.View
 import com.android.systemui.plugins.log.LogBuffer
 import com.android.systemui.plugins.log.LogLevel
 
@@ -141,4 +142,46 @@
             { "Removal of view with id=$str2 is ignored because $str1" }
         )
     }
+
+    fun logViewAddedToWindowManager(info: T, view: View) {
+        buffer.log(
+            tag,
+            LogLevel.DEBUG,
+            {
+                str1 = info.id
+                str2 = info.windowTitle
+                str3 = view.javaClass.name
+                int1 = view.getIdForLogging()
+            },
+            {
+                "Adding view to window manager. " +
+                    "id=$str1 window=$str2 view=$str3(id=${Integer.toHexString(int1)})"
+            }
+        )
+    }
+
+    fun logViewRemovedFromWindowManager(info: T, view: View, isReinflation: Boolean = false) {
+        buffer.log(
+            tag,
+            LogLevel.DEBUG,
+            {
+                str1 = info.id
+                str2 = info.windowTitle
+                str3 = view.javaClass.name
+                int1 = view.getIdForLogging()
+                bool1 = isReinflation
+            },
+            {
+                "Removing view from window manager${if (bool1) " due to reinflation" else ""}. " +
+                    "id=$str1 window=$str2 view=$str3(id=${Integer.toHexString(int1)})"
+            }
+        )
+    }
+
+    companion object {
+        private fun View.getIdForLogging(): Int {
+            // The identityHashCode is guaranteed to be constant for the lifetime of the object.
+            return System.identityHashCode(this)
+        }
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
index 3c7d092..95cc12a 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
@@ -1416,11 +1416,7 @@
             @Override
             public void onAnimationCancel(@NonNull Animator animation) {
                 mInteractionJankMonitor.cancel(CUJ_VOLUME_CONTROL);
-                Log.i(TAG, "onAnimationCancel");
-
-                // We can only have one animation listener for cancel, so the jank listener should
-                // also call for cleanup.
-                finishDismiss();
+                Log.d(TAG, "onAnimationCancel");
             }
 
             @Override
@@ -1529,7 +1525,12 @@
                 .setDuration(mDialogHideAnimationDurationMs)
                 .setInterpolator(new SystemUIInterpolators.LogAccelerateInterpolator())
                 .withEndAction(() -> mHandler.postDelayed(() -> {
-                    finishDismiss();
+                    mController.notifyVisible(false);
+                    mDialog.dismiss();
+                    tryToRemoveCaptionsTooltip();
+                    mIsAnimatingDismiss = false;
+
+                    hideRingerDrawer();
                 }, 50));
         if (!shouldSlideInVolumeTray()) {
             animator.translationX(
@@ -1547,18 +1548,6 @@
         Trace.endSection();
     }
 
-    /**
-     * Clean up and hide volume dialog. Called when animation is finished/cancelled.
-     */
-    private void finishDismiss() {
-        mController.notifyVisible(false);
-        mDialog.dismiss();
-        tryToRemoveCaptionsTooltip();
-        mIsAnimatingDismiss = false;
-
-        hideRingerDrawer();
-    }
-
     private boolean showActiveStreamOnly() {
         return mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_LEANBACK)
                 || mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_TELEVISION);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java
index e1c7417..c068efb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java
@@ -35,7 +35,6 @@
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.inOrder;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.reset;
@@ -930,6 +929,15 @@
         assertNotSame(firstFpLocation, mAuthController.getFingerprintSensorLocation());
     }
 
+    @Test
+    public void testCloseDialog_whenGlobalActionsMenuShown() throws Exception {
+        showDialog(new int[]{1} /* sensorIds */, false /* credentialAllowed */);
+        mAuthController.handleShowGlobalActionsMenu();
+        verify(mReceiver).onDialogDismissed(
+                eq(BiometricPrompt.DISMISSED_REASON_USER_CANCEL),
+                eq(null) /* credentialAttestation */);
+    }
+
     private void showDialog(int[] sensorIds, boolean credentialAllowed) {
         mAuthController.showAuthenticationDialog(createTestPromptInfo(),
                 mReceiver /* receiver */,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerWithCoroutinesTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerWithCoroutinesTest.kt
index 86fb279..786cb01 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerWithCoroutinesTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerWithCoroutinesTest.kt
@@ -138,7 +138,7 @@
 
             // WHEN the bouncer expansion is VISIBLE
             val job = mController.listenForBouncerExpansion(this)
-            keyguardBouncerRepository.setPrimaryVisible(true)
+            keyguardBouncerRepository.setPrimaryShow(true)
             keyguardBouncerRepository.setPanelExpansion(KeyguardBouncerConstants.EXPANSION_VISIBLE)
             yield()
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/settings/ControlsSettingsDialogManagerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/settings/ControlsSettingsDialogManagerImplTest.kt
index 5a613aa..590989d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/settings/ControlsSettingsDialogManagerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/settings/ControlsSettingsDialogManagerImplTest.kt
@@ -45,6 +45,7 @@
 import org.mockito.ArgumentMatchers.anyBoolean
 import org.mockito.Mock
 import org.mockito.Mockito.anyInt
+import org.mockito.Mockito.doAnswer
 import org.mockito.Mockito.never
 import org.mockito.Mockito.verify
 import org.mockito.Mockito.`when`
@@ -234,6 +235,36 @@
     }
 
     @Test
+    fun dialogPositiveButtonWhenCalledOnCompleteSettingIsTrue() {
+        sharedPreferences.putAttempts(0)
+        secureSettings.putBool(SETTING_SHOW, true)
+        secureSettings.putBool(SETTING_ACTION, false)
+
+        doAnswer { assertThat(secureSettings.getBool(SETTING_ACTION, false)).isTrue() }
+            .`when`(completedRunnable)
+            .invoke()
+
+        underTest.maybeShowDialog(context, completedRunnable)
+        clickButton(DialogInterface.BUTTON_POSITIVE)
+
+        verify(completedRunnable).invoke()
+    }
+
+    @Test
+    fun dialogPositiveCancelKeyguardStillCallsOnComplete() {
+        `when`(activityStarter.dismissKeyguardThenExecute(any(), nullable(), anyBoolean()))
+            .thenAnswer { (it.arguments[1] as Runnable).run() }
+        sharedPreferences.putAttempts(0)
+        secureSettings.putBool(SETTING_SHOW, true)
+        secureSettings.putBool(SETTING_ACTION, false)
+
+        underTest.maybeShowDialog(context, completedRunnable)
+        clickButton(DialogInterface.BUTTON_POSITIVE)
+
+        verify(completedRunnable).invoke()
+    }
+
+    @Test
     fun dialogCancelDoesntChangeSetting() {
         sharedPreferences.putAttempts(0)
         secureSettings.putBool(SETTING_SHOW, true)
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 aa17d49..ed73797 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java
@@ -20,7 +20,9 @@
 
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.inOrder;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.verify;
@@ -63,6 +65,7 @@
 import org.junit.runner.RunWith;
 import org.mockito.ArgumentCaptor;
 import org.mockito.Captor;
+import org.mockito.InOrder;
 import org.mockito.Mock;
 import org.mockito.Mockito;
 import org.mockito.MockitoAnnotations;
@@ -295,6 +298,7 @@
         // Inform the overlay service of dream starting.
         client.startDream(mWindowParams, mDreamOverlayCallback, DREAM_COMPONENT,
                 true /*shouldShowComplication*/);
+        mMainExecutor.runAllReady();
 
         assertThat(mService.shouldShowComplications()).isTrue();
     }
@@ -338,6 +342,48 @@
     }
 
     @Test
+    public void testImmediateEndDream() throws Exception {
+        final IDreamOverlayClient client = getClient();
+
+        // Start the dream, but don't execute any Runnables put on the executor yet. We delay
+        // executing Runnables as the timing isn't guaranteed and we want to verify that the overlay
+        // starts and finishes in the proper order even if Runnables are delayed.
+        client.startDream(mWindowParams, mDreamOverlayCallback, DREAM_COMPONENT,
+                false /*shouldShowComplication*/);
+        // Immediately end the dream.
+        client.endDream();
+        // Run any scheduled Runnables.
+        mMainExecutor.runAllReady();
+
+        // The overlay starts then finishes.
+        InOrder inOrder = inOrder(mWindowManager);
+        inOrder.verify(mWindowManager).addView(mViewCaptor.capture(), any());
+        inOrder.verify(mWindowManager).removeView(mViewCaptor.getValue());
+    }
+
+    @Test
+    public void testEndDreamDuringStartDream() throws Exception {
+        final IDreamOverlayClient client = getClient();
+
+        // Schedule the endDream call in the middle of the startDream implementation, as any
+        // ordering is possible.
+        doAnswer(invocation -> {
+            client.endDream();
+            return null;
+        }).when(mStateController).setOverlayActive(true);
+
+        // Start the dream.
+        client.startDream(mWindowParams, mDreamOverlayCallback, DREAM_COMPONENT,
+                false /*shouldShowComplication*/);
+        mMainExecutor.runAllReady();
+
+        // The overlay starts then finishes.
+        InOrder inOrder = inOrder(mWindowManager);
+        inOrder.verify(mWindowManager).addView(mViewCaptor.capture(), any());
+        inOrder.verify(mWindowManager).removeView(mViewCaptor.getValue());
+    }
+
+    @Test
     public void testDestroy() throws RemoteException {
         final IDreamOverlayClient client = getClient();
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/CustomizationProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/CustomizationProviderTest.kt
index 984f4be..1044131 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/CustomizationProviderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/CustomizationProviderTest.kt
@@ -192,6 +192,7 @@
             )
         underTest.previewManager =
             KeyguardRemotePreviewManager(
+                applicationScope = testScope.backgroundScope,
                 previewRendererFactory = previewRendererFactory,
                 mainDispatcher = testDispatcher,
                 backgroundHandler = backgroundHandler,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardBouncerRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardBouncerRepositoryTest.kt
index 2eabda3..e468cc1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardBouncerRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardBouncerRepositoryTest.kt
@@ -57,7 +57,7 @@
 
     @Test
     fun changingFlowValueTriggersLogging() = runBlocking {
-        underTest.setPrimaryHide(true)
-        verify(bouncerLogger).logChange("", "PrimaryBouncerHide", false)
+        underTest.setPrimaryShow(true)
+        verify(bouncerLogger).logChange("", "PrimaryBouncerShow", false)
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepositoryTest.kt
index ae227b4..d9d4013 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepositoryTest.kt
@@ -21,6 +21,7 @@
 import android.util.Log.TerribleFailure
 import android.util.Log.TerribleFailureHandler
 import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.FlakyTest
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.animation.Interpolators
@@ -47,6 +48,7 @@
 
 @SmallTest
 @RunWith(AndroidJUnit4::class)
+@FlakyTest(bugId = 270760395)
 class KeyguardTransitionRepositoryTest : SysuiTestCase() {
 
     private lateinit var underTest: KeyguardTransitionRepository
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt
index 153439e..7f30162 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt
@@ -145,7 +145,7 @@
             repository.setKeyguardOccluded(true)
             assertThat(secureCameraActive()).isTrue()
 
-            bouncerRepository.setPrimaryVisible(true)
+            bouncerRepository.setPrimaryShow(true)
             assertThat(secureCameraActive()).isFalse()
         }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
index 5cd24e6..092fdca 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
@@ -259,7 +259,7 @@
             runCurrent()
 
             // WHEN the primary bouncer is set to show
-            bouncerRepository.setPrimaryVisible(true)
+            bouncerRepository.setPrimaryShow(true)
             runCurrent()
 
             val info =
@@ -741,7 +741,7 @@
             reset(mockTransitionRepository)
 
             // WHEN the alternateBouncer stops showing and then the primary bouncer shows
-            bouncerRepository.setPrimaryVisible(true)
+            bouncerRepository.setPrimaryShow(true)
             runCurrent()
 
             val info =
@@ -779,7 +779,7 @@
             reset(mockTransitionRepository)
 
             // GIVEN the primary bouncer isn't showing, aod available and starting to sleep
-            bouncerRepository.setPrimaryVisible(false)
+            bouncerRepository.setPrimaryShow(false)
             keyguardRepository.setAodAvailable(true)
             keyguardRepository.setWakefulnessModel(startingToSleep())
 
@@ -823,7 +823,7 @@
 
             // GIVEN the primary bouncer isn't showing, aod not available and starting to sleep
             // to sleep
-            bouncerRepository.setPrimaryVisible(false)
+            bouncerRepository.setPrimaryShow(false)
             keyguardRepository.setAodAvailable(false)
             keyguardRepository.setWakefulnessModel(startingToSleep())
 
@@ -866,7 +866,7 @@
             reset(mockTransitionRepository)
 
             // GIVEN the primary bouncer isn't showing and device not sleeping
-            bouncerRepository.setPrimaryVisible(false)
+            bouncerRepository.setPrimaryShow(false)
             keyguardRepository.setWakefulnessModel(startingToWake())
 
             // WHEN the alternateBouncer stops showing
@@ -890,7 +890,7 @@
     fun `PRIMARY_BOUNCER to AOD`() =
         testScope.runTest {
             // GIVEN a prior transition has run to PRIMARY_BOUNCER
-            bouncerRepository.setPrimaryVisible(true)
+            bouncerRepository.setPrimaryShow(true)
             runner.startTransition(
                 testScope,
                 TransitionInfo(
@@ -912,7 +912,7 @@
             keyguardRepository.setWakefulnessModel(startingToSleep())
 
             // WHEN the primaryBouncer stops showing
-            bouncerRepository.setPrimaryVisible(false)
+            bouncerRepository.setPrimaryShow(false)
             runCurrent()
 
             val info =
@@ -932,7 +932,7 @@
     fun `PRIMARY_BOUNCER to DOZING`() =
         testScope.runTest {
             // GIVEN a prior transition has run to PRIMARY_BOUNCER
-            bouncerRepository.setPrimaryVisible(true)
+            bouncerRepository.setPrimaryShow(true)
             runner.startTransition(
                 testScope,
                 TransitionInfo(
@@ -954,7 +954,7 @@
             keyguardRepository.setWakefulnessModel(startingToSleep())
 
             // WHEN the primaryBouncer stops showing
-            bouncerRepository.setPrimaryVisible(false)
+            bouncerRepository.setPrimaryShow(false)
             runCurrent()
 
             val info =
@@ -974,7 +974,7 @@
     fun `PRIMARY_BOUNCER to LOCKSCREEN`() =
         testScope.runTest {
             // GIVEN a prior transition has run to PRIMARY_BOUNCER
-            bouncerRepository.setPrimaryVisible(true)
+            bouncerRepository.setPrimaryShow(true)
             runner.startTransition(
                 testScope,
                 TransitionInfo(
@@ -995,7 +995,7 @@
             keyguardRepository.setWakefulnessModel(startingToWake())
 
             // WHEN the alternateBouncer stops showing
-            bouncerRepository.setPrimaryVisible(false)
+            bouncerRepository.setPrimaryShow(false)
             runCurrent()
 
             val info =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractorTest.kt
index 6b7fd61..5ec6283 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractorTest.kt
@@ -35,7 +35,6 @@
 import com.android.systemui.keyguard.shared.constants.KeyguardBouncerConstants.EXPANSION_HIDDEN
 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 com.android.systemui.plugins.ActivityStarter
 import com.android.systemui.statusbar.phone.KeyguardBypassController
 import com.android.systemui.statusbar.policy.KeyguardStateController
@@ -92,7 +91,7 @@
                 keyguardBypassController,
             )
         `when`(repository.primaryBouncerStartingDisappearAnimation.value).thenReturn(null)
-        `when`(repository.primaryBouncerShow.value).thenReturn(null)
+        `when`(repository.primaryBouncerShow.value).thenReturn(false)
         `when`(bouncerView.delegate).thenReturn(bouncerViewDelegate)
         resources = context.orCreateTestableResources
     }
@@ -101,15 +100,13 @@
     fun testShow_isScrimmed() {
         underTest.show(true)
         verify(repository).setKeyguardAuthenticated(null)
-        verify(repository).setPrimaryHide(false)
         verify(repository).setPrimaryStartingToHide(false)
         verify(repository).setPrimaryScrimmed(true)
         verify(repository).setPanelExpansion(EXPANSION_VISIBLE)
         verify(repository).setPrimaryShowingSoon(true)
         verify(keyguardStateController).notifyPrimaryBouncerShowing(true)
         verify(mPrimaryBouncerCallbackInteractor).dispatchStartingToShow()
-        verify(repository).setPrimaryVisible(true)
-        verify(repository).setPrimaryShow(any(KeyguardBouncerModel::class.java))
+        verify(repository).setPrimaryShow(true)
         verify(repository).setPrimaryShowingSoon(false)
         verify(mPrimaryBouncerCallbackInteractor).dispatchVisibilityChanged(View.VISIBLE)
     }
@@ -132,9 +129,7 @@
         verify(falsingCollector).onBouncerHidden()
         verify(keyguardStateController).notifyPrimaryBouncerShowing(false)
         verify(repository).setPrimaryShowingSoon(false)
-        verify(repository).setPrimaryVisible(false)
-        verify(repository).setPrimaryHide(true)
-        verify(repository).setPrimaryShow(null)
+        verify(repository).setPrimaryShow(false)
         verify(mPrimaryBouncerCallbackInteractor).dispatchVisibilityChanged(View.INVISIBLE)
     }
 
@@ -160,9 +155,7 @@
         `when`(repository.panelExpansionAmount.value).thenReturn(0.5f)
         `when`(repository.primaryBouncerStartingDisappearAnimation.value).thenReturn(null)
         underTest.setPanelExpansion(EXPANSION_HIDDEN)
-        verify(repository).setPrimaryVisible(false)
-        verify(repository).setPrimaryShow(null)
-        verify(repository).setPrimaryHide(true)
+        verify(repository).setPrimaryShow(false)
         verify(falsingCollector).onBouncerHidden()
         verify(mPrimaryBouncerCallbackInteractor).dispatchReset()
         verify(mPrimaryBouncerCallbackInteractor).dispatchFullyHidden()
@@ -243,11 +236,11 @@
 
     @Test
     fun testIsFullShowing() {
-        `when`(repository.primaryBouncerVisible.value).thenReturn(true)
+        `when`(repository.primaryBouncerShow.value).thenReturn(true)
         `when`(repository.panelExpansionAmount.value).thenReturn(EXPANSION_VISIBLE)
         `when`(repository.primaryBouncerStartingDisappearAnimation.value).thenReturn(null)
         assertThat(underTest.isFullyShowing()).isTrue()
-        `when`(repository.primaryBouncerVisible.value).thenReturn(false)
+        `when`(repository.primaryBouncerShow.value).thenReturn(false)
         assertThat(underTest.isFullyShowing()).isFalse()
     }
 
@@ -370,7 +363,7 @@
         isUnlockingWithFpAllowed: Boolean,
         isAnimatingAway: Boolean
     ) {
-        `when`(repository.primaryBouncerVisible.value).thenReturn(isVisible)
+        `when`(repository.primaryBouncerShow.value).thenReturn(isVisible)
         resources.addOverride(R.bool.config_show_sidefps_hint_on_bouncer, sfpsEnabled)
         `when`(keyguardUpdateMonitor.isFingerprintDetectionRunning).thenReturn(fpsDetectionRunning)
         `when`(keyguardUpdateMonitor.isUnlockingWithFingerprintAllowed)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractorWithCoroutinesTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractorWithCoroutinesTest.kt
index f675e79..edac468 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractorWithCoroutinesTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractorWithCoroutinesTest.kt
@@ -77,7 +77,7 @@
     fun notInteractableWhenExpansionIsBelow90Percent() = runTest {
         val isInteractable = collectLastValue(underTest.isInteractable)
 
-        repository.setPrimaryVisible(true)
+        repository.setPrimaryShow(true)
         repository.setPanelExpansion(0.15f)
 
         assertThat(isInteractable()).isFalse()
@@ -87,7 +87,7 @@
     fun notInteractableWhenExpansionAbove90PercentButNotVisible() = runTest {
         val isInteractable = collectLastValue(underTest.isInteractable)
 
-        repository.setPrimaryVisible(false)
+        repository.setPrimaryShow(false)
         repository.setPanelExpansion(0.05f)
 
         assertThat(isInteractable()).isFalse()
@@ -97,7 +97,7 @@
     fun isInteractableWhenExpansionAbove90PercentAndVisible() = runTest {
         var isInteractable = collectLastValue(underTest.isInteractable)
 
-        repository.setPrimaryVisible(true)
+        repository.setPrimaryShow(true)
         repository.setPanelExpansion(0.09f)
 
         assertThat(isInteractable()).isTrue()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBouncerViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBouncerViewModelTest.kt
index 65e4c10..e66be08 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBouncerViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBouncerViewModelTest.kt
@@ -96,7 +96,7 @@
     fun shouldUpdateSideFps() = runTest {
         var count = 0
         val job = underTest.shouldUpdateSideFps.onEach { count++ }.launchIn(this)
-        repository.setPrimaryVisible(true)
+        repository.setPrimaryShow(true)
         // Run the tasks that are pending at this point of virtual time.
         runCurrent()
         assertThat(count).isEqualTo(1)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaHierarchyManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaHierarchyManagerTest.kt
index a579518..feb429d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaHierarchyManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaHierarchyManagerTest.kt
@@ -180,6 +180,57 @@
     }
 
     @Test
+    fun testBlockedWhenConfigurationChangesAndScreenOff() {
+        // Let's set it onto QS:
+        mediaHierarchyManager.qsExpansion = 1.0f
+        verify(mediaCarouselController)
+            .onDesiredLocationChanged(
+                ArgumentMatchers.anyInt(),
+                any(MediaHostState::class.java),
+                anyBoolean(),
+                anyLong(),
+                anyLong()
+            )
+        val observer = wakefullnessObserver.value
+        assertNotNull("lifecycle observer wasn't registered", observer)
+        observer.onStartedGoingToSleep()
+        clearInvocations(mediaCarouselController)
+        configurationController.notifyConfigurationChanged()
+        verify(mediaCarouselController, times(0))
+            .onDesiredLocationChanged(
+                ArgumentMatchers.anyInt(),
+                any(MediaHostState::class.java),
+                anyBoolean(),
+                anyLong(),
+                anyLong()
+            )
+    }
+
+    @Test
+    fun testAllowedWhenConfigurationChanges() {
+        // Let's set it onto QS:
+        mediaHierarchyManager.qsExpansion = 1.0f
+        verify(mediaCarouselController)
+            .onDesiredLocationChanged(
+                ArgumentMatchers.anyInt(),
+                any(MediaHostState::class.java),
+                anyBoolean(),
+                anyLong(),
+                anyLong()
+            )
+        clearInvocations(mediaCarouselController)
+        configurationController.notifyConfigurationChanged()
+        verify(mediaCarouselController)
+            .onDesiredLocationChanged(
+                ArgumentMatchers.anyInt(),
+                any(MediaHostState::class.java),
+                anyBoolean(),
+                anyLong(),
+                anyLong()
+            )
+    }
+
+    @Test
     fun testAllowedWhenNotTurningOff() {
         // Let's set it onto QS:
         mediaHierarchyManager.qsExpansion = 1.0f
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinatorTest.kt
index db890f6..ca2b1da 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinatorTest.kt
@@ -107,6 +107,7 @@
     private lateinit var fakeExecutor: FakeExecutor
     private lateinit var uiEventLoggerFake: UiEventLoggerFake
     private lateinit var uiEventLogger: MediaTttSenderUiEventLogger
+    private val defaultTimeout = context.resources.getInteger(R.integer.heads_up_notification_decay)
 
     @Before
     fun setUp() {
@@ -1356,6 +1357,92 @@
         assertThat(chipbarView.getLoadingIcon().visibility).isEqualTo(View.VISIBLE)
     }
 
+    @Test
+    fun almostClose_hasLongTimeout_eventuallyTimesOut() {
+        whenever(accessibilityManager.getRecommendedTimeoutMillis(any(), any())).thenAnswer {
+            it.arguments[0]
+        }
+
+        commandQueueCallback.updateMediaTapToTransferSenderDisplay(
+            StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_ALMOST_CLOSE_TO_START_CAST,
+            routeInfo,
+            null,
+        )
+
+        // WHEN the default timeout has passed
+        fakeClock.advanceTime(defaultTimeout + 1L)
+
+        // THEN the view is still on-screen because it has a long timeout
+        verify(windowManager, never()).removeView(any())
+
+        // WHEN a very long amount of time has passed
+        fakeClock.advanceTime(5L * defaultTimeout)
+
+        // THEN the view does time out
+        verify(windowManager).removeView(any())
+    }
+
+    @Test
+    fun loading_hasLongTimeout_eventuallyTimesOut() {
+        whenever(accessibilityManager.getRecommendedTimeoutMillis(any(), any())).thenAnswer {
+            it.arguments[0]
+        }
+
+        commandQueueCallback.updateMediaTapToTransferSenderDisplay(
+            StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_TRIGGERED,
+            routeInfo,
+            null,
+        )
+
+        // WHEN the default timeout has passed
+        fakeClock.advanceTime(defaultTimeout + 1L)
+
+        // THEN the view is still on-screen because it has a long timeout
+        verify(windowManager, never()).removeView(any())
+
+        // WHEN a very long amount of time has passed
+        fakeClock.advanceTime(5L * defaultTimeout)
+
+        // THEN the view does time out
+        verify(windowManager).removeView(any())
+    }
+
+    @Test
+    fun succeeded_hasDefaultTimeout() {
+        whenever(accessibilityManager.getRecommendedTimeoutMillis(any(), any())).thenAnswer {
+            it.arguments[0]
+        }
+
+        displayReceiverTriggered()
+        commandQueueCallback.updateMediaTapToTransferSenderDisplay(
+            StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_SUCCEEDED,
+            routeInfo,
+            null,
+        )
+
+        fakeClock.advanceTime(defaultTimeout + 1L)
+
+        verify(windowManager).removeView(any())
+    }
+
+    @Test
+    fun failed_hasDefaultTimeout() {
+        whenever(accessibilityManager.getRecommendedTimeoutMillis(any(), any())).thenAnswer {
+            it.arguments[0]
+        }
+
+        displayThisDeviceTriggered()
+        commandQueueCallback.updateMediaTapToTransferSenderDisplay(
+            StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_FAILED,
+            routeInfo,
+            null,
+        )
+
+        fakeClock.advanceTime(defaultTimeout + 1L)
+
+        verify(windowManager).removeView(any())
+    }
+
     private fun getChipbarView(): ViewGroup {
         val viewCaptor = ArgumentCaptor.forClass(View::class.java)
         verify(windowManager).addView(viewCaptor.capture(), any())
diff --git a/packages/SystemUI/tests/src/com/android/systemui/multishade/data/repository/MultiShadeRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/multishade/data/repository/MultiShadeRepositoryTest.kt
new file mode 100644
index 0000000..ceacaf9
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/multishade/data/repository/MultiShadeRepositoryTest.kt
@@ -0,0 +1,191 @@
+/*
+ * 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.multishade.data.repository
+
+import android.content.Context
+import androidx.test.filters.SmallTest
+import com.android.systemui.R
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.multishade.data.model.MultiShadeInteractionModel
+import com.android.systemui.multishade.data.remoteproxy.MultiShadeInputProxy
+import com.android.systemui.multishade.shared.model.ProxiedInputModel
+import com.android.systemui.multishade.shared.model.ShadeConfig
+import com.android.systemui.multishade.shared.model.ShadeId
+import com.google.common.truth.Truth.assertThat
+import com.google.common.truth.Truth.assertWithMessage
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(JUnit4::class)
+class MultiShadeRepositoryTest : SysuiTestCase() {
+
+    private lateinit var inputProxy: MultiShadeInputProxy
+
+    @Before
+    fun setUp() {
+        inputProxy = MultiShadeInputProxy()
+    }
+
+    @Test
+    fun proxiedInput() = runTest {
+        val underTest = create()
+        val latest: ProxiedInputModel? by collectLastValue(underTest.proxiedInput)
+
+        assertWithMessage("proxiedInput should start with null").that(latest).isNull()
+
+        inputProxy.onProxiedInput(ProxiedInputModel.OnTap)
+        assertThat(latest).isEqualTo(ProxiedInputModel.OnTap)
+
+        inputProxy.onProxiedInput(ProxiedInputModel.OnDrag(0f, 100f))
+        assertThat(latest).isEqualTo(ProxiedInputModel.OnDrag(0f, 100f))
+
+        inputProxy.onProxiedInput(ProxiedInputModel.OnDrag(0f, 120f))
+        assertThat(latest).isEqualTo(ProxiedInputModel.OnDrag(0f, 120f))
+
+        inputProxy.onProxiedInput(ProxiedInputModel.OnDragEnd)
+        assertThat(latest).isEqualTo(ProxiedInputModel.OnDragEnd)
+    }
+
+    @Test
+    fun shadeConfig_dualShadeEnabled() = runTest {
+        overrideResource(R.bool.dual_shade_enabled, true)
+        val underTest = create()
+        val shadeConfig: ShadeConfig? by collectLastValue(underTest.shadeConfig)
+
+        assertThat(shadeConfig).isInstanceOf(ShadeConfig.DualShadeConfig::class.java)
+    }
+
+    @Test
+    fun shadeConfig_dualShadeNotEnabled() = runTest {
+        overrideResource(R.bool.dual_shade_enabled, false)
+        val underTest = create()
+        val shadeConfig: ShadeConfig? by collectLastValue(underTest.shadeConfig)
+
+        assertThat(shadeConfig).isInstanceOf(ShadeConfig.SingleShadeConfig::class.java)
+    }
+
+    @Test
+    fun forceCollapseAll() = runTest {
+        val underTest = create()
+        val forceCollapseAll: Boolean? by collectLastValue(underTest.forceCollapseAll)
+
+        assertWithMessage("forceCollapseAll should start as false!")
+            .that(forceCollapseAll)
+            .isFalse()
+
+        underTest.setForceCollapseAll(true)
+        assertThat(forceCollapseAll).isTrue()
+
+        underTest.setForceCollapseAll(false)
+        assertThat(forceCollapseAll).isFalse()
+    }
+
+    @Test
+    fun shadeInteraction() = runTest {
+        val underTest = create()
+        val shadeInteraction: MultiShadeInteractionModel? by
+            collectLastValue(underTest.shadeInteraction)
+
+        assertWithMessage("shadeInteraction should start as null!").that(shadeInteraction).isNull()
+
+        underTest.setShadeInteraction(
+            MultiShadeInteractionModel(shadeId = ShadeId.LEFT, isProxied = false)
+        )
+        assertThat(shadeInteraction)
+            .isEqualTo(MultiShadeInteractionModel(shadeId = ShadeId.LEFT, isProxied = false))
+
+        underTest.setShadeInteraction(
+            MultiShadeInteractionModel(shadeId = ShadeId.RIGHT, isProxied = true)
+        )
+        assertThat(shadeInteraction)
+            .isEqualTo(MultiShadeInteractionModel(shadeId = ShadeId.RIGHT, isProxied = true))
+
+        underTest.setShadeInteraction(null)
+        assertThat(shadeInteraction).isNull()
+    }
+
+    @Test
+    fun expansion() = runTest {
+        val underTest = create()
+        val leftExpansion: Float? by
+            collectLastValue(underTest.getShade(ShadeId.LEFT).map { it.expansion })
+        val rightExpansion: Float? by
+            collectLastValue(underTest.getShade(ShadeId.RIGHT).map { it.expansion })
+        val singleExpansion: Float? by
+            collectLastValue(underTest.getShade(ShadeId.SINGLE).map { it.expansion })
+
+        assertWithMessage("expansion should start as 0!").that(leftExpansion).isZero()
+        assertWithMessage("expansion should start as 0!").that(rightExpansion).isZero()
+        assertWithMessage("expansion should start as 0!").that(singleExpansion).isZero()
+
+        underTest.setExpansion(
+            shadeId = ShadeId.LEFT,
+            0.4f,
+        )
+        assertThat(leftExpansion).isEqualTo(0.4f)
+        assertThat(rightExpansion).isEqualTo(0f)
+        assertThat(singleExpansion).isEqualTo(0f)
+
+        underTest.setExpansion(
+            shadeId = ShadeId.RIGHT,
+            0.73f,
+        )
+        assertThat(leftExpansion).isEqualTo(0.4f)
+        assertThat(rightExpansion).isEqualTo(0.73f)
+        assertThat(singleExpansion).isEqualTo(0f)
+
+        underTest.setExpansion(
+            shadeId = ShadeId.LEFT,
+            0.1f,
+        )
+        underTest.setExpansion(
+            shadeId = ShadeId.SINGLE,
+            0.88f,
+        )
+        assertThat(leftExpansion).isEqualTo(0.1f)
+        assertThat(rightExpansion).isEqualTo(0.73f)
+        assertThat(singleExpansion).isEqualTo(0.88f)
+    }
+
+    private fun create(): MultiShadeRepository {
+        return create(
+            context = context,
+            inputProxy = inputProxy,
+        )
+    }
+
+    companion object {
+        fun create(
+            context: Context,
+            inputProxy: MultiShadeInputProxy,
+        ): MultiShadeRepository {
+            return MultiShadeRepository(
+                applicationContext = context,
+                inputProxy = inputProxy,
+            )
+        }
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/multishade/domain/interactor/MultiShadeInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/multishade/domain/interactor/MultiShadeInteractorTest.kt
new file mode 100644
index 0000000..415e68f
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/multishade/domain/interactor/MultiShadeInteractorTest.kt
@@ -0,0 +1,301 @@
+/*
+ * 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.multishade.domain.interactor
+
+import android.content.Context
+import androidx.test.filters.SmallTest
+import com.android.systemui.R
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.multishade.data.remoteproxy.MultiShadeInputProxy
+import com.android.systemui.multishade.data.repository.MultiShadeRepositoryTest
+import com.android.systemui.multishade.shared.model.ProxiedInputModel
+import com.android.systemui.multishade.shared.model.ShadeId
+import com.google.common.truth.Truth.assertThat
+import com.google.common.truth.Truth.assertWithMessage
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(JUnit4::class)
+class MultiShadeInteractorTest : SysuiTestCase() {
+
+    private lateinit var testScope: TestScope
+    private lateinit var inputProxy: MultiShadeInputProxy
+
+    @Before
+    fun setUp() {
+        testScope = TestScope()
+        inputProxy = MultiShadeInputProxy()
+    }
+
+    @Test
+    fun maxShadeExpansion() =
+        testScope.runTest {
+            val underTest = create()
+            val maxShadeExpansion: Float? by collectLastValue(underTest.maxShadeExpansion)
+            assertWithMessage("maxShadeExpansion must start with 0.0!")
+                .that(maxShadeExpansion)
+                .isEqualTo(0f)
+
+            underTest.setExpansion(shadeId = ShadeId.LEFT, expansion = 0.441f)
+            assertThat(maxShadeExpansion).isEqualTo(0.441f)
+
+            underTest.setExpansion(shadeId = ShadeId.RIGHT, expansion = 0.442f)
+            assertThat(maxShadeExpansion).isEqualTo(0.442f)
+
+            underTest.setExpansion(shadeId = ShadeId.RIGHT, expansion = 0f)
+            assertThat(maxShadeExpansion).isEqualTo(0.441f)
+
+            underTest.setExpansion(shadeId = ShadeId.LEFT, expansion = 0f)
+            assertThat(maxShadeExpansion).isEqualTo(0f)
+        }
+
+    @Test
+    fun isVisible_dualShadeConfig() =
+        testScope.runTest {
+            overrideResource(R.bool.dual_shade_enabled, true)
+            val underTest = create()
+            val isLeftShadeVisible: Boolean? by collectLastValue(underTest.isVisible(ShadeId.LEFT))
+            val isRightShadeVisible: Boolean? by
+                collectLastValue(underTest.isVisible(ShadeId.RIGHT))
+            val isSingleShadeVisible: Boolean? by
+                collectLastValue(underTest.isVisible(ShadeId.SINGLE))
+
+            assertThat(isLeftShadeVisible).isTrue()
+            assertThat(isRightShadeVisible).isTrue()
+            assertThat(isSingleShadeVisible).isFalse()
+        }
+
+    @Test
+    fun isVisible_singleShadeConfig() =
+        testScope.runTest {
+            overrideResource(R.bool.dual_shade_enabled, false)
+            val underTest = create()
+            val isLeftShadeVisible: Boolean? by collectLastValue(underTest.isVisible(ShadeId.LEFT))
+            val isRightShadeVisible: Boolean? by
+                collectLastValue(underTest.isVisible(ShadeId.RIGHT))
+            val isSingleShadeVisible: Boolean? by
+                collectLastValue(underTest.isVisible(ShadeId.SINGLE))
+
+            assertThat(isLeftShadeVisible).isFalse()
+            assertThat(isRightShadeVisible).isFalse()
+            assertThat(isSingleShadeVisible).isTrue()
+        }
+
+    @Test
+    fun isNonProxiedInputAllowed() =
+        testScope.runTest {
+            val underTest = create()
+            val isLeftShadeNonProxiedInputAllowed: Boolean? by
+                collectLastValue(underTest.isNonProxiedInputAllowed(ShadeId.LEFT))
+            assertWithMessage("isNonProxiedInputAllowed should start as true!")
+                .that(isLeftShadeNonProxiedInputAllowed)
+                .isTrue()
+
+            // Need to collect proxied input so the flows become hot as the gesture cancelation code
+            // logic sits in side the proxiedInput flow for each shade.
+            collectLastValue(underTest.proxiedInput(ShadeId.LEFT))
+            collectLastValue(underTest.proxiedInput(ShadeId.RIGHT))
+
+            // Starting a proxied interaction on the LEFT shade disallows non-proxied interaction on
+            // the
+            // same shade.
+            inputProxy.onProxiedInput(
+                ProxiedInputModel.OnDrag(xFraction = 0f, yDragAmountPx = 123f)
+            )
+            assertThat(isLeftShadeNonProxiedInputAllowed).isFalse()
+
+            // Registering the end of the proxied interaction re-allows it.
+            inputProxy.onProxiedInput(ProxiedInputModel.OnDragEnd)
+            assertThat(isLeftShadeNonProxiedInputAllowed).isTrue()
+
+            // Starting a proxied interaction on the RIGHT shade force-collapses the LEFT shade,
+            // disallowing non-proxied input on the LEFT shade.
+            inputProxy.onProxiedInput(
+                ProxiedInputModel.OnDrag(xFraction = 1f, yDragAmountPx = 123f)
+            )
+            assertThat(isLeftShadeNonProxiedInputAllowed).isFalse()
+
+            // Registering the end of the interaction on the RIGHT shade re-allows it.
+            inputProxy.onProxiedInput(ProxiedInputModel.OnDragEnd)
+            assertThat(isLeftShadeNonProxiedInputAllowed).isTrue()
+        }
+
+    @Test
+    fun isForceCollapsed_whenOtherShadeInteractionUnderway() =
+        testScope.runTest {
+            val underTest = create()
+            val isLeftShadeForceCollapsed: Boolean? by
+                collectLastValue(underTest.isForceCollapsed(ShadeId.LEFT))
+            val isRightShadeForceCollapsed: Boolean? by
+                collectLastValue(underTest.isForceCollapsed(ShadeId.RIGHT))
+            val isSingleShadeForceCollapsed: Boolean? by
+                collectLastValue(underTest.isForceCollapsed(ShadeId.SINGLE))
+
+            assertWithMessage("isForceCollapsed should start as false!")
+                .that(isLeftShadeForceCollapsed)
+                .isFalse()
+            assertWithMessage("isForceCollapsed should start as false!")
+                .that(isRightShadeForceCollapsed)
+                .isFalse()
+            assertWithMessage("isForceCollapsed should start as false!")
+                .that(isSingleShadeForceCollapsed)
+                .isFalse()
+
+            // Registering the start of an interaction on the RIGHT shade force-collapses the LEFT
+            // shade.
+            underTest.onUserInteractionStarted(ShadeId.RIGHT)
+            assertThat(isLeftShadeForceCollapsed).isTrue()
+            assertThat(isRightShadeForceCollapsed).isFalse()
+            assertThat(isSingleShadeForceCollapsed).isFalse()
+
+            // Registering the end of the interaction on the RIGHT shade re-allows it.
+            underTest.onUserInteractionEnded(ShadeId.RIGHT)
+            assertThat(isLeftShadeForceCollapsed).isFalse()
+            assertThat(isRightShadeForceCollapsed).isFalse()
+            assertThat(isSingleShadeForceCollapsed).isFalse()
+
+            // Registering the start of an interaction on the LEFT shade force-collapses the RIGHT
+            // shade.
+            underTest.onUserInteractionStarted(ShadeId.LEFT)
+            assertThat(isLeftShadeForceCollapsed).isFalse()
+            assertThat(isRightShadeForceCollapsed).isTrue()
+            assertThat(isSingleShadeForceCollapsed).isFalse()
+
+            // Registering the end of the interaction on the LEFT shade re-allows it.
+            underTest.onUserInteractionEnded(ShadeId.LEFT)
+            assertThat(isLeftShadeForceCollapsed).isFalse()
+            assertThat(isRightShadeForceCollapsed).isFalse()
+            assertThat(isSingleShadeForceCollapsed).isFalse()
+        }
+
+    @Test
+    fun collapseAll() =
+        testScope.runTest {
+            val underTest = create()
+            val isLeftShadeForceCollapsed: Boolean? by
+                collectLastValue(underTest.isForceCollapsed(ShadeId.LEFT))
+            val isRightShadeForceCollapsed: Boolean? by
+                collectLastValue(underTest.isForceCollapsed(ShadeId.RIGHT))
+            val isSingleShadeForceCollapsed: Boolean? by
+                collectLastValue(underTest.isForceCollapsed(ShadeId.SINGLE))
+
+            assertWithMessage("isForceCollapsed should start as false!")
+                .that(isLeftShadeForceCollapsed)
+                .isFalse()
+            assertWithMessage("isForceCollapsed should start as false!")
+                .that(isRightShadeForceCollapsed)
+                .isFalse()
+            assertWithMessage("isForceCollapsed should start as false!")
+                .that(isSingleShadeForceCollapsed)
+                .isFalse()
+
+            underTest.collapseAll()
+            assertThat(isLeftShadeForceCollapsed).isTrue()
+            assertThat(isRightShadeForceCollapsed).isTrue()
+            assertThat(isSingleShadeForceCollapsed).isTrue()
+
+            // Receiving proxied input on that's not a tap gesture, on the left-hand side resets the
+            // "collapse all". Note that now the RIGHT shade is force-collapsed because we're
+            // interacting with the LEFT shade.
+            inputProxy.onProxiedInput(ProxiedInputModel.OnDrag(0f, 0f))
+            assertThat(isLeftShadeForceCollapsed).isFalse()
+            assertThat(isRightShadeForceCollapsed).isTrue()
+            assertThat(isSingleShadeForceCollapsed).isFalse()
+        }
+
+    @Test
+    fun onTapOutside_collapsesAll() =
+        testScope.runTest {
+            val underTest = create()
+            val isLeftShadeForceCollapsed: Boolean? by
+                collectLastValue(underTest.isForceCollapsed(ShadeId.LEFT))
+            val isRightShadeForceCollapsed: Boolean? by
+                collectLastValue(underTest.isForceCollapsed(ShadeId.RIGHT))
+            val isSingleShadeForceCollapsed: Boolean? by
+                collectLastValue(underTest.isForceCollapsed(ShadeId.SINGLE))
+
+            assertWithMessage("isForceCollapsed should start as false!")
+                .that(isLeftShadeForceCollapsed)
+                .isFalse()
+            assertWithMessage("isForceCollapsed should start as false!")
+                .that(isRightShadeForceCollapsed)
+                .isFalse()
+            assertWithMessage("isForceCollapsed should start as false!")
+                .that(isSingleShadeForceCollapsed)
+                .isFalse()
+
+            inputProxy.onProxiedInput(ProxiedInputModel.OnTap)
+            assertThat(isLeftShadeForceCollapsed).isTrue()
+            assertThat(isRightShadeForceCollapsed).isTrue()
+            assertThat(isSingleShadeForceCollapsed).isTrue()
+        }
+
+    @Test
+    fun proxiedInput_ignoredWhileNonProxiedGestureUnderway() =
+        testScope.runTest {
+            val underTest = create()
+            val proxiedInput: ProxiedInputModel? by
+                collectLastValue(underTest.proxiedInput(ShadeId.RIGHT))
+            underTest.onUserInteractionStarted(shadeId = ShadeId.RIGHT)
+
+            inputProxy.onProxiedInput(ProxiedInputModel.OnDrag(0.9f, 100f))
+            assertThat(proxiedInput).isNull()
+
+            inputProxy.onProxiedInput(ProxiedInputModel.OnDrag(0.8f, 110f))
+            assertThat(proxiedInput).isNull()
+
+            underTest.onUserInteractionEnded(shadeId = ShadeId.RIGHT)
+
+            inputProxy.onProxiedInput(ProxiedInputModel.OnDrag(0.9f, 100f))
+            assertThat(proxiedInput).isNotNull()
+        }
+
+    private fun create(): MultiShadeInteractor {
+        return create(
+            testScope = testScope,
+            context = context,
+            inputProxy = inputProxy,
+        )
+    }
+
+    companion object {
+        fun create(
+            testScope: TestScope,
+            context: Context,
+            inputProxy: MultiShadeInputProxy,
+        ): MultiShadeInteractor {
+            return MultiShadeInteractor(
+                applicationScope = testScope.backgroundScope,
+                repository =
+                    MultiShadeRepositoryTest.create(
+                        context = context,
+                        inputProxy = inputProxy,
+                    ),
+                inputProxy = inputProxy,
+            )
+        }
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/multishade/ui/viewmodel/MultiShadeViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/multishade/ui/viewmodel/MultiShadeViewModelTest.kt
new file mode 100644
index 0000000..0484515
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/multishade/ui/viewmodel/MultiShadeViewModelTest.kt
@@ -0,0 +1,127 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.multishade.ui.viewmodel
+
+import androidx.test.filters.SmallTest
+import com.android.systemui.R
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.multishade.data.remoteproxy.MultiShadeInputProxy
+import com.android.systemui.multishade.domain.interactor.MultiShadeInteractorTest
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(JUnit4::class)
+class MultiShadeViewModelTest : SysuiTestCase() {
+
+    private lateinit var testScope: TestScope
+    private lateinit var inputProxy: MultiShadeInputProxy
+
+    @Before
+    fun setUp() {
+        testScope = TestScope()
+        inputProxy = MultiShadeInputProxy()
+    }
+
+    @Test
+    fun scrim_whenDualShadeCollapsed() =
+        testScope.runTest {
+            val alpha = 0.5f
+            overrideResource(R.dimen.dual_shade_scrim_alpha, alpha)
+            overrideResource(R.bool.dual_shade_enabled, true)
+
+            val underTest = create()
+            val scrimAlpha: Float? by collectLastValue(underTest.scrimAlpha)
+            val isScrimEnabled: Boolean? by collectLastValue(underTest.isScrimEnabled)
+
+            assertThat(scrimAlpha).isZero()
+            assertThat(isScrimEnabled).isFalse()
+        }
+
+    @Test
+    fun scrim_whenDualShadeExpanded() =
+        testScope.runTest {
+            val alpha = 0.5f
+            overrideResource(R.dimen.dual_shade_scrim_alpha, alpha)
+            overrideResource(R.bool.dual_shade_enabled, true)
+            val underTest = create()
+            val scrimAlpha: Float? by collectLastValue(underTest.scrimAlpha)
+            val isScrimEnabled: Boolean? by collectLastValue(underTest.isScrimEnabled)
+            assertThat(scrimAlpha).isZero()
+            assertThat(isScrimEnabled).isFalse()
+
+            underTest.leftShade.onExpansionChanged(0.5f)
+            assertThat(scrimAlpha).isEqualTo(alpha * 0.5f)
+            assertThat(isScrimEnabled).isTrue()
+
+            underTest.rightShade.onExpansionChanged(1f)
+            assertThat(scrimAlpha).isEqualTo(alpha * 1f)
+            assertThat(isScrimEnabled).isTrue()
+        }
+
+    @Test
+    fun scrim_whenSingleShadeCollapsed() =
+        testScope.runTest {
+            val alpha = 0.5f
+            overrideResource(R.dimen.dual_shade_scrim_alpha, alpha)
+            overrideResource(R.bool.dual_shade_enabled, false)
+
+            val underTest = create()
+            val scrimAlpha: Float? by collectLastValue(underTest.scrimAlpha)
+            val isScrimEnabled: Boolean? by collectLastValue(underTest.isScrimEnabled)
+
+            assertThat(scrimAlpha).isZero()
+            assertThat(isScrimEnabled).isFalse()
+        }
+
+    @Test
+    fun scrim_whenSingleShadeExpanded() =
+        testScope.runTest {
+            val alpha = 0.5f
+            overrideResource(R.dimen.dual_shade_scrim_alpha, alpha)
+            overrideResource(R.bool.dual_shade_enabled, false)
+            val underTest = create()
+            val scrimAlpha: Float? by collectLastValue(underTest.scrimAlpha)
+            val isScrimEnabled: Boolean? by collectLastValue(underTest.isScrimEnabled)
+
+            underTest.singleShade.onExpansionChanged(0.95f)
+
+            assertThat(scrimAlpha).isZero()
+            assertThat(isScrimEnabled).isFalse()
+        }
+
+    private fun create(): MultiShadeViewModel {
+        return MultiShadeViewModel(
+            viewModelScope = testScope.backgroundScope,
+            interactor =
+                MultiShadeInteractorTest.create(
+                    testScope = testScope,
+                    context = context,
+                    inputProxy = inputProxy,
+                ),
+        )
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/multishade/ui/viewmodel/ShadeViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/multishade/ui/viewmodel/ShadeViewModelTest.kt
new file mode 100644
index 0000000..e32aac5
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/multishade/ui/viewmodel/ShadeViewModelTest.kt
@@ -0,0 +1,226 @@
+/*
+ * 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.multishade.ui.viewmodel
+
+import androidx.test.filters.SmallTest
+import com.android.systemui.R
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.multishade.data.remoteproxy.MultiShadeInputProxy
+import com.android.systemui.multishade.domain.interactor.MultiShadeInteractor
+import com.android.systemui.multishade.domain.interactor.MultiShadeInteractorTest
+import com.android.systemui.multishade.shared.model.ProxiedInputModel
+import com.android.systemui.multishade.shared.model.ShadeId
+import com.google.common.truth.Truth.assertThat
+import com.google.common.truth.Truth.assertWithMessage
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(JUnit4::class)
+class ShadeViewModelTest : SysuiTestCase() {
+
+    private lateinit var testScope: TestScope
+    private lateinit var inputProxy: MultiShadeInputProxy
+    private var interactor: MultiShadeInteractor? = null
+
+    @Before
+    fun setUp() {
+        testScope = TestScope()
+        inputProxy = MultiShadeInputProxy()
+    }
+
+    @Test
+    fun isVisible_dualShadeConfig() =
+        testScope.runTest {
+            overrideResource(R.bool.dual_shade_enabled, true)
+            val isLeftShadeVisible: Boolean? by collectLastValue(create(ShadeId.LEFT).isVisible)
+            val isRightShadeVisible: Boolean? by collectLastValue(create(ShadeId.RIGHT).isVisible)
+            val isSingleShadeVisible: Boolean? by collectLastValue(create(ShadeId.SINGLE).isVisible)
+
+            assertThat(isLeftShadeVisible).isTrue()
+            assertThat(isRightShadeVisible).isTrue()
+            assertThat(isSingleShadeVisible).isFalse()
+        }
+
+    @Test
+    fun isVisible_singleShadeConfig() =
+        testScope.runTest {
+            overrideResource(R.bool.dual_shade_enabled, false)
+            val isLeftShadeVisible: Boolean? by collectLastValue(create(ShadeId.LEFT).isVisible)
+            val isRightShadeVisible: Boolean? by collectLastValue(create(ShadeId.RIGHT).isVisible)
+            val isSingleShadeVisible: Boolean? by collectLastValue(create(ShadeId.SINGLE).isVisible)
+
+            assertThat(isLeftShadeVisible).isFalse()
+            assertThat(isRightShadeVisible).isFalse()
+            assertThat(isSingleShadeVisible).isTrue()
+        }
+
+    @Test
+    fun isSwipingEnabled() =
+        testScope.runTest {
+            val underTest = create(ShadeId.LEFT)
+            val isSwipingEnabled: Boolean? by collectLastValue(underTest.isSwipingEnabled)
+            assertWithMessage("isSwipingEnabled should start as true!")
+                .that(isSwipingEnabled)
+                .isTrue()
+
+            // Need to collect proxied input so the flows become hot as the gesture cancelation code
+            // logic sits in side the proxiedInput flow for each shade.
+            collectLastValue(underTest.proxiedInput)
+            collectLastValue(create(ShadeId.RIGHT).proxiedInput)
+
+            // Starting a proxied interaction on the LEFT shade disallows non-proxied interaction on
+            // the
+            // same shade.
+            inputProxy.onProxiedInput(
+                ProxiedInputModel.OnDrag(xFraction = 0f, yDragAmountPx = 123f)
+            )
+            assertThat(isSwipingEnabled).isFalse()
+
+            // Registering the end of the proxied interaction re-allows it.
+            inputProxy.onProxiedInput(ProxiedInputModel.OnDragEnd)
+            assertThat(isSwipingEnabled).isTrue()
+
+            // Starting a proxied interaction on the RIGHT shade force-collapses the LEFT shade,
+            // disallowing non-proxied input on the LEFT shade.
+            inputProxy.onProxiedInput(
+                ProxiedInputModel.OnDrag(xFraction = 1f, yDragAmountPx = 123f)
+            )
+            assertThat(isSwipingEnabled).isFalse()
+
+            // Registering the end of the interaction on the RIGHT shade re-allows it.
+            inputProxy.onProxiedInput(ProxiedInputModel.OnDragEnd)
+            assertThat(isSwipingEnabled).isTrue()
+        }
+
+    @Test
+    fun isForceCollapsed_whenOtherShadeInteractionUnderway() =
+        testScope.runTest {
+            val leftShade = create(ShadeId.LEFT)
+            val rightShade = create(ShadeId.RIGHT)
+            val isLeftShadeForceCollapsed: Boolean? by collectLastValue(leftShade.isForceCollapsed)
+            val isRightShadeForceCollapsed: Boolean? by
+                collectLastValue(rightShade.isForceCollapsed)
+            val isSingleShadeForceCollapsed: Boolean? by
+                collectLastValue(create(ShadeId.SINGLE).isForceCollapsed)
+
+            assertWithMessage("isForceCollapsed should start as false!")
+                .that(isLeftShadeForceCollapsed)
+                .isFalse()
+            assertWithMessage("isForceCollapsed should start as false!")
+                .that(isRightShadeForceCollapsed)
+                .isFalse()
+            assertWithMessage("isForceCollapsed should start as false!")
+                .that(isSingleShadeForceCollapsed)
+                .isFalse()
+
+            // Registering the start of an interaction on the RIGHT shade force-collapses the LEFT
+            // shade.
+            rightShade.onDragStarted()
+            assertThat(isLeftShadeForceCollapsed).isTrue()
+            assertThat(isRightShadeForceCollapsed).isFalse()
+            assertThat(isSingleShadeForceCollapsed).isFalse()
+
+            // Registering the end of the interaction on the RIGHT shade re-allows it.
+            rightShade.onDragEnded()
+            assertThat(isLeftShadeForceCollapsed).isFalse()
+            assertThat(isRightShadeForceCollapsed).isFalse()
+            assertThat(isSingleShadeForceCollapsed).isFalse()
+
+            // Registering the start of an interaction on the LEFT shade force-collapses the RIGHT
+            // shade.
+            leftShade.onDragStarted()
+            assertThat(isLeftShadeForceCollapsed).isFalse()
+            assertThat(isRightShadeForceCollapsed).isTrue()
+            assertThat(isSingleShadeForceCollapsed).isFalse()
+
+            // Registering the end of the interaction on the LEFT shade re-allows it.
+            leftShade.onDragEnded()
+            assertThat(isLeftShadeForceCollapsed).isFalse()
+            assertThat(isRightShadeForceCollapsed).isFalse()
+            assertThat(isSingleShadeForceCollapsed).isFalse()
+        }
+
+    @Test
+    fun onTapOutside_collapsesAll() =
+        testScope.runTest {
+            val isLeftShadeForceCollapsed: Boolean? by
+                collectLastValue(create(ShadeId.LEFT).isForceCollapsed)
+            val isRightShadeForceCollapsed: Boolean? by
+                collectLastValue(create(ShadeId.RIGHT).isForceCollapsed)
+            val isSingleShadeForceCollapsed: Boolean? by
+                collectLastValue(create(ShadeId.SINGLE).isForceCollapsed)
+
+            assertWithMessage("isForceCollapsed should start as false!")
+                .that(isLeftShadeForceCollapsed)
+                .isFalse()
+            assertWithMessage("isForceCollapsed should start as false!")
+                .that(isRightShadeForceCollapsed)
+                .isFalse()
+            assertWithMessage("isForceCollapsed should start as false!")
+                .that(isSingleShadeForceCollapsed)
+                .isFalse()
+
+            inputProxy.onProxiedInput(ProxiedInputModel.OnTap)
+            assertThat(isLeftShadeForceCollapsed).isTrue()
+            assertThat(isRightShadeForceCollapsed).isTrue()
+            assertThat(isSingleShadeForceCollapsed).isTrue()
+        }
+
+    @Test
+    fun proxiedInput_ignoredWhileNonProxiedGestureUnderway() =
+        testScope.runTest {
+            val underTest = create(ShadeId.RIGHT)
+            val proxiedInput: ProxiedInputModel? by collectLastValue(underTest.proxiedInput)
+            underTest.onDragStarted()
+
+            inputProxy.onProxiedInput(ProxiedInputModel.OnDrag(0.9f, 100f))
+            assertThat(proxiedInput).isNull()
+
+            inputProxy.onProxiedInput(ProxiedInputModel.OnDrag(0.8f, 110f))
+            assertThat(proxiedInput).isNull()
+
+            underTest.onDragEnded()
+
+            inputProxy.onProxiedInput(ProxiedInputModel.OnDrag(0.9f, 100f))
+            assertThat(proxiedInput).isNotNull()
+        }
+
+    private fun create(
+        shadeId: ShadeId,
+    ): ShadeViewModel {
+        return ShadeViewModel(
+            viewModelScope = testScope.backgroundScope,
+            shadeId = shadeId,
+            interactor = interactor
+                    ?: MultiShadeInteractorTest.create(
+                            testScope = testScope,
+                            context = context,
+                            inputProxy = inputProxy,
+                        )
+                        .also { interactor = it },
+        )
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavBarHelperTest.java b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavBarHelperTest.java
index ce6a98c..9b8605d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavBarHelperTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavBarHelperTest.java
@@ -35,6 +35,7 @@
 import static org.mockito.Mockito.when;
 
 import android.content.ComponentName;
+import android.view.IWindowManager;
 import android.view.accessibility.AccessibilityManager;
 
 import androidx.test.filters.SmallTest;
@@ -46,7 +47,9 @@
 import com.android.systemui.accessibility.SystemActions;
 import com.android.systemui.assist.AssistManager;
 import com.android.systemui.dump.DumpManager;
+import com.android.systemui.navigationbar.gestural.EdgeBackGestureHandler;
 import com.android.systemui.recents.OverviewProxyService;
+import com.android.systemui.settings.DisplayTracker;
 import com.android.systemui.settings.UserTracker;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.phone.CentralSurfaces;
@@ -101,6 +104,14 @@
     NavBarHelper.NavbarTaskbarStateUpdater mNavbarTaskbarStateUpdater;
     @Mock
     CommandQueue mCommandQueue;
+    @Mock
+    IWindowManager mWm;
+    @Mock
+    DisplayTracker mDisplayTracker;
+    @Mock
+    EdgeBackGestureHandler mEdgeBackGestureHandler;
+    @Mock
+    EdgeBackGestureHandler.Factory mEdgeBackGestureHandlerFactory;
     private AccessibilityManager.AccessibilityServicesStateChangeListener
             mAccessibilityServicesStateChangeListener;
 
@@ -114,6 +125,8 @@
         when(mAssistManagerLazy.get()).thenReturn(mAssistManager);
         when(mAssistManager.getAssistInfoForUser(anyInt())).thenReturn(mAssistantComponent);
         when(mUserTracker.getUserId()).thenReturn(1);
+        when(mDisplayTracker.getDefaultDisplayId()).thenReturn(0);
+        when(mEdgeBackGestureHandlerFactory.create(any())).thenReturn(mEdgeBackGestureHandler);
 
         doAnswer((invocation) -> mAccessibilityServicesStateChangeListener =
                 invocation.getArgument(0)).when(
@@ -122,36 +135,70 @@
                 mAccessibilityButtonModeObserver, mAccessibilityButtonTargetObserver,
                 mSystemActions, mOverviewProxyService, mAssistManagerLazy,
                 () -> Optional.of(mock(CentralSurfaces.class)), mock(KeyguardStateController.class),
-                mNavigationModeController, mUserTracker, mDumpManager, mCommandQueue);
+                mNavigationModeController, mEdgeBackGestureHandlerFactory, mWm, mUserTracker,
+                mDisplayTracker, mDumpManager, mCommandQueue);
 
     }
 
     @Test
     public void registerListenersInCtor() {
-        verify(mAccessibilityButtonModeObserver, times(1)).addListener(mNavBarHelper);
         verify(mNavigationModeController, times(1)).addListener(mNavBarHelper);
         verify(mOverviewProxyService, times(1)).addCallback(mNavBarHelper);
+        verify(mCommandQueue, times(1)).addCallback(any());
     }
 
     @Test
-    public void registerAssistantContentObserver() {
-        mNavBarHelper.init();
+    public void testSetupBarsRegistersListeners() throws Exception {
+        mNavBarHelper.registerNavTaskStateUpdater(mNavbarTaskbarStateUpdater);
+        verify(mAccessibilityButtonModeObserver, times(1)).addListener(mNavBarHelper);
+        verify(mAccessibilityButtonTargetObserver, times(1)).addListener(mNavBarHelper);
+        verify(mAccessibilityManager, times(1)).addAccessibilityServicesStateChangeListener(
+                mNavBarHelper);
         verify(mAssistManager, times(1)).getAssistInfoForUser(anyInt());
+        verify(mWm, times(1)).watchRotation(any(), anyInt());
+        verify(mWm, times(1)).registerWallpaperVisibilityListener(any(), anyInt());
+        verify(mEdgeBackGestureHandler, times(1)).onNavBarAttached();
+    }
+
+    @Test
+    public void testCleanupBarsUnregistersListeners() throws Exception {
+        mNavBarHelper.registerNavTaskStateUpdater(mNavbarTaskbarStateUpdater);
+        mNavBarHelper.removeNavTaskStateUpdater(mNavbarTaskbarStateUpdater);
+        verify(mAccessibilityButtonModeObserver, times(1)).removeListener(mNavBarHelper);
+        verify(mAccessibilityButtonTargetObserver, times(1)).removeListener(mNavBarHelper);
+        verify(mAccessibilityManager, times(1)).removeAccessibilityServicesStateChangeListener(
+                mNavBarHelper);
+        verify(mWm, times(1)).removeRotationWatcher(any());
+        verify(mWm, times(1)).unregisterWallpaperVisibilityListener(any(), anyInt());
+        verify(mEdgeBackGestureHandler, times(1)).onNavBarDetached();
+    }
+
+    @Test
+    public void replacingBarsHint() {
+        mNavBarHelper.registerNavTaskStateUpdater(mNavbarTaskbarStateUpdater);
+        mNavBarHelper.setTogglingNavbarTaskbar(true);
+        mNavBarHelper.removeNavTaskStateUpdater(mNavbarTaskbarStateUpdater);
+        mNavBarHelper.registerNavTaskStateUpdater(mNavbarTaskbarStateUpdater);
+        mNavBarHelper.setTogglingNavbarTaskbar(false);
+        // Use any state in cleanup to verify it was not called
+        verify(mAccessibilityButtonModeObserver, times(0)).removeListener(mNavBarHelper);
     }
 
     @Test
     public void callbacksFiredWhenRegistering() {
-        mNavBarHelper.init();
         mNavBarHelper.registerNavTaskStateUpdater(mNavbarTaskbarStateUpdater);
         verify(mNavbarTaskbarStateUpdater, times(1))
                 .updateAccessibilityServicesState();
         verify(mNavbarTaskbarStateUpdater, times(1))
                 .updateAssistantAvailable(anyBoolean(), anyBoolean());
+        verify(mNavbarTaskbarStateUpdater, times(1))
+                .updateRotationWatcherState(anyInt());
+        verify(mNavbarTaskbarStateUpdater, times(1))
+                .updateWallpaperVisibility(anyBoolean(), anyInt());
     }
 
     @Test
     public void assistantCallbacksFiredAfterConnecting() {
-        mNavBarHelper.init();
         // 1st set of callbacks get called when registering
         mNavBarHelper.registerNavTaskStateUpdater(mNavbarTaskbarStateUpdater);
 
@@ -172,7 +219,6 @@
 
     @Test
     public void a11yCallbacksFiredAfterModeChange() {
-        mNavBarHelper.init();
         // 1st set of callbacks get called when registering
         mNavBarHelper.registerNavTaskStateUpdater(mNavbarTaskbarStateUpdater);
 
@@ -185,7 +231,6 @@
 
     @Test
     public void assistantCallbacksFiredAfterNavModeChange() {
-        mNavBarHelper.init();
         // 1st set of callbacks get called when registering
         mNavBarHelper.registerNavTaskStateUpdater(mNavbarTaskbarStateUpdater);
 
@@ -198,7 +243,6 @@
 
     @Test
     public void removeListenerNoCallbacksFired() {
-        mNavBarHelper.init();
         // 1st set of callbacks get called when registering
         mNavBarHelper.registerNavTaskStateUpdater(mNavbarTaskbarStateUpdater);
 
@@ -220,7 +264,7 @@
         when(mAccessibilityManager.getAccessibilityShortcutTargets(
                 AccessibilityManager.ACCESSIBILITY_BUTTON)).thenReturn(createFakeShortcutTargets());
 
-        mNavBarHelper.init();
+        mNavBarHelper.registerNavTaskStateUpdater(mNavbarTaskbarStateUpdater);
 
         assertThat(mNavBarHelper.getA11yButtonState()).isEqualTo(
                 ACCESSIBILITY_BUTTON_CLICKABLE_STATE);
@@ -230,13 +274,15 @@
     public void initAccessibilityStateWithFloatingMenuModeAndTargets_disableClickableState() {
         when(mAccessibilityButtonModeObserver.getCurrentAccessibilityButtonMode()).thenReturn(
                 ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU);
-        mNavBarHelper.init();
+
+        mNavBarHelper.registerNavTaskStateUpdater(mNavbarTaskbarStateUpdater);
 
         assertThat(mNavBarHelper.getA11yButtonState()).isEqualTo(/* disable_clickable_state */ 0);
     }
 
     @Test
     public void onA11yServicesStateChangedWithMultipleServices_a11yButtonClickableState() {
+        mNavBarHelper.registerNavTaskStateUpdater(mNavbarTaskbarStateUpdater);
         when(mAccessibilityButtonModeObserver.getCurrentAccessibilityButtonMode()).thenReturn(
                 ACCESSIBILITY_BUTTON_MODE_NAVIGATION_BAR);
 
@@ -250,14 +296,7 @@
     }
 
     @Test
-    public void registerCommandQueueCallbacks() {
-        mNavBarHelper.init();
-        verify(mCommandQueue, times(1)).addCallback(any());
-    }
-
-    @Test
     public void saveMostRecentSysuiState() {
-        mNavBarHelper.init();
         mNavBarHelper.setWindowState(DISPLAY_ID, WINDOW, STATE_ID);
         NavBarHelper.CurrentSysuiState state1 = mNavBarHelper.getCurrentSysuiState();
 
@@ -274,7 +313,6 @@
 
     @Test
     public void ignoreNonNavbarSysuiState() {
-        mNavBarHelper.init();
         mNavBarHelper.setWindowState(DISPLAY_ID, WINDOW, STATE_ID);
         NavBarHelper.CurrentSysuiState state1 = mNavBarHelper.getCurrentSysuiState();
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java
index 764ddc4..f062ec7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java
@@ -58,6 +58,7 @@
 import android.testing.TestableLooper.RunWithLooper;
 import android.view.Display;
 import android.view.DisplayInfo;
+import android.view.IWindowManager;
 import android.view.MotionEvent;
 import android.view.View;
 import android.view.ViewRootImpl;
@@ -87,6 +88,7 @@
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.recents.OverviewProxyService;
 import com.android.systemui.recents.Recents;
+import com.android.systemui.settings.DisplayTracker;
 import com.android.systemui.settings.FakeDisplayTracker;
 import com.android.systemui.settings.UserContextProvider;
 import com.android.systemui.settings.UserTracker;
@@ -172,8 +174,6 @@
     private UiEventLogger mUiEventLogger;
     @Mock
     private ViewTreeObserver mViewTreeObserver;
-    @Mock
-    EdgeBackGestureHandler mEdgeBackGestureHandler;
     NavBarHelper mNavBarHelper;
     @Mock
     private LightBarController mLightBarController;
@@ -205,6 +205,10 @@
     private Resources mResources;
     @Mock
     private ViewRootImpl mViewRootImpl;
+    @Mock
+    private EdgeBackGestureHandler.Factory mEdgeBackGestureHandlerFactory;
+    @Mock
+    private EdgeBackGestureHandler mEdgeBackGestureHandler;
     private FakeExecutor mFakeExecutor = new FakeExecutor(new FakeSystemClock());
     private DeviceConfigProxyFake mDeviceConfigProxyFake = new DeviceConfigProxyFake();
     private TaskStackChangeListeners mTaskStackChangeListeners =
@@ -234,6 +238,7 @@
                 .thenReturn(mContext);
         when(mNavigationBarView.getResources()).thenReturn(mResources);
         when(mNavigationBarView.getViewRootImpl()).thenReturn(mViewRootImpl);
+        when(mEdgeBackGestureHandlerFactory.create(any())).thenReturn(mEdgeBackGestureHandler);
         setupSysuiDependency();
         // This class inflates views that call Dependency.get, thus these injections are still
         // necessary.
@@ -250,7 +255,9 @@
                     mSystemActions, mOverviewProxyService,
                     () -> mock(AssistManager.class), () -> Optional.of(mCentralSurfaces),
                     mKeyguardStateController, mock(NavigationModeController.class),
-                    mock(UserTracker.class), mock(DumpManager.class), mock(CommandQueue.class)));
+                    mEdgeBackGestureHandlerFactory, mock(IWindowManager.class),
+                    mock(UserTracker.class), mock(DisplayTracker.class), mock(DumpManager.class),
+                    mock(CommandQueue.class)));
             mNavigationBar = createNavBar(mContext);
             mExternalDisplayNavigationBar = createNavBar(mSysuiTestableContextExternal);
         });
@@ -492,7 +499,6 @@
                 mDeadZone,
                 mDeviceConfigProxyFake,
                 mNavigationBarTransitions,
-                mEdgeBackGestureHandler,
                 Optional.of(mock(BackAnimation.class)),
                 mUserContextProvider,
                 mWakefulnessLifecycle,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTransitionsTest.java b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTransitionsTest.java
index 54e6509..fb08bf5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTransitionsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTransitionsTest.java
@@ -27,7 +27,6 @@
 import android.content.Context;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper.RunWithLooper;
-import android.view.IWindowManager;
 
 import androidx.test.filters.SmallTest;
 
@@ -60,8 +59,6 @@
     EdgeBackGestureHandler.Factory mEdgeBackGestureHandlerFactory;
     @Mock
     EdgeBackGestureHandler mEdgeBackGestureHandler;
-    @Mock
-    IWindowManager mIWindowManager;
 
     private NavigationBarTransitions mTransitions;
     private FakeDisplayTracker mDisplayTracker = new FakeDisplayTracker(mContext);
@@ -88,7 +85,7 @@
         when(navBar.getCurrentView()).thenReturn(navBar);
         when(navBar.findViewById(anyInt())).thenReturn(navBar);
         mTransitions = new NavigationBarTransitions(
-                navBar, mIWindowManager, mLightBarTransitionsFactory, mDisplayTracker);
+                navBar, mLightBarTransitionsFactory, mDisplayTracker);
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/TaskbarDelegateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/TaskbarDelegateTest.kt
index 1c9336a..8d01e80d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/TaskbarDelegateTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/TaskbarDelegateTest.kt
@@ -38,8 +38,6 @@
     private lateinit var mTaskStackChangeListeners: TaskStackChangeListeners
     private lateinit var mTaskbarDelegate: TaskbarDelegate
     @Mock
-    lateinit var mEdgeBackGestureHandlerFactory : EdgeBackGestureHandler.Factory
-    @Mock
     lateinit var mEdgeBackGestureHandler : EdgeBackGestureHandler
     @Mock
     lateinit var mLightBarControllerFactory : LightBarTransitionsController.Factory
@@ -73,13 +71,13 @@
     @Before
     fun setup() {
         MockitoAnnotations.initMocks(this)
-        `when`(mEdgeBackGestureHandlerFactory.create(context)).thenReturn(mEdgeBackGestureHandler)
+        `when`(mNavBarHelper.edgeBackGestureHandler).thenReturn(mEdgeBackGestureHandler)
         `when`(mLightBarControllerFactory.create(any())).thenReturn(mLightBarTransitionController)
         `when`(mNavBarHelper.currentSysuiState).thenReturn(mCurrentSysUiState)
         `when`(mSysUiState.setFlag(anyInt(), anyBoolean())).thenReturn(mSysUiState)
         mTaskStackChangeListeners = TaskStackChangeListeners.getTestInstance()
-        mTaskbarDelegate = TaskbarDelegate(context, mEdgeBackGestureHandlerFactory,
-                mLightBarControllerFactory, mStatusBarKeyguardViewManager)
+        mTaskbarDelegate = TaskbarDelegate(context, mLightBarControllerFactory,
+            mStatusBarKeyguardViewManager)
         mTaskbarDelegate.setDependencies(mCommandQueue, mOverviewProxyService, mNavBarHelper,
         mNavigationModeController, mSysUiState, mDumpManager, mAutoHideController,
                 mLightBarController, mOptionalPip, mBackAnimation, mTaskStackChangeListeners)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
index d86ff67..9a2e415 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
@@ -37,6 +37,7 @@
 import static org.mockito.Mockito.inOrder;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
@@ -940,6 +941,38 @@
 
     }
 
+    @Test
+    public void onSplitShadeChanged_duringShadeExpansion_resetsOverScrollState() {
+        // There was a bug where there was left-over overscroll state after going from split shade
+        // to single shade.
+        // Since on single shade we don't set overscroll values on QS nor Scrim, those values that
+        // were there from split shade were never reset.
+        // To prevent this, we will reset all overscroll state.
+        enableSplitShade(true);
+        reset(mQsController, mScrimController, mNotificationStackScrollLayoutController);
+
+        mNotificationPanelViewController.setOverExpansion(123);
+        verify(mQsController).setOverScrollAmount(123);
+        verify(mScrimController).setNotificationsOverScrollAmount(123);
+        verify(mNotificationStackScrollLayoutController).setOverExpansion(123);
+
+        enableSplitShade(false);
+        verify(mQsController).setOverScrollAmount(0);
+        verify(mScrimController).setNotificationsOverScrollAmount(0);
+        verify(mNotificationStackScrollLayoutController).setOverExpansion(0);
+    }
+
+    @Test
+    public void onSplitShadeChanged_alwaysResetsOverScrollState() {
+        enableSplitShade(true);
+        enableSplitShade(false);
+
+        verify(mQsController, times(2)).setOverScrollAmount(0);
+        verify(mScrimController, times(2)).setNotificationsOverScrollAmount(0);
+        verify(mNotificationStackScrollLayoutController, times(2)).setOverExpansion(0);
+        verify(mNotificationStackScrollLayoutController, times(2)).setOverScrollAmount(0);
+    }
+
     /**
      * When shade is flinging to close and this fling is not intercepted,
      * {@link AmbientState#setIsClosing(boolean)} should be called before
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/system/RemoteTransitionTest.java b/packages/SystemUI/tests/src/com/android/systemui/shared/system/RemoteTransitionTest.java
index 64e58d0..0e2a3ac 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shared/system/RemoteTransitionTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shared/system/RemoteTransitionTest.java
@@ -134,7 +134,7 @@
 
         TransitionInfoBuilder(@WindowManager.TransitionType int type) {
             mInfo = new TransitionInfo(type, 0 /* flags */);
-            mInfo.setRootLeash(createMockSurface(true /* valid */), 0, 0);
+            mInfo.addRootLeash(0, createMockSurface(true /* valid */), 0, 0);
         }
 
         TransitionInfoBuilder addChange(@WindowManager.TransitionType int mode,
@@ -144,6 +144,7 @@
             change.setMode(mode);
             change.setFlags(flags);
             change.setTaskInfo(taskInfo);
+            change.setDisplayId(0, 0);
             mInfo.addChange(change);
             return this;
         }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/smartspace/DreamSmartspaceControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/smartspace/DreamSmartspaceControllerTest.kt
index a280510..58b44ae 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/smartspace/DreamSmartspaceControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/smartspace/DreamSmartspaceControllerTest.kt
@@ -24,6 +24,7 @@
 import android.testing.TestableLooper
 import android.view.View
 import android.view.ViewGroup
+import android.widget.FrameLayout
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.dreams.smartspace.DreamSmartspaceController
@@ -46,6 +47,7 @@
 import org.mockito.Mockito
 import org.mockito.Mockito.verify
 import org.mockito.Mockito.`when`
+import org.mockito.Mockito.anyInt
 import org.mockito.MockitoAnnotations
 import org.mockito.Spy
 
@@ -69,12 +71,21 @@
     private lateinit var viewComponent: SmartspaceViewComponent
 
     @Mock
+    private lateinit var weatherViewComponent: SmartspaceViewComponent
+
+    @Spy
+    private var weatherSmartspaceView: SmartspaceView = TestView(context)
+
+    @Mock
     private lateinit var targetFilter: SmartspaceTargetFilter
 
     @Mock
     private lateinit var plugin: BcSmartspaceDataPlugin
 
     @Mock
+    private lateinit var weatherPlugin: BcSmartspaceDataPlugin
+
+    @Mock
     private lateinit var precondition: SmartspacePrecondition
 
     @Spy
@@ -88,6 +99,9 @@
 
     private lateinit var controller: DreamSmartspaceController
 
+    // TODO(b/272811280): Remove usage of real view
+    private val fakeParent = FrameLayout(context)
+
     /**
      * A class which implements SmartspaceView and extends View. This is mocked to provide the right
      * object inheritance and interface implementation used in DreamSmartspaceController
@@ -121,13 +135,17 @@
     @Before
     fun setup() {
         MockitoAnnotations.initMocks(this)
-        `when`(viewComponentFactory.create(any(), eq(plugin), any()))
+        `when`(viewComponentFactory.create(any(), eq(plugin), any(), eq(null)))
                 .thenReturn(viewComponent)
         `when`(viewComponent.getView()).thenReturn(smartspaceView)
+        `when`(viewComponentFactory.create(any(), eq(weatherPlugin), any(), any()))
+            .thenReturn(weatherViewComponent)
+        `when`(weatherViewComponent.getView()).thenReturn(weatherSmartspaceView)
         `when`(smartspaceManager.createSmartspaceSession(any())).thenReturn(session)
 
         controller = DreamSmartspaceController(context, smartspaceManager, execution, uiExecutor,
-                viewComponentFactory, precondition, Optional.of(targetFilter), Optional.of(plugin))
+                viewComponentFactory, precondition, Optional.of(targetFilter), Optional.of(plugin),
+        Optional.of(weatherPlugin))
     }
 
     /**
@@ -168,11 +186,11 @@
         `when`(precondition.conditionsMet()).thenReturn(true)
         controller.buildAndConnectView(Mockito.mock(ViewGroup::class.java))
 
-        var stateChangeListener = withArgCaptor<View.OnAttachStateChangeListener> {
-            verify(viewComponentFactory).create(any(), eq(plugin), capture())
+        val stateChangeListener = withArgCaptor<View.OnAttachStateChangeListener> {
+            verify(viewComponentFactory).create(any(), eq(plugin), capture(), eq(null))
         }
 
-        var mockView = Mockito.mock(TestView::class.java)
+        val mockView = Mockito.mock(TestView::class.java)
         `when`(precondition.conditionsMet()).thenReturn(true)
         stateChangeListener.onViewAttachedToWindow(mockView)
 
@@ -183,4 +201,74 @@
 
         verify(session).close()
     }
+
+    /**
+     * Ensures session is created when weather smartspace view is created and attached.
+     */
+    @Test
+    fun testConnectOnWeatherViewCreate() {
+        `when`(precondition.conditionsMet()).thenReturn(true)
+
+        val customView = Mockito.mock(TestView::class.java)
+        val weatherView = controller.buildAndConnectWeatherView(fakeParent, customView)
+        val weatherSmartspaceView = weatherView as SmartspaceView
+        fakeParent.addView(weatherView)
+
+        // Then weather view is created with custom view and the default weatherPlugin.getView
+        // should not be called
+        verify(viewComponentFactory).create(eq(fakeParent), eq(weatherPlugin), any(),
+            eq(customView))
+        verify(weatherPlugin, Mockito.never()).getView(fakeParent)
+
+        // And then session is created
+        controller.stateChangeListener.onViewAttachedToWindow(weatherView)
+        verify(smartspaceManager).createSmartspaceSession(any())
+        verify(weatherSmartspaceView).setPrimaryTextColor(anyInt())
+        verify(weatherSmartspaceView).setDozeAmount(0f)
+    }
+
+    /**
+     * Ensures weather plugin registers target listener when it is added from the controller.
+     */
+    @Test
+    fun testAddListenerInController_registersListenerForWeatherPlugin() {
+        val customView = Mockito.mock(TestView::class.java)
+        `when`(precondition.conditionsMet()).thenReturn(true)
+
+        // Given a session is created
+        val weatherView = controller.buildAndConnectWeatherView(fakeParent, customView)
+        controller.stateChangeListener.onViewAttachedToWindow(weatherView)
+        verify(smartspaceManager).createSmartspaceSession(any())
+
+        // When a listener is added
+        controller.addListenerForWeatherPlugin(listener)
+
+        // Then the listener is registered to the weather plugin only
+        verify(weatherPlugin).registerListener(listener)
+        verify(plugin, Mockito.never()).registerListener(any())
+    }
+
+    /**
+     * Ensures session is closed and weather plugin unregisters the notifier when weather smartspace
+     * view is detached.
+     */
+    @Test
+    fun testDisconnect_emitsEmptyListAndRemovesNotifier() {
+        `when`(precondition.conditionsMet()).thenReturn(true)
+
+        // Given a session is created
+        val customView = Mockito.mock(TestView::class.java)
+        val weatherView = controller.buildAndConnectWeatherView(fakeParent, customView)
+        controller.stateChangeListener.onViewAttachedToWindow(weatherView)
+        verify(smartspaceManager).createSmartspaceSession(any())
+
+        // When view is detached
+        controller.stateChangeListener.onViewDetachedFromWindow(weatherView)
+        // Then the session is closed
+        verify(session).close()
+
+        // And the listener receives an empty list of targets and unregisters the notifier
+        verify(weatherPlugin).onTargetsAvailable(emptyList())
+        verify(weatherPlugin).registerSmartspaceEventNotifier(null)
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java
index 3b4cc7c..251aced 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java
@@ -1042,24 +1042,6 @@
     }
 
     @Test
-    public void onRefreshBatteryInfo_pluggedWithOverheat_presentChargingLimited() {
-        createController();
-        BatteryStatus status = new BatteryStatus(BatteryManager.BATTERY_STATUS_DISCHARGING,
-                80 /* level */, BatteryManager.BATTERY_PLUGGED_AC,
-                BatteryManager.BATTERY_HEALTH_OVERHEAT, 0 /* maxChargingWattage */,
-                true /* present */);
-
-        mController.getKeyguardCallback().onRefreshBatteryInfo(status);
-        mController.setVisible(true);
-
-        verifyIndicationMessage(
-                INDICATION_TYPE_BATTERY,
-                mContext.getString(
-                        R.string.keyguard_plugged_in_charging_limited,
-                        NumberFormat.getPercentInstance().format(80 / 100f)));
-    }
-
-    @Test
     public void onRefreshBatteryInfo_fullChargedWithOverheat_presentChargingLimited() {
         createController();
         BatteryStatus status = new BatteryStatus(BatteryManager.BATTERY_STATUS_CHARGING,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.kt
index cb4f119..4bb14a1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.kt
@@ -22,6 +22,7 @@
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.dump.logcatLogBuffer
+import com.android.systemui.flags.Flags
 import com.android.systemui.statusbar.NotificationRemoteInputManager
 import com.android.systemui.statusbar.notification.NotifPipelineFlags
 import com.android.systemui.statusbar.notification.collection.GroupEntryBuilder
@@ -58,6 +59,7 @@
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.mockito.ArgumentMatchers.anyString
+import org.mockito.BDDMockito.clearInvocations
 import org.mockito.BDDMockito.given
 import org.mockito.Mockito.never
 import org.mockito.Mockito.times
@@ -166,6 +168,12 @@
         mGroupChild1 = mHelper.createChildNotification(GROUP_ALERT_ALL, 1, "child", 350)
         mGroupChild2 = mHelper.createChildNotification(GROUP_ALERT_ALL, 2, "child", 250)
         mGroupChild3 = mHelper.createChildNotification(GROUP_ALERT_ALL, 3, "child", 150)
+
+        // Set the default FSI decision
+        setShouldFullScreen(any(), FullScreenIntentDecision.NO_FULL_SCREEN_INTENT)
+
+        // Run tests with default feature flag state
+        whenever(mFlags.fsiOnDNDUpdate()).thenReturn(Flags.FSI_ON_DND_UPDATE.default)
     }
 
     @Test
@@ -810,6 +818,39 @@
     }
 
     @Test
+    fun onEntryAdded_whenLaunchingFSI_doesLogDecision() {
+        // GIVEN A new notification can FSI
+        setShouldFullScreen(mEntry, FullScreenIntentDecision.FSI_DEVICE_NOT_INTERACTIVE)
+        mCollectionListener.onEntryAdded(mEntry)
+
+        verify(mLaunchFullScreenIntentProvider).launchFullScreenIntent(mEntry)
+        verify(mNotificationInterruptStateProvider).logFullScreenIntentDecision(
+                mEntry, FullScreenIntentDecision.FSI_DEVICE_NOT_INTERACTIVE)
+    }
+
+    @Test
+    fun onEntryAdded_whenNotLaunchingFSI_doesLogDecision() {
+        // GIVEN A new notification can't FSI
+        setShouldFullScreen(mEntry, FullScreenIntentDecision.NO_FULL_SCREEN_INTENT)
+        mCollectionListener.onEntryAdded(mEntry)
+
+        verify(mLaunchFullScreenIntentProvider, never()).launchFullScreenIntent(any())
+        verify(mNotificationInterruptStateProvider).logFullScreenIntentDecision(
+                mEntry, FullScreenIntentDecision.NO_FULL_SCREEN_INTENT)
+    }
+
+    @Test
+    fun onEntryAdded_whenNotLaunchingFSIBecauseOfDnd_doesLogDecision() {
+        // GIVEN A new notification can't FSI because of DND
+        setShouldFullScreen(mEntry, FullScreenIntentDecision.NO_FSI_SUPPRESSED_ONLY_BY_DND)
+        mCollectionListener.onEntryAdded(mEntry)
+
+        verify(mLaunchFullScreenIntentProvider, never()).launchFullScreenIntent(any())
+        verify(mNotificationInterruptStateProvider).logFullScreenIntentDecision(
+                mEntry, FullScreenIntentDecision.NO_FSI_SUPPRESSED_ONLY_BY_DND)
+    }
+
+    @Test
     fun testOnRankingApplied_noFSIOnUpdateWhenFlagOff() {
         // Ensure the feature flag is off
         whenever(mFlags.fsiOnDNDUpdate()).thenReturn(false)
@@ -818,13 +859,22 @@
         setShouldFullScreen(mEntry, FullScreenIntentDecision.NO_FSI_SUPPRESSED_ONLY_BY_DND)
         mCollectionListener.onEntryAdded(mEntry)
 
+        // Verify that this causes a log
+        verify(mNotificationInterruptStateProvider).logFullScreenIntentDecision(
+                mEntry, FullScreenIntentDecision.NO_FSI_SUPPRESSED_ONLY_BY_DND)
+        clearInvocations(mNotificationInterruptStateProvider)
+
         // and it is then updated to allow full screen
         setShouldFullScreen(mEntry, FullScreenIntentDecision.FSI_DEVICE_NOT_INTERACTIVE)
         whenever(mNotifPipeline.allNotifs).thenReturn(listOf(mEntry))
         mCollectionListener.onRankingApplied()
 
         // THEN it should not full screen because the feature is off
-        verify(mLaunchFullScreenIntentProvider, never()).launchFullScreenIntent(mEntry)
+        verify(mLaunchFullScreenIntentProvider, never()).launchFullScreenIntent(any())
+
+        // VERIFY that no additional logging happens either
+        verify(mNotificationInterruptStateProvider, never())
+                .logFullScreenIntentDecision(any(), any())
     }
 
     @Test
@@ -836,8 +886,11 @@
         setShouldFullScreen(mEntry, FullScreenIntentDecision.NO_FSI_SUPPRESSED_ONLY_BY_DND)
         mCollectionListener.onEntryAdded(mEntry)
 
-        // at this point, it should not have full screened
-        verify(mLaunchFullScreenIntentProvider, never()).launchFullScreenIntent(mEntry)
+        // at this point, it should not have full screened, but should have logged
+        verify(mLaunchFullScreenIntentProvider, never()).launchFullScreenIntent(any())
+        verify(mNotificationInterruptStateProvider).logFullScreenIntentDecision(mEntry,
+                FullScreenIntentDecision.NO_FSI_SUPPRESSED_ONLY_BY_DND)
+        clearInvocations(mNotificationInterruptStateProvider)
 
         // and it is then updated to allow full screen AND HUN
         setShouldFullScreen(mEntry, FullScreenIntentDecision.FSI_DEVICE_NOT_INTERACTIVE)
@@ -847,10 +900,110 @@
         mBeforeTransformGroupsListener.onBeforeTransformGroups(listOf(mEntry))
         mBeforeFinalizeFilterListener.onBeforeFinalizeFilter(listOf(mEntry))
 
-        // THEN it should full screen but it should NOT HUN
+        // THEN it should full screen and log but it should NOT HUN
         verify(mLaunchFullScreenIntentProvider).launchFullScreenIntent(mEntry)
         verify(mHeadsUpViewBinder, never()).bindHeadsUpView(any(), any())
         verify(mHeadsUpManager, never()).showNotification(any())
+        verify(mNotificationInterruptStateProvider).logFullScreenIntentDecision(mEntry,
+                FullScreenIntentDecision.FSI_DEVICE_NOT_INTERACTIVE)
+        clearInvocations(mNotificationInterruptStateProvider)
+
+        // WHEN ranking updates again and the pipeline reruns
+        clearInvocations(mLaunchFullScreenIntentProvider)
+        mCollectionListener.onRankingApplied()
+        mBeforeTransformGroupsListener.onBeforeTransformGroups(listOf(mEntry))
+        mBeforeFinalizeFilterListener.onBeforeFinalizeFilter(listOf(mEntry))
+
+        // VERIFY that the FSI does not launch again or log
+        verify(mLaunchFullScreenIntentProvider, never()).launchFullScreenIntent(any())
+        verify(mNotificationInterruptStateProvider, never())
+                .logFullScreenIntentDecision(any(), any())
+    }
+
+    @Test
+    fun testOnRankingApplied_withOnlyDndSuppressionAllowsFsiLater() {
+        // Turn on the feature
+        whenever(mFlags.fsiOnDNDUpdate()).thenReturn(true)
+
+        // GIVEN that mEntry was previously suppressed from full-screen only by DND
+        setShouldFullScreen(mEntry, FullScreenIntentDecision.NO_FSI_SUPPRESSED_ONLY_BY_DND)
+        mCollectionListener.onEntryAdded(mEntry)
+
+        // at this point, it should not have full screened, but should have logged
+        verify(mLaunchFullScreenIntentProvider, never()).launchFullScreenIntent(any())
+        verify(mNotificationInterruptStateProvider).logFullScreenIntentDecision(mEntry,
+                FullScreenIntentDecision.NO_FSI_SUPPRESSED_ONLY_BY_DND)
+        clearInvocations(mNotificationInterruptStateProvider)
+
+        // ranking is applied with only DND blocking FSI
+        setShouldFullScreen(mEntry, FullScreenIntentDecision.NO_FSI_SUPPRESSED_ONLY_BY_DND)
+        mCollectionListener.onRankingApplied()
+        mBeforeTransformGroupsListener.onBeforeTransformGroups(listOf(mEntry))
+        mBeforeFinalizeFilterListener.onBeforeFinalizeFilter(listOf(mEntry))
+
+        // THEN it should still not yet full screen or HUN
+        verify(mLaunchFullScreenIntentProvider, never()).launchFullScreenIntent(any())
+        verify(mHeadsUpViewBinder, never()).bindHeadsUpView(any(), any())
+        verify(mHeadsUpManager, never()).showNotification(any())
+
+        // Same decision as before; is not logged
+        verify(mNotificationInterruptStateProvider, never())
+                .logFullScreenIntentDecision(any(), any())
+        clearInvocations(mNotificationInterruptStateProvider)
+
+        // and it is then updated to allow full screen AND HUN
+        setShouldFullScreen(mEntry, FullScreenIntentDecision.FSI_DEVICE_NOT_INTERACTIVE)
+        setShouldHeadsUp(mEntry)
+        whenever(mNotifPipeline.allNotifs).thenReturn(listOf(mEntry))
+        mCollectionListener.onRankingApplied()
+        mBeforeTransformGroupsListener.onBeforeTransformGroups(listOf(mEntry))
+        mBeforeFinalizeFilterListener.onBeforeFinalizeFilter(listOf(mEntry))
+
+        // THEN it should full screen and log but it should NOT HUN
+        verify(mLaunchFullScreenIntentProvider).launchFullScreenIntent(mEntry)
+        verify(mHeadsUpViewBinder, never()).bindHeadsUpView(any(), any())
+        verify(mHeadsUpManager, never()).showNotification(any())
+        verify(mNotificationInterruptStateProvider).logFullScreenIntentDecision(mEntry,
+                FullScreenIntentDecision.FSI_DEVICE_NOT_INTERACTIVE)
+        clearInvocations(mNotificationInterruptStateProvider)
+    }
+
+    @Test
+    fun testOnRankingApplied_newNonFullScreenAnswerInvalidatesCandidate() {
+        // Turn on the feature
+        whenever(mFlags.fsiOnDNDUpdate()).thenReturn(true)
+
+        // GIVEN that mEntry was previously suppressed from full-screen only by DND
+        whenever(mNotifPipeline.allNotifs).thenReturn(listOf(mEntry))
+        setShouldFullScreen(mEntry, FullScreenIntentDecision.NO_FSI_SUPPRESSED_ONLY_BY_DND)
+        mCollectionListener.onEntryAdded(mEntry)
+
+        // at this point, it should not have full screened
+        verify(mLaunchFullScreenIntentProvider, never()).launchFullScreenIntent(mEntry)
+
+        // now some other condition blocks FSI in addition to DND
+        setShouldFullScreen(mEntry, FullScreenIntentDecision.NO_FSI_SUPPRESSED_BY_DND)
+        mCollectionListener.onRankingApplied()
+        mBeforeTransformGroupsListener.onBeforeTransformGroups(listOf(mEntry))
+        mBeforeFinalizeFilterListener.onBeforeFinalizeFilter(listOf(mEntry))
+
+        // THEN it should NOT full screen or HUN
+        verify(mLaunchFullScreenIntentProvider, never()).launchFullScreenIntent(any())
+        verify(mHeadsUpViewBinder, never()).bindHeadsUpView(any(), any())
+        verify(mHeadsUpManager, never()).showNotification(any())
+
+        // NOW the DND logic changes and FSI and HUN are available
+        clearInvocations(mLaunchFullScreenIntentProvider)
+        setShouldFullScreen(mEntry, FullScreenIntentDecision.FSI_DEVICE_NOT_INTERACTIVE)
+        setShouldHeadsUp(mEntry)
+        mCollectionListener.onRankingApplied()
+        mBeforeTransformGroupsListener.onBeforeTransformGroups(listOf(mEntry))
+        mBeforeFinalizeFilterListener.onBeforeFinalizeFilter(listOf(mEntry))
+
+        // VERIFY that the FSI didn't happen, but that we do HUN
+        verify(mLaunchFullScreenIntentProvider, never()).launchFullScreenIntent(any())
+        finishBind(mEntry)
+        verify(mHeadsUpManager).showNotification(mEntry)
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImplTest.java
index 07d0dbd..8acf507 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImplTest.java
@@ -36,9 +36,12 @@
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.ArgumentMatchers.contains;
 import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.clearInvocations;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
 import static org.mockito.Mockito.when;
 
 import android.app.ActivityManager;
@@ -76,6 +79,10 @@
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+
 /**
  * Tests for the interruption state provider which understands whether the system & notification
  * is in a state allowing a particular notification to hun, pulse, or bubble.
@@ -560,7 +567,7 @@
                 .isFalse();
         verify(mLogger, never()).logFullscreen(any(), any());
         verify(mLogger, never()).logNoFullscreenWarning(any(), any());
-        verify(mLogger).logNoFullscreen(entry, "Suppressed by DND");
+        verify(mLogger).logNoFullscreen(entry, "NO_FSI_SUPPRESSED_ONLY_BY_DND");
     }
 
     @Test
@@ -579,7 +586,7 @@
                 .isFalse();
         verify(mLogger, never()).logFullscreen(any(), any());
         verify(mLogger, never()).logNoFullscreenWarning(any(), any());
-        verify(mLogger).logNoFullscreen(entry, "Suppressed by DND");
+        verify(mLogger).logNoFullscreen(entry, "NO_FSI_SUPPRESSED_BY_DND");
     }
 
     @Test
@@ -599,7 +606,7 @@
                 .isEqualTo(FullScreenIntentDecision.NO_FSI_NOT_IMPORTANT_ENOUGH);
         assertThat(mNotifInterruptionStateProvider.shouldLaunchFullScreenIntentWhenAdded(entry))
                 .isFalse();
-        verify(mLogger).logNoFullscreen(entry, "Not important enough");
+        verify(mLogger).logNoFullscreen(entry, "NO_FSI_NOT_IMPORTANT_ENOUGH");
         verify(mLogger, never()).logNoFullscreenWarning(any(), any());
         verify(mLogger, never()).logFullscreen(any(), any());
     }
@@ -622,7 +629,8 @@
         assertThat(mNotifInterruptionStateProvider.shouldLaunchFullScreenIntentWhenAdded(entry))
                 .isFalse();
         verify(mLogger, never()).logNoFullscreen(any(), any());
-        verify(mLogger).logNoFullscreenWarning(entry, "GroupAlertBehavior will prevent HUN");
+        verify(mLogger).logNoFullscreenWarning(entry,
+                "NO_FSI_SUPPRESSIVE_GROUP_ALERT_BEHAVIOR: GroupAlertBehavior will prevent HUN");
         verify(mLogger, never()).logFullscreen(any(), any());
 
         assertThat(mUiEventLoggerFake.numLogs()).isEqualTo(1);
@@ -652,7 +660,7 @@
                 .isTrue();
         verify(mLogger, never()).logNoFullscreen(any(), any());
         verify(mLogger, never()).logNoFullscreenWarning(any(), any());
-        verify(mLogger).logFullscreen(entry, "Device is not interactive");
+        verify(mLogger).logFullscreen(entry, "FSI_DEVICE_NOT_INTERACTIVE");
     }
 
     @Test
@@ -674,7 +682,7 @@
                 .isTrue();
         verify(mLogger, never()).logNoFullscreen(any(), any());
         verify(mLogger, never()).logNoFullscreenWarning(any(), any());
-        verify(mLogger).logFullscreen(entry, "Device is dreaming");
+        verify(mLogger).logFullscreen(entry, "FSI_DEVICE_IS_DREAMING");
     }
 
     @Test
@@ -696,7 +704,7 @@
                 .isTrue();
         verify(mLogger, never()).logNoFullscreen(any(), any());
         verify(mLogger, never()).logNoFullscreenWarning(any(), any());
-        verify(mLogger).logFullscreen(entry, "Keyguard is showing");
+        verify(mLogger).logFullscreen(entry, "FSI_KEYGUARD_SHOWING");
     }
 
     @Test
@@ -717,7 +725,7 @@
                 .isEqualTo(FullScreenIntentDecision.NO_FSI_EXPECTED_TO_HUN);
         assertThat(mNotifInterruptionStateProvider.shouldLaunchFullScreenIntentWhenAdded(entry))
                 .isFalse();
-        verify(mLogger).logNoFullscreen(entry, "Expected to HUN");
+        verify(mLogger).logNoFullscreen(entry, "NO_FSI_EXPECTED_TO_HUN");
         verify(mLogger, never()).logNoFullscreenWarning(any(), any());
         verify(mLogger, never()).logFullscreen(any(), any());
     }
@@ -737,7 +745,7 @@
                 .isTrue();
         verify(mLogger, never()).logNoFullscreen(any(), any());
         verify(mLogger, never()).logNoFullscreenWarning(any(), any());
-        verify(mLogger).logFullscreen(entry, "Expected not to HUN");
+        verify(mLogger).logFullscreen(entry, "FSI_EXPECTED_NOT_TO_HUN");
     }
 
     @Test
@@ -756,7 +764,7 @@
                 .isEqualTo(FullScreenIntentDecision.NO_FSI_EXPECTED_TO_HUN);
         assertThat(mNotifInterruptionStateProvider.shouldLaunchFullScreenIntentWhenAdded(entry))
                 .isFalse();
-        verify(mLogger).logNoFullscreen(entry, "Expected to HUN");
+        verify(mLogger).logNoFullscreen(entry, "NO_FSI_EXPECTED_TO_HUN");
         verify(mLogger, never()).logNoFullscreenWarning(any(), any());
         verify(mLogger, never()).logFullscreen(any(), any());
     }
@@ -802,7 +810,7 @@
                 .isEqualTo(FullScreenIntentDecision.NO_FSI_EXPECTED_TO_HUN);
         assertThat(mNotifInterruptionStateProvider.shouldLaunchFullScreenIntentWhenAdded(entry))
                 .isFalse();
-        verify(mLogger).logNoFullscreen(entry, "Expected to HUN");
+        verify(mLogger).logNoFullscreen(entry, "NO_FSI_EXPECTED_TO_HUN");
         verify(mLogger, never()).logNoFullscreenWarning(any(), any());
         verify(mLogger, never()).logFullscreen(any(), any());
     }
@@ -848,12 +856,37 @@
                 .isEqualTo(FullScreenIntentDecision.NO_FSI_EXPECTED_TO_HUN);
         assertThat(mNotifInterruptionStateProvider.shouldLaunchFullScreenIntentWhenAdded(entry))
                 .isFalse();
-        verify(mLogger).logNoFullscreen(entry, "Expected to HUN");
+        verify(mLogger).logNoFullscreen(entry, "NO_FSI_EXPECTED_TO_HUN");
         verify(mLogger, never()).logNoFullscreenWarning(any(), any());
         verify(mLogger, never()).logFullscreen(any(), any());
     }
 
     @Test
+    public void logFullScreenIntentDecision_shouldAlmostAlwaysLogOneTime() {
+        NotificationEntry entry = createFsiNotification(IMPORTANCE_HIGH, /* silenced */ false);
+        Set<FullScreenIntentDecision> warnings = new HashSet<>(Arrays.asList(
+                FullScreenIntentDecision.NO_FSI_SUPPRESSIVE_GROUP_ALERT_BEHAVIOR,
+                FullScreenIntentDecision.NO_FSI_NO_HUN_OR_KEYGUARD
+        ));
+        for (FullScreenIntentDecision decision : FullScreenIntentDecision.values()) {
+            clearInvocations(mLogger);
+            boolean expectedToLog = decision != FullScreenIntentDecision.NO_FULL_SCREEN_INTENT;
+            boolean isWarning = warnings.contains(decision);
+            mNotifInterruptionStateProvider.logFullScreenIntentDecision(entry, decision);
+            if (decision.shouldLaunch) {
+                verify(mLogger).logFullscreen(eq(entry), contains(decision.name()));
+            } else if (expectedToLog) {
+                if (isWarning) {
+                    verify(mLogger).logNoFullscreenWarning(eq(entry), contains(decision.name()));
+                } else {
+                    verify(mLogger).logNoFullscreen(eq(entry), contains(decision.name()));
+                }
+            }
+            verifyNoMoreInteractions(mLogger);
+        }
+    }
+
+    @Test
     public void testShouldHeadsUp_snoozed_unlocked_withStrictRules() throws Exception {
         when(mFlags.fullScreenIntentRequiresKeyguard()).thenReturn(true);
         NotificationEntry entry = createFsiNotification(IMPORTANCE_HIGH, /* silenced */ false);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowControllerTest.kt
index fb3aba1..c92134b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowControllerTest.kt
@@ -28,7 +28,6 @@
 import com.android.systemui.plugins.FalsingManager
 import com.android.systemui.plugins.PluginManager
 import com.android.systemui.plugins.statusbar.StatusBarStateController
-import com.android.systemui.statusbar.NotificationMediaManager
 import com.android.systemui.statusbar.SmartReplyController
 import com.android.systemui.statusbar.notification.collection.provider.NotificationDismissibilityProvider
 import com.android.systemui.statusbar.notification.collection.render.FakeNodeController
@@ -73,7 +72,6 @@
     private val logBufferLogger: NotificationRowLogger = mock()
     private val listContainer: NotificationListContainer = mock()
     private val childrenContainer: NotificationChildrenContainer = mock()
-    private val mediaManager: NotificationMediaManager = mock()
     private val smartReplyConstants: SmartReplyConstants = mock()
     private val smartReplyController: SmartReplyController = mock()
     private val pluginManager: PluginManager = mock()
@@ -95,6 +93,7 @@
     private val bubblesManager: BubblesManager = mock()
     private val dragController: ExpandableNotificationRowDragController = mock()
     private val dismissibilityProvider: NotificationDismissibilityProvider = mock()
+
     private lateinit var controller: ExpandableNotificationRowController
 
     @Before
@@ -108,7 +107,6 @@
                 metricsLogger,
                 logBufferLogger,
                 listContainer,
-                mediaManager,
                 smartReplyConstants,
                 smartReplyController,
                 pluginManager,
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 b0a46e1..f8a8e50 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
@@ -570,7 +570,6 @@
                 mHeadsUpManager,
                 mBindStage,
                 mock(OnExpandClickListener.class),
-                mock(NotificationMediaManager.class),
                 mock(ExpandableNotificationRow.CoordinateOnClickListener.class),
                 new FalsingManagerFake(),
                 new FalsingCollectorFake(),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
index e2019b2..3146262 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
@@ -19,6 +19,7 @@
 import static com.android.systemui.keyguard.shared.constants.KeyguardBouncerConstants.EXPANSION_HIDDEN;
 import static com.android.systemui.keyguard.shared.constants.KeyguardBouncerConstants.EXPANSION_VISIBLE;
 
+import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyBoolean;
@@ -35,6 +36,7 @@
 
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
+import android.view.MotionEvent;
 import android.view.View;
 import android.view.ViewGroup;
 import android.view.ViewRootImpl;
@@ -709,4 +711,48 @@
         // THEN alternate bouncer is NOT hidden
         verify(mAlternateBouncerInteractor, never()).hide();
     }
+
+    @Test
+    public void testAlternateBouncerToShowPrimaryBouncer_updatesScrimControllerOnce() {
+        // GIVEN the alternate bouncer has shown and calls to hide()  will result in successfully
+        // hiding it
+        when(mAlternateBouncerInteractor.hide()).thenReturn(true);
+        when(mKeyguardStateController.isShowing()).thenReturn(true);
+        when(mPrimaryBouncerInteractor.isFullyShowing()).thenReturn(false);
+        when(mAlternateBouncerInteractor.isVisibleState()).thenReturn(false);
+
+        // WHEN request to show primary bouncer
+        mStatusBarKeyguardViewManager.showPrimaryBouncer(true);
+
+        // THEN the scrim isn't updated from StatusBarKeyguardViewManager
+        verify(mCentralSurfaces, never()).updateScrimController();
+    }
+
+    @Test
+    public void testAlternateBouncerOnTouch_actionDown_doesNotHandleTouch() {
+        // GIVEN the alternate bouncer has shown for a minimum amount of time
+        when(mAlternateBouncerInteractor.hasAlternateBouncerShownWithMinTime()).thenReturn(true);
+        when(mAlternateBouncerInteractor.isVisibleState()).thenReturn(true);
+
+        // WHEN ACTION_DOWN touch event comes
+        boolean touchHandled = mStatusBarKeyguardViewManager.onTouch(
+                MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, 0f, 0));
+
+        // THEN the touch is not handled
+        assertFalse(touchHandled);
+    }
+
+    @Test
+    public void testAlternateBouncerOnTouch_actionUp_handlesTouch() {
+        // GIVEN the alternate bouncer has shown for a minimum amount of time
+        when(mAlternateBouncerInteractor.hasAlternateBouncerShownWithMinTime()).thenReturn(true);
+        when(mAlternateBouncerInteractor.isVisibleState()).thenReturn(true);
+
+        // WHEN ACTION_UP touch event comes
+        boolean touchHandled = mStatusBarKeyguardViewManager.onTouch(
+                MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_UP, 0f, 0f, 0));
+
+        // THEN the touch is handled
+        assertTrue(touchHandled);
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt
index f6e5959..542b688 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt
@@ -18,17 +18,16 @@
 
 import android.content.Intent
 import android.telephony.CarrierConfigManager.KEY_INFLATE_SIGNAL_STRENGTH_BOOL
-import android.telephony.CellSignalStrengthCdma
 import android.telephony.NetworkRegistrationInfo
 import android.telephony.ServiceState
 import android.telephony.ServiceState.STATE_IN_SERVICE
 import android.telephony.ServiceState.STATE_OUT_OF_SERVICE
-import android.telephony.SignalStrength
 import android.telephony.TelephonyCallback
 import android.telephony.TelephonyCallback.DataActivityListener
 import android.telephony.TelephonyCallback.ServiceStateListener
 import android.telephony.TelephonyDisplayInfo
 import android.telephony.TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_LTE_CA
+import android.telephony.TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NONE
 import android.telephony.TelephonyManager
 import android.telephony.TelephonyManager.DATA_ACTIVITY_DORMANT
 import android.telephony.TelephonyManager.DATA_ACTIVITY_IN
@@ -68,6 +67,7 @@
 import com.android.systemui.statusbar.pipeline.mobile.data.model.toNetworkNameModel
 import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionsRepository
 import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository.Companion.DEFAULT_NUM_LEVELS
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.prod.MobileTelephonyHelpers.signalStrength
 import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy
 import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel
 import com.android.systemui.statusbar.pipeline.shared.data.model.toMobileDataActivityModel
@@ -75,14 +75,12 @@
 import com.android.systemui.util.mockito.mock
 import com.android.systemui.util.mockito.whenever
 import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.cancel
 import kotlinx.coroutines.flow.launchIn
 import kotlinx.coroutines.flow.onEach
-import kotlinx.coroutines.runBlocking
-import org.junit.After
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.UnconfinedTestDispatcher
+import kotlinx.coroutines.test.runTest
 import org.junit.Before
 import org.junit.Test
 import org.mockito.Mock
@@ -99,7 +97,6 @@
     @Mock private lateinit var logger: MobileInputLogger
     @Mock private lateinit var tableLogger: TableLogBuffer
 
-    private val scope = CoroutineScope(IMMEDIATE)
     private val mobileMappings = FakeMobileMappingsProxy()
     private val systemUiCarrierConfig =
         SystemUiCarrierConfig(
@@ -107,6 +104,9 @@
             createTestConfig(),
         )
 
+    private val testDispatcher = UnconfinedTestDispatcher()
+    private val testScope = TestScope(testDispatcher)
+
     @Before
     fun setUp() {
         MockitoAnnotations.initMocks(this)
@@ -124,21 +124,16 @@
                 systemUiCarrierConfig,
                 fakeBroadcastDispatcher,
                 mobileMappings,
-                IMMEDIATE,
+                testDispatcher,
                 logger,
                 tableLogger,
-                scope,
+                testScope.backgroundScope,
             )
     }
 
-    @After
-    fun tearDown() {
-        scope.cancel()
-    }
-
     @Test
     fun emergencyOnly() =
-        runBlocking(IMMEDIATE) {
+        testScope.runTest {
             var latest: Boolean? = null
             val job = underTest.isEmergencyOnly.onEach { latest = it }.launchIn(this)
 
@@ -154,18 +149,15 @@
 
     @Test
     fun emergencyOnly_toggles() =
-        runBlocking(IMMEDIATE) {
+        testScope.runTest {
             var latest: Boolean? = null
             val job = underTest.isEmergencyOnly.onEach { latest = it }.launchIn(this)
 
             val callback = getTelephonyCallbackForType<ServiceStateListener>()
-            val serviceState = ServiceState()
-            serviceState.isEmergencyOnly = true
-            callback.onServiceStateChanged(serviceState)
+            callback.onServiceStateChanged(ServiceState().also { it.isEmergencyOnly = true })
             assertThat(latest).isTrue()
 
-            serviceState.isEmergencyOnly = false
-            callback.onServiceStateChanged(serviceState)
+            callback.onServiceStateChanged(ServiceState().also { it.isEmergencyOnly = false })
 
             assertThat(latest).isFalse()
 
@@ -174,7 +166,7 @@
 
     @Test
     fun cdmaLevelUpdates() =
-        runBlocking(IMMEDIATE) {
+        testScope.runTest {
             var latest: Int? = null
             val job = underTest.cdmaLevel.onEach { latest = it }.launchIn(this)
 
@@ -194,7 +186,7 @@
 
     @Test
     fun gsmLevelUpdates() =
-        runBlocking(IMMEDIATE) {
+        testScope.runTest {
             var latest: Int? = null
             val job = underTest.primaryLevel.onEach { latest = it }.launchIn(this)
 
@@ -214,7 +206,7 @@
 
     @Test
     fun isGsm() =
-        runBlocking(IMMEDIATE) {
+        testScope.runTest {
             var latest: Boolean? = null
             val job = underTest.isGsm.onEach { latest = it }.launchIn(this)
 
@@ -234,7 +226,7 @@
 
     @Test
     fun dataConnectionState_connected() =
-        runBlocking(IMMEDIATE) {
+        testScope.runTest {
             var latest: DataConnectionState? = null
             val job = underTest.dataConnectionState.onEach { latest = it }.launchIn(this)
 
@@ -249,7 +241,7 @@
 
     @Test
     fun dataConnectionState_connecting() =
-        runBlocking(IMMEDIATE) {
+        testScope.runTest {
             var latest: DataConnectionState? = null
             val job = underTest.dataConnectionState.onEach { latest = it }.launchIn(this)
 
@@ -264,7 +256,7 @@
 
     @Test
     fun dataConnectionState_disconnected() =
-        runBlocking(IMMEDIATE) {
+        testScope.runTest {
             var latest: DataConnectionState? = null
             val job = underTest.dataConnectionState.onEach { latest = it }.launchIn(this)
 
@@ -279,7 +271,7 @@
 
     @Test
     fun dataConnectionState_disconnecting() =
-        runBlocking(IMMEDIATE) {
+        testScope.runTest {
             var latest: DataConnectionState? = null
             val job = underTest.dataConnectionState.onEach { latest = it }.launchIn(this)
 
@@ -294,7 +286,7 @@
 
     @Test
     fun dataConnectionState_suspended() =
-        runBlocking(IMMEDIATE) {
+        testScope.runTest {
             var latest: DataConnectionState? = null
             val job = underTest.dataConnectionState.onEach { latest = it }.launchIn(this)
 
@@ -309,7 +301,7 @@
 
     @Test
     fun dataConnectionState_handoverInProgress() =
-        runBlocking(IMMEDIATE) {
+        testScope.runTest {
             var latest: DataConnectionState? = null
             val job = underTest.dataConnectionState.onEach { latest = it }.launchIn(this)
 
@@ -324,7 +316,7 @@
 
     @Test
     fun dataConnectionState_unknown() =
-        runBlocking(IMMEDIATE) {
+        testScope.runTest {
             var latest: DataConnectionState? = null
             val job = underTest.dataConnectionState.onEach { latest = it }.launchIn(this)
 
@@ -339,7 +331,7 @@
 
     @Test
     fun dataConnectionState_invalid() =
-        runBlocking(IMMEDIATE) {
+        testScope.runTest {
             var latest: DataConnectionState? = null
             val job = underTest.dataConnectionState.onEach { latest = it }.launchIn(this)
 
@@ -354,7 +346,7 @@
 
     @Test
     fun dataActivity() =
-        runBlocking(IMMEDIATE) {
+        testScope.runTest {
             var latest: DataActivityModel? = null
             val job = underTest.dataActivityDirection.onEach { latest = it }.launchIn(this)
 
@@ -368,7 +360,7 @@
 
     @Test
     fun carrierNetworkChange() =
-        runBlocking(IMMEDIATE) {
+        testScope.runTest {
             var latest: Boolean? = null
             val job = underTest.carrierNetworkChangeActive.onEach { latest = it }.launchIn(this)
 
@@ -382,7 +374,7 @@
 
     @Test
     fun networkType_default() =
-        runBlocking(IMMEDIATE) {
+        testScope.runTest {
             var latest: ResolvedNetworkType? = null
             val job = underTest.resolvedNetworkType.onEach { latest = it }.launchIn(this)
 
@@ -395,7 +387,7 @@
 
     @Test
     fun networkType_unknown_hasCorrectKey() =
-        runBlocking(IMMEDIATE) {
+        testScope.runTest {
             var latest: ResolvedNetworkType? = null
             val job = underTest.resolvedNetworkType.onEach { latest = it }.launchIn(this)
 
@@ -413,14 +405,19 @@
 
     @Test
     fun networkType_updatesUsingDefault() =
-        runBlocking(IMMEDIATE) {
+        testScope.runTest {
             var latest: ResolvedNetworkType? = null
             val job = underTest.resolvedNetworkType.onEach { latest = it }.launchIn(this)
 
             val callback = getTelephonyCallbackForType<TelephonyCallback.DisplayInfoListener>()
+            val overrideType = OVERRIDE_NETWORK_TYPE_NONE
             val type = NETWORK_TYPE_LTE
             val expected = DefaultNetworkType(mobileMappings.toIconKey(type))
-            val ti = mock<TelephonyDisplayInfo>().also { whenever(it.networkType).thenReturn(type) }
+            val ti =
+                mock<TelephonyDisplayInfo>().also {
+                    whenever(it.overrideNetworkType).thenReturn(overrideType)
+                    whenever(it.networkType).thenReturn(type)
+                }
             callback.onDisplayInfoChanged(ti)
 
             assertThat(latest).isEqualTo(expected)
@@ -430,7 +427,7 @@
 
     @Test
     fun networkType_updatesUsingOverride() =
-        runBlocking(IMMEDIATE) {
+        testScope.runTest {
             var latest: ResolvedNetworkType? = null
             val job = underTest.resolvedNetworkType.onEach { latest = it }.launchIn(this)
 
@@ -450,16 +447,38 @@
         }
 
     @Test
+    fun networkType_unknownNetworkWithOverride_usesOverrideKey() =
+        testScope.runTest {
+            var latest: ResolvedNetworkType? = null
+            val job = underTest.resolvedNetworkType.onEach { latest = it }.launchIn(this)
+
+            val callback = getTelephonyCallbackForType<TelephonyCallback.DisplayInfoListener>()
+            val unknown = NETWORK_TYPE_UNKNOWN
+            val type = OVERRIDE_NETWORK_TYPE_LTE_CA
+            val expected = OverrideNetworkType(mobileMappings.toIconKeyOverride(type))
+            val ti =
+                mock<TelephonyDisplayInfo>().also {
+                    whenever(it.networkType).thenReturn(unknown)
+                    whenever(it.overrideNetworkType).thenReturn(type)
+                }
+            callback.onDisplayInfoChanged(ti)
+
+            assertThat(latest).isEqualTo(expected)
+
+            job.cancel()
+        }
+
+    @Test
     fun dataEnabled_initial_false() =
-        runBlocking(IMMEDIATE) {
+        testScope.runTest {
             whenever(telephonyManager.isDataConnectionAllowed).thenReturn(false)
 
             assertThat(underTest.dataEnabled.value).isFalse()
         }
 
     @Test
-    fun `is data enabled - tracks telephony callback`() =
-        runBlocking(IMMEDIATE) {
+    fun isDataEnabled_tracksTelephonyCallback() =
+        testScope.runTest {
             var latest: Boolean? = null
             val job = underTest.dataEnabled.onEach { latest = it }.launchIn(this)
 
@@ -479,7 +498,7 @@
 
     @Test
     fun numberOfLevels_isDefault() =
-        runBlocking(IMMEDIATE) {
+        testScope.runTest {
             var latest: Int? = null
             val job = underTest.numberOfLevels.onEach { latest = it }.launchIn(this)
 
@@ -489,51 +508,68 @@
         }
 
     @Test
-    fun `roaming - cdma - queries telephony manager`() =
-        runBlocking(IMMEDIATE) {
+    fun roaming_cdma_queriesTelephonyManager() =
+        testScope.runTest {
             var latest: Boolean? = null
             val job = underTest.cdmaRoaming.onEach { latest = it }.launchIn(this)
 
             val cb = getTelephonyCallbackForType<ServiceStateListener>()
 
-            val serviceState = ServiceState()
-            serviceState.roaming = false
-
-            // CDMA roaming is off, GSM roaming is off
+            // CDMA roaming is off, GSM roaming is on
             whenever(telephonyManager.cdmaEnhancedRoamingIndicatorDisplayNumber).thenReturn(ERI_OFF)
-            cb.onServiceStateChanged(serviceState)
+            cb.onServiceStateChanged(ServiceState().also { it.roaming = true })
 
             assertThat(latest).isFalse()
 
-            // CDMA roaming is off, GSM roaming is on
+            // CDMA roaming is on, GSM roaming is off
             whenever(telephonyManager.cdmaEnhancedRoamingIndicatorDisplayNumber).thenReturn(ERI_ON)
-            cb.onServiceStateChanged(serviceState)
+            cb.onServiceStateChanged(ServiceState().also { it.roaming = false })
 
             assertThat(latest).isTrue()
 
             job.cancel()
         }
 
+    /**
+     * [TelephonyManager.getCdmaEnhancedRoamingIndicatorDisplayNumber] returns -1 if the service is
+     * not running or if there is an error while retrieving the cdma ERI
+     */
     @Test
-    fun `roaming - gsm - queries service state`() =
-        runBlocking(IMMEDIATE) {
+    fun cdmaRoaming_ignoresNegativeOne() =
+        testScope.runTest {
             var latest: Boolean? = null
-            val job = underTest.isRoaming.onEach { latest = it }.launchIn(this)
+            val job = underTest.cdmaRoaming.onEach { latest = it }.launchIn(this)
 
             val serviceState = ServiceState()
             serviceState.roaming = false
 
             val cb = getTelephonyCallbackForType<ServiceStateListener>()
 
-            // CDMA roaming is off, GSM roaming is off
-            whenever(telephonyManager.cdmaEnhancedRoamingIndicatorDisplayNumber).thenReturn(ERI_OFF)
+            // CDMA roaming is unavailable (-1), GSM roaming is off
+            whenever(telephonyManager.cdmaEnhancedRoamingIndicatorDisplayNumber).thenReturn(-1)
             cb.onServiceStateChanged(serviceState)
 
             assertThat(latest).isFalse()
 
+            job.cancel()
+        }
+
+    @Test
+    fun roaming_gsm_queriesServiceState() =
+        testScope.runTest {
+            var latest: Boolean? = null
+            val job = underTest.isRoaming.onEach { latest = it }.launchIn(this)
+
+            val cb = getTelephonyCallbackForType<ServiceStateListener>()
+
+            // CDMA roaming is off, GSM roaming is off
+            whenever(telephonyManager.cdmaEnhancedRoamingIndicatorDisplayNumber).thenReturn(ERI_OFF)
+            cb.onServiceStateChanged(ServiceState().also { it.roaming = false })
+
+            assertThat(latest).isFalse()
+
             // CDMA roaming is off, GSM roaming is on
-            serviceState.roaming = true
-            cb.onServiceStateChanged(serviceState)
+            cb.onServiceStateChanged(ServiceState().also { it.roaming = true })
 
             assertThat(latest).isTrue()
 
@@ -541,8 +577,8 @@
         }
 
     @Test
-    fun `activity - updates from callback`() =
-        runBlocking(IMMEDIATE) {
+    fun activity_updatesFromCallback() =
+        testScope.runTest {
             var latest: DataActivityModel? = null
             val job = underTest.dataActivityDirection.onEach { latest = it }.launchIn(this)
 
@@ -578,8 +614,8 @@
         }
 
     @Test
-    fun `network name - default`() =
-        runBlocking(IMMEDIATE) {
+    fun networkName_default() =
+        testScope.runTest {
             var latest: NetworkNameModel? = null
             val job = underTest.networkName.onEach { latest = it }.launchIn(this)
 
@@ -589,8 +625,8 @@
         }
 
     @Test
-    fun `network name - uses broadcast info - returns derived`() =
-        runBlocking(IMMEDIATE) {
+    fun networkName_usesBroadcastInfo_returnsDerived() =
+        testScope.runTest {
             var latest: NetworkNameModel? = null
             val job = underTest.networkName.onEach { latest = it }.launchIn(this)
 
@@ -606,8 +642,8 @@
         }
 
     @Test
-    fun `network name - broadcast not for this sub id - keeps old value`() =
-        runBlocking(IMMEDIATE) {
+    fun networkName_broadcastNotForThisSubId_keepsOldValue() =
+        testScope.runTest {
             var latest: NetworkNameModel? = null
             val job = underTest.networkName.onEach { latest = it }.launchIn(this)
 
@@ -631,8 +667,8 @@
         }
 
     @Test
-    fun `network name - broadcast has no data - updates to default`() =
-        runBlocking(IMMEDIATE) {
+    fun networkName_broadcastHasNoData_updatesToDefault() =
+        testScope.runTest {
             var latest: NetworkNameModel? = null
             val job = underTest.networkName.onEach { latest = it }.launchIn(this)
 
@@ -658,8 +694,8 @@
         }
 
     @Test
-    fun `operatorAlphaShort - tracked`() =
-        runBlocking(IMMEDIATE) {
+    fun operatorAlphaShort_tracked() =
+        testScope.runTest {
             var latest: String? = null
 
             val job = underTest.operatorAlphaShort.onEach { latest = it }.launchIn(this)
@@ -680,33 +716,45 @@
         }
 
     @Test
-    fun `connection model - isInService - not iwlan`() =
-        runBlocking(IMMEDIATE) {
+    fun isInService_notIwlan() =
+        testScope.runTest {
             var latest: Boolean? = null
             val job = underTest.isInService.onEach { latest = it }.launchIn(this)
 
-            val serviceState = ServiceState()
-            serviceState.voiceRegState = STATE_IN_SERVICE
-            serviceState.dataRegState = STATE_IN_SERVICE
-
-            getTelephonyCallbackForType<ServiceStateListener>().onServiceStateChanged(serviceState)
+            getTelephonyCallbackForType<ServiceStateListener>()
+                .onServiceStateChanged(
+                    ServiceState().also {
+                        it.voiceRegState = STATE_IN_SERVICE
+                        it.dataRegState = STATE_IN_SERVICE
+                    }
+                )
 
             assertThat(latest).isTrue()
 
-            serviceState.voiceRegState = STATE_OUT_OF_SERVICE
-            getTelephonyCallbackForType<ServiceStateListener>().onServiceStateChanged(serviceState)
+            getTelephonyCallbackForType<ServiceStateListener>()
+                .onServiceStateChanged(
+                    ServiceState().also {
+                        it.dataRegState = STATE_IN_SERVICE
+                        it.voiceRegState = STATE_OUT_OF_SERVICE
+                    }
+                )
             assertThat(latest).isTrue()
 
-            serviceState.dataRegState = STATE_OUT_OF_SERVICE
-            getTelephonyCallbackForType<ServiceStateListener>().onServiceStateChanged(serviceState)
+            getTelephonyCallbackForType<ServiceStateListener>()
+                .onServiceStateChanged(
+                    ServiceState().also {
+                        it.voiceRegState = STATE_OUT_OF_SERVICE
+                        it.dataRegState = STATE_OUT_OF_SERVICE
+                    }
+                )
             assertThat(latest).isFalse()
 
             job.cancel()
         }
 
     @Test
-    fun `connection model - isInService - is iwlan - voice out of service - data in service`() =
-        runBlocking(IMMEDIATE) {
+    fun isInService_isIwlan_voiceOutOfService_dataInService() =
+        testScope.runTest {
             var latest: Boolean? = null
             val job = underTest.isInService.onEach { latest = it }.launchIn(this)
 
@@ -730,8 +778,8 @@
         }
 
     @Test
-    fun `number of levels - uses carrier config`() =
-        runBlocking(IMMEDIATE) {
+    fun numberOfLevels_usesCarrierConfig() =
+        testScope.runTest {
             var latest: Int? = null
             val job = underTest.numberOfLevels.onEach { latest = it }.launchIn(this)
 
@@ -756,19 +804,6 @@
         return MobileTelephonyHelpers.getTelephonyCallbackForType(telephonyManager)
     }
 
-    /** Convenience constructor for SignalStrength */
-    private fun signalStrength(gsmLevel: Int, cdmaLevel: Int, isGsm: Boolean): SignalStrength {
-        val signalStrength = mock<SignalStrength>()
-        whenever(signalStrength.isGsm).thenReturn(isGsm)
-        whenever(signalStrength.level).thenReturn(gsmLevel)
-        val cdmaStrength =
-            mock<CellSignalStrengthCdma>().also { whenever(it.level).thenReturn(cdmaLevel) }
-        whenever(signalStrength.getCellSignalStrengths(CellSignalStrengthCdma::class.java))
-            .thenReturn(listOf(cdmaStrength))
-
-        return signalStrength
-    }
-
     private fun spnIntent(
         subId: Int = SUB_1_ID,
         showSpn: Boolean = true,
@@ -785,7 +820,6 @@
         }
 
     companion object {
-        private val IMMEDIATE = Dispatchers.Main.immediate
         private const val SUB_1_ID = 1
 
         private val DEFAULT_NAME = NetworkNameModel.Default("default name")
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionTelephonySmokeTests.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionTelephonySmokeTests.kt
new file mode 100644
index 0000000..bbf04ed2
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionTelephonySmokeTests.kt
@@ -0,0 +1,341 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.pipeline.mobile.data.repository.prod
+
+import android.telephony.ServiceState
+import android.telephony.TelephonyCallback
+import android.telephony.TelephonyCallback.CarrierNetworkListener
+import android.telephony.TelephonyCallback.DataActivityListener
+import android.telephony.TelephonyCallback.DataConnectionStateListener
+import android.telephony.TelephonyCallback.DataEnabledListener
+import android.telephony.TelephonyCallback.DisplayInfoListener
+import android.telephony.TelephonyCallback.ServiceStateListener
+import android.telephony.TelephonyDisplayInfo
+import android.telephony.TelephonyManager
+import android.telephony.TelephonyManager.DATA_ACTIVITY_INOUT
+import android.telephony.TelephonyManager.NETWORK_TYPE_LTE
+import android.telephony.TelephonyManager.NETWORK_TYPE_UNKNOWN
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.log.table.TableLogBuffer
+import com.android.systemui.statusbar.pipeline.mobile.data.MobileInputLogger
+import com.android.systemui.statusbar.pipeline.mobile.data.model.DataConnectionState
+import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameModel
+import com.android.systemui.statusbar.pipeline.mobile.data.model.ResolvedNetworkType
+import com.android.systemui.statusbar.pipeline.mobile.data.model.SystemUiCarrierConfig
+import com.android.systemui.statusbar.pipeline.mobile.data.model.SystemUiCarrierConfigTest
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionsRepository
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.prod.MobileTelephonyHelpers.getTelephonyCallbackForType
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.prod.MobileTelephonyHelpers.signalStrength
+import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy
+import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.UnconfinedTestDispatcher
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.mockito.Mock
+import org.mockito.MockitoAnnotations
+
+/**
+ * Test class to stress test the TelephonyCallbacks that we listen to. In particular, the callbacks
+ * all come back in on a single listener (for reasons defined in the system). This test is built to
+ * ensure that we don't miss any important callbacks.
+ *
+ * Kind of like an interaction test case build just for [TelephonyCallback]
+ *
+ * The list of telephony callbacks we use is: [TelephonyCallback.CarrierNetworkListener]
+ * [TelephonyCallback.DataActivityListener] [TelephonyCallback.DataConnectionStateListener]
+ * [TelephonyCallback.DataEnabledListener] [TelephonyCallback.DisplayInfoListener]
+ * [TelephonyCallback.ServiceStateListener] [TelephonyCallback.SignalStrengthsListener]
+ *
+ * Because each of these callbacks comes in on the same callbackFlow, collecting on a field backed
+ * by only a single callback can immediately create backpressure on the other fields related to a
+ * mobile connection.
+ *
+ * This test should be designed to test _at least_ each individual callback in a smoke-test fashion.
+ * The way we will achieve this is as follows:
+ * 1. Start up a listener (A) collecting on a field which is _not under test_
+ * 2. Send a single event to a telephony callback which supports the field under test (B)
+ * 3. Send many (may be as few as 2) events to the callback backing A to ensure we start seeing
+ *    backpressure on other fields NOTE: poor handling of backpressure here would normally cause B
+ *    to get dropped
+ * 4. Start up a new collector for B
+ * 5. Assert that B has the state sent in step #2
+ */
+@Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+class MobileConnectionTelephonySmokeTests : SysuiTestCase() {
+    private lateinit var underTest: MobileConnectionRepositoryImpl
+    private lateinit var connectionsRepo: FakeMobileConnectionsRepository
+
+    @Mock private lateinit var telephonyManager: TelephonyManager
+    @Mock private lateinit var logger: MobileInputLogger
+    @Mock private lateinit var tableLogger: TableLogBuffer
+
+    private val mobileMappings = FakeMobileMappingsProxy()
+    private val systemUiCarrierConfig =
+        SystemUiCarrierConfig(
+            SUB_1_ID,
+            SystemUiCarrierConfigTest.createTestConfig(),
+        )
+
+    private val testDispatcher = UnconfinedTestDispatcher()
+    private val testScope = TestScope(testDispatcher)
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+        whenever(telephonyManager.subscriptionId).thenReturn(SUB_1_ID)
+
+        connectionsRepo = FakeMobileConnectionsRepository(mobileMappings, tableLogger)
+
+        underTest =
+            MobileConnectionRepositoryImpl(
+                context,
+                SUB_1_ID,
+                DEFAULT_NAME,
+                SEP,
+                telephonyManager,
+                systemUiCarrierConfig,
+                fakeBroadcastDispatcher,
+                mobileMappings,
+                testDispatcher,
+                logger,
+                tableLogger,
+                testScope.backgroundScope,
+            )
+    }
+
+    @Test
+    fun carrierNetworkChangeListener_noisyActivity() =
+        testScope.runTest {
+            var latest: Boolean? = null
+
+            // Start collecting data activity; don't care about the result
+            val activityJob = underTest.dataActivityDirection.launchIn(this)
+            val activityCallback = getTelephonyCallbackForType<DataActivityListener>()
+
+            val callback = getTelephonyCallbackForType<CarrierNetworkListener>()
+            callback.onCarrierNetworkChange(true)
+
+            flipActivity(100, activityCallback)
+
+            val job = underTest.carrierNetworkChangeActive.onEach { latest = it }.launchIn(this)
+
+            assertThat(latest).isTrue()
+
+            activityJob.cancel()
+            job.cancel()
+        }
+
+    @Test
+    fun dataActivityLate_noisyDisplayInfo() =
+        testScope.runTest {
+            var latest: DataActivityModel? = null
+
+            // start collecting displayInfo; don't care about the result
+            val displayInfoJob = underTest.resolvedNetworkType.launchIn(this)
+
+            val activityCallback = getTelephonyCallbackForType<DataActivityListener>()
+            activityCallback.onDataActivity(DATA_ACTIVITY_INOUT)
+
+            val displayInfoCallback = getTelephonyCallbackForType<DisplayInfoListener>()
+            val type1 = NETWORK_TYPE_UNKNOWN
+            val type2 = NETWORK_TYPE_LTE
+            val t1 =
+                mock<TelephonyDisplayInfo>().also { whenever(it.networkType).thenReturn(type1) }
+            val t2 =
+                mock<TelephonyDisplayInfo>().also { whenever(it.networkType).thenReturn(type2) }
+
+            flipDisplayInfo(100, listOf(t1, t2), displayInfoCallback)
+
+            val job = underTest.dataActivityDirection.onEach { latest = it }.launchIn(this)
+
+            assertThat(latest)
+                .isEqualTo(
+                    DataActivityModel(
+                        hasActivityIn = true,
+                        hasActivityOut = true,
+                    )
+                )
+
+            displayInfoJob.cancel()
+            job.cancel()
+        }
+
+    @Test
+    fun dataConnectionStateListener_noisyActivity() =
+        testScope.runTest {
+            var latest: DataConnectionState? = null
+
+            // Start collecting data activity; don't care about the result
+            val activityJob = underTest.dataActivityDirection.launchIn(this)
+
+            val connectionCallback = getTelephonyCallbackForType<DataConnectionStateListener>()
+            val activityCallback = getTelephonyCallbackForType<DataActivityListener>()
+
+            connectionCallback.onDataConnectionStateChanged(
+                TelephonyManager.DATA_CONNECTED,
+                200 /* unused */
+            )
+
+            // Send a bunch of events that we don't care about, to overrun the replay buffer
+            flipActivity(100, activityCallback)
+
+            val connectionJob = underTest.dataConnectionState.onEach { latest = it }.launchIn(this)
+
+            assertThat(latest).isEqualTo(DataConnectionState.Connected)
+
+            activityJob.cancel()
+            connectionJob.cancel()
+        }
+
+    @Test
+    fun dataEnabledLate_noisyActivity() =
+        testScope.runTest {
+            var latest: Boolean? = null
+
+            // Start collecting data activity; don't care about the result
+            val activityJob = underTest.dataActivityDirection.launchIn(this)
+
+            val enabledCallback = getTelephonyCallbackForType<DataEnabledListener>()
+            val activityCallback = getTelephonyCallbackForType<DataActivityListener>()
+
+            enabledCallback.onDataEnabledChanged(true, 1 /* unused */)
+
+            // Send a bunch of events that we don't care about, to overrun the replay buffer
+            flipActivity(100, activityCallback)
+
+            val job = underTest.dataEnabled.onEach { latest = it }.launchIn(this)
+
+            assertThat(latest).isTrue()
+
+            activityJob.cancel()
+            job.cancel()
+        }
+
+    @Test
+    fun displayInfoLate_noisyActivity() =
+        testScope.runTest {
+            var latest: ResolvedNetworkType? = null
+
+            // Start collecting data activity; don't care about the result
+            val activityJob = underTest.dataActivityDirection.launchIn(this)
+
+            val displayInfoCallback = getTelephonyCallbackForType<DisplayInfoListener>()
+            val activityCallback = getTelephonyCallbackForType<DataActivityListener>()
+
+            val type = NETWORK_TYPE_LTE
+            val expected = ResolvedNetworkType.DefaultNetworkType(mobileMappings.toIconKey(type))
+            val ti = mock<TelephonyDisplayInfo>().also { whenever(it.networkType).thenReturn(type) }
+            displayInfoCallback.onDisplayInfoChanged(ti)
+
+            // Send a bunch of events that we don't care about, to overrun the replay buffer
+            flipActivity(100, activityCallback)
+
+            val job = underTest.resolvedNetworkType.onEach { latest = it }.launchIn(this)
+
+            assertThat(latest).isEqualTo(expected)
+
+            activityJob.cancel()
+            job.cancel()
+        }
+
+    @Test
+    fun serviceStateListener_noisyActivity() =
+        testScope.runTest {
+            var latest: Boolean? = null
+
+            // Start collecting data activity; don't care about the result
+            val activityJob = underTest.dataActivityDirection.launchIn(this)
+
+            val serviceStateCallback = getTelephonyCallbackForType<ServiceStateListener>()
+            val activityCallback = getTelephonyCallbackForType<DataActivityListener>()
+
+            // isEmergencyOnly comes in
+            val serviceState = ServiceState()
+            serviceState.isEmergencyOnly = true
+            serviceStateCallback.onServiceStateChanged(serviceState)
+
+            flipActivity(100, activityCallback)
+
+            val job = underTest.isEmergencyOnly.onEach { latest = it }.launchIn(this)
+
+            assertThat(latest).isTrue()
+
+            activityJob.cancel()
+            job.cancel()
+        }
+
+    @Test
+    fun signalStrengthsListenerLate_noisyActivity() =
+        testScope.runTest {
+            var latest: Int? = null
+
+            // Start collecting data activity; don't care about the result
+            val activityJob = underTest.dataActivityDirection.launchIn(this)
+            val activityCallback = getTelephonyCallbackForType<DataActivityListener>()
+
+            val callback = getTelephonyCallbackForType<TelephonyCallback.SignalStrengthsListener>()
+            val strength = signalStrength(gsmLevel = 1, cdmaLevel = 2, isGsm = true)
+            callback.onSignalStrengthsChanged(strength)
+
+            flipActivity(100, activityCallback)
+
+            val job = underTest.cdmaLevel.onEach { latest = it }.launchIn(this)
+
+            assertThat(latest).isEqualTo(2)
+
+            activityJob.cancel()
+            job.cancel()
+        }
+
+    private fun flipActivity(
+        times: Int,
+        callback: DataActivityListener,
+    ) {
+        repeat(times) { index -> callback.onDataActivity(index % 4) }
+    }
+
+    private fun flipDisplayInfo(
+        times: Int,
+        infos: List<TelephonyDisplayInfo>,
+        callback: DisplayInfoListener,
+    ) {
+        val len = infos.size
+        repeat(times) { index -> callback.onDisplayInfoChanged(infos[index % len]) }
+    }
+
+    private inline fun <reified T> getTelephonyCallbackForType(): T {
+        return getTelephonyCallbackForType(telephonyManager)
+    }
+
+    companion object {
+        private const val SUB_1_ID = 1
+
+        private val DEFAULT_NAME = NetworkNameModel.Default("default name")
+        private const val SEP = "-"
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileTelephonyHelpers.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileTelephonyHelpers.kt
index 621f793..d07b96f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileTelephonyHelpers.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileTelephonyHelpers.kt
@@ -16,10 +16,14 @@
 
 package com.android.systemui.statusbar.pipeline.mobile.data.repository.prod
 
+import android.telephony.CellSignalStrengthCdma
+import android.telephony.SignalStrength
 import android.telephony.TelephonyCallback
 import android.telephony.TelephonyManager
 import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.argumentCaptor
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
 import com.google.common.truth.Truth.assertThat
 import org.mockito.Mockito.verify
 
@@ -31,6 +35,19 @@
         return callbackCaptor.allValues
     }
 
+    /** Convenience constructor for SignalStrength */
+    fun signalStrength(gsmLevel: Int, cdmaLevel: Int, isGsm: Boolean): SignalStrength {
+        val signalStrength = mock<SignalStrength>()
+        whenever(signalStrength.isGsm).thenReturn(isGsm)
+        whenever(signalStrength.level).thenReturn(gsmLevel)
+        val cdmaStrength =
+            mock<CellSignalStrengthCdma>().also { whenever(it.level).thenReturn(cdmaLevel) }
+        whenever(signalStrength.getCellSignalStrengths(CellSignalStrengthCdma::class.java))
+            .thenReturn(listOf(cdmaStrength))
+
+        return signalStrength
+    }
+
     inline fun <reified T> getTelephonyCallbackForType(mockTelephonyManager: TelephonyManager): T {
         val cbs = getTelephonyCallbacks(mockTelephonyManager).filterIsInstance<T>()
         assertThat(cbs.size).isEqualTo(1)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelIconParameterizedTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelIconParameterizedTest.kt
index 12b1664..1c71f8b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelIconParameterizedTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelIconParameterizedTest.kt
@@ -368,40 +368,37 @@
 
                 // network = CarrierMerged => not shown
                 TestCase(
+                    enabled = true,
+                    isDefault = true,
+                    forceHidden = false,
                     network =
                         WifiNetworkModel.CarrierMerged(NETWORK_ID, subscriptionId = 1, level = 1),
                     expected = null,
                 ),
 
-                // network = Inactive => not shown
+                // isDefault = false => no networks shown
                 TestCase(
+                    isDefault = false,
                     network = WifiNetworkModel.Inactive,
                     expected = null,
                 ),
-
-                // network = Unavailable => not shown
                 TestCase(
+                    isDefault = false,
                     network = WifiNetworkModel.Unavailable,
                     expected = null,
                 ),
-
-                // network = Active & validated = false => not shown
                 TestCase(
+                    isDefault = false,
                     network = WifiNetworkModel.Active(NETWORK_ID, isValidated = false, level = 3),
                     expected = null,
                 ),
 
-                // network = Active & validated = true => shown
+                // Even though this network is active and validated, we still doesn't want it shown
+                // because wifi isn't the default connection (b/272509965).
                 TestCase(
+                    isDefault = false,
                     network = WifiNetworkModel.Active(NETWORK_ID, isValidated = true, level = 4),
-                    expected =
-                        Expected(
-                            iconResource = WIFI_FULL_ICONS[4],
-                            contentDescription = { context ->
-                                context.getString(WIFI_CONNECTION_STRENGTH[4])
-                            },
-                            description = "Full internet level 4 icon",
-                        ),
+                    expected = null,
                 ),
             )
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseShaderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseShaderTest.kt
new file mode 100644
index 0000000..71bd511
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseShaderTest.kt
@@ -0,0 +1,39 @@
+/*
+ * 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.surfaceeffects.turbulencenoise
+
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class TurbulenceNoiseShaderTest : SysuiTestCase() {
+
+    private lateinit var turbulenceNoiseShader: TurbulenceNoiseShader
+
+    @Test
+    fun compliesSimplexNoise() {
+        turbulenceNoiseShader = TurbulenceNoiseShader()
+    }
+
+    @Test
+    fun compliesFractalNoise() {
+        turbulenceNoiseShader = TurbulenceNoiseShader(useFractal = true)
+    }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardBouncerRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardBouncerRepository.kt
index 9cdce20..1dda472 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardBouncerRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardBouncerRepository.kt
@@ -19,21 +19,16 @@
 
 import com.android.systemui.keyguard.shared.constants.KeyguardBouncerConstants.EXPANSION_HIDDEN
 import com.android.systemui.keyguard.shared.model.BouncerShowMessageModel
-import com.android.systemui.keyguard.shared.model.KeyguardBouncerModel
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.asStateFlow
 
 /** Fake implementation of [KeyguardRepository] */
 class FakeKeyguardBouncerRepository : KeyguardBouncerRepository {
-    private val _primaryBouncerVisible = MutableStateFlow(false)
-    override val primaryBouncerVisible = _primaryBouncerVisible.asStateFlow()
-    private val _primaryBouncerShow = MutableStateFlow<KeyguardBouncerModel?>(null)
+    private val _primaryBouncerShow = MutableStateFlow(false)
     override val primaryBouncerShow = _primaryBouncerShow.asStateFlow()
     private val _primaryBouncerShowingSoon = MutableStateFlow(false)
     override val primaryBouncerShowingSoon = _primaryBouncerShowingSoon.asStateFlow()
-    private val _primaryBouncerHide = MutableStateFlow(false)
-    override val primaryBouncerHide = _primaryBouncerHide.asStateFlow()
     private val _primaryBouncerStartingToHide = MutableStateFlow(false)
     override val primaryBouncerStartingToHide = _primaryBouncerStartingToHide.asStateFlow()
     private val _primaryBouncerDisappearAnimation = MutableStateFlow<Runnable?>(null)
@@ -67,10 +62,6 @@
         _primaryBouncerScrimmed.value = isScrimmed
     }
 
-    override fun setPrimaryVisible(isVisible: Boolean) {
-        _primaryBouncerVisible.value = isVisible
-    }
-
     override fun setAlternateVisible(isVisible: Boolean) {
         _isAlternateBouncerVisible.value = isVisible
     }
@@ -79,18 +70,14 @@
         _isAlternateBouncerUIAvailable.value = isAvailable
     }
 
-    override fun setPrimaryShow(keyguardBouncerModel: KeyguardBouncerModel?) {
-        _primaryBouncerShow.value = keyguardBouncerModel
+    override fun setPrimaryShow(isShowing: Boolean) {
+        _primaryBouncerShow.value = isShowing
     }
 
     override fun setPrimaryShowingSoon(showingSoon: Boolean) {
         _primaryBouncerShowingSoon.value = showingSoon
     }
 
-    override fun setPrimaryHide(hide: Boolean) {
-        _primaryBouncerHide.value = hide
-    }
-
     override fun setPrimaryStartingToHide(startingToHide: Boolean) {
         _primaryBouncerStartingToHide.value = startingToHide
     }
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index 6503595..d422f9a 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -39,6 +39,7 @@
 import static com.android.internal.util.FunctionalUtils.ignoreRemoteException;
 import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
 import static com.android.server.accessibility.AccessibilityUserState.doesShortcutTargetsStringContain;
+import static com.android.settingslib.RestrictedLockUtils.EnforcedAdmin;
 
 import android.Manifest;
 import android.accessibilityservice.AccessibilityGestureEvent;
@@ -54,8 +55,10 @@
 import android.annotation.UserIdInt;
 import android.app.ActivityOptions;
 import android.app.AlertDialog;
+import android.app.AppOpsManager;
 import android.app.PendingIntent;
 import android.app.RemoteAction;
+import android.app.admin.DevicePolicyManager;
 import android.appwidget.AppWidgetManagerInternal;
 import android.content.ActivityNotFoundException;
 import android.content.BroadcastReceiver;
@@ -156,6 +159,7 @@
 import com.android.server.policy.WindowManagerPolicy;
 import com.android.server.wm.ActivityTaskManagerInternal;
 import com.android.server.wm.WindowManagerInternal;
+import com.android.settingslib.RestrictedLockUtils;
 
 import org.xmlpull.v1.XmlPullParserException;
 
@@ -3872,6 +3876,51 @@
     }
 
     @Override
+    @RequiresPermission(anyOf = {
+            android.Manifest.permission.MANAGE_USERS,
+            android.Manifest.permission.QUERY_ADMIN_POLICY})
+    public boolean isAccessibilityTargetAllowed(String packageName, int uid, int userId) {
+        final DevicePolicyManager dpm = mContext.getSystemService(DevicePolicyManager.class);
+        final List<String> permittedServices = dpm.getPermittedAccessibilityServices(userId);
+
+        // permittedServices null means all accessibility services are allowed.
+        boolean allowed = permittedServices == null || permittedServices.contains(packageName);
+        if (allowed) {
+            final AppOpsManager appOps = mContext.getSystemService(AppOpsManager.class);
+            final int mode = appOps.noteOpNoThrow(
+                    AppOpsManager.OP_ACCESS_RESTRICTED_SETTINGS,
+                    uid, packageName, /* attributionTag= */ null, /* message= */ null);
+            final boolean ecmEnabled = mContext.getResources().getBoolean(
+                    R.bool.config_enhancedConfirmationModeEnabled);
+            return !ecmEnabled || mode == AppOpsManager.MODE_ALLOWED;
+        }
+        return false;
+    }
+
+    @Override
+    @RequiresPermission(anyOf = {
+            android.Manifest.permission.MANAGE_USERS,
+            android.Manifest.permission.QUERY_ADMIN_POLICY})
+    public boolean sendRestrictedDialogIntent(String packageName, int uid, int userId) {
+        // The accessibility service is allowed. Don't show the restricted dialog.
+        if (isAccessibilityTargetAllowed(packageName, uid, userId)) {
+            return false;
+        }
+
+        final EnforcedAdmin admin =
+                RestrictedLockUtilsInternal.checkIfAccessibilityServiceDisallowed(
+                        mContext, packageName, userId);
+        if (admin != null) {
+            RestrictedLockUtils.sendShowAdminSupportDetailsIntent(mContext, admin);
+            return true;
+        }
+
+        RestrictedLockUtils.sendShowRestrictedSettingDialogIntent(mContext,
+                packageName, uid);
+        return true;
+    }
+
+    @Override
     public void dump(FileDescriptor fd, final PrintWriter pw, String[] args) {
         if (!DumpUtils.checkDumpPermission(mContext, LOG_TAG, pw)) return;
         synchronized (mLock) {
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilitySecurityPolicy.java b/services/accessibility/java/com/android/server/accessibility/AccessibilitySecurityPolicy.java
index 4806001..c37ea50 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilitySecurityPolicy.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilitySecurityPolicy.java
@@ -24,9 +24,7 @@
 import android.accessibilityservice.AccessibilityServiceInfo;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
-import android.annotation.UserIdInt;
 import android.app.AppOpsManager;
-import android.app.admin.DevicePolicyManager;
 import android.appwidget.AppWidgetManagerInternal;
 import android.content.ComponentName;
 import android.content.Context;
@@ -46,7 +44,6 @@
 
 import com.android.internal.util.ArrayUtils;
 import com.android.server.inputmethod.InputMethodManagerInternal;
-import com.android.settingslib.RestrictedLockUtils;
 
 import libcore.util.EmptyArray;
 
@@ -420,7 +417,7 @@
 
         // TODO(b/207697949, b/208872785): Add cts test for managed device.
         //  Use RestrictedLockUtilsInternal in AccessibilitySecurityPolicy
-        if (checkIfInputMethodDisallowed(
+        if (RestrictedLockUtilsInternal.checkIfInputMethodDisallowed(
                 mContext, inputMethodInfo.getPackageName(), callingUserId) != null) {
             return ENABLE_IME_FAIL_BY_ADMIN;
         }
@@ -429,72 +426,6 @@
     }
 
     /**
-     * @return the UserHandle for a userId. Return null for USER_NULL
-     */
-    private static UserHandle getUserHandleOf(@UserIdInt int userId) {
-        if (userId == UserHandle.USER_NULL) {
-            return null;
-        } else {
-            return UserHandle.of(userId);
-        }
-    }
-
-    private static int getManagedProfileId(Context context, int userId) {
-        UserManager um = context.getSystemService(UserManager.class);
-        List<UserInfo> userProfiles = um.getProfiles(userId);
-        for (UserInfo uInfo : userProfiles) {
-            if (uInfo.id == userId) {
-                continue;
-            }
-            if (uInfo.isManagedProfile()) {
-                return uInfo.id;
-            }
-        }
-        return UserHandle.USER_NULL;
-    }
-
-    private static RestrictedLockUtils.EnforcedAdmin checkIfInputMethodDisallowed(Context context,
-            String packageName, int userId) {
-        DevicePolicyManager dpm = context.getSystemService(DevicePolicyManager.class);
-        if (dpm == null) {
-            return null;
-        }
-        RestrictedLockUtils.EnforcedAdmin admin =
-                RestrictedLockUtils.getProfileOrDeviceOwner(context, getUserHandleOf(userId));
-        boolean permitted = true;
-        if (admin != null) {
-            permitted = dpm.isInputMethodPermittedByAdmin(admin.component,
-                    packageName, userId);
-        }
-
-        boolean permittedByParentAdmin = true;
-        RestrictedLockUtils.EnforcedAdmin profileAdmin = null;
-        int managedProfileId = getManagedProfileId(context, userId);
-        if (managedProfileId != UserHandle.USER_NULL) {
-            profileAdmin = RestrictedLockUtils.getProfileOrDeviceOwner(
-                    context, getUserHandleOf(managedProfileId));
-            // If the device is an organization-owned device with a managed profile, the
-            // managedProfileId will be used instead of the affected userId. This is because
-            // isInputMethodPermittedByAdmin is called on the parent DPM instance, which will
-            // return results affecting the personal profile.
-            if (profileAdmin != null && dpm.isOrganizationOwnedDeviceWithManagedProfile()) {
-                DevicePolicyManager parentDpm = dpm.getParentProfileInstance(
-                        UserManager.get(context).getUserInfo(managedProfileId));
-                permittedByParentAdmin = parentDpm.isInputMethodPermittedByAdmin(
-                        profileAdmin.component, packageName, managedProfileId);
-            }
-        }
-        if (!permitted && !permittedByParentAdmin) {
-            return RestrictedLockUtils.EnforcedAdmin.MULTIPLE_ENFORCED_ADMIN;
-        } else if (!permitted) {
-            return admin;
-        } else if (!permittedByParentAdmin) {
-            return profileAdmin;
-        }
-        return null;
-    }
-
-    /**
      * Returns the parent userId of the profile according to the specified userId.
      *
      * @param userId The userId to check
diff --git a/services/accessibility/java/com/android/server/accessibility/RestrictedLockUtilsInternal.java b/services/accessibility/java/com/android/server/accessibility/RestrictedLockUtilsInternal.java
new file mode 100644
index 0000000..28810e6
--- /dev/null
+++ b/services/accessibility/java/com/android/server/accessibility/RestrictedLockUtilsInternal.java
@@ -0,0 +1,140 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.accessibility;
+
+import static com.android.settingslib.RestrictedLockUtils.EnforcedAdmin;
+
+import android.annotation.UserIdInt;
+import android.app.admin.DevicePolicyManager;
+import android.content.Context;
+import android.content.pm.UserInfo;
+import android.os.UserHandle;
+import android.os.UserManager;
+
+import com.android.settingslib.RestrictedLockUtils;
+
+import java.util.List;
+
+/**
+ * Utility class to host methods usable to return {@link EnforcedAdmin} instances based on device
+ * admin policy.
+ */
+public class RestrictedLockUtilsInternal {
+
+    /**
+     * Disables accessibility service that are not permitted.
+     */
+    public static EnforcedAdmin checkIfAccessibilityServiceDisallowed(Context context,
+            String packageName, int userId) {
+        final DevicePolicyManager dpm = context.getSystemService(DevicePolicyManager.class);
+        if (dpm == null) {
+            return null;
+        }
+        final EnforcedAdmin admin =
+                RestrictedLockUtils.getProfileOrDeviceOwner(context, getUserHandleOf(userId));
+        boolean permitted = true;
+        if (admin != null) {
+            permitted = dpm.isAccessibilityServicePermittedByAdmin(admin.component,
+                    packageName, userId);
+        }
+        int managedProfileId = getManagedProfileId(context, userId);
+        final EnforcedAdmin profileAdmin = RestrictedLockUtils.getProfileOrDeviceOwner(context,
+                getUserHandleOf(managedProfileId));
+        boolean permittedByProfileAdmin = true;
+        if (profileAdmin != null) {
+            permittedByProfileAdmin = dpm.isAccessibilityServicePermittedByAdmin(
+                    profileAdmin.component, packageName, managedProfileId);
+        }
+        if (!permitted && !permittedByProfileAdmin) {
+            return EnforcedAdmin.MULTIPLE_ENFORCED_ADMIN;
+        } else if (!permitted) {
+            return admin;
+        } else if (!permittedByProfileAdmin) {
+            return profileAdmin;
+        }
+        return null;
+    }
+
+    /**
+     * Disables input method that are not permitted.
+     */
+    public static EnforcedAdmin checkIfInputMethodDisallowed(Context context, String packageName,
+            int userId) {
+        final DevicePolicyManager dpm = context.getSystemService(DevicePolicyManager.class);
+        if (dpm == null) {
+            return null;
+        }
+        final EnforcedAdmin admin =
+                RestrictedLockUtils.getProfileOrDeviceOwner(context, getUserHandleOf(userId));
+        boolean permitted = true;
+        if (admin != null) {
+            permitted = dpm.isInputMethodPermittedByAdmin(admin.component,
+                    packageName, userId);
+        }
+
+        boolean permittedByParentAdmin = true;
+        EnforcedAdmin profileAdmin = null;
+        int managedProfileId = getManagedProfileId(context, userId);
+        if (managedProfileId != UserHandle.USER_NULL) {
+            profileAdmin = RestrictedLockUtils.getProfileOrDeviceOwner(
+                    context, getUserHandleOf(managedProfileId));
+            // If the device is an organization-owned device with a managed profile, the
+            // managedProfileId will be used instead of the affected userId. This is because
+            // isInputMethodPermittedByAdmin is called on the parent DPM instance, which will
+            // return results affecting the personal profile.
+            if (profileAdmin != null && dpm.isOrganizationOwnedDeviceWithManagedProfile()) {
+                final DevicePolicyManager parentDpm = dpm.getParentProfileInstance(
+                        UserManager.get(context).getUserInfo(managedProfileId));
+                permittedByParentAdmin = parentDpm.isInputMethodPermittedByAdmin(
+                        profileAdmin.component, packageName, managedProfileId);
+            }
+        }
+        if (!permitted && !permittedByParentAdmin) {
+            return EnforcedAdmin.MULTIPLE_ENFORCED_ADMIN;
+        } else if (!permitted) {
+            return admin;
+        } else if (!permittedByParentAdmin) {
+            return profileAdmin;
+        }
+        return null;
+    }
+
+    private static int getManagedProfileId(Context context, int userId) {
+        final UserManager um = context.getSystemService(UserManager.class);
+        final List<UserInfo> userProfiles = um.getProfiles(userId);
+        for (UserInfo uInfo : userProfiles) {
+            if (uInfo.id == userId) {
+                continue;
+            }
+            if (uInfo.isManagedProfile()) {
+                return uInfo.id;
+            }
+        }
+        return UserHandle.USER_NULL;
+    }
+
+    /**
+     * @return the UserHandle for a userId. Return null for USER_NULL
+     */
+    private static UserHandle getUserHandleOf(@UserIdInt int userId) {
+        if (userId == UserHandle.USER_NULL) {
+            return null;
+        } else {
+            return UserHandle.of(userId);
+        }
+    }
+}
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationController.java b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationController.java
index f8ab0d5..ed8a35f 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationController.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationController.java
@@ -63,6 +63,7 @@
 import com.android.server.accessibility.AccessibilityTraceManager;
 import com.android.server.wm.WindowManagerInternal;
 
+import java.util.ArrayList;
 import java.util.Locale;
 import java.util.function.Supplier;
 
@@ -90,7 +91,9 @@
 
     private final ScreenStateObserver mScreenStateObserver;
 
-    private final MagnificationInfoChangedCallback mMagnificationInfoChangedCallback;
+    @GuardedBy("mLock")
+    private final ArrayList<MagnificationInfoChangedCallback>
+            mMagnificationInfoChangedCallbacks = new ArrayList<>();
 
     private final MagnificationScaleProvider mScaleProvider;
 
@@ -393,8 +396,10 @@
                     .setScale(scale)
                     .setCenterX(centerX)
                     .setCenterY(centerY).build();
-            mMagnificationInfoChangedCallback.onFullScreenMagnificationChanged(mDisplayId,
-                    mMagnificationRegion, config);
+            mMagnificationInfoChangedCallbacks.forEach(callback -> {
+                callback.onFullScreenMagnificationChanged(mDisplayId,
+                        mMagnificationRegion, config);
+            });
             if (mUnregisterPending && !isActivated()) {
                 unregister(mDeleteAfterUnregister);
             }
@@ -502,8 +507,10 @@
 
             if (changed) {
                 mMagnificationActivated = activated;
-                mMagnificationInfoChangedCallback.onFullScreenMagnificationActivationState(
-                        mDisplayId, mMagnificationActivated);
+                mMagnificationInfoChangedCallbacks.forEach(callback -> {
+                    callback.onFullScreenMagnificationActivationState(
+                            mDisplayId, mMagnificationActivated);
+                });
                 mControllerCtx.getWindowManager().setForceShowMagnifiableBounds(
                         mDisplayId, activated);
             }
@@ -580,8 +587,10 @@
             sendSpecToAnimation(mCurrentMagnificationSpec, animationCallback);
             if (isActivated() && (id != INVALID_SERVICE_ID)) {
                 mIdOfLastServiceToMagnify = id;
-                mMagnificationInfoChangedCallback.onRequestMagnificationSpec(mDisplayId,
-                        mIdOfLastServiceToMagnify);
+                mMagnificationInfoChangedCallbacks.forEach(callback -> {
+                    callback.onRequestMagnificationSpec(mDisplayId,
+                            mIdOfLastServiceToMagnify);
+                });
             }
             return changed;
         }
@@ -787,7 +796,7 @@
         mLock = lock;
         mMainThreadId = mControllerCtx.getContext().getMainLooper().getThread().getId();
         mScreenStateObserver = new ScreenStateObserver(mControllerCtx.getContext(), this);
-        mMagnificationInfoChangedCallback = magnificationInfoChangedCallback;
+        addInfoChangedCallback(magnificationInfoChangedCallback);
         mScaleProvider = scaleProvider;
         mDisplayManagerInternal = LocalServices.getService(DisplayManagerInternal.class);
         mThumbnailSupplier = thumbnailSupplier;
@@ -892,6 +901,10 @@
      */
     void onUserContextChanged(int displayId) {
         synchronized (mLock) {
+            if (!isActivated(displayId)) {
+                return;
+            }
+
             if (isAlwaysOnMagnificationEnabled()) {
                 setScaleAndCenter(displayId, 1.0f, Float.NaN, Float.NaN,
                         true,
@@ -1349,7 +1362,11 @@
      *                           hidden.
      */
     void notifyImeWindowVisibilityChanged(int displayId, boolean shown) {
-        mMagnificationInfoChangedCallback.onImeWindowVisibilityChanged(displayId, shown);
+        synchronized (mLock) {
+            mMagnificationInfoChangedCallbacks.forEach(callback -> {
+                callback.onImeWindowVisibilityChanged(displayId, shown);
+            });
+        }
     }
 
     private void onScreenTurnedOff() {
@@ -1411,6 +1428,18 @@
         }
     }
 
+    void addInfoChangedCallback(@NonNull MagnificationInfoChangedCallback callback) {
+        synchronized (mLock) {
+            mMagnificationInfoChangedCallbacks.add(callback);
+        }
+    }
+
+    void removeInfoChangedCallback(@NonNull MagnificationInfoChangedCallback callback) {
+        synchronized (mLock) {
+            mMagnificationInfoChangedCallbacks.remove(callback);
+        }
+    }
+
     private boolean traceEnabled() {
         return mControllerCtx.getTraceManager().isA11yTracingEnabledForTypes(
                 FLAGS_WINDOW_MANAGER_INTERNAL);
@@ -1709,7 +1738,7 @@
         return animate ? STUB_ANIMATION_CALLBACK : null;
     }
 
-    interface  MagnificationInfoChangedCallback {
+    interface MagnificationInfoChangedCallback {
 
         /**
          * Called when the {@link MagnificationSpec} is changed with non-default
@@ -1722,7 +1751,6 @@
 
         /**
          * Called when the state of the magnification activation is changed.
-         * It is for the logging data of the magnification activation state.
          *
          * @param displayId the logical display id
          * @param activated {@code true} if the magnification is activated, otherwise {@code false}.
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java
index f85ef43f..038847e 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java
@@ -32,6 +32,7 @@
 import static java.util.Arrays.asList;
 import static java.util.Arrays.copyOfRange;
 
+import android.accessibilityservice.MagnificationConfig;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.UiContext;
@@ -40,6 +41,7 @@
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.graphics.PointF;
+import android.graphics.Region;
 import android.os.Handler;
 import android.os.Looper;
 import android.os.Message;
@@ -129,6 +131,8 @@
 
     @VisibleForTesting final FullScreenMagnificationController mFullScreenMagnificationController;
 
+    private final FullScreenMagnificationController.MagnificationInfoChangedCallback
+            mMagnificationInfoChangedCallback;
     @VisibleForTesting final DelegatingState mDelegatingState;
     @VisibleForTesting final DetectingState mDetectingState;
     @VisibleForTesting final PanningScalingState mPanningScalingState;
@@ -158,6 +162,40 @@
                             + ", detectShortcutTrigger = " + detectShortcutTrigger + ")");
         }
         mFullScreenMagnificationController = fullScreenMagnificationController;
+        mMagnificationInfoChangedCallback =
+                new FullScreenMagnificationController.MagnificationInfoChangedCallback() {
+                    @Override
+                    public void onRequestMagnificationSpec(int displayId, int serviceId) {
+                        return;
+                    }
+
+                    @Override
+                    public void onFullScreenMagnificationActivationState(int displayId,
+                            boolean activated) {
+                        if (displayId != mDisplayId) {
+                            return;
+                        }
+
+                        if (!activated) {
+                            clearAndTransitionToStateDetecting();
+                        }
+                    }
+
+                    @Override
+                    public void onImeWindowVisibilityChanged(int displayId, boolean shown) {
+                        return;
+                    }
+
+                    @Override
+                    public void onFullScreenMagnificationChanged(int displayId,
+                            @NonNull Region region,
+                            @NonNull MagnificationConfig config) {
+                        return;
+                    }
+                };
+        mFullScreenMagnificationController.addInfoChangedCallback(
+                mMagnificationInfoChangedCallback);
+
         mPromptController = promptController;
 
         mDelegatingState = new DelegatingState();
@@ -217,6 +255,8 @@
         // Check if need to reset when MagnificationGestureHandler is the last magnifying service.
         mFullScreenMagnificationController.resetIfNeeded(
                 mDisplayId, AccessibilityManagerService.MAGNIFICATION_GESTURE_HANDLER_ID);
+        mFullScreenMagnificationController.removeInfoChangedCallback(
+                mMagnificationInfoChangedCallback);
         clearAndTransitionToStateDetecting();
     }
 
diff --git a/services/companion/java/com/android/server/companion/virtual/InputController.java b/services/companion/java/com/android/server/companion/virtual/InputController.java
index 607439b..bd67889 100644
--- a/services/companion/java/com/android/server/companion/virtual/InputController.java
+++ b/services/companion/java/com/android/server/companion/virtual/InputController.java
@@ -23,6 +23,7 @@
 import android.hardware.display.DisplayManagerInternal;
 import android.hardware.input.InputDeviceIdentifier;
 import android.hardware.input.InputManager;
+import android.hardware.input.InputManagerGlobal;
 import android.hardware.input.VirtualKeyEvent;
 import android.hardware.input.VirtualMouseButtonEvent;
 import android.hardware.input.VirtualMouseRelativeEvent;
@@ -686,7 +687,7 @@
             mListener = new InputManager.InputDeviceListener() {
                 @Override
                 public void onInputDeviceAdded(int deviceId) {
-                    final InputDevice device = InputManager.getInstance().getInputDevice(
+                    final InputDevice device = InputManagerGlobal.getInstance().getInputDevice(
                             deviceId);
                     Objects.requireNonNull(device, "Newly added input device was null.");
                     if (!device.getName().equals(deviceName)) {
diff --git a/services/core/java/com/android/server/TelephonyRegistry.java b/services/core/java/com/android/server/TelephonyRegistry.java
index bffa3dd..e76b628 100644
--- a/services/core/java/com/android/server/TelephonyRegistry.java
+++ b/services/core/java/com/android/server/TelephonyRegistry.java
@@ -28,6 +28,7 @@
 import android.annotation.Nullable;
 import android.app.ActivityManager;
 import android.app.AppOpsManager;
+import android.app.BroadcastOptions;
 import android.app.compat.CompatChanges;
 import android.compat.annotation.ChangeId;
 import android.compat.annotation.EnabledSince;
@@ -3533,28 +3534,36 @@
         // - Sanitized ServiceState sent to all other apps with READ_PHONE_STATE
         // - Sanitized ServiceState sent to all other apps with READ_PRIVILEGED_PHONE_STATE but not
         //   READ_PHONE_STATE
+        BroadcastOptions options = createServiceStateBroadcastOptions(subId, phoneId);
         if (LocationAccessPolicy.isLocationModeEnabled(mContext, mContext.getUserId())) {
             Intent fullIntent = createServiceStateIntent(state, subId, phoneId, false);
             mContext.createContextAsUser(UserHandle.ALL, 0).sendBroadcastMultiplePermissions(
                     fullIntent,
                     new String[]{Manifest.permission.READ_PHONE_STATE,
-                            Manifest.permission.ACCESS_FINE_LOCATION});
+                            Manifest.permission.ACCESS_FINE_LOCATION},
+                    options);
             mContext.createContextAsUser(UserHandle.ALL, 0).sendBroadcastMultiplePermissions(
                     fullIntent,
                     new String[]{Manifest.permission.READ_PRIVILEGED_PHONE_STATE,
                             Manifest.permission.ACCESS_FINE_LOCATION},
-                    new String[]{Manifest.permission.READ_PHONE_STATE});
+                    new String[]{Manifest.permission.READ_PHONE_STATE},
+                    null,
+                    options);
 
             Intent sanitizedIntent = createServiceStateIntent(state, subId, phoneId, true);
             mContext.createContextAsUser(UserHandle.ALL, 0).sendBroadcastMultiplePermissions(
                     sanitizedIntent,
                     new String[]{Manifest.permission.READ_PHONE_STATE},
-                    new String[]{Manifest.permission.ACCESS_FINE_LOCATION});
+                    new String[]{Manifest.permission.ACCESS_FINE_LOCATION},
+                    null,
+                    options);
             mContext.createContextAsUser(UserHandle.ALL, 0).sendBroadcastMultiplePermissions(
                     sanitizedIntent,
                     new String[]{Manifest.permission.READ_PRIVILEGED_PHONE_STATE},
                     new String[]{Manifest.permission.READ_PHONE_STATE,
-                            Manifest.permission.ACCESS_FINE_LOCATION});
+                            Manifest.permission.ACCESS_FINE_LOCATION},
+                    null,
+                    options);
         } else {
             String[] locationBypassPackages = Binder.withCleanCallingIdentity(() ->
                     LocationAccessPolicy.getLocationBypassPackages(mContext));
@@ -3563,11 +3572,14 @@
                 fullIntent.setPackage(locationBypassPackage);
                 mContext.createContextAsUser(UserHandle.ALL, 0).sendBroadcastMultiplePermissions(
                         fullIntent,
-                        new String[]{Manifest.permission.READ_PHONE_STATE});
+                        new String[]{Manifest.permission.READ_PHONE_STATE},
+                        options);
                 mContext.createContextAsUser(UserHandle.ALL, 0).sendBroadcastMultiplePermissions(
                         fullIntent,
                         new String[]{Manifest.permission.READ_PRIVILEGED_PHONE_STATE},
-                        new String[]{Manifest.permission.READ_PHONE_STATE});
+                        new String[]{Manifest.permission.READ_PHONE_STATE},
+                        null,
+                        options);
             }
 
             Intent sanitizedIntent = createServiceStateIntent(state, subId, phoneId, true);
@@ -3575,12 +3587,14 @@
                     sanitizedIntent,
                     new String[]{Manifest.permission.READ_PHONE_STATE},
                     new String[]{/* no excluded permissions */},
-                    locationBypassPackages);
+                    locationBypassPackages,
+                    options);
             mContext.createContextAsUser(UserHandle.ALL, 0).sendBroadcastMultiplePermissions(
                     sanitizedIntent,
                     new String[]{Manifest.permission.READ_PRIVILEGED_PHONE_STATE},
                     new String[]{Manifest.permission.READ_PHONE_STATE},
-                    locationBypassPackages);
+                    locationBypassPackages,
+                    options);
         }
     }
 
@@ -3602,6 +3616,15 @@
         return intent;
     }
 
+    private BroadcastOptions createServiceStateBroadcastOptions(int subId, int phoneId) {
+        return new BroadcastOptions()
+                .setDeliveryGroupPolicy(BroadcastOptions.DELIVERY_GROUP_POLICY_MOST_RECENT)
+                // Use a combination of subId and phoneId as the key so that older broadcasts
+                // with same subId and phoneId will get discarded.
+                .setDeliveryGroupMatchingKey(Intent.ACTION_SERVICE_STATE, subId + "-" + phoneId)
+                .setDeferralPolicy(BroadcastOptions.DEFERRAL_POLICY_UNTIL_ACTIVE);
+    }
+
     private void broadcastSignalStrengthChanged(SignalStrength signalStrength, int phoneId,
             int subId) {
         final long ident = Binder.clearCallingIdentity();
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index c5008fa..9bde039 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -337,6 +337,13 @@
     final ArrayMap<ForegroundServiceDelegation, ServiceRecord> mFgsDelegations = new ArrayMap<>();
 
     /**
+     * A global counter for generating sequence numbers to uniquely identify bindService requests.
+     * It is purely for logging purposes.
+     */
+    @GuardedBy("mAm")
+    private long mBindServiceSeqCounter = 0;
+
+    /**
      * Whether there is a rate limit that suppresses immediate re-deferral of new FGS
      * notifications from each app.  On by default, disabled only by shell command for
      * test-suite purposes.  To disable the behavior more generally, use the usual
@@ -4429,8 +4436,12 @@
             try {
                 bumpServiceExecutingLocked(r, execInFg, "bind",
                         OomAdjuster.OOM_ADJ_REASON_BIND_SERVICE);
+                if (Trace.isTagEnabled(Trace.TRACE_TAG_ACTIVITY_MANAGER)) {
+                    Trace.instant(Trace.TRACE_TAG_ACTIVITY_MANAGER, "requestServiceBinding="
+                            + i.intent.getIntent() + ". bindSeq=" + mBindServiceSeqCounter);
+                }
                 r.app.getThread().scheduleBindService(r, i.intent.getIntent(), rebind,
-                        r.app.mState.getReportedProcState());
+                        r.app.mState.getReportedProcState(), mBindServiceSeqCounter++);
                 if (!rebind) {
                     i.requested = true;
                 }
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 4b2467d..2a1e860 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -13102,8 +13102,8 @@
     @Override
     public void logFgsApiBegin(@ForegroundServiceApiType int apiType,
             int uid, int pid) {
-        enforceCallingPermission(android.Manifest.permission.LOG_PROCESS_ACTIVITIES,
-                "logFgsApiStart");
+        enforceCallingPermission(android.Manifest.permission.LOG_FOREGROUND_RESOURCE_USE,
+                "logFgsApiBegin");
         synchronized (this) {
             mServices.logFgsApiBeginLocked(apiType, uid, pid);
         }
@@ -13112,8 +13112,8 @@
     @Override
     public void logFgsApiEnd(@ForegroundServiceApiType int apiType,
             int uid, int pid) {
-        enforceCallingPermission(android.Manifest.permission.LOG_PROCESS_ACTIVITIES,
-                "logFgsApiStart");
+        enforceCallingPermission(android.Manifest.permission.LOG_FOREGROUND_RESOURCE_USE,
+                "logFgsApiEnd");
         synchronized (this) {
             mServices.logFgsApiEndLocked(apiType, uid, pid);
         }
@@ -13122,8 +13122,8 @@
     @Override
     public void logFgsApiStateChanged(@ForegroundServiceApiType int apiType,
             int state, int uid, int pid) {
-        enforceCallingPermission(android.Manifest.permission.LOG_PROCESS_ACTIVITIES,
-                "logFgsApiStart");
+        enforceCallingPermission(android.Manifest.permission.LOG_FOREGROUND_RESOURCE_USE,
+                "logFgsApiEvent");
         synchronized (this) {
             mServices.logFgsApiStateChangedLocked(apiType, uid, pid, state);
         }
@@ -19729,8 +19729,9 @@
                 final long identity = Binder.clearCallingIdentity();
                 try {
                     return superImpl.apply(code, new AttributionSource(shellUid,
-                            "com.android.shell", attributionSource.getAttributionTag(),
-                            attributionSource.getToken(), attributionSource.getNext()),
+                            Process.INVALID_PID, "com.android.shell",
+                            attributionSource.getAttributionTag(), attributionSource.getToken(),
+                            /*renouncedPermissions*/ null, attributionSource.getNext()),
                             shouldCollectAsyncNotedOp, message, shouldCollectMessage,
                             skiProxyOperation);
                 } finally {
@@ -19781,8 +19782,9 @@
                 final long identity = Binder.clearCallingIdentity();
                 try {
                     return superImpl.apply(clientId, code, new AttributionSource(shellUid,
-                            "com.android.shell", attributionSource.getAttributionTag(),
-                            attributionSource.getToken(), attributionSource.getNext()),
+                            Process.INVALID_PID, "com.android.shell",
+                            attributionSource.getAttributionTag(), attributionSource.getToken(),
+                            /*renouncedPermissions*/ null, attributionSource.getNext()),
                             startIfModeDefault, shouldCollectAsyncNotedOp, message,
                             shouldCollectMessage, skipProxyOperation, proxyAttributionFlags,
                             proxiedAttributionFlags, attributionChainId);
@@ -19806,8 +19808,9 @@
                 final long identity = Binder.clearCallingIdentity();
                 try {
                     superImpl.apply(clientId, code, new AttributionSource(shellUid,
-                            "com.android.shell", attributionSource.getAttributionTag(),
-                            attributionSource.getToken(), attributionSource.getNext()),
+                            Process.INVALID_PID, "com.android.shell",
+                            attributionSource.getAttributionTag(), attributionSource.getToken(),
+                            /*renouncedPermissions*/ null, attributionSource.getNext()),
                             skipProxyOperation);
                 } finally {
                     Binder.restoreCallingIdentity(identity);
diff --git a/services/core/java/com/android/server/am/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java
index f236a96..d09ca5c 100644
--- a/services/core/java/com/android/server/am/BatteryStatsService.java
+++ b/services/core/java/com/android/server/am/BatteryStatsService.java
@@ -30,7 +30,6 @@
 import android.annotation.EnforcePermission;
 import android.annotation.NonNull;
 import android.annotation.RequiresNoPermission;
-import android.app.AlarmManager;
 import android.app.StatsManager;
 import android.app.usage.NetworkStatsManager;
 import android.bluetooth.BluetoothActivityEnergyInfo;
@@ -414,18 +413,6 @@
             Slog.e(TAG, "Could not register INetworkManagement event observer " + e);
         }
 
-        final AlarmManager am = mContext.getSystemService(AlarmManager.class);
-        mHandler.post(() -> {
-            synchronized (mStats) {
-                mStats.setLongPlugInAlarmInterface(new AlarmInterface(am, () -> {
-                    synchronized (mStats) {
-                        if (mStats.isOnBattery()) return;
-                        mStats.maybeResetWhilePluggedInLocked();
-                    }
-                }));
-            }
-        });
-
         synchronized (mPowerStatsLock) {
             mPowerStatsInternal = LocalServices.getService(PowerStatsInternal.class);
             if (mPowerStatsInternal != null) {
@@ -2529,32 +2516,6 @@
         }
     }
 
-    final class AlarmInterface implements BatteryStatsImpl.AlarmInterface,
-            AlarmManager.OnAlarmListener {
-        private AlarmManager mAm;
-        private Runnable mOnAlarm;
-
-        AlarmInterface(AlarmManager am, Runnable onAlarm) {
-            mAm = am;
-            mOnAlarm = onAlarm;
-        }
-
-        @Override
-        public void schedule(long rtcTimeMs, long windowLengthMs) {
-            mAm.setWindow(AlarmManager.RTC, rtcTimeMs, windowLengthMs, TAG, this, mHandler);
-        }
-
-        @Override
-        public void cancel() {
-            mAm.cancel(this);
-        }
-
-        @Override
-        public void onAlarm() {
-            mOnAlarm.run();
-        }
-    }
-
     private static native int nativeWaitWakeup(ByteBuffer outBuffer);
 
     private void dumpHelp(PrintWriter pw) {
diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java
index 5bea614..fc22935 100644
--- a/services/core/java/com/android/server/appop/AppOpsService.java
+++ b/services/core/java/com/android/server/appop/AppOpsService.java
@@ -2096,7 +2096,9 @@
                         final int code = opModes.keyAt(j);
                         if (AppOpsManager.opAllowsReset(code)) {
                             int previousMode = opModes.valueAt(j);
-                            uidState.setUidMode(code, AppOpsManager.opToDefaultMode(code));
+                            int newMode = isUidOpGrantedByRole(uidState.uid, code) ? MODE_ALLOWED :
+                                    AppOpsManager.opToDefaultMode(code);
+                            uidState.setUidMode(code, newMode);
                             for (String packageName : getPackagesForUid(uidState.uid)) {
                                 callbacks = addCallbacks(callbacks, code, uidState.uid, packageName,
                                         previousMode,
@@ -2139,10 +2141,15 @@
                             deferResetOpToDpm(curOp.op, reqPackageName, reqUserId);
                             continue;
                         }
-                        if (AppOpsManager.opAllowsReset(curOp.op)
-                                && curOp.getMode() != AppOpsManager.opToDefaultMode(curOp.op)) {
+                        if (AppOpsManager.opAllowsReset(curOp.op)) {
                             int previousMode = curOp.getMode();
-                            curOp.setMode(AppOpsManager.opToDefaultMode(curOp.op));
+                            int newMode = isPackageOpGrantedByRole(packageName, uidState.uid,
+                                    curOp.op) ? MODE_ALLOWED : AppOpsManager.opToDefaultMode(
+                                    curOp.op);
+                            if (previousMode == newMode) {
+                                continue;
+                            }
+                            curOp.setMode(newMode);
                             changed = true;
                             uidChanged = true;
                             final int uid = curOp.uidState.uid;
@@ -2198,6 +2205,41 @@
         }
     }
 
+    private boolean isUidOpGrantedByRole(int uid, int code) {
+        if (!AppOpsManager.opIsUidAppOpPermission(code)) {
+            return false;
+        }
+        PackageManager packageManager = mContext.getPackageManager();
+        long token = Binder.clearCallingIdentity();
+        try {
+            // Permissions are managed by UIDs, but unfortunately a package name is required in API.
+            String packageName = ArrayUtils.firstOrNull(packageManager.getPackagesForUid(uid));
+            if (packageName == null) {
+                return false;
+            }
+            int permissionFlags = packageManager.getPermissionFlags(AppOpsManager.opToPermission(
+                    code), packageName, UserHandle.getUserHandleForUid(uid));
+            return (permissionFlags & PackageManager.FLAG_PERMISSION_GRANTED_BY_ROLE) != 0;
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+    }
+
+    private boolean isPackageOpGrantedByRole(@NonNull String packageName, int uid, int code) {
+        if (!AppOpsManager.opIsPackageAppOpPermission(code)) {
+            return false;
+        }
+        PackageManager packageManager = mContext.getPackageManager();
+        long token = Binder.clearCallingIdentity();
+        try {
+            int permissionFlags = packageManager.getPermissionFlags(AppOpsManager.opToPermission(
+                    code), packageName, UserHandle.getUserHandleForUid(uid));
+            return (permissionFlags & PackageManager.FLAG_PERMISSION_GRANTED_BY_ROLE) != 0;
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+    }
+
     private boolean shouldDeferResetOpToDpm(int op) {
         // TODO(b/174582385): avoid special-casing app-op resets by migrating app-op permission
         //  pre-grants to a role-based mechanism or another general-purpose mechanism.
diff --git a/services/core/java/com/android/server/audio/SoundDoseHelper.java b/services/core/java/com/android/server/audio/SoundDoseHelper.java
index 6a97243..3dac04c 100644
--- a/services/core/java/com/android/server/audio/SoundDoseHelper.java
+++ b/services/core/java/com/android/server/audio/SoundDoseHelper.java
@@ -574,10 +574,8 @@
 
     /*package*/ void configureSafeMedia(boolean forced, String caller) {
         int msg = MSG_CONFIGURE_SAFE_MEDIA;
-        if (forced) {
-            // unforced should not cancel forced configure messages
-            mAudioHandler.removeMessages(msg);
-        }
+
+        mAudioHandler.removeMessages(msg);
 
         long time = 0;
         if (forced) {
diff --git a/services/core/java/com/android/server/display/DeviceStateToLayoutMap.java b/services/core/java/com/android/server/display/DeviceStateToLayoutMap.java
index 63218ee..f84a58c 100644
--- a/services/core/java/com/android/server/display/DeviceStateToLayoutMap.java
+++ b/services/core/java/com/android/server/display/DeviceStateToLayoutMap.java
@@ -121,24 +121,17 @@
                 final Layout layout = createLayout(state);
                 for (com.android.server.display.config.layout.Display d: l.getDisplay()) {
                     assert layout != null;
-                    Layout.Display display = layout.createDisplayLocked(
+                    int position = getPosition(d.getPosition());
+                    layout.createDisplayLocked(
                             DisplayAddress.fromPhysicalDisplayId(d.getAddress().longValue()),
                             d.isDefaultDisplay(),
                             d.isEnabled(),
                             d.getDisplayGroup(),
                             mIdProducer,
+                            position,
+                            leadDisplayId,
                             d.getBrightnessThrottlingMapId(),
-                            leadDisplayId);
-
-                    if (FRONT_STRING.equals(d.getPosition())) {
-                        display.setPosition(POSITION_FRONT);
-                    } else if (REAR_STRING.equals(d.getPosition())) {
-                        display.setPosition(POSITION_REAR);
-                    } else {
-                        display.setPosition(POSITION_UNKNOWN);
-                    }
-                    display.setRefreshRateZoneId(d.getRefreshRateZoneId());
-                    display.setRefreshRateThermalThrottlingMapId(
+                            d.getRefreshRateZoneId(),
                             d.getRefreshRateThermalThrottlingMapId());
                 }
             }
@@ -148,6 +141,16 @@
         }
     }
 
+    private int getPosition(@NonNull String position) {
+        int positionInt = POSITION_UNKNOWN;
+        if (FRONT_STRING.equals(position)) {
+            positionInt = POSITION_FRONT;
+        } else if (REAR_STRING.equals(position)) {
+            positionInt = POSITION_REAR;
+        }
+        return positionInt;
+    }
+
     private Layout createLayout(int state) {
         if (mLayoutMap.contains(state)) {
             Slog.e(TAG, "Attempted to create a second layout for state " + state);
diff --git a/services/core/java/com/android/server/display/DisplayDeviceConfig.java b/services/core/java/com/android/server/display/DisplayDeviceConfig.java
index cdab77d..f4b3f1a 100644
--- a/services/core/java/com/android/server/display/DisplayDeviceConfig.java
+++ b/services/core/java/com/android/server/display/DisplayDeviceConfig.java
@@ -889,11 +889,12 @@
     }
 
     /**
-     * Calculate the HDR brightness for the specified SDR brightenss.
+     * Calculate the HDR brightness for the specified SDR brightenss, restricted by the
+     * maxDesiredHdrSdrRatio (the ratio between the HDR luminance and SDR luminance)
      *
      * @return the HDR brightness or BRIGHTNESS_INVALID when no mapping exists.
      */
-    public float getHdrBrightnessFromSdr(float brightness) {
+    public float getHdrBrightnessFromSdr(float brightness, float maxDesiredHdrSdrRatio) {
         if (mSdrToHdrRatioSpline == null) {
             return PowerManager.BRIGHTNESS_INVALID;
         }
@@ -904,7 +905,7 @@
             return PowerManager.BRIGHTNESS_INVALID;
         }
 
-        float ratio = mSdrToHdrRatioSpline.interpolate(nits);
+        float ratio = Math.min(mSdrToHdrRatioSpline.interpolate(nits), maxDesiredHdrSdrRatio);
         float hdrNits = nits * ratio;
         if (mNitsToBacklightSpline == null) {
             return PowerManager.BRIGHTNESS_INVALID;
diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java
index 055ca37..e12cd8c 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController.java
@@ -975,8 +975,10 @@
                 mDisplayDeviceConfig.getHighBrightnessModeData(),
                 new HighBrightnessModeController.HdrBrightnessDeviceConfig() {
                     @Override
-                    public float getHdrBrightnessFromSdr(float sdrBrightness) {
-                        return mDisplayDeviceConfig.getHdrBrightnessFromSdr(sdrBrightness);
+                    public float getHdrBrightnessFromSdr(
+                            float sdrBrightness, float maxDesiredHdrSdrRatio) {
+                        return mDisplayDeviceConfig.getHdrBrightnessFromSdr(
+                                sdrBrightness, maxDesiredHdrSdrRatio);
                     }
                 });
         mBrightnessThrottler.resetThrottlingData(
@@ -1449,8 +1451,17 @@
                 sendOnProximityNegativeWithWakelock();
             }
         } else {
+            setProximitySensorEnabled(false);
             mWaitingForNegativeProximity = false;
             mIgnoreProximityUntilChanged = false;
+
+            if (mScreenOffBecauseOfProximity) {
+                // The screen *was* off due to prox being near, but now there's no prox sensor, so
+                // let's turn the screen back on.
+                mScreenOffBecauseOfProximity = false;
+                skipRampBecauseOfProximityChangeToNegative = true;
+                sendOnProximityNegativeWithWakelock();
+            }
         }
 
         if (!mIsEnabled
@@ -1876,6 +1887,7 @@
                 ? mCdsi.getReduceBrightColorsStrength() : -1);
         mTempBrightnessEvent.setPowerFactor(mPowerRequest.screenLowPowerBrightnessFactor);
         mTempBrightnessEvent.setWasShortTermModelActive(wasShortTermModelActive);
+        mTempBrightnessEvent.setAutomaticBrightnessEnabled(mUseAutoBrightness);
         // Temporary is what we use during slider interactions. We avoid logging those so that
         // we don't spam logcat when the slider is being used.
         boolean tempToTempTransition =
@@ -1884,9 +1896,7 @@
                         == BrightnessReason.REASON_TEMPORARY;
         if ((!mTempBrightnessEvent.equalsMainData(mLastBrightnessEvent) && !tempToTempTransition)
                 || brightnessAdjustmentFlags != 0) {
-            float lastBrightness = mLastBrightnessEvent.getBrightness();
-            mTempBrightnessEvent.setInitialBrightness(lastBrightness);
-            mTempBrightnessEvent.setAutomaticBrightnessEnabled(mUseAutoBrightness);
+            mTempBrightnessEvent.setInitialBrightness(mLastBrightnessEvent.getBrightness());
             mLastBrightnessEvent.copyFrom(mTempBrightnessEvent);
             BrightnessEvent newEvent = new BrightnessEvent(mTempBrightnessEvent);
             // Adjustment flags (and user-set flag) only get added after the equality checks since
@@ -2079,8 +2089,10 @@
                 displayUniqueId, PowerManager.BRIGHTNESS_MIN, PowerManager.BRIGHTNESS_MAX, hbmData,
                 new HighBrightnessModeController.HdrBrightnessDeviceConfig() {
                     @Override
-                    public float getHdrBrightnessFromSdr(float sdrBrightness) {
-                        return mDisplayDeviceConfig.getHdrBrightnessFromSdr(sdrBrightness);
+                    public float getHdrBrightnessFromSdr(
+                            float sdrBrightness, float maxDesiredHdrSdrRatio) {
+                        return mDisplayDeviceConfig.getHdrBrightnessFromSdr(
+                                sdrBrightness, maxDesiredHdrSdrRatio);
                     }
                 },
                 () -> {
diff --git a/services/core/java/com/android/server/display/DisplayPowerController2.java b/services/core/java/com/android/server/display/DisplayPowerController2.java
index 3c937e8..fbc354e 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController2.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController2.java
@@ -809,8 +809,10 @@
                 mDisplayDeviceConfig.getHighBrightnessModeData(),
                 new HighBrightnessModeController.HdrBrightnessDeviceConfig() {
                     @Override
-                    public float getHdrBrightnessFromSdr(float sdrBrightness) {
-                        return mDisplayDeviceConfig.getHdrBrightnessFromSdr(sdrBrightness);
+                    public float getHdrBrightnessFromSdr(
+                            float sdrBrightness, float maxDesiredHdrSdrRatio) {
+                        return mDisplayDeviceConfig.getHdrBrightnessFromSdr(
+                                sdrBrightness, maxDesiredHdrSdrRatio);
                     }
                 });
         mBrightnessThrottler.resetThrottlingData(
@@ -1595,6 +1597,7 @@
         mTempBrightnessEvent.setWasShortTermModelActive(wasShortTermModelActive);
         mTempBrightnessEvent.setDisplayBrightnessStrategyName(displayBrightnessState
                 .getDisplayBrightnessStrategyName());
+        mTempBrightnessEvent.setAutomaticBrightnessEnabled(mUseAutoBrightness);
         // Temporary is what we use during slider interactions. We avoid logging those so that
         // we don't spam logcat when the slider is being used.
         boolean tempToTempTransition =
@@ -1603,9 +1606,7 @@
                         == BrightnessReason.REASON_TEMPORARY;
         if ((!mTempBrightnessEvent.equalsMainData(mLastBrightnessEvent) && !tempToTempTransition)
                 || brightnessAdjustmentFlags != 0) {
-            float lastBrightness = mLastBrightnessEvent.getBrightness();
-            mTempBrightnessEvent.setInitialBrightness(lastBrightness);
-            mTempBrightnessEvent.setAutomaticBrightnessEnabled(mUseAutoBrightness);
+            mTempBrightnessEvent.setInitialBrightness(mLastBrightnessEvent.getBrightness());
             mLastBrightnessEvent.copyFrom(mTempBrightnessEvent);
             BrightnessEvent newEvent = new BrightnessEvent(mTempBrightnessEvent);
             // Adjustment flags (and user-set flag) only get added after the equality checks since
@@ -1796,8 +1797,10 @@
                 displayUniqueId, PowerManager.BRIGHTNESS_MIN, PowerManager.BRIGHTNESS_MAX, hbmData,
                 new HighBrightnessModeController.HdrBrightnessDeviceConfig() {
                     @Override
-                    public float getHdrBrightnessFromSdr(float sdrBrightness) {
-                        return mDisplayDeviceConfig.getHdrBrightnessFromSdr(sdrBrightness);
+                    public float getHdrBrightnessFromSdr(
+                            float sdrBrightness, float maxDesiredHdrSdrRatio) {
+                        return mDisplayDeviceConfig.getHdrBrightnessFromSdr(
+                                sdrBrightness, maxDesiredHdrSdrRatio);
                     }
                 },
                 () -> {
diff --git a/services/core/java/com/android/server/display/DisplayPowerProximityStateController.java b/services/core/java/com/android/server/display/DisplayPowerProximityStateController.java
index 5bb3e6b..c074786 100644
--- a/services/core/java/com/android/server/display/DisplayPowerProximityStateController.java
+++ b/services/core/java/com/android/server/display/DisplayPowerProximityStateController.java
@@ -262,8 +262,17 @@
                 sendOnProximityNegativeWithWakelock();
             }
         } else {
+            setProximitySensorEnabled(false);
             mWaitingForNegativeProximity = false;
             mIgnoreProximityUntilChanged = false;
+
+            if (mScreenOffBecauseOfProximity) {
+                // The screen *was* off due to prox being near, but now there's no prox sensor, so
+                // let's turn the screen back on.
+                mScreenOffBecauseOfProximity = false;
+                mSkipRampBecauseOfProximityChangeToNegative = true;
+                sendOnProximityNegativeWithWakelock();
+            }
         }
     }
 
diff --git a/services/core/java/com/android/server/display/HighBrightnessModeController.java b/services/core/java/com/android/server/display/HighBrightnessModeController.java
index 2c843a4..ca208ac 100644
--- a/services/core/java/com/android/server/display/HighBrightnessModeController.java
+++ b/services/core/java/com/android/server/display/HighBrightnessModeController.java
@@ -61,8 +61,12 @@
     @VisibleForTesting
     static final float HBM_TRANSITION_POINT_INVALID = Float.POSITIVE_INFINITY;
 
+    private static final float DEFAULT_MAX_DESIRED_HDR_SDR_RATIO = 1.0f;
+
     public interface HdrBrightnessDeviceConfig {
-        float getHdrBrightnessFromSdr(float sdrBrightness);
+        // maxDesiredHdrSdrRatio will restrict the HDR brightness if the ratio is less than
+        // Float.POSITIVE_INFINITY
+        float getHdrBrightnessFromSdr(float sdrBrightness, float maxDesiredHdrSdrRatio);
     }
 
     private final float mBrightnessMin;
@@ -96,6 +100,9 @@
 
     private int mHbmMode = BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF;
     private boolean mIsHdrLayerPresent = false;
+
+    // mMaxDesiredHdrSdrRatio should only be applied when there is a valid backlight->nits mapping
+    private float mMaxDesiredHdrSdrRatio = DEFAULT_MAX_DESIRED_HDR_SDR_RATIO;
     private boolean mIsThermalStatusWithinLimit = true;
     private boolean mIsBlockedByLowPowerMode = false;
     private int mWidth;
@@ -177,7 +184,8 @@
 
     float getHdrBrightnessValue() {
         if (mHdrBrightnessCfg != null) {
-            float hdrBrightness = mHdrBrightnessCfg.getHdrBrightnessFromSdr(mBrightness);
+            float hdrBrightness = mHdrBrightnessCfg.getHdrBrightnessFromSdr(
+                    mBrightness, mMaxDesiredHdrSdrRatio);
             if (hdrBrightness != PowerManager.BRIGHTNESS_INVALID) {
                 return hdrBrightness;
             }
@@ -457,6 +465,7 @@
                     + ", isLuxHigh: " + mIsInAllowedAmbientRange
                     + ", isHBMCurrentlyAllowed: " + isCurrentlyAllowed()
                     + ", isHdrLayerPresent: " + mIsHdrLayerPresent
+                    + ", mMaxDesiredHdrSdrRatio: " + mMaxDesiredHdrSdrRatio
                     + ", isAutoBrightnessEnabled: " +  mIsAutoBrightnessEnabled
                     + ", mIsTimeAvailable: " + mIsTimeAvailable
                     + ", mIsInAllowedAmbientRange: " + mIsInAllowedAmbientRange
@@ -600,11 +609,25 @@
     class HdrListener extends SurfaceControlHdrLayerInfoListener {
         @Override
         public void onHdrInfoChanged(IBinder displayToken, int numberOfHdrLayers,
-                int maxW, int maxH, int flags) {
+                int maxW, int maxH, int flags, float maxDesiredHdrSdrRatio) {
             mHandler.post(() -> {
                 mIsHdrLayerPresent = numberOfHdrLayers > 0
                         && (float) (maxW * maxH) >= ((float) (mWidth * mHeight)
                                    * mHbmData.minimumHdrPercentOfScreen);
+
+                final float candidateDesiredHdrSdrRatio =
+                        mIsHdrLayerPresent && mHdrBrightnessCfg != null
+                                ? maxDesiredHdrSdrRatio
+                                : DEFAULT_MAX_DESIRED_HDR_SDR_RATIO;
+
+                if (candidateDesiredHdrSdrRatio >= 1.0f) {
+                    mMaxDesiredHdrSdrRatio = candidateDesiredHdrSdrRatio;
+                } else {
+                    Slog.w(TAG, "Ignoring invalid desired HDR/SDR Ratio: "
+                            + candidateDesiredHdrSdrRatio);
+                    mMaxDesiredHdrSdrRatio = DEFAULT_MAX_DESIRED_HDR_SDR_RATIO;
+                }
+
                 // Calling the brightness update so that we can recalculate
                 // brightness with HDR in mind.
                 onBrightnessChanged(mBrightness, mUnthrottledBrightness, mThrottlingReason);
diff --git a/services/core/java/com/android/server/display/LogicalDisplayMapper.java b/services/core/java/com/android/server/display/LogicalDisplayMapper.java
index e290b7a..250ba43 100644
--- a/services/core/java/com/android/server/display/LogicalDisplayMapper.java
+++ b/services/core/java/com/android/server/display/LogicalDisplayMapper.java
@@ -18,8 +18,6 @@
 
 import static android.view.Display.DEFAULT_DISPLAY;
 
-import static com.android.server.display.layout.Layout.NO_LEAD_DISPLAY;
-
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.Context;
@@ -646,10 +644,7 @@
                 if ((nextDeviceInfo.flags
                         & DisplayDeviceInfo.FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY) != 0
                         && !nextDeviceInfo.address.equals(deviceInfo.address)) {
-                    layout.createDisplayLocked(nextDeviceInfo.address,
-                            /* isDefault= */ true, /* isEnabled= */ true,
-                            Layout.DEFAULT_DISPLAY_GROUP_NAME, mIdProducer,
-                            /* brightnessThrottlingMapId= */ null, DEFAULT_DISPLAY);
+                    layout.createDefaultDisplayLocked(nextDeviceInfo.address, mIdProducer);
                     applyLayoutLocked();
                     return;
                 }
@@ -1110,9 +1105,7 @@
             return;
         }
         final DisplayDeviceInfo info = device.getDisplayDeviceInfoLocked();
-        layout.createDisplayLocked(info.address, /* isDefault= */ true, /* isEnabled= */ true,
-                Layout.DEFAULT_DISPLAY_GROUP_NAME, mIdProducer,
-                /* brightnessThrottlingMapId= */ null, NO_LEAD_DISPLAY);
+        layout.createDefaultDisplayLocked(info.address, mIdProducer);
     }
 
     private int assignLayerStackLocked(int displayId) {
diff --git a/services/core/java/com/android/server/display/brightness/BrightnessEvent.java b/services/core/java/com/android/server/display/brightness/BrightnessEvent.java
index aff80de..a3bca9a 100644
--- a/services/core/java/com/android/server/display/brightness/BrightnessEvent.java
+++ b/services/core/java/com/android/server/display/brightness/BrightnessEvent.java
@@ -142,8 +142,6 @@
                 && Float.floatToRawIntBits(mLux) == Float.floatToRawIntBits(that.mLux)
                 && Float.floatToRawIntBits(mPreThresholdLux)
                 == Float.floatToRawIntBits(that.mPreThresholdLux)
-                && Float.floatToRawIntBits(mInitialBrightness)
-                == Float.floatToRawIntBits(that.mInitialBrightness)
                 && Float.floatToRawIntBits(mBrightness)
                 == Float.floatToRawIntBits(that.mBrightness)
                 && Float.floatToRawIntBits(mRecommendedBrightness)
diff --git a/services/core/java/com/android/server/display/layout/Layout.java b/services/core/java/com/android/server/display/layout/Layout.java
index 6a4d23b..f86ee24 100644
--- a/services/core/java/com/android/server/display/layout/Layout.java
+++ b/services/core/java/com/android/server/display/layout/Layout.java
@@ -39,7 +39,6 @@
     public static final String DEFAULT_DISPLAY_GROUP_NAME = "";
 
     private static final String TAG = "Layout";
-    private static int sNextNonDefaultDisplayId = DEFAULT_DISPLAY + 1;
 
     // Lead display Id is set to this if this is not a follower display, and therefore
     // has no lead.
@@ -47,13 +46,6 @@
 
     private final List<Display> mDisplays = new ArrayList<>(2);
 
-    /**
-     *  @return The default display ID, or a new unique one to use.
-     */
-    public static int assignDisplayIdLocked(boolean isDefault) {
-        return isDefault ? DEFAULT_DISPLAY : sNextNonDefaultDisplayId++;
-    }
-
     @Override
     public String toString() {
         return mDisplays.toString();
@@ -76,25 +68,17 @@
     }
 
     /**
-     * Creates a simple 1:1 LogicalDisplay mapping for the specified DisplayDevice.
+     * Creates the default 1:1 LogicalDisplay mapping for the specified DisplayDevice.
      *
      * @param address Address of the device.
-     * @param isDefault Indicates if the device is meant to be the default display.
-     * @param isEnabled Indicates if this display is usable and can be switched on
-     * @param displayGroupName Name of the display group to which the display is assigned.
      * @param idProducer Produces the logical display id.
-     * @param brightnessThrottlingMapId Name of which throttling policy should be used.
-     * @param leadDisplayId Display that this one follows (-1 if none).
-     * @exception IllegalArgumentException When a default display owns a display group other than
-     *            DEFAULT_DISPLAY_GROUP.
-     * @return The new Display.
      */
-    public Display createDisplayLocked(
-            @NonNull DisplayAddress address, boolean isDefault, boolean isEnabled,
-            String displayGroupName, DisplayIdProducer idProducer, String brightnessThrottlingMapId,
-            int leadDisplayId) {
-        return createDisplayLocked(address, isDefault, isEnabled, displayGroupName, idProducer,
-                brightnessThrottlingMapId, POSITION_UNKNOWN, leadDisplayId);
+    public void createDefaultDisplayLocked(@NonNull DisplayAddress address,
+            DisplayIdProducer idProducer) {
+        createDisplayLocked(address, /* isDefault= */ true, /* isEnabled= */ true,
+                DEFAULT_DISPLAY_GROUP_NAME, idProducer, POSITION_UNKNOWN,
+                NO_LEAD_DISPLAY, /* brightnessThrottlingMapId= */ null,
+                /* refreshRateZoneId= */ null, /* refreshRateThermalThrottlingMapId= */ null);
     }
 
     /**
@@ -105,26 +89,30 @@
      * @param isEnabled Indicates if this display is usable and can be switched on
      * @param displayGroupName Name of the display group to which the display is assigned.
      * @param idProducer Produces the logical display id.
-     * @param brightnessThrottlingMapId Name of which throttling policy should be used.
      * @param position Indicates the position this display is facing in this layout.
      * @param leadDisplayId Display that this one follows (-1 if none).
+     * @param brightnessThrottlingMapId Name of which brightness throttling policy should be used.
+     * @param refreshRateZoneId Layout limited refresh rate zone name.
+     * @param refreshRateThermalThrottlingMapId Name of which refresh rate throttling
+     *                                          policy should be used.
+
      * @exception IllegalArgumentException When a default display owns a display group other than
      *            DEFAULT_DISPLAY_GROUP.
-     * @return The new Display.
      */
-    public Display createDisplayLocked(
+    public void createDisplayLocked(
             @NonNull DisplayAddress address, boolean isDefault, boolean isEnabled,
-            String displayGroupName, DisplayIdProducer idProducer, String brightnessThrottlingMapId,
-            int position, int leadDisplayId) {
+            String displayGroupName, DisplayIdProducer idProducer, int position, int leadDisplayId,
+            String brightnessThrottlingMapId, @Nullable String refreshRateZoneId,
+            @Nullable String refreshRateThermalThrottlingMapId) {
         if (contains(address)) {
             Slog.w(TAG, "Attempting to add second definition for display-device: " + address);
-            return null;
+            return;
         }
 
         // See if we're dealing with the "default" display
         if (isDefault && getById(DEFAULT_DISPLAY) != null) {
             Slog.w(TAG, "Ignoring attempt to add a second default display: " + address);
-            return null;
+            return;
         }
 
         // Assign a logical display ID and create the new display.
@@ -138,11 +126,13 @@
             throw new IllegalArgumentException("Default display should own DEFAULT_DISPLAY_GROUP");
         }
         final int logicalDisplayId = idProducer.getId(isDefault);
+        leadDisplayId = isDefault ? NO_LEAD_DISPLAY : leadDisplayId;
+
         final Display display = new Display(address, logicalDisplayId, isEnabled, displayGroupName,
-                brightnessThrottlingMapId, position, leadDisplayId);
+                brightnessThrottlingMapId, position, leadDisplayId, refreshRateZoneId,
+                refreshRateThermalThrottlingMapId);
 
         mDisplays.add(display);
-        return display;
     }
 
     /**
@@ -242,7 +232,7 @@
         // {@link DeviceStateToLayoutMap.POSITION_FRONT} or
         // {@link DeviceStateToLayoutMap.POSITION_REAR}.
         // {@link DeviceStateToLayoutMap.POSITION_UNKNOWN} is unspecified.
-        private int mPosition;
+        private final int mPosition;
 
         // The ID of the brightness throttling map that should be used. This can change e.g. in
         // concurrent displays mode in which a stricter brightness throttling policy might need to
@@ -251,31 +241,28 @@
         private final String mBrightnessThrottlingMapId;
 
         // The ID of the lead display that this display will follow in a layout. -1 means no lead.
-        private int mLeadDisplayId;
+        private final int mLeadDisplayId;
 
         // Refresh rate zone id for specific layout
         @Nullable
-        private String mRefreshRateZoneId;
+        private final String mRefreshRateZoneId;
 
         @Nullable
-        private String mRefreshRateThermalThrottlingMapId;
+        private final String mRefreshRateThermalThrottlingMapId;
 
-        Display(@NonNull DisplayAddress address, int logicalDisplayId, boolean isEnabled,
+        private Display(@NonNull DisplayAddress address, int logicalDisplayId, boolean isEnabled,
                 @NonNull String displayGroupName, String brightnessThrottlingMapId, int position,
-                int leadDisplayId) {
+                int leadDisplayId, @Nullable String refreshRateZoneId,
+                @Nullable String refreshRateThermalThrottlingMapId) {
             mAddress = address;
             mLogicalDisplayId = logicalDisplayId;
             mIsEnabled = isEnabled;
             mDisplayGroupName = displayGroupName;
             mPosition = position;
             mBrightnessThrottlingMapId = brightnessThrottlingMapId;
-
-            if (leadDisplayId == mLogicalDisplayId) {
-                mLeadDisplayId = NO_LEAD_DISPLAY;
-            } else {
-                mLeadDisplayId = leadDisplayId;
-            }
-
+            mRefreshRateZoneId = refreshRateZoneId;
+            mRefreshRateThermalThrottlingMapId = refreshRateThermalThrottlingMapId;
+            mLeadDisplayId = leadDisplayId;
         }
 
         @Override
@@ -345,24 +332,12 @@
             return mDisplayGroupName;
         }
 
-        public void setRefreshRateZoneId(@Nullable String refreshRateZoneId) {
-            mRefreshRateZoneId = refreshRateZoneId;
-        }
-
         @Nullable
         public String getRefreshRateZoneId() {
             return mRefreshRateZoneId;
         }
 
         /**
-         * Sets the position that this display is facing.
-         * @param position the display is facing.
-         */
-        public void setPosition(int position) {
-            mPosition = position;
-        }
-
-        /**
          * @return The ID of the brightness throttling map that this display should use.
          */
         public String getBrightnessThrottlingMapId() {
@@ -370,7 +345,6 @@
         }
 
         /**
-         *
          * @return the position that this display is facing.
          */
         public int getPosition() {
@@ -378,28 +352,12 @@
         }
 
         /**
-         * Set the display that this display should follow certain properties of, for example,
-         * brightness
-         * @param displayId of the lead display.
-         */
-        public void setLeadDisplay(int displayId) {
-            if (displayId != mLogicalDisplayId) {
-                mLeadDisplayId = displayId;
-            }
-        }
-
-        /**
-         *
          * @return logical displayId of the display that this one follows.
          */
         public int getLeadDisplayId() {
             return mLeadDisplayId;
         }
 
-        public void setRefreshRateThermalThrottlingMapId(String refreshRateThermalThrottlingMapId) {
-            mRefreshRateThermalThrottlingMapId = refreshRateThermalThrottlingMapId;
-        }
-
         public String getRefreshRateThermalThrottlingMapId() {
             return mRefreshRateThermalThrottlingMapId;
         }
diff --git a/services/core/java/com/android/server/display/mode/DisplayModeDirector.java b/services/core/java/com/android/server/display/mode/DisplayModeDirector.java
index db6944d0..3864200 100644
--- a/services/core/java/com/android/server/display/mode/DisplayModeDirector.java
+++ b/services/core/java/com/android/server/display/mode/DisplayModeDirector.java
@@ -2088,7 +2088,7 @@
         }
 
         @VisibleForTesting
-        void onRefreshRateSettingChangedLocked(float min, float max) {
+        public void onRefreshRateSettingChangedLocked(float min, float max) {
             boolean changeable = (max - min > 1f && max > 60f);
             if (mRefreshRateChangeable != changeable) {
                 mRefreshRateChangeable = changeable;
diff --git a/services/core/java/com/android/server/locales/AppLocaleChangedAtomRecord.java b/services/core/java/com/android/server/locales/AppLocaleChangedAtomRecord.java
index 282e3c1..2be2ef8 100644
--- a/services/core/java/com/android/server/locales/AppLocaleChangedAtomRecord.java
+++ b/services/core/java/com/android/server/locales/AppLocaleChangedAtomRecord.java
@@ -30,7 +30,8 @@
     String mPrevLocales = "";
     int mStatus = FrameworkStatsLog
             .APPLICATION_LOCALES_CHANGED__STATUS__STATUS_UNSPECIFIED;
-
+    int mCaller = FrameworkStatsLog
+            .APPLICATION_LOCALES_CHANGED__CALLER__CALLER_UNKNOWN;
     AppLocaleChangedAtomRecord(int callingUid) {
         this.mCallingUid = callingUid;
     }
@@ -50,4 +51,8 @@
     void setStatus(int status) {
         this.mStatus = status;
     }
+
+    void setCaller(int caller) {
+        this.mCaller = caller;
+    }
 }
diff --git a/services/core/java/com/android/server/locales/AppUpdateTracker.java b/services/core/java/com/android/server/locales/AppUpdateTracker.java
deleted file mode 100644
index 3474f1e..0000000
--- a/services/core/java/com/android/server/locales/AppUpdateTracker.java
+++ /dev/null
@@ -1,180 +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.locales;
-
-import android.app.LocaleConfig;
-import android.content.Context;
-import android.content.SharedPreferences;
-import android.content.pm.PackageManager;
-import android.os.LocaleList;
-import android.os.RemoteException;
-import android.os.UserHandle;
-import android.util.ArraySet;
-import android.util.FeatureFlagUtils;
-import android.util.Log;
-import android.util.Slog;
-
-import com.android.internal.annotations.VisibleForTesting;
-
-import java.util.HashSet;
-import java.util.Locale;
-import java.util.Set;
-
-/**
- * Track when a app is being updated.
- */
-public class AppUpdateTracker {
-    private static final String TAG = "AppUpdateTracker";
-
-    private final Context mContext;
-    private final LocaleManagerService mLocaleManagerService;
-    private final LocaleManagerBackupHelper mBackupHelper;
-
-    AppUpdateTracker(Context context, LocaleManagerService localeManagerService,
-            LocaleManagerBackupHelper backupHelper) {
-        mContext = context;
-        mLocaleManagerService = localeManagerService;
-        mBackupHelper = backupHelper;
-    }
-
-    /**
-     * <p><b>Note:</b> This is invoked by service's common monitor
-     * {@link LocaleManagerServicePackageMonitor#onPackageUpdateFinished} when a package is upgraded
-     * on device.
-     */
-    public void onPackageUpdateFinished(String packageName, int uid) {
-        Log.d(TAG, "onPackageUpdateFinished " + packageName);
-        int userId = UserHandle.getUserId(uid);
-        cleanApplicationLocalesIfNeeded(packageName, userId);
-    }
-
-    /**
-     * When the user has set per-app locales for a specific application from a delegate selector,
-     * and then the LocaleConfig of that application is removed in the upgraded version, the per-app
-     * locales needs to be reset to system default locales to avoid the user being unable to change
-     * system locales setting.
-     */
-    private void cleanApplicationLocalesIfNeeded(String packageName, int userId) {
-        Set<String> packageNames = new ArraySet<>();
-        SharedPreferences delegateAppLocalePackages = mBackupHelper.getPersistedInfo();
-        if (delegateAppLocalePackages != null) {
-            packageNames = delegateAppLocalePackages.getStringSet(Integer.toString(userId),
-                    new ArraySet<>());
-        }
-
-        try {
-            LocaleList appLocales = mLocaleManagerService.getApplicationLocales(packageName,
-                    userId);
-            if (appLocales.isEmpty() || isLocalesExistedInLocaleConfig(appLocales, packageName,
-                    userId) || !packageNames.contains(packageName)) {
-                return;
-            }
-        } catch (RemoteException | IllegalArgumentException e) {
-            Slog.e(TAG, "Exception when getting locales for " + packageName, e);
-            return;
-        }
-
-        Slog.d(TAG, "Clear app locales for " + packageName);
-        try {
-            mLocaleManagerService.setApplicationLocales(packageName, userId,
-                    LocaleList.forLanguageTags(""), false);
-        } catch (RemoteException | IllegalArgumentException e) {
-            Slog.e(TAG, "Could not clear locales for " + packageName, e);
-        }
-    }
-
-    /**
-     * Check whether the LocaleConfig is existed and the per-app locales is presented in the
-     * LocaleConfig file after the application is upgraded.
-     */
-    private boolean isLocalesExistedInLocaleConfig(LocaleList appLocales, String packageName,
-            int userId) {
-        LocaleList packageLocalesList = getPackageLocales(packageName, userId);
-        HashSet<Locale> packageLocales = new HashSet<>();
-
-        if (isSettingsAppLocalesOptIn()) {
-            if (packageLocalesList == null || packageLocalesList.isEmpty()) {
-                // The app locale feature is not enabled by the app
-                Slog.d(TAG, "opt-in: the app locale feature is not enabled");
-                return false;
-            }
-        } else {
-            if (packageLocalesList != null && packageLocalesList.isEmpty()) {
-                // The app locale feature is not enabled by the app
-                Slog.d(TAG, "opt-out: the app locale feature is not enabled");
-                return false;
-            }
-        }
-
-        if (packageLocalesList != null && !packageLocalesList.isEmpty()) {
-            // The app has added the supported locales into the LocaleConfig
-            for (int i = 0; i < packageLocalesList.size(); i++) {
-                packageLocales.add(packageLocalesList.get(i));
-            }
-            if (!matchesLocale(packageLocales, appLocales)) {
-                // The set app locales do not match with the list of app supported locales
-                Slog.d(TAG, "App locales: " + appLocales.toLanguageTags()
-                        + " are not existed in the supported locale list");
-                return false;
-            }
-        }
-
-        return true;
-    }
-
-    /**
-     * Get locales from LocaleConfig.
-     */
-    @VisibleForTesting
-    public LocaleList getPackageLocales(String packageName, int userId) {
-        try {
-            LocaleConfig localeConfig = new LocaleConfig(
-                    mContext.createPackageContextAsUser(packageName, 0, UserHandle.of(userId)));
-            if (localeConfig.getStatus() == LocaleConfig.STATUS_SUCCESS) {
-                return localeConfig.getSupportedLocales();
-            }
-        } catch (PackageManager.NameNotFoundException e) {
-            Slog.e(TAG, "Can not found the package name : " + packageName + " / " + e);
-        }
-        return null;
-    }
-
-    /**
-     * Check whether the feature to show per-app locales list in Settings is enabled.
-     */
-    @VisibleForTesting
-    public boolean isSettingsAppLocalesOptIn() {
-        return FeatureFlagUtils.isEnabled(mContext,
-                FeatureFlagUtils.SETTINGS_APP_LOCALE_OPT_IN_ENABLED);
-    }
-
-    private boolean matchesLocale(HashSet<Locale> supported, LocaleList appLocales) {
-        if (supported.size() <= 0 || appLocales.size() <= 0) {
-            return true;
-        }
-
-        for (int i = 0; i < appLocales.size(); i++) {
-            final Locale appLocale = appLocales.get(i);
-            if (supported.stream().anyMatch(
-                    locale -> LocaleList.matchesLanguageAndScript(locale, appLocale))) {
-                return true;
-            }
-        }
-
-        return false;
-    }
-}
diff --git a/services/core/java/com/android/server/locales/LocaleManagerBackupHelper.java b/services/core/java/com/android/server/locales/LocaleManagerBackupHelper.java
index 898c6f1..6cd2ed4 100644
--- a/services/core/java/com/android/server/locales/LocaleManagerBackupHelper.java
+++ b/services/core/java/com/android/server/locales/LocaleManagerBackupHelper.java
@@ -22,6 +22,7 @@
 
 import android.annotation.NonNull;
 import android.annotation.UserIdInt;
+import android.app.LocaleConfig;
 import android.app.backup.BackupManager;
 import android.content.BroadcastReceiver;
 import android.content.Context;
@@ -298,6 +299,16 @@
 
     /**
      * <p><b>Note:</b> This is invoked by service's common monitor
+     * {@link LocaleManagerServicePackageMonitor#onPackageUpdateFinished} when a package is upgraded
+     * on device.
+     */
+    void onPackageUpdateFinished(String packageName, int uid) {
+        int userId = UserHandle.getUserId(uid);
+        cleanApplicationLocalesIfNeeded(packageName, userId);
+    }
+
+    /**
+     * <p><b>Note:</b> This is invoked by service's common monitor
      * {@link LocaleManagerServicePackageMonitor#onPackageDataCleared} when a package's data
      * is cleared.
      */
@@ -608,4 +619,52 @@
             Slog.e(TAG, "failed to commit locale setter info");
         }
     }
+
+    boolean areLocalesSetFromDelegate(@UserIdInt int userId, String packageName) {
+        if (mDelegateAppLocalePackages == null) {
+            Slog.w(TAG, "Failed to persist data into the shared preference!");
+            return false;
+        }
+
+        String user = Integer.toString(userId);
+        Set<String> packageNames = new ArraySet<>(
+                mDelegateAppLocalePackages.getStringSet(user, new ArraySet<>()));
+
+        return packageNames.contains(packageName);
+    }
+
+    /**
+     * When the user has set per-app locales for a specific application from a delegate selector,
+     * and then the LocaleConfig of that application is removed in the upgraded version, the per-app
+     * locales need to be removed or reset to system default locales to avoid the user being unable
+     * to change system locales setting.
+     */
+    private void cleanApplicationLocalesIfNeeded(String packageName, int userId) {
+        if (mDelegateAppLocalePackages == null) {
+            Slog.w(TAG, "Failed to persist data into the shared preference!");
+            return;
+        }
+
+        String user = Integer.toString(userId);
+        Set<String> packageNames = new ArraySet<>(
+                mDelegateAppLocalePackages.getStringSet(user, new ArraySet<>()));
+        try {
+            LocaleList appLocales = mLocaleManagerService.getApplicationLocales(packageName,
+                    userId);
+            if (appLocales.isEmpty() || !packageNames.contains(packageName)) {
+                return;
+            }
+        } catch (RemoteException | IllegalArgumentException e) {
+            Slog.e(TAG, "Exception when getting locales for " + packageName, e);
+            return;
+        }
+
+        try {
+            LocaleConfig localeConfig = new LocaleConfig(
+                    mContext.createPackageContextAsUser(packageName, 0, UserHandle.of(userId)));
+            mLocaleManagerService.removeUnsupportedAppLocales(packageName, userId, localeConfig);
+        } catch (PackageManager.NameNotFoundException e) {
+            Slog.e(TAG, "Can not found the package name : " + packageName + " / " + e);
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/locales/LocaleManagerService.java b/services/core/java/com/android/server/locales/LocaleManagerService.java
index e5f5897..e3a555b 100644
--- a/services/core/java/com/android/server/locales/LocaleManagerService.java
+++ b/services/core/java/com/android/server/locales/LocaleManagerService.java
@@ -129,11 +129,8 @@
 
         mBackupHelper = new LocaleManagerBackupHelper(this,
                 mPackageManager, broadcastHandlerThread);
-        AppUpdateTracker appUpdateTracker =
-                new AppUpdateTracker(mContext, this, mBackupHelper);
-
         mPackageMonitor = new LocaleManagerServicePackageMonitor(mBackupHelper,
-                systemAppUpdateTracker, appUpdateTracker, this);
+                systemAppUpdateTracker, this);
         mPackageMonitor.register(context, broadcastHandlerThread.getLooper(),
                 UserHandle.ALL,
                 true);
@@ -554,7 +551,8 @@
                 atomRecordForMetrics.mTargetUid,
                 atomRecordForMetrics.mNewLocales,
                 atomRecordForMetrics.mPrevLocales,
-                atomRecordForMetrics.mStatus);
+                atomRecordForMetrics.mStatus,
+                atomRecordForMetrics.mCaller);
     }
 
     /**
@@ -598,7 +596,7 @@
     }
 
     private void setOverrideLocaleConfigUnchecked(@NonNull String appPackageName,
-            @UserIdInt int userId, @Nullable LocaleConfig overridelocaleConfig,
+            @UserIdInt int userId, @Nullable LocaleConfig overrideLocaleConfig,
             @NonNull AppSupportedLocalesChangedAtomRecord atomRecord) {
         synchronized (mWriteLock) {
             if (DEBUG) {
@@ -606,26 +604,35 @@
                         "set the override LocaleConfig for package " + appPackageName + " and user "
                                 + userId);
             }
+            LocaleConfig resLocaleConfig = null;
+            try {
+                resLocaleConfig = LocaleConfig.fromContextIgnoringOverride(
+                        mContext.createPackageContext(appPackageName, 0));
+            } catch (PackageManager.NameNotFoundException e) {
+                Slog.e(TAG, "Unknown package name " + appPackageName);
+                return;
+            }
             final File file = getXmlFileNameForUser(appPackageName, userId);
 
-            if (overridelocaleConfig == null) {
+            if (overrideLocaleConfig == null) {
                 if (file.exists()) {
                     Slog.d(TAG, "remove the override LocaleConfig");
                     file.delete();
                 }
+                removeUnsupportedAppLocales(appPackageName, userId, resLocaleConfig);
                 atomRecord.setOverrideRemoved(true);
                 atomRecord.setStatus(FrameworkStatsLog
                         .APP_SUPPORTED_LOCALES_CHANGED__STATUS__SUCCESS);
                 return;
             } else {
-                if (overridelocaleConfig.isSameLocaleConfig(
+                if (overrideLocaleConfig.isSameLocaleConfig(
                         getOverrideLocaleConfig(appPackageName, userId))) {
                     Slog.d(TAG, "the same override, ignore it");
                     atomRecord.setSameAsPrevConfig(true);
                     return;
                 }
 
-                LocaleList localeList = overridelocaleConfig.getSupportedLocales();
+                LocaleList localeList = overrideLocaleConfig.getSupportedLocales();
                 // Normally the LocaleList object should not be null. However we reassign it as the
                 // empty list in case it happens.
                 if (localeList == null) {
@@ -654,16 +661,10 @@
                 }
                 atomicFile.finishWrite(stream);
                 // Clear per-app locales if they are not in the override LocaleConfig.
-                removeUnsupportedAppLocales(appPackageName, userId, overridelocaleConfig);
-                try {
-                    Context appContext = mContext.createPackageContext(appPackageName, 0);
-                    if (overridelocaleConfig.isSameLocaleConfig(
-                            LocaleConfig.fromContextIgnoringOverride(appContext))) {
-                        Slog.d(TAG, "setOverrideLocaleConfig, same as the app's LocaleConfig");
-                        atomRecord.setSameAsResConfig(true);
-                    }
-                } catch (PackageManager.NameNotFoundException e) {
-                    Slog.e(TAG, "Unknown package name " + appPackageName);
+                removeUnsupportedAppLocales(appPackageName, userId, overrideLocaleConfig);
+                if (overrideLocaleConfig.isSameLocaleConfig(resLocaleConfig)) {
+                    Slog.d(TAG, "setOverrideLocaleConfig, same as the app's LocaleConfig");
+                    atomRecord.setSameAsResConfig(true);
                 }
                 atomRecord.setStatus(FrameworkStatsLog
                         .APP_SUPPORTED_LOCALES_CHANGED__STATUS__SUCCESS);
@@ -675,23 +676,29 @@
     }
 
     /**
-     * Checks if the per-app locales are in the new override LocaleConfig. Per-app locales
-     * missing from the new LocaleConfig will be removed.
+     * Checks if the per-app locales are in the LocaleConfig. Per-app locales missing from the
+     * LocaleConfig will be removed.
      */
-    private void removeUnsupportedAppLocales(String appPackageName, int userId,
+    void removeUnsupportedAppLocales(String appPackageName, int userId,
             LocaleConfig localeConfig) {
         LocaleList appLocales = getApplicationLocalesUnchecked(appPackageName, userId);
-        // Remove the app locale from the locale list if it doesn't exist in the override
-        // LocaleConfig.
+        // Remove the per-app locales from the locale list if they don't exist in the LocaleConfig.
         boolean resetAppLocales = false;
         List<Locale> newAppLocales = new ArrayList<Locale>();
-        for (int i = 0; i < appLocales.size(); i++) {
-            if (!localeConfig.containsLocale(appLocales.get(i))) {
-                Slog.i(TAG, "reset the app locales");
-                resetAppLocales = true;
-                continue;
+
+        if (localeConfig == null) {
+            //Reset the app locales to the system default
+            Slog.i(TAG, "There is no LocaleConfig, reset app locales");
+            resetAppLocales = true;
+        } else {
+            for (int i = 0; i < appLocales.size(); i++) {
+                if (!localeConfig.containsLocale(appLocales.get(i))) {
+                    Slog.i(TAG, "Missing from the LocaleConfig, reset app locales");
+                    resetAppLocales = true;
+                    continue;
+                }
+                newAppLocales.add(appLocales.get(i));
             }
-            newAppLocales.add(appLocales.get(i));
         }
 
         if (resetAppLocales) {
@@ -699,7 +706,8 @@
             Locale[] locales = new Locale[newAppLocales.size()];
             try {
                 setApplicationLocales(appPackageName, userId,
-                        new LocaleList(newAppLocales.toArray(locales)), false);
+                        new LocaleList(newAppLocales.toArray(locales)),
+                        mBackupHelper.areLocalesSetFromDelegate(userId, appPackageName));
             } catch (RemoteException | IllegalArgumentException e) {
                 Slog.e(TAG, "Could not set locales for " + appPackageName, e);
             }
@@ -829,7 +837,7 @@
     @NonNull
     private File getXmlFileNameForUser(@NonNull String appPackageName, @UserIdInt int userId) {
         // TODO(b/262752965): use per-package data directory
-        final File dir = new File(Environment.getDataSystemDeDirectory(userId), LOCALE_CONFIGS);
+        final File dir = new File(Environment.getDataSystemCeDirectory(userId), LOCALE_CONFIGS);
         return new File(dir, appPackageName + SUFFIX_FILE_NAME);
     }
 
diff --git a/services/core/java/com/android/server/locales/LocaleManagerServicePackageMonitor.java b/services/core/java/com/android/server/locales/LocaleManagerServicePackageMonitor.java
index 771e1b0..ecd3614 100644
--- a/services/core/java/com/android/server/locales/LocaleManagerServicePackageMonitor.java
+++ b/services/core/java/com/android/server/locales/LocaleManagerServicePackageMonitor.java
@@ -37,16 +37,13 @@
 final class LocaleManagerServicePackageMonitor extends PackageMonitor {
     private LocaleManagerBackupHelper mBackupHelper;
     private SystemAppUpdateTracker mSystemAppUpdateTracker;
-    private AppUpdateTracker mAppUpdateTracker;
     private LocaleManagerService mLocaleManagerService;
 
     LocaleManagerServicePackageMonitor(@NonNull LocaleManagerBackupHelper localeManagerBackupHelper,
             @NonNull SystemAppUpdateTracker systemAppUpdateTracker,
-            @NonNull AppUpdateTracker appUpdateTracker,
             @NonNull LocaleManagerService localeManagerService) {
         mBackupHelper = localeManagerBackupHelper;
         mSystemAppUpdateTracker = systemAppUpdateTracker;
-        mAppUpdateTracker = appUpdateTracker;
         mLocaleManagerService = localeManagerService;
     }
 
@@ -68,7 +65,7 @@
 
     @Override
     public void onPackageUpdateFinished(String packageName, int uid) {
-        mAppUpdateTracker.onPackageUpdateFinished(packageName, uid);
+        mBackupHelper.onPackageUpdateFinished(packageName, uid);
         mSystemAppUpdateTracker.onPackageUpdateFinished(packageName, uid);
     }
 }
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsService.java b/services/core/java/com/android/server/locksettings/LockSettingsService.java
index afae08d..2b5f874 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsService.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsService.java
@@ -421,6 +421,8 @@
     static class Injector {
 
         protected Context mContext;
+        private ServiceThread mHandlerThread;
+        private Handler mHandler;
 
         public Injector(Context context) {
             mContext = context;
@@ -431,14 +433,20 @@
         }
 
         public ServiceThread getServiceThread() {
-            ServiceThread handlerThread = new ServiceThread(TAG, Process.THREAD_PRIORITY_BACKGROUND,
-                    true /*allowIo*/);
-            handlerThread.start();
-            return handlerThread;
+            if (mHandlerThread == null) {
+                mHandlerThread = new ServiceThread(TAG,
+                        Process.THREAD_PRIORITY_BACKGROUND,
+                        true /*allowIo*/);
+                mHandlerThread.start();
+            }
+            return mHandlerThread;
         }
 
         public Handler getHandler(ServiceThread handlerThread) {
-            return new Handler(handlerThread.getLooper());
+            if (mHandler == null) {
+                mHandler = new Handler(handlerThread.getLooper());
+            }
+            return mHandler;
         }
 
         public LockSettingsStorage getStorage() {
@@ -519,7 +527,8 @@
 
         public RebootEscrowManager getRebootEscrowManager(RebootEscrowManager.Callbacks callbacks,
                 LockSettingsStorage storage) {
-            return new RebootEscrowManager(mContext, callbacks, storage);
+            return new RebootEscrowManager(mContext, callbacks, storage,
+                    getHandler(getServiceThread()));
         }
 
         public int binderGetCallingUid() {
diff --git a/services/core/java/com/android/server/locksettings/RebootEscrowManager.java b/services/core/java/com/android/server/locksettings/RebootEscrowManager.java
index 9b42cfc..e1cd2c5 100644
--- a/services/core/java/com/android/server/locksettings/RebootEscrowManager.java
+++ b/services/core/java/com/android/server/locksettings/RebootEscrowManager.java
@@ -205,6 +205,8 @@
 
     private final RebootEscrowKeyStoreManager mKeyStoreManager;
 
+    private final Handler mHandler;
+
     PowerManager.WakeLock mWakeLock;
 
     private ConnectivityManager.NetworkCallback mNetworkCallback;
@@ -399,19 +401,21 @@
         }
     }
 
-    RebootEscrowManager(Context context, Callbacks callbacks, LockSettingsStorage storage) {
-        this(new Injector(context, storage), callbacks, storage);
+    RebootEscrowManager(Context context, Callbacks callbacks, LockSettingsStorage storage,
+            Handler handler) {
+        this(new Injector(context, storage), callbacks, storage, handler);
     }
 
     @VisibleForTesting
     RebootEscrowManager(Injector injector, Callbacks callbacks,
-            LockSettingsStorage storage) {
+            LockSettingsStorage storage, Handler handler) {
         mInjector = injector;
         mCallbacks = callbacks;
         mStorage = storage;
         mUserManager = injector.getUserManager();
         mEventLog = injector.getEventLog();
         mKeyStoreManager = injector.getKeyStoreManager();
+        mHandler = handler;
     }
 
     /** Wrapper function to set error code serialized through handler, */
@@ -937,7 +941,7 @@
 
     private void setRebootEscrowReady(boolean ready) {
         if (mRebootEscrowReady != ready) {
-            mRebootEscrowListener.onPreparedForReboot(ready);
+            mHandler.post(() -> mRebootEscrowListener.onPreparedForReboot(ready));
         }
         mRebootEscrowReady = ready;
     }
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 53b03d5..c2e8df1 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -2055,8 +2055,8 @@
 
     // TODO - replace these methods with new fields in the VisibleForTesting constructor
     @VisibleForTesting
-    void setAudioManager(AudioManager audioMananger) {
-        mAudioManager = audioMananger;
+    void setAudioManager(AudioManager audioManager) {
+        mAudioManager = audioManager;
     }
 
     @VisibleForTesting
@@ -5772,7 +5772,7 @@
                 switch (report) {
                     case REPORT_REMOTE_VIEWS:
                         Slog.e(TAG, "pullStats REPORT_REMOTE_VIEWS from: "
-                                + startMs + "  wtih " + doAgg);
+                                + startMs + "  with " + doAgg);
                         PulledStats stats = mUsageStats.remoteViewStats(startMs, doAgg);
                         if (stats != null) {
                             out.add(stats.toParcelFileDescriptor(report));
@@ -6470,7 +6470,7 @@
             }
         }
 
-        // Don't allow client applications to cancel foreground service notis or autobundled
+        // Don't allow client applications to cancel foreground service notifs or autobundled
         // summaries.
         final int mustNotHaveFlags = isCallingUidSystem() ? 0 :
                 (FLAG_FOREGROUND_SERVICE | FLAG_AUTOGROUP_SUMMARY);
@@ -8342,7 +8342,7 @@
                         Thread.sleep(waitMs);
                     } catch (InterruptedException e) { }
                     // Notifications might be canceled before it actually vibrates due to waitMs,
-                    // so need to check the notification still valide for vibrate.
+                    // so need to check that the notification is still valid for vibrate.
                     synchronized (mNotificationLock) {
                         if (mNotificationsByKey.get(record.getKey()) != null) {
                             if (record.getKey().equals(mVibrateNotificationKey)) {
@@ -11696,7 +11696,7 @@
                     }
                 } else if (PRIORITY_ARG.equals(a)) {
                     // Bugreport will call the service twice with priority arguments, first to dump
-                    // critical sections and then non critical ones. Set approriate filters
+                    // critical sections and then non critical ones. Set appropriate filters
                     // to generate the desired data.
                     if (ai < args.length - 1) {
                         ai++;
diff --git a/services/core/java/com/android/server/notification/PreferencesHelper.java b/services/core/java/com/android/server/notification/PreferencesHelper.java
index 59af58f..4bafbc7 100644
--- a/services/core/java/com/android/server/notification/PreferencesHelper.java
+++ b/services/core/java/com/android/server/notification/PreferencesHelper.java
@@ -135,7 +135,6 @@
     private static final String ATT_SHOW_BADGE = "show_badge";
     private static final String ATT_APP_USER_LOCKED_FIELDS = "app_user_locked_fields";
     private static final String ATT_ENABLED = "enabled";
-    private static final String ATT_USER_ALLOWED = "allowed";
     private static final String ATT_HIDE_SILENT = "hide_gentle";
     private static final String ATT_SENT_INVALID_MESSAGE = "sent_invalid_msg";
     private static final String ATT_SENT_VALID_MESSAGE = "sent_valid_msg";
@@ -363,15 +362,12 @@
                     String delegateName = XmlUtils.readStringAttribute(parser, ATT_NAME);
                     boolean delegateEnabled = parser.getAttributeBoolean(
                             null, ATT_ENABLED, Delegate.DEFAULT_ENABLED);
-                    boolean userAllowed = parser.getAttributeBoolean(
-                            null, ATT_USER_ALLOWED, Delegate.DEFAULT_USER_ALLOWED);
                     Delegate d = null;
                     if (delegateId != UNKNOWN_UID && !TextUtils.isEmpty(delegateName)) {
-                        d = new Delegate(delegateName, delegateId, delegateEnabled, userAllowed);
+                        d = new Delegate(delegateName, delegateId, delegateEnabled);
                     }
                     r.delegate = d;
                 }
-
             }
 
             try {
@@ -640,9 +636,6 @@
                     if (r.delegate.mEnabled != Delegate.DEFAULT_ENABLED) {
                         out.attributeBoolean(null, ATT_ENABLED, r.delegate.mEnabled);
                     }
-                    if (r.delegate.mUserAllowed != Delegate.DEFAULT_USER_ALLOWED) {
-                        out.attributeBoolean(null, ATT_USER_ALLOWED, r.delegate.mUserAllowed);
-                    }
                     out.endTag(null, TAG_DELEGATE);
                 }
 
@@ -725,10 +718,17 @@
 
     @Override
     public void setShowBadge(String packageName, int uid, boolean showBadge) {
+        boolean changed = false;
         synchronized (mPackagePreferences) {
-            getOrCreatePackagePreferencesLocked(packageName, uid).showBadge = showBadge;
+            PackagePreferences pkgPrefs = getOrCreatePackagePreferencesLocked(packageName, uid);
+            if (pkgPrefs.showBadge != showBadge) {
+                pkgPrefs.showBadge = showBadge;
+                changed = true;
+            }
         }
-        updateConfig();
+        if (changed) {
+            updateConfig();
+        }
     }
 
     public boolean isInInvalidMsgState(String packageName, int uid) {
@@ -985,7 +985,9 @@
                     needsPolicyFileChange = true;
                 }
 
-                updateConfig();
+                if (needsPolicyFileChange) {
+                    updateConfig();
+                }
                 if (needsPolicyFileChange && !wasUndeleted) {
                     mNotificationChannelLogger.logNotificationChannelModified(existing, uid, pkg,
                             previousLoggingImportance, false);
@@ -1072,6 +1074,7 @@
             boolean fromUser) {
         Objects.requireNonNull(updatedChannel);
         Objects.requireNonNull(updatedChannel.getId());
+        boolean changed = false;
         boolean needsDndChange = false;
         synchronized (mPackagePreferences) {
             PackagePreferences r = getOrCreatePackagePreferencesLocked(pkg, uid);
@@ -1105,6 +1108,7 @@
                         ? Notification.PRIORITY_MAX : Notification.PRIORITY_DEFAULT;
                 r.visibility = updatedChannel.getLockscreenVisibility();
                 r.showBadge = updatedChannel.canShowBadge();
+                changed = true;
             }
 
             if (!channel.equals(updatedChannel)) {
@@ -1113,17 +1117,21 @@
                         .setSubtype(fromUser ? 1 : 0));
                 mNotificationChannelLogger.logNotificationChannelModified(updatedChannel, uid, pkg,
                         NotificationChannelLogger.getLoggingImportance(channel), fromUser);
+                changed = true;
             }
 
             if (updatedChannel.canBypassDnd() != mAreChannelsBypassingDnd
                     || channel.getImportance() != updatedChannel.getImportance()) {
                 needsDndChange = true;
+                changed = true;
             }
         }
         if (needsDndChange) {
             updateChannelsBypassingDnd();
         }
-        updateConfig();
+        if (changed) {
+            updateConfig();
+        }
     }
 
     @Override
@@ -1788,7 +1796,7 @@
             if (prefs == null || prefs.delegate == null) {
                 return null;
             }
-            if (!prefs.delegate.mUserAllowed || !prefs.delegate.mEnabled) {
+            if (!prefs.delegate.mEnabled) {
                 return null;
             }
             return prefs.delegate.mPkg;
@@ -1802,46 +1810,20 @@
             String delegatePkg, int delegateUid) {
         synchronized (mPackagePreferences) {
             PackagePreferences prefs = getOrCreatePackagePreferencesLocked(sourcePkg, sourceUid);
-
-            boolean userAllowed = prefs.delegate == null || prefs.delegate.mUserAllowed;
-            Delegate delegate = new Delegate(delegatePkg, delegateUid, true, userAllowed);
-            prefs.delegate = delegate;
+            prefs.delegate = new Delegate(delegatePkg, delegateUid, true);
         }
-        updateConfig();
     }
 
     /**
      * Used by an app to turn off its notification delegate.
      */
     public void revokeNotificationDelegate(String sourcePkg, int sourceUid) {
-        boolean changed = false;
         synchronized (mPackagePreferences) {
             PackagePreferences prefs = getPackagePreferencesLocked(sourcePkg, sourceUid);
             if (prefs != null && prefs.delegate != null) {
                 prefs.delegate.mEnabled = false;
-                changed = true;
             }
         }
-        if (changed) {
-            updateConfig();
-        }
-    }
-
-    /**
-     * Toggles whether an app can have a notification delegate on behalf of a user.
-     */
-    public void toggleNotificationDelegate(String sourcePkg, int sourceUid, boolean userAllowed) {
-        boolean changed = false;
-        synchronized (mPackagePreferences) {
-            PackagePreferences prefs = getPackagePreferencesLocked(sourcePkg, sourceUid);
-            if (prefs != null && prefs.delegate != null) {
-                prefs.delegate.mUserAllowed = userAllowed;
-                changed = true;
-            }
-        }
-        if (changed) {
-            updateConfig();
-        }
     }
 
     /**
@@ -2726,17 +2708,15 @@
 
     private static class Delegate {
         static final boolean DEFAULT_ENABLED = true;
-        static final boolean DEFAULT_USER_ALLOWED = true;
-        String mPkg;
-        int mUid = UNKNOWN_UID;
-        boolean mEnabled = DEFAULT_ENABLED;
-        boolean mUserAllowed = DEFAULT_USER_ALLOWED;
 
-        Delegate(String pkg, int uid, boolean enabled, boolean userAllowed) {
+        final String mPkg;
+        final int mUid;
+        boolean mEnabled;
+
+        Delegate(String pkg, int uid, boolean enabled) {
             mPkg = pkg;
             mUid = uid;
             mEnabled = enabled;
-            mUserAllowed = userAllowed;
         }
 
         public boolean isAllowed(String pkg, int uid) {
@@ -2745,7 +2725,7 @@
             }
             return pkg.equals(mPkg)
                     && uid == mUid
-                    && (mUserAllowed && mEnabled);
+                    && mEnabled;
         }
     }
 }
diff --git a/services/core/java/com/android/server/pm/UserManagerInternal.java b/services/core/java/com/android/server/pm/UserManagerInternal.java
index c2c9fc1..721ad88 100644
--- a/services/core/java/com/android/server/pm/UserManagerInternal.java
+++ b/services/core/java/com/android/server/pm/UserManagerInternal.java
@@ -587,5 +587,6 @@
 
      * @throws UserManager.CheckedUserOperationException if no switchable user can be found
      */
-    public abstract @UserIdInt int getBootUser() throws UserManager.CheckedUserOperationException;
+    public abstract @UserIdInt int getBootUser(boolean waitUntilSet)
+            throws UserManager.CheckedUserOperationException;
 }
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index 10795fa..cc40363 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -161,7 +161,9 @@
 import java.util.List;
 import java.util.Objects;
 import java.util.Set;
+import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.ThreadLocalRandom;
+import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicInteger;
 import java.util.concurrent.atomic.AtomicReference;
 
@@ -278,6 +280,8 @@
     static final int WRITE_USER_MSG = 1;
     static final int WRITE_USER_DELAY = 2*1000;  // 2 seconds
 
+    private static final long BOOT_USER_SET_TIMEOUT_MS = 300_000;
+
     // Tron counters
     private static final String TRON_GUEST_CREATED = "users_guest_created";
     private static final String TRON_USER_CREATED = "users_user_created";
@@ -333,6 +337,8 @@
     /** Indicates that this is the 1st boot after the system user mode was changed by emulation. */
     private boolean mUpdatingSystemUserMode;
 
+    /** Count down latch to wait while boot user is not set.*/
+    private final CountDownLatch mBootUserLatch = new CountDownLatch(1);
     /**
      * Internal non-parcelable wrapper for UserInfo that is not exposed to other system apps.
      */
@@ -952,18 +958,62 @@
             Slogf.i(LOG_TAG, "setBootUser %d", userId);
             mBootUser = userId;
         }
+        mBootUserLatch.countDown();
     }
 
     @Override
     public @UserIdInt int getBootUser() {
         checkCreateUsersPermission("Get boot user");
         try {
-            return mLocalService.getBootUser();
+            return getBootUserUnchecked();
         } catch (UserManager.CheckedUserOperationException e) {
             throw e.toServiceSpecificException();
         }
     }
 
+    private @UserIdInt int getBootUserUnchecked() throws UserManager.CheckedUserOperationException {
+        synchronized (mUsersLock) {
+            if (mBootUser != UserHandle.USER_NULL) {
+                final UserData userData = mUsers.get(mBootUser);
+                if (userData != null && userData.info.supportsSwitchToByUser()) {
+                    Slogf.i(LOG_TAG, "Using provided boot user: %d", mBootUser);
+                    return mBootUser;
+                } else {
+                    Slogf.w(LOG_TAG,
+                            "Provided boot user cannot be switched to: %d", mBootUser);
+                }
+            }
+        }
+
+        if (isHeadlessSystemUserMode()) {
+            // Return the previous foreground user, if there is one.
+            final int previousUser = getPreviousFullUserToEnterForeground();
+            if (previousUser != UserHandle.USER_NULL) {
+                Slogf.i(LOG_TAG, "Boot user is previous user %d", previousUser);
+                return previousUser;
+            }
+            // No previous user. Return the first switchable user if there is one.
+            synchronized (mUsersLock) {
+                final int userSize = mUsers.size();
+                for (int i = 0; i < userSize; i++) {
+                    final UserData userData = mUsers.valueAt(i);
+                    if (userData.info.supportsSwitchToByUser()) {
+                        int firstSwitchable = userData.info.id;
+                        Slogf.i(LOG_TAG,
+                                "Boot user is first switchable user %d", firstSwitchable);
+                        return firstSwitchable;
+                    }
+                }
+            }
+            // No switchable users found. Uh oh!
+            throw new UserManager.CheckedUserOperationException(
+                    "No switchable users found", USER_OPERATION_ERROR_UNKNOWN);
+        }
+        // Not HSUM, return system user.
+        return UserHandle.USER_SYSTEM;
+    }
+
+
     @Override
     public int getPreviousFullUserToEnterForeground() {
         checkQueryOrCreateUsersPermission("get previous user");
@@ -1495,7 +1545,8 @@
         // intentSender
         unlockIntent.putExtra(Intent.EXTRA_INTENT, pendingIntent.getIntentSender());
         unlockIntent.setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
-        mContext.startActivity(unlockIntent);
+        mContext.startActivityAsUser(
+                unlockIntent, UserHandle.of(getProfileParentIdUnchecked(userId)));
     }
 
     @Override
@@ -7187,47 +7238,29 @@
         }
 
         @Override
-        public @UserIdInt int getBootUser() throws UserManager.CheckedUserOperationException {
-            synchronized (mUsersLock) {
-                // TODO(b/242195409): On Automotive, block if boot user not provided.
-                if (mBootUser != UserHandle.USER_NULL) {
-                    final UserData userData = mUsers.get(mBootUser);
-                    if (userData != null && userData.info.supportsSwitchToByUser()) {
-                        Slogf.i(LOG_TAG, "Using provided boot user: %d", mBootUser);
-                        return mBootUser;
-                    } else {
-                        Slogf.w(LOG_TAG,
-                                "Provided boot user cannot be switched to: %d", mBootUser);
+        public @UserIdInt int getBootUser(boolean waitUntilSet)
+                throws UserManager.CheckedUserOperationException {
+            if (waitUntilSet) {
+                final TimingsTraceAndSlog t = new TimingsTraceAndSlog();
+                t.traceBegin("wait-boot-user");
+                try {
+                    if (mBootUserLatch.getCount() != 0) {
+                        Slogf.d(LOG_TAG,
+                                "Sleeping for boot user to be set. "
+                                + "Max sleep for Time: %d", BOOT_USER_SET_TIMEOUT_MS);
                     }
+                    if (!mBootUserLatch.await(BOOT_USER_SET_TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
+                        Slogf.w(LOG_TAG, "Boot user not set. Timeout: %d",
+                                BOOT_USER_SET_TIMEOUT_MS);
+                    }
+                } catch (InterruptedException e) {
+                    Thread.currentThread().interrupt();
+                    Slogf.w(LOG_TAG, e, "InterruptedException during wait for boot user.");
                 }
+                t.traceEnd();
             }
 
-            if (isHeadlessSystemUserMode()) {
-                // Return the previous foreground user, if there is one.
-                final int previousUser = getPreviousFullUserToEnterForeground();
-                if (previousUser != UserHandle.USER_NULL) {
-                    Slogf.i(LOG_TAG, "Boot user is previous user %d", previousUser);
-                    return previousUser;
-                }
-                // No previous user. Return the first switchable user if there is one.
-                synchronized (mUsersLock) {
-                    final int userSize = mUsers.size();
-                    for (int i = 0; i < userSize; i++) {
-                        final UserData userData = mUsers.valueAt(i);
-                        if (userData.info.supportsSwitchToByUser()) {
-                            int firstSwitchable = userData.info.id;
-                            Slogf.i(LOG_TAG,
-                                    "Boot user is first switchable user %d", firstSwitchable);
-                            return firstSwitchable;
-                        }
-                    }
-                }
-                // No switchable users found. Uh oh!
-                throw new UserManager.CheckedUserOperationException(
-                        "No switchable users found", USER_OPERATION_ERROR_UNKNOWN);
-            }
-            // Not HSUM, return system user.
-            return UserHandle.USER_SYSTEM;
+            return getBootUserUnchecked();
         }
 
     } // class LocalService
diff --git a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
index e74b459..e5e32f0 100644
--- a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
+++ b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
@@ -1085,7 +1085,7 @@
     public void grantDefaultPermissionsToActiveLuiApp(String packageName, int userId) {
         Log.i(TAG, "Granting permissions to active LUI app for user:" + userId);
         grantSystemFixedPermissionsToSystemPackage(NO_PM_CACHE, packageName, userId,
-                CAMERA_PERMISSIONS);
+                CAMERA_PERMISSIONS, NOTIFICATION_PERMISSIONS);
     }
 
     public void revokeDefaultPermissionsFromLuiApps(String[] packageNames, int userId) {
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 4ec8afd..ea53ea5 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -647,6 +647,9 @@
 
     private boolean mLockNowPending = false;
 
+    // Timeout for showing the keyguard after the screen is on, in case no "ready" is received.
+    private int mKeyguardDrawnTimeout = 1000;
+
     private static final int MSG_DISPATCH_MEDIA_KEY_WITH_WAKE_LOCK = 3;
     private static final int MSG_DISPATCH_MEDIA_KEY_REPEAT_WITH_WAKE_LOCK = 4;
     private static final int MSG_KEYGUARD_DRAWN_COMPLETE = 5;
@@ -2236,6 +2239,8 @@
             }
         });
 
+        mKeyguardDrawnTimeout = mContext.getResources().getInteger(
+                com.android.internal.R.integer.config_keyguardDrawnTimeout);
         mKeyguardDelegate = new KeyguardServiceDelegate(mContext,
                 new StateCallback() {
                     @Override
@@ -4981,7 +4986,7 @@
         final boolean bootCompleted =
                 LocalServices.getService(SystemServiceManager.class).isBootCompleted();
         // Set longer timeout if it has not booted yet to prevent showing empty window.
-        return bootCompleted ? 1000 : 5000;
+        return bootCompleted ? mKeyguardDrawnTimeout : 5000;
     }
 
     // Called on the DisplayManager's DisplayPowerController thread.
diff --git a/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java b/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
index bc23020..661715c 100644
--- a/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
+++ b/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
@@ -26,6 +26,7 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.ActivityManager;
+import android.app.AlarmManager;
 import android.app.usage.NetworkStatsManager;
 import android.bluetooth.BluetoothActivityEnergyInfo;
 import android.bluetooth.UidTraffic;
@@ -500,14 +501,6 @@
 
     }
 
-    /** Handles calls to AlarmManager */
-    public interface AlarmInterface {
-        /** Schedule an RTC alarm */
-        void schedule(long rtcTimeMs, long windowLengthMs);
-        /** Cancel the previously scheduled alarm */
-        void cancel();
-    }
-
     private final PlatformIdleStateCallback mPlatformIdleStateCallback;
 
     private final Runnable mDeferSetCharging = new Runnable() {
@@ -1569,8 +1562,15 @@
     @GuardedBy("this")
     protected BatteryStatsConfig mBatteryStatsConfig = new BatteryStatsConfig.Builder().build();
 
-    @VisibleForTesting
-    protected AlarmInterface mLongPlugInAlarmInterface = null;
+    @GuardedBy("this")
+    private AlarmManager mAlarmManager = null;
+
+    private final AlarmManager.OnAlarmListener mLongPlugInAlarmHandler = () ->
+            mHandler.post(() -> {
+                synchronized (BatteryStatsImpl.this) {
+                    maybeResetWhilePluggedInLocked();
+                }
+            });
 
     /*
      * Holds a SamplingTimer associated with each Resource Power Manager state and voter,
@@ -11061,18 +11061,6 @@
     }
 
     /**
-     * Injects an AlarmInterface for the long plug in alarm.
-     */
-    public void setLongPlugInAlarmInterface(AlarmInterface longPlugInAlarmInterface) {
-        synchronized (this) {
-            mLongPlugInAlarmInterface = longPlugInAlarmInterface;
-            if (mBatteryPluggedIn) {
-                scheduleNextResetWhilePluggedInCheck();
-            }
-        }
-    }
-
-    /**
      * Starts tracking CPU time-in-state for threads of the system server process,
      * keeping a separate account of threads receiving incoming binder calls.
      */
@@ -14173,6 +14161,7 @@
     /**
      * Might reset battery stats if conditions are met. Assumed the device is currently plugged in.
      */
+    @VisibleForTesting
     @GuardedBy("this")
     public void maybeResetWhilePluggedInLocked() {
         final long elapsedRealtimeMs = mClock.elapsedRealtime();
@@ -14189,28 +14178,31 @@
 
     @GuardedBy("this")
     private void scheduleNextResetWhilePluggedInCheck() {
-        if (mLongPlugInAlarmInterface != null) {
-            final long timeoutMs = mClock.currentTimeMillis()
-                    + mConstants.RESET_WHILE_PLUGGED_IN_MINIMUM_DURATION_HOURS
-                    * DateUtils.HOUR_IN_MILLIS;
-            Calendar nextAlarm = Calendar.getInstance();
-            nextAlarm.setTimeInMillis(timeoutMs);
+        if (mAlarmManager == null) return;
+        final long timeoutMs = mClock.currentTimeMillis()
+                + mConstants.RESET_WHILE_PLUGGED_IN_MINIMUM_DURATION_HOURS
+                * DateUtils.HOUR_IN_MILLIS;
+        Calendar nextAlarm = Calendar.getInstance();
+        nextAlarm.setTimeInMillis(timeoutMs);
 
-            // Find the 2 AM the same day as the end of the minimum duration.
-            // This logic does not handle a Daylight Savings transition, or a timezone change
-            // while the alarm has been set. The need to reset after a long period while plugged
-            // in is not strict enough to warrant a well architected out solution.
-            nextAlarm.set(Calendar.MILLISECOND, 0);
-            nextAlarm.set(Calendar.SECOND, 0);
-            nextAlarm.set(Calendar.MINUTE, 0);
-            nextAlarm.set(Calendar.HOUR_OF_DAY, 2);
-            long nextTimeMs = nextAlarm.getTimeInMillis();
-            if (nextTimeMs < timeoutMs) {
-                // The 2AM on the day of the timeout, move on the next day.
-                nextTimeMs += DateUtils.DAY_IN_MILLIS;
-            }
-            mLongPlugInAlarmInterface.schedule(nextTimeMs, DateUtils.HOUR_IN_MILLIS);
+        // Find the 2 AM the same day as the end of the minimum duration.
+        // This logic does not handle a Daylight Savings transition, or a timezone change
+        // while the alarm has been set. The need to reset after a long period while plugged
+        // in is not strict enough to warrant a well architected out solution.
+        nextAlarm.set(Calendar.MILLISECOND, 0);
+        nextAlarm.set(Calendar.SECOND, 0);
+        nextAlarm.set(Calendar.MINUTE, 0);
+        nextAlarm.set(Calendar.HOUR_OF_DAY, 2);
+        long possibleNextTimeMs = nextAlarm.getTimeInMillis();
+        if (possibleNextTimeMs < timeoutMs) {
+            // The 2AM on the day of the timeout, move on the next day.
+            possibleNextTimeMs += DateUtils.DAY_IN_MILLIS;
         }
+        final long nextTimeMs = possibleNextTimeMs;
+        final AlarmManager am = mAlarmManager;
+        mHandler.post(() -> am.setWindow(AlarmManager.RTC, nextTimeMs,
+                DateUtils.HOUR_IN_MILLIS,
+                TAG, mLongPlugInAlarmHandler, mHandler));
     }
 
 
@@ -14339,8 +14331,12 @@
                 initActiveHistoryEventsLocked(mSecRealtime, mSecUptime);
             }
             mBatteryPluggedIn = false;
-            if (mLongPlugInAlarmInterface != null) {
-                mLongPlugInAlarmInterface.cancel();
+            if (mAlarmManager != null) {
+                final AlarmManager am = mAlarmManager;
+                mHandler.post(() -> {
+                    // No longer plugged in. Cancel the long plug in alarm.
+                    am.cancel(mLongPlugInAlarmHandler);
+                });
             }
             mHistory.recordBatteryState(mSecRealtime, mSecUptime, level, mBatteryPluggedIn);
             mDischargeCurrentLevel = mDischargeUnplugLevel = level;
@@ -15178,6 +15174,14 @@
     public void systemServicesReady(Context context) {
         mConstants.startObserving(context.getContentResolver());
         registerUsbStateReceiver(context);
+
+        synchronized (this) {
+            mAlarmManager = context.getSystemService(AlarmManager.class);
+            if (mBatteryPluggedIn) {
+                // Already plugged in. Schedule the long plug in alarm.
+                scheduleNextResetWhilePluggedInCheck();
+            }
+        }
     }
 
     /**
diff --git a/services/core/java/com/android/server/speech/RemoteSpeechRecognitionService.java b/services/core/java/com/android/server/speech/RemoteSpeechRecognitionService.java
index 8e8abf6..96f4a01 100644
--- a/services/core/java/com/android/server/speech/RemoteSpeechRecognitionService.java
+++ b/services/core/java/com/android/server/speech/RemoteSpeechRecognitionService.java
@@ -250,15 +250,7 @@
                 service.checkRecognitionSupport(recognizerIntent, attributionSource, callback));
     }
 
-    void triggerModelDownload(Intent recognizerIntent, AttributionSource attributionSource) {
-        if (!mConnected) {
-            Slog.e(TAG, "#downloadModel failed due to connection.");
-            return;
-        }
-        run(service -> service.triggerModelDownload(recognizerIntent, attributionSource));
-    }
-
-    void setModelDownloadListener(
+    void triggerModelDownload(
             Intent recognizerIntent,
             AttributionSource attributionSource,
             IModelDownloadListener listener) {
@@ -266,25 +258,12 @@
             try {
                 listener.onError(SpeechRecognizer.ERROR_SERVER_DISCONNECTED);
             } catch (RemoteException e) {
-                Slog.w(TAG, "Failed to report the connection broke to the caller.", e);
+                Slog.w(TAG, "#downloadModel failed due to connection.", e);
                 e.printStackTrace();
             }
             return;
         }
-
-        run(service ->
-                service.setModelDownloadListener(recognizerIntent, attributionSource, listener));
-    }
-
-    void clearModelDownloadListener(
-            Intent recognizerIntent,
-            AttributionSource attributionSource) {
-        if (!mConnected) {
-            return;
-        }
-
-        run(service ->
-                service.clearModelDownloadListener(recognizerIntent, attributionSource));
+        run(service -> service.triggerModelDownload(recognizerIntent, attributionSource, listener));
     }
 
     void shutdown(IBinder clientToken) {
diff --git a/services/core/java/com/android/server/speech/SpeechRecognitionManagerServiceImpl.java b/services/core/java/com/android/server/speech/SpeechRecognitionManagerServiceImpl.java
index bc73db1..bff6d50 100644
--- a/services/core/java/com/android/server/speech/SpeechRecognitionManagerServiceImpl.java
+++ b/services/core/java/com/android/server/speech/SpeechRecognitionManagerServiceImpl.java
@@ -193,25 +193,11 @@
                         @Override
                         public void triggerModelDownload(
                                 Intent recognizerIntent,
-                                AttributionSource attributionSource) {
-                            service.triggerModelDownload(recognizerIntent, attributionSource);
-                        }
-
-                        @Override
-                        public void setModelDownloadListener(
-                                Intent recognizerIntent,
                                 AttributionSource attributionSource,
-                                IModelDownloadListener listener) throws RemoteException {
-                            service.setModelDownloadListener(
+                                IModelDownloadListener listener) {
+                            service.triggerModelDownload(
                                     recognizerIntent, attributionSource, listener);
                         }
-
-                        @Override
-                        public void clearModelDownloadListener(
-                                Intent recognizerIntent,
-                                AttributionSource attributionSource) throws RemoteException {
-                            service.clearModelDownloadListener(recognizerIntent, attributionSource);
-                        }
                     });
                 } catch (RemoteException e) {
                     Slog.e(TAG, "Error creating a speech recognition session", e);
diff --git a/services/core/java/com/android/server/wm/ActivityClientController.java b/services/core/java/com/android/server/wm/ActivityClientController.java
index d108f0d..f14a432 100644
--- a/services/core/java/com/android/server/wm/ActivityClientController.java
+++ b/services/core/java/com/android/server/wm/ActivityClientController.java
@@ -251,11 +251,6 @@
                     // {@link #restartActivityProcessIfVisible}.
                     restartingName = r.app.mName;
                     restartingUid = r.app.mUid;
-                    // Make EnsureActivitiesVisibleHelper#makeVisibleAndRestartIfNeeded not skip
-                    // restarting non-top activity.
-                    if (r != r.getTask().topRunningActivity()) {
-                        r.setVisibleRequested(false);
-                    }
                 }
                 r.activityStopped(icicle, persistentState, description);
             }
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index faa0cc9..d249f8c 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -4135,9 +4135,7 @@
         } else if (!mVisibleRequested && launchCount > 2
                 && lastLaunchTime > (SystemClock.uptimeMillis() - 60000)) {
             // We have launched this activity too many times since it was able to run, so give up
-            // and remove it. (Note if the activity is visible, we don't remove the record. We leave
-            // the dead window on the screen but the process will not be restarted unless user
-            // explicitly tap on it.)
+            // and remove it.
             remove = true;
         } else {
             // The process may be gone, but the activity lives on!
@@ -4159,11 +4157,6 @@
             if (DEBUG_APP) {
                 Slog.v(TAG_APP, "Keeping entry during removeHistory for activity " + this);
             }
-            // Set nowVisible to previous visible state. If the app was visible while it died, we
-            // leave the dead window on screen so it's basically visible. This is needed when user
-            // later tap on the dead window, we need to stop other apps when user transfers focus
-            // to the restarted activity.
-            nowVisible = mVisibleRequested;
         }
         // upgrade transition trigger to task if this is the last activity since it means we are
         // closing the task.
@@ -5290,10 +5283,6 @@
         mLastDeferHidingClient = deferHidingClient;
 
         if (!visible) {
-            // If the app is dead while it was visible, we kept its dead window on screen.
-            // Now that the app is going invisible, we can remove it. It will be restarted
-            // if made visible again.
-            removeDeadWindows();
             // If this activity is about to finish/stopped and now becomes invisible, remove it
             // from the unknownApp list in case the activity does not want to draw anything, which
             // keep the user waiting for the next transition to start.
@@ -6642,9 +6631,6 @@
         // stop tracking
         mSplashScreenStyleSolidColor = true;
 
-        // We now have a good window to show, remove dead placeholders
-        removeDeadWindows();
-
         if (mStartingWindow != null) {
             ProtoLog.v(WM_DEBUG_STARTING_WINDOW, "Finish starting %s"
                     + ": first real window is shown, no animation", win.mToken);
@@ -7380,20 +7366,6 @@
         }
     }
 
-    void removeDeadWindows() {
-        for (int winNdx = mChildren.size() - 1; winNdx >= 0; --winNdx) {
-            WindowState win = mChildren.get(winNdx);
-            if (win.mAppDied) {
-                ProtoLog.w(WM_DEBUG_ADD_REMOVE,
-                        "removeDeadWindows: %s", win);
-                // Set mDestroying, we don't want any animation or delayed removal here.
-                win.mDestroying = true;
-                // Also removes child windows.
-                win.removeIfPossible();
-            }
-        }
-    }
-
     void setWillReplaceWindows(boolean animate) {
         ProtoLog.d(WM_DEBUG_ADD_REMOVE,
                 "Marking app token %s with replacing windows.", this);
@@ -9074,6 +9046,7 @@
 
         final boolean wasInPictureInPicture = inPinnedWindowingMode();
         final DisplayContent display = mDisplayContent;
+        final int activityType = getActivityType();
         if (wasInPictureInPicture && attachedToProcess() && display != null) {
             // If the PIP activity is changing to fullscreen with display orientation change, the
             // fixed rotation will take effect that requires to send fixed rotation adjustments
@@ -9098,6 +9071,11 @@
         } else {
             super.onConfigurationChanged(newParentConfig);
         }
+        if (activityType != ACTIVITY_TYPE_UNDEFINED
+                && activityType != getActivityType()) {
+            Slog.w(TAG, "Can't change activity type once set: " + this
+                    + " activityType=" + activityTypeToString(getActivityType()));
+        }
 
         // Configuration's equality doesn't consider seq so if only seq number changes in resolved
         // override configuration. Therefore ConfigurationContainer doesn't change merged override
diff --git a/services/core/java/com/android/server/wm/ActivitySecurityModelFeatureFlags.java b/services/core/java/com/android/server/wm/ActivitySecurityModelFeatureFlags.java
index 1f7af41..19d8129 100644
--- a/services/core/java/com/android/server/wm/ActivitySecurityModelFeatureFlags.java
+++ b/services/core/java/com/android/server/wm/ActivitySecurityModelFeatureFlags.java
@@ -43,7 +43,7 @@
     static final String DOC_LINK = "go/android-asm";
 
     /** Used to determine which version of the ASM logic was used in logs while we iterate */
-    static final int ASM_VERSION = 6;
+    static final int ASM_VERSION = 7;
 
     private static final String NAMESPACE = NAMESPACE_WINDOW_MANAGER;
     private static final String KEY_ASM_PREFIX = "ActivitySecurity__";
@@ -89,6 +89,9 @@
 
         if (flagEnabled) {
             String[] packageNames = sPm.getPackagesForUid(uid);
+            if (packageNames == null) {
+                return true;
+            }
             for (int i = 0; i < packageNames.length; i++) {
                 if (sExcludedPackageNames.contains(packageNames[i])) {
                     return false;
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index 7024886..211c230 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -406,6 +406,8 @@
         PendingIntentRecord originatingPendingIntent;
         BackgroundStartPrivileges backgroundStartPrivileges;
 
+        final StringBuilder logMessage = new StringBuilder();
+
         /**
          * The error callback token passed in {@link android.window.WindowContainerTransaction}
          * for TaskFragment operation error handling via
@@ -734,7 +736,14 @@
                 if (res != START_SUCCESS) {
                     return res;
                 }
-                res = executeRequest(mRequest);
+
+                try {
+                    res = executeRequest(mRequest);
+                } finally {
+                    mRequest.logMessage.append(" result code=").append(res);
+                    Slog.i(TAG, mRequest.logMessage.toString());
+                    mRequest.logMessage.setLength(0);
+                }
 
                 Binder.restoreCallingIdentity(origId);
 
@@ -933,8 +942,14 @@
                 ? UserHandle.getUserId(aInfo.applicationInfo.uid) : 0;
         final int launchMode = aInfo != null ? aInfo.launchMode : 0;
         if (err == ActivityManager.START_SUCCESS) {
-            Slog.i(TAG, "START u" + userId + " {" + intent.toShortString(true, true, true, false)
-                    + "} with " + launchModeToString(launchMode) + " from uid " + callingUid);
+            request.logMessage.append("START u").append(userId).append(" {")
+                    .append(intent.toShortString(true, true, true, false))
+                    .append("} with ").append(launchModeToString(launchMode))
+                    .append(" from uid ").append(callingUid);
+            if (callingUid != realCallingUid
+                    && realCallingUid != Request.DEFAULT_REAL_CALLING_UID) {
+                request.logMessage.append(" (realCallingUid=").append(realCallingUid).append(")");
+            }
         }
 
         ActivityRecord sourceRecord = null;
@@ -1103,6 +1118,11 @@
                                 request.backgroundStartPrivileges,
                                 intent,
                                 checkedOptions);
+                if (balCode != BAL_ALLOW_DEFAULT) {
+                    request.logMessage.append(" (").append(
+                                    BackgroundActivityStartController.balCodeToString(balCode))
+                            .append(")");
+                }
             } finally {
                 Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
             }
@@ -2249,13 +2269,14 @@
      */
     private void clearTopIfNeeded(@NonNull Task targetTask, int callingUid, int realCallingUid,
             int startingUid, int launchFlags) {
-        if ((launchFlags & FLAG_ACTIVITY_NEW_TASK) != FLAG_ACTIVITY_NEW_TASK) {
-            // Launch is from the same task, so must be a top or privileged UID
+        if ((launchFlags & FLAG_ACTIVITY_NEW_TASK) != FLAG_ACTIVITY_NEW_TASK
+                || mBalCode == BAL_ALLOW_ALLOWLISTED_UID) {
+            // Launch is from the same task, (a top or privileged UID), or is directly privileged.
             return;
         }
 
-        Predicate<ActivityRecord> isLaunchingOrLaunched = ar -> !ar.finishing
-                && (ar.isUid(startingUid) || ar.isUid(callingUid) || ar.isUid(realCallingUid));
+        Predicate<ActivityRecord> isLaunchingOrLaunched = ar ->
+                ar.isUid(startingUid) || ar.isUid(callingUid) || ar.isUid(realCallingUid);
 
         // Return early if we know for sure we won't need to clear any activities by just checking
         // the top activity.
diff --git a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
index a0a2557..eaf5583 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
@@ -1666,7 +1666,7 @@
             String callerActivityClassName) {
         // We may have already checked that the callingUid has additional clearTask privileges, and
         // cleared the calling identify. If so, we infer we do not need further restrictions here.
-        if (callingUid == SYSTEM_UID) {
+        if (callingUid == SYSTEM_UID || !task.isVisible() || task.inMultiWindowMode()) {
             return;
         }
 
@@ -1772,13 +1772,19 @@
             return new Pair<>(true, true);
         }
 
+        // Always allow actual top activity to clear task
+        ActivityRecord topActivity = task.getTopMostActivity();
+        if (topActivity != null && topActivity.isUid(uid)) {
+            return new Pair<>(true, true);
+        }
+
         // Consider the source activity, whether or not it is finishing. Do not consider any other
         // finishing activity.
         Predicate<ActivityRecord> topOfStackPredicate = (ar) -> ar.equals(sourceRecord)
                 || (!ar.finishing && !ar.isAlwaysOnTop());
 
         // Check top of stack (or the first task fragment for embedding).
-        ActivityRecord topActivity = task.getActivity(topOfStackPredicate);
+        topActivity = task.getActivity(topOfStackPredicate);
         if (topActivity == null) {
             return new Pair<>(false, false);
         }
diff --git a/services/core/java/com/android/server/wm/AsyncRotationController.java b/services/core/java/com/android/server/wm/AsyncRotationController.java
index 5c9c813..7a11120 100644
--- a/services/core/java/com/android/server/wm/AsyncRotationController.java
+++ b/services/core/java/com/android/server/wm/AsyncRotationController.java
@@ -93,6 +93,9 @@
     /** Whether the start transaction of the transition is committed (by shell). */
     private boolean mIsStartTransactionCommitted;
 
+    /** Whether all windows should wait for the start transaction. */
+    private boolean mAlwaysWaitForStartTransaction;
+
     /** Whether the target windows have been requested to sync their draw transactions. */
     private boolean mIsSyncDrawRequested;
 
@@ -144,6 +147,15 @@
         if (mTransitionOp == OP_LEGACY) {
             mIsStartTransactionCommitted = true;
         } else if (displayContent.mTransitionController.isCollecting(displayContent)) {
+            final Transition transition =
+                    mDisplayContent.mTransitionController.getCollectingTransition();
+            if (transition != null) {
+                final BLASTSyncEngine.SyncGroup syncGroup =
+                        mDisplayContent.mWmService.mSyncEngine.getSyncSet(transition.getSyncId());
+                if (syncGroup != null && syncGroup.mSyncMethod == BLASTSyncEngine.METHOD_BLAST) {
+                    mAlwaysWaitForStartTransaction = true;
+                }
+            }
             keepAppearanceInPreviousRotation();
         }
     }
@@ -202,7 +214,7 @@
         // target windows. But the windows still need to use sync transaction to keep the appearance
         // in previous rotation, so request a no-op sync to keep the state.
         for (int i = mTargetWindowTokens.size() - 1; i >= 0; i--) {
-            if (mTargetWindowTokens.valueAt(i).canDrawBeforeStartTransaction()) {
+            if (canDrawBeforeStartTransaction(mTargetWindowTokens.valueAt(i))) {
                 // Expect a screenshot layer will cover the non seamless windows.
                 continue;
             }
@@ -521,7 +533,7 @@
         if (op == null) return false;
         if (DEBUG) Slog.d(TAG, "handleFinishDrawing " + w);
         if (postDrawTransaction == null || !mIsSyncDrawRequested
-                || op.canDrawBeforeStartTransaction()) {
+                || canDrawBeforeStartTransaction(op)) {
             mDisplayContent.finishAsyncRotation(w.mToken);
             return false;
         }
@@ -563,6 +575,14 @@
         return super.getFadeOutAnimation();
     }
 
+    /**
+     * Returns {@code true} if the corresponding window can draw its latest content before the
+     * start transaction of rotation transition is applied.
+     */
+    private boolean canDrawBeforeStartTransaction(Operation op) {
+        return !mAlwaysWaitForStartTransaction && op.mAction != Operation.ACTION_SEAMLESS;
+    }
+
     /** The operation to control the rotation appearance associated with window token. */
     private static class Operation {
         @Retention(RetentionPolicy.SOURCE)
@@ -588,14 +608,5 @@
         Operation(@Action int action) {
             mAction = action;
         }
-
-        /**
-         * Returns {@code true} if the corresponding window can draw its latest content before the
-         * start transaction of rotation transition is applied.
-         */
-        boolean canDrawBeforeStartTransaction() {
-            return TransitionController.SYNC_METHOD != BLASTSyncEngine.METHOD_BLAST
-                    && mAction != ACTION_SEAMLESS;
-        }
     }
 }
diff --git a/services/core/java/com/android/server/wm/BLASTSyncEngine.java b/services/core/java/com/android/server/wm/BLASTSyncEngine.java
index cd26e2e..48cf567 100644
--- a/services/core/java/com/android/server/wm/BLASTSyncEngine.java
+++ b/services/core/java/com/android/server/wm/BLASTSyncEngine.java
@@ -95,7 +95,7 @@
      */
     class SyncGroup {
         final int mSyncId;
-        final int mSyncMethod;
+        int mSyncMethod = METHOD_BLAST;
         final TransactionReadyListener mListener;
         final Runnable mOnTimeout;
         boolean mReady = false;
@@ -103,9 +103,8 @@
         private SurfaceControl.Transaction mOrphanTransaction = null;
         private String mTraceName;
 
-        private SyncGroup(TransactionReadyListener listener, int id, String name, int method) {
+        private SyncGroup(TransactionReadyListener listener, int id, String name) {
             mSyncId = id;
-            mSyncMethod = method;
             mListener = listener;
             mOnTimeout = () -> {
                 Slog.w(TAG, "Sync group " + mSyncId + " timeout");
@@ -288,13 +287,12 @@
      * Prepares a {@link SyncGroup} that is not active yet. Caller must call {@link #startSyncSet}
      * before calling {@link #addToSyncSet(int, WindowContainer)} on any {@link WindowContainer}.
      */
-    SyncGroup prepareSyncSet(TransactionReadyListener listener, String name, int method) {
-        return new SyncGroup(listener, mNextSyncId++, name, method);
+    SyncGroup prepareSyncSet(TransactionReadyListener listener, String name) {
+        return new SyncGroup(listener, mNextSyncId++, name);
     }
 
-    int startSyncSet(TransactionReadyListener listener, long timeoutMs, String name,
-            int method) {
-        final SyncGroup s = prepareSyncSet(listener, name, method);
+    int startSyncSet(TransactionReadyListener listener, long timeoutMs, String name) {
+        final SyncGroup s = prepareSyncSet(listener, name);
         startSyncSet(s, timeoutMs);
         return s.mSyncId;
     }
@@ -334,6 +332,15 @@
         getSyncGroup(id).addToSync(wc);
     }
 
+    void setSyncMethod(int id, int method) {
+        final SyncGroup syncGroup = getSyncGroup(id);
+        if (!syncGroup.mRootMembers.isEmpty()) {
+            throw new IllegalStateException(
+                    "Not allow to change sync method after adding group member, id=" + id);
+        }
+        syncGroup.mSyncMethod = method;
+    }
+
     void setReady(int id, boolean ready) {
         getSyncGroup(id).setReady(ready);
     }
diff --git a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
index 8fc3797..2344739596 100644
--- a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
+++ b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
@@ -430,7 +430,7 @@
         // anything that has fallen through would currently be aborted
         Slog.w(
                 TAG,
-                "Background activity start [callingPackage: "
+                "Background activity launch blocked [callingPackage: "
                         + callingPackage
                         + "; callingUid: "
                         + callingUid
@@ -502,7 +502,7 @@
             }
             builder.append("Activity start allowed: " + msg + ". callingUid: " + callingUid + ". ");
             builder.append("BAL Code: ");
-            builder.append(code);
+            builder.append(balCodeToString(code));
             Slog.d(TAG,  builder.toString());
         }
         return code;
diff --git a/services/core/java/com/android/server/wm/DeviceStateController.java b/services/core/java/com/android/server/wm/DeviceStateController.java
index f5313cb..270b2f8 100644
--- a/services/core/java/com/android/server/wm/DeviceStateController.java
+++ b/services/core/java/com/android/server/wm/DeviceStateController.java
@@ -24,6 +24,7 @@
 
 import com.android.internal.R;
 import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.ArrayUtils;
 
 import java.util.ArrayList;
@@ -51,7 +52,8 @@
     private final int[] mReverseRotationAroundZAxisStates;
     @GuardedBy("this")
     @NonNull
-    private final List<Consumer<DeviceState>> mDeviceStateCallbacks = new ArrayList<>();
+    @VisibleForTesting
+    final List<Consumer<DeviceState>> mDeviceStateCallbacks = new ArrayList<>();
 
     private final boolean mMatchBuiltInDisplayOrientationToDefaultDisplay;
 
@@ -98,6 +100,12 @@
         }
     }
 
+    void unregisterDeviceStateCallback(@NonNull Consumer<DeviceState> callback) {
+        synchronized (this) {
+            mDeviceStateCallbacks.remove(callback);
+        }
+    }
+
     /**
      * @return true if the rotation direction on the Z axis should be reversed.
      */
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 87f5703b..a4d475f 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -601,6 +601,7 @@
 
     @VisibleForTesting
     final DeviceStateController mDeviceStateController;
+    final Consumer<DeviceStateController.DeviceState> mDeviceStateConsumer;
     private final PhysicalDisplaySwitchTransitionLauncher mDisplaySwitchTransitionLauncher;
     final RemoteDisplayChangeController mRemoteDisplayChangeController;
 
@@ -1166,12 +1167,12 @@
         mDisplayRotation = new DisplayRotation(mWmService, this, mDisplayInfo.address,
                 mDeviceStateController, root.getDisplayRotationCoordinator());
 
-        final Consumer<DeviceStateController.DeviceState> deviceStateConsumer =
+        mDeviceStateConsumer =
                 (@NonNull DeviceStateController.DeviceState newFoldState) -> {
                     mDisplaySwitchTransitionLauncher.foldStateChanged(newFoldState);
                     mDisplayRotation.foldStateChanged(newFoldState);
                 };
-        mDeviceStateController.registerDeviceStateCallback(deviceStateConsumer);
+        mDeviceStateController.registerDeviceStateCallback(mDeviceStateConsumer);
 
         mCloseToSquareMaxAspectRatio = mWmService.mContext.getResources().getFloat(
                 R.dimen.config_closeToSquareDisplayMaxAspectRatio);
@@ -3283,6 +3284,7 @@
             handleAnimatingStoppedAndTransition();
             mWmService.stopFreezingDisplayLocked();
             mDisplayRotation.removeDefaultDisplayRotationChangedCallback();
+            mDeviceStateController.unregisterDeviceStateCallback(mDeviceStateConsumer);
             super.removeImmediately();
             if (DEBUG_DISPLAY) Slog.v(TAG_WM, "Removing display=" + this);
             mPointerEventDispatcher.dispose();
diff --git a/services/core/java/com/android/server/wm/EnsureActivitiesVisibleHelper.java b/services/core/java/com/android/server/wm/EnsureActivitiesVisibleHelper.java
index dde89e9..9cc311d 100644
--- a/services/core/java/com/android/server/wm/EnsureActivitiesVisibleHelper.java
+++ b/services/core/java/com/android/server/wm/EnsureActivitiesVisibleHelper.java
@@ -193,7 +193,7 @@
             }
 
             if (!r.attachedToProcess()) {
-                makeVisibleAndRestartIfNeeded(mStarting, mConfigChanges, isTop,
+                makeVisibleAndRestartIfNeeded(mStarting, mConfigChanges,
                         resumeTopActivity && isTop, r);
             } else if (r.isVisibleRequested()) {
                 // If this activity is already visible, then there is nothing to do here.
@@ -243,15 +243,7 @@
     }
 
     private void makeVisibleAndRestartIfNeeded(ActivityRecord starting, int configChanges,
-            boolean isTop, boolean andResume, ActivityRecord r) {
-        // We need to make sure the app is running if it's the top, or it is just made visible from
-        // invisible. If the app is already visible, it must have died while it was visible. In this
-        // case, we'll show the dead window but will not restart the app. Otherwise we could end up
-        // thrashing.
-        if (!isTop && r.isVisibleRequested() && !r.isState(INITIALIZING)) {
-            return;
-        }
-
+            boolean andResume, ActivityRecord r) {
         // This activity needs to be visible, but isn't even running...
         // get it started and resume if no other root task in this root task is resumed.
         if (DEBUG_VISIBILITY) {
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index ebdc537..969f65c 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -5781,8 +5781,11 @@
             final int taskId = activity != null
                     ? mTaskSupervisor.getNextTaskIdForUser(activity.mUserId)
                     : mTaskSupervisor.getNextTaskIdForUser();
+            final int activityType = getActivityType();
             task = new Task.Builder(mAtmService)
                     .setTaskId(taskId)
+                    .setActivityType(activityType != ACTIVITY_TYPE_UNDEFINED ? activityType
+                            : ACTIVITY_TYPE_STANDARD)
                     .setActivityInfo(info)
                     .setActivityOptions(options)
                     .setIntent(intent)
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index bf6983b..4e0f120 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -53,6 +53,7 @@
 import static android.window.TransitionInfo.FLAG_NO_ANIMATION;
 import static android.window.TransitionInfo.FLAG_OCCLUDES_KEYGUARD;
 import static android.window.TransitionInfo.FLAG_SHOW_WALLPAPER;
+import static android.window.TransitionInfo.FLAG_TASK_LAUNCHING_BEHIND;
 import static android.window.TransitionInfo.FLAG_TRANSLUCENT;
 import static android.window.TransitionInfo.FLAG_WILL_IME_SHOWN;
 
@@ -368,7 +369,6 @@
         return mState;
     }
 
-    @VisibleForTesting
     int getSyncId() {
         return mSyncId;
     }
@@ -408,18 +408,14 @@
         return mState == STATE_FINISHED;
     }
 
-    @VisibleForTesting
-    void startCollecting(long timeoutMs) {
-        startCollecting(timeoutMs, TransitionController.SYNC_METHOD);
-    }
-
     /** Starts collecting phase. Once this starts, all relevant surface operations are sync. */
-    void startCollecting(long timeoutMs, int method) {
+    void startCollecting(long timeoutMs) {
         if (mState != STATE_PENDING) {
             throw new IllegalStateException("Attempting to re-use a transition");
         }
         mState = STATE_COLLECTING;
-        mSyncId = mSyncEngine.startSyncSet(this, timeoutMs, TAG, method);
+        mSyncId = mSyncEngine.startSyncSet(this, timeoutMs, TAG);
+        mSyncEngine.setSyncMethod(mSyncId, TransitionController.SYNC_METHOD);
 
         mLogger.mSyncId = mSyncId;
         mLogger.mCollectTimeNs = SystemClock.elapsedRealtimeNanos();
@@ -679,7 +675,7 @@
      * needs to be passed/applied in shell because until finish is called, shell owns the surfaces.
      * Additionally, this gives shell the ability to better deal with merged transitions.
      */
-    private void buildFinishTransaction(SurfaceControl.Transaction t, SurfaceControl rootLeash) {
+    private void buildFinishTransaction(SurfaceControl.Transaction t, TransitionInfo info) {
         final Point tmpPos = new Point();
         // usually only size 1
         final ArraySet<DisplayContent> displays = new ArraySet<>();
@@ -732,8 +728,8 @@
         } finally {
             mController.mBuildingFinishLayers = false;
         }
-        if (rootLeash.isValid()) {
-            t.reparent(rootLeash, null);
+        for (int i = 0; i < info.getRootCount(); ++i) {
+            t.reparent(info.getRoot(i).getLeash(), null);
         }
     }
 
@@ -1070,13 +1066,6 @@
             Slog.e(TAG, "Unexpected Sync ID " + syncId + ". Expected " + mSyncId);
             return;
         }
-        if (mTargetDisplays.isEmpty()) {
-            mTargetDisplays.add(mController.mAtm.mRootWindowContainer.getDefaultDisplay());
-        }
-        // While there can be multiple DC's involved. For now, we just use the first one as
-        // the "primary" one for most things. Eventually, this will need to change, but, for the
-        // time being, we don't have full cross-display transitions so it isn't a problem.
-        final DisplayContent dc = mTargetDisplays.get(0);
 
         // Commit the visibility of visible activities before calculateTransitionInfo(), so the
         // TaskInfo can be visible. Also it needs to be done before moveToPlaying(), otherwise
@@ -1086,6 +1075,9 @@
 
         if (mState == STATE_ABORT) {
             mController.abort(this);
+            // Fall-back to the default display if there isn't one participating.
+            final DisplayContent dc = !mTargetDisplays.isEmpty() ? mTargetDisplays.get(0)
+                    : mController.mAtm.mRootWindowContainer.getDefaultDisplay();
             dc.getPendingTransaction().merge(transaction);
             mSyncId = -1;
             mOverrideOptions = null;
@@ -1098,16 +1090,24 @@
         mFinishTransaction = mController.mAtm.mWindowManager.mTransactionFactory.get();
         mController.moveToPlaying(this);
 
-        if (dc.isKeyguardLocked()) {
-            mFlags |= TRANSIT_FLAG_KEYGUARD_LOCKED;
-        }
-
         // Check whether the participants were animated from back navigation.
         final boolean markBackAnimated = mController.mAtm.mBackNavigationController
                 .containsBackAnimationTargets(this);
-        // Resolve the animating targets from the participants
+        // Resolve the animating targets from the participants.
         mTargets = calculateTargets(mParticipants, mChanges);
         final TransitionInfo info = calculateTransitionInfo(mType, mFlags, mTargets, transaction);
+
+        // Repopulate the displays based on the resolved targets.
+        mTargetDisplays.clear();
+        for (int i = 0; i < info.getRootCount(); ++i) {
+            final DisplayContent dc = mController.mAtm.mRootWindowContainer.getDisplayContent(
+                    info.getRoot(i).getDisplayId());
+            mTargetDisplays.add(dc);
+            if (dc.isKeyguardLocked()) {
+                mFlags |= TRANSIT_FLAG_KEYGUARD_LOCKED;
+            }
+        }
+
         if (markBackAnimated) {
             mController.mAtm.mBackNavigationController.clearBackAnimations(mStartTransaction);
         }
@@ -1129,9 +1129,12 @@
         }
 
         // TODO(b/188669821): Move to animation impl in shell.
-        handleLegacyRecentsStartBehavior(dc, info);
+        for (int i = 0; i < mTargetDisplays.size(); ++i) {
+            handleLegacyRecentsStartBehavior(mTargetDisplays.get(i), info);
+            if (mRecentsDisplayId != INVALID_DISPLAY) break;
+        }
 
-        handleNonAppWindowsInTransition(dc, mType, mFlags);
+        handleNonAppWindowsInTransition(mType, mFlags);
 
         // The callback is only populated for custom activity-level client animations
         sendRemoteCallback(mClientAnimationStartCallback);
@@ -1195,11 +1198,14 @@
 
         // This is non-null only if display has changes. It handles the visible windows that don't
         // need to be participated in the transition.
-        final AsyncRotationController controller = dc.getAsyncRotationController();
-        if (controller != null && containsChangeFor(dc, mTargets)) {
-            controller.setupStartTransaction(transaction);
+        for (int i = 0; i < mTargetDisplays.size(); ++i) {
+            final DisplayContent dc = mTargetDisplays.get(i);
+            final AsyncRotationController controller = dc.getAsyncRotationController();
+            if (controller != null && containsChangeFor(dc, mTargets)) {
+                controller.setupStartTransaction(transaction);
+            }
         }
-        buildFinishTransaction(mFinishTransaction, info.getRootLeash());
+        buildFinishTransaction(mFinishTransaction, info);
         if (mController.getTransitionPlayer() != null && mIsPlayerEnabled) {
             mController.dispatchLegacyAppTransitionStarting(info, mStatusBarTransitionDelay);
             try {
@@ -1220,10 +1226,13 @@
                 // client, we should finish and apply it here so the transactions aren't lost.
                 postCleanupOnFailure();
             }
-            final AccessibilityController accessibilityController =
-                    dc.mWmService.mAccessibilityController;
-            if (accessibilityController.hasCallbacks()) {
-                accessibilityController.onWMTransition(dc.getDisplayId(), mType);
+            for (int i = 0; i < mTargetDisplays.size(); ++i) {
+                final DisplayContent dc = mTargetDisplays.get(i);
+                final AccessibilityController accessibilityController =
+                        dc.mWmService.mAccessibilityController;
+                if (accessibilityController.hasCallbacks()) {
+                    accessibilityController.onWMTransition(dc.getDisplayId(), mType);
+                }
             }
         } else {
             // No player registered or it's not enabled, so just finish/apply immediately
@@ -1300,16 +1309,15 @@
         if ((mFlags & TRANSIT_FLAG_IS_RECENTS) == 0) {
             return;
         }
-        mRecentsDisplayId = dc.mDisplayId;
 
         // Recents has an input-consumer to grab input from the "live tile" app. Set that up here
         final InputConsumerImpl recentsAnimationInputConsumer =
                 dc.getInputMonitor().getInputConsumer(INPUT_CONSUMER_RECENTS_ANIMATION);
+        ActivityRecord recentsActivity = null;
         if (recentsAnimationInputConsumer != null) {
             // find the top-most going-away activity and the recents activity. The top-most
             // is used as layer reference while the recents is used for registering the consumer
             // override.
-            ActivityRecord recentsActivity = null;
             ActivityRecord topActivity = null;
             for (int i = 0; i < info.getChanges().size(); ++i) {
                 final TransitionInfo.Change change = info.getChanges().get(i);
@@ -1333,6 +1341,12 @@
             }
         }
 
+        if (recentsActivity == null) {
+            // No recents activity on `dc`, its probably on a different display.
+            return;
+        }
+        mRecentsDisplayId = dc.mDisplayId;
+
         // The rest of this function handles nav-bar reparenting
 
         if (!dc.getDisplayPolicy().shouldAttachNavBarToAppDuringTransition()
@@ -1427,7 +1441,7 @@
         }
     }
 
-    private void handleNonAppWindowsInTransition(@NonNull DisplayContent dc,
+    private void handleNonAppWindowsInTransition(
             @TransitionType int transit, @TransitionFlags int flags) {
         if ((flags & TRANSIT_FLAG_KEYGUARD_LOCKED) != 0) {
             // If the occlusion changed but the transition isn't an occlude/unocclude transition,
@@ -1805,6 +1819,41 @@
         return wc instanceof DisplayContent;
     }
 
+    private static int getDisplayId(@NonNull WindowContainer wc) {
+        return wc.getDisplayContent() != null
+                ? wc.getDisplayContent().getDisplayId() : INVALID_DISPLAY;
+    }
+
+    @VisibleForTesting
+    static void calculateTransitionRoots(@NonNull TransitionInfo outInfo,
+            ArrayList<ChangeInfo> sortedTargets,
+            @NonNull SurfaceControl.Transaction startT) {
+        // There needs to be a root on each display.
+        for (int i = 0; i < sortedTargets.size(); ++i) {
+            final WindowContainer<?> wc = sortedTargets.get(i).mContainer;
+            // Don't include wallpapers since they are in a different DA.
+            if (isWallpaper(wc)) continue;
+            final int endDisplayId = getDisplayId(wc);
+            if (endDisplayId < 0) continue;
+
+            // Check if Root was already created for this display with a higher-Z window
+            if (outInfo.findRootIndex(endDisplayId) >= 0) continue;
+
+            WindowContainer<?> ancestor = findCommonAncestor(sortedTargets, wc);
+
+            // Make leash based on highest (z-order) direct child of ancestor with a participant.
+            WindowContainer leashReference = wc;
+            while (leashReference.getParent() != ancestor) {
+                leashReference = leashReference.getParent();
+            }
+            final SurfaceControl rootLeash = leashReference.makeAnimationLeash().setName(
+                    "Transition Root: " + leashReference.getName()).build();
+            startT.setLayer(rootLeash, leashReference.getLastLayer());
+            outInfo.addRootLeash(endDisplayId, rootLeash,
+                    ancestor.getBounds().left, ancestor.getBounds().top);
+        }
+    }
+
     /**
      * Construct a TransitionInfo object from a set of targets and changes. Also populates the
      * root surface.
@@ -1815,37 +1864,13 @@
     @NonNull
     static TransitionInfo calculateTransitionInfo(@TransitionType int type, int flags,
             ArrayList<ChangeInfo> sortedTargets,
-            @Nullable SurfaceControl.Transaction startT) {
+            @NonNull SurfaceControl.Transaction startT) {
         final TransitionInfo out = new TransitionInfo(type, flags);
-
-        WindowContainer<?> topApp = null;
-        for (int i = 0; i < sortedTargets.size(); i++) {
-            final WindowContainer<?> wc = sortedTargets.get(i).mContainer;
-            if (!isWallpaper(wc)) {
-                topApp = wc;
-                break;
-            }
-        }
-        if (topApp == null) {
-            out.setRootLeash(new SurfaceControl(), 0, 0);
+        calculateTransitionRoots(out, sortedTargets, startT);
+        if (out.getRootCount() == 0) {
             return out;
         }
 
-        WindowContainer<?> ancestor = findCommonAncestor(sortedTargets, topApp);
-
-        // Make leash based on highest (z-order) direct child of ancestor with a participant.
-        // TODO(b/261418859): Handle the case when the target contains window containers which
-        // belong to a different display. As a workaround we use topApp, from which wallpaper
-        // window container is removed, instead of sortedTargets here.
-        WindowContainer leashReference = topApp;
-        while (leashReference.getParent() != ancestor) {
-            leashReference = leashReference.getParent();
-        }
-        final SurfaceControl rootLeash = leashReference.makeAnimationLeash().setName(
-                "Transition Root: " + leashReference.getName()).build();
-        startT.setLayer(rootLeash, leashReference.getLastLayer());
-        out.setRootLeash(rootLeash, ancestor.getBounds().left, ancestor.getBounds().top);
-
         // Convert all the resolved ChangeInfos into TransactionInfo.Change objects in order.
         final int count = sortedTargets.size();
         for (int i = 0; i < count; ++i) {
@@ -1865,6 +1890,7 @@
             change.setMode(info.getTransitMode(target));
             change.setStartAbsBounds(info.mAbsoluteBounds);
             change.setFlags(info.getChangeFlags(target));
+            change.setDisplayId(info.mDisplayId, getDisplayId(target));
 
             final Task task = target.asTask();
             final TaskFragment taskFragment = target.asTaskFragment();
@@ -1951,6 +1977,15 @@
         }
 
         TransitionInfo.AnimationOptions animOptions = null;
+
+        // Check if the top-most app is an activity (ie. activity->activity). If so, make sure to
+        // honor its custom transition options.
+        WindowContainer<?> topApp = null;
+        for (int i = 0; i < sortedTargets.size(); i++) {
+            if (isWallpaper(sortedTargets.get(i).mContainer)) continue;
+            topApp = sortedTargets.get(i).mContainer;
+            break;
+        }
         if (topApp.asActivityRecord() != null) {
             final ActivityRecord topActivity = topApp.asActivityRecord();
             animOptions = addCustomActivityTransition(topActivity, true/* open */, null);
@@ -2001,14 +2036,15 @@
     private static WindowContainer<?> findCommonAncestor(
             @NonNull ArrayList<ChangeInfo> targets,
             @NonNull WindowContainer<?> topApp) {
+        final int displayId = getDisplayId(topApp);
         WindowContainer<?> ancestor = topApp.getParent();
         // Go up ancestor parent chain until all targets are descendants. Ancestor should never be
         // null because all targets are attached.
         for (int i = targets.size() - 1; i >= 0; i--) {
             final ChangeInfo change = targets.get(i);
             final WindowContainer wc = change.mContainer;
-            if (isWallpaper(wc)) {
-                // Skip the non-app window.
+            if (isWallpaper(wc) || getDisplayId(wc) != displayId) {
+                // Skip the non-app window or windows on a different display
                 continue;
             }
             while (!wc.isDescendantOf(ancestor)) {
@@ -2184,6 +2220,7 @@
         final Rect mAbsoluteBounds = new Rect();
         boolean mShowWallpaper;
         int mRotation = ROTATION_UNDEFINED;
+        int mDisplayId = -1;
         @ActivityInfo.Config int mKnownConfigChanges;
 
         /** These are just extra info. They aren't used for change-detection. */
@@ -2201,6 +2238,7 @@
             mShowWallpaper = origState.showWallpaper();
             mRotation = origState.getWindowConfiguration().getRotation();
             mStartParent = origState.getParent();
+            mDisplayId = getDisplayId(origState);
         }
 
         @VisibleForTesting
@@ -2232,7 +2270,8 @@
                     // assume no change in windowing-mode.
                     || (mWindowingMode != 0 && mContainer.getWindowingMode() != mWindowingMode)
                     || !mContainer.getBounds().equals(mAbsoluteBounds)
-                    || mRotation != mContainer.getWindowConfiguration().getRotation();
+                    || mRotation != mContainer.getWindowConfiguration().getRotation()
+                    || mDisplayId != getDisplayId(mContainer);
         }
 
         @TransitionInfo.TransitionMode
@@ -2272,6 +2311,10 @@
                             .isMonitorTransitionTarget(topActivity)) {
                         flags |= TransitionInfo.FLAG_BACK_GESTURE_ANIMATED;
                     }
+                    if (topActivity != null && topActivity.mLaunchTaskBehind) {
+                        Slog.e(TAG, "Unexpected launch-task-behind operation in shell transition");
+                        flags |= FLAG_TASK_LAUNCHING_BEHIND;
+                    }
                 } else {
                     if (task.mAtmService.mBackNavigationController
                             .isMonitorTransitionTarget(task)) {
diff --git a/services/core/java/com/android/server/wm/TransitionController.java b/services/core/java/com/android/server/wm/TransitionController.java
index 6c951bf..bacc6e6 100644
--- a/services/core/java/com/android/server/wm/TransitionController.java
+++ b/services/core/java/com/android/server/wm/TransitionController.java
@@ -51,7 +51,6 @@
 import android.window.WindowContainerTransaction;
 
 import com.android.internal.annotations.GuardedBy;
-import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.protolog.ProtoLogGroup;
 import com.android.internal.protolog.common.ProtoLog;
 import com.android.server.FgThread;
@@ -200,12 +199,6 @@
 
     /** Starts Collecting */
     void moveToCollecting(@NonNull Transition transition) {
-        moveToCollecting(transition, SYNC_METHOD);
-    }
-
-    /** Starts Collecting */
-    @VisibleForTesting
-    void moveToCollecting(@NonNull Transition transition, int method) {
         if (mCollectingTransition != null) {
             throw new IllegalStateException("Simultaneous transition collection not supported.");
         }
@@ -219,7 +212,7 @@
         // Distinguish change type because the response time is usually expected to be not too long.
         final long timeoutMs =
                 transition.mType == TRANSIT_CHANGE ? CHANGE_TIMEOUT_MS : DEFAULT_TIMEOUT_MS;
-        mCollectingTransition.startCollecting(timeoutMs, method);
+        mCollectingTransition.startCollecting(timeoutMs);
         ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "Start collecting in Transition: %s",
                 mCollectingTransition);
         dispatchLegacyAppTransitionPending();
@@ -492,6 +485,15 @@
         } else {
             newTransition = requestStartTransition(createTransition(type, flags),
                     trigger != null ? trigger.asTask() : null, remoteTransition, displayChange);
+            if (newTransition != null && displayChange != null && (displayChange.getStartRotation()
+                    + displayChange.getEndRotation()) % 2 == 0) {
+                // 180 degrees rotation change may not change screen size. So the clients may draw
+                // some frames before and after the display projection transaction is applied by the
+                // remote player. That may cause some buffers to show in different rotation. So use
+                // sync method to pause clients drawing until the projection transaction is applied.
+                mAtm.mWindowManager.mSyncEngine.setSyncMethod(newTransition.getSyncId(),
+                        BLASTSyncEngine.METHOD_BLAST);
+            }
         }
         if (trigger != null) {
             if (isExistenceType(type)) {
diff --git a/services/core/java/com/android/server/wm/WindowManagerShellCommand.java b/services/core/java/com/android/server/wm/WindowManagerShellCommand.java
index cfb3c6c..8c2dd2d 100644
--- a/services/core/java/com/android/server/wm/WindowManagerShellCommand.java
+++ b/services/core/java/com/android/server/wm/WindowManagerShellCommand.java
@@ -42,6 +42,7 @@
 import android.util.DisplayMetrics;
 import android.util.Pair;
 import android.view.Display;
+import android.view.IWindow;
 import android.view.IWindowManager;
 import android.view.ViewDebug;
 
@@ -569,6 +570,22 @@
         return 0;
     }
 
+    private void dumpLocalWindowAsync(IWindow client, ParcelFileDescriptor pfd) {
+        // Make it asynchronous to avoid writer from being blocked
+        // by waiting for the buffer to be consumed in the same process.
+        IoThread.getExecutor().execute(() -> {
+            synchronized (mInternal.mGlobalLock) {
+                try {
+                    client.executeCommand(ViewDebug.REMOTE_COMMAND_DUMP_ENCODED, null, pfd);
+                } catch (Exception e) {
+                    // Ignore RemoteException for local call. Just print trace for other
+                    // exceptions caused by RC with tolerable low possibility.
+                    e.printStackTrace();
+                }
+            }
+        });
+    }
+
     private int runDumpVisibleWindowViews(PrintWriter pw) {
         if (!mInternal.checkCallingPermission(android.Manifest.permission.DUMP,
                 "runDumpVisibleWindowViews()")) {
@@ -591,16 +608,7 @@
                             pipe = new ByteTransferPipe();
                             final ParcelFileDescriptor pfd = pipe.getWriteFd();
                             if (w.isClientLocal()) {
-                                // Make it asynchronous to avoid writer from being blocked
-                                // by waiting for the buffer to be consumed in the same process.
-                                IoThread.getExecutor().execute(() -> {
-                                    try {
-                                        w.mClient.executeCommand(
-                                                ViewDebug.REMOTE_COMMAND_DUMP_ENCODED, null, pfd);
-                                    } catch (RemoteException e) {
-                                        // Ignore for local call.
-                                    }
-                                });
+                                dumpLocalWindowAsync(w.mClient, pfd);
                             } else {
                                 w.mClient.executeCommand(
                                         ViewDebug.REMOTE_COMMAND_DUMP_ENCODED, null, pfd);
diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java
index 495d7ce4..c3c87af 100644
--- a/services/core/java/com/android/server/wm/WindowOrganizerController.java
+++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java
@@ -1648,7 +1648,7 @@
     private BLASTSyncEngine.SyncGroup prepareSyncWithOrganizer(
             IWindowContainerTransactionCallback callback) {
         final BLASTSyncEngine.SyncGroup s = mService.mWindowManager.mSyncEngine
-                .prepareSyncSet(this, "", BLASTSyncEngine.METHOD_BLAST);
+                .prepareSyncSet(this, "Organizer");
         mTransactionCallbacksByPendingSyncId.put(s.mSyncId, callback);
         return s;
     }
diff --git a/services/core/java/com/android/server/wm/WindowProcessController.java b/services/core/java/com/android/server/wm/WindowProcessController.java
index 694f1be..834b708 100644
--- a/services/core/java/com/android/server/wm/WindowProcessController.java
+++ b/services/core/java/com/android/server/wm/WindowProcessController.java
@@ -1381,6 +1381,13 @@
     }
 
     /**
+     * Destroys the WindwoProcessController, after the process has been removed.
+     */
+    void destroy() {
+        unregisterConfigurationListeners();
+    }
+
+    /**
      * Check if activity configuration override for the activity process needs an update and perform
      * if needed. By default we try to override the process configuration to match the top activity
      * config to increase app compatibility with multi-window and multi-display. The process will
diff --git a/services/core/java/com/android/server/wm/WindowProcessControllerMap.java b/services/core/java/com/android/server/wm/WindowProcessControllerMap.java
index 2767972..424b043 100644
--- a/services/core/java/com/android/server/wm/WindowProcessControllerMap.java
+++ b/services/core/java/com/android/server/wm/WindowProcessControllerMap.java
@@ -19,8 +19,8 @@
 import android.util.ArraySet;
 import android.util.SparseArray;
 
-import java.util.Map;
 import java.util.HashMap;
+import java.util.Map;
 
 final class WindowProcessControllerMap {
 
@@ -67,6 +67,7 @@
             mPidMap.remove(pid);
             // remove process from mUidMap
             removeProcessFromUidMap(proc);
+            proc.destroy();
         }
     }
 
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index f86b997..d6c0311 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -221,8 +221,6 @@
 import android.view.IWindowFocusObserver;
 import android.view.IWindowId;
 import android.view.InputChannel;
-import android.view.InputEvent;
-import android.view.InputEventReceiver;
 import android.view.InputWindowHandle;
 import android.view.InsetsSource;
 import android.view.InsetsState;
@@ -572,12 +570,6 @@
     boolean mRemoveOnExit;
 
     /**
-     * Whether the app died while it was visible, if true we might need
-     * to continue to show it until it's restarted.
-     */
-    boolean mAppDied;
-
-    /**
      * Set when the orientation is changing and this window has not yet
      * been updated for the new orientation.
      */
@@ -760,7 +752,6 @@
      */
     private InsetsState mFrozenInsetsState;
 
-    private static final float DEFAULT_DIM_AMOUNT_DEAD_WINDOW = 0.5f;
     private KeyInterceptionInfo mKeyInterceptionInfo;
 
     /**
@@ -1504,13 +1495,6 @@
                 }
             }
 
-            // If it's a dead window left on screen, and the configuration changed, there is nothing
-            // we can do about it. Remove the window now.
-            if (mActivityRecord != null && mAppDied) {
-                mActivityRecord.removeDeadWindows();
-                return;
-            }
-
             onResizeHandled();
             mWmService.makeWindowFreezingScreenIfNeededLocked(this);
 
@@ -2009,7 +1993,7 @@
     boolean isInteresting() {
         final RecentsAnimationController recentsAnimationController =
                 mWmService.getRecentsAnimationController();
-        return mActivityRecord != null && !mAppDied
+        return mActivityRecord != null
                 && (!mActivityRecord.isFreezingScreen() || !mAppFreezing)
                 && mViewVisibility == View.VISIBLE
                 && (recentsAnimationController == null
@@ -2448,11 +2432,6 @@
 
     @Override
     void removeIfPossible() {
-        super.removeIfPossible();
-        removeIfPossible(false /*keepVisibleDeadWindow*/);
-    }
-
-    private void removeIfPossible(boolean keepVisibleDeadWindow) {
         mWindowRemovalAllowed = true;
         ProtoLog.v(WM_DEBUG_ADD_REMOVE,
                 "removeIfPossible: %s callers=%s", this, Debug.getCallers(5));
@@ -2527,21 +2506,6 @@
                 // If we are not currently running the exit animation, we need to see about starting one
                 wasVisible = isVisible();
 
-                if (keepVisibleDeadWindow) {
-                    ProtoLog.v(WM_DEBUG_ADD_REMOVE,
-                            "Not removing %s because app died while it's visible", this);
-
-                    mAppDied = true;
-                    setDisplayLayoutNeeded();
-                    mWmService.mWindowPlacerLocked.performSurfacePlacement();
-
-                    // Set up a replacement input channel since the app is now dead.
-                    // We need to catch tapping on the dead window to restart the app.
-                    openInputChannel(null);
-                    displayContent.getInputMonitor().updateInputWindowsLw(true /*force*/);
-                    return;
-                }
-
                 // Remove immediately if there is display transition because the animation is
                 // usually unnoticeable (e.g. covered by rotation animation) and the animation
                 // bounds could be inconsistent, such as depending on when the window applies
@@ -2715,19 +2679,7 @@
                 || (isVisible() && mActivityRecord != null && mActivityRecord.isVisible());
     }
 
-    private final class DeadWindowEventReceiver extends InputEventReceiver {
-        DeadWindowEventReceiver(InputChannel inputChannel) {
-            super(inputChannel, mWmService.mH.getLooper());
-        }
-        @Override
-        public void onInputEvent(InputEvent event) {
-            finishInputEvent(event, true);
-        }
-    }
-    /** Fake event receiver for windows that died visible. */
-    private DeadWindowEventReceiver mDeadWindowEventReceiver;
-
-    void openInputChannel(InputChannel outInputChannel) {
+    void openInputChannel(@NonNull InputChannel outInputChannel) {
         if (mInputChannel != null) {
             throw new IllegalStateException("Window already has an input channel.");
         }
@@ -2736,14 +2688,7 @@
         mInputChannelToken = mInputChannel.getToken();
         mInputWindowHandle.setToken(mInputChannelToken);
         mWmService.mInputToWindowMap.put(mInputChannelToken, this);
-        if (outInputChannel != null) {
-            mInputChannel.copyTo(outInputChannel);
-        } else {
-            // If the window died visible, we setup a fake input channel, so that taps
-            // can still detected by input monitor channel, and we can relaunch the app.
-            // Create fake event receiver that simply reports all events as handled.
-            mDeadWindowEventReceiver = new DeadWindowEventReceiver(mInputChannel);
-        }
+        mInputChannel.copyTo(outInputChannel);
     }
 
     /**
@@ -2754,10 +2699,6 @@
     }
 
     void disposeInputChannel() {
-        if (mDeadWindowEventReceiver != null) {
-            mDeadWindowEventReceiver.dispose();
-            mDeadWindowEventReceiver = null;
-        }
         if (mInputChannelToken != null) {
             // Unregister server channel first otherwise it complains about broken channel.
             mWmService.mInputManager.removeInputChannel(mInputChannelToken);
@@ -3084,11 +3025,10 @@
                             .windowForClientLocked(mSession, mClient, false);
                     Slog.i(TAG, "WIN DEATH: " + win);
                     if (win != null) {
-                        final DisplayContent dc = getDisplayContent();
                         if (win.mActivityRecord != null && win.mActivityRecord.findMainWindow() == win) {
                             mWmService.mTaskSnapshotController.onAppDied(win.mActivityRecord);
                         }
-                        win.removeIfPossible(shouldKeepVisibleDeadAppWindow());
+                        win.removeIfPossible();
                     } else if (mHasSurface) {
                         Slog.e(TAG, "!!! LEAK !!! Window removed but surface still valid.");
                         WindowState.this.removeIfPossible();
@@ -3100,32 +3040,6 @@
         }
     }
 
-    /**
-     * Returns true if this window is visible and belongs to a dead app and shouldn't be removed,
-     * because we want to preserve its location on screen to be re-activated later when the user
-     * interacts with it.
-     */
-    private boolean shouldKeepVisibleDeadAppWindow() {
-        if (!isVisible() || mActivityRecord == null || !mActivityRecord.isClientVisible()) {
-            // Not a visible app window or the app isn't dead.
-            return false;
-        }
-
-        if (mAttrs.token != mClient.asBinder()) {
-            // The window was add by a client using another client's app token. We don't want to
-            // keep the dead window around for this case since this is meant for 'real' apps.
-            return false;
-        }
-
-        if (mAttrs.type == TYPE_APPLICATION_STARTING) {
-            // We don't keep starting windows since they were added by the window manager before
-            // the app even launched.
-            return false;
-        }
-
-        return getWindowConfiguration().keepVisibleDeadAppWindowOnScreen();
-    }
-
     /** Returns {@code true} if this window desires key events. */
     boolean canReceiveKeys() {
         return canReceiveKeys(false /* fromUserTouch */);
@@ -3972,7 +3886,7 @@
     @Override
     public void notifyInsetsControlChanged() {
         ProtoLog.d(WM_DEBUG_WINDOW_INSETS, "notifyInsetsControlChanged for %s ", this);
-        if (mAppDied || mRemoved) {
+        if (mRemoved) {
             return;
         }
         final InsetsStateController stateController =
@@ -4278,7 +4192,6 @@
             pw.println(prefix + "mToken=" + mToken);
             if (mActivityRecord != null) {
                 pw.println(prefix + "mActivityRecord=" + mActivityRecord);
-                pw.print(prefix + "mAppDied=" + mAppDied);
                 pw.print(prefix + "drawnStateEvaluated=" + getDrawnStateEvaluated());
                 pw.println(prefix + "mightAffectAllDrawn=" + mightAffectAllDrawn());
             }
@@ -5407,10 +5320,7 @@
     }
 
     private void applyDims() {
-        if (!mAnimatingExit && mAppDied) {
-            mIsDimming = true;
-            getDimmer().dimAbove(getSyncTransaction(), this, DEFAULT_DIM_AMOUNT_DEAD_WINDOW);
-        } else if (((mAttrs.flags & FLAG_DIM_BEHIND) != 0 || shouldDrawBlurBehind())
+        if (((mAttrs.flags & FLAG_DIM_BEHIND) != 0 || shouldDrawBlurBehind())
                    && isVisibleNow() && !mHidden) {
             // Only show the Dimmer when the following is satisfied:
             // 1. The window has the flag FLAG_DIM_BEHIND or blur behind is requested
diff --git a/services/credentials/java/com/android/server/credentials/ClearRequestSession.java b/services/credentials/java/com/android/server/credentials/ClearRequestSession.java
index 94260e20..e09c0a2 100644
--- a/services/credentials/java/com/android/server/credentials/ClearRequestSession.java
+++ b/services/credentials/java/com/android/server/credentials/ClearRequestSession.java
@@ -122,7 +122,7 @@
     private void respondToClientWithResponseAndFinish() {
         Log.i(TAG, "respondToClientWithResponseAndFinish");
         if (isSessionCancelled()) {
-            mChosenProviderMetric.setChosenProviderStatus(
+            mChosenProviderFinalPhaseMetric.setChosenProviderStatus(
                     ProviderStatusForMetrics.FINAL_SUCCESS.getMetricCode());
             logApiCall(ApiName.CLEAR_CREDENTIAL, /* apiStatus */
                     ApiStatus.CLIENT_CANCELED);
@@ -134,7 +134,7 @@
             logApiCall(ApiName.CLEAR_CREDENTIAL, /* apiStatus */
                     ApiStatus.SUCCESS);
         } catch (RemoteException e) {
-            mChosenProviderMetric.setChosenProviderStatus(
+            mChosenProviderFinalPhaseMetric.setChosenProviderStatus(
                     ProviderStatusForMetrics.FINAL_FAILURE.getMetricCode());
             Log.i(TAG, "Issue while propagating the response to the client");
             logApiCall(ApiName.CLEAR_CREDENTIAL, /* apiStatus */
diff --git a/services/credentials/java/com/android/server/credentials/CreateRequestSession.java b/services/credentials/java/com/android/server/credentials/CreateRequestSession.java
index 47b8c7d..793d83e 100644
--- a/services/credentials/java/com/android/server/credentials/CreateRequestSession.java
+++ b/services/credentials/java/com/android/server/credentials/CreateRequestSession.java
@@ -83,6 +83,7 @@
 
     @Override
     protected void launchUiWithProviderData(ArrayList<ProviderData> providerDataList) {
+        mChosenProviderFinalPhaseMetric.setUiCallStartTimeNanoseconds(System.nanoTime());
         try {
             mClientCallback.onPendingIntent(mCredentialManagerUi.createPendingIntent(
                     RequestInfo.newCreateRequestInfo(
@@ -90,6 +91,7 @@
                             mClientAppInfo.getPackageName()),
                     providerDataList));
         } catch (RemoteException e) {
+            mChosenProviderFinalPhaseMetric.setUiReturned(false);
             respondToClientWithErrorAndFinish(
                     CreateCredentialException.TYPE_UNKNOWN,
                     "Unable to invoke selector");
@@ -99,14 +101,16 @@
     @Override
     public void onFinalResponseReceived(ComponentName componentName,
             @Nullable CreateCredentialResponse response) {
+        mChosenProviderFinalPhaseMetric.setUiReturned(true);
+        mChosenProviderFinalPhaseMetric.setUiCallEndTimeNanoseconds(System.nanoTime());
         Log.i(TAG, "onFinalCredentialReceived from: " + componentName.flattenToString());
         setChosenMetric(componentName);
         if (response != null) {
-            mChosenProviderMetric.setChosenProviderStatus(
+            mChosenProviderFinalPhaseMetric.setChosenProviderStatus(
                     ProviderStatusForMetrics.FINAL_SUCCESS.getMetricCode());
             respondToClientWithResponseAndFinish(response);
         } else {
-            mChosenProviderMetric.setChosenProviderStatus(
+            mChosenProviderFinalPhaseMetric.setChosenProviderStatus(
                     ProviderStatusForMetrics.FINAL_FAILURE.getMetricCode());
             respondToClientWithErrorAndFinish(CreateCredentialException.TYPE_NO_CREATE_OPTIONS,
                     "Invalid response");
@@ -138,6 +142,8 @@
 
     private void respondToClientWithResponseAndFinish(CreateCredentialResponse response) {
         Log.i(TAG, "respondToClientWithResponseAndFinish");
+        // TODO immediately add exception bit to chosen provider and do final emits across all
+        // including sequenceCounter!
         if (mRequestSessionStatus == RequestSessionStatus.COMPLETE) {
             Log.i(TAG, "Request has already been completed. This is strange.");
             return;
@@ -162,6 +168,7 @@
 
     private void respondToClientWithErrorAndFinish(String errorType, String errorMsg) {
         Log.i(TAG, "respondToClientWithErrorAndFinish");
+
         if (mRequestSessionStatus == RequestSessionStatus.COMPLETE) {
             Log.i(TAG, "Request has already been completed. This is strange.");
             return;
diff --git a/services/credentials/java/com/android/server/credentials/CredentialManagerService.java b/services/credentials/java/com/android/server/credentials/CredentialManagerService.java
index 10d3dc0..4c5c366 100644
--- a/services/credentials/java/com/android/server/credentials/CredentialManagerService.java
+++ b/services/credentials/java/com/android/server/credentials/CredentialManagerService.java
@@ -41,6 +41,7 @@
 import android.credentials.ICreateCredentialCallback;
 import android.credentials.ICredentialManager;
 import android.credentials.IGetCredentialCallback;
+import android.credentials.IGetPendingCredentialCallback;
 import android.credentials.ISetEnabledProvidersCallback;
 import android.credentials.RegisterCredentialDescriptionRequest;
 import android.credentials.UnregisterCredentialDescriptionRequest;
@@ -438,6 +439,17 @@
             return cancelTransport;
         }
 
+        @Override
+        public ICancellationSignal executeGetPendingCredential(
+                GetCredentialRequest request,
+                IGetPendingCredentialCallback callback,
+                final String callingPackage) {
+            // TODO(b/273308895): implement
+
+            ICancellationSignal cancelTransport = CancellationSignal.createTransport();
+            return cancelTransport;
+        }
+
         private void processGetCredential(
                 GetCredentialRequest request,
                 IGetCredentialCallback callback,
diff --git a/services/credentials/java/com/android/server/credentials/GetRequestSession.java b/services/credentials/java/com/android/server/credentials/GetRequestSession.java
index 8e90c09..00fbbba 100644
--- a/services/credentials/java/com/android/server/credentials/GetRequestSession.java
+++ b/services/credentials/java/com/android/server/credentials/GetRequestSession.java
@@ -82,12 +82,14 @@
 
     @Override
     protected void launchUiWithProviderData(ArrayList<ProviderData> providerDataList) {
+        mChosenProviderFinalPhaseMetric.setUiCallStartTimeNanoseconds(System.nanoTime());
         try {
             mClientCallback.onPendingIntent(mCredentialManagerUi.createPendingIntent(
                     RequestInfo.newGetRequestInfo(
                     mRequestId, mClientRequest, mClientAppInfo.getPackageName()),
                     providerDataList));
         } catch (RemoteException e) {
+            mChosenProviderFinalPhaseMetric.setUiReturned(false);
             respondToClientWithErrorAndFinish(
                     GetCredentialException.TYPE_UNKNOWN, "Unable to instantiate selector");
         }
@@ -96,14 +98,16 @@
     @Override
     public void onFinalResponseReceived(ComponentName componentName,
             @Nullable GetCredentialResponse response) {
+        mChosenProviderFinalPhaseMetric.setUiReturned(true);
+        mChosenProviderFinalPhaseMetric.setUiCallEndTimeNanoseconds(System.nanoTime());
         Log.i(TAG, "onFinalCredentialReceived from: " + componentName.flattenToString());
         setChosenMetric(componentName);
         if (response != null) {
-            mChosenProviderMetric.setChosenProviderStatus(
+            mChosenProviderFinalPhaseMetric.setChosenProviderStatus(
                     ProviderStatusForMetrics.FINAL_SUCCESS.getMetricCode());
             respondToClientWithResponseAndFinish(response);
         } else {
-            mChosenProviderMetric.setChosenProviderStatus(
+            mChosenProviderFinalPhaseMetric.setChosenProviderStatus(
                     ProviderStatusForMetrics.FINAL_FAILURE.getMetricCode());
             respondToClientWithErrorAndFinish(GetCredentialException.TYPE_NO_CREDENTIAL,
                     "Invalid response from provider");
diff --git a/services/credentials/java/com/android/server/credentials/MetricUtilities.java b/services/credentials/java/com/android/server/credentials/MetricUtilities.java
index 1b3e37a..ed139b5 100644
--- a/services/credentials/java/com/android/server/credentials/MetricUtilities.java
+++ b/services/credentials/java/com/android/server/credentials/MetricUtilities.java
@@ -25,7 +25,7 @@
 import com.android.server.credentials.metrics.ApiName;
 import com.android.server.credentials.metrics.ApiStatus;
 import com.android.server.credentials.metrics.CandidatePhaseMetric;
-import com.android.server.credentials.metrics.ChosenProviderMetric;
+import com.android.server.credentials.metrics.ChosenProviderFinalPhaseMetric;
 import com.android.server.credentials.metrics.InitialPhaseMetric;
 
 import java.util.Map;
@@ -89,11 +89,11 @@
      * @param apiStatus            the api status to log
      * @param providers            a map with known providers
      * @param callingUid           the calling UID of the client app
-     * @param chosenProviderMetric the metric data type of the final chosen provider
+     * @param chosenProviderFinalPhaseMetric the metric data type of the final chosen provider
      */
     protected static void logApiCalled(ApiName apiName, ApiStatus apiStatus,
             Map<String, ProviderSession> providers, int callingUid,
-            ChosenProviderMetric chosenProviderMetric) {
+            ChosenProviderFinalPhaseMetric chosenProviderFinalPhaseMetric) {
         try {
             var providerSessions = providers.values();
             int providerSize = providerSessions.size();
@@ -102,7 +102,7 @@
             int[] candidateStatusList = new int[providerSize];
             int index = 0;
             for (var session : providerSessions) {
-                CandidatePhaseMetric metric = session.mCandidateProviderMetric;
+                CandidatePhaseMetric metric = session.mCandidatePhasePerProviderMetric;
                 candidateUidList[index] = metric.getCandidateUid();
                 candidateQueryRoundTripTimeList[index] = metric.getQueryLatencyMicroseconds();
                 candidateStatusList[index] = metric.getProviderQueryStatus();
@@ -116,12 +116,13 @@
                     /* repeated_candidate_provider_round_trip_time_query_microseconds */
                     candidateQueryRoundTripTimeList,
                     /* repeated_candidate_provider_status */ candidateStatusList,
-                    /* chosen_provider_uid */ chosenProviderMetric.getChosenUid(),
+                    /* chosen_provider_uid */ chosenProviderFinalPhaseMetric.getChosenUid(),
                     /* chosen_provider_round_trip_time_overall_microseconds */
-                    chosenProviderMetric.getEntireProviderLatencyMicroseconds(),
+                    chosenProviderFinalPhaseMetric.getEntireProviderLatencyMicroseconds(),
                     /* chosen_provider_final_phase_microseconds (backwards compat only) */
                     DEFAULT_INT_32,
-                    /* chosen_provider_status */ chosenProviderMetric.getChosenProviderStatus());
+                    /* chosen_provider_status */ chosenProviderFinalPhaseMetric
+                            .getChosenProviderStatus());
         } catch (Exception e) {
             Log.w(TAG, "Unexpected error during metric logging: " + e);
         }
diff --git a/services/credentials/java/com/android/server/credentials/ProviderClearSession.java b/services/credentials/java/com/android/server/credentials/ProviderClearSession.java
index ab29acc..950cf4f 100644
--- a/services/credentials/java/com/android/server/credentials/ProviderClearSession.java
+++ b/services/credentials/java/com/android/server/credentials/ProviderClearSession.java
@@ -120,7 +120,14 @@
     @Override
     protected void invokeSession() {
         if (mRemoteCredentialService != null) {
-            mCandidateProviderMetric.setStartQueryTimeNanoseconds(System.nanoTime());
+            /*
+            InitialPhaseMetric initMetric = ((RequestSession)mCallbacks).initMetric;
+            TODO immediately once the other change patched through
+            mCandidateProviderMetric.setSessionId(initMetric
+            .mInitialPhaseMetric.getSessionId());
+            mCandidateProviderMetric.setStartTime(initMetric.getStartTime())
+             */
+            mCandidatePhasePerProviderMetric.setStartQueryTimeNanoseconds(System.nanoTime());
             mRemoteCredentialService.onClearCredentialState(mProviderRequest, this);
         }
     }
diff --git a/services/credentials/java/com/android/server/credentials/ProviderCreateSession.java b/services/credentials/java/com/android/server/credentials/ProviderCreateSession.java
index 8c9c6cf..3ec0fc0 100644
--- a/services/credentials/java/com/android/server/credentials/ProviderCreateSession.java
+++ b/services/credentials/java/com/android/server/credentials/ProviderCreateSession.java
@@ -226,7 +226,14 @@
     @Override
     protected void invokeSession() {
         if (mRemoteCredentialService != null) {
-            mCandidateProviderMetric.setStartQueryTimeNanoseconds(System.nanoTime());
+            /*
+            InitialPhaseMetric initMetric = ((RequestSession)mCallbacks).initMetric;
+            TODO immediately once the other change patched through
+            mCandidateProviderMetric.setSessionId(initMetric
+            .mInitialPhaseMetric.getSessionId());
+            mCandidateProviderMetric.setStartTime(initMetric.getStartTime())
+             */
+            mCandidatePhasePerProviderMetric.setStartQueryTimeNanoseconds(System.nanoTime());
             mRemoteCredentialService.onCreateCredential(mProviderRequest, this);
         }
     }
diff --git a/services/credentials/java/com/android/server/credentials/ProviderGetSession.java b/services/credentials/java/com/android/server/credentials/ProviderGetSession.java
index 851ccb3..ec8bf22 100644
--- a/services/credentials/java/com/android/server/credentials/ProviderGetSession.java
+++ b/services/credentials/java/com/android/server/credentials/ProviderGetSession.java
@@ -61,13 +61,13 @@
         RemoteCredentialService.ProviderCallbacks<BeginGetCredentialResponse> {
     private static final String TAG = "ProviderGetSession";
     // Key to be used as the entry key for an action entry
-    private static final String ACTION_ENTRY_KEY = "action_key";
+    public static final String ACTION_ENTRY_KEY = "action_key";
     // Key to be used as the entry key for the authentication entry
-    private static final String AUTHENTICATION_ACTION_ENTRY_KEY = "authentication_action_key";
+    public static final String AUTHENTICATION_ACTION_ENTRY_KEY = "authentication_action_key";
     // Key to be used as an entry key for a remote entry
-    private static final String REMOTE_ENTRY_KEY = "remote_entry_key";
+    public static final String REMOTE_ENTRY_KEY = "remote_entry_key";
     // Key to be used as an entry key for a credential entry
-    private static final String CREDENTIAL_ENTRY_KEY = "credential_key";
+    public static final String CREDENTIAL_ENTRY_KEY = "credential_key";
 
     @NonNull
     private final Map<String, CredentialOption> mBeginGetOptionToCredentialOptionMap;
@@ -269,7 +269,14 @@
     @Override
     protected void invokeSession() {
         if (mRemoteCredentialService != null) {
-            mCandidateProviderMetric.setStartQueryTimeNanoseconds(System.nanoTime());
+            /*
+            InitialPhaseMetric initMetric = ((RequestSession)mCallbacks).initMetric;
+            TODO immediately once the other change patched through
+            mCandidateProviderMetric.setSessionId(initMetric
+            .mInitialPhaseMetric.getSessionId());
+            mCandidateProviderMetric.setStartTime(initMetric.getStartTime())
+             */
+            mCandidatePhasePerProviderMetric.setStartQueryTimeNanoseconds(System.nanoTime());
             mRemoteCredentialService.onBeginGetCredential(mProviderRequest, this);
         }
     }
@@ -427,10 +434,13 @@
     private void onSetInitialRemoteResponse(BeginGetCredentialResponse response) {
         mProviderResponse = response;
         addToInitialRemoteResponse(response, /*isInitialResponse=*/true);
+        // Log the data.
         if (mProviderResponseDataHandler.isEmptyResponse(response)) {
             updateStatusAndInvokeCallback(Status.EMPTY_RESPONSE);
             return;
         }
+        // TODO immediately, add to Candidate Phase counts, repeat across all sessions
+        // Use sets to dedup type counts
         updateStatusAndInvokeCallback(Status.CREDENTIALS_RECEIVED);
     }
 
@@ -545,7 +555,7 @@
             String id = generateUniqueId();
             Entry entry = new Entry(REMOTE_ENTRY_KEY,
                     id, remoteEntry.getSlice(), setUpFillInIntentForRemoteEntry());
-            mUiRemoteEntry = new Pair<>(generateUniqueId(), new Pair<>(remoteEntry, entry));
+            mUiRemoteEntry = new Pair<>(id, new Pair<>(remoteEntry, entry));
         }
 
 
diff --git a/services/credentials/java/com/android/server/credentials/ProviderSession.java b/services/credentials/java/com/android/server/credentials/ProviderSession.java
index 03e2a32..77d4e77 100644
--- a/services/credentials/java/com/android/server/credentials/ProviderSession.java
+++ b/services/credentials/java/com/android/server/credentials/ProviderSession.java
@@ -38,6 +38,7 @@
 
 /**
  * Provider session storing the state of provider response and ui entries.
+ *
  * @param <T> The request to be sent to the provider
  * @param <R> The response to be expected from the provider
  */
@@ -46,21 +47,35 @@
 
     private static final String TAG = "ProviderSession";
 
-    @NonNull protected final Context mContext;
-    @NonNull protected final ComponentName mComponentName;
-    @Nullable protected final CredentialProviderInfo mProviderInfo;
-    @Nullable protected final RemoteCredentialService mRemoteCredentialService;
-    @NonNull protected final int mUserId;
-    @NonNull protected Status mStatus = Status.NOT_STARTED;
-    @Nullable protected final ProviderInternalCallback mCallbacks;
-    @Nullable protected Credential mFinalCredentialResponse;
-    @Nullable protected ICancellationSignal mProviderCancellationSignal;
-    @NonNull protected final T mProviderRequest;
-    @Nullable protected R mProviderResponse;
-    @NonNull protected Boolean mProviderResponseSet = false;
+    @NonNull
+    protected final Context mContext;
+    @NonNull
+    protected final ComponentName mComponentName;
+    @Nullable
+    protected final CredentialProviderInfo mProviderInfo;
+    @Nullable
+    protected final RemoteCredentialService mRemoteCredentialService;
+    @NonNull
+    protected final int mUserId;
+    @NonNull
+    protected Status mStatus = Status.NOT_STARTED;
+    @Nullable
+    protected final ProviderInternalCallback mCallbacks;
+    @Nullable
+    protected Credential mFinalCredentialResponse;
+    @Nullable
+    protected ICancellationSignal mProviderCancellationSignal;
+    @NonNull
+    protected final T mProviderRequest;
+    @Nullable
+    protected R mProviderResponse;
+    @NonNull
+    protected Boolean mProviderResponseSet = false;
     // Specific candidate provider metric for the provider this session handles
-    @Nullable protected CandidatePhaseMetric mCandidateProviderMetric;
-    @NonNull private int mProviderSessionUid;
+    @Nullable
+    protected CandidatePhaseMetric mCandidatePhasePerProviderMetric;
+    @NonNull
+    private int mProviderSessionUid;
 
     /**
      * Returns true if the given status reflects that the provider state is ready to be shown
@@ -100,6 +115,7 @@
      * Interface to be implemented by any class that wishes to get a callback when a particular
      * provider session's status changes. Typically, implemented by the {@link RequestSession}
      * class.
+     *
      * @param <V> the type of the final response expected
      */
     public interface ProviderInternalCallback<V> {
@@ -127,7 +143,7 @@
         mUserId = userId;
         mComponentName = componentName;
         mRemoteCredentialService = remoteCredentialService;
-        mCandidateProviderMetric = new CandidatePhaseMetric();
+        mCandidatePhasePerProviderMetric = new CandidatePhaseMetric();
         mProviderSessionUid = MetricUtilities.getPackageUid(mContext, mComponentName);
     }
 
@@ -158,7 +174,7 @@
     }
 
     public Credential getFinalCredentialResponse() {
-        return  mFinalCredentialResponse;
+        return mFinalCredentialResponse;
     }
 
     /** Propagates cancellation signal to the remote provider service. */
@@ -192,7 +208,7 @@
         return mRemoteCredentialService;
     }
 
-    /** Updates the status .*/
+    /** Updates the status . */
     protected void updateStatusAndInvokeCallback(@NonNull Status status) {
         setStatus(status);
         updateCandidateMetric(status);
@@ -200,15 +216,18 @@
     }
 
     private void updateCandidateMetric(Status status) {
-        mCandidateProviderMetric.setCandidateUid(mProviderSessionUid);
-        mCandidateProviderMetric
+        mCandidatePhasePerProviderMetric.setCandidateUid(mProviderSessionUid);
+        // TODO immediately update the candidate phase here to have more new data
+        mCandidatePhasePerProviderMetric
                 .setQueryFinishTimeNanoseconds(System.nanoTime());
         if (isTerminatingStatus(status)) {
-            mCandidateProviderMetric.setProviderQueryStatus(ProviderStatusForMetrics.QUERY_FAILURE
-                    .getMetricCode());
+            mCandidatePhasePerProviderMetric.setProviderQueryStatus(
+                    ProviderStatusForMetrics.QUERY_FAILURE
+                            .getMetricCode());
         } else if (isCompletionStatus(status)) {
-            mCandidateProviderMetric.setProviderQueryStatus(ProviderStatusForMetrics.QUERY_SUCCESS
-                    .getMetricCode());
+            mCandidatePhasePerProviderMetric.setProviderQueryStatus(
+                    ProviderStatusForMetrics.QUERY_SUCCESS
+                            .getMetricCode());
         }
     }
 
@@ -228,7 +247,8 @@
     }
 
     /** Update the response state stored with the provider session. */
-    @Nullable protected R getProviderResponse() {
+    @Nullable
+    protected R getProviderResponse() {
         return mProviderResponse;
     }
 
@@ -265,15 +285,20 @@
         return false;
     }
 
-    /** Should be overridden to prepare, and stores state for {@link ProviderData} to be
-     * shown on the UI. */
-    @Nullable protected abstract ProviderData prepareUiData();
+    /**
+     * Should be overridden to prepare, and stores state for {@link ProviderData} to be
+     * shown on the UI.
+     */
+    @Nullable
+    protected abstract ProviderData prepareUiData();
 
     /** Should be overridden to handle the selected entry from the UI. */
     protected abstract void onUiEntrySelected(String entryType, String entryId,
             ProviderPendingIntentResponse providerPendingIntentResponse);
 
-    /** Should be overridden to invoke the provider at a defined location. Helpful for
-     * situations such as metric generation. */
+    /**
+     * Should be overridden to invoke the provider at a defined location. Helpful for
+     * situations such as metric generation.
+     */
     protected abstract void invokeSession();
 }
diff --git a/services/credentials/java/com/android/server/credentials/RequestSession.java b/services/credentials/java/com/android/server/credentials/RequestSession.java
index edddba0..ed42bb2 100644
--- a/services/credentials/java/com/android/server/credentials/RequestSession.java
+++ b/services/credentials/java/com/android/server/credentials/RequestSession.java
@@ -19,6 +19,7 @@
 import static com.android.server.credentials.MetricUtilities.logApiCalled;
 
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.annotation.UserIdInt;
 import android.content.ComponentName;
 import android.content.Context;
@@ -36,12 +37,15 @@
 import com.android.internal.R;
 import com.android.server.credentials.metrics.ApiName;
 import com.android.server.credentials.metrics.ApiStatus;
+import com.android.server.credentials.metrics.CandidateBrowsingPhaseMetric;
 import com.android.server.credentials.metrics.CandidatePhaseMetric;
-import com.android.server.credentials.metrics.ChosenProviderMetric;
+import com.android.server.credentials.metrics.ChosenProviderFinalPhaseMetric;
+import com.android.server.credentials.metrics.EntryEnum;
 import com.android.server.credentials.metrics.InitialPhaseMetric;
 
 import java.util.ArrayList;
 import java.util.HashMap;
+import java.util.List;
 import java.util.Map;
 
 /**
@@ -75,8 +79,16 @@
     protected final CancellationSignal mCancellationSignal;
 
     protected final Map<String, ProviderSession> mProviders = new HashMap<>();
-    protected ChosenProviderMetric mChosenProviderMetric = new ChosenProviderMetric();
     protected InitialPhaseMetric mInitialPhaseMetric = new InitialPhaseMetric();
+    protected ChosenProviderFinalPhaseMetric
+            mChosenProviderFinalPhaseMetric = new ChosenProviderFinalPhaseMetric();
+
+    // TODO(b/271135048) - Group metrics used in a scope together, such as here in RequestSession
+    // TODO(b/271135048) - Replace this with a new atom per each browsing emit (V4)
+    @Nullable
+    protected List<CandidateBrowsingPhaseMetric> mCandidateBrowsingPhaseMetric;
+    // As emits occur in sequential order, increment this counter and utilize
+    protected int mSequenceCounter = 0;
     protected final String mHybridService;
 
     @NonNull
@@ -152,10 +164,22 @@
             return;
         }
         Log.i(TAG, "Provider session found");
+        logBrowsingPhasePerSelect(selection, providerSession);
         providerSession.onUiEntrySelected(selection.getEntryKey(),
                 selection.getEntrySubkey(), selection.getPendingIntentProviderResponse());
     }
 
+    private void logBrowsingPhasePerSelect(UserSelectionDialogResult selection,
+            ProviderSession providerSession) {
+        CandidateBrowsingPhaseMetric browsingPhaseMetric = new CandidateBrowsingPhaseMetric();
+        browsingPhaseMetric.setSessionId(this.mInitialPhaseMetric.getSessionId());
+        browsingPhaseMetric.setEntryEnum(
+                EntryEnum.getMetricCodeFromString(selection.getEntryKey()));
+        browsingPhaseMetric.setProviderUid(providerSession.mCandidatePhasePerProviderMetric
+                .getCandidateUid());
+        this.mCandidateBrowsingPhaseMetric.add(new CandidateBrowsingPhaseMetric());
+    }
+
     protected void finishSession(boolean propagateCancellation) {
         Log.i(TAG, "finishing session");
         if (propagateCancellation) {
@@ -176,7 +200,14 @@
 
     protected void logApiCall(ApiName apiName, ApiStatus apiStatus) {
         logApiCalled(apiName, apiStatus, mProviders, mCallingUid,
-                mChosenProviderMetric);
+                mChosenProviderFinalPhaseMetric);
+    }
+
+    protected void logApiCall(ChosenProviderFinalPhaseMetric finalPhaseMetric,
+            List<CandidateBrowsingPhaseMetric> browsingPhaseMetrics) {
+        // TODO (b/270403549) - this browsing phase object is fine but also have a new emit
+        // For the returned types by authentication entries - i.e. a CandidatePhase During Browse
+        // TODO call MetricUtilities with new setup
     }
 
     protected boolean isSessionCancelled() {
@@ -218,8 +249,10 @@
         }
         if (!providerDataList.isEmpty()) {
             Log.i(TAG, "provider list not empty about to initiate ui");
+            // TODO immediately Add paths to end it (say it fails)
             if (isSessionCancelled()) {
                 Log.i(TAG, "In getProviderDataAndInitiateUi but session has been cancelled");
+                // TODO immedaitely Add paths
             } else {
                 launchUiWithProviderData(providerDataList);
             }
@@ -233,11 +266,21 @@
      */
     protected void setChosenMetric(ComponentName componentName) {
         CandidatePhaseMetric metric = this.mProviders.get(componentName.flattenToString())
-                .mCandidateProviderMetric;
-        mChosenProviderMetric.setChosenUid(metric.getCandidateUid());
-        mChosenProviderMetric.setFinalFinishTimeNanoseconds(System.nanoTime());
-        mChosenProviderMetric.setQueryPhaseLatencyMicroseconds(
+                .mCandidatePhasePerProviderMetric;
+
+        mChosenProviderFinalPhaseMetric.setSessionId(metric.getSessionId());
+        mChosenProviderFinalPhaseMetric.setChosenUid(metric.getCandidateUid());
+
+        mChosenProviderFinalPhaseMetric.setQueryPhaseLatencyMicroseconds(
                 metric.getQueryLatencyMicroseconds());
-        mChosenProviderMetric.setQueryStartTimeNanoseconds(metric.getStartQueryTimeNanoseconds());
+
+        mChosenProviderFinalPhaseMetric.setServiceBeganTimeNanoseconds(
+                metric.getServiceBeganTimeNanoseconds());
+        mChosenProviderFinalPhaseMetric.setQueryStartTimeNanoseconds(
+                metric.getStartQueryTimeNanoseconds());
+
+        // TODO immediately update with the entry count numbers from the candidate metrics
+
+        mChosenProviderFinalPhaseMetric.setFinalFinishTimeNanoseconds(System.nanoTime());
     }
 }
diff --git a/services/credentials/java/com/android/server/credentials/metrics/CandidateBrowsingPhaseMetric.java b/services/credentials/java/com/android/server/credentials/metrics/CandidateBrowsingPhaseMetric.java
index 37ec8f0..0e1e0389 100644
--- a/services/credentials/java/com/android/server/credentials/metrics/CandidateBrowsingPhaseMetric.java
+++ b/services/credentials/java/com/android/server/credentials/metrics/CandidateBrowsingPhaseMetric.java
@@ -17,23 +17,22 @@
 package com.android.server.credentials.metrics;
 
 /**
- * A part of the Candidate Phase, but emitted alongside {@link ChosenProviderMetric}. The user is
- * shown various entries from the provider responses, and may selectively browse through many
- * entries. It is possible that the initial set of browsing is for a provider that is ultimately
- * not chosen. This metric will be gathered PER browsing click, and aggregated, so that we can
- * understand where user interaction is more cumbersome, informing us for future improvements. This
- * can only be complete when the browsing is finished, ending in a final user choice, or possibly
- * a cancellation. Thus, this will be collected and emitted in the final phase, though collection
- * will begin in the candidate phase when the user begins browsing options.
+ * A part of the Candidate Phase, but emitted alongside {@link ChosenProviderFinalPhaseMetric}.
+ * The user is shown various entries from the provider responses, and may selectively browse through
+ * many entries. It is possible that the initial set of browsing is for a provider that is
+ * ultimately not chosen. This metric will be gathered PER browsing click, and aggregated, so that
+ * we can understand where user interaction is more cumbersome, informing us for future
+ * improvements. This can only be complete when the browsing is finished, ending in a final user
+ * choice, or possibly a cancellation. Thus, this will be collected and emitted in the final phase,
+ * though collection will begin in the candidate phase when the user begins browsing options.
  */
 public class CandidateBrowsingPhaseMetric {
 
-    private static final String TAG = "CandidateSelectionPhaseMetric";
-    private static final int SEQUENCE_ID = 3;
+    private static final String TAG = "CandidateBrowsingPhaseMetric";
     // The session id associated with the API Call this candidate provider is a part of, default -1
     private int mSessionId = -1;
-    // The EntryEnum that was pressed, defaults to -1 (TODO immediately, generate entry enum).
-    private int mEntryEnum = -1;
+    // The EntryEnum that was pressed, defaults to -1
+    private int mEntryEnum = EntryEnum.UNKNOWN.getMetricCode();
     // The provider associated with the press, defaults to -1
     private int mProviderUid = -1;
 
@@ -47,12 +46,6 @@
         return mSessionId;
     }
 
-    /* -- The sequence ID -- */
-
-    public int getSequenceId() {
-        return SEQUENCE_ID;
-    }
-
     /* -- The Entry of this tap -- */
 
     public void setEntryEnum(int entryEnum) {
diff --git a/services/credentials/java/com/android/server/credentials/metrics/CandidatePhaseMetric.java b/services/credentials/java/com/android/server/credentials/metrics/CandidatePhaseMetric.java
index 1c7fb69..c392d78 100644
--- a/services/credentials/java/com/android/server/credentials/metrics/CandidatePhaseMetric.java
+++ b/services/credentials/java/com/android/server/credentials/metrics/CandidatePhaseMetric.java
@@ -30,10 +30,8 @@
 public class CandidatePhaseMetric {
 
     private static final String TAG = "CandidateProviderMetric";
-    // Since this will always be the second in the split sequence, this is statically 2
-    private static final int SESSION_ID = 2;
-    // The sequence number of this emit of the API call, default -1, equal for all candidates
-    private int mSequenceId = -1;
+    // The session id of this provider, default set to -1
+    private int mSessionId = -1;
     // Indicates if this provider returned from the query phase, default false
     private boolean mQueryReturned = false;
 
@@ -150,18 +148,13 @@
     }
 
     /* -------------- Session Id ---------------- */
+
+    public void setSessionId(int sessionId) {
+        mSessionId = sessionId;
+    }
+
     public int getSessionId() {
-        return SESSION_ID;
-    }
-
-    /* -------------- Sequence Id ---------------- */
-
-    public void setSequenceId(int sequenceId) {
-        mSequenceId = sequenceId;
-    }
-
-    public int getSequenceId() {
-        return mSequenceId;
+        return mSessionId;
     }
 
     /* -------------- Query Returned Status ---------------- */
diff --git a/services/credentials/java/com/android/server/credentials/metrics/ChosenProviderMetric.java b/services/credentials/java/com/android/server/credentials/metrics/ChosenProviderFinalPhaseMetric.java
similarity index 80%
rename from services/credentials/java/com/android/server/credentials/metrics/ChosenProviderMetric.java
rename to services/credentials/java/com/android/server/credentials/metrics/ChosenProviderFinalPhaseMetric.java
index 1a61091..32fe204 100644
--- a/services/credentials/java/com/android/server/credentials/metrics/ChosenProviderMetric.java
+++ b/services/credentials/java/com/android/server/credentials/metrics/ChosenProviderFinalPhaseMetric.java
@@ -21,16 +21,22 @@
 import com.android.server.credentials.MetricUtilities;
 
 /**
- * The central chosen provider metric object that mimics our defined metric setup.
+ * The central chosen provider metric object that mimics our defined metric setup. This is used
+ * in the final phase of the flow and emits final status metrics.
  * Some types are redundant across these metric collectors, but that has debug use-cases as
  * these data-types are available at different moments of the flow (and typically, one can feed
  * into the next).
  * TODO(b/270403549) - iterate on this in V3+
+ * TODO(Immediately) - finalize V3 only types
  */
-public class ChosenProviderMetric {
+public class ChosenProviderFinalPhaseMetric {
 
-    // TODO(b/270403549) - applies elsewhere, likely removed or replaced with a count-index (1,2,3)
-    private static final String TAG = "ChosenProviderMetric";
+    // TODO(b/270403549) - applies elsewhere, likely removed or replaced w/ some hashed/count index
+    private static final String TAG = "ChosenFinalPhaseMetric";
+    // The session id associated with this API call, used to unite split emits
+    private long mSessionId = -1;
+    // Reveals if the UI was returned, false by default
+    private boolean mUiReturned = false;
     private int mChosenUid = -1;
 
     // Latency figures typically fed in from prior CandidateProviderMetric
@@ -39,16 +45,29 @@
     private int mQueryPhaseLatencyMicroseconds = -1;
 
     // Timestamps kept in raw nanoseconds. Expected to be converted to microseconds from using
-    // reference 'mServiceBeganTimeNanoseconds' during metric log point.
+    // reference 'mServiceBeganTimeNanoseconds' during metric log point
 
+    // Kept for local reference purposes, the initial timestamp of the service called passed in
     private long mServiceBeganTimeNanoseconds = -1;
+    // The first query timestamp, which upon emit is normalized to microseconds using the reference
+    // start timestamp
     private long mQueryStartTimeNanoseconds = -1;
+    // The UI call timestamp, which upon emit will be normalized to microseconds using reference
     private long mUiCallStartTimeNanoseconds = -1;
+    // The UI return timestamp, which upon emit will be normalized to microseconds using reference
     private long mUiCallEndTimeNanoseconds = -1;
+    // The final finish timestamp, which upon emit will be normalized to microseconds with reference
     private long mFinalFinishTimeNanoseconds = -1;
-    private int mChosenProviderStatus = -1;
+    // The status of this provider after selection
 
-    public ChosenProviderMetric() {
+    // Other General Information, such as final api status, provider status, entry info, etc...
+
+    private int mChosenProviderStatus = -1;
+    // TODO add remaining properties based on the Atom ; specifically, migrate the candidate
+    // Entry information, and store final status here
+
+
+    public ChosenProviderFinalPhaseMetric() {
     }
 
     /* ------------------- UID ------------------- */
@@ -200,4 +219,24 @@
     public void setChosenProviderStatus(int chosenProviderStatus) {
         mChosenProviderStatus = chosenProviderStatus;
     }
+
+    /* ----------- Session ID -------------- */
+
+    public void setSessionId(long sessionId) {
+        mSessionId = sessionId;
+    }
+
+    public long getSessionId() {
+        return mSessionId;
+    }
+
+    /* ----------- UI Returned Successfully -------------- */
+
+    public void setUiReturned(boolean uiReturned) {
+        mUiReturned = uiReturned;
+    }
+
+    public boolean isUiReturned() {
+        return mUiReturned;
+    }
 }
diff --git a/services/credentials/java/com/android/server/credentials/metrics/EntryEnum.java b/services/credentials/java/com/android/server/credentials/metrics/EntryEnum.java
new file mode 100644
index 0000000..73403a6
--- /dev/null
+++ b/services/credentials/java/com/android/server/credentials/metrics/EntryEnum.java
@@ -0,0 +1,86 @@
+/*
+ * 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.credentials.metrics;
+
+import static com.android.internal.util.FrameworkStatsLog.CREDENTIAL_MANAGER_API_CALLED__API_NAME__API_NAME_CLEAR_CREDENTIAL;
+import static com.android.internal.util.FrameworkStatsLog.CREDENTIAL_MANAGER_API_CALLED__API_NAME__API_NAME_CREATE_CREDENTIAL;
+import static com.android.internal.util.FrameworkStatsLog.CREDENTIAL_MANAGER_API_CALLED__API_NAME__API_NAME_GET_CREDENTIAL;
+import static com.android.internal.util.FrameworkStatsLog.CREDENTIAL_MANAGER_API_CALLED__API_NAME__API_NAME_IS_ENABLED_CREDENTIAL_PROVIDER_SERVICE;
+import static com.android.internal.util.FrameworkStatsLog.CREDENTIAL_MANAGER_API_CALLED__API_NAME__API_NAME_UNKNOWN;
+import static com.android.server.credentials.ProviderGetSession.ACTION_ENTRY_KEY;
+import static com.android.server.credentials.ProviderGetSession.AUTHENTICATION_ACTION_ENTRY_KEY;
+import static com.android.server.credentials.ProviderGetSession.CREDENTIAL_ENTRY_KEY;
+import static com.android.server.credentials.ProviderGetSession.REMOTE_ENTRY_KEY;
+
+import android.util.Log;
+
+import java.util.AbstractMap;
+import java.util.Map;
+
+public enum EntryEnum {
+    // TODO immediately, update with built entries
+    UNKNOWN(CREDENTIAL_MANAGER_API_CALLED__API_NAME__API_NAME_UNKNOWN),
+    ACTION_ENTRY(CREDENTIAL_MANAGER_API_CALLED__API_NAME__API_NAME_GET_CREDENTIAL),
+    CREDENTIAL_ENTRY(CREDENTIAL_MANAGER_API_CALLED__API_NAME__API_NAME_CREATE_CREDENTIAL),
+    REMOTE_ENTRY(CREDENTIAL_MANAGER_API_CALLED__API_NAME__API_NAME_CLEAR_CREDENTIAL),
+    AUTHENTICATION_ENTRY(
+            CREDENTIAL_MANAGER_API_CALLED__API_NAME__API_NAME_IS_ENABLED_CREDENTIAL_PROVIDER_SERVICE
+    );
+
+    private static final String TAG = "EntryEnum";
+
+    private final int mInnerMetricCode;
+
+    private static final Map<String, Integer> sKeyToEntryCode = Map.ofEntries(
+            new AbstractMap.SimpleEntry<>(ACTION_ENTRY_KEY,
+                    ACTION_ENTRY.mInnerMetricCode),
+            new AbstractMap.SimpleEntry<>(AUTHENTICATION_ACTION_ENTRY_KEY,
+                    AUTHENTICATION_ENTRY.mInnerMetricCode),
+            new AbstractMap.SimpleEntry<>(REMOTE_ENTRY_KEY,
+                    REMOTE_ENTRY.mInnerMetricCode),
+            new AbstractMap.SimpleEntry<>(CREDENTIAL_ENTRY_KEY,
+                    CREDENTIAL_ENTRY.mInnerMetricCode)
+    );
+
+    EntryEnum(int innerMetricCode) {
+        this.mInnerMetricCode = innerMetricCode;
+    }
+
+    /**
+     * Gives the West-world version of the metric name.
+     *
+     * @return a code corresponding to the west world metric name
+     */
+    public int getMetricCode() {
+        return this.mInnerMetricCode;
+    }
+
+    /**
+     * Given a string key type known to the framework, this returns the known metric code associated
+     * with that string.
+     *
+     * @param stringKey a string key type for a particular entry
+     * @return the metric code associated with this enum
+     */
+    public static int getMetricCodeFromString(String stringKey) {
+        if (!sKeyToEntryCode.containsKey(stringKey)) {
+            Log.w(TAG, "Attempted to use an unsupported string key entry type");
+            return UNKNOWN.mInnerMetricCode;
+        }
+        return sKeyToEntryCode.get(stringKey);
+    }
+}
diff --git a/services/credentials/java/com/android/server/credentials/metrics/InitialPhaseMetric.java b/services/credentials/java/com/android/server/credentials/metrics/InitialPhaseMetric.java
index 31c6f6f..a73495f 100644
--- a/services/credentials/java/com/android/server/credentials/metrics/InitialPhaseMetric.java
+++ b/services/credentials/java/com/android/server/credentials/metrics/InitialPhaseMetric.java
@@ -24,16 +24,14 @@
  * TODO(b/270403549) - iterate on this in V3+
  */
 public class InitialPhaseMetric {
-    private static final String TAG = "PreCandidateMetric";
-    // A sequence id to order united emits, due to split, this will statically always be 1
-    public static final int SEQUENCE_ID = 1;
+    private static final String TAG = "InitialPhaseMetric";
 
     // The api being called, default set to unknown
     private int mApiName = ApiName.UNKNOWN.getMetricCode();
     // The caller uid of the calling application, default to -1
     private int mCallerUid = -1;
     // The session id to unite multiple atom emits, default to -1
-    private long mSessionId = -1;
+    private int mSessionId = -1;
     private int mCountRequestClassType = -1;
 
     // Raw timestamps in nanoseconds, *the only* one logged as such (i.e. 64 bits) since it is a
@@ -100,15 +98,14 @@
 
     /* ------ SessionId ------ */
 
-    public void setSessionId(long sessionId) {
+    public void setSessionId(int sessionId) {
         mSessionId = sessionId;
     }
 
-    public long getSessionId() {
+    public int getSessionId() {
         return mSessionId;
     }
 
-
     /* ------ Count Request Class Types ------ */
 
     public void setCountRequestClassType(int countRequestClassType) {
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java
index c7f9952..7eeb51c 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java
@@ -16,17 +16,19 @@
 
 package com.android.server.devicepolicy;
 
-import static android.app.admin.PolicyUpdateResult.RESULT_FAILURE_CONFLICTING_ADMIN_POLICY;
-import static android.app.admin.PolicyUpdateResult.RESULT_POLICY_CLEARED;
-import static android.app.admin.PolicyUpdateResult.RESULT_SUCCESS;
 import static android.app.admin.PolicyUpdateReceiver.EXTRA_POLICY_TARGET_USER_ID;
 import static android.app.admin.PolicyUpdateReceiver.EXTRA_POLICY_UPDATE_RESULT_KEY;
+import static android.app.admin.PolicyUpdateResult.RESULT_FAILURE_CONFLICTING_ADMIN_POLICY;
+import static android.app.admin.PolicyUpdateResult.RESULT_FAILURE_HARDWARE_LIMITATION;
+import static android.app.admin.PolicyUpdateResult.RESULT_POLICY_CLEARED;
+import static android.app.admin.PolicyUpdateResult.RESULT_POLICY_SET;
 import static android.content.pm.UserProperties.INHERIT_DEVICE_POLICY_FROM_PARENT;
 import static android.provider.DeviceConfig.NAMESPACE_DEVICE_POLICY_MANAGER;
 
 import android.Manifest;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.app.admin.DevicePolicyIdentifiers;
 import android.app.admin.DevicePolicyManager;
 import android.app.admin.DevicePolicyState;
 import android.app.admin.PolicyKey;
@@ -46,6 +48,7 @@
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.provider.DeviceConfig;
+import android.telephony.TelephonyManager;
 import android.util.AtomicFile;
 import android.util.Log;
 import android.util.SparseArray;
@@ -79,6 +82,10 @@
 final class DevicePolicyEngine {
     static final String TAG = "DevicePolicyEngine";
 
+    private static final String CELLULAR_2G_USER_RESTRICTION_ID =
+            DevicePolicyIdentifiers.getIdentifierForUserRestriction(
+                    UserManager.DISALLOW_CELLULAR_2G);
+
     private static final String ENABLE_COEXISTENCE_FLAG = "enable_coexistence";
     private static final boolean DEFAULT_ENABLE_COEXISTENCE_FLAG = true;
 
@@ -167,7 +174,8 @@
                         enforcingAdmin,
                         policyDefinition,
                         // TODO: we're always sending this for now, should properly handle errors.
-                        policyEnforced ? RESULT_SUCCESS : RESULT_FAILURE_CONFLICTING_ADMIN_POLICY,
+                        policyEnforced
+                                ? RESULT_POLICY_SET : RESULT_FAILURE_CONFLICTING_ADMIN_POLICY,
                         userId);
             }
 
@@ -379,6 +387,15 @@
         Objects.requireNonNull(value);
 
         synchronized (mLock) {
+            // TODO(b/270999567): Move error handling for DISALLOW_CELLULAR_2G into the code
+            //  that honors the restriction once there's an API available
+            if (checkFor2gFailure(policyDefinition, enforcingAdmin)) {
+                Log.i(TAG,
+                        "Device does not support capabilities required to disable 2g. Not setting"
+                                + " global policy state.");
+                return;
+            }
+
             PolicyState<V> globalPolicyState = getGlobalPolicyStateLocked(policyDefinition);
 
             boolean policyChanged = globalPolicyState.addPolicy(enforcingAdmin, value);
@@ -400,7 +417,7 @@
                         enforcingAdmin,
                         policyDefinition,
                         // TODO: we're always sending this for now, should properly handle errors.
-                        policyApplied ? RESULT_SUCCESS : RESULT_FAILURE_CONFLICTING_ADMIN_POLICY,
+                        policyApplied ? RESULT_POLICY_SET : RESULT_FAILURE_CONFLICTING_ADMIN_POLICY,
                         UserHandle.USER_ALL);
             }
 
@@ -792,7 +809,7 @@
             int result = Objects.equals(
                     policyState.getPoliciesSetByAdmins().get(admin),
                     policyState.getCurrentResolvedPolicy())
-                    ? RESULT_SUCCESS : RESULT_FAILURE_CONFLICTING_ADMIN_POLICY;
+                    ? RESULT_POLICY_SET : RESULT_FAILURE_CONFLICTING_ADMIN_POLICY;
             maybeSendOnPolicyChanged(
                     admin, policyDefinition, result, userId);
         }
@@ -1161,6 +1178,36 @@
                 DEFAULT_ENABLE_COEXISTENCE_FLAG);
     }
 
+    private <V> boolean checkFor2gFailure(@NonNull PolicyDefinition<V> policyDefinition,
+            @NonNull EnforcingAdmin enforcingAdmin) {
+        if (!policyDefinition.getPolicyKey().getIdentifier().equals(
+                CELLULAR_2G_USER_RESTRICTION_ID)) {
+            return false;
+        }
+
+        boolean isCapabilitySupported;
+        try {
+            isCapabilitySupported = mContext.getSystemService(
+                    TelephonyManager.class).isRadioInterfaceCapabilitySupported(
+                    TelephonyManager.CAPABILITY_USES_ALLOWED_NETWORK_TYPES_BITMASK);
+        } catch (IllegalStateException e) {
+            // isRadioInterfaceCapabilitySupported can throw if there is no Telephony
+            // service initialized.
+            isCapabilitySupported = false;
+        }
+
+        if (!isCapabilitySupported) {
+            sendPolicyResultToAdmin(
+                    enforcingAdmin,
+                    policyDefinition,
+                    RESULT_FAILURE_HARDWARE_LIMITATION,
+                    UserHandle.USER_ALL);
+            return true;
+        }
+
+        return false;
+    }
+
     private class DevicePoliciesReaderWriter {
         private static final String DEVICE_POLICIES_XML = "device_policy_state.xml";
         private static final String TAG_LOCAL_POLICY_ENTRY = "local-policy-entry";
@@ -1315,8 +1362,8 @@
             if (adminsPolicy != null) {
                 mLocalPolicies.get(userId).put(policyKey, adminsPolicy);
             } else {
-                Log.e(TAG, "Error parsing file, " + policyKey + "doesn't have an "
-                        + "AdminsPolicy.");
+                Log.e(TAG,
+                        "Error parsing file, " + policyKey + "doesn't have an " + "AdminsPolicy.");
             }
         }
 
@@ -1327,8 +1374,8 @@
             if (adminsPolicy != null) {
                 mGlobalPolicies.put(policyKey, adminsPolicy);
             } else {
-                Log.e(TAG, "Error parsing file, " + policyKey + "doesn't have an "
-                        + "AdminsPolicy.");
+                Log.e(TAG,
+                        "Error parsing file, " + policyKey + "doesn't have an " + "AdminsPolicy.");
             }
         }
 
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index a220ad1..6cd9f1c 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -2237,6 +2237,16 @@
             }
             isOrgOwned = mOwners.isProfileOwnerOfOrganizationOwnedDevice(userHandle);
 
+            // Clear any restrictions set by the a profile owner and the parent admin.
+            final ActiveAdmin admin = getProfileOwnerLocked(userHandle);
+            if (admin != null) {
+                admin.userRestrictions = null;
+                final ActiveAdmin parentAdmin = admin.getParentActiveAdmin();
+                if (parentAdmin != null) {
+                    parentAdmin.userRestrictions = null;
+                }
+                pushUserRestrictions(userHandle);
+            }
             mOwners.removeProfileOwner(userHandle);
             mOwners.writeProfileOwner(userHandle);
             pushScreenCapturePolicy(userHandle);
diff --git a/services/java/com/android/server/HsumBootUserInitializer.java b/services/java/com/android/server/HsumBootUserInitializer.java
index 50113fe..b895812 100644
--- a/services/java/com/android/server/HsumBootUserInitializer.java
+++ b/services/java/com/android/server/HsumBootUserInitializer.java
@@ -18,6 +18,7 @@
 import android.annotation.Nullable;
 import android.annotation.UserIdInt;
 import android.content.ContentResolver;
+import android.content.pm.PackageManager;
 import android.content.pm.UserInfo;
 import android.database.ContentObserver;
 import android.os.Handler;
@@ -27,6 +28,7 @@
 import android.provider.Settings;
 
 import com.android.server.am.ActivityManagerService;
+import com.android.server.pm.PackageManagerService;
 import com.android.server.pm.UserManagerInternal;
 import com.android.server.utils.Slogf;
 import com.android.server.utils.TimingsTraceAndSlog;
@@ -41,6 +43,7 @@
 
     private final UserManagerInternal mUmi;
     private final ActivityManagerService mAms;
+    private final PackageManagerService mPms;
     private final ContentResolver mContentResolver;
 
     private final ContentObserver mDeviceProvisionedObserver =
@@ -63,20 +66,23 @@
 
     /** Static factory method for creating a {@link HsumBootUserInitializer} instance. */
     public static @Nullable HsumBootUserInitializer createInstance(ActivityManagerService am,
-            ContentResolver contentResolver, boolean shouldAlwaysHaveMainUser) {
+            PackageManagerService pms, ContentResolver contentResolver,
+            boolean shouldAlwaysHaveMainUser) {
 
         if (!UserManager.isHeadlessSystemUserMode()) {
             return null;
         }
         return new HsumBootUserInitializer(
                 LocalServices.getService(UserManagerInternal.class),
-                am, contentResolver, shouldAlwaysHaveMainUser);
+                am, pms, contentResolver, shouldAlwaysHaveMainUser);
     }
 
     private HsumBootUserInitializer(UserManagerInternal umi, ActivityManagerService am,
-            ContentResolver contentResolver, boolean shouldAlwaysHaveMainUser) {
+            PackageManagerService pms, ContentResolver contentResolver,
+            boolean shouldAlwaysHaveMainUser) {
         mUmi = umi;
         mAms = am;
+        mPms = pms;
         mContentResolver = contentResolver;
         mShouldAlwaysHaveMainUser = shouldAlwaysHaveMainUser;
     }
@@ -131,7 +137,8 @@
 
         try {
             t.traceBegin("getBootUser");
-            final int bootUser = mUmi.getBootUser();
+            final int bootUser = mUmi.getBootUser(/* waitUntilSet= */ mPms
+                    .hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE, /* version= */0));
             t.traceEnd();
             t.traceBegin("switchToBootUser-" + bootUser);
             switchToBootUser(bootUser);
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index edfe95e..b933508 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -1793,6 +1793,10 @@
         }
         t.traceEnd();
 
+        t.traceBegin("StartAppHibernationService");
+        mSystemServiceManager.startService(APP_HIBERNATION_SERVICE_CLASS);
+        t.traceEnd();
+
         t.traceBegin("ArtManagerLocal");
         DexOptHelper.initializeArtManagerLocal(context, mPackageManagerService);
         t.traceEnd();
@@ -2200,9 +2204,11 @@
                 t.traceEnd();
             }
 
-            t.traceBegin("StartDockObserver");
-            mSystemServiceManager.startService(DockObserver.class);
-            t.traceEnd();
+            if (!isTv) {
+                t.traceBegin("StartDockObserver");
+                mSystemServiceManager.startService(DockObserver.class);
+                t.traceEnd();
+            }
 
             if (isWatch) {
                 t.traceBegin("StartThermalObserver");
@@ -2314,10 +2320,6 @@
             mSystemServiceManager.startService(VOICE_RECOGNITION_MANAGER_SERVICE_CLASS);
             t.traceEnd();
 
-            t.traceBegin("StartAppHibernationService");
-            mSystemServiceManager.startService(APP_HIBERNATION_SERVICE_CLASS);
-            t.traceEnd();
-
             if (GestureLauncherService.isGestureLauncherEnabled(context.getResources())) {
                 t.traceBegin("StartGestureLauncher");
                 mSystemServiceManager.startService(GestureLauncherService.class);
@@ -2719,7 +2721,7 @@
         // on it in their setup, but likely needs to be done after LockSettingsService is ready.
         final HsumBootUserInitializer hsumBootUserInitializer =
                 HsumBootUserInitializer.createInstance(
-                        mActivityManagerService, mContentResolver,
+                        mActivityManagerService, mPackageManagerService, mContentResolver,
                         context.getResources().getBoolean(R.bool.config_isMainUserPermanentAdmin));
         if (hsumBootUserInitializer != null) {
             t.traceBegin("HsumBootUserInitializer.init");
@@ -3019,8 +3021,7 @@
             mSystemServiceManager.startBootPhase(t, SystemService.PHASE_THIRD_PARTY_APPS_CAN_START);
             t.traceEnd();
 
-            if (hsumBootUserInitializer != null && !isAutomotive) {
-                // TODO(b/261924826): remove isAutomotive check once the workflow is finalized
+            if (hsumBootUserInitializer != null) {
                 t.traceBegin("HsumBootUserInitializer.systemRunning");
                 hsumBootUserInitializer.systemRunning(t);
                 t.traceEnd();
diff --git a/services/tests/PackageManagerServiceTests/apks/install_uses_sdk/Android.bp b/services/tests/PackageManagerServiceTests/apks/install_uses_sdk/Android.bp
index 2894395..24e380c 100644
--- a/services/tests/PackageManagerServiceTests/apks/install_uses_sdk/Android.bp
+++ b/services/tests/PackageManagerServiceTests/apks/install_uses_sdk/Android.bp
@@ -32,9 +32,9 @@
 }
 
 android_test_helper_app {
-    name: "FrameworksServicesTests_install_uses_sdk_r1000",
+    name: "FrameworksServicesTests_install_uses_sdk_r10000",
     defaults: ["FrameworksServicesTests_apks_defaults"],
-    manifest: "AndroidManifest-r1000.xml",
+    manifest: "AndroidManifest-r10000.xml",
 }
 
 android_test_helper_app {
@@ -44,9 +44,9 @@
 }
 
 android_test_helper_app {
-    name: "FrameworksServicesTests_install_uses_sdk_r0_s1000",
+    name: "FrameworksServicesTests_install_uses_sdk_r0_s10000",
     defaults: ["FrameworksServicesTests_apks_defaults"],
-    manifest: "AndroidManifest-r0-s1000.xml",
+    manifest: "AndroidManifest-r0-s10000.xml",
 }
 
 android_test_helper_app {
diff --git a/services/tests/PackageManagerServiceTests/apks/install_uses_sdk/AndroidManifest-r0-s1000.xml b/services/tests/PackageManagerServiceTests/apks/install_uses_sdk/AndroidManifest-r0-s10000.xml
similarity index 97%
rename from services/tests/PackageManagerServiceTests/apks/install_uses_sdk/AndroidManifest-r0-s1000.xml
rename to services/tests/PackageManagerServiceTests/apks/install_uses_sdk/AndroidManifest-r0-s10000.xml
index 25743b8..383e60a 100644
--- a/services/tests/PackageManagerServiceTests/apks/install_uses_sdk/AndroidManifest-r0-s1000.xml
+++ b/services/tests/PackageManagerServiceTests/apks/install_uses_sdk/AndroidManifest-r0-s10000.xml
@@ -19,7 +19,7 @@
     <uses-sdk android:minSdkVersion="4" android:targetSdkVersion="29">
         <!-- This fails because 31 is not version 5 -->
         <extension-sdk android:sdkVersion="30" android:minExtensionVersion="0" />
-        <extension-sdk android:sdkVersion="31" android:minExtensionVersion="1000" />
+        <extension-sdk android:sdkVersion="31" android:minExtensionVersion="10000" />
     </uses-sdk>
 
     <application>
diff --git a/services/tests/PackageManagerServiceTests/apks/install_uses_sdk/AndroidManifest-r1000.xml b/services/tests/PackageManagerServiceTests/apks/install_uses_sdk/AndroidManifest-r10000.xml
similarity index 97%
rename from services/tests/PackageManagerServiceTests/apks/install_uses_sdk/AndroidManifest-r1000.xml
rename to services/tests/PackageManagerServiceTests/apks/install_uses_sdk/AndroidManifest-r10000.xml
index 9bf9254..fe7a212 100644
--- a/services/tests/PackageManagerServiceTests/apks/install_uses_sdk/AndroidManifest-r1000.xml
+++ b/services/tests/PackageManagerServiceTests/apks/install_uses_sdk/AndroidManifest-r10000.xml
@@ -18,7 +18,7 @@
 
     <uses-sdk android:minSdkVersion="4" android:targetSdkVersion="29">
         <!-- This will fail to install, because minExtensionVersion is not met -->
-        <extension-sdk android:sdkVersion="30" android:minExtensionVersion="1000" />
+        <extension-sdk android:sdkVersion="30" android:minExtensionVersion="10000" />
     </uses-sdk>
 
     <application>
diff --git a/services/tests/PackageManagerServiceTests/server/Android.bp b/services/tests/PackageManagerServiceTests/server/Android.bp
index e711cab..1146271 100644
--- a/services/tests/PackageManagerServiceTests/server/Android.bp
+++ b/services/tests/PackageManagerServiceTests/server/Android.bp
@@ -127,10 +127,10 @@
         ":FrameworksServicesTests_install_uses_sdk_q0",
         ":FrameworksServicesTests_install_uses_sdk_q0_r0",
         ":FrameworksServicesTests_install_uses_sdk_r0",
-        ":FrameworksServicesTests_install_uses_sdk_r1000",
+        ":FrameworksServicesTests_install_uses_sdk_r10000",
         ":FrameworksServicesTests_install_uses_sdk_r_none",
         ":FrameworksServicesTests_install_uses_sdk_r0_s0",
-        ":FrameworksServicesTests_install_uses_sdk_r0_s1000",
+        ":FrameworksServicesTests_install_uses_sdk_r0_s10000",
         ":FrameworksServicesTests_keyset_permdef_sa_unone",
         ":FrameworksServicesTests_keyset_permuse_sa_ua_ub",
         ":FrameworksServicesTests_keyset_permuse_sb_ua_ub",
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/PackageParserLegacyCoreTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/PackageParserLegacyCoreTest.java
index ebf309f..906cc83 100644
--- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/PackageParserLegacyCoreTest.java
+++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/PackageParserLegacyCoreTest.java
@@ -575,10 +575,10 @@
         assertEquals(0, minExtVers.get(31, -1));
 
         Map<Pair<String, Integer>, Integer> appToError = new HashMap<>();
-        appToError.put(Pair.create("install_uses_sdk.apk_r1000", R.raw.install_uses_sdk_r1000),
+        appToError.put(Pair.create("install_uses_sdk.apk_r10000", R.raw.install_uses_sdk_r10000),
                        PackageManager.INSTALL_FAILED_OLDER_SDK);
         appToError.put(
-                Pair.create("install_uses_sdk.apk_r0_s1000", R.raw.install_uses_sdk_r0_s1000),
+                Pair.create("install_uses_sdk.apk_r0_s10000", R.raw.install_uses_sdk_r0_s10000),
                 PackageManager.INSTALL_FAILED_OLDER_SDK);
 
         appToError.put(Pair.create("install_uses_sdk.apk_q0", R.raw.install_uses_sdk_q0),
diff --git a/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerController2Test.java b/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerController2Test.java
index 19aae19..1ced95b 100644
--- a/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerController2Test.java
+++ b/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerController2Test.java
@@ -30,6 +30,7 @@
 import static org.mockito.Mockito.clearInvocations;
 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.when;
@@ -92,6 +93,7 @@
     private static final String FOLLOWER_UNIQUE_ID = "unique_id_456";
     private static final int SECOND_FOLLOWER_DISPLAY_ID = FOLLOWER_DISPLAY_ID + 1;
     private static final String SECOND_FOLLOWER_UNIQUE_DISPLAY_ID = "unique_id_789";
+    private static final float PROX_SENSOR_MAX_RANGE = 5;
 
     private MockitoSession mSession;
     private OffsettableClock mClock;
@@ -160,7 +162,7 @@
     @Test
     public void testReleaseProxSuspendBlockersOnExit() throws Exception {
         when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_ON);
-        // send a display power request
+        // Send a display power request
         DisplayPowerRequest dpr = new DisplayPowerRequest();
         dpr.policy = DisplayPowerRequest.POLICY_BRIGHT;
         dpr.useProximitySensor = true;
@@ -172,7 +174,7 @@
         SensorEventListener listener = getSensorEventListener(mProxSensor);
         assertNotNull(listener);
 
-        listener.onSensorChanged(TestUtils.createSensorEvent(mProxSensor, 5 /* lux */));
+        listener.onSensorChanged(TestUtils.createSensorEvent(mProxSensor, /* value= */ 5));
         advanceTime(1);
 
         // two times, one for unfinished business and one for proximity
@@ -191,6 +193,83 @@
     }
 
     @Test
+    public void testScreenOffBecauseOfProximity() throws Exception {
+        when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_ON);
+        // Send a display power request
+        DisplayPowerRequest dpr = new DisplayPowerRequest();
+        dpr.policy = DisplayPowerRequest.POLICY_BRIGHT;
+        dpr.useProximitySensor = true;
+        mHolder.dpc.requestPowerState(dpr, false /* waitForNegativeProximity */);
+
+        // Run updatePowerState to start listener for the prox sensor
+        advanceTime(1);
+
+        SensorEventListener listener = getSensorEventListener(mProxSensor);
+        assertNotNull(listener);
+
+        // Send a positive proximity event
+        listener.onSensorChanged(TestUtils.createSensorEvent(mProxSensor, /* value= */ 1));
+        advanceTime(1);
+
+        // The display should have been turned off
+        verify(mHolder.displayPowerState).setScreenState(Display.STATE_OFF);
+
+        clearInvocations(mHolder.displayPowerState);
+        when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_OFF);
+        // Send a negative proximity event
+        listener.onSensorChanged(TestUtils.createSensorEvent(mProxSensor,
+                (int) PROX_SENSOR_MAX_RANGE + 1));
+        // Advance time by less than PROXIMITY_SENSOR_NEGATIVE_DEBOUNCE_DELAY
+        advanceTime(1);
+
+        // The prox sensor is debounced so the display should not have been turned back on yet
+        verify(mHolder.displayPowerState, never()).setScreenState(Display.STATE_ON);
+
+        // Advance time by more than PROXIMITY_SENSOR_NEGATIVE_DEBOUNCE_DELAY
+        advanceTime(1000);
+
+        // The display should have been turned back on
+        verify(mHolder.displayPowerState).setScreenState(Display.STATE_ON);
+    }
+
+    @Test
+    public void testScreenOffBecauseOfProximity_ProxSensorGone() throws Exception {
+        when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_ON);
+        // Send a display power request
+        DisplayPowerRequest dpr = new DisplayPowerRequest();
+        dpr.policy = DisplayPowerRequest.POLICY_BRIGHT;
+        dpr.useProximitySensor = true;
+        mHolder.dpc.requestPowerState(dpr, false /* waitForNegativeProximity */);
+
+        // Run updatePowerState to start listener for the prox sensor
+        advanceTime(1);
+
+        SensorEventListener listener = getSensorEventListener(mProxSensor);
+        assertNotNull(listener);
+
+        // Send a positive proximity event
+        listener.onSensorChanged(TestUtils.createSensorEvent(mProxSensor, /* value= */ 1));
+        advanceTime(1);
+
+        // The display should have been turned off
+        verify(mHolder.displayPowerState).setScreenState(Display.STATE_OFF);
+
+        when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_OFF);
+        // The display device changes and we no longer have a prox sensor
+        reset(mSensorManagerMock);
+        setUpDisplay(DISPLAY_ID, "new_unique_id", mHolder.display, mock(DisplayDevice.class),
+                mock(DisplayDeviceConfig.class), /* isEnabled= */ true);
+        mHolder.dpc.onDisplayChanged(mHolder.hbmMetadata, Layout.NO_LEAD_DISPLAY);
+
+        advanceTime(1); // Run updatePowerState
+
+        // The display should have been turned back on and the listener should have been
+        // unregistered
+        verify(mHolder.displayPowerState).setScreenState(Display.STATE_ON);
+        verify(mSensorManagerMock).unregisterListener(listener);
+    }
+
+    @Test
     public void testProximitySensorListenerNotRegisteredForNonDefaultDisplay() {
         when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_ON);
         // send a display power request
@@ -222,8 +301,8 @@
     }
 
     private void setUpSensors() throws Exception {
-        mProxSensor = TestUtils.createSensor(
-                Sensor.TYPE_PROXIMITY, Sensor.STRING_TYPE_PROXIMITY);
+        mProxSensor = TestUtils.createSensor(Sensor.TYPE_PROXIMITY, Sensor.STRING_TYPE_PROXIMITY,
+                PROX_SENSOR_MAX_RANGE);
         Sensor screenOffBrightnessSensor = TestUtils.createSensor(
                 Sensor.TYPE_LIGHT, Sensor.STRING_TYPE_LIGHT);
         when(mSensorManagerMock.getSensorList(eq(Sensor.TYPE_ALL)))
@@ -801,7 +880,13 @@
                 SensorManager sensorManager) {
             return new DisplayPowerProximityStateController(wakelockController,
                     displayDeviceConfig, looper, nudgeUpdatePowerState, displayId,
-                    sensorManager, /* injector= */ null);
+                    sensorManager,
+                    new DisplayPowerProximityStateController.Injector() {
+                        @Override
+                        DisplayPowerProximityStateController.Clock createClock() {
+                            return mClock::now;
+                        }
+                    });
         }
 
         @Override
diff --git a/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerControllerTest.java
index 02b6ea0..53fcdad 100644
--- a/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerControllerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerControllerTest.java
@@ -30,6 +30,7 @@
 import static org.mockito.Mockito.clearInvocations;
 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.when;
@@ -92,6 +93,7 @@
     private static final String FOLLOWER_UNIQUE_DISPLAY_ID = "unique_id_456";
     private static final int SECOND_FOLLOWER_DISPLAY_ID = FOLLOWER_DISPLAY_ID + 1;
     private static final String SECOND_FOLLOWER_UNIQUE_DISPLAY_ID = "unique_id_789";
+    private static final float PROX_SENSOR_MAX_RANGE = 5;
 
     private MockitoSession mSession;
     private OffsettableClock mClock;
@@ -161,7 +163,7 @@
     @Test
     public void testReleaseProxSuspendBlockersOnExit() throws Exception {
         when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_ON);
-        // send a display power request
+        // Send a display power request
         DisplayPowerRequest dpr = new DisplayPowerRequest();
         dpr.policy = DisplayPowerRequest.POLICY_BRIGHT;
         dpr.useProximitySensor = true;
@@ -173,7 +175,7 @@
         SensorEventListener listener = getSensorEventListener(mProxSensor);
         assertNotNull(listener);
 
-        listener.onSensorChanged(TestUtils.createSensorEvent(mProxSensor, 5 /* lux */));
+        listener.onSensorChanged(TestUtils.createSensorEvent(mProxSensor, /* value= */ 5));
         advanceTime(1);
 
         // two times, one for unfinished business and one for proximity
@@ -193,6 +195,83 @@
     }
 
     @Test
+    public void testScreenOffBecauseOfProximity() throws Exception {
+        when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_ON);
+        // Send a display power request
+        DisplayPowerRequest dpr = new DisplayPowerRequest();
+        dpr.policy = DisplayPowerRequest.POLICY_BRIGHT;
+        dpr.useProximitySensor = true;
+        mHolder.dpc.requestPowerState(dpr, false /* waitForNegativeProximity */);
+
+        // Run updatePowerState to start listener for the prox sensor
+        advanceTime(1);
+
+        SensorEventListener listener = getSensorEventListener(mProxSensor);
+        assertNotNull(listener);
+
+        // Send a positive proximity event
+        listener.onSensorChanged(TestUtils.createSensorEvent(mProxSensor, /* value= */ 1));
+        advanceTime(1);
+
+        // The display should have been turned off
+        verify(mHolder.displayPowerState).setScreenState(Display.STATE_OFF);
+
+        clearInvocations(mHolder.displayPowerState);
+        when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_OFF);
+        // Send a negative proximity event
+        listener.onSensorChanged(TestUtils.createSensorEvent(mProxSensor,
+                (int) PROX_SENSOR_MAX_RANGE + 1));
+        // Advance time by less than PROXIMITY_SENSOR_NEGATIVE_DEBOUNCE_DELAY
+        advanceTime(1);
+
+        // The prox sensor is debounced so the display should not have been turned back on yet
+        verify(mHolder.displayPowerState, never()).setScreenState(Display.STATE_ON);
+
+        // Advance time by more than PROXIMITY_SENSOR_NEGATIVE_DEBOUNCE_DELAY
+        advanceTime(1000);
+
+        // The display should have been turned back on
+        verify(mHolder.displayPowerState).setScreenState(Display.STATE_ON);
+    }
+
+    @Test
+    public void testScreenOffBecauseOfProximity_ProxSensorGone() throws Exception {
+        when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_ON);
+        // Send a display power request
+        DisplayPowerRequest dpr = new DisplayPowerRequest();
+        dpr.policy = DisplayPowerRequest.POLICY_BRIGHT;
+        dpr.useProximitySensor = true;
+        mHolder.dpc.requestPowerState(dpr, false /* waitForNegativeProximity */);
+
+        // Run updatePowerState to start listener for the prox sensor
+        advanceTime(1);
+
+        SensorEventListener listener = getSensorEventListener(mProxSensor);
+        assertNotNull(listener);
+
+        // Send a positive proximity event
+        listener.onSensorChanged(TestUtils.createSensorEvent(mProxSensor, /* value= */ 1));
+        advanceTime(1);
+
+        // The display should have been turned off
+        verify(mHolder.displayPowerState).setScreenState(Display.STATE_OFF);
+
+        when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_OFF);
+        // The display device changes and we no longer have a prox sensor
+        reset(mSensorManagerMock);
+        setUpDisplay(DISPLAY_ID, "new_unique_id", mHolder.display, mock(DisplayDevice.class),
+                mock(DisplayDeviceConfig.class), /* isEnabled= */ true);
+        mHolder.dpc.onDisplayChanged(mHolder.hbmMetadata, Layout.NO_LEAD_DISPLAY);
+
+        advanceTime(1); // Run updatePowerState
+
+        // The display should have been turned back on and the listener should have been
+        // unregistered
+        verify(mHolder.displayPowerState).setScreenState(Display.STATE_ON);
+        verify(mSensorManagerMock).unregisterListener(listener);
+    }
+
+    @Test
     public void testProximitySensorListenerNotRegisteredForNonDefaultDisplay() {
         DisplayPowerControllerHolder followerDpc =
                 createDisplayPowerController(FOLLOWER_DISPLAY_ID, FOLLOWER_UNIQUE_DISPLAY_ID);
@@ -225,8 +304,8 @@
     }
 
     private void setUpSensors() throws Exception {
-        mProxSensor = TestUtils.createSensor(
-                Sensor.TYPE_PROXIMITY, Sensor.STRING_TYPE_PROXIMITY);
+        mProxSensor = TestUtils.createSensor(Sensor.TYPE_PROXIMITY, Sensor.STRING_TYPE_PROXIMITY,
+                PROX_SENSOR_MAX_RANGE);
         Sensor screenOffBrightnessSensor = TestUtils.createSensor(
                 Sensor.TYPE_LIGHT, Sensor.STRING_TYPE_LIGHT);
         when(mSensorManagerMock.getSensorList(eq(Sensor.TYPE_ALL)))
diff --git a/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerProximityStateControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerProximityStateControllerTest.java
index 6e91b24..5b0b989 100644
--- a/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerProximityStateControllerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerProximityStateControllerTest.java
@@ -87,12 +87,7 @@
                 new DisplayPowerProximityStateController.Injector() {
                     @Override
                     DisplayPowerProximityStateController.Clock createClock() {
-                        return new DisplayPowerProximityStateController.Clock() {
-                            @Override
-                            public long uptimeMillis() {
-                                return mClock.now();
-                            }
-                        };
+                        return mClock::now;
                     }
                 };
         mDisplayPowerProximityStateController = new DisplayPowerProximityStateController(
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java
index 564893c..e7b3e6f 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java
@@ -260,7 +260,7 @@
         mUms.setBootUser(OTHER_USER_ID);
 
         assertWithMessage("getBootUser")
-                .that(mUmi.getBootUser()).isEqualTo(OTHER_USER_ID);
+                .that(mUmi.getBootUser(/* waitUntilSet= */ false)).isEqualTo(OTHER_USER_ID);
     }
 
     @Test
@@ -273,7 +273,8 @@
         mUms.setBootUser(PROFILE_USER_ID);
 
         assertWithMessage("getBootUser")
-                .that(mUmi.getBootUser()).isEqualTo(UserHandle.USER_SYSTEM);
+                .that(mUmi.getBootUser(/* waitUntilSet= */ false))
+                .isEqualTo(UserHandle.USER_SYSTEM);
     }
 
     @Test
@@ -289,7 +290,7 @@
 
         // Boot user not switchable so return most recently in foreground.
         assertWithMessage("getBootUser")
-                .that(mUmi.getBootUser()).isEqualTo(OTHER_USER_ID);
+                .that(mUmi.getBootUser(/* waitUntilSet= */ false)).isEqualTo(OTHER_USER_ID);
     }
 
     @Test
@@ -299,7 +300,8 @@
         addUser(OTHER_USER_ID);
 
         assertWithMessage("getBootUser")
-                .that(mUmi.getBootUser()).isEqualTo(UserHandle.USER_SYSTEM);
+                .that(mUmi.getBootUser(/* waitUntilSet= */ false))
+                .isEqualTo(UserHandle.USER_SYSTEM);
     }
 
     @Test
@@ -312,14 +314,15 @@
         setLastForegroundTime(OTHER_USER_ID, 2_000_000L);
 
         assertWithMessage("getBootUser")
-                .that(mUmi.getBootUser()).isEqualTo(OTHER_USER_ID);
+                .that(mUmi.getBootUser(/* waitUntilSet= */ false)).isEqualTo(OTHER_USER_ID);
     }
 
     @Test
     public void testGetBootUser_Headless_ThrowsIfOnlySystemUserExists() throws Exception {
         setSystemUserHeadless(true);
 
-        assertThrows(UserManager.CheckedUserOperationException.class, () -> mUmi.getBootUser());
+        assertThrows(UserManager.CheckedUserOperationException.class,
+                () -> mUmi.getBootUser(/* waitUntilSet= */ false));
     }
 
     private void mockCurrentUser(@UserIdInt int userId) {
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorVisibleBackgroundUserTestCase.java b/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorVisibleBackgroundUserTestCase.java
index e82910f..08b094b 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorVisibleBackgroundUserTestCase.java
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorVisibleBackgroundUserTestCase.java
@@ -160,6 +160,8 @@
 
         assertUserCanBeAssignedExtraDisplay(USER_ID, OTHER_SECONDARY_DISPLAY_ID);
 
+        listener.verify();
+
         // Assign again, without unassigning (to make sure it becomes invisible on stop)
         AsyncUserVisibilityListener listener2 = addListenerForEvents(onInvisible(USER_ID));
         assertUserCanBeAssignedExtraDisplay(USER_ID, OTHER_SECONDARY_DISPLAY_ID,
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationControllerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationControllerTest.java
index 88f0c93..f1ad577 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationControllerTest.java
@@ -238,7 +238,7 @@
     }
 
     private void notRegistered_publicMethodsShouldBeBenign(int displayId) {
-        checkActivatedAndMagnifyingState(/* activated= */false, /* magnifying= */false, displayId);
+        checkActivatedAndMagnifying(/* activated= */ false, /* magnifying= */ false, displayId);
 
         assertFalse(
                 mFullScreenMagnificationController.magnificationRegionContains(displayId, 100,
@@ -673,9 +673,9 @@
                 .setScale(displayId, 1.5f, startCenter.x, startCenter.y, false,
                         SERVICE_ID_2);
         assertFalse(mFullScreenMagnificationController.resetIfNeeded(displayId, SERVICE_ID_1));
-        checkActivatedAndMagnifyingState(/* activated= */true, /* magnifying= */true, displayId);
+        checkActivatedAndMagnifying(/* activated= */ true, /* magnifying= */ true, displayId);
         assertTrue(mFullScreenMagnificationController.resetIfNeeded(displayId, SERVICE_ID_2));
-        checkActivatedAndMagnifyingState(/* activated= */false, /* magnifying= */false, displayId);
+        checkActivatedAndMagnifying(/* activated= */ false, /* magnifying= */ false, displayId);
     }
 
     @Test
@@ -694,7 +694,7 @@
         assertTrue(mFullScreenMagnificationController.resetIfNeeded(displayId, false));
         verify(mRequestObserver).onFullScreenMagnificationChanged(eq(displayId),
                 eq(INITIAL_MAGNIFICATION_REGION), any(MagnificationConfig.class));
-        checkActivatedAndMagnifyingState(/* activated= */false, /* magnifying= */false, displayId);
+        checkActivatedAndMagnifying(/* activated= */ false, /* magnifying= */ false, displayId);
         assertFalse(mFullScreenMagnificationController.resetIfNeeded(displayId, false));
     }
 
@@ -758,7 +758,7 @@
         mTargetAnimationListener.onAnimationUpdate(mMockValueAnimator);
         mStateListener.onAnimationEnd(mMockValueAnimator);
 
-        checkActivatedAndMagnifyingState(/* activated= */false, /* magnifying= */false, displayId);
+        checkActivatedAndMagnifying(/* activated= */ false, /* magnifying= */ false, displayId);
         verify(lastAnimationCallback).onResult(true);
     }
 
@@ -776,26 +776,72 @@
         mMessageCapturingHandler.sendAllMessages();
         br.onReceive(mMockContext, null);
         mMessageCapturingHandler.sendAllMessages();
-        checkActivatedAndMagnifyingState(/* activated= */false, /* magnifying= */false, DISPLAY_0);
-        checkActivatedAndMagnifyingState(/* activated= */false, /* magnifying= */false, DISPLAY_1);
+        checkActivatedAndMagnifying(/* activated= */ false, /* magnifying= */ false, DISPLAY_0);
+        checkActivatedAndMagnifying(/* activated= */ false, /* magnifying= */ false, DISPLAY_1);
     }
 
     @Test
-    public void testUserContextChange_resetsMagnification() {
+    public void testUserContextChange_magnifierActivated_resetMagnification() {
         for (int i = 0; i < DISPLAY_COUNT; i++) {
-            contextChange_resetsMagnification(i);
+            contextChange_expectedValues(
+                    /* displayId= */ i,
+                    /* isMagnifierActivated= */ true,
+                    /* isAlwaysOnEnabled= */ false,
+                    /* expectedActivated= */ false);
             resetMockWindowManager();
         }
     }
 
-    private void contextChange_resetsMagnification(int displayId) {
+    @Test
+    public void testUserContextChange_magnifierActivatedAndAlwaysOnEnabled_stayActivated() {
+        for (int i = 0; i < DISPLAY_COUNT; i++) {
+            contextChange_expectedValues(
+                    /* displayId= */ i,
+                    /* isMagnifierActivated= */ true,
+                    /* isAlwaysOnEnabled= */ true,
+                    /* expectedActivated= */ true);
+            resetMockWindowManager();
+        }
+    }
+
+    @Test
+    public void testUserContextChange_magnifierDeactivated_stayDeactivated() {
+        for (int i = 0; i < DISPLAY_COUNT; i++) {
+            contextChange_expectedValues(
+                    /* displayId= */ i,
+                    /* isMagnifierActivated= */ false,
+                    /* isAlwaysOnEnabled= */ false,
+                    /* expectedActivated= */ false);
+            resetMockWindowManager();
+        }
+    }
+
+    @Test
+    public void testUserContextChange_magnifierDeactivatedAndAlwaysOnEnabled_stayDeactivated() {
+        for (int i = 0; i < DISPLAY_COUNT; i++) {
+            contextChange_expectedValues(
+                    /* displayId= */ i,
+                    /* isMagnifierActivated= */ false,
+                    /* isAlwaysOnEnabled= */ true,
+                    /* expectedActivated= */ false);
+            resetMockWindowManager();
+        }
+    }
+
+    private void contextChange_expectedValues(
+            int displayId, boolean isMagnifierActivated, boolean isAlwaysOnEnabled,
+            boolean expectedActivated) {
+        mFullScreenMagnificationController.setAlwaysOnMagnificationEnabled(isAlwaysOnEnabled);
         register(displayId);
         MagnificationCallbacks callbacks = getMagnificationCallbacks(displayId);
-        zoomIn2xToMiddle(displayId);
-        mMessageCapturingHandler.sendAllMessages();
+        if (isMagnifierActivated) {
+            zoomIn2xToMiddle(displayId);
+            mMessageCapturingHandler.sendAllMessages();
+        }
         callbacks.onUserContextChanged();
         mMessageCapturingHandler.sendAllMessages();
-        checkActivatedAndMagnifyingState(/* activated= */false, /* magnifying= */false, displayId);
+        checkActivatedAndMagnifying(
+                /* activated= */ expectedActivated, /* magnifying= */ false, displayId);
     }
 
     @Test
@@ -811,10 +857,10 @@
         MagnificationCallbacks callbacks = getMagnificationCallbacks(displayId);
         zoomIn2xToMiddle(displayId);
         mMessageCapturingHandler.sendAllMessages();
-        checkActivatedAndMagnifyingState(/* activated= */true, /* magnifying= */true, displayId);
+        checkActivatedAndMagnifying(/* activated= */ true, /* magnifying= */ true, displayId);
         callbacks.onDisplaySizeChanged();
         mMessageCapturingHandler.sendAllMessages();
-        checkActivatedAndMagnifyingState(/* activated= */false, /* magnifying= */false, DISPLAY_0);
+        checkActivatedAndMagnifying(/* activated= */ false, /* magnifying= */ false, DISPLAY_0);
     }
 
     @Test
@@ -1169,7 +1215,7 @@
         mFullScreenMagnificationController.setScaleAndCenter(
                 DISPLAY_0, scale, Float.NaN, Float.NaN, true, SERVICE_ID_1);
 
-        checkActivatedAndMagnifyingState(/* activated= */true, /* magnifying= */false, DISPLAY_0);
+        checkActivatedAndMagnifying(/* activated= */ true, /* magnifying= */ false, DISPLAY_0);
         verify(mMockWindowManager).setForceShowMagnifiableBounds(DISPLAY_0, true);
     }
 
@@ -1280,11 +1326,10 @@
         float scale = 2.0f;
         mFullScreenMagnificationController.setScale(displayId, scale, startCenter.x, startCenter.y,
                 false, SERVICE_ID_1);
-        checkActivatedAndMagnifyingState(/* activated= */true, /* magnifying= */true, displayId);
+        checkActivatedAndMagnifying(/* activated= */ true, /* magnifying= */ true, displayId);
     }
 
-    private void checkActivatedAndMagnifyingState(
-            boolean activated, boolean magnifying, int displayId) {
+    private void checkActivatedAndMagnifying(boolean activated, boolean magnifying, int displayId) {
         final boolean isActivated = mFullScreenMagnificationController.isActivated(displayId);
         final boolean isMagnifying = mFullScreenMagnificationController.getScale(displayId) > 1.0f;
         assertTrue(isActivated == activated);
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java
index 306ce4d..cf5ba27 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java
@@ -416,6 +416,15 @@
     }
 
     @Test
+    public void testMagnifierDeactivates_shortcutTriggeredState_returnToIdleState() {
+        goFromStateIdleTo(STATE_SHORTCUT_TRIGGERED);
+
+        mFullScreenMagnificationController.reset(DISPLAY_0, /* animate= */ false);
+
+        assertIn(STATE_IDLE);
+    }
+
+    @Test
     public void testThreeFingersOneTap_activatedState_dispatchMotionEvents() {
         goFromStateIdleTo(STATE_ACTIVATED);
         final EventCaptor eventCaptor = new EventCaptor();
diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/InputControllerTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/InputControllerTest.java
index 7642e7b..6c6b608 100644
--- a/services/tests/servicestests/src/com/android/server/companion/virtual/InputControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/companion/virtual/InputControllerTest.java
@@ -28,7 +28,7 @@
 
 import android.hardware.display.DisplayManagerInternal;
 import android.hardware.input.IInputManager;
-import android.hardware.input.InputManager;
+import android.hardware.input.InputManagerGlobal;
 import android.os.Binder;
 import android.os.Handler;
 import android.os.IBinder;
@@ -109,7 +109,7 @@
                 device1Id).isNotEqualTo(device2Id);
 
 
-        int[] deviceIds = InputManager.getInstance().getInputDeviceIds();
+        int[] deviceIds = InputManagerGlobal.getInstance().getInputDeviceIds();
         assertWithMessage("InputManager's deviceIds list should contain id of device 1").that(
                 deviceIds).asList().contains(device1Id);
         assertWithMessage("InputManager's deviceIds list should contain id of device 2").that(
@@ -153,7 +153,7 @@
                 deviceToken, /* displayId= */ 1, /* touchpadHeight= */ 50, /* touchpadWidth= */ 50);
 
         int deviceId = mInputController.getInputDeviceId(deviceToken);
-        int[] deviceIds = InputManager.getInstance().getInputDeviceIds();
+        int[] deviceIds = InputManagerGlobal.getInstance().getInputDeviceIds();
 
         assertWithMessage("InputManager's deviceIds list should contain id of the device").that(
             deviceIds).asList().contains(deviceId);
diff --git a/services/tests/servicestests/src/com/android/server/display/DeviceStateToLayoutMapTest.java b/services/tests/servicestests/src/com/android/server/display/DeviceStateToLayoutMapTest.java
index e74b278..a7d3df9 100644
--- a/services/tests/servicestests/src/com/android/server/display/DeviceStateToLayoutMapTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/DeviceStateToLayoutMapTest.java
@@ -18,6 +18,7 @@
 
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
 
 import android.view.Display;
 import android.view.DisplayAddress;
@@ -63,26 +64,10 @@
         Layout configLayout = mDeviceStateToLayoutMap.get(0);
 
         Layout testLayout = new Layout();
-        testLayout.createDisplayLocked(
-                DisplayAddress.fromPhysicalDisplayId(123456L), /* isDefault= */ true,
-                /* isEnabled= */ true, /* displayGroup= */ null, mDisplayIdProducerMock,
-                /* brightnessThrottlingMapId= */ null,
-                /* leadDisplayId= */ Display.DEFAULT_DISPLAY);
-        testLayout.createDisplayLocked(
-                DisplayAddress.fromPhysicalDisplayId(78910L), /* isDefault= */ false,
-                /* isEnabled= */ false, /* displayGroup= */ null, mDisplayIdProducerMock,
-                /* brightnessThrottlingMapId= */ null,
-                /* leadDisplayId= */ Display.DEFAULT_DISPLAY);
-        testLayout.createDisplayLocked(
-                DisplayAddress.fromPhysicalDisplayId(98765L), /* isDefault= */ false,
-                /* isEnabled= */ true, "group1", mDisplayIdProducerMock,
-                /* brightnessThrottlingMapId= */ null,
-                /* leadDisplayId= */ Display.DEFAULT_DISPLAY);
-        testLayout.createDisplayLocked(
-                DisplayAddress.fromPhysicalDisplayId(786L), /* isDefault= */ false,
-                /* isEnabled= */ false, "group2", mDisplayIdProducerMock,
-                /* brightnessThrottlingMapId= */ null,
-                /* leadDisplayId= */ Display.DEFAULT_DISPLAY);
+        createDefaultDisplay(testLayout, 123456L);
+        createNonDefaultDisplay(testLayout, 78910L, /* enabled= */ false, /* group= */ null);
+        createNonDefaultDisplay(testLayout, 98765L, /* enabled= */ true, /* group= */ "group1");
+        createNonDefaultDisplay(testLayout, 786L, /* enabled= */ false, /* group= */ "group2");
 
         assertEquals(testLayout, configLayout);
     }
@@ -92,41 +77,18 @@
         Layout configLayout = mDeviceStateToLayoutMap.get(1);
 
         Layout testLayout = new Layout();
-        testLayout.createDisplayLocked(
-                DisplayAddress.fromPhysicalDisplayId(78910L), /* isDefault= */ true,
-                /* isEnabled= */ true, /* displayGroup= */ null, mDisplayIdProducerMock,
-                /* brightnessThrottlingMapId= */ null,
-                /* leadDisplayId= */ Display.DEFAULT_DISPLAY);
-        testLayout.createDisplayLocked(
-                DisplayAddress.fromPhysicalDisplayId(123456L), /* isDefault= */ false,
-                /* isEnabled= */ false, /* displayGroup= */ null, mDisplayIdProducerMock,
-                /* brightnessThrottlingMapId= */ null,
-                /* leadDisplayId= */ Display.DEFAULT_DISPLAY);
+        createDefaultDisplay(testLayout, 78910L);
+        createNonDefaultDisplay(testLayout, 123456L, /* enabled= */ false, /* group= */ null);
 
         assertEquals(testLayout, configLayout);
     }
 
     @Test
-    public void testConcurrentState() {
+    public void testBrightnessThrottlingMapId() {
         Layout configLayout = mDeviceStateToLayoutMap.get(2);
 
-        Layout testLayout = new Layout();
-
-        Layout.Display display1 = testLayout.createDisplayLocked(
-                DisplayAddress.fromPhysicalDisplayId(345L), /* isDefault= */ true,
-                /* isEnabled= */ true, /* displayGroup= */ null, mDisplayIdProducerMock,
-                /* brightnessThrottlingMapId= */ "concurrent",
-                /* leadDisplayId= */ Display.DEFAULT_DISPLAY);
-        display1.setPosition(Layout.Display.POSITION_FRONT);
-
-        Layout.Display display2 = testLayout.createDisplayLocked(
-                DisplayAddress.fromPhysicalDisplayId(678L), /* isDefault= */ false,
-                /* isEnabled= */ true, /* displayGroup= */ null, mDisplayIdProducerMock,
-                /* brightnessThrottlingMapId= */ "concurrent",
-                /* leadDisplayId= */ Display.DEFAULT_DISPLAY);
-        display2.setPosition(Layout.Display.POSITION_REAR);
-
-        assertEquals(testLayout, configLayout);
+        assertEquals("concurrent1", configLayout.getAt(0).getBrightnessThrottlingMapId());
+        assertEquals("concurrent2", configLayout.getAt(1).getBrightnessThrottlingMapId());
     }
 
     @Test
@@ -141,38 +103,33 @@
     public void testRefreshRateZoneId() {
         Layout configLayout = mDeviceStateToLayoutMap.get(3);
 
-        Layout testLayout = new Layout();
-        Layout.Display display1 = testLayout.createDisplayLocked(
-                DisplayAddress.fromPhysicalDisplayId(345L), /* isDefault= */ true,
-                /* isEnabled= */ true, /* displayGroup= */ null, mDisplayIdProducerMock,
-                /* brightnessThrottlingMapId= */ null,
-                /* leadDisplayId= */ Display.DEFAULT_DISPLAY);
-        display1.setRefreshRateZoneId("test1");
-        testLayout.createDisplayLocked(
-                DisplayAddress.fromPhysicalDisplayId(678L), /* isDefault= */ false,
-                /* isEnabled= */ true, /* displayGroup= */ null, mDisplayIdProducerMock,
-                /* brightnessThrottlingMapId= */ null,
-                /* leadDisplayId= */ Display.DEFAULT_DISPLAY);
-
-        assertEquals(testLayout, configLayout);
+        assertEquals("test1", configLayout.getAt(0).getRefreshRateZoneId());
+        assertNull(configLayout.getAt(1).getRefreshRateZoneId());
     }
 
     @Test
     public void testRefreshRateThermalThrottlingMapId() {
         Layout configLayout = mDeviceStateToLayoutMap.get(4);
 
+        assertEquals("test2", configLayout.getAt(0).getRefreshRateThermalThrottlingMapId());
+        assertNull(configLayout.getAt(1).getRefreshRateThermalThrottlingMapId());
+    }
+
+    @Test
+    public void testWholeStateConfig() {
+        Layout configLayout = mDeviceStateToLayoutMap.get(99);
+
         Layout testLayout = new Layout();
-        Layout.Display display1 = testLayout.createDisplayLocked(
-                DisplayAddress.fromPhysicalDisplayId(345L), /* isDefault= */ true,
-                /* isEnabled= */ true, /* displayGroup= */ null, mDisplayIdProducerMock,
-                /* brightnessThrottlingMapId= */ null,
-                /* leadDisplayId= */ Display.DEFAULT_DISPLAY);
-        display1.setRefreshRateThermalThrottlingMapId("test2");
-        testLayout.createDisplayLocked(
-                DisplayAddress.fromPhysicalDisplayId(678L), /* isDefault= */ false,
-                /* isEnabled= */ true, /* displayGroup= */ null, mDisplayIdProducerMock,
-                /* brightnessThrottlingMapId= */ null,
-                /* leadDisplayId= */ Display.DEFAULT_DISPLAY);
+        testLayout.createDisplayLocked(DisplayAddress.fromPhysicalDisplayId(345L),
+                /* isDefault= */ true, /* isEnabled= */ true, /* displayGroupName= */ null,
+                mDisplayIdProducerMock,  Layout.Display.POSITION_FRONT, Display.DEFAULT_DISPLAY,
+                /* brightnessThrottlingMapId= */ "brightness1",
+                /* refreshRateZoneId= */ "zone1", /* refreshRateThermalThrottlingMapId= */ "rr1");
+        testLayout.createDisplayLocked(DisplayAddress.fromPhysicalDisplayId(678L),
+                /* isDefault= */ false, /* isEnabled= */ false, /* displayGroupName= */ "group1",
+                mDisplayIdProducerMock, Layout.Display.POSITION_REAR, Display.DEFAULT_DISPLAY,
+                /* brightnessThrottlingMapId= */ "brightness2",
+                /* refreshRateZoneId= */ "zone2", /* refreshRateThermalThrottlingMapId= */ "rr2");
 
         assertEquals(testLayout, configLayout);
     }
@@ -181,6 +138,18 @@
     // Helper Methods //
     ////////////////////
 
+    private void createDefaultDisplay(Layout layout, long id) {
+        layout.createDefaultDisplayLocked(DisplayAddress.fromPhysicalDisplayId(id),
+                mDisplayIdProducerMock);
+    }
+
+    private void createNonDefaultDisplay(Layout layout, long id, boolean enabled, String group) {
+        layout.createDisplayLocked(DisplayAddress.fromPhysicalDisplayId(id), /* isDefault= */ false,
+                enabled, group, mDisplayIdProducerMock, Layout.Display.POSITION_UNKNOWN,
+                Display.DEFAULT_DISPLAY, /* brightnessThrottlingMapId= */ null,
+                /* refreshRateZoneId= */ null, /* refreshRateThermalThrottlingMapId= */ null);
+    }
+
     private void setupDeviceStateToLayoutMap() throws IOException {
         Path tempFile = Files.createTempFile("device_state_layout_map", ".tmp");
         Files.write(tempFile, getContent().getBytes(StandardCharsets.UTF_8));
@@ -222,12 +191,12 @@
                 +      "<display enabled=\"true\" defaultDisplay=\"true\">\n"
                 +        "<address>345</address>\n"
                 +        "<position>front</position>\n"
-                +        "<brightnessThrottlingMapId>concurrent</brightnessThrottlingMapId>\n"
+                +        "<brightnessThrottlingMapId>concurrent1</brightnessThrottlingMapId>\n"
                 +      "</display>\n"
                 +      "<display enabled=\"true\">\n"
                 +        "<address>678</address>\n"
                 +        "<position>rear</position>\n"
-                +        "<brightnessThrottlingMapId>concurrent</brightnessThrottlingMapId>\n"
+                +        "<brightnessThrottlingMapId>concurrent2</brightnessThrottlingMapId>\n"
                 +      "</display>\n"
                 +    "</layout>\n"
 
@@ -254,6 +223,27 @@
                 +        "<address>678</address>\n"
                 +      "</display>\n"
                 +    "</layout>\n"
-                +  "</layouts>\n";
+                +    "<layout>\n"
+                +      "<state>99</state> \n"
+                +      "<display enabled=\"true\" defaultDisplay=\"true\" "
+                +                                          "refreshRateZoneId=\"zone1\">\n"
+                +         "<address>345</address>\n"
+                +         "<position>front</position>\n"
+                +         "<brightnessThrottlingMapId>brightness1</brightnessThrottlingMapId>\n"
+                +         "<refreshRateThermalThrottlingMapId>"
+                +           "rr1"
+                +         "</refreshRateThermalThrottlingMapId>"
+                +       "</display>\n"
+                +       "<display enabled=\"false\" displayGroup=\"group1\" "
+                +                                           "refreshRateZoneId=\"zone2\">\n"
+                +         "<address>678</address>\n"
+                +         "<position>rear</position>\n"
+                +         "<brightnessThrottlingMapId>brightness2</brightnessThrottlingMapId>\n"
+                +         "<refreshRateThermalThrottlingMapId>"
+                +           "rr2"
+                +         "</refreshRateThermalThrottlingMapId>"
+                +       "</display>\n"
+                +     "</layout>\n"
+                +   "</layouts>\n";
     }
 }
diff --git a/services/tests/servicestests/src/com/android/server/display/DisplayDeviceConfigTest.java b/services/tests/servicestests/src/com/android/server/display/DisplayDeviceConfigTest.java
index 45f1037..9fd647b 100644
--- a/services/tests/servicestests/src/com/android/server/display/DisplayDeviceConfigTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/DisplayDeviceConfigTest.java
@@ -235,6 +235,18 @@
         assertEquals(mDisplayDeviceConfig.getHostUsiVersion().getMajorVersion(), 2);
         assertEquals(mDisplayDeviceConfig.getHostUsiVersion().getMinorVersion(), 0);
 
+        // Max desired Hdr/SDR ratio upper-bounds the HDR brightness.
+        assertEquals(1.0f,
+                mDisplayDeviceConfig.getHdrBrightnessFromSdr(0.62f, Float.POSITIVE_INFINITY),
+                ZERO_DELTA);
+        assertEquals(0.62f,
+                mDisplayDeviceConfig.getHdrBrightnessFromSdr(0.62f, 1.0f),
+                ZERO_DELTA);
+        assertEquals(0.77787f,
+                mDisplayDeviceConfig.getHdrBrightnessFromSdr(0.62f, 1.25f),
+                SMALL_DELTA);
+
+
         // Todo: Add asserts for BrightnessThrottlingData, DensityMapping,
         // HighBrightnessModeData AmbientLightSensor, RefreshRateLimitations and ProximitySensor.
     }
@@ -417,6 +429,16 @@
                 +       "</refreshRate>\n"
                 +       "<thermalStatusLimit>light</thermalStatusLimit>\n"
                 +       "<allowInLowPowerMode>false</allowInLowPowerMode>\n"
+                +       "<sdrHdrRatioMap>\n"
+                +            "<point>\n"
+                +                "<sdrNits>2.000</sdrNits>\n"
+                +                "<hdrRatio>4.000</hdrRatio>\n"
+                +            "</point>\n"
+                +            "<point>\n"
+                +                "<sdrNits>500.0</sdrNits>\n"
+                +                "<hdrRatio>1.6</hdrRatio>\n"
+                +            "</point>\n"
+                +       "</sdrHdrRatioMap>\n"
                 +   "</highBrightnessMode>\n"
                 +   "<screenOffBrightnessSensor>\n"
                 +       "<type>sensor_12345</type>\n"
diff --git a/services/tests/servicestests/src/com/android/server/display/HighBrightnessModeControllerTest.java b/services/tests/servicestests/src/com/android/server/display/HighBrightnessModeControllerTest.java
index 2655c3f..3b10db4 100644
--- a/services/tests/servicestests/src/com/android/server/display/HighBrightnessModeControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/HighBrightnessModeControllerTest.java
@@ -29,6 +29,7 @@
 import static com.android.server.display.HighBrightnessModeController.HBM_TRANSITION_POINT_INVALID;
 
 import static org.junit.Assert.assertEquals;
+import static org.mockito.Mockito.anyFloat;
 import static org.mockito.Mockito.anyInt;
 import static org.mockito.Mockito.eq;
 import static org.mockito.Mockito.never;
@@ -103,6 +104,7 @@
 
     @Mock IThermalService mThermalServiceMock;
     @Mock Injector mInjectorMock;
+    @Mock HighBrightnessModeController.HdrBrightnessDeviceConfig mHdrBrightnessDeviceConfigMock;
 
     @Captor ArgumentCaptor<IThermalEventListener> mThermalEventListenerCaptor;
 
@@ -376,18 +378,49 @@
 
         // ensure hdr doesn't turn on if layer is too small
         hbmc.getHdrListener().onHdrInfoChanged(null /*displayToken*/, 1 /*numberOfHdrLayers*/,
-                layerWidth, smallLayerHeight, 0 /*flags*/);
+                layerWidth, smallLayerHeight, 0 /*flags*/, 1.0f /*maxDesiredHdrSdrRatio*/);
         advanceTime(0);
         assertEquals(HIGH_BRIGHTNESS_MODE_OFF, hbmc.getHighBrightnessMode());
 
         // Now check with layer larger than 50%
         hbmc.getHdrListener().onHdrInfoChanged(null /*displayToken*/, 1 /*numberOfHdrLayers*/,
-                layerWidth, largeLayerHeight, 0 /*flags*/);
+                layerWidth, largeLayerHeight, 0 /*flags*/, 1.0f /*maxDesiredHdrSdrRatio*/);
         advanceTime(0);
         assertEquals(HIGH_BRIGHTNESS_MODE_HDR, hbmc.getHighBrightnessMode());
     }
 
     @Test
+    public void testHdrRespectsMaxDesiredHdrSdrRatio() {
+        final HighBrightnessModeController hbmc = new TestHbmBuilder()
+                .setClock(new OffsettableClock())
+                .setHdrBrightnessConfig(mHdrBrightnessDeviceConfigMock)
+                .build();
+
+        // Passthrough return the max desired hdr/sdr ratio
+        when(mHdrBrightnessDeviceConfigMock.getHdrBrightnessFromSdr(anyFloat(), anyFloat()))
+                .thenAnswer(i -> i.getArgument(1));
+
+        hbmc.getHdrListener().onHdrInfoChanged(null /*displayToken*/, 1 /*numberOfHdrLayers*/,
+                DISPLAY_WIDTH, DISPLAY_HEIGHT, 0 /*flags*/, 2.0f /*maxDesiredHdrSdrRatio*/);
+        advanceTime(0);
+        assertEquals(2.0f, hbmc.getHdrBrightnessValue(), EPSILON);
+
+        // The hdr ratio cannot be less than 1.
+        hbmc.getHdrListener().onHdrInfoChanged(null /*displayToken*/, 1 /*numberOfHdrLayers*/,
+                DISPLAY_WIDTH, DISPLAY_HEIGHT, 0 /*flags*/, 0.5f /*maxDesiredHdrSdrRatio*/);
+        advanceTime(0);
+        assertEquals(1.0f, hbmc.getHdrBrightnessValue(), EPSILON);
+
+        // The hdr ratio can be as much as positive infinity
+        hbmc.getHdrListener().onHdrInfoChanged(null /*displayToken*/, 1 /*numberOfHdrLayers*/,
+                DISPLAY_WIDTH, DISPLAY_HEIGHT, 0 /*flags*/,
+                Float.POSITIVE_INFINITY /*maxDesiredHdrSdrRatio*/);
+        advanceTime(0);
+        assertEquals(Float.POSITIVE_INFINITY, hbmc.getHdrBrightnessValue(), 0.0);
+    }
+
+
+    @Test
     public void testHdrTrumpsSunlight() {
         final HighBrightnessModeController hbmc = createDefaultHbm(new OffsettableClock());
 
@@ -400,7 +433,7 @@
 
         // turn on hdr
         hbmc.getHdrListener().onHdrInfoChanged(null /*displayToken*/, 1 /*numberOfHdrLayers*/,
-                DISPLAY_WIDTH, DISPLAY_HEIGHT, 0 /*flags*/);
+                DISPLAY_WIDTH, DISPLAY_HEIGHT, 0 /*flags*/, 1.0f /*maxDesiredHdrSdrRatio*/);
         advanceTime(0);
         assertEquals(HIGH_BRIGHTNESS_MODE_HDR, hbmc.getHighBrightnessMode());
         assertEquals(TRANSITION_POINT, hbmc.getCurrentBrightnessMax(), EPSILON);
@@ -412,14 +445,14 @@
 
         // Check limit when HBM is off
         hbmc.getHdrListener().onHdrInfoChanged(null /*displayToken*/, 1 /*numberOfHdrLayers*/,
-                DISPLAY_WIDTH, DISPLAY_HEIGHT, 0 /*flags*/);
+                DISPLAY_WIDTH, DISPLAY_HEIGHT, 0 /*flags*/, 1.0f /*maxDesiredHdrSdrRatio*/);
         advanceTime(0);
         assertEquals(HIGH_BRIGHTNESS_MODE_HDR, hbmc.getHighBrightnessMode());
         assertEquals(TRANSITION_POINT, hbmc.getCurrentBrightnessMax(), EPSILON);
 
         // Check limit with HBM is set to HDR
         hbmc.getHdrListener().onHdrInfoChanged(null /*displayToken*/, 0 /*numberOfHdrLayers*/,
-                DISPLAY_WIDTH, DISPLAY_HEIGHT, 0 /*flags*/);
+                DISPLAY_WIDTH, DISPLAY_HEIGHT, 0 /*flags*/, 1.0f /*maxDesiredHdrSdrRatio*/);
         advanceTime(0);
         assertEquals(HIGH_BRIGHTNESS_MODE_OFF, hbmc.getHighBrightnessMode());
         assertEquals(TRANSITION_POINT, hbmc.getCurrentBrightnessMax(), EPSILON);
@@ -430,7 +463,7 @@
         final HighBrightnessModeController hbmc = createDefaultHbm(new OffsettableClock());
 
         hbmc.getHdrListener().onHdrInfoChanged(null /*displayToken*/, 1 /*numberOfHdrLayers*/,
-                DISPLAY_WIDTH, DISPLAY_HEIGHT, 0 /*flags*/);
+                DISPLAY_WIDTH, DISPLAY_HEIGHT, 0 /*flags*/, 1.0f /*maxDesiredHdrSdrRatio*/);
         advanceTime(0);
         assertEquals(HIGH_BRIGHTNESS_MODE_HDR, hbmc.getHighBrightnessMode());
 
@@ -473,7 +506,7 @@
         hbmc.setAutoBrightnessEnabled(AUTO_BRIGHTNESS_ENABLED);
         hbmcOnBrightnessChanged(hbmc, TRANSITION_POINT);
         hbmc.getHdrListener().onHdrInfoChanged(null /*displayToken*/, 1 /*numberOfHdrLayers*/,
-                DISPLAY_WIDTH, DISPLAY_HEIGHT, 0 /*flags*/);
+                DISPLAY_WIDTH, DISPLAY_HEIGHT, 0 /*flags*/, 1.0f /*maxDesiredHdrSdrRatio*/);
         advanceTime(0);
         assertEquals(HIGH_BRIGHTNESS_MODE_HDR, hbmc.getHighBrightnessMode());
 
@@ -483,7 +516,7 @@
             eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__REASON__HBM_TRANSITION_REASON_UNKNOWN));
 
         hbmc.getHdrListener().onHdrInfoChanged(null /*displayToken*/, 0 /*numberOfHdrLayers*/,
-                0, 0, 0 /*flags*/);
+                0, 0, 0 /*flags*/, 1.0f /*maxDesiredHdrSdrRatio*/);
         advanceTime(0);
 
         // Verify Stats HBM_OFF
@@ -517,7 +550,7 @@
         hbmc.setAutoBrightnessEnabled(AUTO_BRIGHTNESS_ENABLED);
         hbmcOnBrightnessChanged(hbmc, DEFAULT_MIN);
         hbmc.getHdrListener().onHdrInfoChanged(null /*displayToken*/, 1 /*numberOfHdrLayers*/,
-                DISPLAY_WIDTH, DISPLAY_HEIGHT, 0 /*flags*/);
+                DISPLAY_WIDTH, DISPLAY_HEIGHT, 0 /*flags*/, 1.0f /*maxDesiredHdrSdrRatio*/);
         advanceTime(0);
         assertEquals(HIGH_BRIGHTNESS_MODE_HDR, hbmc.getHighBrightnessMode());
 
@@ -657,7 +690,7 @@
             eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__REASON__HBM_TRANSITION_REASON_UNKNOWN));
 
         hbmc.getHdrListener().onHdrInfoChanged(null /*displayToken*/, 1 /*numberOfHdrLayers*/,
-                DISPLAY_WIDTH, DISPLAY_HEIGHT, 0 /*flags*/);
+                DISPLAY_WIDTH, DISPLAY_HEIGHT, 0 /*flags*/, 1.0f /*maxDesiredHdrSdrRatio*/);
         advanceTime(0);
 
         verify(mInjectorMock).reportHbmStateChange(eq(displayStatsId),
@@ -696,19 +729,42 @@
         assertEquals(hbmMode, hbmc.getHighBrightnessMode());
     }
 
+    private class TestHbmBuilder {
+        OffsettableClock mClock;
+        HighBrightnessModeController.HdrBrightnessDeviceConfig mHdrBrightnessCfg;
+
+        TestHbmBuilder setClock(OffsettableClock clock) {
+            mClock = clock;
+            return this;
+        }
+
+        TestHbmBuilder setHdrBrightnessConfig(
+                HighBrightnessModeController.HdrBrightnessDeviceConfig hdrBrightnessCfg
+        ) {
+            mHdrBrightnessCfg = hdrBrightnessCfg;
+            return this;
+        }
+
+        HighBrightnessModeController build() {
+            initHandler(mClock);
+            if (mHighBrightnessModeMetadata == null) {
+                mHighBrightnessModeMetadata = new HighBrightnessModeMetadata();
+            }
+            return new HighBrightnessModeController(mInjectorMock, mHandler, DISPLAY_WIDTH,
+                    DISPLAY_HEIGHT, mDisplayToken, mDisplayUniqueId, DEFAULT_MIN, DEFAULT_MAX,
+                    DEFAULT_HBM_DATA, mHdrBrightnessCfg, () -> {}, mHighBrightnessModeMetadata,
+                    mContextSpy);
+        }
+
+    }
+
     private HighBrightnessModeController createDefaultHbm() {
-        return createDefaultHbm(null);
+        return new TestHbmBuilder().build();
     }
 
     // Creates instance with standard initialization values.
     private HighBrightnessModeController createDefaultHbm(OffsettableClock clock) {
-        initHandler(clock);
-        if (mHighBrightnessModeMetadata == null) {
-            mHighBrightnessModeMetadata = new HighBrightnessModeMetadata();
-        }
-        return new HighBrightnessModeController(mInjectorMock, mHandler, DISPLAY_WIDTH,
-                DISPLAY_HEIGHT, mDisplayToken, mDisplayUniqueId, DEFAULT_MIN, DEFAULT_MAX,
-                DEFAULT_HBM_DATA, null, () -> {}, mHighBrightnessModeMetadata, mContextSpy);
+        return new TestHbmBuilder().setClock(clock).build();
     }
 
     private void initHandler(OffsettableClock clock) {
diff --git a/services/tests/servicestests/src/com/android/server/display/LogicalDisplayMapperTest.java b/services/tests/servicestests/src/com/android/server/display/LogicalDisplayMapperTest.java
index 9eb6003..567548e 100644
--- a/services/tests/servicestests/src/com/android/server/display/LogicalDisplayMapperTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/LogicalDisplayMapperTest.java
@@ -60,8 +60,8 @@
 import android.view.DisplayAddress;
 import android.view.DisplayInfo;
 
+import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
 
 import com.android.server.display.layout.DisplayIdProducer;
 import com.android.server.display.layout.Layout;
@@ -300,14 +300,8 @@
         add(device2);
 
         Layout layout1 = new Layout();
-        layout1.createDisplayLocked(info(device1).address, /* isDefault= */ true,
-                /* isEnabled= */ true, /* displayGroup= */ null, mIdProducer,
-                /* brightnessThrottlingMapId= */ null,
-                /* leadDisplayId= */ Display.DEFAULT_DISPLAY);
-        layout1.createDisplayLocked(info(device2).address, /* isDefault= */ false,
-                /* isEnabled= */ true, /* displayGroup= */ null, mIdProducer,
-                /* brightnessThrottlingMapId= */ null,
-                /* leadDisplayId= */ Display.DEFAULT_DISPLAY);
+        createDefaultDisplay(layout1, device1);
+        createNonDefaultDisplay(layout1, device2, /* enabled= */ true, /* group= */ null);
         when(mDeviceStateToLayoutMapSpy.get(STATE_DEFAULT)).thenReturn(layout1);
         assertThat(layout1.size()).isEqualTo(2);
         final int logicalId2 = layout1.getByAddress(info(device2).address).getLogicalDisplayId();
@@ -340,23 +334,14 @@
         add(device3);
 
         Layout layout1 = new Layout();
-        layout1.createDisplayLocked(info(device1).address, /* isDefault= */ true,
-                /* isEnabled= */ true, /* displayGroup= */ null, mIdProducer,
-                /* brightnessThrottlingMapId= */ null,
-                /* leadDisplayId= */ Display.DEFAULT_DISPLAY);
+        createDefaultDisplay(layout1, device1);
         when(mDeviceStateToLayoutMapSpy.get(STATE_DEFAULT)).thenReturn(layout1);
 
         final int layoutState2 = 2;
         Layout layout2 = new Layout();
-        layout2.createDisplayLocked(info(device2).address, /* isDefault= */ false,
-                /* isEnabled= */ true, /* displayGroup= */ null, mIdProducer,
-                /* brightnessThrottlingMapId= */ null,
-                /* leadDisplayId= */ Display.DEFAULT_DISPLAY);
+        createNonDefaultDisplay(layout2, device2, /* enabled= */ true, /* group= */ null);
         // Device3 is the default display.
-        layout2.createDisplayLocked(info(device3).address, /* isDefault= */ true,
-                /* isEnabled= */ true, /* displayGroup= */ null, mIdProducer,
-                /* brightnessThrottlingMapId= */ null,
-                /* leadDisplayId= */ Display.DEFAULT_DISPLAY);
+        createDefaultDisplay(layout2, device3);
         when(mDeviceStateToLayoutMapSpy.get(layoutState2)).thenReturn(layout2);
         assertThat(layout2.size()).isEqualTo(2);
         final int logicalId2 = layout2.getByAddress(info(device2).address).getLogicalDisplayId();
@@ -398,22 +383,11 @@
                 FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY);
 
         Layout layout = new Layout();
-        layout.createDisplayLocked(info(device1).address,
-                /* isDefault= */ true, /* isEnabled= */ true, /* displayGroup= */ null,
-                mIdProducer, /* brightnessThrottlingMapId= */ null,
-                /* leadDisplayId= */ Display.DEFAULT_DISPLAY);
-        layout.createDisplayLocked(info(device2).address,
-                /* isDefault= */ false, /* isEnabled= */ true, "group1", mIdProducer,
-                /* brightnessThrottlingMapId= */ null,
-                /* leadDisplayId= */ Display.DEFAULT_DISPLAY);
-        layout.createDisplayLocked(info(device3).address,
-                /* isDefault= */ false, /* isEnabled= */ true, "group1", mIdProducer,
-                /* brightnessThrottlingMapId= */ null,
-                /* leadDisplayId= */ Display.DEFAULT_DISPLAY);
-        layout.createDisplayLocked(info(device4).address,
-                /* isDefault= */ false, /* isEnabled= */ true, "group2", mIdProducer,
-                /* brightnessThrottlingMapId= */ null,
-                /* leadDisplayId= */ Display.DEFAULT_DISPLAY);
+        createDefaultDisplay(layout, device1);
+        createNonDefaultDisplay(layout, device2, /* enabled= */  true, /* group= */ "group1");
+        createNonDefaultDisplay(layout, device3, /* enabled= */  true, /* group= */ "group1");
+        createNonDefaultDisplay(layout, device4, /* enabled= */  true, /* group= */ "group2");
+
         when(mDeviceStateToLayoutMapSpy.get(STATE_DEFAULT)).thenReturn(layout);
 
         LogicalDisplay display1 = add(device1);
@@ -629,23 +603,21 @@
         Layout layout = new Layout();
         layout.createDisplayLocked(device1.getDisplayDeviceInfoLocked().address,
                 /* isDefault= */ true, /* isEnabled= */ true, /* displayGroup= */ null,
-                mIdProducer, /* brightnessThrottlingMapId= */ "concurrent",
-                /* leadDisplayId= */ Display.DEFAULT_DISPLAY);
+                mIdProducer, POSITION_UNKNOWN,
+                /* leadDisplayId= */ Display.DEFAULT_DISPLAY,
+                /* brightnessThrottlingMapId= */ "concurrent",
+                /* refreshRateZoneId= */ null, /* refreshRateThermalThrottlingMapId= */ null);
         layout.createDisplayLocked(device2.getDisplayDeviceInfoLocked().address,
                 /* isDefault= */ false, /* isEnabled= */ true, /* displayGroup= */ null,
-                mIdProducer, /* brightnessThrottlingMapId= */ "concurrent",
-                /* leadDisplayId= */ Display.DEFAULT_DISPLAY);
+                mIdProducer, POSITION_UNKNOWN,
+                /* leadDisplayId= */ Display.DEFAULT_DISPLAY,
+                /* brightnessThrottlingMapId= */ "concurrent",
+                /* refreshRateZoneId= */ null, /* refreshRateThermalThrottlingMapId= */ null);
         when(mDeviceStateToLayoutMapSpy.get(0)).thenReturn(layout);
 
         layout = new Layout();
-        layout.createDisplayLocked(device1.getDisplayDeviceInfoLocked().address,
-                /* isDefault= */ false, /* isEnabled= */ false, /* displayGroup= */ null,
-                mIdProducer, /* brightnessThrottlingMapId= */ null,
-                /* leadDisplayId= */ Display.DEFAULT_DISPLAY);
-        layout.createDisplayLocked(device2.getDisplayDeviceInfoLocked().address,
-                /* isDefault= */ true, /* isEnabled= */ true, /* displayGroup= */ null,
-                mIdProducer, /* brightnessThrottlingMapId= */ null,
-                /* leadDisplayId= */ Display.DEFAULT_DISPLAY);
+        createNonDefaultDisplay(layout, device1, /* enabled= */ false, /* group= */ null);
+        createDefaultDisplay(layout, device2);
         when(mDeviceStateToLayoutMapSpy.get(1)).thenReturn(layout);
         when(mDeviceStateToLayoutMapSpy.get(2)).thenReturn(layout);
 
@@ -722,30 +694,11 @@
                 TYPE_INTERNAL, 600, 900, DisplayDeviceInfo.FLAG_OWN_DISPLAY_GROUP);
 
         Layout threeDevicesEnabledLayout = new Layout();
-        threeDevicesEnabledLayout.createDisplayLocked(
-                displayAddressOne,
-                /* isDefault= */ true,
-                /* isEnabled= */ true,
-                /* displayGroup= */ null,
-                mIdProducer,
-                /* brightnessThrottlingMapId= */ null,
-                /* leadDisplayId= */ Display.DEFAULT_DISPLAY);
-        threeDevicesEnabledLayout.createDisplayLocked(
-                displayAddressTwo,
-                /* isDefault= */ false,
-                /* isEnabled= */ true,
-                /* displayGroup= */ null,
-                mIdProducer,
-                /* brightnessThrottlingMapId= */ null,
-                /* leadDisplayId= */ Display.DEFAULT_DISPLAY);
-        threeDevicesEnabledLayout.createDisplayLocked(
-                displayAddressThree,
-                /* isDefault= */ false,
-                /* isEnabled= */ true,
-                /* displayGroup= */ null,
-                mIdProducer,
-                /* brightnessThrottlingMapId= */ null,
-                /* leadDisplayId= */ Display.DEFAULT_DISPLAY);
+        createDefaultDisplay(threeDevicesEnabledLayout, displayAddressOne);
+        createNonDefaultDisplay(threeDevicesEnabledLayout, displayAddressTwo,
+                /* enabled= */ true, /* group= */ null);
+        createNonDefaultDisplay(threeDevicesEnabledLayout, displayAddressThree,
+                /* enabled= */ true, /* group= */ null);
 
         when(mDeviceStateToLayoutMapSpy.get(STATE_DEFAULT))
                 .thenReturn(threeDevicesEnabledLayout);
@@ -776,30 +729,11 @@
                 /* includeDisabled= */ false));
 
         Layout oneDeviceEnabledLayout = new Layout();
-        oneDeviceEnabledLayout.createDisplayLocked(
-                displayAddressOne,
-                /* isDefault= */ true,
-                /* isEnabled= */ true,
-                /* displayGroup= */ null,
-                mIdProducer,
-                /* brightnessThrottlingMapId= */ null,
-                /* leadDisplayId= */ Display.DEFAULT_DISPLAY);
-        oneDeviceEnabledLayout.createDisplayLocked(
-                displayAddressTwo,
-                /* isDefault= */ false,
-                /* isEnabled= */ false,
-                /* displayGroup= */ null,
-                mIdProducer,
-                /* brightnessThrottlingMapId= */ null,
-                /* leadDisplayId= */ Display.DEFAULT_DISPLAY);
-        oneDeviceEnabledLayout.createDisplayLocked(
-                displayAddressThree,
-                /* isDefault= */ false,
-                /* isEnabled= */ false,
-                /* displayGroup= */ null,
-                mIdProducer,
-                /* brightnessThrottlingMapId= */ null,
-                /* leadDisplayId= */ Display.DEFAULT_DISPLAY);
+        createDefaultDisplay(oneDeviceEnabledLayout, displayAddressOne);
+        createNonDefaultDisplay(oneDeviceEnabledLayout, displayAddressTwo,
+                /* enabled= */ false, /* group= */ null);
+        createNonDefaultDisplay(oneDeviceEnabledLayout, displayAddressThree,
+                /* enabled= */ false, /* group= */ null);
 
         when(mDeviceStateToLayoutMapSpy.get(0)).thenReturn(oneDeviceEnabledLayout);
         when(mDeviceStateToLayoutMapSpy.get(1)).thenReturn(threeDevicesEnabledLayout);
@@ -873,12 +807,13 @@
                 FLAG_REAR);
 
         Layout layout = new Layout();
-        layout.createDisplayLocked(device1.getDisplayDeviceInfoLocked().address,
-                true, true, null, mIdProducer, /* brightnessThrottlingMapId= */ null,
-                /* leadDisplayId= */ Display.DEFAULT_DISPLAY);
+        layout.createDefaultDisplayLocked(device1.getDisplayDeviceInfoLocked().address,
+                mIdProducer);
         layout.createDisplayLocked(device2.getDisplayDeviceInfoLocked().address,
-                false, true, null, mIdProducer, /* brightnessThrottlingMapId= */ null,
-                POSITION_REAR, Display.DEFAULT_DISPLAY);
+                /* isDefault= */ false, /* isEnabled= */ true, /* displayGroupName= */ null,
+                mIdProducer, POSITION_REAR, Display.DEFAULT_DISPLAY,
+                /* brightnessThrottlingMapId= */ null, /* refreshRateZoneId= */ null,
+                /* refreshRateThermalThrottlingMapId= */null);
         when(mDeviceStateToLayoutMapSpy.get(0)).thenReturn(layout);
 
         when(mDeviceStateToLayoutMapSpy.size()).thenReturn(1);
@@ -910,6 +845,27 @@
     // Helper Methods
     /////////////////
 
+    private void createDefaultDisplay(Layout layout, DisplayDevice device) {
+        createDefaultDisplay(layout, info(device).address);
+    }
+
+    private void createDefaultDisplay(Layout layout, DisplayAddress address) {
+        layout.createDefaultDisplayLocked(address, mIdProducer);
+    }
+
+    private void createNonDefaultDisplay(Layout layout, DisplayDevice device, boolean enabled,
+            String group) {
+        createNonDefaultDisplay(layout, info(device).address, enabled, group);
+    }
+
+    private void createNonDefaultDisplay(Layout layout, DisplayAddress address, boolean enabled,
+            String group) {
+        layout.createDisplayLocked(address, /* isDefault= */ false, enabled, group, mIdProducer,
+                Layout.Display.POSITION_UNKNOWN, Display.DEFAULT_DISPLAY,
+                /* brightnessThrottlingMapId= */ null, /* refreshRateZoneId= */ null,
+                /* refreshRateThrottlingMapId= */ null);
+    }
+
     private void advanceTime(long timeMs) {
         mLooper.moveTimeForward(1000);
         mLooper.dispatchAll();
diff --git a/services/tests/servicestests/src/com/android/server/dreams/DreamOverlayServiceTest.java b/services/tests/servicestests/src/com/android/server/dreams/DreamOverlayServiceTest.java
index 6c73f71..851d8f9 100644
--- a/services/tests/servicestests/src/com/android/server/dreams/DreamOverlayServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/dreams/DreamOverlayServiceTest.java
@@ -18,6 +18,8 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.verify;
 
@@ -39,10 +41,13 @@
 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.MockitoAnnotations;
 
+import java.util.concurrent.Executor;
+
 /**
  * A collection of tests to exercise {@link DreamOverlayService}.
  */
@@ -60,6 +65,9 @@
     @Mock
     IDreamOverlayCallback mOverlayCallback;
 
+    @Mock
+    Executor mExecutor;
+
     /**
      * {@link TestDreamOverlayService} is a simple {@link DreamOverlayService} implementation for
      * tracking interactions across {@link IDreamOverlay} binder interface. The service reports
@@ -78,8 +86,8 @@
 
         private final Monitor mMonitor;
 
-        TestDreamOverlayService(Monitor monitor) {
-            super();
+        TestDreamOverlayService(Monitor monitor, Executor executor) {
+            super(executor);
             mMonitor = monitor;
         }
 
@@ -118,13 +126,63 @@
     }
 
     /**
+     * Verifies that callbacks for subclasses are run on the provided executor.
+     */
+    @Test
+    public void testCallbacksRunOnExecutor() throws RemoteException {
+        final TestDreamOverlayService.Monitor monitor = Mockito.mock(
+                TestDreamOverlayService.Monitor.class);
+        final TestDreamOverlayService service = new TestDreamOverlayService(monitor, mExecutor);
+        final IBinder binder = service.onBind(new Intent());
+        final IDreamOverlay overlay = IDreamOverlay.Stub.asInterface(binder);
+
+        final IDreamOverlayClient client = getClient(overlay);
+
+        // Start the dream.
+        client.startDream(mLayoutParams, mOverlayCallback,
+                FIRST_DREAM_COMPONENT.flattenToString(), false);
+
+        // The callback should not have run yet.
+        verify(monitor, never()).onStartDream();
+
+        // Run the Runnable sent to the executor.
+        ArgumentCaptor<Runnable> mRunnableCaptor = ArgumentCaptor.forClass(Runnable.class);
+        verify(mExecutor).execute(mRunnableCaptor.capture());
+        mRunnableCaptor.getValue().run();
+
+        // Callback is run.
+        verify(monitor).onStartDream();
+
+        // Verify onWakeUp is run on the executor.
+        client.wakeUp();
+        verify(monitor, never()).onWakeUp();
+        mRunnableCaptor = ArgumentCaptor.forClass(Runnable.class);
+        verify(mExecutor).execute(mRunnableCaptor.capture());
+        mRunnableCaptor.getValue().run();
+        verify(monitor).onWakeUp();
+
+        // Verify onEndDream is run on the executor.
+        client.endDream();
+        verify(monitor, never()).onEndDream();
+        mRunnableCaptor = ArgumentCaptor.forClass(Runnable.class);
+        verify(mExecutor).execute(mRunnableCaptor.capture());
+        mRunnableCaptor.getValue().run();
+        verify(monitor).onEndDream();
+    }
+
+    /**
      * Verifies that only the currently started dream is able to affect the overlay.
      */
     @Test
     public void testOverlayClientInteraction() throws RemoteException {
+        doAnswer(invocation -> {
+            ((Runnable) invocation.getArgument(0)).run();
+            return null;
+        }).when(mExecutor).execute(any());
+
         final TestDreamOverlayService.Monitor monitor = Mockito.mock(
                 TestDreamOverlayService.Monitor.class);
-        final TestDreamOverlayService service = new TestDreamOverlayService(monitor);
+        final TestDreamOverlayService service = new TestDreamOverlayService(monitor, mExecutor);
         final IBinder binder = service.onBind(new Intent());
         final IDreamOverlay overlay = IDreamOverlay.Stub.asInterface(binder);
 
diff --git a/services/tests/servicestests/src/com/android/server/locales/AppUpdateTrackerTest.java b/services/tests/servicestests/src/com/android/server/locales/AppUpdateTrackerTest.java
deleted file mode 100644
index 2c5d97d..0000000
--- a/services/tests/servicestests/src/com/android/server/locales/AppUpdateTrackerTest.java
+++ /dev/null
@@ -1,190 +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.locales;
-
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyBoolean;
-import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.ArgumentMatchers.anyString;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.spy;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-
-import android.content.Context;
-import android.content.SharedPreferences;
-import android.os.Binder;
-import android.os.LocaleList;
-import android.util.ArraySet;
-
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-
-import java.util.Arrays;
-import java.util.Set;
-
-/**
- * Unit tests for {@link AppUpdateTracker}.
- */
-@RunWith(AndroidJUnit4.class)
-public class AppUpdateTrackerTest {
-    private static final String DEFAULT_PACKAGE_NAME = "com.android.myapp";
-    private static final int DEFAULT_UID = Binder.getCallingUid() + 100;
-    private static final int DEFAULT_USER_ID = 0;
-    private static final String DEFAULT_LOCALE_TAGS = "en-XC,ar-XB";
-    private static final LocaleList DEFAULT_LOCALES = LocaleList.forLanguageTags(
-            DEFAULT_LOCALE_TAGS);
-    private AppUpdateTracker mAppUpdateTracker;
-
-    @Mock
-    private Context mMockContext;
-    @Mock
-    private LocaleManagerService mMockLocaleManagerService;
-    @Mock
-    private ShadowLocaleManagerBackupHelper mMockBackupHelper;
-
-    @Before
-    public void setUp() throws Exception {
-        mMockContext = mock(Context.class);
-        mMockLocaleManagerService = mock(LocaleManagerService.class);
-        mMockBackupHelper = mock(ShadowLocaleManagerBackupHelper.class);
-        mAppUpdateTracker = spy(
-                new AppUpdateTracker(mMockContext, mMockLocaleManagerService, mMockBackupHelper));
-    }
-
-    @Test
-    public void testPackageUpgraded_localeEmpty_doNothing() throws Exception {
-        setUpLocalesForPackage(DEFAULT_PACKAGE_NAME, LocaleList.getEmptyLocaleList());
-        setUpPackageNamesForSp(new ArraySet<>(Arrays.asList(DEFAULT_PACKAGE_NAME)));
-        setUpPackageLocaleConfig(null, DEFAULT_PACKAGE_NAME);
-        setUpAppLocalesOptIn(true);
-
-        mAppUpdateTracker.onPackageUpdateFinished(DEFAULT_PACKAGE_NAME, DEFAULT_UID);
-        verifyNoLocalesCleared();
-    }
-
-    @Test
-    public void testPackageUpgraded_pkgNotInSp_doNothing() throws Exception {
-        setUpLocalesForPackage(DEFAULT_PACKAGE_NAME, DEFAULT_LOCALES);
-        String pkgNameA = "com.android.myAppA";
-        String pkgNameB = "com.android.myAppB";
-        setUpPackageNamesForSp(new ArraySet<>(Arrays.asList(pkgNameA, pkgNameB)));
-        setUpPackageLocaleConfig(null, DEFAULT_PACKAGE_NAME);
-        setUpAppLocalesOptIn(true);
-
-        mAppUpdateTracker.onPackageUpdateFinished(DEFAULT_PACKAGE_NAME, DEFAULT_UID);
-        verifyNoLocalesCleared();
-    }
-
-    @Test
-    public void testPackageUpgraded_appLocalesSupported_doNothing() throws Exception {
-        setUpLocalesForPackage(DEFAULT_PACKAGE_NAME, DEFAULT_LOCALES);
-        setUpPackageNamesForSp(new ArraySet<>(Arrays.asList(DEFAULT_PACKAGE_NAME)));
-        setUpPackageLocaleConfig(DEFAULT_LOCALES, DEFAULT_PACKAGE_NAME);
-
-        setUpAppLocalesOptIn(true);
-        mAppUpdateTracker.onPackageUpdateFinished(DEFAULT_PACKAGE_NAME, DEFAULT_UID);
-        verifyNoLocalesCleared();
-
-        setUpAppLocalesOptIn(false);
-        mAppUpdateTracker.onPackageUpdateFinished(DEFAULT_PACKAGE_NAME, DEFAULT_UID);
-        verifyNoLocalesCleared();
-
-        setUpAppLocalesOptIn(false);
-        setUpPackageLocaleConfig(null, DEFAULT_PACKAGE_NAME);
-        mAppUpdateTracker.onPackageUpdateFinished(DEFAULT_PACKAGE_NAME, DEFAULT_UID);
-        verifyNoLocalesCleared();
-    }
-
-    @Test
-    public void testPackageUpgraded_appLocalesNotSupported_clearAppLocale() throws Exception {
-        setUpLocalesForPackage(DEFAULT_PACKAGE_NAME, DEFAULT_LOCALES);
-        setUpPackageNamesForSp(new ArraySet<>(Arrays.asList(DEFAULT_PACKAGE_NAME)));
-        setUpPackageLocaleConfig(null, DEFAULT_PACKAGE_NAME);
-        setUpAppLocalesOptIn(true);
-
-        mAppUpdateTracker.onPackageUpdateFinished(DEFAULT_PACKAGE_NAME, DEFAULT_UID);
-        verify(mMockLocaleManagerService, times(1)).setApplicationLocales(DEFAULT_PACKAGE_NAME,
-                DEFAULT_USER_ID, LocaleList.forLanguageTags(""), false);
-
-        setUpPackageLocaleConfig(LocaleList.getEmptyLocaleList(), DEFAULT_PACKAGE_NAME);
-
-        mAppUpdateTracker.onPackageUpdateFinished(DEFAULT_PACKAGE_NAME, DEFAULT_UID);
-        verify(mMockLocaleManagerService, times(2)).setApplicationLocales(DEFAULT_PACKAGE_NAME,
-                DEFAULT_USER_ID, LocaleList.forLanguageTags(""), false);
-
-        setUpAppLocalesOptIn(false);
-
-        mAppUpdateTracker.onPackageUpdateFinished(DEFAULT_PACKAGE_NAME, DEFAULT_UID);
-        verify(mMockLocaleManagerService, times(3)).setApplicationLocales(DEFAULT_PACKAGE_NAME,
-                DEFAULT_USER_ID, LocaleList.forLanguageTags(""), false);
-    }
-
-    @Test
-    public void testPackageUpgraded_appLocalesNotInLocaleConfig_clearAppLocale() throws Exception {
-        setUpLocalesForPackage(DEFAULT_PACKAGE_NAME, DEFAULT_LOCALES);
-        setUpPackageNamesForSp(new ArraySet<>(Arrays.asList(DEFAULT_PACKAGE_NAME)));
-        setUpPackageLocaleConfig(LocaleList.forLanguageTags("hi,fr"), DEFAULT_PACKAGE_NAME);
-        setUpAppLocalesOptIn(true);
-
-        mAppUpdateTracker.onPackageUpdateFinished(DEFAULT_PACKAGE_NAME, DEFAULT_UID);
-        verify(mMockLocaleManagerService, times(1)).setApplicationLocales(DEFAULT_PACKAGE_NAME,
-                DEFAULT_USER_ID, LocaleList.forLanguageTags(""), false);
-
-        setUpAppLocalesOptIn(false);
-
-        mAppUpdateTracker.onPackageUpdateFinished(DEFAULT_PACKAGE_NAME, DEFAULT_UID);
-        verify(mMockLocaleManagerService, times(2)).setApplicationLocales(DEFAULT_PACKAGE_NAME,
-                DEFAULT_USER_ID, LocaleList.forLanguageTags(""), false);
-    }
-
-    private void setUpLocalesForPackage(String packageName, LocaleList locales) throws Exception {
-        doReturn(locales).when(mMockLocaleManagerService).getApplicationLocales(eq(packageName),
-                anyInt());
-    }
-
-    private void setUpPackageNamesForSp(Set<String> packageNames) {
-        SharedPreferences mockSharedPreference = mock(SharedPreferences.class);
-        doReturn(mockSharedPreference).when(mMockBackupHelper).getPersistedInfo();
-        doReturn(packageNames).when(mockSharedPreference).getStringSet(anyString(), any());
-    }
-
-    private void setUpPackageLocaleConfig(LocaleList locales, String packageName) {
-        doReturn(locales).when(mAppUpdateTracker).getPackageLocales(eq(packageName), anyInt());
-    }
-
-    private void setUpAppLocalesOptIn(boolean optIn) {
-        doReturn(optIn).when(mAppUpdateTracker).isSettingsAppLocalesOptIn();
-    }
-
-    /**
-     * Verifies that no app locales needs to be cleared for any package.
-     *
-     * <p>If {@link LocaleManagerService#setApplicationLocales} is not invoked when receiving the
-     * callback of package upgraded, we can conclude that no app locales needs to be cleared.
-     */
-    private void verifyNoLocalesCleared() throws Exception {
-        verify(mMockLocaleManagerService, times(0)).setApplicationLocales(anyString(), anyInt(),
-                any(), anyBoolean());
-    }
-}
diff --git a/services/tests/servicestests/src/com/android/server/locales/LocaleManagerBackupRestoreTest.java b/services/tests/servicestests/src/com/android/server/locales/LocaleManagerBackupRestoreTest.java
index 1b8958b..13371cc 100644
--- a/services/tests/servicestests/src/com/android/server/locales/LocaleManagerBackupRestoreTest.java
+++ b/services/tests/servicestests/src/com/android/server/locales/LocaleManagerBackupRestoreTest.java
@@ -142,7 +142,6 @@
         mMockDelegateAppLocalePackages = mock(SharedPreferences.class);
         mMockSpEditor = mock(SharedPreferences.Editor.class);
         SystemAppUpdateTracker systemAppUpdateTracker = mock(SystemAppUpdateTracker.class);
-        AppUpdateTracker appUpdateTracker = mock(AppUpdateTracker.class);
 
         doReturn(mMockPackageManager).when(mMockContext).getPackageManager();
         doReturn(mMockSpEditor).when(mMockDelegateAppLocalePackages).edit();
@@ -158,7 +157,7 @@
 
         mUserMonitor = mBackupHelper.getUserMonitor();
         mPackageMonitor = new LocaleManagerServicePackageMonitor(mBackupHelper,
-            systemAppUpdateTracker, appUpdateTracker, mMockLocaleManagerService);
+            systemAppUpdateTracker, mMockLocaleManagerService);
         setCurrentTimeMillis(DEFAULT_CREATION_TIME_MILLIS);
     }
 
diff --git a/services/tests/servicestests/src/com/android/server/locales/SystemAppUpdateTrackerTest.java b/services/tests/servicestests/src/com/android/server/locales/SystemAppUpdateTrackerTest.java
index 9429462..da9de25 100644
--- a/services/tests/servicestests/src/com/android/server/locales/SystemAppUpdateTrackerTest.java
+++ b/services/tests/servicestests/src/com/android/server/locales/SystemAppUpdateTrackerTest.java
@@ -138,9 +138,8 @@
         mSystemAppUpdateTracker = new SystemAppUpdateTracker(mMockContext,
             mLocaleManagerService, mStoragefile);
 
-        AppUpdateTracker appUpdateTracker = mock(AppUpdateTracker.class);
         mPackageMonitor = new LocaleManagerServicePackageMonitor(mockLocaleManagerBackupHelper,
-                mSystemAppUpdateTracker, appUpdateTracker, mLocaleManagerService);
+                mSystemAppUpdateTracker, mLocaleManagerService);
     }
 
     @After
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/RebootEscrowManagerTests.java b/services/tests/servicestests/src/com/android/server/locksettings/RebootEscrowManagerTests.java
index d9d0715..64e6236 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/RebootEscrowManagerTests.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/RebootEscrowManagerTests.java
@@ -74,6 +74,8 @@
 import java.io.File;
 import java.io.IOException;
 import java.util.ArrayList;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
 import java.util.function.Consumer;
 
 import javax.crypto.SecretKey;
@@ -324,16 +326,30 @@
         mInjected = mock(MockableRebootEscrowInjected.class);
         mMockInjector = new MockInjector(mContext, mUserManager, mRebootEscrow,
                 mKeyStoreManager, mStorage, mInjected);
-        mService = new RebootEscrowManager(mMockInjector, mCallbacks, mStorage);
         HandlerThread thread = new HandlerThread("RebootEscrowManagerTest");
         thread.start();
         mHandler = new Handler(thread.getLooper());
+        mService = new RebootEscrowManager(mMockInjector, mCallbacks, mStorage, mHandler);
+
     }
 
     private void setServerBasedRebootEscrowProvider() throws Exception {
         mMockInjector = new MockInjector(mContext, mUserManager, mServiceConnection,
                 mKeyStoreManager, mStorage, mInjected);
-        mService = new RebootEscrowManager(mMockInjector, mCallbacks, mStorage);
+        mService = new RebootEscrowManager(mMockInjector, mCallbacks, mStorage, mHandler);
+    }
+
+    private void waitForHandler() throws InterruptedException {
+        // Wait for handler to complete processing.
+        CountDownLatch latch = new CountDownLatch(1);
+        mHandler.post(latch::countDown);
+        assertTrue(latch.await(5, TimeUnit.SECONDS));
+
+    }
+
+    private void callToRebootEscrowIfNeededAndWait(int userId) throws InterruptedException {
+        mService.callToRebootEscrowIfNeeded(userId, FAKE_SP_VERSION, FAKE_AUTH_TOKEN);
+        waitForHandler();
     }
 
     @Test
@@ -343,7 +359,7 @@
         mService.prepareRebootEscrow();
 
         clearInvocations(mRebootEscrow);
-        mService.callToRebootEscrowIfNeeded(PRIMARY_USER_ID, FAKE_SP_VERSION, FAKE_AUTH_TOKEN);
+        callToRebootEscrowIfNeededAndWait(PRIMARY_USER_ID);
         verify(mockListener).onPreparedForReboot(eq(true));
         verify(mRebootEscrow, never()).storeKey(any());
     }
@@ -355,8 +371,7 @@
         mService.setRebootEscrowListener(mockListener);
         mService.prepareRebootEscrow();
 
-        mService.callToRebootEscrowIfNeeded(PRIMARY_USER_ID, FAKE_SP_VERSION, FAKE_AUTH_TOKEN);
-        verify(mockListener).onPreparedForReboot(eq(true));
+        callToRebootEscrowIfNeededAndWait(PRIMARY_USER_ID);
         verify(mServiceConnection, never()).wrapBlob(any(), anyLong(), anyLong());
         assertFalse(mStorage.hasRebootEscrowServerBlob());
     }
@@ -366,7 +381,7 @@
         RebootEscrowListener mockListener = mock(RebootEscrowListener.class);
         mService.setRebootEscrowListener(mockListener);
         mService.prepareRebootEscrow();
-        mService.callToRebootEscrowIfNeeded(PRIMARY_USER_ID, FAKE_SP_VERSION, FAKE_AUTH_TOKEN);
+        callToRebootEscrowIfNeededAndWait(PRIMARY_USER_ID);
         verify(mockListener).onPreparedForReboot(eq(true));
 
         clearInvocations(mRebootEscrow);
@@ -390,7 +405,7 @@
         mService.prepareRebootEscrow();
 
         clearInvocations(mRebootEscrow);
-        mService.callToRebootEscrowIfNeeded(PRIMARY_USER_ID, FAKE_SP_VERSION, FAKE_AUTH_TOKEN);
+        callToRebootEscrowIfNeededAndWait(PRIMARY_USER_ID);
         verify(mockListener).onPreparedForReboot(eq(true));
         verify(mRebootEscrow, never()).storeKey(any());
 
@@ -414,7 +429,7 @@
         mService.prepareRebootEscrow();
 
         clearInvocations(mServiceConnection);
-        mService.callToRebootEscrowIfNeeded(PRIMARY_USER_ID, FAKE_SP_VERSION, FAKE_AUTH_TOKEN);
+        callToRebootEscrowIfNeededAndWait(PRIMARY_USER_ID);
         verify(mockListener).onPreparedForReboot(eq(true));
         verify(mServiceConnection, never()).wrapBlob(any(), anyLong(), anyLong());
 
@@ -435,7 +450,7 @@
         mService.prepareRebootEscrow();
 
         clearInvocations(mRebootEscrow);
-        mService.callToRebootEscrowIfNeeded(PRIMARY_USER_ID, FAKE_SP_VERSION, FAKE_AUTH_TOKEN);
+        callToRebootEscrowIfNeededAndWait(PRIMARY_USER_ID);
         verify(mockListener).onPreparedForReboot(eq(true));
         verify(mRebootEscrow, never()).storeKey(any());
 
@@ -453,10 +468,9 @@
         mService.prepareRebootEscrow();
 
         clearInvocations(mRebootEscrow);
-        mService.callToRebootEscrowIfNeeded(PRIMARY_USER_ID, FAKE_SP_VERSION, FAKE_AUTH_TOKEN);
+        callToRebootEscrowIfNeededAndWait(PRIMARY_USER_ID);
         verify(mockListener).onPreparedForReboot(eq(true));
-        mService.callToRebootEscrowIfNeeded(SECURE_SECONDARY_USER_ID, FAKE_SP_VERSION,
-                FAKE_AUTH_TOKEN);
+        callToRebootEscrowIfNeededAndWait(SECURE_SECONDARY_USER_ID);
         verify(mRebootEscrow, never()).storeKey(any());
 
         assertTrue(mStorage.hasRebootEscrow(PRIMARY_USER_ID));
@@ -488,7 +502,7 @@
         mService.prepareRebootEscrow();
 
         clearInvocations(mRebootEscrow);
-        mService.callToRebootEscrowIfNeeded(PRIMARY_USER_ID, FAKE_SP_VERSION, FAKE_AUTH_TOKEN);
+        callToRebootEscrowIfNeededAndWait(PRIMARY_USER_ID);
         verify(mockListener).onPreparedForReboot(eq(true));
         verify(mRebootEscrow, never()).storeKey(any());
 
@@ -511,7 +525,7 @@
         mService.prepareRebootEscrow();
 
         clearInvocations(mRebootEscrow);
-        mService.callToRebootEscrowIfNeeded(PRIMARY_USER_ID, FAKE_SP_VERSION, FAKE_AUTH_TOKEN);
+        callToRebootEscrowIfNeededAndWait(PRIMARY_USER_ID);
         verify(mockListener).onPreparedForReboot(eq(true));
 
         verify(mRebootEscrow, never()).storeKey(any());
@@ -554,7 +568,7 @@
         mService.prepareRebootEscrow();
 
         clearInvocations(mServiceConnection);
-        mService.callToRebootEscrowIfNeeded(PRIMARY_USER_ID, FAKE_SP_VERSION, FAKE_AUTH_TOKEN);
+        callToRebootEscrowIfNeededAndWait(PRIMARY_USER_ID);
         verify(mockListener).onPreparedForReboot(eq(true));
         verify(mServiceConnection, never()).wrapBlob(any(), anyLong(), anyLong());
 
@@ -598,7 +612,7 @@
         mService.prepareRebootEscrow();
 
         clearInvocations(mServiceConnection);
-        mService.callToRebootEscrowIfNeeded(PRIMARY_USER_ID, FAKE_SP_VERSION, FAKE_AUTH_TOKEN);
+        callToRebootEscrowIfNeededAndWait(PRIMARY_USER_ID);
         verify(mockListener).onPreparedForReboot(eq(true));
         verify(mServiceConnection, never()).wrapBlob(any(), anyLong(), anyLong());
 
@@ -643,7 +657,7 @@
         mService.prepareRebootEscrow();
 
         clearInvocations(mServiceConnection);
-        mService.callToRebootEscrowIfNeeded(PRIMARY_USER_ID, FAKE_SP_VERSION, FAKE_AUTH_TOKEN);
+        callToRebootEscrowIfNeededAndWait(PRIMARY_USER_ID);
         verify(mockListener).onPreparedForReboot(eq(true));
         verify(mServiceConnection, never()).wrapBlob(any(), anyLong(), anyLong());
 
@@ -689,7 +703,7 @@
         mService.prepareRebootEscrow();
 
         clearInvocations(mServiceConnection);
-        mService.callToRebootEscrowIfNeeded(PRIMARY_USER_ID, FAKE_SP_VERSION, FAKE_AUTH_TOKEN);
+        callToRebootEscrowIfNeededAndWait(PRIMARY_USER_ID);
         verify(mockListener).onPreparedForReboot(eq(true));
         verify(mServiceConnection, never()).wrapBlob(any(), anyLong(), anyLong());
 
@@ -738,7 +752,7 @@
         mService.prepareRebootEscrow();
 
         clearInvocations(mServiceConnection);
-        mService.callToRebootEscrowIfNeeded(PRIMARY_USER_ID, FAKE_SP_VERSION, FAKE_AUTH_TOKEN);
+        callToRebootEscrowIfNeededAndWait(PRIMARY_USER_ID);
         verify(mockListener).onPreparedForReboot(eq(true));
         verify(mServiceConnection, never()).wrapBlob(any(), anyLong(), anyLong());
 
@@ -791,7 +805,7 @@
         mService.prepareRebootEscrow();
 
         clearInvocations(mServiceConnection);
-        mService.callToRebootEscrowIfNeeded(PRIMARY_USER_ID, FAKE_SP_VERSION, FAKE_AUTH_TOKEN);
+        callToRebootEscrowIfNeededAndWait(PRIMARY_USER_ID);
         verify(mockListener).onPreparedForReboot(eq(true));
         verify(mServiceConnection, never()).wrapBlob(any(), anyLong(), anyLong());
 
@@ -846,7 +860,7 @@
         mService.prepareRebootEscrow();
 
         clearInvocations(mServiceConnection);
-        mService.callToRebootEscrowIfNeeded(PRIMARY_USER_ID, FAKE_SP_VERSION, FAKE_AUTH_TOKEN);
+        callToRebootEscrowIfNeededAndWait(PRIMARY_USER_ID);
         verify(mockListener).onPreparedForReboot(eq(true));
         verify(mServiceConnection, never()).wrapBlob(any(), anyLong(), anyLong());
 
@@ -893,7 +907,7 @@
         mService.prepareRebootEscrow();
 
         clearInvocations(mServiceConnection);
-        mService.callToRebootEscrowIfNeeded(PRIMARY_USER_ID, FAKE_SP_VERSION, FAKE_AUTH_TOKEN);
+        callToRebootEscrowIfNeededAndWait(PRIMARY_USER_ID);
         verify(mockListener).onPreparedForReboot(eq(true));
         verify(mServiceConnection, never()).wrapBlob(any(), anyLong(), anyLong());
 
@@ -949,7 +963,7 @@
         mService.prepareRebootEscrow();
 
         clearInvocations(mServiceConnection);
-        mService.callToRebootEscrowIfNeeded(PRIMARY_USER_ID, FAKE_SP_VERSION, FAKE_AUTH_TOKEN);
+        callToRebootEscrowIfNeededAndWait(PRIMARY_USER_ID);
         verify(mockListener).onPreparedForReboot(eq(true));
         verify(mServiceConnection, never()).wrapBlob(any(), anyLong(), anyLong());
 
@@ -1008,7 +1022,7 @@
         mService.prepareRebootEscrow();
 
         clearInvocations(mServiceConnection);
-        mService.callToRebootEscrowIfNeeded(PRIMARY_USER_ID, FAKE_SP_VERSION, FAKE_AUTH_TOKEN);
+        callToRebootEscrowIfNeededAndWait(PRIMARY_USER_ID);
         verify(mockListener).onPreparedForReboot(eq(true));
         verify(mServiceConnection, never()).wrapBlob(any(), anyLong(), anyLong());
 
@@ -1068,7 +1082,7 @@
         mService.prepareRebootEscrow();
 
         clearInvocations(mServiceConnection);
-        mService.callToRebootEscrowIfNeeded(PRIMARY_USER_ID, FAKE_SP_VERSION, FAKE_AUTH_TOKEN);
+        callToRebootEscrowIfNeededAndWait(PRIMARY_USER_ID);
         verify(mockListener).onPreparedForReboot(eq(true));
         verify(mServiceConnection, never()).wrapBlob(any(), anyLong(), anyLong());
 
@@ -1124,7 +1138,7 @@
         mService.prepareRebootEscrow();
 
         clearInvocations(mServiceConnection);
-        mService.callToRebootEscrowIfNeeded(PRIMARY_USER_ID, FAKE_SP_VERSION, FAKE_AUTH_TOKEN);
+        callToRebootEscrowIfNeededAndWait(PRIMARY_USER_ID);
         verify(mockListener).onPreparedForReboot(eq(true));
         verify(mServiceConnection, never()).wrapBlob(any(), anyLong(), anyLong());
 
@@ -1176,7 +1190,7 @@
         mService.prepareRebootEscrow();
 
         clearInvocations(mRebootEscrow);
-        mService.callToRebootEscrowIfNeeded(PRIMARY_USER_ID, FAKE_SP_VERSION, FAKE_AUTH_TOKEN);
+        callToRebootEscrowIfNeededAndWait(PRIMARY_USER_ID);
         verify(mockListener).onPreparedForReboot(eq(true));
 
         verify(mRebootEscrow, never()).storeKey(any());
@@ -1207,7 +1221,7 @@
         mService.prepareRebootEscrow();
 
         clearInvocations(mRebootEscrow);
-        mService.callToRebootEscrowIfNeeded(PRIMARY_USER_ID, FAKE_SP_VERSION, FAKE_AUTH_TOKEN);
+        callToRebootEscrowIfNeededAndWait(PRIMARY_USER_ID);
         verify(mockListener).onPreparedForReboot(eq(true));
 
         verify(mRebootEscrow, never()).storeKey(any());
@@ -1235,7 +1249,7 @@
         mService.prepareRebootEscrow();
 
         clearInvocations(mRebootEscrow);
-        mService.callToRebootEscrowIfNeeded(PRIMARY_USER_ID, FAKE_SP_VERSION, FAKE_AUTH_TOKEN);
+        callToRebootEscrowIfNeededAndWait(PRIMARY_USER_ID);
         verify(mockListener).onPreparedForReboot(eq(true));
 
         verify(mRebootEscrow, never()).storeKey(any());
@@ -1274,7 +1288,7 @@
         mService.prepareRebootEscrow();
 
         clearInvocations(mRebootEscrow);
-        mService.callToRebootEscrowIfNeeded(PRIMARY_USER_ID, FAKE_SP_VERSION, FAKE_AUTH_TOKEN);
+        callToRebootEscrowIfNeededAndWait(PRIMARY_USER_ID);
         verify(mockListener).onPreparedForReboot(eq(true));
 
         verify(mRebootEscrow, never()).storeKey(any());
@@ -1309,7 +1323,7 @@
         mService.prepareRebootEscrow();
 
         clearInvocations(mRebootEscrow);
-        mService.callToRebootEscrowIfNeeded(PRIMARY_USER_ID, FAKE_SP_VERSION, FAKE_AUTH_TOKEN);
+        callToRebootEscrowIfNeededAndWait(PRIMARY_USER_ID);
         verify(mockListener).onPreparedForReboot(eq(true));
         assertTrue(mStorage.hasRebootEscrow(PRIMARY_USER_ID));
         verify(mRebootEscrow, never()).storeKey(any());
diff --git a/services/tests/servicestests/src/com/android/server/vibrator/InputDeviceDelegateTest.java b/services/tests/servicestests/src/com/android/server/vibrator/InputDeviceDelegateTest.java
index 6edef75..07b4345 100644
--- a/services/tests/servicestests/src/com/android/server/vibrator/InputDeviceDelegateTest.java
+++ b/services/tests/servicestests/src/com/android/server/vibrator/InputDeviceDelegateTest.java
@@ -34,6 +34,7 @@
 import android.hardware.input.IInputDevicesChangedListener;
 import android.hardware.input.IInputManager;
 import android.hardware.input.InputManager;
+import android.hardware.input.InputManagerGlobal;
 import android.os.CombinedVibration;
 import android.os.Handler;
 import android.os.Process;
@@ -82,8 +83,8 @@
     @Before
     public void setUp() throws Exception {
         mTestLooper = new TestLooper();
-        mContextSpy = spy(new ContextWrapper(InstrumentationRegistry.getContext()));
         InputManager inputManager = InputManager.resetInstance(mIInputManagerMock);
+        mContextSpy = spy(new ContextWrapper(InstrumentationRegistry.getContext()));
 
         when(mContextSpy.getSystemService(eq(Context.INPUT_SERVICE))).thenReturn(inputManager);
         doAnswer(invocation -> mIInputDevicesChangedListener = invocation.getArgument(0))
@@ -314,7 +315,7 @@
             deviceIdsAndGenerations[i + 1] = 2; // update by increasing it's generation to 2.
         }
         // Force initialization of mIInputDevicesChangedListener, if it still haven't
-        InputManager.getInstance().getInputDeviceIds();
+        InputManagerGlobal.getInstance().getInputDeviceIds();
         mIInputDevicesChangedListener.onInputDevicesChanged(deviceIdsAndGenerations);
         // Makes sure all callbacks from InputDeviceDelegate are executed.
         mTestLooper.dispatchAll();
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 79f69ee..06bcb91 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
@@ -63,6 +63,7 @@
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.clearInvocations;
 import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.mock;
@@ -70,6 +71,7 @@
 import static org.mockito.Mockito.reset;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyZeroInteractions;
 import static org.mockito.Mockito.when;
 
 import android.app.AppOpsManager;
@@ -161,7 +163,6 @@
     private static final String SYSTEM_PKG = "android";
     private static final int SYSTEM_UID = 1000;
     private static final UserHandle USER2 = UserHandle.of(10);
-    private static final String TEST_CHANNEL_ID = "test_channel_id";
     private static final String TEST_AUTHORITY = "test";
     private static final Uri SOUND_URI =
             Uri.parse("content://" + TEST_AUTHORITY + "/internal/audio/media/10");
@@ -284,9 +285,9 @@
                 anyString(), eq(null), anyString())).thenReturn(MODE_DEFAULT);
 
         ArrayMap<Pair<Integer, String>, Pair<Boolean, Boolean>> appPermissions = new ArrayMap<>();
-        appPermissions.put(new Pair(UID_P, PKG_P), new Pair(true, false));
-        appPermissions.put(new Pair(UID_O, PKG_O), new Pair(true, false));
-        appPermissions.put(new Pair(UID_N_MR1, PKG_N_MR1), new Pair(true, false));
+        appPermissions.put(new Pair<>(UID_P, PKG_P), new Pair<>(true, false));
+        appPermissions.put(new Pair<>(UID_O, PKG_O), new Pair<>(true, false));
+        appPermissions.put(new Pair<>(UID_N_MR1, PKG_N_MR1), new Pair<>(true, false));
 
         when(mPermissionHelper.getNotificationPermissionValues(USER_SYSTEM))
                 .thenReturn(appPermissions);
@@ -422,8 +423,8 @@
         assertTrue(mHelper.createNotificationChannel(package10, uid10, channel10, true, false));
 
         ArrayMap<Pair<Integer, String>, Pair<Boolean, Boolean>> appPermissions = new ArrayMap<>();
-        appPermissions.put(new Pair(uid0, package0), new Pair(false, false));
-        appPermissions.put(new Pair(uid10, package10), new Pair(true, false));
+        appPermissions.put(new Pair<>(uid0, package0), new Pair<>(false, false));
+        appPermissions.put(new Pair<>(uid10, package10), new Pair<>(true, false));
 
         when(mPermissionHelper.getNotificationPermissionValues(10))
                 .thenReturn(appPermissions);
@@ -454,7 +455,7 @@
         assertTrue(mHelper.createNotificationChannel(package0, uid0, channel0, true, false));
 
         ArrayMap<Pair<Integer, String>, Pair<Boolean, Boolean>> appPermissions = new ArrayMap<>();
-        appPermissions.put(new Pair(uid0, package0), new Pair(true, false));
+        appPermissions.put(new Pair<>(uid0, package0), new Pair<>(true, false));
 
         when(mPermissionHelper.getNotificationPermissionValues(USER_SYSTEM))
                 .thenReturn(appPermissions);
@@ -1009,11 +1010,11 @@
                 mPermissionHelper, mLogger, mAppOpsManager, mStatsEventBuilderFactory, false);
 
         ArrayMap<Pair<Integer, String>, Pair<Boolean, Boolean>> appPermissions = new ArrayMap<>();
-        appPermissions.put(new Pair(1, "first"), new Pair(true, false));
-        appPermissions.put(new Pair(3, "third"), new Pair(false, false));
-        appPermissions.put(new Pair(UID_P, PKG_P), new Pair(true, false));
-        appPermissions.put(new Pair(UID_O, PKG_O), new Pair(false, false));
-        appPermissions.put(new Pair(UID_N_MR1, PKG_N_MR1), new Pair(true, false));
+        appPermissions.put(new Pair<>(1, "first"), new Pair<>(true, false));
+        appPermissions.put(new Pair<>(3, "third"), new Pair<>(false, false));
+        appPermissions.put(new Pair<>(UID_P, PKG_P), new Pair<>(true, false));
+        appPermissions.put(new Pair<>(UID_O, PKG_O), new Pair<>(false, false));
+        appPermissions.put(new Pair<>(UID_N_MR1, PKG_N_MR1), new Pair<>(true, false));
 
         when(mPermissionHelper.getNotificationPermissionValues(USER_SYSTEM))
                 .thenReturn(appPermissions);
@@ -1089,11 +1090,11 @@
                 mPermissionHelper, mLogger, mAppOpsManager, mStatsEventBuilderFactory, false);
 
         ArrayMap<Pair<Integer, String>, Pair<Boolean, Boolean>> appPermissions = new ArrayMap<>();
-        appPermissions.put(new Pair(1, "first"), new Pair(true, false));
-        appPermissions.put(new Pair(3, "third"), new Pair(false, false));
-        appPermissions.put(new Pair(UID_P, PKG_P), new Pair(true, false));
-        appPermissions.put(new Pair(UID_O, PKG_O), new Pair(false, false));
-        appPermissions.put(new Pair(UID_N_MR1, PKG_N_MR1), new Pair(true, false));
+        appPermissions.put(new Pair<>(1, "first"), new Pair<>(true, false));
+        appPermissions.put(new Pair<>(3, "third"), new Pair<>(false, false));
+        appPermissions.put(new Pair<>(UID_P, PKG_P), new Pair<>(true, false));
+        appPermissions.put(new Pair<>(UID_O, PKG_O), new Pair<>(false, false));
+        appPermissions.put(new Pair<>(UID_N_MR1, PKG_N_MR1), new Pair<>(true, false));
 
         when(mPermissionHelper.getNotificationPermissionValues(USER_SYSTEM))
                 .thenReturn(appPermissions);
@@ -1175,8 +1176,8 @@
                 mPermissionHelper, mLogger, mAppOpsManager, mStatsEventBuilderFactory, false);
 
         ArrayMap<Pair<Integer, String>, Pair<Boolean, Boolean>> appPermissions = new ArrayMap<>();
-        appPermissions.put(new Pair(UID_P, PKG_P), new Pair(true, false));
-        appPermissions.put(new Pair(UID_O, PKG_O), new Pair(false, false));
+        appPermissions.put(new Pair<>(UID_P, PKG_P), new Pair<>(true, false));
+        appPermissions.put(new Pair<>(UID_O, PKG_O), new Pair<>(false, false));
         when(mPermissionHelper.getNotificationPermissionValues(USER_SYSTEM))
                 .thenReturn(appPermissions);
 
@@ -1254,11 +1255,11 @@
                 mPermissionHelper, mLogger, mAppOpsManager, mStatsEventBuilderFactory, false);
 
         ArrayMap<Pair<Integer, String>, Pair<Boolean, Boolean>> appPermissions = new ArrayMap<>();
-        appPermissions.put(new Pair(1, "first"), new Pair(true, false));
-        appPermissions.put(new Pair(3, "third"), new Pair(false, false));
-        appPermissions.put(new Pair(UID_P, PKG_P), new Pair(true, false));
-        appPermissions.put(new Pair(UID_O, PKG_O), new Pair(false, false));
-        appPermissions.put(new Pair(UID_N_MR1, PKG_N_MR1), new Pair(true, false));
+        appPermissions.put(new Pair<>(1, "first"), new Pair<>(true, false));
+        appPermissions.put(new Pair<>(3, "third"), new Pair<>(false, false));
+        appPermissions.put(new Pair<>(UID_P, PKG_P), new Pair<>(true, false));
+        appPermissions.put(new Pair<>(UID_O, PKG_O), new Pair<>(false, false));
+        appPermissions.put(new Pair<>(UID_N_MR1, PKG_N_MR1), new Pair<>(true, false));
 
         when(mPermissionHelper.getNotificationPermissionValues(USER_SYSTEM))
                 .thenReturn(appPermissions);
@@ -1372,7 +1373,7 @@
     @Test
     public void testBackupRestoreXml_withNullSoundUri() throws Exception {
         ArrayMap<Pair<Integer, String>, Pair<Boolean, Boolean>> appPermissions = new ArrayMap<>();
-        appPermissions.put(new Pair(UID_N_MR1, PKG_N_MR1), new Pair(true, false));
+        appPermissions.put(new Pair<>(UID_N_MR1, PKG_N_MR1), new Pair<>(true, false));
 
         when(mPermissionHelper.getNotificationPermissionValues(USER_SYSTEM))
                 .thenReturn(appPermissions);
@@ -2605,10 +2606,8 @@
 
     @Test
     public void testClearData() {
-        ArraySet<String> pkg = new ArraySet<>();
-        pkg.add(PKG_O);
         ArraySet<Pair<String, Integer>> pkgPair = new ArraySet<>();
-        pkgPair.add(new Pair(PKG_O, UID_O));
+        pkgPair.add(new Pair<>(PKG_O, UID_O));
         mHelper.createNotificationChannel(PKG_O, UID_O, getChannel(), true, false);
         mHelper.createNotificationChannelGroup(
                 PKG_O, UID_O, new NotificationChannelGroup("1", "bye"), true);
@@ -2879,10 +2878,10 @@
 
         // package permissions map to be passed in
         ArrayMap<Pair<Integer, String>, Pair<Boolean, Boolean>> appPermissions = new ArrayMap<>();
-        appPermissions.put(new Pair(1, "first"), new Pair(true, false));    // not in local prefs
-        appPermissions.put(new Pair(3, "third"), new Pair(false, false));   // not in local prefs
-        appPermissions.put(new Pair(UID_P, PKG_P), new Pair(true, false));  // in local prefs
-        appPermissions.put(new Pair(UID_O, PKG_O), new Pair(false, false)); // in local prefs
+        appPermissions.put(new Pair<>(1, "first"), new Pair<>(true, false)); // not in local prefs
+        appPermissions.put(new Pair<>(3, "third"), new Pair<>(false, false)); // not in local prefs
+        appPermissions.put(new Pair<>(UID_P, PKG_P), new Pair<>(true, false)); // in local prefs
+        appPermissions.put(new Pair<>(UID_O, PKG_O), new Pair<>(false, false)); // in local prefs
 
         NotificationChannel channel1 =
                 new NotificationChannel("id1", "name1", NotificationManager.IMPORTANCE_HIGH);
@@ -2901,15 +2900,15 @@
         ArrayMap<Pair<Integer, String>, String> expected = new ArrayMap<>();
 
         // packages that only exist via the app permissions; should be present
-        expected.put(new Pair(UserHandle.getUserId(1), "first"), "DEFAULT");
-        expected.put(new Pair(UserHandle.getUserId(3), "third"), "NONE");
+        expected.put(new Pair<>(UserHandle.getUserId(1), "first"), "DEFAULT");
+        expected.put(new Pair<>(UserHandle.getUserId(3), "third"), "NONE");
 
         // packages that exist in both app permissions & local preferences
-        expected.put(new Pair(UserHandle.getUserId(UID_P), PKG_P), "DEFAULT");
-        expected.put(new Pair(UserHandle.getUserId(UID_O), PKG_O), "NONE");
+        expected.put(new Pair<>(UserHandle.getUserId(UID_P), PKG_P), "DEFAULT");
+        expected.put(new Pair<>(UserHandle.getUserId(UID_O), PKG_O), "NONE");
 
         // package that only exists in local preferences; expect no importance output
-        expected.put(new Pair(UserHandle.getUserId(UID_N_MR1), PKG_N_MR1), null);
+        expected.put(new Pair<>(UserHandle.getUserId(UID_N_MR1), PKG_N_MR1), null);
 
         JSONArray actual = (JSONArray) mHelper.dumpJson(
                 new NotificationManagerService.DumpFilter(), appPermissions)
@@ -2918,7 +2917,7 @@
         for (int i = 0; i < actual.length(); i++) {
             JSONObject pkgInfo = actual.getJSONObject(i);
             Pair<Integer, String> pkgKey =
-                    new Pair(pkgInfo.getInt("userId"), pkgInfo.getString("packageName"));
+                    new Pair<>(pkgInfo.getInt("userId"), pkgInfo.getString("packageName"));
             assertTrue(expected.containsKey(pkgKey));
             if (pkgInfo.has("importance")) {
                 assertThat(pkgInfo.getString("importance")).isEqualTo(expected.get(pkgKey));
@@ -2957,16 +2956,16 @@
         // have their permission set to false, and not based on PackagePreferences importance
 
         ArrayMap<Pair<Integer, String>, Pair<Boolean, Boolean>> appPermissions = new ArrayMap<>();
-        appPermissions.put(new Pair(1, "first"), new Pair(true, false));    // not in local prefs
-        appPermissions.put(new Pair(3, "third"), new Pair(false, false));   // not in local prefs
-        appPermissions.put(new Pair(UID_O, PKG_O), new Pair(false, false)); // in local prefs
+        appPermissions.put(new Pair<>(1, "first"), new Pair<>(true, false)); // not in local prefs
+        appPermissions.put(new Pair<>(3, "third"), new Pair<>(false, false)); // not in local prefs
+        appPermissions.put(new Pair<>(UID_O, PKG_O), new Pair<>(false, false)); // in local prefs
 
         mHelper.canShowBadge(PKG_O, UID_O);
 
         // expected output
         ArraySet<Pair<Integer, String>> expected = new ArraySet<>();
-        expected.add(new Pair(UserHandle.getUserId(3), "third"));
-        expected.add(new Pair(UserHandle.getUserId(UID_O), PKG_O));
+        expected.add(new Pair<>(UserHandle.getUserId(3), "third"));
+        expected.add(new Pair<>(UserHandle.getUserId(UID_O), PKG_O));
 
         // make sure that's the only thing in the package ban output
         JSONArray actual = mHelper.dumpBansJson(
@@ -2976,7 +2975,7 @@
         for (int i = 0; i < actual.length(); i++) {
             JSONObject ban = actual.getJSONObject(i);
             assertTrue(expected.contains(
-                    new Pair(ban.getInt("userId"), ban.getString("packageName"))));
+                    new Pair<>(ban.getInt("userId"), ban.getString("packageName"))));
         }
     }
 
@@ -2994,9 +2993,9 @@
         // confirm that the string resulting from dumpImpl contains only importances from permission
 
         ArrayMap<Pair<Integer, String>, Pair<Boolean, Boolean>> appPermissions = new ArrayMap<>();
-        appPermissions.put(new Pair(1, "first"), new Pair(true, false));    // not in local prefs
-        appPermissions.put(new Pair(3, "third"), new Pair(false, true));   // not in local prefs
-        appPermissions.put(new Pair(UID_O, PKG_O), new Pair(false, false)); // in local prefs
+        appPermissions.put(new Pair<>(1, "first"), new Pair<>(true, false)); // not in local prefs
+        appPermissions.put(new Pair<>(3, "third"), new Pair<>(false, true)); // not in local prefs
+        appPermissions.put(new Pair<>(UID_O, PKG_O), new Pair<>(false, false)); // in local prefs
 
         // local package preferences
         mHelper.canShowBadge(PKG_O, UID_O);
@@ -3055,9 +3054,9 @@
 
         // permissions -- these should take precedence
         ArrayMap<Pair<Integer, String>, Pair<Boolean, Boolean>> appPermissions = new ArrayMap<>();
-        appPermissions.put(new Pair(1, "first"), new Pair(true, false));    // not in local prefs
-        appPermissions.put(new Pair(3, "third"), new Pair(false, false));   // not in local prefs
-        appPermissions.put(new Pair(UID_O, PKG_O), new Pair(false, false)); // in local prefs
+        appPermissions.put(new Pair<>(1, "first"), new Pair<>(true, false)); // not in local prefs
+        appPermissions.put(new Pair<>(3, "third"), new Pair<>(false, false)); // not in local prefs
+        appPermissions.put(new Pair<>(UID_O, PKG_O), new Pair<>(false, false)); // in local prefs
 
         // local package preferences
         mHelper.canShowBadge(PKG_O, UID_O);
@@ -3067,14 +3066,14 @@
         // should have importance set (aka not PKG_P)
         // map format: (uid, package name) -> importance (int)
         ArrayMap<Pair<Integer, String>, Integer> expected = new ArrayMap<>();
-        expected.put(new Pair(1, "first"), IMPORTANCE_DEFAULT);
-        expected.put(new Pair(3, "third"), IMPORTANCE_NONE);
-        expected.put(new Pair(UID_O, PKG_O), IMPORTANCE_NONE);
+        expected.put(new Pair<>(1, "first"), IMPORTANCE_DEFAULT);
+        expected.put(new Pair<>(3, "third"), IMPORTANCE_NONE);
+        expected.put(new Pair<>(UID_O, PKG_O), IMPORTANCE_NONE);
 
         // unfortunately, due to how nano protos work, there's no distinction between unset
         // fields and default-value fields, so we have no choice here but to check for a value of 0.
         // at least we can make sure the local importance for PKG_P in this test is not 0 (NONE).
-        expected.put(new Pair(UID_P, PKG_P), 0);
+        expected.put(new Pair<>(UID_P, PKG_P), 0);
 
         // get the proto output and inspect its contents
         ProtoOutputStream proto = new ProtoOutputStream();
@@ -3084,7 +3083,7 @@
         assertThat(actual.records.length).isEqualTo(expected.size());
         for (int i = 0; i < actual.records.length; i++) {
             RankingHelperProto.RecordProto record = actual.records[i];
-            Pair<Integer, String> pkgKey = new Pair(record.uid, record.package_);
+            Pair<Integer, String> pkgKey = new Pair<>(record.uid, record.package_);
             assertTrue(expected.containsKey(pkgKey));
             assertThat(record.importance).isEqualTo(expected.get(pkgKey));
         }
@@ -3403,26 +3402,6 @@
     }
 
     @Test
-    public void testToggleNotificationDelegate() {
-        mHelper.setNotificationDelegate(PKG_O, UID_O, "other", 53);
-        mHelper.toggleNotificationDelegate(PKG_O, UID_O, false);
-
-        assertNull(mHelper.getNotificationDelegate(PKG_O, UID_O));
-
-        mHelper.toggleNotificationDelegate(PKG_O, UID_O, true);
-        assertEquals("other", mHelper.getNotificationDelegate(PKG_O, UID_O));
-    }
-
-    @Test
-    public void testToggleNotificationDelegate_noDelegateExistsNoCrash() {
-        mHelper.toggleNotificationDelegate(PKG_O, UID_O, false);
-        assertNull(mHelper.getNotificationDelegate(PKG_O, UID_O));
-
-        mHelper.toggleNotificationDelegate(PKG_O, UID_O, true);
-        assertNull(mHelper.getNotificationDelegate(PKG_O, UID_O));
-    }
-
-    @Test
     public void testIsDelegateAllowed_noSource() {
         assertFalse(mHelper.isDelegateAllowed("does not exist", -1, "whatever", 0));
     }
@@ -3451,14 +3430,6 @@
     }
 
     @Test
-    public void testIsDelegateAllowed_delegateDisabledByUser() {
-        mHelper.setNotificationDelegate(PKG_O, UID_O, "other", 53);
-        mHelper.toggleNotificationDelegate(PKG_O, UID_O, false);
-
-        assertFalse(mHelper.isDelegateAllowed(PKG_O, UID_O, "other", 53));
-    }
-
-    @Test
     public void testIsDelegateAllowed() {
         mHelper.setNotificationDelegate(PKG_O, UID_O, "other", 53);
 
@@ -3503,46 +3474,8 @@
         loadStreamXml(baos, false, UserHandle.USER_ALL);
 
         assertNull(mHelper.getNotificationDelegate(PKG_O, UID_O));
-    }
-
-    @Test
-    public void testDelegateXml_userDisabledDelegate() throws Exception {
-        mHelper.setNotificationDelegate(PKG_O, UID_O, "other", 53);
-        mHelper.toggleNotificationDelegate(PKG_O, UID_O, false);
-
-        ByteArrayOutputStream baos = writeXmlAndPurge(PKG_O, UID_O, false, UserHandle.USER_ALL);
-        mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
-                mPermissionHelper, mLogger,
-                mAppOpsManager, mStatsEventBuilderFactory, false);
-        loadStreamXml(baos, false, UserHandle.USER_ALL);
-
-        // appears disabled
-        assertNull(mHelper.getNotificationDelegate(PKG_O, UID_O));
-
-        // but was loaded and can be toggled back on
-        mHelper.toggleNotificationDelegate(PKG_O, UID_O, true);
-        assertEquals("other", mHelper.getNotificationDelegate(PKG_O, UID_O));
-    }
-
-    @Test
-    public void testDelegateXml_entirelyDisabledDelegate() throws Exception {
-        mHelper.setNotificationDelegate(PKG_O, UID_O, "other", 53);
-        mHelper.toggleNotificationDelegate(PKG_O, UID_O, false);
-        mHelper.revokeNotificationDelegate(PKG_O, UID_O);
-
-        ByteArrayOutputStream baos = writeXmlAndPurge(PKG_O, UID_O, false, UserHandle.USER_ALL);
-        mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
-                mPermissionHelper, mLogger,
-                mAppOpsManager, mStatsEventBuilderFactory, false);
-        loadStreamXml(baos, false, UserHandle.USER_ALL);
-
-        // appears disabled
-        assertNull(mHelper.getNotificationDelegate(PKG_O, UID_O));
 
         mHelper.setNotificationDelegate(PKG_O, UID_O, "other", 53);
-        assertNull(mHelper.getNotificationDelegate(PKG_O, UID_O));
-
-        mHelper.toggleNotificationDelegate(PKG_O, UID_O, true);
         assertEquals("other", mHelper.getNotificationDelegate(PKG_O, UID_O));
     }
 
@@ -3686,7 +3619,7 @@
     @Test
     public void testUpdateNotificationChannel_defaultApp() {
         ArraySet<Pair<String, Integer>> toAdd = new ArraySet<>();
-        toAdd.add(new Pair(PKG_O, UID_O));
+        toAdd.add(new Pair<>(PKG_O, UID_O));
         mHelper.updateDefaultApps(0, null, toAdd);
         NotificationChannel a = new NotificationChannel("a", "a", IMPORTANCE_HIGH);
         mHelper.createNotificationChannel(PKG_O, UID_O, a, true, false);
@@ -3820,7 +3753,7 @@
         mHelper.createNotificationChannel(PKG_O, UserHandle.PER_USER_RANGE + 1, c, true, true);
 
         ArraySet<Pair<String, Integer>> toAdd = new ArraySet<>();
-        toAdd.add(new Pair(PKG_O, UID_O));
+        toAdd.add(new Pair<>(PKG_O, UID_O));
         mHelper.updateDefaultApps(UserHandle.getUserId(UID_O), null, toAdd);
 
         assertTrue(mHelper.getNotificationChannel(PKG_O, UID_O, a.getId(), false)
@@ -3840,7 +3773,7 @@
         mHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1, b, false, false);
 
         ArraySet<Pair<String, Integer>> toAdd = new ArraySet<>();
-        toAdd.add(new Pair(PKG_O, UID_O));
+        toAdd.add(new Pair<>(PKG_O, UID_O));
         mHelper.updateDefaultApps(UserHandle.getUserId(UID_O), null, toAdd);
 
         assertTrue(mHelper.getNotificationChannel(PKG_O, UID_O, a.getId(), false)
@@ -3858,7 +3791,7 @@
         mHelper.createNotificationChannel(PKG_O, UID_O, b, false, false);
 
         ArraySet<Pair<String, Integer>> toAdd = new ArraySet<>();
-        toAdd.add(new Pair(PKG_O, UID_O));
+        toAdd.add(new Pair<>(PKG_O, UID_O));
         mHelper.updateDefaultApps(UserHandle.getUserId(UID_O), null, toAdd);
 
         assertTrue(mHelper.getNotificationChannel(PKG_O, UID_O, a.getId(), false)
@@ -3884,7 +3817,7 @@
         mHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1, b, false, false);
 
         ArraySet<Pair<String, Integer>> toAdd = new ArraySet<>();
-        toAdd.add(new Pair(PKG_O, UID_O));
+        toAdd.add(new Pair<>(PKG_O, UID_O));
         mHelper.updateDefaultApps(UserHandle.getUserId(UID_O), null, toAdd);
 
 
@@ -3897,7 +3830,7 @@
         ArraySet<String> toRemove = new ArraySet<>();
         toRemove.add(PKG_O);
         toAdd = new ArraySet<>();
-        toAdd.add(new Pair(PKG_N_MR1, UID_N_MR1));
+        toAdd.add(new Pair<>(PKG_N_MR1, UID_N_MR1));
         mHelper.updateDefaultApps(USER.getIdentifier(), toRemove, toAdd);
 
         assertFalse(mHelper.getNotificationChannel(PKG_O, UID_O, a.getId(), false)
@@ -3909,7 +3842,7 @@
     @Test
     public void testUpdateDefaultApps_appDoesNotExist_noCrash() {
         ArraySet<Pair<String, Integer>> toAdd = new ArraySet<>();
-        toAdd.add(new Pair(PKG_O, UID_O));
+        toAdd.add(new Pair<>(PKG_O, UID_O));
         ArraySet<String> toRemove = new ArraySet<>();
         toRemove.add(PKG_N_MR1);
         mHelper.updateDefaultApps(UserHandle.getUserId(UID_O), toRemove, toAdd);
@@ -3922,7 +3855,7 @@
         mHelper.createNotificationChannel(PKG_O, UID_O, a, true, false);
 
         ArraySet<Pair<String, Integer>> toAdd = new ArraySet<>();
-        toAdd.add(new Pair(PKG_O, UID_O));
+        toAdd.add(new Pair<>(PKG_O, UID_O));
         mHelper.updateDefaultApps(UserHandle.getUserId(UID_O), null, toAdd);
 
         assertTrue(mHelper.getNotificationChannel(PKG_O, UID_O, a.getId(), false)
@@ -3938,7 +3871,7 @@
         NotificationChannel a = new NotificationChannel("a", "a", IMPORTANCE_HIGH);
         mHelper.createNotificationChannel(PKG_O, UID_O, a, true, false);
         ArraySet<Pair<String, Integer>> toAdd = new ArraySet<>();
-        toAdd.add(new Pair(PKG_O, UID_O));
+        toAdd.add(new Pair<>(PKG_O, UID_O));
         mHelper.updateDefaultApps(UserHandle.getUserId(UID_O), null, toAdd);
 
         NotificationChannel update = new NotificationChannel("a", "a", IMPORTANCE_NONE);
@@ -3964,7 +3897,7 @@
     @Test
     public void testDefaultApp_appHasNoSettingsYet() {
         ArraySet<Pair<String, Integer>> toAdd = new ArraySet<>();
-        toAdd.add(new Pair(PKG_O, UID_O));
+        toAdd.add(new Pair<>(PKG_O, UID_O));
         mHelper.updateDefaultApps(UserHandle.getUserId(UID_O), null, toAdd);
 
         NotificationChannel a = new NotificationChannel("a", "a", IMPORTANCE_HIGH);
@@ -4004,7 +3937,7 @@
     @Test
     public void testUpdateDefaultApps_thenNotFixedPermission() {
         ArraySet<Pair<String, Integer>> toAdd = new ArraySet<>();
-        toAdd.add(new Pair(PKG_O, UID_O));
+        toAdd.add(new Pair<>(PKG_O, UID_O));
         mHelper.updateDefaultApps(0, null, toAdd);
 
         UserInfo user = new UserInfo();
@@ -4043,7 +3976,7 @@
                 UID_O});
 
         ArraySet<Pair<String, Integer>> toAdd = new ArraySet<>();
-        toAdd.add(new Pair(PKG_O, UID_O));
+        toAdd.add(new Pair<>(PKG_O, UID_O));
         mHelper.updateDefaultApps(UserHandle.getUserId(UID_O), null, toAdd);
 
         TypedXmlPullParser parser = Xml.newFastPullParser();
@@ -5041,9 +4974,9 @@
 
         // build a collection of app permissions that should be passed in but ignored
         ArrayMap<Pair<Integer, String>, Pair<Boolean, Boolean>> appPermissions = new ArrayMap<>();
-        appPermissions.put(new Pair(1, "first"), new Pair(true, false));    // not in local prefs
-        appPermissions.put(new Pair(3, "third"), new Pair(false, true));   // not in local prefs
-        appPermissions.put(new Pair(UID_O, PKG_O), new Pair(false, true)); // in local prefs
+        appPermissions.put(new Pair<>(1, "first"), new Pair<>(true, false)); // not in local prefs
+        appPermissions.put(new Pair<>(3, "third"), new Pair<>(false, true)); // not in local prefs
+        appPermissions.put(new Pair<>(UID_O, PKG_O), new Pair<>(false, true)); // in local prefs
 
         // local preferences
         mHelper.canShowBadge(PKG_O, UID_O);
@@ -5052,10 +4985,10 @@
         // expected output. format: uid -> importance, as only uid (and not package name)
         // is in PackageNotificationPreferences
         ArrayMap<Integer, Pair<Integer, Boolean>> expected = new ArrayMap<>();
-        expected.put(1, new Pair(IMPORTANCE_DEFAULT, false));
-        expected.put(3, new Pair(IMPORTANCE_NONE, true));
-        expected.put(UID_O, new Pair(IMPORTANCE_NONE, true));     // banned by permissions
-        expected.put(UID_P, new Pair(IMPORTANCE_NONE, false));    // defaults to none, false
+        expected.put(1, new Pair<>(IMPORTANCE_DEFAULT, false));
+        expected.put(3, new Pair<>(IMPORTANCE_NONE, true));
+        expected.put(UID_O, new Pair<>(IMPORTANCE_NONE, true));     // banned by permissions
+        expected.put(UID_P, new Pair<>(IMPORTANCE_NONE, false));    // defaults to none, false
 
         ArrayList<StatsEvent> events = new ArrayList<>();
         mHelper.pullPackagePreferencesStats(events, appPermissions);
@@ -5109,4 +5042,72 @@
         assertTrue((channelB.getUserLockedFields() & USER_LOCKED_IMPORTANCE) == 0);
         assertTrue((channelC.getUserLockedFields() & USER_LOCKED_IMPORTANCE) == 0);
     }
+
+    @Test
+    public void createNotificationChannel_updateDifferent_requestsSort() {
+        NotificationChannel original = new NotificationChannel("id", "Bah", IMPORTANCE_DEFAULT);
+        mHelper.createNotificationChannel(PKG_P, 0, original, true, false);
+        clearInvocations(mHandler);
+
+        NotificationChannel updated = new NotificationChannel("id", "Wow", IMPORTANCE_DEFAULT);
+        mHelper.createNotificationChannel(PKG_P, 0, updated, true, false);
+
+        verify(mHandler).requestSort();
+    }
+
+    @Test
+    public void createNotificationChannel_updateSame_doesNotRequestSort() {
+        NotificationChannel original = new NotificationChannel("id", "Bah", IMPORTANCE_DEFAULT);
+        mHelper.createNotificationChannel(PKG_P, 0, original, true, false);
+        clearInvocations(mHandler);
+
+        NotificationChannel same = new NotificationChannel("id", "Bah", IMPORTANCE_DEFAULT);
+        mHelper.createNotificationChannel(PKG_P, 0, same, true, false);
+
+        verifyZeroInteractions(mHandler);
+    }
+
+    @Test
+    public void updateNotificationChannel_different_requestsSort() {
+        NotificationChannel original = new NotificationChannel("id", "Bah", IMPORTANCE_DEFAULT);
+        mHelper.createNotificationChannel(PKG_P, 0, original, true, false);
+        clearInvocations(mHandler);
+
+        NotificationChannel updated = new NotificationChannel("id", "Wow", IMPORTANCE_DEFAULT);
+        mHelper.updateNotificationChannel(PKG_P, 0, updated, false);
+
+        verify(mHandler).requestSort();
+    }
+
+    @Test
+    public void updateNotificationChannel_same_doesNotRequestSort() {
+        NotificationChannel original = new NotificationChannel("id", "Bah", IMPORTANCE_DEFAULT);
+        mHelper.createNotificationChannel(PKG_P, 0, original, true, false);
+        clearInvocations(mHandler);
+        // Note: Creating a NotificationChannel identical to the original is not equals(), because
+        // of mOriginalImportance. So we create a "true copy" instead.
+        Parcel parcel = Parcel.obtain();
+        original.writeToParcel(parcel, 0);
+        parcel.setDataPosition(0);
+        NotificationChannel same = NotificationChannel.CREATOR.createFromParcel(parcel);
+        parcel.recycle();
+
+        mHelper.updateNotificationChannel(PKG_P, 0, same, false);
+
+        verifyZeroInteractions(mHandler);
+    }
+
+    @Test
+    public void setShowBadge_update_requestsSort() {
+        mHelper.setShowBadge(PKG_P, 0, false);
+
+        verify(mHandler).requestSort();
+    }
+
+    @Test
+    public void setShowBadge_same_doesNotRequestSort() {
+        mHelper.setShowBadge(PKG_P, 0, true); // true == DEFAULT_SHOW_BADGE
+
+        verifyZeroInteractions(mHandler);
+    }
 }
diff --git a/services/tests/voiceinteractiontests/src/com/android/server/soundtrigger_middleware/ConversionUtilTest.java b/services/tests/voiceinteractiontests/src/com/android/server/soundtrigger_middleware/ConversionUtilTest.java
index 5a2ce45..5661b12 100644
--- a/services/tests/voiceinteractiontests/src/com/android/server/soundtrigger_middleware/ConversionUtilTest.java
+++ b/services/tests/voiceinteractiontests/src/com/android/server/soundtrigger_middleware/ConversionUtilTest.java
@@ -16,15 +16,33 @@
 
 package com.android.server.soundtrigger_middleware;
 
+import static android.hardware.soundtrigger.ConversionUtil.aidl2apiAudioFormatWithDefault;
+import static android.hardware.soundtrigger.ConversionUtil.aidl2apiPhrase;
+import static android.hardware.soundtrigger.ConversionUtil.aidl2apiRecognitionConfig;
+import static android.hardware.soundtrigger.ConversionUtil.api2aidlPhrase;
+import static android.hardware.soundtrigger.ConversionUtil.api2aidlRecognitionConfig;
+import static android.hardware.soundtrigger.ConversionUtil.byteArrayToSharedMemory;
+import static android.hardware.soundtrigger.ConversionUtil.sharedMemoryToByteArray;
+import static android.hardware.soundtrigger.SoundTrigger.ConfidenceLevel;
+import static android.hardware.soundtrigger.SoundTrigger.RECOGNITION_MODE_GENERIC;
+import static android.hardware.soundtrigger.SoundTrigger.RECOGNITION_MODE_USER_AUTHENTICATION;
+import static android.hardware.soundtrigger.SoundTrigger.RECOGNITION_MODE_USER_IDENTIFICATION;
+import static android.hardware.soundtrigger.SoundTrigger.RECOGNITION_MODE_VOICE_TRIGGER;
+
+import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
 
 import android.hardware.audio.common.V2_0.Uuid;
+import android.hardware.soundtrigger.SoundTrigger;
 
 import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import java.util.Locale;
+
 @RunWith(AndroidJUnit4.class)
 public class ConversionUtilTest {
     private static final String TAG = "ConversionUtilTest";
@@ -44,4 +62,54 @@
         Uuid reconstructed = ConversionUtil.aidl2hidlUuid(aidl);
         assertEquals(hidl, reconstructed);
     }
+
+    @Test
+    public void testDefaultAudioFormatConstruction() {
+        // This method should generate a real format when passed null
+        final var format = aidl2apiAudioFormatWithDefault(
+                null /** exercise default **/,
+                true /** isInput **/
+                );
+        assertNotNull(format);
+    }
+
+    @Test
+    public void testRecognitionConfigRoundTrip() {
+        final int flags = SoundTrigger.ModuleProperties.AUDIO_CAPABILITY_ECHO_CANCELLATION
+                | SoundTrigger.ModuleProperties.AUDIO_CAPABILITY_NOISE_SUPPRESSION;
+        final var data = new byte[] {0x11, 0x22};
+        final var keyphrases = new SoundTrigger.KeyphraseRecognitionExtra[2];
+        keyphrases[0] = new SoundTrigger.KeyphraseRecognitionExtra(99,
+                RECOGNITION_MODE_VOICE_TRIGGER | RECOGNITION_MODE_USER_IDENTIFICATION, 13,
+                    new ConfidenceLevel[] {new ConfidenceLevel(9999, 50),
+                                           new ConfidenceLevel(5000, 80)});
+        keyphrases[1] = new SoundTrigger.KeyphraseRecognitionExtra(101,
+                RECOGNITION_MODE_GENERIC, 8, new ConfidenceLevel[] {
+                    new ConfidenceLevel(7777, 30),
+                    new ConfidenceLevel(2222, 60)});
+
+        var apiconfig = new SoundTrigger.RecognitionConfig(true, false /** must be false **/,
+                keyphrases, data, flags);
+        assertEquals(apiconfig, aidl2apiRecognitionConfig(api2aidlRecognitionConfig(apiconfig)));
+    }
+
+    @Test
+    public void testByteArraySharedMemRoundTrip() {
+        final var data = new byte[] { 0x11, 0x22, 0x33, 0x44,
+                (byte) 0xde, (byte) 0xad, (byte) 0xbe, (byte) 0xef };
+        assertArrayEquals(data, sharedMemoryToByteArray(byteArrayToSharedMemory(data, "name"),
+                    10000000));
+
+    }
+
+    @Test
+    public void testPhraseRoundTrip() {
+        final var users = new int[] {10001, 10002};
+        final var apiphrase = new SoundTrigger.Keyphrase(17 /** id **/,
+                RECOGNITION_MODE_VOICE_TRIGGER | RECOGNITION_MODE_USER_AUTHENTICATION,
+                Locale.forLanguageTag("no_NO"),
+                "Hello Android", /** keyphrase **/
+                users);
+        assertEquals(apiphrase, aidl2apiPhrase(api2aidlPhrase(apiphrase)));
+    }
 }
diff --git a/services/tests/wmtests/src/com/android/server/wm/DeviceStateControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/DeviceStateControllerTests.java
index 2723289..9d1fde4 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DeviceStateControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DeviceStateControllerTests.java
@@ -21,6 +21,7 @@
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.when;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
 
 import android.content.Context;
@@ -55,6 +56,7 @@
     private DeviceStateManager mMockDeviceStateManager;
     private DeviceStateController.DeviceState mCurrentState =
             DeviceStateController.DeviceState.UNKNOWN;
+    private Consumer<DeviceStateController.DeviceState> mDelegate;
 
     @Before
     public void setUp() {
@@ -64,10 +66,10 @@
 
     private void initialize(boolean supportFold, boolean supportHalfFold) {
         mBuilder.setSupportFold(supportFold, supportHalfFold);
-        Consumer<DeviceStateController.DeviceState> delegate = (newFoldState) -> {
+        mDelegate = (newFoldState) -> {
             mCurrentState = newFoldState;
         };
-        mBuilder.setDelegate(delegate);
+        mBuilder.setDelegate(mDelegate);
         mBuilder.build();
         verify(mMockDeviceStateManager).registerCallback(any(), any());
     }
@@ -111,6 +113,24 @@
         assertEquals(DeviceStateController.DeviceState.CONCURRENT, mCurrentState);
     }
 
+    @Test
+    public void testUnregisterDeviceStateCallback() {
+        initialize(true /* supportFold */, true /* supportHalfFolded */);
+        assertEquals(1, mTarget.mDeviceStateCallbacks.size());
+        assertEquals(mDelegate, mTarget.mDeviceStateCallbacks.get(0));
+
+        mTarget.onStateChanged(mOpenDeviceStates[0]);
+        assertEquals(DeviceStateController.DeviceState.OPEN, mCurrentState);
+        mTarget.onStateChanged(mFoldedStates[0]);
+        assertEquals(DeviceStateController.DeviceState.FOLDED, mCurrentState);
+
+        // The callback should not receive state change when the it is unregistered.
+        mTarget.unregisterDeviceStateCallback(mDelegate);
+        assertTrue(mTarget.mDeviceStateCallbacks.isEmpty());
+        mTarget.onStateChanged(mOpenDeviceStates[0]);
+        assertEquals(DeviceStateController.DeviceState.FOLDED /* unchanged */, mCurrentState);
+    }
+
     private final int[] mFoldedStates = {0};
     private final int[] mOpenDeviceStates = {1};
     private final int[] mHalfFoldedStates = {2};
diff --git a/services/tests/wmtests/src/com/android/server/wm/SyncEngineTests.java b/services/tests/wmtests/src/com/android/server/wm/SyncEngineTests.java
index aff9c1a..8e91ca2 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SyncEngineTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SyncEngineTests.java
@@ -22,7 +22,6 @@
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.times;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
-import static com.android.server.wm.BLASTSyncEngine.METHOD_BLAST;
 import static com.android.server.wm.BLASTSyncEngine.METHOD_NONE;
 import static com.android.server.wm.WindowContainer.POSITION_BOTTOM;
 import static com.android.server.wm.WindowContainer.POSITION_TOP;
@@ -363,7 +362,8 @@
         BLASTSyncEngine.TransactionReadyListener listener = mock(
                 BLASTSyncEngine.TransactionReadyListener.class);
 
-        int id = startSyncSet(bse, listener, METHOD_NONE);
+        final int id = startSyncSet(bse, listener);
+        bse.setSyncMethod(id, METHOD_NONE);
         bse.addToSyncSet(id, mAppWindow.mToken);
         mAppWindow.prepareSync();
         assertFalse(mAppWindow.shouldSyncWithBuffers());
@@ -373,12 +373,7 @@
 
     static int startSyncSet(BLASTSyncEngine engine,
             BLASTSyncEngine.TransactionReadyListener listener) {
-        return startSyncSet(engine, listener, METHOD_BLAST);
-    }
-
-    static int startSyncSet(BLASTSyncEngine engine,
-            BLASTSyncEngine.TransactionReadyListener listener, int method) {
-        return engine.startSyncSet(listener, BLAST_TIMEOUT_DURATION, "", method);
+        return engine.startSyncSet(listener, BLAST_TIMEOUT_DURATION, "Test");
     }
 
     static class TestWindowContainer extends WindowContainer {
diff --git a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
index 048e2cc..3045812 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
@@ -520,6 +520,47 @@
     }
 
     @Test
+    public void testCreateInfo_MultiDisplay() {
+        DisplayContent otherDisplay = createNewDisplay();
+        final Transition transition = createTestTransition(TRANSIT_OPEN);
+        ArrayMap<WindowContainer, Transition.ChangeInfo> changes = transition.mChanges;
+        ArraySet<WindowContainer> participants = transition.mParticipants;
+
+        final Task display0Task = createTask(mDisplayContent);
+        final Task display1Task = createTask(otherDisplay);
+        // Start states.
+        changes.put(display0Task,
+                new Transition.ChangeInfo(display0Task, false /* vis */, true /* exChg */));
+        changes.put(display1Task,
+                new Transition.ChangeInfo(display1Task, false /* vis */, true /* exChg */));
+        fillChangeMap(changes, display0Task);
+        fillChangeMap(changes, display1Task);
+        // End states.
+        display0Task.setVisibleRequested(true);
+        display1Task.setVisibleRequested(true);
+
+        final int transit = transition.mType;
+        int flags = 0;
+
+        participants.add(display0Task);
+        participants.add(display1Task);
+        ArrayList<Transition.ChangeInfo> targets =
+                Transition.calculateTargets(participants, changes);
+        TransitionInfo info = Transition.calculateTransitionInfo(transit, flags, targets, mMockT);
+        assertEquals(2, info.getRootCount());
+        // Check that the changes are assigned to the correct display
+        assertEquals(mDisplayContent.getDisplayId(), info.getChange(
+                display0Task.mRemoteToken.toWindowContainerToken()).getEndDisplayId());
+        assertEquals(otherDisplay.getDisplayId(), info.getChange(
+                display1Task.mRemoteToken.toWindowContainerToken()).getEndDisplayId());
+        // Check that roots can be found by display and have the correct display
+        assertEquals(mDisplayContent.getDisplayId(),
+                info.getRoot(info.findRootIndex(mDisplayContent.getDisplayId())).getDisplayId());
+        assertEquals(otherDisplay.getDisplayId(),
+                info.getRoot(info.findRootIndex(otherDisplay.getDisplayId())).getDisplayId());
+    }
+
+    @Test
     public void testTargets_noIntermediatesToWallpaper() {
         final Transition transition = createTestTransition(TRANSIT_OPEN);
 
@@ -1704,7 +1745,8 @@
                 mDisplayContent, WINDOWING_MODE_MULTI_WINDOW, ACTIVITY_TYPE_STANDARD);
         final Transition transition = new Transition(TRANSIT_OPEN, 0 /* flags */,
                 app.mTransitionController, mWm.mSyncEngine);
-        app.mTransitionController.moveToCollecting(transition, BLASTSyncEngine.METHOD_NONE);
+        app.mTransitionController.moveToCollecting(transition);
+        mWm.mSyncEngine.setSyncMethod(transition.getSyncId(), BLASTSyncEngine.METHOD_NONE);
         final ArrayList<WindowContainer> freezeCalls = new ArrayList<>();
         transition.setContainerFreezer(new Transition.IContainerFreezer() {
             @Override
@@ -1755,7 +1797,8 @@
                 mDisplayContent, WINDOWING_MODE_MULTI_WINDOW, ACTIVITY_TYPE_STANDARD);
         final Transition transition = new Transition(TRANSIT_CHANGE, 0 /* flags */,
                 app.mTransitionController, mWm.mSyncEngine);
-        app.mTransitionController.moveToCollecting(transition, BLASTSyncEngine.METHOD_NONE);
+        app.mTransitionController.moveToCollecting(transition);
+        mWm.mSyncEngine.setSyncMethod(transition.getSyncId(), BLASTSyncEngine.METHOD_NONE);
         final SurfaceControl mockSnapshot = mock(SurfaceControl.class);
         transition.setContainerFreezer(new Transition.IContainerFreezer() {
             @Override
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
index d7e4c55..a68a573 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
@@ -42,7 +42,6 @@
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.when;
 import static com.android.server.wm.ActivityRecord.State.RESUMED;
-import static com.android.server.wm.BLASTSyncEngine.METHOD_BLAST;
 import static com.android.server.wm.WindowContainer.POSITION_TOP;
 import static com.android.server.wm.WindowContainer.SYNC_STATE_READY;
 import static com.android.server.wm.WindowState.BLAST_TIMEOUT_DURATION;
@@ -1001,7 +1000,7 @@
         BLASTSyncEngine.TransactionReadyListener transactionListener =
                 mock(BLASTSyncEngine.TransactionReadyListener.class);
 
-        int id = bse.startSyncSet(transactionListener, BLAST_TIMEOUT_DURATION, "", METHOD_BLAST);
+        final int id = bse.startSyncSet(transactionListener, BLAST_TIMEOUT_DURATION, "Test");
         bse.addToSyncSet(id, task);
         bse.setReady(id);
         bse.onSurfacePlacement();
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerMapTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerMapTests.java
index c2ee079..2a3c9bc 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerMapTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerMapTests.java
@@ -22,6 +22,8 @@
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
 
 import android.os.UserHandle;
 import android.platform.test.annotations.Presubmit;
@@ -129,4 +131,14 @@
         assertEquals(uid2processes.size(), 1);
         assertEquals(mProcessMap.getProcess(FAKE_PID1), pid1uid2);
     }
+
+    @Test
+    public void testRemove_callsDestroy() {
+        var proc = spy(pid1uid1);
+        mProcessMap.put(FAKE_PID1, proc);
+
+        mProcessMap.remove(FAKE_PID1);
+
+        verify(proc).destroy();
+    }
 }
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerTests.java
index d6cfd00..cf83981 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerTests.java
@@ -159,6 +159,17 @@
     }
 
     @Test
+    public void testDestroy_unregistersDisplayAreaListener() {
+        final TestDisplayContent testDisplayContent1 = createTestDisplayContentInContainer();
+        final DisplayArea imeContainer1 = testDisplayContent1.getImeContainer();
+        mWpc.registerDisplayAreaConfigurationListener(imeContainer1);
+
+        mWpc.destroy();
+
+        assertNull(mWpc.getDisplayArea());
+    }
+
+    @Test
     public void testSetRunningRecentsAnimation() {
         mWpc.setRunningRecentsAnimation(true);
         mWpc.setRunningRecentsAnimation(false);
diff --git a/telecomm/java/android/telecom/Connection.java b/telecomm/java/android/telecom/Connection.java
index 6138f67..943d8d6 100644
--- a/telecomm/java/android/telecom/Connection.java
+++ b/telecomm/java/android/telecom/Connection.java
@@ -87,7 +87,7 @@
  * must call {@link #destroy()} to signal to the framework that the {@code Connection} is no
  * longer used and associated resources may be recovered.
  * <p>
- * Subclasses of {@code Connection} override the {@code on*} methods to provide the the
+ * Subclasses of {@code Connection} override the {@code on*} methods to provide the
  * {@link ConnectionService}'s implementation of calling functionality.  The {@code on*} methods are
  * called by Telecom to inform an instance of a {@code Connection} of actions specific to that
  * {@code Connection} instance.
diff --git a/telephony/java/android/service/euicc/EuiccProfileInfo.java b/telephony/java/android/service/euicc/EuiccProfileInfo.java
index 728dfa6..f7c8237 100644
--- a/telephony/java/android/service/euicc/EuiccProfileInfo.java
+++ b/telephony/java/android/service/euicc/EuiccProfileInfo.java
@@ -453,7 +453,7 @@
                 + ", accessRules="
                 + Arrays.toString(mAccessRules)
                 + ", iccid="
-                + SubscriptionInfo.givePrintableIccid(mIccid)
+                + SubscriptionInfo.getPrintableId(mIccid)
                 + ")";
     }
 }
diff --git a/telephony/java/android/telephony/SubscriptionInfo.java b/telephony/java/android/telephony/SubscriptionInfo.java
index 3b84b65..faf97b5 100644
--- a/telephony/java/android/telephony/SubscriptionInfo.java
+++ b/telephony/java/android/telephony/SubscriptionInfo.java
@@ -947,29 +947,30 @@
     }
 
     /**
-     * Get ICCID stripped PII information on user build.
+     * Get stripped PII information from the id.
      *
-     * @param iccId The original ICCID.
+     * @param id The raw id (e.g. ICCID, IMSI, etc...).
      * @return The stripped string.
      *
      * @hide
      */
-    public static String givePrintableIccid(String iccId) {
-        String iccIdToPrint = null;
-        if (iccId != null) {
-            if (iccId.length() > 9 && !TelephonyUtils.IS_DEBUGGABLE) {
-                iccIdToPrint = iccId.substring(0, 9) + Rlog.pii(false, iccId.substring(9));
+    @Nullable
+    public static String getPrintableId(@Nullable String id) {
+        String idToPrint = null;
+        if (id != null) {
+            if (id.length() > 9 && !TelephonyUtils.IS_DEBUGGABLE) {
+                idToPrint = id.substring(0, 9) + Rlog.pii(false, id.substring(9));
             } else {
-                iccIdToPrint = iccId;
+                idToPrint = id;
             }
         }
-        return iccIdToPrint;
+        return idToPrint;
     }
 
     @Override
     public String toString() {
-        String iccIdToPrint = givePrintableIccid(mIccId);
-        String cardStringToPrint = givePrintableIccid(mCardString);
+        String iccIdToPrint = getPrintableId(mIccId);
+        String cardStringToPrint = getPrintableId(mCardString);
         return "[SubscriptionInfo: id=" + mId
                 + " iccId=" + iccIdToPrint
                 + " simSlotIndex=" + mSimSlotIndex
diff --git a/telephony/java/android/telephony/UiccPortInfo.java b/telephony/java/android/telephony/UiccPortInfo.java
index 6fb0470..41e743c 100644
--- a/telephony/java/android/telephony/UiccPortInfo.java
+++ b/telephony/java/android/telephony/UiccPortInfo.java
@@ -165,7 +165,7 @@
         return "UiccPortInfo (isActive="
                 + mIsActive
                 + ", iccId="
-                + SubscriptionInfo.givePrintableIccid(mIccId)
+                + SubscriptionInfo.getPrintableId(mIccId)
                 + ", portIndex="
                 + mPortIndex
                 + ", mLogicalSlotIndex="
diff --git a/telephony/java/android/telephony/UiccSlotInfo.java b/telephony/java/android/telephony/UiccSlotInfo.java
index 5e02532..1863a03b 100644
--- a/telephony/java/android/telephony/UiccSlotInfo.java
+++ b/telephony/java/android/telephony/UiccSlotInfo.java
@@ -281,7 +281,7 @@
                 + ", mIsEuicc="
                 + mIsEuicc
                 + ", mCardId="
-                + SubscriptionInfo.givePrintableIccid(mCardId)
+                + SubscriptionInfo.getPrintableId(mCardId)
                 + ", cardState="
                 + mCardStateInfo
                 + ", mIsExtendedApduSupported="
diff --git a/telephony/java/android/telephony/satellite/ISatelliteStateCallback.aidl b/telephony/java/android/telephony/satellite/ISatelliteStateCallback.aidl
index 98221c9..cd9d81e 100644
--- a/telephony/java/android/telephony/satellite/ISatelliteStateCallback.aidl
+++ b/telephony/java/android/telephony/satellite/ISatelliteStateCallback.aidl
@@ -22,13 +22,6 @@
  */
 oneway interface ISatelliteStateCallback {
     /**
-     * Indicates that the satellite has pending datagrams for the device to be pulled.
-     *
-     * @param count Number of pending datagrams.
-     */
-    void onPendingDatagramCount(in int count);
-
-    /**
      * Indicates that the satellite modem state has changed.
      *
      * @param state The current satellite modem state.
diff --git a/telephony/java/android/telephony/satellite/ISatellitePositionUpdateCallback.aidl b/telephony/java/android/telephony/satellite/ISatelliteTransmissionUpdateCallback.aidl
similarity index 96%
rename from telephony/java/android/telephony/satellite/ISatellitePositionUpdateCallback.aidl
rename to telephony/java/android/telephony/satellite/ISatelliteTransmissionUpdateCallback.aidl
index d3f1091..2442083 100644
--- a/telephony/java/android/telephony/satellite/ISatellitePositionUpdateCallback.aidl
+++ b/telephony/java/android/telephony/satellite/ISatelliteTransmissionUpdateCallback.aidl
@@ -22,7 +22,7 @@
  * Interface for position update and datagram transfer state change callback.
  * @hide
  */
-oneway interface ISatellitePositionUpdateCallback {
+oneway interface ISatelliteTransmissionUpdateCallback {
     /**
      * Called when satellite datagram transfer state changed.
      *
diff --git a/telephony/java/android/telephony/satellite/PointingInfo.java b/telephony/java/android/telephony/satellite/PointingInfo.java
index a3c3f19..7c794473 100644
--- a/telephony/java/android/telephony/satellite/PointingInfo.java
+++ b/telephony/java/android/telephony/satellite/PointingInfo.java
@@ -30,31 +30,12 @@
     /** Satellite elevation in degrees */
     private float mSatelliteElevationDegrees;
 
-    /** Antenna azimuth in degrees */
-    private float mAntennaAzimuthDegrees;
-
-    /**
-     * Angle of rotation about the x axis. This value represents the angle between a plane
-     * parallel to the device's screen and a plane parallel to the ground.
-     */
-    private float mAntennaPitchDegrees;
-
-    /**
-     * Angle of rotation about the y axis. This value represents the angle between a plane
-     * perpendicular to the device's screen and a plane parallel to the ground.
-     */
-    private float mAntennaRollDegrees;
-
     /**
      * @hide
      */
-    public PointingInfo(float satelliteAzimuthDegrees, float satelliteElevationDegrees,
-            float antennaAzimuthDegrees, float antennaPitchDegrees, float antennaRollDegrees) {
+    public PointingInfo(float satelliteAzimuthDegrees, float satelliteElevationDegrees) {
         mSatelliteAzimuthDegrees = satelliteAzimuthDegrees;
         mSatelliteElevationDegrees = satelliteElevationDegrees;
-        mAntennaAzimuthDegrees = antennaAzimuthDegrees;
-        mAntennaPitchDegrees = antennaPitchDegrees;
-        mAntennaRollDegrees = antennaRollDegrees;
     }
 
     private PointingInfo(Parcel in) {
@@ -70,9 +51,6 @@
     public void writeToParcel(@NonNull Parcel out, int flags) {
         out.writeFloat(mSatelliteAzimuthDegrees);
         out.writeFloat(mSatelliteElevationDegrees);
-        out.writeFloat(mAntennaAzimuthDegrees);
-        out.writeFloat(mAntennaPitchDegrees);
-        out.writeFloat(mAntennaRollDegrees);
     }
 
     public static final @android.annotation.NonNull Creator<PointingInfo> CREATOR =
@@ -99,18 +77,6 @@
 
         sb.append("SatelliteElevationDegrees:");
         sb.append(mSatelliteElevationDegrees);
-        sb.append(",");
-
-        sb.append("AntennaAzimuthDegrees:");
-        sb.append(mAntennaAzimuthDegrees);
-        sb.append(",");
-
-        sb.append("AntennaPitchDegrees:");
-        sb.append(mAntennaPitchDegrees);
-        sb.append(",");
-
-        sb.append("AntennaRollDegrees:");
-        sb.append(mAntennaRollDegrees);
         return sb.toString();
     }
 
@@ -122,23 +88,8 @@
         return mSatelliteElevationDegrees;
     }
 
-    public float getAntennaAzimuthDegrees() {
-        return mAntennaAzimuthDegrees;
-    }
-
-    public float getAntennaPitchDegrees() {
-        return mAntennaPitchDegrees;
-    }
-
-    public float getAntennaRollDegrees() {
-        return mAntennaRollDegrees;
-    }
-
     private void readFromParcel(Parcel in) {
         mSatelliteAzimuthDegrees = in.readFloat();
         mSatelliteElevationDegrees = in.readFloat();
-        mAntennaAzimuthDegrees = in.readFloat();
-        mAntennaPitchDegrees = in.readFloat();
-        mAntennaRollDegrees = in.readFloat();
     }
 }
diff --git a/telephony/java/android/telephony/satellite/SatelliteCapabilities.java b/telephony/java/android/telephony/satellite/SatelliteCapabilities.java
index 889856b..df80159 100644
--- a/telephony/java/android/telephony/satellite/SatelliteCapabilities.java
+++ b/telephony/java/android/telephony/satellite/SatelliteCapabilities.java
@@ -33,31 +33,24 @@
     @NonNull @SatelliteManager.NTRadioTechnology private Set<Integer> mSupportedRadioTechnologies;
 
     /**
-     * Whether satellite modem is always on.
-     * This indicates the power impact of keeping it on is very minimal.
-     */
-    private boolean mIsAlwaysOn;
-
-    /**
      * Whether UE needs to point to a satellite to send and receive data.
      */
-    private boolean mNeedsPointingToSatellite;
+    private boolean mIsPointingRequired;
 
     /**
-     * Whether UE needs a separate SIM profile to communicate with the satellite network.
+     * The maximum number of bytes per datagram that can be sent over satellite.
      */
-    private boolean mNeedsSeparateSimProfile;
+    private int mMaxBytesPerOutgoingDatagram;
 
     /**
      * @hide
      */
-    public SatelliteCapabilities(Set<Integer> supportedRadioTechnologies, boolean isAlwaysOn,
-            boolean needsPointingToSatellite, boolean needsSeparateSimProfile) {
+    public SatelliteCapabilities(Set<Integer> supportedRadioTechnologies,
+            boolean isPointingRequired, int maxBytesPerOutgoingDatagram) {
         mSupportedRadioTechnologies = supportedRadioTechnologies == null
                 ? new HashSet<>() : supportedRadioTechnologies;
-        mIsAlwaysOn = isAlwaysOn;
-        mNeedsPointingToSatellite = needsPointingToSatellite;
-        mNeedsSeparateSimProfile = needsSeparateSimProfile;
+        mIsPointingRequired = isPointingRequired;
+        mMaxBytesPerOutgoingDatagram = maxBytesPerOutgoingDatagram;
     }
 
     private SatelliteCapabilities(Parcel in) {
@@ -80,9 +73,8 @@
             out.writeInt(0);
         }
 
-        out.writeBoolean(mIsAlwaysOn);
-        out.writeBoolean(mNeedsPointingToSatellite);
-        out.writeBoolean(mNeedsSeparateSimProfile);
+        out.writeBoolean(mIsPointingRequired);
+        out.writeInt(mMaxBytesPerOutgoingDatagram);
     }
 
     @NonNull public static final Creator<SatelliteCapabilities> CREATOR = new Creator<>() {
@@ -111,16 +103,12 @@
             sb.append("none,");
         }
 
-        sb.append("isAlwaysOn:");
-        sb.append(mIsAlwaysOn);
+        sb.append("isPointingRequired:");
+        sb.append(mIsPointingRequired);
         sb.append(",");
 
-        sb.append("needsPointingToSatellite:");
-        sb.append(mNeedsPointingToSatellite);
-        sb.append(",");
-
-        sb.append("needsSeparateSimProfile:");
-        sb.append(mNeedsSeparateSimProfile);
+        sb.append("maxBytesPerOutgoingDatagram");
+        sb.append(mMaxBytesPerOutgoingDatagram);
         return sb.toString();
     }
 
@@ -133,33 +121,22 @@
     }
 
     /**
-     * Get whether the satellite modem is always on.
-     * This indicates the power impact of keeping it on is very minimal.
-     *
-     * @return {@code true} if the satellite modem is always on and {@code false} otherwise.
-     */
-    public boolean isAlwaysOn() {
-        return mIsAlwaysOn;
-    }
-
-    /**
      * Get whether UE needs to point to a satellite to send and receive data.
      *
-     * @return {@code true} if UE needs to pointing to a satellite to send and receive data and
+     * @return {@code true} if UE needs to point to a satellite to send and receive data and
      *         {@code false} otherwise.
      */
-    public boolean needsPointingToSatellite() {
-        return mNeedsPointingToSatellite;
+    public boolean isPointingRequired() {
+        return mIsPointingRequired;
     }
 
     /**
-     * Get whether UE needs a separate SIM profile to communicate with the satellite network.
+     * The maximum number of bytes per datagram that can be sent over satellite.
      *
-     * @return {@code true} if UE needs a separate SIM profile to comunicate with the satellite
-     *         network and {@code false} otherwise.
+     * @return The maximum number of bytes per datagram that can be sent over satellite.
      */
-    public boolean needsSeparateSimProfile() {
-        return mNeedsSeparateSimProfile;
+    public int getMaxBytesPerOutgoingDatagram() {
+        return mMaxBytesPerOutgoingDatagram;
     }
 
     private void readFromParcel(Parcel in) {
@@ -171,8 +148,7 @@
             }
         }
 
-        mIsAlwaysOn = in.readBoolean();
-        mNeedsPointingToSatellite = in.readBoolean();
-        mNeedsSeparateSimProfile = in.readBoolean();
+        mIsPointingRequired = in.readBoolean();
+        mMaxBytesPerOutgoingDatagram = in.readInt();
     }
 }
diff --git a/telephony/java/android/telephony/satellite/SatelliteDatagram.java b/telephony/java/android/telephony/satellite/SatelliteDatagram.java
index cc5a9f4..d3cb8a0 100644
--- a/telephony/java/android/telephony/satellite/SatelliteDatagram.java
+++ b/telephony/java/android/telephony/satellite/SatelliteDatagram.java
@@ -17,7 +17,6 @@
 package android.telephony.satellite;
 
 import android.annotation.NonNull;
-import android.annotation.Nullable;
 import android.os.Parcel;
 import android.os.Parcelable;
 
@@ -28,7 +27,7 @@
     /**
      * Datagram to be sent or received over satellite.
      */
-    private byte[] mData;
+    @NonNull private byte[] mData;
 
     /**
      * @hide
@@ -51,8 +50,8 @@
         out.writeByteArray(mData);
     }
 
-    public static final @android.annotation.NonNull Creator<SatelliteDatagram> CREATOR =
-            new Creator<SatelliteDatagram>() {
+    @NonNull public static final Creator<SatelliteDatagram> CREATOR =
+            new Creator<>() {
                 @Override
                 public SatelliteDatagram createFromParcel(Parcel in) {
                     return new SatelliteDatagram(in);
@@ -64,8 +63,7 @@
                 }
             };
 
-    @Nullable
-    public byte[] getSatelliteDatagram() {
+    @NonNull public byte[] getSatelliteDatagram() {
         return mData;
     }
 
diff --git a/telephony/java/android/telephony/satellite/SatelliteDatagramCallback.java b/telephony/java/android/telephony/satellite/SatelliteDatagramCallback.java
index 213b985..f237ada 100644
--- a/telephony/java/android/telephony/satellite/SatelliteDatagramCallback.java
+++ b/telephony/java/android/telephony/satellite/SatelliteDatagramCallback.java
@@ -17,48 +17,18 @@
 package android.telephony.satellite;
 
 import android.annotation.NonNull;
-import android.os.Binder;
 
 import com.android.internal.telephony.ILongConsumer;
 
-import java.util.concurrent.Executor;
-
 /**
  * A callback class for listening to satellite datagrams.
  *
  * @hide
  */
-public class SatelliteDatagramCallback {
-    private final CallbackBinder mBinder = new CallbackBinder(this);
-
-    private static class CallbackBinder extends ISatelliteDatagramCallback.Stub {
-        private final SatelliteDatagramCallback mLocalCallback;
-        private Executor mExecutor;
-
-        private CallbackBinder(SatelliteDatagramCallback localCallback) {
-            mLocalCallback = localCallback;
-        }
-
-        @Override
-        public void onSatelliteDatagramReceived(long datagramId,
-                @NonNull SatelliteDatagram datagram, int pendingCount,
-                @NonNull ILongConsumer callback) {
-            final long callingIdentity = Binder.clearCallingIdentity();
-            try {
-                mExecutor.execute(() -> mLocalCallback.onSatelliteDatagramReceived(datagramId,
-                        datagram, pendingCount, callback));
-            } finally {
-                restoreCallingIdentity(callingIdentity);
-            }
-        }
-
-        private void setExecutor(Executor executor) {
-            mExecutor = executor;
-        }
-    }
-
+public interface SatelliteDatagramCallback {
     /**
      * Called when there is an incoming datagram to be received.
+     *
      * @param datagramId An id that uniquely identifies incoming datagram.
      * @param datagram Datagram to be received over satellite.
      * @param pendingCount Number of datagrams yet to be received by the app.
@@ -66,19 +36,6 @@
      *                 datagramId to Telephony. If the callback is not received within five minutes,
      *                 Telephony will resend the datagram.
      */
-    public void onSatelliteDatagramReceived(long datagramId, @NonNull SatelliteDatagram datagram,
-            int pendingCount, @NonNull ILongConsumer callback) {
-        // Base Implementation
-    }
-
-    /** @hide */
-    @NonNull
-    final ISatelliteDatagramCallback getBinder() {
-        return mBinder;
-    }
-
-    /** @hide */
-    public void setExecutor(@NonNull Executor executor) {
-        mBinder.setExecutor(executor);
-    }
+    void onSatelliteDatagramReceived(long datagramId, @NonNull SatelliteDatagram datagram,
+            int pendingCount, @NonNull ILongConsumer callback);
 }
diff --git a/telephony/java/android/telephony/satellite/SatelliteManager.java b/telephony/java/android/telephony/satellite/SatelliteManager.java
index 248d9df..d0abfbf 100644
--- a/telephony/java/android/telephony/satellite/SatelliteManager.java
+++ b/telephony/java/android/telephony/satellite/SatelliteManager.java
@@ -36,12 +36,15 @@
 import android.telephony.TelephonyFrameworkInitializer;
 
 import com.android.internal.telephony.IIntegerConsumer;
+import com.android.internal.telephony.ILongConsumer;
 import com.android.internal.telephony.ITelephony;
 import com.android.telephony.Rlog;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
+import java.time.Duration;
 import java.util.Objects;
+import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.Executor;
 import java.util.function.Consumer;
 
@@ -55,6 +58,17 @@
 public class SatelliteManager {
     private static final String TAG = "SatelliteManager";
 
+    private static final ConcurrentHashMap<SatelliteDatagramCallback, ISatelliteDatagramCallback>
+            sSatelliteDatagramCallbackMap = new ConcurrentHashMap<>();
+    private static final ConcurrentHashMap<SatelliteProvisionStateCallback,
+            ISatelliteProvisionStateCallback> sSatelliteProvisionStateCallbackMap =
+            new ConcurrentHashMap<>();
+    private static final ConcurrentHashMap<SatelliteStateCallback, ISatelliteStateCallback>
+            sSatelliteStateCallbackMap = new ConcurrentHashMap<>();
+    private static final ConcurrentHashMap<SatelliteTransmissionUpdateCallback,
+            ISatelliteTransmissionUpdateCallback> sSatelliteTransmissionUpdateCallbackMap =
+            new ConcurrentHashMap<>();
+
     private final int mSubId;
 
     /**
@@ -66,6 +80,7 @@
      * Create an instance of the SatelliteManager.
      *
      * @param context The context the SatelliteManager belongs to.
+     * @hide
      */
     public SatelliteManager(@Nullable Context context) {
         this(context, SubscriptionManager.DEFAULT_SUBSCRIPTION_ID);
@@ -116,7 +131,7 @@
 
     /**
      * Bundle key to get the response from
-     * {@link #requestIsSatelliteDemoModeEnabled(Executor, OutcomeReceiver)}.
+     * {@link #requestIsDemoModeEnabled(Executor, OutcomeReceiver)}.
      * @hide
      */
     public static final String KEY_DEMO_MODE_ENABLED = "demo_mode_enabled";
@@ -137,14 +152,6 @@
 
     /**
      * Bundle key to get the response from
-     * {@link #requestMaxSizePerSendingDatagram(Executor, OutcomeReceiver)} .
-     * @hide
-     */
-    public static final String KEY_MAX_CHARACTERS_PER_SATELLITE_TEXT =
-            "max_characters_per_satellite_text";
-
-    /**
-     * Bundle key to get the response from
      * {@link #requestIsSatelliteProvisioned(Executor, OutcomeReceiver)}.
      * @hide
      */
@@ -286,56 +293,58 @@
     public @interface SatelliteError {}
 
     /**
-     * 3GPP NB-IoT (Narrowband Internet of Things) over Non-Terrestrial-Networks technology.
-     */
-    public static final int NT_RADIO_TECHNOLOGY_NB_IOT_NTN = 0;
-    /**
-     * 3GPP 5G NR over Non-Terrestrial-Networks technology.
-     */
-    public static final int NT_RADIO_TECHNOLOGY_NR_NTN = 1;
-    /**
-     * 3GPP eMTC (enhanced Machine-Type Communication) over Non-Terrestrial-Networks technology.
-     */
-    public static final int NT_RADIO_TECHNOLOGY_EMTC_NTN = 2;
-    /**
-     * Proprietary technology.
-     */
-    public static final int NT_RADIO_TECHNOLOGY_PROPRIETARY = 3;
-    /**
      * Unknown Non-Terrestrial radio technology. This generic radio technology should be used
      * only when the radio technology cannot be mapped to other specific radio technologies.
      */
-    public static final int NT_RADIO_TECHNOLOGY_UNKNOWN = -1;
+    public static final int NT_RADIO_TECHNOLOGY_UNKNOWN = 0;
+    /**
+     * 3GPP NB-IoT (Narrowband Internet of Things) over Non-Terrestrial-Networks technology.
+     */
+    public static final int NT_RADIO_TECHNOLOGY_NB_IOT_NTN = 1;
+    /**
+     * 3GPP 5G NR over Non-Terrestrial-Networks technology.
+     */
+    public static final int NT_RADIO_TECHNOLOGY_NR_NTN = 2;
+    /**
+     * 3GPP eMTC (enhanced Machine-Type Communication) over Non-Terrestrial-Networks technology.
+     */
+    public static final int NT_RADIO_TECHNOLOGY_EMTC_NTN = 3;
+    /**
+     * Proprietary technology.
+     */
+    public static final int NT_RADIO_TECHNOLOGY_PROPRIETARY = 4;
 
     /** @hide */
     @IntDef(prefix = "NT_RADIO_TECHNOLOGY_", value = {
+            NT_RADIO_TECHNOLOGY_UNKNOWN,
             NT_RADIO_TECHNOLOGY_NB_IOT_NTN,
             NT_RADIO_TECHNOLOGY_NR_NTN,
             NT_RADIO_TECHNOLOGY_EMTC_NTN,
-            NT_RADIO_TECHNOLOGY_PROPRIETARY,
-            NT_RADIO_TECHNOLOGY_UNKNOWN
+            NT_RADIO_TECHNOLOGY_PROPRIETARY
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface NTRadioTechnology {}
 
     /**
-     * Request to enable or disable the satellite modem. If the satellite modem is enabled, this
-     * will also disable the cellular modem, and if the satellite modem is disabled, this will also
-     * re-enable the cellular modem.
+     * Request to enable or disable the satellite modem and demo mode. If the satellite modem is
+     * enabled, this may also disable the cellular modem, and if the satellite modem is disabled,
+     * this may also re-enable the cellular modem.
      *
-     * @param enable {@code true} to enable the satellite modem and {@code false} to disable.
+     * @param enableSatellite {@code true} to enable the satellite modem and
+     *                        {@code false} to disable.
+     * @param enableDemoMode {@code true} to enable demo mode and {@code false} to disable.
      * @param executor The executor on which the error code listener will be called.
-     * @param errorCodeListener Listener for the {@link SatelliteError} result of the operation.
+     * @param resultListener Listener for the {@link SatelliteError} result of the operation.
      *
      * @throws SecurityException if the caller doesn't have required permission.
      * @throws IllegalStateException if the Telephony process is not currently available.
      */
     @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
-    public void requestSatelliteEnabled(
-            boolean enable, @NonNull @CallbackExecutor Executor executor,
-            @NonNull Consumer<Integer> errorCodeListener) {
+    public void requestSatelliteEnabled(boolean enableSatellite, boolean enableDemoMode,
+            @NonNull @CallbackExecutor Executor executor,
+            @SatelliteError @NonNull Consumer<Integer> resultListener) {
         Objects.requireNonNull(executor);
-        Objects.requireNonNull(errorCodeListener);
+        Objects.requireNonNull(resultListener);
 
         try {
             ITelephony telephony = getITelephony();
@@ -344,10 +353,11 @@
                     @Override
                     public void accept(int result) {
                         executor.execute(() -> Binder.withCleanCallingIdentity(
-                                () -> errorCodeListener.accept(result)));
+                                () -> resultListener.accept(result)));
                     }
                 };
-                telephony.requestSatelliteEnabled(mSubId, enable, errorCallback);
+                telephony.requestSatelliteEnabled(mSubId, enableSatellite, enableDemoMode,
+                        errorCallback);
             } else {
                 throw new IllegalStateException("telephony service is null.");
             }
@@ -412,50 +422,13 @@
     }
 
     /**
-     * Request to enable or disable the satellite service demo mode.
-     *
-     * @param enable {@code true} to enable the satellite demo mode and {@code false} to disable.
-     * @param executor The executor on which the error code listener will be called.
-     * @param errorCodeListener Listener for the {@link SatelliteError} result of the operation.
-     *
-     * @throws SecurityException if the caller doesn't have required permission.
-     * @throws IllegalStateException if the Telephony process is not currently available.
-     */
-    @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
-    public void requestSatelliteDemoModeEnabled(boolean enable,
-            @NonNull @CallbackExecutor Executor executor,
-            @NonNull Consumer<Integer> errorCodeListener) {
-        Objects.requireNonNull(executor);
-        Objects.requireNonNull(errorCodeListener);
-
-        try {
-            ITelephony telephony = getITelephony();
-            if (telephony != null) {
-                IIntegerConsumer errorCallback = new IIntegerConsumer.Stub() {
-                    @Override
-                    public void accept(int result) {
-                        executor.execute(() -> Binder.withCleanCallingIdentity(
-                                () -> errorCodeListener.accept(result)));
-                    }
-                };
-                telephony.requestSatelliteDemoModeEnabled(mSubId, enable, errorCallback);
-            } else {
-                throw new IllegalStateException("telephony service is null.");
-            }
-        } catch (RemoteException ex) {
-            Rlog.e(TAG, "requestSatelliteDemoModeEnabled() RemoteException: ", ex);
-            ex.rethrowFromSystemServer();
-        }
-    }
-
-    /**
      * Request to get whether the satellite service demo mode is enabled.
      *
      * @param executor The executor on which the callback will be called.
      * @param callback The callback object to which the result will be delivered.
      *                 If the request is successful, {@link OutcomeReceiver#onResult(Object)}
-     *                 will return a {@code boolean} with value {@code true} if the satellite
-     *                 demo mode is enabled and {@code false} otherwise.
+     *                 will return a {@code boolean} with value {@code true} if demo mode is enabled
+     *                 and {@code false} otherwise.
      *                 If the request is not successful, {@link OutcomeReceiver#onError(Throwable)}
      *                 will return a {@link SatelliteException} with the {@link SatelliteError}.
      *
@@ -463,7 +436,7 @@
      * @throws IllegalStateException if the Telephony process is not currently available.
      */
     @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
-    public void requestIsSatelliteDemoModeEnabled(@NonNull @CallbackExecutor Executor executor,
+    public void requestIsDemoModeEnabled(@NonNull @CallbackExecutor Executor executor,
             @NonNull OutcomeReceiver<Boolean, SatelliteException> callback) {
         Objects.requireNonNull(executor);
         Objects.requireNonNull(callback);
@@ -492,12 +465,12 @@
                         }
                     }
                 };
-                telephony.requestIsSatelliteDemoModeEnabled(mSubId, receiver);
+                telephony.requestIsDemoModeEnabled(mSubId, receiver);
             } else {
                 throw new IllegalStateException("telephony service is null.");
             }
         } catch (RemoteException ex) {
-            loge("requestIsSatelliteDemoModeEnabled() RemoteException: " + ex);
+            loge("requestIsDemoModeEnabled() RemoteException: " + ex);
             ex.rethrowFromSystemServer();
         }
     }
@@ -742,145 +715,117 @@
     public @interface DatagramType {}
 
     /**
-     * Start receiving satellite position updates.
+     * Start receiving satellite transmission updates.
      * This can be called by the pointing UI when the user starts pointing to the satellite.
      * Modem should continue to report the pointing input as the device or satellite moves.
-     * Satellite position updates are started only on {@link #SATELLITE_ERROR_NONE}.
+     * Satellite transmission updates are started only on {@link #SATELLITE_ERROR_NONE}.
      * All other results indicate that this operation failed.
-     * Once satellite position updates begin, datagram transfer state updates will be sent
-     * through {@link SatellitePositionUpdateCallback}.
+     * Once satellite transmission updates begin, position and datagram transfer state updates
+     * will be sent through {@link SatelliteTransmissionUpdateCallback}.
      *
      * @param executor The executor on which the callback and error code listener will be called.
-     * @param errorCodeListener Listener for the {@link SatelliteError} result of the operation.
-     * @param callback The callback to notify of changes in satellite position.
+     * @param resultListener Listener for the {@link SatelliteError} result of the operation.
+     * @param callback The callback to notify of satellite transmission updates.
      *
      * @throws SecurityException if the caller doesn't have required permission.
      * @throws IllegalStateException if the Telephony process is not currently available.
      */
     @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
-    public void startSatellitePositionUpdates(@NonNull @CallbackExecutor Executor executor,
-            @NonNull Consumer<Integer> errorCodeListener,
-            @NonNull SatellitePositionUpdateCallback callback) {
+    public void startSatelliteTransmissionUpdates(@NonNull @CallbackExecutor Executor executor,
+            @SatelliteError @NonNull Consumer<Integer> resultListener,
+            @NonNull SatelliteTransmissionUpdateCallback callback) {
         Objects.requireNonNull(executor);
-        Objects.requireNonNull(errorCodeListener);
+        Objects.requireNonNull(resultListener);
         Objects.requireNonNull(callback);
 
         try {
             ITelephony telephony = getITelephony();
             if (telephony != null) {
-                callback.setExecutor(executor);
                 IIntegerConsumer errorCallback = new IIntegerConsumer.Stub() {
                     @Override
                     public void accept(int result) {
                         executor.execute(() -> Binder.withCleanCallingIdentity(
-                                () -> errorCodeListener.accept(result)));
+                                () -> resultListener.accept(result)));
                     }
                 };
-                telephony.startSatellitePositionUpdates(
-                        mSubId, errorCallback, callback.getBinder());
+                ISatelliteTransmissionUpdateCallback internalCallback =
+                        new ISatelliteTransmissionUpdateCallback.Stub() {
+                            @Override
+                            public void onDatagramTransferStateChanged(int state,
+                                    int sendPendingCount, int receivePendingCount, int errorCode) {
+                                executor.execute(() -> Binder.withCleanCallingIdentity(
+                                        () -> callback.onDatagramTransferStateChanged(
+                                                state, sendPendingCount, receivePendingCount,
+                                                errorCode)));
+                            }
+
+                            @Override
+                            public void onSatellitePositionChanged(PointingInfo pointingInfo) {
+                                executor.execute(() -> Binder.withCleanCallingIdentity(
+                                        () -> callback.onSatellitePositionChanged(pointingInfo)));
+                            }
+                        };
+                sSatelliteTransmissionUpdateCallbackMap.put(callback, internalCallback);
+                telephony.startSatelliteTransmissionUpdates(mSubId, errorCallback,
+                        internalCallback);
             } else {
                 throw new IllegalStateException("telephony service is null.");
             }
         } catch (RemoteException ex) {
-            loge("startSatellitePositionUpdates() RemoteException: " + ex);
+            loge("startSatelliteTransmissionUpdates() RemoteException: " + ex);
             ex.rethrowFromSystemServer();
         }
     }
 
     /**
-     * Stop receiving satellite position updates.
+     * Stop receiving satellite transmission updates.
      * This can be called by the pointing UI when the user stops pointing to the satellite.
-     * Satellite position updates are stopped and the callback is unregistered only on
+     * Satellite transmission updates are stopped and the callback is unregistered only on
      * {@link #SATELLITE_ERROR_NONE}. All other results that this operation failed.
      *
-     * @param callback The callback that was passed to
-     * {@link #startSatellitePositionUpdates(Executor, Consumer, SatellitePositionUpdateCallback)}.
+     * @param callback The callback that was passed to {@link
+     * #startSatelliteTransmissionUpdates(Executor, Consumer, SatelliteTransmissionUpdateCallback)}.
      * @param executor The executor on which the error code listener will be called.
-     * @param errorCodeListener Listener for the {@link SatelliteError} result of the operation.
+     * @param resultListener Listener for the {@link SatelliteError} result of the operation.
      *
      * @throws SecurityException if the caller doesn't have required permission.
      * @throws IllegalStateException if the Telephony process is not currently available.
      */
     @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
-    public void stopSatellitePositionUpdates(@NonNull SatellitePositionUpdateCallback callback,
+    public void stopSatelliteTransmissionUpdates(
+            @NonNull SatelliteTransmissionUpdateCallback callback,
             @NonNull @CallbackExecutor Executor executor,
-            @NonNull Consumer<Integer> errorCodeListener) {
+            @SatelliteError @NonNull Consumer<Integer> resultListener) {
         Objects.requireNonNull(callback);
         Objects.requireNonNull(executor);
-        Objects.requireNonNull(errorCodeListener);
+        Objects.requireNonNull(resultListener);
+        ISatelliteTransmissionUpdateCallback internalCallback =
+                sSatelliteTransmissionUpdateCallbackMap.remove(callback);
 
         try {
             ITelephony telephony = getITelephony();
             if (telephony != null) {
-                IIntegerConsumer errorCallback = new IIntegerConsumer.Stub() {
-                    @Override
-                    public void accept(int result) {
-                        executor.execute(() -> Binder.withCleanCallingIdentity(
-                                () -> errorCodeListener.accept(result)));
-                    }
-                };
-                telephony.stopSatellitePositionUpdates(mSubId, errorCallback,
-                        callback.getBinder());
-                // TODO: Notify SmsHandler that pointing UI stopped
-            } else {
-                throw new IllegalStateException("telephony service is null.");
-            }
-        } catch (RemoteException ex) {
-            loge("stopSatellitePositionUpdates() RemoteException: " + ex);
-            ex.rethrowFromSystemServer();
-        }
-    }
-
-    /**
-     * Request to get the maximum number of bytes per datagram that can be sent to satellite.
-     *
-     * @param executor The executor on which the callback will be called.
-     * @param callback The callback object to which the result will be delivered.
-     *                 If the request is successful, {@link OutcomeReceiver#onResult(Object)}
-     *                 will return the maximum number of bytes per datagram that can be sent to
-     *                 satellite.
-     *                 If the request is not successful, {@link OutcomeReceiver#onError(Throwable)}
-     *
-     * @throws SecurityException if the caller doesn't have required permission.
-     * @throws IllegalStateException if the Telephony process is not currently available.
-     */
-    @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
-    public void requestMaxSizePerSendingDatagram(
-            @NonNull @CallbackExecutor Executor executor,
-            @NonNull OutcomeReceiver<Integer, SatelliteException> callback) {
-        Objects.requireNonNull(executor);
-        Objects.requireNonNull(callback);
-
-        try {
-            ITelephony telephony = getITelephony();
-            if (telephony != null) {
-                ResultReceiver receiver = new ResultReceiver(null) {
-                    @Override
-                    protected void onReceiveResult(int resultCode, Bundle resultData) {
-                        if (resultCode == SATELLITE_ERROR_NONE) {
-                            if (resultData.containsKey(KEY_MAX_CHARACTERS_PER_SATELLITE_TEXT)) {
-                                int maxCharacters =
-                                        resultData.getInt(KEY_MAX_CHARACTERS_PER_SATELLITE_TEXT);
-                                executor.execute(() -> Binder.withCleanCallingIdentity(() ->
-                                        callback.onResult(maxCharacters)));
-                            } else {
-                                loge("KEY_MAX_CHARACTERS_PER_SATELLITE_TEXT does not exist.");
-                                executor.execute(() -> Binder.withCleanCallingIdentity(() ->
-                                        callback.onError(
-                                                new SatelliteException(SATELLITE_REQUEST_FAILED))));
-                            }
-                        } else {
-                            executor.execute(() -> Binder.withCleanCallingIdentity(() ->
-                                    callback.onError(new SatelliteException(resultCode))));
+                if (internalCallback != null) {
+                    IIntegerConsumer errorCallback = new IIntegerConsumer.Stub() {
+                        @Override
+                        public void accept(int result) {
+                            executor.execute(() -> Binder.withCleanCallingIdentity(
+                                    () -> resultListener.accept(result)));
                         }
-                    }
-                };
-                telephony.requestMaxSizePerSendingDatagram(mSubId, receiver);
+                    };
+                    telephony.stopSatelliteTransmissionUpdates(mSubId, errorCallback,
+                            internalCallback);
+                    // TODO: Notify SmsHandler that pointing UI stopped
+                } else {
+                    loge("stopSatelliteTransmissionUpdates: No internal callback.");
+                    executor.execute(() -> Binder.withCleanCallingIdentity(
+                            () -> resultListener.accept(SATELLITE_INVALID_ARGUMENTS)));
+                }
             } else {
                 throw new IllegalStateException("telephony service is null.");
             }
         } catch (RemoteException ex) {
-            loge("requestMaxCharactersPerSatelliteTextMessage() RemoteException: " + ex);
+            loge("stopSatelliteTransmissionUpdates() RemoteException: " + ex);
             ex.rethrowFromSystemServer();
         }
     }
@@ -891,23 +836,24 @@
      *
      * @param token The token to be used as a unique identifier for provisioning with satellite
      *              gateway.
+     * @param regionId The region ID for the device's current location.
      * @param cancellationSignal The optional signal used by the caller to cancel the provision
      *                           request. Even when the cancellation is signaled, Telephony will
      *                           still trigger the callback to return the result of this request.
      * @param executor The executor on which the error code listener will be called.
-     * @param errorCodeListener Listener for the {@link SatelliteError} result of the operation.
+     * @param resultListener Listener for the {@link SatelliteError} result of the operation.
      *
      * @throws SecurityException if the caller doesn't have required permission.
      * @throws IllegalStateException if the Telephony process is not currently available.
      */
     @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
-    public void provisionSatelliteService(@NonNull String token,
+    public void provisionSatelliteService(@NonNull String token, @NonNull String regionId,
             @Nullable CancellationSignal cancellationSignal,
             @NonNull @CallbackExecutor Executor executor,
-            @SatelliteError @NonNull Consumer<Integer> errorCodeListener) {
+            @SatelliteError @NonNull Consumer<Integer> resultListener) {
         Objects.requireNonNull(token);
         Objects.requireNonNull(executor);
-        Objects.requireNonNull(errorCodeListener);
+        Objects.requireNonNull(resultListener);
 
         ICancellationSignal cancelRemote = null;
         try {
@@ -917,10 +863,11 @@
                     @Override
                     public void accept(int result) {
                         executor.execute(() -> Binder.withCleanCallingIdentity(
-                                () -> errorCodeListener.accept(result)));
+                                () -> resultListener.accept(result)));
                     }
                 };
-                cancelRemote = telephony.provisionSatelliteService(mSubId, token, errorCallback);
+                cancelRemote = telephony.provisionSatelliteService(mSubId, token, regionId,
+                        errorCallback);
             } else {
                 throw new IllegalStateException("telephony service is null.");
             }
@@ -942,7 +889,7 @@
      * {@link #provisionSatelliteService(String, CancellationSignal, Executor, Consumer)}.
      *
      * @param token The token of the device/subscription to be deprovisioned.
-     * @param errorCodeListener Listener for the {@link SatelliteError} result of the operation.
+     * @param resultListener Listener for the {@link SatelliteError} result of the operation.
      *
      * @throws SecurityException if the caller doesn't have required permission.
      * @throws IllegalStateException if the Telephony process is not currently available.
@@ -950,10 +897,10 @@
     @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
     public void deprovisionSatelliteService(@NonNull String token,
             @NonNull @CallbackExecutor Executor executor,
-            @SatelliteError @NonNull Consumer<Integer> errorCodeListener) {
+            @SatelliteError @NonNull Consumer<Integer> resultListener) {
         Objects.requireNonNull(token);
         Objects.requireNonNull(executor);
-        Objects.requireNonNull(errorCodeListener);
+        Objects.requireNonNull(resultListener);
 
         try {
             ITelephony telephony = getITelephony();
@@ -962,7 +909,7 @@
                     @Override
                     public void accept(int result) {
                         executor.execute(() -> Binder.withCleanCallingIdentity(
-                                () -> errorCodeListener.accept(result)));
+                                () -> resultListener.accept(result)));
                     }
                 };
                 telephony.deprovisionSatelliteService(mSubId, token, errorCallback);
@@ -996,9 +943,18 @@
         try {
             ITelephony telephony = getITelephony();
             if (telephony != null) {
-                callback.setExecutor(executor);
+                ISatelliteProvisionStateCallback internalCallback =
+                        new ISatelliteProvisionStateCallback.Stub() {
+                            @Override
+                            public void onSatelliteProvisionStateChanged(boolean provisioned) {
+                                executor.execute(() -> Binder.withCleanCallingIdentity(
+                                        () -> callback.onSatelliteProvisionStateChanged(
+                                                provisioned)));
+                            }
+                        };
+                sSatelliteProvisionStateCallbackMap.put(callback, internalCallback);
                 return telephony.registerForSatelliteProvisionStateChanged(
-                        mSubId, callback.getBinder());
+                        mSubId, internalCallback);
             } else {
                 throw new IllegalStateException("telephony service is null.");
             }
@@ -1023,11 +979,17 @@
     public void unregisterForSatelliteProvisionStateChanged(
             @NonNull SatelliteProvisionStateCallback callback) {
         Objects.requireNonNull(callback);
+        ISatelliteProvisionStateCallback internalCallback =
+                sSatelliteProvisionStateCallbackMap.remove(callback);
 
         try {
             ITelephony telephony = getITelephony();
             if (telephony != null) {
-                telephony.unregisterForSatelliteProvisionStateChanged(mSubId, callback.getBinder());
+                if (internalCallback != null) {
+                    telephony.unregisterForSatelliteProvisionStateChanged(mSubId, internalCallback);
+                } else {
+                    loge("unregisterForSatelliteProvisionStateChanged: No internal callback.");
+                }
             } else {
                 throw new IllegalStateException("telephony service is null.");
             }
@@ -1112,9 +1074,15 @@
         try {
             ITelephony telephony = getITelephony();
             if (telephony != null) {
-                callback.setExecutor(executor);
-                return telephony.registerForSatelliteModemStateChanged(mSubId,
-                        callback.getBinder());
+                ISatelliteStateCallback internalCallback = new ISatelliteStateCallback.Stub() {
+                    @Override
+                    public void onSatelliteModemStateChanged(int state) {
+                        executor.execute(() -> Binder.withCleanCallingIdentity(() ->
+                                callback.onSatelliteModemStateChanged(state)));
+                    }
+                };
+                sSatelliteStateCallbackMap.put(callback, internalCallback);
+                return telephony.registerForSatelliteModemStateChanged(mSubId, internalCallback);
             } else {
                 throw new IllegalStateException("telephony service is null.");
             }
@@ -1138,11 +1106,16 @@
     @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
     public void unregisterForSatelliteModemStateChanged(@NonNull SatelliteStateCallback callback) {
         Objects.requireNonNull(callback);
+        ISatelliteStateCallback internalCallback = sSatelliteStateCallbackMap.remove(callback);
 
         try {
             ITelephony telephony = getITelephony();
             if (telephony != null) {
-                telephony.unregisterForSatelliteModemStateChanged(mSubId, callback.getBinder());
+                if (internalCallback != null) {
+                    telephony.unregisterForSatelliteModemStateChanged(mSubId, internalCallback);
+                } else {
+                    loge("unregisterForSatelliteModemStateChanged: No internal callback.");
+                }
             } else {
                 throw new IllegalStateException("telephony service is null.");
             }
@@ -1155,8 +1128,6 @@
     /**
      * Register to receive incoming datagrams over satellite.
      *
-     * @param datagramType datagram type indicating whether the datagram is of type
-     *                     SOS_SMS or LOCATION_SHARING.
      * @param executor The executor on which the callback will be called.
      * @param callback The callback to handle incoming datagrams over satellite.
      *                 This callback with be invoked when a new datagram is received from satellite.
@@ -1167,7 +1138,7 @@
      * @throws IllegalStateException if the Telephony process is not currently available.
      */
     @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
-    @SatelliteError public int registerForSatelliteDatagram(@DatagramType int datagramType,
+    @SatelliteError public int registerForSatelliteDatagram(
             @NonNull @CallbackExecutor Executor executor,
             @NonNull SatelliteDatagramCallback callback) {
         Objects.requireNonNull(executor);
@@ -1176,9 +1147,19 @@
         try {
             ITelephony telephony = getITelephony();
             if (telephony != null) {
-                callback.setExecutor(executor);
-                return telephony.registerForSatelliteDatagram(mSubId, datagramType,
-                            callback.getBinder());
+                ISatelliteDatagramCallback internalCallback =
+                        new ISatelliteDatagramCallback.Stub() {
+                            @Override
+                            public void onSatelliteDatagramReceived(long datagramId,
+                                    @NonNull SatelliteDatagram datagram, int pendingCount,
+                                    @NonNull ILongConsumer ack) {
+                                executor.execute(() -> Binder.withCleanCallingIdentity(
+                                        () -> callback.onSatelliteDatagramReceived(
+                                                datagramId, datagram, pendingCount, ack)));
+                            }
+                        };
+                sSatelliteDatagramCallbackMap.put(callback, internalCallback);
+                return telephony.registerForSatelliteDatagram(mSubId, internalCallback);
             } else {
                 throw new IllegalStateException("telephony service is null.");
             }
@@ -1194,7 +1175,7 @@
      * If callback was not registered before, the request will be ignored.
      *
      * @param callback The callback that was passed to
-     * {@link #registerForSatelliteDatagram(int, Executor, SatelliteDatagramCallback)}.
+     * {@link #registerForSatelliteDatagram(Executor, SatelliteDatagramCallback)}.
      *
      * @throws SecurityException if the caller doesn't have required permission.
      * @throws IllegalStateException if the Telephony process is not currently available.
@@ -1202,11 +1183,17 @@
     @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
     public void unregisterForSatelliteDatagram(@NonNull SatelliteDatagramCallback callback) {
         Objects.requireNonNull(callback);
+        ISatelliteDatagramCallback internalCallback =
+                sSatelliteDatagramCallbackMap.remove(callback);
 
         try {
             ITelephony telephony = getITelephony();
             if (telephony != null) {
-                telephony.unregisterForSatelliteDatagram(mSubId, callback.getBinder());
+                if (internalCallback != null) {
+                    telephony.unregisterForSatelliteDatagram(mSubId, internalCallback);
+                } else {
+                    loge("unregisterForSatelliteDatagram: No internal callback.");
+                }
             } else {
                 throw new IllegalStateException("telephony service is null.");
             }
@@ -1222,7 +1209,7 @@
      * This method requests modem to check if there are any pending datagrams to be received over
      * satellite. If there are any incoming datagrams, they will be received via
      * {@link SatelliteDatagramCallback#onSatelliteDatagramReceived(long, SatelliteDatagram, int,
-     *          ISatelliteDatagramReceiverAck)}
+     *        ILongConsumer)}
      *
      * @param executor The executor on which the result listener will be called.
      * @param resultListener Listener for the {@link SatelliteError} result of the operation.
@@ -1371,9 +1358,8 @@
     }
 
     /**
-     * Request to get the time after which the satellite will be visible. This is an
-     * {@code int} representing the duration in seconds after which the satellite will be visible.
-     * This will return {@code 0} if the satellite is currently visible.
+     * Request to get the duration in seconds after which the satellite will be visible.
+     * This will be {@link Duration#ZERO} if the satellite is currently visible.
      *
      * @param executor The executor on which the callback will be called.
      * @param callback The callback object to which the result will be delivered.
@@ -1387,7 +1373,7 @@
      */
     @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
     public void requestTimeForNextSatelliteVisibility(@NonNull @CallbackExecutor Executor executor,
-            @NonNull OutcomeReceiver<Integer, SatelliteException> callback) {
+            @NonNull OutcomeReceiver<Duration, SatelliteException> callback) {
         Objects.requireNonNull(executor);
         Objects.requireNonNull(callback);
 
@@ -1402,7 +1388,8 @@
                                 int nextVisibilityDuration =
                                         resultData.getInt(KEY_SATELLITE_NEXT_VISIBILITY);
                                 executor.execute(() -> Binder.withCleanCallingIdentity(() ->
-                                        callback.onResult(nextVisibilityDuration)));
+                                        callback.onResult(
+                                                Duration.ofSeconds(nextVisibilityDuration))));
                             } else {
                                 loge("KEY_SATELLITE_NEXT_VISIBILITY does not exist.");
                                 executor.execute(() -> Binder.withCleanCallingIdentity(() ->
diff --git a/telephony/java/android/telephony/satellite/SatellitePositionUpdateCallback.java b/telephony/java/android/telephony/satellite/SatellitePositionUpdateCallback.java
deleted file mode 100644
index d44a84d..0000000
--- a/telephony/java/android/telephony/satellite/SatellitePositionUpdateCallback.java
+++ /dev/null
@@ -1,104 +0,0 @@
-/*
- * 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.telephony.satellite;
-
-import android.annotation.NonNull;
-import android.os.Binder;
-
-import java.util.concurrent.Executor;
-
-/**
- * A callback class for monitoring satellite position update and datagram transfer state change
- * events.
- *
- * @hide
- */
-public class SatellitePositionUpdateCallback {
-    private final CallbackBinder mBinder = new CallbackBinder(this);
-
-    private static class CallbackBinder extends ISatellitePositionUpdateCallback.Stub {
-        private final SatellitePositionUpdateCallback mLocalCallback;
-        private Executor mExecutor;
-
-        private CallbackBinder(SatellitePositionUpdateCallback localCallback) {
-            mLocalCallback = localCallback;
-        }
-
-        @Override
-        public void onSatellitePositionChanged(@NonNull PointingInfo pointingInfo) {
-            final long callingIdentity = Binder.clearCallingIdentity();
-            try {
-                mExecutor.execute(() ->
-                        mLocalCallback.onSatellitePositionChanged(pointingInfo));
-            } finally {
-                restoreCallingIdentity(callingIdentity);
-            }
-        }
-
-        @Override
-        public void onDatagramTransferStateChanged(
-                @SatelliteManager.SatelliteDatagramTransferState int state, int sendPendingCount,
-                int receivePendingCount, @SatelliteManager.SatelliteError int errorCode) {
-            final long callingIdentity = Binder.clearCallingIdentity();
-            try {
-                mExecutor.execute(() ->
-                        mLocalCallback.onDatagramTransferStateChanged(
-                                state, sendPendingCount, receivePendingCount, errorCode));
-            } finally {
-                restoreCallingIdentity(callingIdentity);
-            }
-        }
-
-        private void setExecutor(Executor executor) {
-            mExecutor = executor;
-        }
-    }
-
-    /**
-     * Called when the satellite position changed.
-     *
-     * @param pointingInfo The pointing info containing the satellite location.
-     */
-    public void onSatellitePositionChanged(@NonNull PointingInfo pointingInfo) {
-        // Base Implementation
-    }
-
-    /**
-     * Called when satellite datagram transfer state changed.
-     *
-     * @param state The new datagram transfer state.
-     * @param sendPendingCount The number of datagrams that are currently being sent.
-     * @param receivePendingCount The number of datagrams that are currently being received.
-     * @param errorCode If datagram transfer failed, the reason for failure.
-     */
-    public void onDatagramTransferStateChanged(
-            @SatelliteManager.SatelliteDatagramTransferState int state, int sendPendingCount,
-            int receivePendingCount, @SatelliteManager.SatelliteError int errorCode) {
-        // Base Implementation
-    }
-
-    /**@hide*/
-    @NonNull
-    final ISatellitePositionUpdateCallback getBinder() {
-        return mBinder;
-    }
-
-    /**@hide*/
-    public void setExecutor(@NonNull Executor executor) {
-        mBinder.setExecutor(executor);
-    }
-}
diff --git a/telephony/java/android/telephony/satellite/SatelliteProvisionStateCallback.java b/telephony/java/android/telephony/satellite/SatelliteProvisionStateCallback.java
index 2b6a5d9..a62eb8b 100644
--- a/telephony/java/android/telephony/satellite/SatelliteProvisionStateCallback.java
+++ b/telephony/java/android/telephony/satellite/SatelliteProvisionStateCallback.java
@@ -16,61 +16,17 @@
 
 package android.telephony.satellite;
 
-import android.annotation.NonNull;
-import android.os.Binder;
-
-import java.util.concurrent.Executor;
-
 /**
  * A callback class for monitoring satellite provision state change events.
  *
  * @hide
  */
-public class SatelliteProvisionStateCallback {
-    private final CallbackBinder mBinder = new CallbackBinder(this);
-
-    private static class CallbackBinder extends ISatelliteProvisionStateCallback.Stub {
-        private final SatelliteProvisionStateCallback mLocalCallback;
-        private Executor mExecutor;
-
-        private CallbackBinder(SatelliteProvisionStateCallback localCallback) {
-            mLocalCallback = localCallback;
-        }
-
-        @Override
-        public void onSatelliteProvisionStateChanged(boolean provisioned) {
-            final long callingIdentity = Binder.clearCallingIdentity();
-            try {
-                mExecutor.execute(() ->
-                        mLocalCallback.onSatelliteProvisionStateChanged(provisioned));
-            } finally {
-                restoreCallingIdentity(callingIdentity);
-            }
-        }
-
-        private void setExecutor(Executor executor) {
-            mExecutor = executor;
-        }
-    }
-
+public interface SatelliteProvisionStateCallback {
     /**
      * Called when satellite provision state changes.
      *
      * @param provisioned The new provision state. {@code true} means satellite is provisioned
      *                    {@code false} means satellite is not provisioned.
      */
-    public void onSatelliteProvisionStateChanged(boolean provisioned) {
-        // Base Implementation
-    }
-
-    /**@hide*/
-    @NonNull
-    final ISatelliteProvisionStateCallback getBinder() {
-        return mBinder;
-    }
-
-    /**@hide*/
-    public void setExecutor(@NonNull Executor executor) {
-        mBinder.setExecutor(executor);
-    }
+    void onSatelliteProvisionStateChanged(boolean provisioned);
 }
diff --git a/telephony/java/android/telephony/satellite/SatelliteStateCallback.java b/telephony/java/android/telephony/satellite/SatelliteStateCallback.java
index 17d05b7..d9ecaa3 100644
--- a/telephony/java/android/telephony/satellite/SatelliteStateCallback.java
+++ b/telephony/java/android/telephony/satellite/SatelliteStateCallback.java
@@ -16,80 +16,15 @@
 
 package android.telephony.satellite;
 
-import android.annotation.NonNull;
-import android.os.Binder;
-
-import java.util.concurrent.Executor;
-
 /**
  * A callback class for monitoring satellite modem state change events.
  *
  * @hide
  */
-public class SatelliteStateCallback {
-    private final CallbackBinder mBinder = new CallbackBinder(this);
-
-    private static class CallbackBinder extends ISatelliteStateCallback.Stub {
-        private final SatelliteStateCallback mLocalCallback;
-        private Executor mExecutor;
-
-        private CallbackBinder(SatelliteStateCallback localCallback) {
-            mLocalCallback = localCallback;
-        }
-
-        @Override
-        public void onSatelliteModemStateChanged(@SatelliteManager.SatelliteModemState int state) {
-            final long callingIdentity = Binder.clearCallingIdentity();
-            try {
-                mExecutor.execute(() ->
-                        mLocalCallback.onSatelliteModemStateChanged(state));
-            } finally {
-                restoreCallingIdentity(callingIdentity);
-            }
-        }
-
-        @Override
-        public void onPendingDatagramCount(int count) {
-            final long callingIdentity = Binder.clearCallingIdentity();
-            try {
-                mExecutor.execute(() ->
-                        mLocalCallback.onPendingDatagramCount(count));
-            } finally {
-                restoreCallingIdentity(callingIdentity);
-            }
-        }
-
-        private void setExecutor(Executor executor) {
-            mExecutor = executor;
-        }
-    }
-
+public interface SatelliteStateCallback {
     /**
      * Called when satellite modem state changes.
      * @param state The new satellite modem state.
      */
-    public void onSatelliteModemStateChanged(@SatelliteManager.SatelliteModemState int state) {
-        // Base Implementation
-    }
-
-    /**
-     * Called when there are pending datagrams to be received from satellite.
-     * @param count Pending datagram count.
-     */
-    public void onPendingDatagramCount(int count) {
-        // Base Implementation
-    }
-
-    //TODO: Add an API for datagram transfer state update here.
-
-    /**@hide*/
-    @NonNull
-    final ISatelliteStateCallback getBinder() {
-        return mBinder;
-    }
-
-    /**@hide*/
-    public void setExecutor(@NonNull Executor executor) {
-        mBinder.setExecutor(executor);
-    }
+    void onSatelliteModemStateChanged(@SatelliteManager.SatelliteModemState int state);
 }
diff --git a/telephony/java/android/telephony/satellite/ISatellitePositionUpdateCallback.aidl b/telephony/java/android/telephony/satellite/SatelliteTransmissionUpdateCallback.java
similarity index 68%
copy from telephony/java/android/telephony/satellite/ISatellitePositionUpdateCallback.aidl
copy to telephony/java/android/telephony/satellite/SatelliteTransmissionUpdateCallback.java
index d3f1091..0efbd1f 100644
--- a/telephony/java/android/telephony/satellite/ISatellitePositionUpdateCallback.aidl
+++ b/telephony/java/android/telephony/satellite/SatelliteTransmissionUpdateCallback.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2023 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.
@@ -16,13 +16,22 @@
 
 package android.telephony.satellite;
 
-import android.telephony.satellite.PointingInfo;
+import android.annotation.NonNull;
 
 /**
- * Interface for position update and datagram transfer state change callback.
+ * A callback class for monitoring satellite position update and datagram transfer state change
+ * events.
+ *
  * @hide
  */
-oneway interface ISatellitePositionUpdateCallback {
+public interface SatelliteTransmissionUpdateCallback {
+    /**
+     * Called when the satellite position changed.
+     *
+     * @param pointingInfo The pointing info containing the satellite location.
+     */
+    void onSatellitePositionChanged(@NonNull PointingInfo pointingInfo);
+
     /**
      * Called when satellite datagram transfer state changed.
      *
@@ -31,13 +40,7 @@
      * @param receivePendingCount The number of datagrams that are currently being received.
      * @param errorCode If datagram transfer failed, the reason for failure.
      */
-    void onDatagramTransferStateChanged(in int state, in int sendPendingCount,
-            in int receivePendingCount, in int errorCode);
-
-    /**
-     * Called when the satellite position changed.
-     *
-     * @param pointingInfo The pointing info containing the satellite location.
-     */
-    void onSatellitePositionChanged(in PointingInfo pointingInfo);
+    void onDatagramTransferStateChanged(@SatelliteManager.SatelliteDatagramTransferState int state,
+            int sendPendingCount, int receivePendingCount,
+            @SatelliteManager.SatelliteError int errorCode);
 }
diff --git a/telephony/java/android/telephony/satellite/stub/ISatellite.aidl b/telephony/java/android/telephony/satellite/stub/ISatellite.aidl
index d93ee21..a780cb9 100644
--- a/telephony/java/android/telephony/satellite/stub/ISatellite.aidl
+++ b/telephony/java/android/telephony/satellite/stub/ISatellite.aidl
@@ -49,10 +49,9 @@
      * Listening mode allows the satellite service to listen for incoming pages.
      *
      * @param enable True to enable satellite listening mode and false to disable.
-     * @param isDemoMode Whether demo mode is enabled.
      * @param timeout How long the satellite modem should wait for the next incoming page before
      *                disabling listening mode.
-     * @param errorCallback The callback to receive the error code result of the operation.
+     * @param resultCallback The callback to receive the error code result of the operation.
      *
      * Valid error codes returned:
      *   SatelliteError:ERROR_NONE
@@ -64,16 +63,17 @@
      *   SatelliteError:REQUEST_NOT_SUPPORTED
      *   SatelliteError:NO_RESOURCES
      */
-    void requestSatelliteListeningEnabled(in boolean enable, in boolean isDemoMode, in int timeout,
-            in IIntegerConsumer errorCallback);
+    void requestSatelliteListeningEnabled(in boolean enable, in int timeout,
+            in IIntegerConsumer resultCallback);
 
     /**
-     * Request to enable or disable the satellite modem. If the satellite modem is enabled,
-     * this will also disable the cellular modem, and if the satellite modem is disabled,
-     * this will also re-enable the cellular modem.
+     * Request to enable or disable the satellite modem and demo mode. If the satellite modem
+     * is enabled, this may also disable the cellular modem, and if the satellite modem is disabled,
+     * this may also re-enable the cellular modem.
      *
-     * @param enable True to enable the satellite modem and false to disable.
-     * @param errorCallback The callback to receive the error code result of the operation.
+     * @param enableSatellite True to enable the satellite modem and false to disable.
+     * @param enableDemoMode True to enable demo mode and false to disable.
+     * @param resultCallback The callback to receive the error code result of the operation.
      *
      * Valid error codes returned:
      *   SatelliteError:ERROR_NONE
@@ -85,13 +85,14 @@
      *   SatelliteError:REQUEST_NOT_SUPPORTED
      *   SatelliteError:NO_RESOURCES
      */
-    void requestSatelliteEnabled(in boolean enabled, in IIntegerConsumer errorCallback);
+    void requestSatelliteEnabled(in boolean enableSatellite, in boolean enableDemoMode,
+            in IIntegerConsumer resultCallback);
 
     /**
      * Request to get whether the satellite modem is enabled.
      *
-     * @param errorCallback The callback to receive the error code result of the operation.
-     *                      This must only be sent when the result is not SatelliteError#ERROR_NONE.
+     * @param resultCallback The callback to receive the error code result of the operation.
+     *                       This must only be sent when the error is not SatelliteError#ERROR_NONE.
      * @param callback If the result is SatelliteError#ERROR_NONE, the callback to receive
      *                 whether the satellite modem is enabled.
      *
@@ -105,13 +106,13 @@
      *   SatelliteError:REQUEST_NOT_SUPPORTED
      *   SatelliteError:NO_RESOURCES
      */
-    void requestIsSatelliteEnabled(in IIntegerConsumer errorCallback, in IBooleanConsumer callback);
+    void requestIsSatelliteEnabled(in IIntegerConsumer resultCallback, in IBooleanConsumer callback);
 
     /**
      * Request to get whether the satellite service is supported on the device.
      *
-     * @param errorCallback The callback to receive the error code result of the operation.
-     *                      This must only be sent when the result is not SatelliteError#ERROR_NONE.
+     * @param resultCallback The callback to receive the error code result of the operation.
+     *                       This must only be sent when the error is not SatelliteError#ERROR_NONE.
      * @param callback If the result is SatelliteError#ERROR_NONE, the callback to receive
      *                 whether the satellite service is supported on the device.
      *
@@ -125,14 +126,14 @@
      *   SatelliteError:REQUEST_NOT_SUPPORTED
      *   SatelliteError:NO_RESOURCES
      */
-    void requestIsSatelliteSupported(in IIntegerConsumer errorCallback,
+    void requestIsSatelliteSupported(in IIntegerConsumer resultCallback,
             in IBooleanConsumer callback);
 
     /**
      * Request to get the SatelliteCapabilities of the satellite service.
      *
-     * @param errorCallback The callback to receive the error code result of the operation.
-     *                      This must only be sent when the result is not SatelliteError#ERROR_NONE.
+     * @param resultCallback The callback to receive the error code result of the operation.
+     *                       This must only be sent when the error is not SatelliteError#ERROR_NONE.
      * @param callback If the result is SatelliteError#ERROR_NONE, the callback to receive
      *                 the SatelliteCapabilities of the satellite service.
      *
@@ -146,7 +147,7 @@
      *   SatelliteError:REQUEST_NOT_SUPPORTED
      *   SatelliteError:NO_RESOURCES
      */
-    void requestSatelliteCapabilities(in IIntegerConsumer errorCallback,
+    void requestSatelliteCapabilities(in IIntegerConsumer resultCallback,
             in ISatelliteCapabilitiesConsumer callback);
 
     /**
@@ -154,7 +155,7 @@
      * The satellite service should report the satellite pointing info via
      * ISatelliteListener#onSatellitePositionChanged as the user device/satellite moves.
      *
-     * @param errorCallback The callback to receive the error code result of the operation.
+     * @param resultCallback The callback to receive the error code result of the operation.
      *
      * Valid error codes returned:
      *   SatelliteError:ERROR_NONE
@@ -166,13 +167,13 @@
      *   SatelliteError:REQUEST_NOT_SUPPORTED
      *   SatelliteError:NO_RESOURCES
      */
-    void startSendingSatellitePointingInfo(in IIntegerConsumer errorCallback);
+    void startSendingSatellitePointingInfo(in IIntegerConsumer resultCallback);
 
     /**
      * User stopped pointing to the satellite.
      * The satellite service should stop reporting satellite pointing info to the framework.
      *
-     * @param errorCallback The callback to receive the error code result of the operation.
+     * @param resultCallback The callback to receive the error code result of the operation.
      *
      * Valid error codes returned:
      *   SatelliteError:ERROR_NONE
@@ -184,28 +185,7 @@
      *   SatelliteError:REQUEST_NOT_SUPPORTED
      *   SatelliteError:NO_RESOURCES
      */
-    void stopSendingSatellitePointingInfo(in IIntegerConsumer errorCallback);
-
-    /**
-     * Request to get the maximum number of characters per MO text message on satellite.
-     *
-     * @param errorCallback The callback to receive the error code result of the operation.
-     *                      This must only be sent when the result is not SatelliteError#ERROR_NONE.
-     * @param callback If the result is SatelliteError#ERROR_NONE, the callback to receive
-     *                 the maximum number of characters per MO text message on satellite.
-     *
-     * Valid error codes returned:
-     *   SatelliteError:ERROR_NONE
-     *   SatelliteError:SERVICE_ERROR
-     *   SatelliteError:MODEM_ERROR
-     *   SatelliteError:INVALID_MODEM_STATE
-     *   SatelliteError:INVALID_ARGUMENTS
-     *   SatelliteError:RADIO_NOT_AVAILABLE
-     *   SatelliteError:REQUEST_NOT_SUPPORTED
-     *   SatelliteError:NO_RESOURCES
-     */
-    void requestMaxCharactersPerMOTextMessage(in IIntegerConsumer errorCallback,
-            in IIntegerConsumer callback);
+    void stopSendingSatellitePointingInfo(in IIntegerConsumer resultCallback);
 
     /**
      * Provision the device with a satellite provider.
@@ -214,7 +194,8 @@
      *
      * @param token The token to be used as a unique identifier for provisioning with satellite
      *              gateway.
-     * @param errorCallback The callback to receive the error code result of the operation.
+     * @param regionId The region ID for the device's current location.
+     * @param resultCallback The callback to receive the error code result of the operation.
      *
      * Valid error codes returned:
      *   SatelliteError:ERROR_NONE
@@ -229,7 +210,8 @@
      *   SatelliteError:REQUEST_ABORTED
      *   SatelliteError:NETWORK_TIMEOUT
      */
-    void provisionSatelliteService(in String token, in IIntegerConsumer errorCallback);
+    void provisionSatelliteService(in String token, in String regionId,
+            in IIntegerConsumer resultCallback);
 
     /**
      * Deprovision the device with the satellite provider.
@@ -237,7 +219,7 @@
      * Once deprovisioned, ISatelliteListener#onSatelliteProvisionStateChanged should report false.
      *
      * @param token The token of the device/subscription to be deprovisioned.
-     * @param errorCallback The callback to receive the error code result of the operation.
+     * @param resultCallback The callback to receive the error code result of the operation.
      *
      * Valid error codes returned:
      *   SatelliteError:ERROR_NONE
@@ -252,13 +234,13 @@
      *   SatelliteError:REQUEST_ABORTED
      *   SatelliteError:NETWORK_TIMEOUT
      */
-    void deprovisionSatelliteService(in String token, in IIntegerConsumer errorCallback);
+    void deprovisionSatelliteService(in String token, in IIntegerConsumer resultCallback);
 
     /**
      * Request to get whether this device is provisioned with a satellite provider.
      *
-     * @param errorCallback The callback to receive the error code result of the operation.
-     *                      This must only be sent when the result is not SatelliteError#ERROR_NONE.
+     * @param resultCallback The callback to receive the error code result of the operation.
+     *                       This must only be sent when the error is not SatelliteError#ERROR_NONE.
      * @param callback If the result is SatelliteError#ERROR_NONE, the callback to receive
      *                 whether this device is provisioned with a satellite provider.
      *
@@ -272,7 +254,7 @@
      *   SatelliteError:REQUEST_NOT_SUPPORTED
      *   SatelliteError:NO_RESOURCES
      */
-    void requestIsSatelliteProvisioned(in IIntegerConsumer errorCallback,
+    void requestIsSatelliteProvisioned(in IIntegerConsumer resultCallback,
             in IBooleanConsumer callback);
 
     /**
@@ -280,7 +262,7 @@
      * The satellite service should check if there are any pending datagrams to be received over
      * satellite and report them via ISatelliteListener#onSatelliteDatagramsReceived.
      *
-     * @param errorCallback The callback to receive the error code result of the operation.
+     * @param resultCallback The callback to receive the error code result of the operation.
      *
      * Valid error codes returned:
      *   SatelliteError:ERROR_NONE
@@ -297,15 +279,14 @@
      *   SatelliteError:SATELLITE_NOT_REACHABLE
      *   SatelliteError:NOT_AUTHORIZED
      */
-    void pollPendingSatelliteDatagrams(in IIntegerConsumer errorCallback);
+    void pollPendingSatelliteDatagrams(in IIntegerConsumer resultCallback);
 
     /**
      * Send datagram over satellite.
      *
      * @param datagram Datagram to send in byte format.
-     * @param isDemoMode Whether demo mode is enabled.
      * @param isEmergency Whether this is an emergency datagram.
-     * @param errorCallback The callback to receive the error code result of the operation.
+     * @param resultCallback The callback to receive the error code result of the operation.
      *
      * Valid error codes returned:
      *   SatelliteError:ERROR_NONE
@@ -323,16 +304,16 @@
      *   SatelliteError:SATELLITE_NOT_REACHABLE
      *   SatelliteError:NOT_AUTHORIZED
      */
-    void sendSatelliteDatagram(in SatelliteDatagram datagram, in boolean isDemoMode,
-            in boolean isEmergency, in IIntegerConsumer errorCallback);
+    void sendSatelliteDatagram(in SatelliteDatagram datagram, in boolean isEmergency,
+            in IIntegerConsumer resultCallback);
 
     /**
      * Request the current satellite modem state.
      * The satellite service should report the current satellite modem state via
      * ISatelliteListener#onSatelliteModemStateChanged.
      *
-     * @param errorCallback The callback to receive the error code result of the operation.
-     *                      This must only be sent when the result is not SatelliteError#ERROR_NONE.
+     * @param resultCallback The callback to receive the error code result of the operation.
+     *                       This must only be sent when the error is not SatelliteError#ERROR_NONE.
      * @param callback If the result is SatelliteError#ERROR_NONE, the callback to receive
      *                 the current satellite modem state.
      *
@@ -346,14 +327,14 @@
      *   SatelliteError:REQUEST_NOT_SUPPORTED
      *   SatelliteError:NO_RESOURCES
      */
-    void requestSatelliteModemState(in IIntegerConsumer errorCallback,
+    void requestSatelliteModemState(in IIntegerConsumer resultCallback,
             in IIntegerConsumer callback);
 
     /**
      * Request to get whether satellite communication is allowed for the current location.
      *
-     * @param errorCallback The callback to receive the error code result of the operation.
-     *                      This must only be sent when the result is not SatelliteError#ERROR_NONE.
+     * @param resultCallback The callback to receive the error code result of the operation.
+     *                       This must only be sent when the error is not SatelliteError#ERROR_NONE.
      * @param callback If the result is SatelliteError#ERROR_NONE, the callback to receive
      *                 whether satellite communication is allowed for the current location.
      *
@@ -368,15 +349,15 @@
      *   SatelliteError:NO_RESOURCES
      */
     void requestIsSatelliteCommunicationAllowedForCurrentLocation(
-            in IIntegerConsumer errorCallback, in IBooleanConsumer callback);
+            in IIntegerConsumer resultCallback, in IBooleanConsumer callback);
 
     /**
      * Request to get the time after which the satellite will be visible. This is an int
      * representing the duration in seconds after which the satellite will be visible.
      * This will return 0 if the satellite is currently visible.
      *
-     * @param errorCallback The callback to receive the error code result of the operation.
-     *                      This must only be sent when the result is not SatelliteError#ERROR_NONE.
+     * @param resultCallback The callback to receive the error code result of the operation.
+     *                       This must only be sent when the error is not SatelliteError#ERROR_NONE.
      * @param callback If the result is SatelliteError#ERROR_NONE, the callback to receive
      *                 the time after which the satellite will be visible.
      *
@@ -390,6 +371,6 @@
      *   SatelliteError:REQUEST_NOT_SUPPORTED
      *   SatelliteError:NO_RESOURCES
      */
-    void requestTimeForNextSatelliteVisibility(in IIntegerConsumer errorCallback,
+    void requestTimeForNextSatelliteVisibility(in IIntegerConsumer resultCallback,
             in IIntegerConsumer callback);
 }
diff --git a/telephony/java/android/telephony/satellite/stub/ISatelliteListener.aidl b/telephony/java/android/telephony/satellite/stub/ISatelliteListener.aidl
index d966868..5e69215 100644
--- a/telephony/java/android/telephony/satellite/stub/ISatelliteListener.aidl
+++ b/telephony/java/android/telephony/satellite/stub/ISatelliteListener.aidl
@@ -43,10 +43,8 @@
 
     /**
      * Indicates that the satellite has pending datagrams for the device to be pulled.
-     *
-     * @param count Number of pending datagrams.
      */
-    void onPendingDatagramCount(in int count);
+    void onPendingDatagrams();
 
     /**
      * Indicates that the satellite pointing input has changed.
@@ -61,11 +59,4 @@
      * @param state The current satellite modem state.
      */
     void onSatelliteModemStateChanged(in SatelliteModemState state);
-
-    /**
-     * Indicates that the satellite radio technology has changed.
-     *
-     * @param technology The current satellite radio technology.
-     */
-    void onSatelliteRadioTechnologyChanged(in NTRadioTechnology technology);
 }
diff --git a/telephony/java/android/telephony/satellite/stub/PointingInfo.aidl b/telephony/java/android/telephony/satellite/stub/PointingInfo.aidl
index 83392dd..52a36d8 100644
--- a/telephony/java/android/telephony/satellite/stub/PointingInfo.aidl
+++ b/telephony/java/android/telephony/satellite/stub/PointingInfo.aidl
@@ -29,21 +29,4 @@
      * Satellite elevation in degrees.
      */
     float satelliteElevation;
-
-    /**
-     * Antenna azimuth in degrees.
-     */
-    float antennaAzimuth;
-
-    /**
-     * Angle of rotation about the x axis. This value represents the angle between a plane
-     * parallel to the device's screen and a plane parallel to the ground.
-     */
-    float antennaPitch;
-
-    /**
-     * Angle of rotation about the y axis. This value represents the angle between a plane
-     * perpendicular to the device's screen and a plane parallel to the ground.
-     */
-    float antennaRoll;
 }
diff --git a/telephony/java/android/telephony/satellite/stub/SatelliteCapabilities.aidl b/telephony/java/android/telephony/satellite/stub/SatelliteCapabilities.aidl
index 10c2ea3..cd69da1 100644
--- a/telephony/java/android/telephony/satellite/stub/SatelliteCapabilities.aidl
+++ b/telephony/java/android/telephony/satellite/stub/SatelliteCapabilities.aidl
@@ -28,18 +28,12 @@
     NTRadioTechnology[] supportedRadioTechnologies;
 
     /**
-     * Whether satellite modem is always on.
-     * This indicates the power impact of keeping it on is very minimal.
-     */
-    boolean isAlwaysOn;
-
-    /**
      * Whether UE needs to point to a satellite to send and receive data.
      */
-    boolean needsPointingToSatellite;
+    boolean isPointingRequired;
 
     /**
-     * Whether UE needs a separate SIM profile to communicate with the satellite network.
+     * The maximum number of bytes per datagram that can be sent over satellite.
      */
-    boolean needsSeparateSimProfile;
+    int maxBytesPerOutgoingDatagram;
 }
diff --git a/telephony/java/android/telephony/satellite/stub/SatelliteImplBase.java b/telephony/java/android/telephony/satellite/stub/SatelliteImplBase.java
index 711dcbe..debb394 100644
--- a/telephony/java/android/telephony/satellite/stub/SatelliteImplBase.java
+++ b/telephony/java/android/telephony/satellite/stub/SatelliteImplBase.java
@@ -70,20 +70,21 @@
         }
 
         @Override
-        public void requestSatelliteListeningEnabled(boolean enable, boolean isDemoMode,
-                int timeout, IIntegerConsumer errorCallback) throws RemoteException {
+        public void requestSatelliteListeningEnabled(boolean enable, int timeout,
+                IIntegerConsumer errorCallback) throws RemoteException {
             executeMethodAsync(
                     () -> SatelliteImplBase.this
-                            .requestSatelliteListeningEnabled(
-                                    enable, isDemoMode, timeout, errorCallback),
+                            .requestSatelliteListeningEnabled(enable, timeout, errorCallback),
                     "requestSatelliteListeningEnabled");
         }
 
         @Override
-        public void requestSatelliteEnabled(boolean enable, IIntegerConsumer errorCallback)
-                throws RemoteException {
+        public void requestSatelliteEnabled(boolean enableSatellite, boolean enableDemoMode,
+                IIntegerConsumer errorCallback) throws RemoteException {
             executeMethodAsync(
-                    () -> SatelliteImplBase.this.requestSatelliteEnabled(enable, errorCallback),
+                    () -> SatelliteImplBase.this
+                            .requestSatelliteEnabled(
+                                    enableSatellite, enableDemoMode, errorCallback),
                     "requestSatelliteEnabled");
         }
 
@@ -131,19 +132,11 @@
         }
 
         @Override
-        public void requestMaxCharactersPerMOTextMessage(IIntegerConsumer errorCallback,
-                IIntegerConsumer callback) throws RemoteException {
+        public void provisionSatelliteService(String token, String regionId,
+                IIntegerConsumer errorCallback) throws RemoteException {
             executeMethodAsync(
                     () -> SatelliteImplBase.this
-                            .requestMaxCharactersPerMOTextMessage(errorCallback, callback),
-                    "requestMaxCharactersPerMOTextMessage");
-        }
-
-        @Override
-        public void provisionSatelliteService(String token, IIntegerConsumer errorCallback)
-                throws RemoteException {
-            executeMethodAsync(
-                    () -> SatelliteImplBase.this.provisionSatelliteService(token, errorCallback),
+                            .provisionSatelliteService(token, regionId, errorCallback),
                     "provisionSatelliteService");
         }
 
@@ -173,12 +166,11 @@
         }
 
         @Override
-        public void sendSatelliteDatagram(SatelliteDatagram datagram, boolean isDemoMode,
-                boolean isEmergency, IIntegerConsumer errorCallback) throws RemoteException {
+        public void sendSatelliteDatagram(SatelliteDatagram datagram, boolean isEmergency,
+                IIntegerConsumer errorCallback) throws RemoteException {
             executeMethodAsync(
                     () -> SatelliteImplBase.this
-                            .sendSatelliteDatagram(
-                                    datagram, isDemoMode, isEmergency, errorCallback),
+                            .sendSatelliteDatagram(datagram, isEmergency, errorCallback),
                     "sendSatelliteDatagram");
         }
 
@@ -249,7 +241,6 @@
      * Listening mode allows the satellite service to listen for incoming pages.
      *
      * @param enable True to enable satellite listening mode and false to disable.
-     * @param isDemoMode Whether demo mode is enabled.
      * @param timeout How long the satellite modem should wait for the next incoming page before
      *                disabling listening mode.
      * @param errorCallback The callback to receive the error code result of the operation.
@@ -264,17 +255,18 @@
      *   SatelliteError:REQUEST_NOT_SUPPORTED
      *   SatelliteError:NO_RESOURCES
      */
-    public void requestSatelliteListeningEnabled(boolean enable, boolean isDemoMode, int timeout,
+    public void requestSatelliteListeningEnabled(boolean enable, int timeout,
             @NonNull IIntegerConsumer errorCallback) {
         // stub implementation
     }
 
     /**
-     * Request to enable or disable the satellite modem. If the satellite modem is enabled,
-     * this will also disable the cellular modem, and if the satellite modem is disabled,
-     * this will also re-enable the cellular modem.
+     * Request to enable or disable the satellite modem and demo mode. If the satellite modem is
+     * enabled, this may also disable the cellular modem, and if the satellite modem is disabled,
+     * this may also re-enable the cellular modem.
      *
-     * @param enable True to enable the satellite modem and false to disable.
+     * @param enableSatellite True to enable the satellite modem and false to disable.
+     * @param enableDemoMode True to enable demo mode and false to disable.
      * @param errorCallback The callback to receive the error code result of the operation.
      *
      * Valid error codes returned:
@@ -287,7 +279,8 @@
      *   SatelliteError:REQUEST_NOT_SUPPORTED
      *   SatelliteError:NO_RESOURCES
      */
-    public void requestSatelliteEnabled(boolean enable, @NonNull IIntegerConsumer errorCallback) {
+    public void requestSatelliteEnabled(boolean enableSatellite, boolean enableDemoMode,
+            @NonNull IIntegerConsumer errorCallback) {
         // stub implementation
     }
 
@@ -402,35 +395,13 @@
     }
 
     /**
-     * Request to get the maximum number of characters per MO text message on satellite.
-     *
-     * @param errorCallback The callback to receive the error code result of the operation.
-     *                      This must only be sent when the result is not SatelliteError#ERROR_NONE.
-     * @param callback If the result is SatelliteError#ERROR_NONE, the callback to receive
-     *                 the maximum number of characters per MO text message on satellite.
-     *
-     * Valid error codes returned:
-     *   SatelliteError:ERROR_NONE
-     *   SatelliteError:SERVICE_ERROR
-     *   SatelliteError:MODEM_ERROR
-     *   SatelliteError:INVALID_MODEM_STATE
-     *   SatelliteError:INVALID_ARGUMENTS
-     *   SatelliteError:RADIO_NOT_AVAILABLE
-     *   SatelliteError:REQUEST_NOT_SUPPORTED
-     *   SatelliteError:NO_RESOURCES
-     */
-    public void requestMaxCharactersPerMOTextMessage(@NonNull IIntegerConsumer errorCallback,
-            @NonNull IIntegerConsumer callback) {
-        // stub implementation
-    }
-
-    /**
      * Provision the device with a satellite provider.
      * This is needed if the provider allows dynamic registration.
      * Once provisioned, ISatelliteListener#onSatelliteProvisionStateChanged should report true.
      *
      * @param token The token to be used as a unique identifier for provisioning with satellite
      *              gateway.
+     * @param regionId The region ID for the device's current location.
      * @param errorCallback The callback to receive the error code result of the operation.
      *
      * Valid error codes returned:
@@ -446,7 +417,7 @@
      *   SatelliteError:REQUEST_ABORTED
      *   SatelliteError:NETWORK_TIMEOUT
      */
-    public void provisionSatelliteService(@NonNull String token,
+    public void provisionSatelliteService(@NonNull String token, @NonNull String regionId,
             @NonNull IIntegerConsumer errorCallback) {
         // stub implementation
     }
@@ -530,7 +501,6 @@
      * Send datagram over satellite.
      *
      * @param datagram Datagram to send in byte format.
-     * @param isDemoMode Whether demo mode is enabled.
      * @param isEmergency Whether this is an emergency datagram.
      * @param errorCallback The callback to receive the error code result of the operation.
      *
@@ -550,8 +520,8 @@
      *   SatelliteError:SATELLITE_NOT_REACHABLE
      *   SatelliteError:NOT_AUTHORIZED
      */
-    public void sendSatelliteDatagram(@NonNull SatelliteDatagram datagram, boolean isDemoMode,
-            boolean isEmergency, @NonNull IIntegerConsumer errorCallback) {
+    public void sendSatelliteDatagram(@NonNull SatelliteDatagram datagram, boolean isEmergency,
+            @NonNull IIntegerConsumer errorCallback) {
         // stub implementation
     }
 
diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl
index 323fa6f..d0de3ac 100644
--- a/telephony/java/com/android/internal/telephony/ITelephony.aidl
+++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl
@@ -68,7 +68,7 @@
 import android.telephony.ims.aidl.IImsRegistrationCallback;
 import android.telephony.ims.aidl.IRcsConfigCallback;
 import android.telephony.satellite.ISatelliteDatagramCallback;
-import android.telephony.satellite.ISatellitePositionUpdateCallback;
+import android.telephony.satellite.ISatelliteTransmissionUpdateCallback;
 import android.telephony.satellite.ISatelliteProvisionStateCallback;
 import android.telephony.satellite.ISatelliteStateCallback;
 import android.telephony.satellite.SatelliteCapabilities;
@@ -2726,11 +2726,13 @@
      *
      * @param subId The subId of the subscription to enable or disable the satellite modem for.
      * @param enable True to enable the satellite modem and false to disable.
-     * @param callback The callback to get the error code of the request.
+     * @param isDemoModeEnabled True if demo mode is enabled and false otherwise.
+     * @param callback The callback to get the result of the request.
      */
     @JavaPassthrough(annotation="@android.annotation.RequiresPermission("
             + "android.Manifest.permission.SATELLITE_COMMUNICATION)")
-    void requestSatelliteEnabled(int subId, boolean enable, in IIntegerConsumer callback);
+    void requestSatelliteEnabled(int subId, boolean enable, boolean isDemoModeEnabled,
+            in IIntegerConsumer callback);
 
     /**
      * Request to get whether the satellite modem is enabled.
@@ -2744,17 +2746,6 @@
     void requestIsSatelliteEnabled(int subId, in ResultReceiver receiver);
 
     /**
-     * Request to enable or disable the satellite service demo mode.
-     *
-     * @param subId The subId of the subscription to enable or disable the satellite demo mode for.
-     * @param enable True to enable the satellite demo mode and false to disable.
-     * @param callback The callback to get the error code of the request.
-     */
-    @JavaPassthrough(annotation="@android.annotation.RequiresPermission("
-            + "android.Manifest.permission.SATELLITE_COMMUNICATION)")
-    void requestSatelliteDemoModeEnabled(int subId, boolean enable, in IIntegerConsumer callback);
-
-    /**
      * Request to get whether the satellite service demo mode is enabled.
      *
      * @param subId The subId of the subscription to request whether the satellite demo mode is
@@ -2764,7 +2755,7 @@
      */
     @JavaPassthrough(annotation="@android.annotation.RequiresPermission("
             + "android.Manifest.permission.SATELLITE_COMMUNICATION)")
-    void requestIsSatelliteDemoModeEnabled(int subId, in ResultReceiver receiver);
+    void requestIsDemoModeEnabled(int subId, in ResultReceiver receiver);
 
     /**
      * Request to get whether the satellite service is supported on the device.
@@ -2787,39 +2778,28 @@
     void requestSatelliteCapabilities(int subId, in ResultReceiver receiver);
 
     /**
-     * Start receiving satellite pointing updates.
+     * Start receiving satellite transmission updates.
      *
-     * @param subId The subId of the subscription to stop satellite position updates for.
-     * @param errorCallback The callback to get the error code of the request.
-     * @param callback The callback to handle position updates.
+     * @param subId The subId of the subscription to stop satellite transmission updates for.
+     * @param resultCallback The callback to get the result of the request.
+     * @param callback The callback to handle transmission updates.
      */
     @JavaPassthrough(annotation="@android.annotation.RequiresPermission("
             + "android.Manifest.permission.SATELLITE_COMMUNICATION)")
-    void startSatellitePositionUpdates(int subId, in IIntegerConsumer errorCallback,
-            in ISatellitePositionUpdateCallback callback);
+    void startSatelliteTransmissionUpdates(int subId, in IIntegerConsumer resultCallback,
+            in ISatelliteTransmissionUpdateCallback callback);
 
     /**
-     * Stop receiving satellite pointing updates.
+     * Stop receiving satellite transmission updates.
      *
-     * @param subId The subId of the subscritpion to stop satellite position updates for.
-     * @param errorCallback The callback to get the error code of the request.
-     * @param callback The callback that was passed to startSatellitePositionUpdates.
+     * @param subId The subId of the subscritpion to stop satellite transmission updates for.
+     * @param resultCallback The callback to get the result of the request.
+     * @param callback The callback that was passed to startSatelliteTransmissionUpdates.
      */
     @JavaPassthrough(annotation="@android.annotation.RequiresPermission("
             + "android.Manifest.permission.SATELLITE_COMMUNICATION)")
-    void stopSatellitePositionUpdates(int subId, in IIntegerConsumer errorCallback,
-            in ISatellitePositionUpdateCallback callback);
-
-    /**
-     * Request to get the maximum number of bytes per datagram that can be sent to satellite.
-     *
-     * @param subId The subId of the subscription to get the maximum number of characters for.
-     * @param receiver Result receiver to get the error code of the request and the requested
-     *                 maximum number of bytes per datagram that can be sent to satellite.
-     */
-    @JavaPassthrough(annotation="@android.annotation.RequiresPermission("
-            + "android.Manifest.permission.SATELLITE_COMMUNICATION)")
-    void requestMaxSizePerSendingDatagram(int subId, in ResultReceiver receiver);
+    void stopSatelliteTransmissionUpdates(int subId, in IIntegerConsumer resultCallback,
+            in ISatelliteTransmissionUpdateCallback callback);
 
     /**
      * Register the subscription with a satellite provider.
@@ -2828,13 +2808,14 @@
      * @param subId The subId of the subscription to be provisioned.
      * @param token The token to be used as a unique identifier for provisioning with satellite
      *              gateway.
-     * @param callback The callback to get the error code of the request.
+     * @param regionId The region ID for the device's current location.
+     * @param callback The callback to get the result of the request.
      *
      * @return The signal transport used by callers to cancel the provision request.
      */
     @JavaPassthrough(annotation="@android.annotation.RequiresPermission("
             + "android.Manifest.permission.SATELLITE_COMMUNICATION)")
-    ICancellationSignal provisionSatelliteService(int subId, in String token,
+    ICancellationSignal provisionSatelliteService(int subId, in String token, in String regionId,
             in IIntegerConsumer callback);
 
     /**
@@ -2846,7 +2827,7 @@
      *
      * @param subId The subId of the subscription to be deprovisioned.
      * @param token The token of the device/subscription to be deprovisioned.
-     * @param callback The callback to get the error code of the request.
+     * @param callback The callback to get the result of the request.
      */
     @JavaPassthrough(annotation="@android.annotation.RequiresPermission("
             + "android.Manifest.permission.SATELLITE_COMMUNICATION)")
@@ -2916,15 +2897,13 @@
      * Register to receive incoming datagrams over satellite.
      *
      * @param subId The subId of the subscription to register for incoming satellite datagrams.
-     * @param datagramType Type of datagram.
      * @param callback The callback to handle the incoming datagrams.
      *
      * @return The {@link SatelliteError} result of the operation.
      */
     @JavaPassthrough(annotation="@android.annotation.RequiresPermission("
             + "android.Manifest.permission.SATELLITE_COMMUNICATION)")
-    int registerForSatelliteDatagram(
-            int subId, int datagramType, ISatelliteDatagramCallback callback);
+    int registerForSatelliteDatagram(int subId, ISatelliteDatagramCallback callback);
 
    /**
      * Unregister to stop receiving incoming datagrams over satellite.
@@ -2941,7 +2920,7 @@
     * Poll pending satellite datagrams over satellite.
     *
     * @param subId The subId of the subscription used for receiving datagrams.
-    * @param callback The callback to get the error code of the request.
+    * @param callback The callback to get the result of the request.
     */
     @JavaPassthrough(annotation="@android.annotation.RequiresPermission("
                 + "android.Manifest.permission.SATELLITE_COMMUNICATION)")
@@ -2955,7 +2934,7 @@
     * @param datagram Datagram to send over satellite.
     * @param needFullScreenPointingUI this is used to indicate pointingUI app to open in
     *                                 full screen mode.
-    * @param callback The callback to get the error code of the request.
+    * @param callback The callback to get the result of the request.
     */
     @JavaPassthrough(annotation="@android.annotation.RequiresPermission("
             + "android.Manifest.permission.SATELLITE_COMMUNICATION)")
diff --git a/tests/EnforcePermission/Android.bp b/tests/EnforcePermission/Android.bp
new file mode 100644
index 0000000..719a898
--- /dev/null
+++ b/tests/EnforcePermission/Android.bp
@@ -0,0 +1,22 @@
+// 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 {
+    default_applicable_licenses: ["frameworks_base_license"],
+}
+
+filegroup {
+    name: "frameworks-enforce-permission-test-aidl",
+    srcs: ["aidl/**/*.aidl"],
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardBouncerModel.kt b/tests/EnforcePermission/aidl/android/tests/enforcepermission/INested.aidl
similarity index 60%
copy from packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardBouncerModel.kt
copy to tests/EnforcePermission/aidl/android/tests/enforcepermission/INested.aidl
index ad783da..1eb773d 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardBouncerModel.kt
+++ b/tests/EnforcePermission/aidl/android/tests/enforcepermission/INested.aidl
@@ -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.
@@ -11,14 +11,15 @@
  * 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
+ * limitations under the License.
  */
 
-package com.android.systemui.keyguard.shared.model
+package android.tests.enforcepermission;
 
-/** Models the state of the lock-screen bouncer */
-data class KeyguardBouncerModel(
-    val promptReason: Int = 0,
-    val errorMessage: CharSequence? = null,
-    val expansionAmount: Float = 0f,
-)
+interface INested {
+    @EnforcePermission("ACCESS_NETWORK_STATE")
+    void ProtectedByAccessNetworkState();
+
+    @EnforcePermission("READ_SYNC_SETTINGS")
+    void ProtectedByReadSyncSettings();
+}
diff --git a/tests/EnforcePermission/aidl/android/tests/enforcepermission/IProtected.aidl b/tests/EnforcePermission/aidl/android/tests/enforcepermission/IProtected.aidl
new file mode 100644
index 0000000..18e3aec
--- /dev/null
+++ b/tests/EnforcePermission/aidl/android/tests/enforcepermission/IProtected.aidl
@@ -0,0 +1,34 @@
+/*
+ * 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.tests.enforcepermission;
+
+interface IProtected {
+    @EnforcePermission("INTERNET")
+    void ProtectedByInternet();
+
+    @EnforcePermission("VIBRATE")
+    void ProtectedByVibrate();
+
+    @EnforcePermission("INTERNET")
+    void ProtectedByInternetAndVibrateImplicitly();
+
+    @EnforcePermission("INTERNET")
+    void ProtectedByInternetAndAccessNetworkStateImplicitly();
+
+    @EnforcePermission("INTERNET")
+    void ProtectedByInternetAndReadSyncSettingsImplicitly();
+}
diff --git a/tests/EnforcePermission/service-app/Android.bp b/tests/EnforcePermission/service-app/Android.bp
new file mode 100644
index 0000000..a4ac1d7
--- /dev/null
+++ b/tests/EnforcePermission/service-app/Android.bp
@@ -0,0 +1,32 @@
+// 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: "EnforcePermissionTestHelper",
+    srcs: [
+        "src/**/*.java",
+        ":frameworks-enforce-permission-test-aidl",
+    ],
+    platform_apis: true,
+    certificate: "platform",
+}
diff --git a/services/tests/PackageManagerServiceTests/apks/install_uses_sdk/AndroidManifest-r1000.xml b/tests/EnforcePermission/service-app/AndroidManifest.xml
similarity index 64%
copy from services/tests/PackageManagerServiceTests/apks/install_uses_sdk/AndroidManifest-r1000.xml
copy to tests/EnforcePermission/service-app/AndroidManifest.xml
index 9bf9254..ddafe15 100644
--- a/services/tests/PackageManagerServiceTests/apks/install_uses_sdk/AndroidManifest-r1000.xml
+++ b/tests/EnforcePermission/service-app/AndroidManifest.xml
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2010 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.
@@ -14,13 +14,14 @@
      limitations under the License.
 -->
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-        package="com.android.frameworks.servicestests.install_uses_sdk">
-
-    <uses-sdk android:minSdkVersion="4" android:targetSdkVersion="29">
-        <!-- This will fail to install, because minExtensionVersion is not met -->
-        <extension-sdk android:sdkVersion="30" android:minExtensionVersion="1000" />
-    </uses-sdk>
-
+  package="android.tests.enforcepermission.service">
     <application>
+        <service
+          android:name=".TestService"
+          android:exported="true" />
+
+        <service
+          android:name=".NestedTestService"
+          android:exported="true" />
     </application>
 </manifest>
diff --git a/tests/EnforcePermission/service-app/src/android/tests/enforcepermission/service/NestedTestService.java b/tests/EnforcePermission/service-app/src/android/tests/enforcepermission/service/NestedTestService.java
new file mode 100644
index 0000000..7879a12
--- /dev/null
+++ b/tests/EnforcePermission/service-app/src/android/tests/enforcepermission/service/NestedTestService.java
@@ -0,0 +1,48 @@
+/**
+ * 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.tests.enforcepermission.service;
+
+import android.annotation.EnforcePermission;
+import android.app.Service;
+import android.content.Intent;
+import android.os.IBinder;
+import android.tests.enforcepermission.INested;
+import android.util.Log;
+
+public class NestedTestService extends Service {
+    private static final String TAG = "EnforcePermission.NestedTestService";
+
+    @Override
+    public IBinder onBind(Intent intent) {
+        Log.i(TAG, "onBind");
+        return mBinder;
+    }
+
+    private final INested.Stub mBinder = new INested.Stub() {
+        @Override
+        @EnforcePermission(android.Manifest.permission.ACCESS_NETWORK_STATE)
+        public void ProtectedByAccessNetworkState() {
+            ProtectedByAccessNetworkState_enforcePermission();
+        }
+
+        @Override
+        @EnforcePermission(android.Manifest.permission.READ_SYNC_SETTINGS)
+        public void ProtectedByReadSyncSettings() {
+            ProtectedByReadSyncSettings_enforcePermission();
+        }
+    };
+}
diff --git a/tests/EnforcePermission/service-app/src/android/tests/enforcepermission/service/TestService.java b/tests/EnforcePermission/service-app/src/android/tests/enforcepermission/service/TestService.java
new file mode 100644
index 0000000..e9b897d
--- /dev/null
+++ b/tests/EnforcePermission/service-app/src/android/tests/enforcepermission/service/TestService.java
@@ -0,0 +1,119 @@
+/**
+ * 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.tests.enforcepermission.service;
+
+import android.annotation.EnforcePermission;
+import android.app.Service;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.tests.enforcepermission.INested;
+import android.tests.enforcepermission.IProtected;
+import android.util.Log;
+
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+public class TestService extends Service {
+
+    private static final String TAG = "EnforcePermission.TestService";
+    private volatile ServiceConnection mNestedServiceConnection;
+
+    @Override
+    public void onCreate() {
+        mNestedServiceConnection = new ServiceConnection();
+        Intent intent = new Intent(this, NestedTestService.class);
+        boolean bound = bindService(intent, mNestedServiceConnection, Context.BIND_AUTO_CREATE);
+        if (!bound) {
+            Log.wtf(TAG, "bindService() on NestedTestService failed");
+        }
+    }
+
+    @Override
+    public void onDestroy() {
+        unbindService(mNestedServiceConnection);
+    }
+
+    private static final class ServiceConnection implements android.content.ServiceConnection {
+        private volatile CompletableFuture<INested> mFuture = new CompletableFuture<>();
+
+        public INested get() {
+            try {
+                return mFuture.get(1, TimeUnit.SECONDS);
+            } catch (ExecutionException | InterruptedException | TimeoutException e) {
+                throw new RuntimeException("Unable to reach NestedTestService: " + e.getMessage());
+            }
+        }
+
+        public void onServiceConnected(ComponentName className, IBinder service) {
+            mFuture.complete(INested.Stub.asInterface(service));
+        }
+
+        public void onServiceDisconnected(ComponentName className) {
+            mFuture = new CompletableFuture<>();
+        }
+    };
+
+
+    @Override
+    public IBinder onBind(Intent intent) {
+        return mBinder;
+    }
+
+    private final IProtected.Stub mBinder = new IProtected.Stub() {
+        @Override
+        @EnforcePermission(android.Manifest.permission.INTERNET)
+        public void ProtectedByInternet() {
+            ProtectedByInternet_enforcePermission();
+        }
+
+        @Override
+        @EnforcePermission(android.Manifest.permission.VIBRATE)
+        public void ProtectedByVibrate() {
+            ProtectedByVibrate_enforcePermission();
+        }
+
+        @Override
+        @EnforcePermission(android.Manifest.permission.INTERNET)
+        public void ProtectedByInternetAndVibrateImplicitly() {
+            ProtectedByInternetAndVibrateImplicitly_enforcePermission();
+
+            ProtectedByVibrate();
+        }
+
+        @Override
+        @EnforcePermission(android.Manifest.permission.INTERNET)
+        public void ProtectedByInternetAndAccessNetworkStateImplicitly() throws RemoteException {
+            ProtectedByInternetAndAccessNetworkStateImplicitly_enforcePermission();
+
+            mNestedServiceConnection.get().ProtectedByAccessNetworkState();
+
+        }
+
+        @Override
+        @EnforcePermission(android.Manifest.permission.INTERNET)
+        public void ProtectedByInternetAndReadSyncSettingsImplicitly() throws RemoteException {
+            ProtectedByInternetAndReadSyncSettingsImplicitly_enforcePermission();
+
+            mNestedServiceConnection.get().ProtectedByReadSyncSettings();
+        }
+    };
+}
diff --git a/tests/EnforcePermission/test-app/Android.bp b/tests/EnforcePermission/test-app/Android.bp
new file mode 100644
index 0000000..305ed8f
--- /dev/null
+++ b/tests/EnforcePermission/test-app/Android.bp
@@ -0,0 +1,38 @@
+// 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 {
+    default_applicable_licenses: ["frameworks_base_license"],
+}
+
+android_test {
+    name: "EnforcePermissionTests",
+    srcs: [
+        "src/**/*.java",
+        ":frameworks-enforce-permission-test-aidl",
+    ],
+    static_libs: [
+        "androidx.test.rules",
+    ],
+    libs: [
+        "android.test.base",
+        "android.test.runner",
+    ],
+    data: [
+        ":EnforcePermissionTestHelper",
+    ],
+    platform_apis: true,
+    certificate: "platform",
+    test_suites: ["general-tests"],
+}
diff --git a/tests/EnforcePermission/test-app/AndroidManifest.xml b/tests/EnforcePermission/test-app/AndroidManifest.xml
new file mode 100644
index 0000000..4a0c6a8
--- /dev/null
+++ b/tests/EnforcePermission/test-app/AndroidManifest.xml
@@ -0,0 +1,32 @@
+<?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.tests.enforcepermission.tests">
+
+    <!-- Expected for the tests (not actually used) -->
+    <uses-permission android:name="android.permission.INTERNET" />
+    <uses-permission android:name="android.permission.READ_SYNC_SETTINGS" />
+
+    <queries>
+        <package android:name="android.tests.enforcepermission.service" />
+    </queries>
+
+    <application>
+        <uses-library android:name="android.test.runner" />
+    </application>
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+                     android:targetPackage="android.tests.enforcepermission.tests"/>
+</manifest>
diff --git a/tests/EnforcePermission/test-app/AndroidTest.xml b/tests/EnforcePermission/test-app/AndroidTest.xml
new file mode 100644
index 0000000..120381a
--- /dev/null
+++ b/tests/EnforcePermission/test-app/AndroidTest.xml
@@ -0,0 +1,28 @@
+<?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="Runs EnforcePermission End-to-End Tests">
+    <target_preparer class="com.android.tradefed.targetprep.TestAppInstallSetup">
+      <option name="test-file-name" value="EnforcePermissionTestHelper.apk"/>
+      <option name="test-file-name" value="EnforcePermissionTests.apk"/>
+      <option name="cleanup-apks" value="true" />
+    </target_preparer>
+
+    <option name="test-tag" value="EnforcePermissionTests"/>
+    <test class="com.android.tradefed.testtype.AndroidJUnitTest">
+        <option name="package" value="android.tests.enforcepermission.tests"/>
+        <option name="runner" value="androidx.test.runner.AndroidJUnitRunner"/>
+    </test>
+</configuration>
diff --git a/tests/EnforcePermission/test-app/src/android/tests/enforcepermission/tests/ServiceTest.java b/tests/EnforcePermission/test-app/src/android/tests/enforcepermission/tests/ServiceTest.java
new file mode 100644
index 0000000..d2a4a03
--- /dev/null
+++ b/tests/EnforcePermission/test-app/src/android/tests/enforcepermission/tests/ServiceTest.java
@@ -0,0 +1,129 @@
+/**
+ * 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.tests.enforcepermission.tests;
+
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertThrows;
+import static org.junit.Assert.assertTrue;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.tests.enforcepermission.IProtected;
+import android.util.Log;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+@RunWith(AndroidJUnit4.class)
+public class ServiceTest {
+
+    private static final String TAG = "EnforcePermission.Tests";
+    private static final String SERVICE_NAME = "android.tests.enforcepermission.service";
+    private static final int SERVICE_TIMEOUT_SEC = 5;
+
+    private Context mContext;
+    private volatile ServiceConnection mServiceConnection;
+
+    @Before
+    public void bindTestService() throws Exception {
+        Log.d(TAG, "bindTestService");
+        mContext = InstrumentationRegistry.getTargetContext();
+        mServiceConnection = new ServiceConnection();
+        Intent intent = new Intent();
+        intent.setClassName(SERVICE_NAME, SERVICE_NAME + ".TestService");
+        assertTrue(mContext.bindService(intent, mServiceConnection, Context.BIND_AUTO_CREATE));
+    }
+
+    @After
+    public void unbindTestService() throws Exception {
+        mContext.unbindService(mServiceConnection);
+    }
+
+    private static final class ServiceConnection implements android.content.ServiceConnection {
+        private volatile CompletableFuture<IProtected> mFuture = new CompletableFuture<>();
+
+        @Override
+        public void onServiceConnected(ComponentName className, IBinder service) {
+            mFuture.complete(IProtected.Stub.asInterface(service));
+        }
+
+        @Override
+        public void onServiceDisconnected(ComponentName className) {
+            mFuture = new CompletableFuture<>();
+        }
+
+        public IProtected get() {
+            try {
+                return mFuture.get(SERVICE_TIMEOUT_SEC, TimeUnit.SECONDS);
+            } catch (ExecutionException | InterruptedException | TimeoutException e) {
+                throw new RuntimeException("Unable to reach TestService: " + e.toString());
+            }
+        }
+    }
+
+    @Test
+    public void testImmediatePermissionGranted_succeeds()
+            throws RemoteException {
+        mServiceConnection.get().ProtectedByInternet();
+    }
+
+    @Test
+    public void testImmediatePermissionNotGranted_fails()
+            throws RemoteException {
+        final Exception ex = assertThrows(SecurityException.class,
+                () -> mServiceConnection.get().ProtectedByVibrate());
+        assertThat(ex.getMessage(), containsString("VIBRATE"));
+    }
+
+    @Test
+    public void testImmediatePermissionGrantedButImplicitLocalNotGranted_fails()
+            throws RemoteException {
+        final Exception ex = assertThrows(SecurityException.class,
+                () -> mServiceConnection.get().ProtectedByInternetAndVibrateImplicitly());
+        assertThat(ex.getMessage(), containsString("VIBRATE"));
+    }
+
+    @Test
+    public void testImmediatePermissionGrantedButImplicitNestedNotGranted_fails()
+            throws RemoteException {
+        final Exception ex = assertThrows(SecurityException.class,
+                () -> mServiceConnection.get()
+                      .ProtectedByInternetAndAccessNetworkStateImplicitly());
+        assertThat(ex.getMessage(), containsString("ACCESS_NETWORK_STATE"));
+    }
+
+    @Test
+    public void testImmediatePermissionGrantedAndImplicitNestedGranted_succeeds()
+            throws RemoteException {
+        mServiceConnection.get().ProtectedByInternetAndReadSyncSettingsImplicitly();
+    }
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeOnDismissPopupDialogTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeOnDismissPopupDialogTest.kt
index c23cf34..d72f528 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeOnDismissPopupDialogTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeOnDismissPopupDialogTest.kt
@@ -37,7 +37,7 @@
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class CloseImeOnDismissPopupDialogTest(flicker: FlickerTest) : BaseTest(flicker) {
+open class CloseImeOnDismissPopupDialogTest(flicker: FlickerTest) : BaseTest(flicker) {
     private val imeTestApp = ImeEditorPopupDialogAppHelper(instrumentation)
 
     /** {@inheritDoc} */
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeOnStartWhenLaunchingAppCfArmTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeOnDismissPopupDialogTestCfArm.kt
similarity index 68%
copy from tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeOnStartWhenLaunchingAppCfArmTest.kt
copy to tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeOnDismissPopupDialogTestCfArm.kt
index 194c86b..432df20 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeOnStartWhenLaunchingAppCfArmTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeOnDismissPopupDialogTestCfArm.kt
@@ -16,8 +16,10 @@
 
 package com.android.server.wm.flicker.ime
 
+import android.tools.common.Rotation
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
 import android.tools.device.flicker.legacy.FlickerTest
+import android.tools.device.flicker.legacy.FlickerTestFactory
 import org.junit.FixMethodOrder
 import org.junit.runner.RunWith
 import org.junit.runners.MethodSorters
@@ -26,5 +28,15 @@
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class ShowImeOnStartWhenLaunchingAppCfArmTest(flicker: FlickerTest) :
-    ShowImeOnAppStartWhenLaunchingAppTest(flicker)
+class CloseImeOnDismissPopupDialogTestCfArm(flicker: FlickerTest) :
+    CloseImeOnDismissPopupDialogTest(flicker) {
+    companion object {
+        @Parameterized.Parameters(name = "{0}")
+        @JvmStatic
+        fun getParams(): Collection<FlickerTest> {
+            return FlickerTestFactory.nonRotationTests(
+                supportedRotations = listOf(Rotation.ROTATION_0)
+            )
+        }
+    }
+}
\ No newline at end of file
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeOnStartWhenLaunchingAppCfArmTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppCfArmTest.kt
similarity index 93%
rename from tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeOnStartWhenLaunchingAppCfArmTest.kt
rename to tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppCfArmTest.kt
index 194c86b..03f21f9 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeOnStartWhenLaunchingAppCfArmTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppCfArmTest.kt
@@ -26,5 +26,5 @@
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class ShowImeOnStartWhenLaunchingAppCfArmTest(flicker: FlickerTest) :
+class ShowImeOnAppStartWhenLaunchingAppCfArmTest(flicker: FlickerTest) :
     ShowImeOnAppStartWhenLaunchingAppTest(flicker)
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeOnStartWhenLaunchingAppCfArmTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppFromOverviewTestCfArm.kt
similarity index 66%
copy from tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeOnStartWhenLaunchingAppCfArmTest.kt
copy to tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppFromOverviewTestCfArm.kt
index 194c86b..efda0ff 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeOnStartWhenLaunchingAppCfArmTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppFromOverviewTestCfArm.kt
@@ -16,8 +16,10 @@
 
 package com.android.server.wm.flicker.ime
 
+import android.tools.common.Rotation
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
 import android.tools.device.flicker.legacy.FlickerTest
+import android.tools.device.flicker.legacy.FlickerTestFactory
 import org.junit.FixMethodOrder
 import org.junit.runner.RunWith
 import org.junit.runners.MethodSorters
@@ -26,5 +28,16 @@
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class ShowImeOnStartWhenLaunchingAppCfArmTest(flicker: FlickerTest) :
-    ShowImeOnAppStartWhenLaunchingAppTest(flicker)
+class ShowImeOnAppStartWhenLaunchingAppFromOverviewTestCfArm(flicker: FlickerTest) :
+    ShowImeOnAppStartWhenLaunchingAppFromOverviewTest(flicker) {
+
+    companion object {
+        @Parameterized.Parameters(name = "{0}")
+        @JvmStatic
+        fun getParams(): Collection<FlickerTest> {
+            return FlickerTestFactory.nonRotationTests(
+                supportedRotations = listOf(Rotation.ROTATION_0)
+            )
+        }
+    }
+}
\ No newline at end of file
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppFromQuickSwitchTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppFromQuickSwitchTest.kt
index 954f589..daee332 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppFromQuickSwitchTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppFromQuickSwitchTest.kt
@@ -16,13 +16,11 @@
 
 package com.android.server.wm.flicker.ime
 
-import android.platform.test.annotations.FlakyTest
 import android.platform.test.annotations.Presubmit
 import android.tools.common.NavBar
 import android.tools.common.Rotation
 import android.tools.common.datatypes.component.ComponentNameMatcher
 import com.android.server.wm.flicker.helpers.ImeShownOnAppStartHelper
-import android.tools.device.flicker.isShellTransitionsEnabled
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
 import android.tools.device.flicker.legacy.FlickerBuilder
 import android.tools.device.flicker.legacy.FlickerTest
@@ -31,8 +29,6 @@
 import com.android.server.wm.flicker.BaseTest
 import com.android.server.wm.flicker.helpers.SimpleAppHelper
 import com.android.server.wm.flicker.helpers.setRotation
-import org.junit.Assume
-import org.junit.Before
 import org.junit.FixMethodOrder
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -54,11 +50,6 @@
     private val imeTestApp =
         ImeShownOnAppStartHelper(instrumentation, flicker.scenario.startRotation)
 
-    @Before
-    open fun before() {
-        Assume.assumeFalse(isShellTransitionsEnabled)
-    }
-
     /** {@inheritDoc} */
     override val transition: FlickerBuilder.() -> Unit = {
         setup {
@@ -95,7 +86,7 @@
         }
     }
     /** {@inheritDoc} */
-    @FlakyTest(bugId = 265016201)
+    @Presubmit
     @Test
     override fun entireScreenCovered() = super.entireScreenCovered()
 
@@ -115,7 +106,7 @@
         }
     }
 
-    @FlakyTest(bugId = 244414110)
+    @Presubmit
     @Test
     open fun imeLayerIsVisibleWhenSwitchingToImeApp() {
         flicker.assertLayersStart { isVisible(ComponentNameMatcher.IME) }
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppFromQuickSwitchTestShellTransit.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppFromQuickSwitchTestShellTransit.kt
deleted file mode 100644
index a927102..0000000
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppFromQuickSwitchTestShellTransit.kt
+++ /dev/null
@@ -1,69 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.wm.flicker.ime
-
-import android.platform.test.annotations.Presubmit
-import android.tools.device.flicker.isShellTransitionsEnabled
-import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
-import android.tools.device.flicker.legacy.FlickerTest
-import androidx.test.filters.RequiresDevice
-import org.junit.Assume
-import org.junit.Before
-import org.junit.FixMethodOrder
-import org.junit.Ignore
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.MethodSorters
-import org.junit.runners.Parameterized
-
-/**
- * Test IME windows switching with 2-Buttons or gestural navigation. To run this test: `atest
- * FlickerTests:SwitchImeWindowsFromGestureNavTest`
- */
-@RequiresDevice
-@RunWith(Parameterized::class)
-@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class ShowImeOnAppStartWhenLaunchingAppFromQuickSwitchTestShellTransit(flicker: FlickerTest) :
-    ShowImeOnAppStartWhenLaunchingAppFromQuickSwitchTest(flicker) {
-    @Before
-    override fun before() {
-        Assume.assumeTrue(isShellTransitionsEnabled)
-    }
-
-    @Presubmit @Test override fun entireScreenCovered() = super.entireScreenCovered()
-
-    @Presubmit
-    @Test
-    override fun imeLayerIsVisibleWhenSwitchingToImeApp() =
-        super.imeLayerIsVisibleWhenSwitchingToImeApp()
-
-    @Presubmit
-    @Test
-    override fun visibleLayersShownMoreThanOneConsecutiveEntry() =
-        super.visibleLayersShownMoreThanOneConsecutiveEntry()
-
-    @Presubmit
-    @Test
-    override fun visibleWindowsShownMoreThanOneConsecutiveEntry() =
-        super.visibleWindowsShownMoreThanOneConsecutiveEntry()
-
-    /** {@inheritDoc} */
-    @Ignore("Nav bar window becomes invisible during quick switch")
-    @Test
-    override fun navBarWindowIsAlwaysVisible() = super.navBarWindowIsAlwaysVisible()
-}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeWhileDismissingThemedPopupDialogTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeWhileDismissingThemedPopupDialogTest.kt
index 99b9bd2bfc..a57aa5b 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeWhileDismissingThemedPopupDialogTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeWhileDismissingThemedPopupDialogTest.kt
@@ -45,7 +45,7 @@
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class ShowImeWhileDismissingThemedPopupDialogTest(flicker: FlickerTest) : BaseTest(flicker) {
+open class ShowImeWhileDismissingThemedPopupDialogTest(flicker: FlickerTest) : BaseTest(flicker) {
     private val testApp = ImeShownOnAppStartHelper(instrumentation, flicker.scenario.startRotation)
 
     /** {@inheritDoc} */
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeWhileDismissingThemedPopupDialogTestCfArm.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeWhileDismissingThemedPopupDialogTestCfArm.kt
new file mode 100644
index 0000000..cffc05d
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeWhileDismissingThemedPopupDialogTestCfArm.kt
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.flicker.ime
+
+import android.tools.common.Rotation
+import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
+import android.tools.device.flicker.legacy.FlickerTest
+import android.tools.device.flicker.legacy.FlickerTestFactory
+import org.junit.FixMethodOrder
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+class ShowImeWhileDismissingThemedPopupDialogTestCfArm(flicker: FlickerTest) :
+    ShowImeWhileDismissingThemedPopupDialogTest(flicker) {
+    companion object {
+        /**
+         * Creates the test configurations.
+         *
+         * See [FlickerTestFactory.nonRotationTests] for configuring screen orientation and
+         * navigation modes.
+         */
+        @Parameterized.Parameters(name = "{0}")
+        @JvmStatic
+        fun getParams(): Collection<FlickerTest> {
+            return FlickerTestFactory.nonRotationTests(
+                supportedRotations = listOf(Rotation.ROTATION_0)
+            )
+        }
+    }
+}
\ No newline at end of file
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdFromIconCfArm.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdFromIconCfArm.kt
index 46899f3..ccbe74f 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdFromIconCfArm.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdFromIconCfArm.kt
@@ -16,12 +16,14 @@
 
 package com.android.server.wm.flicker.launch
 
+import android.platform.test.annotations.FlakyTest
 import android.tools.common.NavBar
 import android.tools.device.flicker.annotation.FlickerServiceCompatible
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
 import android.tools.device.flicker.legacy.FlickerTest
 import android.tools.device.flicker.legacy.FlickerTestFactory
 import org.junit.FixMethodOrder
+import org.junit.Test
 import org.junit.runner.RunWith
 import org.junit.runners.MethodSorters
 import org.junit.runners.Parameterized
@@ -32,6 +34,12 @@
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
 class OpenAppColdFromIconCfArm(flicker: FlickerTest) : OpenAppColdFromIcon(flicker) {
+    @Test
+    @FlakyTest
+    override fun visibleLayersShownMoreThanOneConsecutiveEntry() {
+        super.visibleLayersShownMoreThanOneConsecutiveEntry()
+    }
+
     companion object {
         /**
          * Creates the test configurations.
@@ -44,7 +52,7 @@
         fun getParams(): Collection<FlickerTest> {
             // TAPL fails on landscape mode b/240916028
             return FlickerTestFactory.nonRotationTests(
-                    supportedNavigationModes = listOf(NavBar.MODE_3BUTTON)
+                supportedNavigationModes = listOf(NavBar.MODE_3BUTTON)
             )
         }
     }
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsBackTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsBackTest.kt
index 78cee3c..eadeef5 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsBackTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsBackTest.kt
@@ -16,11 +16,11 @@
 
 package com.android.server.wm.flicker.quickswitch
 
+import android.platform.test.annotations.FlakyTest
 import android.platform.test.annotations.Presubmit
 import android.tools.common.NavBar
 import android.tools.common.datatypes.Rect
 import android.tools.common.datatypes.component.ComponentNameMatcher
-import android.tools.device.flicker.isShellTransitionsEnabled
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
 import android.tools.device.flicker.legacy.FlickerBuilder
 import android.tools.device.flicker.legacy.FlickerTest
@@ -29,9 +29,8 @@
 import com.android.server.wm.flicker.BaseTest
 import com.android.server.wm.flicker.helpers.NonResizeableAppHelper
 import com.android.server.wm.flicker.helpers.SimpleAppHelper
-import org.junit.Assume
-import org.junit.Before
 import org.junit.FixMethodOrder
+import org.junit.Ignore
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.junit.runners.MethodSorters
@@ -57,11 +56,6 @@
     private val testApp1 = SimpleAppHelper(instrumentation)
     private val testApp2 = NonResizeableAppHelper(instrumentation)
 
-    @Before
-    open fun before() {
-        Assume.assumeFalse(isShellTransitionsEnabled)
-    }
-
     /** {@inheritDoc} */
     override val transition: FlickerBuilder.() -> Unit = {
         setup {
@@ -102,7 +96,7 @@
      * Checks that the transition starts with [testApp2]'s layers filling/covering exactly the
      * entirety of the display.
      */
-    @Presubmit
+    @FlakyTest(bugId = 250520840)
     @Test
     open fun startsWithApp2LayersCoverFullScreen() {
         flicker.assertLayersStart { this.visibleRegion(testApp2).coversExactly(startDisplayBounds) }
@@ -230,6 +224,20 @@
         }
     }
 
+    /** {@inheritDoc} */
+    @Ignore("Nav bar window becomes invisible during quick switch")
+    @Test
+    override fun navBarWindowIsAlwaysVisible() = super.navBarWindowIsAlwaysVisible()
+
+    @FlakyTest(bugId = 246284708)
+    @Test
+    override fun visibleLayersShownMoreThanOneConsecutiveEntry() =
+        super.visibleLayersShownMoreThanOneConsecutiveEntry()
+
+    @FlakyTest(bugId = 250518877)
+    @Test
+    override fun navBarLayerPositionAtStartAndEnd() = super.navBarLayerPositionAtStartAndEnd()
+
     companion object {
         private var startDisplayBounds = Rect.EMPTY
 
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsBackTest_ShellTransit.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsBackTest_ShellTransit.kt
deleted file mode 100644
index 2b69e9b..0000000
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsBackTest_ShellTransit.kt
+++ /dev/null
@@ -1,74 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.wm.flicker.quickswitch
-
-import android.platform.test.annotations.FlakyTest
-import android.tools.device.flicker.isShellTransitionsEnabled
-import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
-import android.tools.device.flicker.legacy.FlickerTest
-import androidx.test.filters.RequiresDevice
-import org.junit.Assume
-import org.junit.Before
-import org.junit.FixMethodOrder
-import org.junit.Ignore
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.MethodSorters
-import org.junit.runners.Parameterized
-
-/**
- * Test quick switching back to previous app from last opened app
- *
- * To run this test: `atest FlickerTests:QuickSwitchBetweenTwoAppsBackTest`
- *
- * Actions:
- * ```
- *     Launch an app [testApp1]
- *     Launch another app [testApp2]
- *     Swipe right from the bottom of the screen to quick switch back to the first app [testApp1]
- * ```
- */
-@RequiresDevice
-@RunWith(Parameterized::class)
-@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class QuickSwitchBetweenTwoAppsBackTest_ShellTransit(flicker: FlickerTest) :
-    QuickSwitchBetweenTwoAppsBackTest(flicker) {
-    @Before
-    override fun before() {
-        Assume.assumeTrue(isShellTransitionsEnabled)
-    }
-
-    /** {@inheritDoc} */
-    @Ignore("Nav bar window becomes invisible during quick switch")
-    @Test
-    override fun navBarWindowIsAlwaysVisible() = super.navBarWindowIsAlwaysVisible()
-
-    /** {@inheritDoc} */
-    @FlakyTest(bugId = 250520840)
-    @Test
-    override fun startsWithApp2LayersCoverFullScreen() = super.startsWithApp2LayersCoverFullScreen()
-
-    @FlakyTest(bugId = 246284708)
-    @Test
-    override fun visibleLayersShownMoreThanOneConsecutiveEntry() =
-        super.visibleLayersShownMoreThanOneConsecutiveEntry()
-
-    @FlakyTest(bugId = 250518877)
-    @Test
-    override fun navBarLayerPositionAtStartAndEnd() = super.navBarLayerPositionAtStartAndEnd()
-}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsForwardTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsForwardTest.kt
index cd7d6fa..1360495 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsForwardTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsForwardTest.kt
@@ -16,11 +16,11 @@
 
 package com.android.server.wm.flicker.quickswitch
 
+import android.platform.test.annotations.FlakyTest
 import android.platform.test.annotations.Presubmit
 import android.tools.common.NavBar
 import android.tools.common.datatypes.Rect
 import android.tools.common.datatypes.component.ComponentNameMatcher
-import android.tools.device.flicker.isShellTransitionsEnabled
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
 import android.tools.device.flicker.legacy.FlickerBuilder
 import android.tools.device.flicker.legacy.FlickerTest
@@ -29,9 +29,8 @@
 import com.android.server.wm.flicker.BaseTest
 import com.android.server.wm.flicker.helpers.NonResizeableAppHelper
 import com.android.server.wm.flicker.helpers.SimpleAppHelper
-import org.junit.Assume
-import org.junit.Before
 import org.junit.FixMethodOrder
+import org.junit.Ignore
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.junit.runners.MethodSorters
@@ -58,11 +57,6 @@
     private val testApp1 = SimpleAppHelper(instrumentation)
     private val testApp2 = NonResizeableAppHelper(instrumentation)
 
-    @Before
-    open fun before() {
-        Assume.assumeFalse(isShellTransitionsEnabled)
-    }
-
     /** {@inheritDoc} */
     override val transition: FlickerBuilder.() -> Unit = {
         setup {
@@ -113,7 +107,7 @@
      * Checks that the transition starts with [testApp1]'s layers filling/covering exactly the
      * entirety of the display.
      */
-    @Presubmit
+    @FlakyTest(bugId = 250522691)
     @Test
     open fun startsWithApp1LayersCoverFullScreen() {
         flicker.assertLayersStart { this.visibleRegion(testApp1).coversExactly(startDisplayBounds) }
@@ -248,6 +242,20 @@
     @Test
     override fun taskBarLayerIsVisibleAtStartAndEnd() = super.taskBarLayerIsVisibleAtStartAndEnd()
 
+    /** {@inheritDoc} */
+    @Ignore("Nav bar window becomes invisible during quick switch")
+    @Test
+    override fun navBarWindowIsAlwaysVisible() = super.navBarWindowIsAlwaysVisible()
+
+    @FlakyTest(bugId = 246284708)
+    @Test
+    override fun visibleLayersShownMoreThanOneConsecutiveEntry() =
+        super.visibleLayersShownMoreThanOneConsecutiveEntry()
+
+    @FlakyTest(bugId = 250518877)
+    @Test
+    override fun navBarLayerPositionAtStartAndEnd() = super.navBarLayerPositionAtStartAndEnd()
+
     companion object {
         private var startDisplayBounds = Rect.EMPTY
 
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsForwardTest_ShellTransit.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsForwardTest_ShellTransit.kt
deleted file mode 100644
index b0d4e27..0000000
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsForwardTest_ShellTransit.kt
+++ /dev/null
@@ -1,74 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.wm.flicker.quickswitch
-
-import android.platform.test.annotations.FlakyTest
-import android.tools.device.flicker.isShellTransitionsEnabled
-import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
-import android.tools.device.flicker.legacy.FlickerTest
-import androidx.test.filters.RequiresDevice
-import org.junit.Assume
-import org.junit.Before
-import org.junit.FixMethodOrder
-import org.junit.Ignore
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.MethodSorters
-import org.junit.runners.Parameterized
-
-/**
- * Test quick switching back to previous app from last opened app
- *
- * To run this test: `atest FlickerTests:QuickSwitchBetweenTwoAppsForwardTest`
- *
- * Actions:
- * ```
- *     Launch an app [testApp1]
- *     Launch another app [testApp2]
- *     Swipe right from the bottom of the screen to quick switch back to the first app [testApp1]
- *     Swipe left from the bottom of the screen to quick switch forward to the second app [testApp2]
- * ```
- */
-@RequiresDevice
-@RunWith(Parameterized::class)
-@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class QuickSwitchBetweenTwoAppsForwardTest_ShellTransit(flicker: FlickerTest) :
-    QuickSwitchBetweenTwoAppsForwardTest(flicker) {
-    @Before
-    override fun before() {
-        Assume.assumeTrue(isShellTransitionsEnabled)
-    }
-
-    /** {@inheritDoc} */
-    @Ignore("Nav bar window becomes invisible during quick switch")
-    @Test
-    override fun navBarWindowIsAlwaysVisible() = super.navBarWindowIsAlwaysVisible()
-
-    @FlakyTest(bugId = 246284708)
-    @Test
-    override fun visibleLayersShownMoreThanOneConsecutiveEntry() =
-        super.visibleLayersShownMoreThanOneConsecutiveEntry()
-
-    @FlakyTest(bugId = 250518877)
-    @Test
-    override fun navBarLayerPositionAtStartAndEnd() = super.navBarLayerPositionAtStartAndEnd()
-
-    @FlakyTest(bugId = 250522691)
-    @Test
-    override fun startsWithApp1LayersCoverFullScreen() = super.startsWithApp1LayersCoverFullScreen()
-}
diff --git a/tests/Internal/src/com/android/internal/app/LocaleStoreTest.java b/tests/Internal/src/com/android/internal/app/LocaleStoreTest.java
index e704cc2..46ebfed 100644
--- a/tests/Internal/src/com/android/internal/app/LocaleStoreTest.java
+++ b/tests/Internal/src/com/android/internal/app/LocaleStoreTest.java
@@ -35,6 +35,7 @@
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.HashMap;
+import java.util.HashSet;
 import java.util.IllformedLocaleException;
 import java.util.List;
 import java.util.Locale;
@@ -141,14 +142,118 @@
 
         HashMap<String, LocaleInfo> result =
                 LocaleStore.convertExplicitLocales(locales, supportedLocale);
-
         assertEquals("en", result.get("en").getId());
         assertEquals("en-US", result.get("en-US").getId());
         assertNull(result.get("en-Latn-US"));
     }
 
+    @Test
+    public void getLevelLocales_languageTier_returnAllSupportLanguages() {
+        LocaleList testSupportedLocales =
+                LocaleList.forLanguageTags(
+                        "en-US,zh-Hant-TW,ja-JP,en-GB,bn-IN-u-nu-arab,ks-Arab-IN,bn-IN");
+
+        Set<String> ignorableLocales = new HashSet<>();
+        ignorableLocales.add("zh-Hant-HK");
+        LocaleInfo parent = null;
+
+        Set<LocaleInfo> localeInfos = LocaleStore.getLevelLocales(
+                null, ignorableLocales, parent, false, testSupportedLocales);
+
+        assertEquals(5, localeInfos.size());
+        localeInfos.forEach(localeInfo -> {
+            assertTrue(localeInfo.getLocale().getCountry().isEmpty());
+        });
+        assertTrue(localeInfos.stream().anyMatch(
+                info -> info.getLocale().toLanguageTag().equals("en")));
+        assertTrue(localeInfos.stream().anyMatch(
+                info -> info.getLocale().toLanguageTag().equals("zh-Hant")));
+        assertTrue(localeInfos.stream().anyMatch(
+                info -> info.getLocale().toLanguageTag().equals("ja")));
+        assertTrue(localeInfos.stream().anyMatch(
+                info -> info.getLocale().toLanguageTag().equals("bn")));
+        assertTrue(localeInfos.stream().anyMatch(
+                info -> info.getLocale().toLanguageTag().equals("ks-Arab")));
+    }
+
+    @Test
+    public void getLevelLocales_regionTierAndParentIsEn_returnEnLocales() {
+        LocaleList testSupportedLocales =
+                LocaleList.forLanguageTags(
+                        "en-US,en-GB,bn-IN-u-nu-arab,ks-Arab-IN,en-ZA,bn-IN");
+        Set<String> ignorableLocales = new HashSet<>();
+        ignorableLocales.add("zh-Hant-HK");
+        LocaleInfo parent = LocaleStore.fromLocale(Locale.forLanguageTag("en"));
+
+        Set<LocaleInfo> localeInfos = LocaleStore.getLevelLocales(
+                null, ignorableLocales, parent, false, testSupportedLocales);
+
+        assertEquals(3, localeInfos.size());
+        localeInfos.forEach(localeInfo -> {
+            assertEquals("en", localeInfo.getLocale().getLanguage());
+        });
+        assertTrue(localeInfos.stream().anyMatch(
+                info -> info.getLocale().toLanguageTag().equals("en-US")));
+        assertTrue(localeInfos.stream().anyMatch(
+                info -> info.getLocale().toLanguageTag().equals("en-GB")));
+        assertTrue(localeInfos.stream().anyMatch(
+                info -> info.getLocale().toLanguageTag().equals("en-ZA")));
+    }
+
+    @Test
+    public void getLevelLocales_numberingTierAndParentIsBnIn_returnBnInLocales() {
+        LocaleList testSupportedLocales =
+                LocaleList.forLanguageTags(
+                        "en-US,zh-Hant-TW,bn-IN-u-nu-arab,ks-Arab-IN,en-ZA,bn-IN,bn-IN-u-nu-adlm");
+        Set<String> ignorableLocales = new HashSet<>();
+        ignorableLocales.add("zh-Hant-HK");
+        LocaleInfo parent = LocaleStore.fromLocale(Locale.forLanguageTag("bn"));
+
+        Set<LocaleInfo> localeInfos = LocaleStore.getLevelLocales(
+                null, ignorableLocales, parent, false, testSupportedLocales);
+
+        assertEquals(1, localeInfos.size());
+        assertEquals("bn-IN", localeInfos.iterator().next().getLocale().toLanguageTag());
+    }
+
+    @Test
+    public void getLevelLocales_regionTierAndParentIsBnInAndIgnoreBn_returnEmpty() {
+        LocaleList testSupportedLocales =
+                LocaleList.forLanguageTags(
+                        "en-US,zh-Hant-TW,bn-IN-u-nu-arab,ks-Arab-IN,en-ZA,bn-IN,bn-IN-u-nu-adlm");
+        Set<String> ignorableLocales = new HashSet<>();
+        ignorableLocales.add("bn-IN");
+        LocaleInfo parent = LocaleStore.fromLocale(Locale.forLanguageTag("bn-IN"));
+
+        Set<LocaleInfo> localeInfos = LocaleStore.getLevelLocales(
+                null, ignorableLocales, parent, false, testSupportedLocales);
+
+        assertEquals(0, localeInfos.size());
+    }
+
+    @Test
+    public void getLevelLocales_regionTierAndParentIsBnIn_returnBnLocaleFamily() {
+        LocaleList testSupportedLocales =
+                LocaleList.forLanguageTags(
+                        "en-US,zh-Hant-TW,bn-IN-u-nu-arab,ks-Arab-IN,en-ZA,bn-IN,bn-IN-u-nu-adlm");
+        Set<String> ignorableLocales = new HashSet<>();
+        ignorableLocales.add("en-US");
+        LocaleInfo parent = LocaleStore.fromLocale(Locale.forLanguageTag("bn-IN"));
+
+        Set<LocaleInfo> localeInfos = LocaleStore.getLevelLocales(
+                null, ignorableLocales, parent, false, testSupportedLocales);
+
+        assertEquals(3, localeInfos.size());
+        assertTrue(localeInfos.stream().anyMatch(
+                info -> info.getLocale().toLanguageTag().equals("bn-IN-u-nu-adlm")));
+        assertTrue(localeInfos.stream().anyMatch(
+                info -> info.getLocale().toLanguageTag().equals("bn-IN-u-nu-arab")));
+        assertTrue(localeInfos.stream().anyMatch(
+                info -> info.getLocale().toLanguageTag().equals("bn-IN")));
+    }
+
     private ArrayList<LocaleInfo> getFakeSupportedLocales() {
-        String[] locales = {"en-US", "zh-Hant-TW", "ja-JP", "en-GB"};
+        String[] locales = {"en-US", "zh-Hant-TW", "ja-JP", "en-GB", "en-US-u-nu-arab"};
         ArrayList<LocaleInfo> supportedLocales = new ArrayList<>();
         for (String localeTag : locales) {
             supportedLocales.add(LocaleStore.fromLocale(Locale.forLanguageTag(localeTag)));